github.com/ks888/tgo@v0.0.0-20190130135156-80bf89407292/debugapi/client_linux.go (about)

     1  package debugapi
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"runtime"
    11  	"strconv"
    12  	"syscall"
    13  
    14  	"github.com/ks888/tgo/log"
    15  	"golang.org/x/sys/unix"
    16  )
    17  
    18  // Client is the client proxy in order to execute the ptrace requests in the only one go routine.
    19  // It is because the tracer thread must remain same, which is the limitation of ptrace.
    20  type Client struct {
    21  	reqCh  chan func()
    22  	doneCh chan struct{}
    23  	raw    *rawClient
    24  }
    25  
    26  // NewClient returns the new client proxy.
    27  func NewClient() *Client {
    28  	clientProxy := &Client{reqCh: make(chan func()), doneCh: make(chan struct{}), raw: newRawClient()}
    29  	go func() {
    30  		runtime.LockOSThread()
    31  
    32  		// this go routine may leak, but it doesn't matter in typical use cases.
    33  		for f := range clientProxy.reqCh {
    34  			f()
    35  			clientProxy.doneCh <- struct{}{}
    36  		}
    37  	}()
    38  	return clientProxy
    39  }
    40  
    41  func (c *Client) LaunchProcess(name string, arg ...string) (err error) {
    42  	c.reqCh <- func() { err = c.raw.LaunchProcess(name, arg...) }
    43  	<-c.doneCh
    44  	return
    45  }
    46  
    47  func (c *Client) AttachProcess(pid int) (err error) {
    48  	c.reqCh <- func() { err = c.raw.AttachProcess(pid) }
    49  	_ = <-c.doneCh
    50  	return
    51  }
    52  
    53  func (c *Client) DetachProcess() (err error) {
    54  	c.reqCh <- func() { err = c.raw.DetachProcess() }
    55  	_ = <-c.doneCh
    56  	return
    57  }
    58  
    59  func (c *Client) ReadMemory(addr uint64, out []byte) (err error) {
    60  	c.reqCh <- func() { err = c.raw.ReadMemory(addr, out) }
    61  	_ = <-c.doneCh
    62  	return
    63  }
    64  
    65  func (c *Client) WriteMemory(addr uint64, data []byte) (err error) {
    66  	c.reqCh <- func() { err = c.raw.WriteMemory(addr, data) }
    67  	_ = <-c.doneCh
    68  	return
    69  }
    70  
    71  func (c *Client) ReadRegisters(threadID int) (regs Registers, err error) {
    72  	c.reqCh <- func() { regs, err = c.raw.ReadRegisters(threadID) }
    73  	_ = <-c.doneCh
    74  	return
    75  }
    76  
    77  func (c *Client) WriteRegisters(threadID int, regs Registers) (err error) {
    78  	c.reqCh <- func() { err = c.raw.WriteRegisters(threadID, regs) }
    79  	_ = <-c.doneCh
    80  	return
    81  }
    82  
    83  func (c *Client) ReadTLS(threadID int, offset int32) (addr uint64, err error) {
    84  	c.reqCh <- func() { addr, err = c.raw.ReadTLS(threadID, offset) }
    85  	_ = <-c.doneCh
    86  	return
    87  }
    88  
    89  func (c *Client) ContinueAndWait() (ev Event, err error) {
    90  	c.reqCh <- func() { ev, err = c.raw.ContinueAndWait() }
    91  	_ = <-c.doneCh
    92  	return
    93  }
    94  
    95  func (c *Client) StepAndWait(threadID int) (ev Event, err error) {
    96  	c.reqCh <- func() { ev, err = c.raw.StepAndWait(threadID) }
    97  	_ = <-c.doneCh
    98  	return
    99  }
   100  
   101  // rawClient is the debug api client which depends on OS API.
   102  type rawClient struct {
   103  	tracingProcessID int
   104  	tracingThreadIDs []int
   105  	trappedThreadIDs []int
   106  
   107  	killOnDetach bool
   108  }
   109  
   110  // newRawClient returns the new debug api client which depends on linux ptrace.
   111  func newRawClient() *rawClient {
   112  	return &rawClient{}
   113  }
   114  
   115  // LaunchProcess launches the new prcoess with ptrace enabled.
   116  func (c *rawClient) LaunchProcess(name string, arg ...string) error {
   117  	cmd := exec.Command(name, arg...)
   118  	cmd.SysProcAttr = &syscall.SysProcAttr{
   119  		Ptrace: true,
   120  	}
   121  
   122  	if err := cmd.Start(); err != nil {
   123  		return err
   124  	}
   125  
   126  	c.killOnDetach = true
   127  	c.tracingProcessID = cmd.Process.Pid
   128  
   129  	// SIGTRAP signal is sent when execve is called.
   130  	return c.waitAndInitialize(cmd.Process.Pid)
   131  }
   132  
   133  // AttachProcess attaches to the process.
   134  func (c *rawClient) AttachProcess(pid int) error {
   135  	// There is a race because a new thread may be created after we get the member list and before attaching to all of them.
   136  	// TODO: Recheck the member list later.
   137  	members, err := c.threadGroupMembers(pid)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	for _, member := range members {
   143  		if err := unix.PtraceAttach(member); err != nil {
   144  			return err
   145  		}
   146  	}
   147  
   148  	c.killOnDetach = false
   149  	c.tracingProcessID = pid
   150  
   151  	for _, member := range members {
   152  		// SIGSTOP signal is sent when attached.
   153  		if err := c.waitAndInitialize(member); err != nil {
   154  			return err
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  func (c *rawClient) threadGroupMembers(pid int) ([]int, error) {
   161  	files, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	var members []int
   167  	for _, f := range files {
   168  		pid, err := strconv.Atoi(f.Name())
   169  		if err != nil {
   170  			log.Debugf("failed to parse %s: %v", f.Name(), err)
   171  		}
   172  		members = append(members, pid)
   173  	}
   174  	return members, nil
   175  }
   176  
   177  func (c *rawClient) waitAndInitialize(threadID int) error {
   178  	var status unix.WaitStatus
   179  	if _, err := unix.Wait4(threadID, &status, 0, nil); err != nil {
   180  		return err
   181  	}
   182  
   183  	if !status.Stopped() {
   184  		return fmt.Errorf("process is not stopped: %#v", status)
   185  	} else if status.StopSignal() != syscall.SIGTRAP && status.StopSignal() != syscall.SIGSTOP {
   186  		return fmt.Errorf("unexpected signal: %s", status.StopSignal())
   187  	}
   188  
   189  	unix.PtraceSetOptions(threadID, unix.PTRACE_O_TRACECLONE)
   190  
   191  	c.tracingThreadIDs = append(c.tracingThreadIDs, threadID)
   192  	c.trappedThreadIDs = append(c.trappedThreadIDs, threadID)
   193  
   194  	return nil
   195  }
   196  
   197  // DetachProcess detaches from the process.
   198  func (c *rawClient) DetachProcess() error {
   199  	// detach the processes even when we will kill them soon, because
   200  	// next wait call may receive the terminated event of these processes.
   201  	for _, pid := range c.tracingThreadIDs {
   202  		if err := unix.PtraceDetach(pid); err != nil {
   203  			// the process may have exited already
   204  			log.Debugf("failed to detach %d: %v", pid, err)
   205  		}
   206  	}
   207  
   208  	if c.killOnDetach {
   209  		return c.killProcess()
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  func (c *rawClient) killProcess() error {
   216  	// it may be exited already
   217  	proc, _ := os.FindProcess(c.tracingProcessID)
   218  	_ = proc.Kill()
   219  
   220  	// We can't simply call proc.Wait, since it will hang when the thread leader exits while there are still subthreads.
   221  	// By calling wait4 like below, it reaps the subthreads first and then reaps the thread leader.
   222  	var status unix.WaitStatus
   223  	for {
   224  		if wpid, err := unix.Wait4(-1, &status, 0, nil); err != nil || wpid == c.tracingProcessID {
   225  			return err
   226  		}
   227  	}
   228  }
   229  
   230  // ReadMemory reads the specified memory region in the prcoess.
   231  func (c *rawClient) ReadMemory(addr uint64, out []byte) error {
   232  	if len(c.trappedThreadIDs) == 0 {
   233  		return errors.New("failed to read memory: currently no trapped threads")
   234  	}
   235  
   236  	count, err := unix.PtracePeekData(c.trappedThreadIDs[0], uintptr(addr), out)
   237  	if err != nil {
   238  		return err
   239  	} else if count != len(out) {
   240  		return fmt.Errorf("the number of data read is invalid: expect: %d, actual %d", len(out), count)
   241  	}
   242  	return nil
   243  }
   244  
   245  // WriteMemory write the data to the specified memory region in the prcoess.
   246  func (c *rawClient) WriteMemory(addr uint64, data []byte) error {
   247  	if len(c.trappedThreadIDs) == 0 {
   248  		return errors.New("failed to write memory: currently no trapped threads")
   249  	}
   250  
   251  	count, err := unix.PtracePokeData(c.trappedThreadIDs[0], uintptr(addr), data)
   252  	if err != nil {
   253  		return err
   254  	} else if count != len(data) {
   255  		return fmt.Errorf("the number of data written is invalid: expect: %d, actual %d", len(data), count)
   256  	}
   257  	return nil
   258  }
   259  
   260  // ReadRegisters reads the registers of the prcoess.
   261  func (c *rawClient) ReadRegisters(threadID int) (regs Registers, err error) {
   262  	var rawRegs unix.PtraceRegs
   263  	if err = unix.PtraceGetRegs(threadID, &rawRegs); err != nil {
   264  		return regs, err
   265  	}
   266  
   267  	regs.Rip = rawRegs.Rip
   268  	regs.Rsp = rawRegs.Rsp
   269  	regs.Rcx = rawRegs.Rcx
   270  	return regs, nil
   271  }
   272  
   273  // WriteRegisters change the registers of the prcoess.
   274  func (c *rawClient) WriteRegisters(threadID int, regs Registers) error {
   275  	var rawRegs unix.PtraceRegs
   276  	if err := unix.PtraceGetRegs(threadID, &rawRegs); err != nil {
   277  		return err
   278  	}
   279  
   280  	rawRegs.Rip = regs.Rip
   281  	rawRegs.Rsp = regs.Rsp
   282  	rawRegs.Rcx = regs.Rcx
   283  	return unix.PtraceSetRegs(threadID, &rawRegs)
   284  }
   285  
   286  // ReadTLS reads the offset from the beginning of the TLS block.
   287  func (c *rawClient) ReadTLS(threadID int, offset int32) (uint64, error) {
   288  	var rawRegs unix.PtraceRegs
   289  	if err := unix.PtraceGetRegs(threadID, &rawRegs); err != nil {
   290  		return 0, err
   291  	}
   292  
   293  	buff := make([]byte, 8)
   294  	if err := c.ReadMemory(rawRegs.Fs_base+uint64(offset), buff); err != nil {
   295  		return 0, err
   296  	}
   297  	return binary.LittleEndian.Uint64(buff), nil
   298  }
   299  
   300  // ContinueAndWait resumes the list of processes and waits until an event happens.
   301  func (c *rawClient) ContinueAndWait() (Event, error) {
   302  	return c.continueAndWait(0)
   303  }
   304  
   305  func (c *rawClient) continueAndWait(sig int) (Event, error) {
   306  	for _, threadID := range c.trappedThreadIDs {
   307  		if err := unix.PtraceCont(threadID, sig); err != nil {
   308  			return Event{}, err
   309  		}
   310  	}
   311  	c.trappedThreadIDs = nil
   312  
   313  	var status unix.WaitStatus
   314  	waitedThreadID, err := unix.Wait4(-1 /* any tracing thread */, &status, 0, nil)
   315  	if err != nil {
   316  		return Event{}, err
   317  	}
   318  
   319  	return c.handleWaitStatus(status, waitedThreadID)
   320  }
   321  
   322  // StepAndWait executes the single instruction of the specified process and waits until an event happens.
   323  // Note that an event happens to any children of the current process is reported.
   324  func (c *rawClient) StepAndWait(threadID int) (Event, error) {
   325  	if err := unix.PtraceSingleStep(threadID); err != nil {
   326  		return Event{}, err
   327  	}
   328  
   329  	for i, candidate := range c.trappedThreadIDs {
   330  		if candidate == threadID {
   331  			c.trappedThreadIDs = append(c.trappedThreadIDs[0:i], c.trappedThreadIDs[i+1:]...)
   332  		}
   333  	}
   334  
   335  	var status unix.WaitStatus
   336  	waitedThreadID, err := unix.Wait4(threadID, &status, unix.WNOTHREAD, nil)
   337  	if err != nil {
   338  		return Event{}, err
   339  	}
   340  
   341  	return c.handleWaitStatus(status, waitedThreadID)
   342  }
   343  
   344  func (c *rawClient) handleWaitStatus(status unix.WaitStatus, threadID int) (event Event, err error) {
   345  	if status.Stopped() {
   346  		c.trappedThreadIDs = append(c.trappedThreadIDs, threadID)
   347  
   348  		if status.StopSignal() == unix.SIGTRAP {
   349  			if status.TrapCause() == unix.PTRACE_EVENT_CLONE {
   350  				_, err := c.continueClone(threadID)
   351  				if err != nil {
   352  					return Event{}, err
   353  				}
   354  				return c.continueAndWait(0)
   355  			}
   356  
   357  			event = Event{Type: EventTypeTrapped, Data: []int{threadID}}
   358  		} else {
   359  			return c.continueAndWait(int(status.StopSignal()))
   360  		}
   361  	} else if status.Exited() {
   362  		event = Event{Type: EventTypeExited, Data: status.ExitStatus()}
   363  	} else if status.CoreDump() {
   364  		event = Event{Type: EventTypeCoreDump}
   365  	} else if status.Signaled() {
   366  		event = Event{Type: EventTypeTerminated, Data: int(status.Signal())}
   367  	}
   368  	return event, nil
   369  }
   370  
   371  func (c *rawClient) continueClone(parentThreadID int) (int, error) {
   372  	clonedThreadID, err := unix.PtraceGetEventMsg(parentThreadID)
   373  	if err != nil {
   374  		return 0, err
   375  	}
   376  	c.tracingThreadIDs = append(c.tracingThreadIDs, int(clonedThreadID))
   377  
   378  	// Cloned process may not exist yet.
   379  	if _, err := unix.Wait4(int(clonedThreadID), nil, 0, nil); err != nil {
   380  		return 0, err
   381  	}
   382  	err = unix.PtraceCont(int(clonedThreadID), 0)
   383  	return int(clonedThreadID), err
   384  }