-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A very basic debugger that lets you set breakpoints!
- Loading branch information
Showing
4 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"os" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
func inputContinue(pid int) bool { | ||
sub := false | ||
scanner := bufio.NewScanner(os.Stdin) | ||
fmt.Printf("\n(C)ontinue, (S)tep, set (B)reakpoint or (Q)uit? > ") | ||
for { | ||
scanner.Scan() | ||
input := scanner.Text() | ||
switch strings.ToUpper(input) { | ||
case "C": | ||
return true | ||
case "S": | ||
return false | ||
case "B": | ||
fmt.Printf(" Enter line number in %s: > ", targetfile) | ||
sub = true | ||
case "Q": | ||
os.Exit(0) | ||
default: | ||
if sub { | ||
line, _ = strconv.Atoi(input) | ||
breakpointSet, originalCode = setBreak(pid, targetfile, line) | ||
return true | ||
} | ||
fmt.Printf("Unexpected input %s\n", input) | ||
fmt.Printf("\n(C)ontinue, (S)tep, set (B)reakpoint or (Q)uit? > ") | ||
} | ||
} | ||
} | ||
|
||
func setBreak(pid int, filename string, line int) (bool, []byte) { | ||
var err error | ||
pc, _, err = symTable.LineToPC(filename, line) | ||
if err != nil { | ||
fmt.Printf("Can't find breakpoint for %s, %d\n", filename, line) | ||
return false, []byte{} | ||
} | ||
|
||
// fmt.Printf("Stopping at %X\n", pc) | ||
return true, replaceCode(pid, pc, []byte{0xCC}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package main | ||
|
||
import ( | ||
"debug/gosym" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"syscall" | ||
) | ||
|
||
var targetfile string | ||
var line int | ||
var pc uint64 | ||
var fn *gosym.Func | ||
var symTable *gosym.Table | ||
var regs syscall.PtraceRegs | ||
var ws syscall.WaitStatus | ||
var originalCode []byte | ||
var breakpointSet bool | ||
|
||
var interruptCode = []byte{0xCC} | ||
|
||
func main() { | ||
target := os.Args[1] | ||
symTable = getSymbolTable(target) | ||
fn = symTable.LookupFunc("main.main") | ||
targetfile, line, fn = symTable.PCToLine(fn.Entry) | ||
run(target) | ||
} | ||
|
||
func run(target string) { | ||
var filename string | ||
|
||
cmd := exec.Command(target) | ||
cmd.Stderr = os.Stderr | ||
cmd.Stdin = os.Stdin | ||
cmd.Stdout = os.Stdout | ||
cmd.SysProcAttr = &syscall.SysProcAttr{ | ||
Ptrace: true, | ||
} | ||
|
||
cmd.Start() | ||
err := cmd.Wait() | ||
if err != nil { | ||
fmt.Printf("Wait returned: %v\n\n", err) | ||
} | ||
|
||
pid := cmd.Process.Pid | ||
pgid, _ := syscall.Getpgid(pid) | ||
|
||
must(syscall.PtraceSetOptions(pid, syscall.PTRACE_O_TRACECLONE)) | ||
|
||
if inputContinue(pid) { | ||
must(syscall.PtraceCont(pid, 0)) | ||
} else { | ||
must(syscall.PtraceSingleStep(pid)) | ||
} | ||
|
||
for { | ||
wpid, err := syscall.Wait4(-1*pgid, &ws, 0, nil) | ||
must(err) | ||
if ws.Exited() { | ||
if wpid == pid { | ||
break | ||
} | ||
} else { | ||
// We are only interested in tracing if we're stopped by a trap and | ||
// if the trap was generated by our breakpoint. | ||
// Cloning a child process also generates a trap, and we want to ignore that. | ||
if ws.StopSignal() == syscall.SIGTRAP && ws.TrapCause() != syscall.PTRACE_EVENT_CLONE { | ||
must(syscall.PtraceGetRegs(wpid, ®s)) | ||
filename, line, fn = symTable.PCToLine(regs.Rip) | ||
fmt.Printf("Stopped at %s at %d in %s\n", fn.Name, line, filename) | ||
outputStack(symTable, wpid, ®s) | ||
|
||
if breakpointSet { | ||
replaceCode(wpid, pc, originalCode) | ||
breakpointSet = false | ||
} | ||
|
||
if inputContinue(wpid) { | ||
must(syscall.PtraceCont(wpid, 0)) | ||
} else { | ||
must(syscall.PtraceSingleStep(wpid)) | ||
} | ||
} else { | ||
must(syscall.PtraceCont(wpid, 0)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func replaceCode(pid int, breakpoint uint64, code []byte) []byte { | ||
original := make([]byte, len(code)) | ||
syscall.PtracePeekData(pid, uintptr(breakpoint), original) | ||
syscall.PtracePokeData(pid, uintptr(breakpoint), code) | ||
return original | ||
} | ||
|
||
func must(err error) { | ||
if err != nil { | ||
panic(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package main | ||
|
||
import ( | ||
"debug/gosym" | ||
"encoding/binary" | ||
"fmt" | ||
"syscall" | ||
) | ||
|
||
func outputStack(symTable *gosym.Table, pid int, regs *syscall.PtraceRegs) { | ||
|
||
// fmt.Printf("%#v\n", regs) | ||
|
||
_, _, fn = symTable.PCToLine(regs.Rip) | ||
|
||
sp := regs.Rsp | ||
bp := regs.Rbp | ||
var i uint64 | ||
var nextbp uint64 | ||
|
||
for { | ||
i = 0 | ||
frameSize := bp - sp + 8 | ||
|
||
if frameSize > 1000 || bp == 0 { | ||
fmt.Printf("Strange frame size: SP: %X | BP : %X \n", sp, bp) | ||
return | ||
// frameSize = 32 | ||
// bp = sp + frameSize - 8 | ||
} | ||
|
||
b := make([]byte, frameSize) | ||
_, err := syscall.PtracePeekData(pid, uintptr(sp), b) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
content := binary.LittleEndian.Uint64(b[i : i+8]) | ||
_, lineno, nextfn := symTable.PCToLine(content) | ||
if nextfn != nil { | ||
fn = nextfn | ||
// fmt.Printf(" %X %X: return to %s line %d\n", sp, content, fn.Name, lineno) | ||
fmt.Printf(" called by %s line %d\n", fn.Name, lineno) | ||
} | ||
|
||
for i = 8; sp+i <= bp; i += 8 { | ||
content := binary.LittleEndian.Uint64(b[i : i+8]) | ||
if sp+i == bp { | ||
// fmt.Printf("Frame added calling %s\n", fn.Name) | ||
nextbp = content | ||
} | ||
// fmt.Printf(" %X %X \n", sp+i, content) | ||
} | ||
|
||
if fn.Name == "main.main" || fn.Name == "runtime.main" { | ||
break | ||
} | ||
|
||
sp = sp + i | ||
bp = nextbp | ||
} | ||
|
||
fmt.Println() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package main | ||
|
||
import ( | ||
"debug/elf" | ||
"debug/gosym" | ||
) | ||
|
||
func getSymbolTable(prog string) *gosym.Table { | ||
exe, err := elf.Open(prog) | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer exe.Close() | ||
|
||
addr := exe.Section(".text").Addr | ||
|
||
lineTableData, err := exe.Section(".gopclntab").Data() | ||
if err != nil { | ||
panic(err) | ||
} | ||
lineTable := gosym.NewLineTable(lineTableData, addr) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
symTableData, err := exe.Section(".gosymtab").Data() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
symTable, err := gosym.NewTable(symTableData, lineTable) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
return symTable | ||
} |