github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/strace/tracer.go (about) 1 // Copyright 2018 the u-root 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 // strace traces Linux process events. 6 // 7 // An straced process will emit events for syscalls, signals, exits, and new 8 // children. 9 package strace 10 11 import ( 12 "encoding/binary" 13 "fmt" 14 "io" 15 "os" 16 "os/exec" 17 "runtime" 18 "sync/atomic" 19 "syscall" 20 "time" 21 22 "github.com/u-root/u-root/pkg/ubinary" 23 "golang.org/x/sys/unix" 24 ) 25 26 func wait(pid int) (int, unix.WaitStatus, error) { 27 var w unix.WaitStatus 28 pid, err := unix.Wait4(pid, &w, 0, nil) 29 return pid, w, err 30 } 31 32 // TraceError is returned when something failed on a specific process. 33 type TraceError struct { 34 // PID is the process ID associated with the error. 35 PID int 36 Err error 37 } 38 39 func (t *TraceError) Error() string { 40 return fmt.Sprintf("trace error on pid %d: %v", t.PID, t.Err) 41 } 42 43 // SyscallEvent is populated for both SyscallEnter and SyscallExit event types. 44 type SyscallEvent struct { 45 // Regs are the process's registers as they were when the event was 46 // recorded. 47 Regs unix.PtraceRegs 48 49 // Sysno is the syscall number. 50 Sysno int 51 52 // Args are the arguments to the syscall. 53 Args SyscallArguments 54 55 // Ret is the return value of the syscall. Only populated on 56 // SyscallExit. 57 Ret [2]SyscallArgument 58 59 // Errno is an errno, if there was on in Ret. Only populated on 60 // SyscallExit. 61 Errno unix.Errno 62 63 // Duration is the duration from enter to exit for this particular 64 // syscall. Only populated on SyscallExit. 65 Duration time.Duration 66 } 67 68 // SignalEvent is a signal that was delivered to the process. 69 type SignalEvent struct { 70 // Signal is the signal number. 71 Signal unix.Signal 72 73 // TODO: Add other siginfo_t stuff 74 } 75 76 // ExitEvent is emitted when the process exits regularly using exit_group(2). 77 type ExitEvent struct { 78 // WaitStatus is the exit status. 79 WaitStatus unix.WaitStatus 80 } 81 82 // NewChildEvent is emitted when a clone/fork/vfork syscall is done. 83 type NewChildEvent struct { 84 PID int 85 } 86 87 // TraceRecord has information about a process event. 88 type TraceRecord struct { 89 PID int 90 Time time.Time 91 Event EventType 92 93 // Poor man's union. One of the following five will be populated 94 // depending on the Event. 95 96 Syscall *SyscallEvent 97 SignalExit *SignalEvent 98 SignalStop *SignalEvent 99 Exit *ExitEvent 100 NewChild *NewChildEvent 101 } 102 103 // process is a Linux thread. 104 type process struct { 105 pid int 106 107 // ptrace does not tell you whether a syscall-stop is a 108 // syscall-enter-stop or syscall-exit-stop. You gotta keep track of 109 // that shit your own self. 110 lastSyscallStop *TraceRecord 111 } 112 113 // Name implements Task.Name. 114 func (p *process) Name() string { 115 return fmt.Sprintf("[pid %d]", p.pid) 116 } 117 118 // Read reads from the process at Addr to the interface{} 119 // and returns a byte count and error. 120 func (p *process) Read(addr Addr, v interface{}) (int, error) { 121 r := newProcReader(p.pid, uintptr(addr)) 122 err := binary.Read(r, ubinary.NativeEndian, v) 123 return r.bytes, err 124 } 125 126 func (p *process) cont(signal unix.Signal) error { 127 // Event has been processed. Restart 'em. 128 if err := unix.PtraceSyscall(p.pid, int(signal)); err != nil { 129 return os.NewSyscallError("ptrace(PTRACE_SYSCALL)", fmt.Errorf("on pid %d: %v", p.pid, err)) 130 } 131 return nil 132 } 133 134 type tracer struct { 135 processes map[int]*process 136 callback []EventCallback 137 } 138 139 func (t *tracer) call(p *process, rec *TraceRecord) error { 140 for _, c := range t.callback { 141 if err := c(p, rec); err != nil { 142 return err 143 } 144 } 145 return nil 146 } 147 148 var traceActive uint32 149 150 // Trace traces `c` and any children c clones. 151 // 152 // Only one trace can be active per process. 153 // 154 // recordCallback is called every time a process event happens with the process 155 // in a stopped state. 156 func Trace(c *exec.Cmd, recordCallback ...EventCallback) error { 157 if !atomic.CompareAndSwapUint32(&traceActive, 0, 1) { 158 return fmt.Errorf("a process trace is already active in this process") 159 } 160 defer func() { 161 atomic.StoreUint32(&traceActive, 0) 162 }() 163 164 if c.SysProcAttr == nil { 165 c.SysProcAttr = &syscall.SysProcAttr{} 166 } 167 c.SysProcAttr.Ptrace = true 168 169 // Because the go runtime forks traced processes with PTRACE_TRACEME 170 // we need to maintain the parent-child relationship for ptrace to work. 171 runtime.LockOSThread() 172 defer runtime.UnlockOSThread() 173 174 if err := c.Start(); err != nil { 175 return err 176 } 177 178 tracer := &tracer{ 179 processes: make(map[int]*process), 180 callback: recordCallback, 181 } 182 183 // Start will fork, set PTRACE_TRACEME, and then execve. Once that 184 // happens, we should be stopped at the execve "exit". This wait will 185 // return at that exit point. 186 // 187 // The new task image has been loaded at this point, with us just about 188 // to jump into _start. 189 // 190 // It'd make sense to assume, but this stop is NOT a syscall-exit-stop 191 // of the execve. It is a signal-stop triggered at the end of execve, 192 // within the confines of the new task image. This means the execve 193 // syscall args are not in their registers, and we can't print the 194 // exit. 195 // 196 // NOTE(chrisko): we could make it such that we can read the args of 197 // the execve. If we were to signal ourselves between PTRACE_TRACEME 198 // and execve, we'd stop before the execve and catch execve as a 199 // syscall-stop after. To do so, we have 3 options: (1) write a copy of 200 // stdlib exec.Cmd.Start/os.StartProcess with the change, or (2) 201 // upstreaming a change that would make it into the next Go version, or 202 // (3) use something other than *exec.Cmd as the API. 203 // 204 // A copy of the StartProcess logic would be tedious, an upstream 205 // change would take a while to get into Go, and we want this API to be 206 // easily usable. I think it's ok to sacrifice the execve for now. 207 if _, ws, err := wait(c.Process.Pid); err != nil { 208 return err 209 } else if ws.TrapCause() != 0 { 210 return fmt.Errorf("wait(pid=%d): got %v, want stopped process", c.Process.Pid, ws) 211 } 212 tracer.addProcess(c.Process.Pid, SyscallExit) 213 214 if err := unix.PtraceSetOptions(c.Process.Pid, 215 // Make it easy to distinguish syscall-stops from other SIGTRAPS. 216 unix.PTRACE_O_TRACESYSGOOD| 217 // Kill tracee if tracer exits. 218 unix.PTRACE_O_EXITKILL| 219 // Automatically trace fork(2)'d, clone(2)'d, and vfork(2)'d children. 220 unix.PTRACE_O_TRACECLONE|unix.PTRACE_O_TRACEFORK|unix.PTRACE_O_TRACEVFORK); err != nil { 221 return &TraceError{ 222 PID: c.Process.Pid, 223 Err: os.NewSyscallError("ptrace(PTRACE_SETOPTIONS)", err), 224 } 225 } 226 227 // Start the process back up. 228 if err := unix.PtraceSyscall(c.Process.Pid, 0); err != nil { 229 return &TraceError{ 230 PID: c.Process.Pid, 231 Err: fmt.Errorf("failed to resume: %v", err), 232 } 233 } 234 235 return tracer.runLoop() 236 } 237 238 func (t *tracer) addProcess(pid int, event EventType) { 239 t.processes[pid] = &process{ 240 pid: pid, 241 lastSyscallStop: &TraceRecord{ 242 Event: event, 243 Time: time.Now(), 244 }, 245 } 246 } 247 248 func (t *TraceRecord) syscallStop(p *process) error { 249 t.Syscall = &SyscallEvent{} 250 251 if err := unix.PtraceGetRegs(p.pid, &t.Syscall.Regs); err != nil { 252 return &TraceError{ 253 PID: p.pid, 254 Err: os.NewSyscallError("ptrace(PTRACE_GETREGS)", err), 255 } 256 } 257 258 t.Syscall.FillArgs() 259 260 // TODO: the ptrace man page mentions that seccomp can inject a 261 // syscall-exit-stop without a preceding syscall-enter-stop. Detect 262 // that here, however you'd detect it... 263 if p.lastSyscallStop.Event == SyscallEnter { 264 t.Event = SyscallExit 265 t.Syscall.FillRet() 266 t.Syscall.Duration = time.Since(p.lastSyscallStop.Time) 267 } else { 268 t.Event = SyscallEnter 269 } 270 p.lastSyscallStop = t 271 return nil 272 } 273 274 func (t *tracer) runLoop() error { 275 for { 276 // TODO: we cannot have any other children. I'm not sure this 277 // is actually solvable: if we used a session or process group, 278 // a tracee process's usage of them would mess up our accounting. 279 // 280 // If we just ignored wait's of processes that we're not 281 // tracing, we'll be messing up other stuff in this program 282 // waiting on those. 283 // 284 // To actually encapsulate this library in a packge, we could 285 // do one of two things: 286 // 287 // 1) fork from the parent in order to be able to trace 288 // children correctly. Then, a user of this library could 289 // actually independently trace two different processes. 290 // I don't know if that's worth doing. 291 // 2) have one goroutine per process, and call wait4 292 // individually on each process we expect. We gotta check 293 // if each has to be tied to an OS thread or not. 294 // 295 // The latter option seems much nicer. 296 pid, status, err := wait(-1) 297 if err == unix.ECHILD { 298 // All our children are gone. 299 return nil 300 } else if err != nil { 301 return os.NewSyscallError("wait4", err) 302 } 303 304 // Which process was stopped? 305 p, ok := t.processes[pid] 306 if !ok { 307 continue 308 } 309 310 rec := &TraceRecord{ 311 PID: p.pid, 312 Time: time.Now(), 313 } 314 315 var injectSignal unix.Signal 316 if status.Exited() { 317 rec.Event = Exit 318 rec.Exit = &ExitEvent{ 319 WaitStatus: status, 320 } 321 } else if status.Signaled() { 322 rec.Event = SignalExit 323 rec.SignalExit = &SignalEvent{ 324 Signal: status.Signal(), 325 } 326 } else if status.Stopped() { 327 // Ptrace stops kinds. 328 switch signal := status.StopSignal(); signal { 329 // Syscall-stop. 330 // 331 // Setting PTRACE_O_TRACESYSGOOD means StopSignal == 332 // SIGTRAP|0x80 (0x85) for syscall-stops. 333 // 334 // It allows us to distinguish syscall-stops from regular 335 // SIGTRAPs (e.g. sent by tkill(2)). 336 case syscall.SIGTRAP | 0x80: 337 if err := rec.syscallStop(p); err != nil { 338 return err 339 } 340 341 // Group-stop, but also a special stop: first stop after 342 // fork/clone/vforking a new task. 343 // 344 // TODO: is that different than a group-stop, or the same? 345 case syscall.SIGSTOP: 346 // TODO: have a list of expected children SIGSTOPs, and 347 // make events only for all the unexpected ones. 348 fallthrough 349 350 // Group-stop. 351 // 352 // TODO: do something. 353 case syscall.SIGTSTP, syscall.SIGTTOU, syscall.SIGTTIN: 354 rec.Event = SignalStop 355 injectSignal = signal 356 rec.SignalStop = &SignalEvent{ 357 Signal: signal, 358 } 359 360 // TODO: Do we have to use PTRACE_LISTEN to 361 // restart the task in order to keep the task 362 // in stopped state, as expected by whomever 363 // sent the stop signal? 364 365 // Either a regular signal-delivery-stop, or a PTRACE_EVENT stop. 366 case syscall.SIGTRAP: 367 switch tc := status.TrapCause(); tc { 368 // This is a PTRACE_EVENT stop. 369 case unix.PTRACE_EVENT_CLONE, unix.PTRACE_EVENT_FORK, unix.PTRACE_EVENT_VFORK: 370 childPID, err := unix.PtraceGetEventMsg(pid) 371 if err != nil { 372 return &TraceError{ 373 PID: pid, 374 Err: os.NewSyscallError("ptrace(PTRACE_GETEVENTMSG)", err), 375 } 376 } 377 // The first event will be an Enter syscall, so 378 // set the last event to an exit. 379 t.addProcess(int(childPID), SyscallExit) 380 381 rec.Event = NewChild 382 rec.NewChild = &NewChildEvent{ 383 PID: int(childPID), 384 } 385 386 // Regular signal-delivery-stop. 387 default: 388 rec.Event = SignalStop 389 rec.SignalStop = &SignalEvent{ 390 Signal: signal, 391 } 392 injectSignal = signal 393 } 394 395 // Signal-delivery-stop. 396 default: 397 rec.Event = SignalStop 398 rec.SignalStop = &SignalEvent{ 399 Signal: signal, 400 } 401 injectSignal = signal 402 } 403 } else { 404 rec.Event = Unknown 405 } 406 407 if err := t.call(p, rec); err != nil { 408 return err 409 } 410 411 if rec.Event == SignalExit || rec.Event == Exit { 412 delete(t.processes, pid) 413 continue 414 } 415 416 if err := p.cont(injectSignal); err != nil { 417 return err 418 } 419 } 420 } 421 422 // EventCallback is a function called on each event while the subject process 423 // is stopped. 424 type EventCallback func(t Task, record *TraceRecord) error 425 426 // RecordTraces sends each event on c. 427 func RecordTraces(c chan<- *TraceRecord) EventCallback { 428 return func(t Task, record *TraceRecord) error { 429 c <- record 430 return nil 431 } 432 } 433 434 func signalString(s unix.Signal) string { 435 if 0 <= s && int(s) < len(signals) { 436 return fmt.Sprintf("%s (%d)", signals[s], int(s)) 437 } 438 return fmt.Sprintf("signal %d", int(s)) 439 } 440 441 // PrintTraces prints every trace event to w. 442 func PrintTraces(w io.Writer) EventCallback { 443 return func(t Task, record *TraceRecord) error { 444 switch record.Event { 445 case SyscallEnter: 446 fmt.Fprintln(w, SysCallEnter(t, record.Syscall)) 447 case SyscallExit: 448 fmt.Fprintln(w, SysCallExit(t, record.Syscall)) 449 case SignalExit: 450 fmt.Fprintf(w, "PID %d exited from signal %s\n", record.PID, signalString(record.SignalExit.Signal)) 451 case Exit: 452 fmt.Fprintf(w, "PID %d exited from exit status %d (code = %d)\n", record.PID, record.Exit.WaitStatus, record.Exit.WaitStatus.ExitStatus()) 453 case SignalStop: 454 fmt.Fprintf(w, "PID %d got signal %s\n", record.PID, signalString(record.SignalStop.Signal)) 455 case NewChild: 456 fmt.Fprintf(w, "PID %d spawned new child %d\n", record.PID, record.NewChild.PID) 457 } 458 return nil 459 } 460 } 461 462 // Strace traces and prints process events for `c` and its children to `out`. 463 func Strace(c *exec.Cmd, out io.Writer) error { 464 return Trace(c, PrintTraces(out)) 465 } 466 467 // EventType describes a process event. 468 type EventType int 469 470 const ( 471 // Unknown is for events we do not know how to interpret. 472 Unknown EventType = 0x0 473 474 // SyscallEnter is the event for a process calling a syscall. Event 475 // Args will contain the arguments sent by the userspace process. 476 // 477 // ptrace calls this a syscall-enter-stop. 478 SyscallEnter EventType = 0x2 479 480 // SyscallExit is the event for the kernel returning a syscall. Args 481 // will contain the arguments as returned by the kernel. 482 // 483 // ptrace calls this a syscall-exit-stop. 484 SyscallExit EventType = 0x3 485 486 // SignalExit means the process has been terminated by a signal. 487 SignalExit EventType = 0x4 488 489 // Exit means the process has exited with an exit code. 490 Exit EventType = 0x5 491 492 // SignalStop means the process was stopped by a signal. 493 // 494 // ptrace calls this a signal-delivery-stop. 495 SignalStop EventType = 0x6 496 497 // NewChild means the process created a new child thread or child 498 // process via fork, clone, or vfork. 499 // 500 // ptrace calls this a PTRACE_EVENT_(FORK|CLONE|VFORK). 501 NewChild EventType = 0x7 502 ) 503 504 // A procIO is used to implement io.Reader and io.Writer. 505 // it contains a pid, which is unchanging; and an 506 // addr and byte count which change as IO proceeds. 507 type procIO struct { 508 pid int 509 addr uintptr 510 bytes int 511 } 512 513 // newProcReader returns an io.Reader for a procIO. 514 func newProcReader(pid int, addr uintptr) *procIO { 515 return &procIO{pid: pid, addr: addr} 516 } 517 518 // Read implements io.Read for a procIO. 519 func (p *procIO) Read(b []byte) (int, error) { 520 n, err := unix.PtracePeekData(p.pid, p.addr, b) 521 if err != nil { 522 return n, err 523 } 524 p.addr += uintptr(n) 525 p.bytes += n 526 return n, nil 527 } 528 529 // ReadString reads a null-terminated string from the process 530 // at Addr and any errors. 531 func ReadString(t Task, addr Addr, max int) (string, error) { 532 if addr == 0 { 533 return "<nil>", nil 534 } 535 var s string 536 var b [1]byte 537 for len(s) < max { 538 if _, err := t.Read(addr, b[:]); err != nil { 539 return "", err 540 } 541 if b[0] == 0 { 542 break 543 } 544 s = s + string(b[:]) 545 addr++ 546 } 547 return s, nil 548 } 549 550 // ReadStringVector takes an address, max string size, and max number of string to read, 551 // and returns a string slice or error. 552 func ReadStringVector(t Task, addr Addr, maxsize, maxno int) ([]string, error) { 553 var v []Addr 554 if addr == 0 { 555 return []string{}, nil 556 } 557 558 // Read in a maximum of maxno addresses 559 for len(v) < maxno { 560 var a uint64 561 n, err := t.Read(addr, &a) 562 if err != nil { 563 return nil, fmt.Errorf("could not read vector element at %#x: %v", addr, err) 564 } 565 if a == 0 { 566 break 567 } 568 addr += Addr(n) 569 v = append(v, Addr(a)) 570 } 571 var vs []string 572 for _, a := range v { 573 s, err := ReadString(t, a, maxsize) 574 if err != nil { 575 return vs, fmt.Errorf("could not read string at %#x: %v", a, err) 576 } 577 vs = append(vs, s) 578 } 579 return vs, nil 580 } 581 582 // CaptureAddress pulls a socket address from the process as a byte slice. 583 // It returns any errors. 584 func CaptureAddress(t Task, addr Addr, addrlen uint32) ([]byte, error) { 585 b := make([]byte, addrlen) 586 if _, err := t.Read(addr, b); err != nil { 587 return nil, err 588 } 589 return b, nil 590 }