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  }