LEFT | RIGHT |
(no file at all) | |
| 1 // Copyright 2014 The Go Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style |
| 3 // license that can be found in the LICENSE file. |
| 4 |
| 5 // This program is a standalone demo that demonstrates a Go tracer program |
| 6 // (this program) forking, executing and inserting breakpoints into a (multi- |
| 7 // threaded) Go tracee program. It logs all syscall.Wait4 results, and reading |
| 8 // those logs, as well as ../../doc/ptrace-nptl.txt, should help understand how |
| 9 // the (more complicated) code.google.com/p/ogle/program/server package works. |
| 10 // |
| 11 // Only tested on linux/amd64. |
| 12 package main |
| 13 |
| 14 // TODO: other threads are not stopped when one tracee thread hits a breakpoint. |
| 15 // Thus, other threads could racily miss a breakpoint when they concurrently |
| 16 // execute code where the trap should be, in between lifting and re-setting the |
| 17 // trap. |
| 18 // |
| 19 // One option is to simply accept this behavior as is. Another option, "all- |
| 20 // stop", is to stop all other threads when one traps. A third option, "non- |
| 21 // stop", is to not lift the trap and not stop other threads, but to simulate |
| 22 // the original instruction when single-stepping: the "Non-stop Multi-Threaded |
| 23 // Debugging in GDB" paper calls this "displaced stepping". "Non-stop" is less |
| 24 // intrusive than "all-stop" (e.g. network I/O can still happen while at a |
| 25 // breakpoint), but it involves complicated, architecture-specific code. |
| 26 |
| 27 import ( |
| 28 "fmt" |
| 29 "log" |
| 30 "os" |
| 31 "runtime" |
| 32 "syscall" |
| 33 "time" |
| 34 |
| 35 "code.google.com/p/ogle/debug/dwarf" |
| 36 "code.google.com/p/ogle/debug/elf" |
| 37 "code.google.com/p/ogle/debug/macho" |
| 38 ) |
| 39 |
| 40 const ( |
| 41 exeFilename = "tracee/tracee" |
| 42 |
| 43 // TODO: don't be amd64-specific. |
| 44 breakpointInstr = 0xcc |
| 45 breakpointInstrLen = 1 |
| 46 ) |
| 47 |
| 48 type breakpoint struct { |
| 49 pc uint64 |
| 50 origInstr byte // TODO: don't be amd64-specific. |
| 51 } |
| 52 |
| 53 func main() { |
| 54 go run() |
| 55 time.Sleep(2 * time.Second) |
| 56 } |
| 57 |
| 58 func run() { |
| 59 // If the debugger itself is multi-threaded, ptrace calls must come from |
| 60 // the same thread that originally attached to the remote thread. |
| 61 runtime.LockOSThread() |
| 62 |
| 63 f, err := os.Open(exeFilename) |
| 64 if err != nil { |
| 65 log.Printf(`%q not found. Did you run "go build ." in that direc
tory?`, exeFilename) |
| 66 log.Fatalf("Open: %v", err) |
| 67 } |
| 68 defer f.Close() |
| 69 dwarfData, err := loadDwarfData(f) |
| 70 if err != nil { |
| 71 log.Fatalf("loadDwarfData: %v", err) |
| 72 } |
| 73 |
| 74 proc, err := os.StartProcess(exeFilename, []string{exeFilename}, &os.Pro
cAttr{ |
| 75 Files: []*os.File{ |
| 76 os.Stdin, |
| 77 os.Stdout, |
| 78 os.Stderr, |
| 79 }, |
| 80 Sys: &syscall.SysProcAttr{ |
| 81 Ptrace: true, |
| 82 Pdeathsig: syscall.SIGKILL, |
| 83 }, |
| 84 }) |
| 85 if err != nil { |
| 86 log.Fatalf("StartProcess: %v", err) |
| 87 } |
| 88 |
| 89 fmt.Printf("\tproc.Pid=%d\n", proc.Pid) |
| 90 |
| 91 _, status, err := wait(proc.Pid) |
| 92 if err != nil { |
| 93 log.Fatalf("wait: %v", err) |
| 94 } |
| 95 if status != 0x00057f { // 0x05=SIGTRAP, 0x7f=stopped. |
| 96 log.Fatalf("status: got %#x, want %#x", status, 0x57f) |
| 97 } |
| 98 err = syscall.PtraceSetOptions(proc.Pid, syscall.PTRACE_O_TRACECLONE|sys
call.PTRACE_O_TRACEEXIT) |
| 99 if err != nil { |
| 100 log.Fatalf("PtraceSetOptions: %v", err) |
| 101 } |
| 102 |
| 103 addr, err := lookupSym(dwarfData, "fmt.Printf") |
| 104 if err != nil { |
| 105 log.Fatalf("lookupSym: %v", err) |
| 106 } |
| 107 fmt.Printf("\tfmt.Printf=%#x\n", addr) |
| 108 |
| 109 var buf [1]byte |
| 110 if err := peek(proc.Pid, addr, buf[:1]); err != nil { |
| 111 log.Fatalf("peek: %v", err) |
| 112 } |
| 113 breakpoints := map[uint64]breakpoint{ |
| 114 addr: {pc: addr, origInstr: buf[0]}, |
| 115 } |
| 116 buf[0] = breakpointInstr |
| 117 if err := poke(proc.Pid, addr, buf[:1]); err != nil { |
| 118 log.Fatalf("poke: %v", err) |
| 119 } |
| 120 |
| 121 err = syscall.PtraceCont(proc.Pid, 0) |
| 122 if err != nil { |
| 123 log.Fatalf("PtraceCont: %v", err) |
| 124 } |
| 125 |
| 126 for { |
| 127 pid, status, err := wait(-1) |
| 128 if err != nil { |
| 129 log.Fatalf("wait: %v", err) |
| 130 } |
| 131 |
| 132 switch status { |
| 133 case 0x00057f: // 0x05=SIGTRAP, 0x7f=stopped. |
| 134 regs := syscall.PtraceRegs{} |
| 135 if err := syscall.PtraceGetRegs(pid, ®s); err != nil
{ |
| 136 log.Fatalf("PtraceGetRegs: %v", err) |
| 137 } |
| 138 regs.Rip -= breakpointInstrLen |
| 139 if err := syscall.PtraceSetRegs(pid, ®s); err != nil
{ |
| 140 log.Fatalf("PtraceSetRegs: %v", err) |
| 141 } |
| 142 bp, ok := breakpoints[regs.Rip] |
| 143 if !ok { |
| 144 log.Fatalf("no breakpoint for address %#x\n", re
gs.Rip) |
| 145 } |
| 146 buf[0] = bp.origInstr |
| 147 if err := poke(pid, addr, buf[:1]); err != nil { |
| 148 log.Fatalf("poke: %v", err) |
| 149 } |
| 150 fmt.Printf("\thit breakpoint at %#x, pid=%5d\n", regs.Ri
p, pid) |
| 151 if err := syscall.PtraceSingleStep(pid); err != nil { |
| 152 log.Fatalf("PtraceSingleStep: %v", err) |
| 153 } |
| 154 _, status, err := wait(pid) |
| 155 if err != nil { |
| 156 log.Fatalf("wait: %v", err) |
| 157 } |
| 158 if status != 0x00057f { |
| 159 log.Fatalf("PtraceSingleStep: unexpected status
%#x\n", status) |
| 160 } |
| 161 buf[0] = breakpointInstr |
| 162 if err := poke(pid, addr, buf[:1]); err != nil { |
| 163 log.Fatalf("poke: %v", err) |
| 164 } |
| 165 |
| 166 case 0x00137f: // 0x13=SIGSTOP, 0x7f=stopped. |
| 167 // No-op. |
| 168 |
| 169 case 0x03057f: // 0x05=SIGTRAP, 0x7f=stopped, 0x03=PTRACE_EVENT_
CLONE. |
| 170 msg, err := syscall.PtraceGetEventMsg(pid) |
| 171 if err != nil { |
| 172 log.Fatalf("PtraceGetEventMsg: %v", err) |
| 173 } |
| 174 fmt.Printf("\tclone: new pid=%d\n", msg) |
| 175 |
| 176 default: |
| 177 log.Fatalf("unexpected status %#x\n", status) |
| 178 } |
| 179 |
| 180 err = syscall.PtraceCont(pid, 0) |
| 181 if err != nil { |
| 182 log.Fatalf("PtraceCont: %v", err) |
| 183 } |
| 184 } |
| 185 } |
| 186 |
| 187 func wait(pid int) (wpid int, status syscall.WaitStatus, err error) { |
| 188 wpid, err = syscall.Wait4(pid, &status, syscall.WALL, nil) |
| 189 if err != nil { |
| 190 return 0, 0, err |
| 191 } |
| 192 fmt.Printf("\t\twait: wpid=%5d, status=0x%06x\n", wpid, status) |
| 193 return wpid, status, nil |
| 194 } |
| 195 |
| 196 func peek(pid int, addr uint64, data []byte) error { |
| 197 n, err := syscall.PtracePeekText(pid, uintptr(addr), data) |
| 198 if err != nil { |
| 199 return err |
| 200 } |
| 201 if n != len(data) { |
| 202 return fmt.Errorf("peek: got %d bytes, want %d", len(data)) |
| 203 } |
| 204 return nil |
| 205 } |
| 206 |
| 207 func poke(pid int, addr uint64, data []byte) error { |
| 208 n, err := syscall.PtracePokeText(pid, uintptr(addr), data) |
| 209 if err != nil { |
| 210 return err |
| 211 } |
| 212 if n != len(data) { |
| 213 return fmt.Errorf("poke: got %d bytes, want %d", len(data)) |
| 214 } |
| 215 return nil |
| 216 } |
| 217 |
| 218 func lookupSym(dwarfData *dwarf.Data, name string) (uint64, error) { |
| 219 r := dwarfData.Reader() |
| 220 for { |
| 221 entry, err := r.Next() |
| 222 if err != nil { |
| 223 return 0, err |
| 224 } |
| 225 if entry == nil { |
| 226 // TODO: why don't we get an error here. |
| 227 break |
| 228 } |
| 229 if entry.Tag != dwarf.TagSubprogram { |
| 230 continue |
| 231 } |
| 232 nameAttr := lookupAttr(entry, dwarf.AttrName) |
| 233 if nameAttr == nil { |
| 234 // TODO: this shouldn't be possible. |
| 235 continue |
| 236 } |
| 237 if nameAttr.(string) != name { |
| 238 continue |
| 239 } |
| 240 addrAttr := lookupAttr(entry, dwarf.AttrLowpc) |
| 241 if addrAttr == nil { |
| 242 return 0, fmt.Errorf("symbol %q has no LowPC attribute",
name) |
| 243 } |
| 244 addr, ok := addrAttr.(uint64) |
| 245 if !ok { |
| 246 return 0, fmt.Errorf("symbol %q has non-uint64 LowPC att
ribute", name) |
| 247 } |
| 248 return addr, nil |
| 249 } |
| 250 return 0, fmt.Errorf("symbol %q not found", name) |
| 251 } |
| 252 |
| 253 func lookupAttr(e *dwarf.Entry, a dwarf.Attr) interface{} { |
| 254 for _, f := range e.Field { |
| 255 if f.Attr == a { |
| 256 return f.Val |
| 257 } |
| 258 } |
| 259 return nil |
| 260 } |
| 261 |
| 262 func loadDwarfData(f *os.File) (*dwarf.Data, error) { |
| 263 if obj, err := elf.NewFile(f); err == nil { |
| 264 return obj.DWARF() |
| 265 } |
| 266 if obj, err := macho.NewFile(f); err == nil { |
| 267 return obj.DWARF() |
| 268 } |
| 269 return nil, fmt.Errorf("unrecognized binary format") |
| 270 } |
LEFT | RIGHT |