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 }