github.com/ks888/tgo@v0.0.0-20190130135156-80bf89407292/tracer/controller.go (about) 1 package tracer 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "strings" 9 10 "github.com/ks888/tgo/debugapi" 11 "github.com/ks888/tgo/tracee" 12 "golang.org/x/arch/x86/x86asm" 13 ) 14 15 const chanBufferSize = 64 16 17 // ErrInterrupted indicates the tracer is interrupted due to the Interrupt() call. 18 var ErrInterrupted = errors.New("interrupted") 19 20 type breakpointHint int 21 22 const ( 23 // These hints are used to determine how to handle a go routine which hit a breakpoint. 24 // No need to cover all the breakpoints. 25 breakpointHintUnknown breakpointHint = iota 26 breakpointHintCall 27 breakpointHintDeferredFunc 28 ) 29 30 // Controller controls the associated tracee process. 31 type Controller struct { 32 process *tracee.Process 33 firstModuleDataAddr uint64 34 statusStore map[int64]goRoutineStatus 35 callInstAddrCache map[uint64][]uint64 36 37 breakpointHints map[uint64]breakpointHint 38 breakpoints Breakpoints 39 40 tracingPoints tracingPoints 41 tracingGoRoutines tracingGoRoutines 42 traceLevel int 43 parseLevel int 44 45 // Use the buffered channels to handle the requests to the controller asyncronously. 46 // It's because the tracee process must be trapped to handle these requests, but the process may not 47 // be trapped when the requests are sent. 48 interruptCh chan bool 49 pendingStartTracePoint chan uint64 50 pendingEndTracePoint chan uint64 51 // The traced data is written to this writer. 52 outputWriter io.Writer 53 } 54 55 type goRoutineStatus struct { 56 // This list include only the functions which hit the breakpoint before and so is not complete. 57 callingFunctions []callingFunction 58 } 59 60 func (status goRoutineStatus) usedStackSize() uint64 { 61 if len(status.callingFunctions) > 0 { 62 return status.callingFunctions[len(status.callingFunctions)-1].usedStackSize 63 } 64 65 return 0 66 } 67 68 type callingFunction struct { 69 *tracee.Function 70 returnAddress uint64 71 usedStackSize uint64 72 setCallInstBreakpoints bool 73 } 74 75 // NewController returns the new controller. 76 func NewController() *Controller { 77 return &Controller{ 78 outputWriter: os.Stdout, 79 statusStore: make(map[int64]goRoutineStatus), 80 breakpointHints: make(map[uint64]breakpointHint), 81 callInstAddrCache: make(map[uint64][]uint64), 82 interruptCh: make(chan bool, chanBufferSize), 83 pendingStartTracePoint: make(chan uint64, chanBufferSize), 84 pendingEndTracePoint: make(chan uint64, chanBufferSize), 85 } 86 } 87 88 // Attributes represents the tracee's attributes. 89 type Attributes tracee.Attributes 90 91 // LaunchTracee launches the new tracee process to be controlled. 92 func (c *Controller) LaunchTracee(name string, arg []string, attrs Attributes) error { 93 var err error 94 c.process, err = tracee.LaunchProcess(name, arg, tracee.Attributes(attrs)) 95 c.breakpoints = NewBreakpoints(c.process.SetBreakpoint, c.process.ClearBreakpoint) 96 return err 97 } 98 99 // AttachTracee attaches to the existing process. 100 func (c *Controller) AttachTracee(pid int, attrs Attributes) error { 101 var err error 102 c.process, err = tracee.AttachProcess(pid, tracee.Attributes(attrs)) 103 c.breakpoints = NewBreakpoints(c.process.SetBreakpoint, c.process.ClearBreakpoint) 104 return err 105 } 106 107 // AddStartTracePoint adds the starting point of the tracing. The go routines which passed one of the starting points before are traced. 108 func (c *Controller) AddStartTracePoint(startAddr uint64) error { 109 select { 110 case c.pendingStartTracePoint <- startAddr: 111 default: 112 // maybe buffer full 113 return errors.New("failed to add start trace point") 114 } 115 return nil 116 } 117 118 // AddEndTracePoint adds the ending point of the tracing. The go routines which passed one of the ending points are not traced anymore. 119 func (c *Controller) AddEndTracePoint(endAddr uint64) error { 120 select { 121 case c.pendingEndTracePoint <- endAddr: 122 default: 123 // maybe buffer full 124 return errors.New("failed to add end trace point") 125 } 126 return nil 127 } 128 129 // SetTraceLevel set the tracing level, which determines whether to print the traced info of the functions. 130 // The traced info is printed if the function is (directly or indirectly) called by the trace point function AND 131 // the stack depth is within the `level`. 132 // The depth here is the relative value from the point the tracing starts. 133 func (c *Controller) SetTraceLevel(level int) { 134 c.traceLevel = level 135 } 136 137 // SetParseLevel sets the parsing level, which determines how deeply the parser parses the value of args. 138 func (c *Controller) SetParseLevel(level int) { 139 c.parseLevel = level 140 } 141 142 // MainLoop repeatedly lets the tracee continue and then wait an event. It returns ErrInterrupted error if 143 // the trace ends due to the interrupt. 144 func (c *Controller) MainLoop() error { 145 defer c.process.Detach() // the connection status is unknown at this point 146 147 event, err := c.continueAndWait() 148 if err == ErrInterrupted { 149 return err 150 } else if err != nil { 151 return fmt.Errorf("failed to trace: %v", err) 152 } 153 154 for { 155 switch event.Type { 156 case debugapi.EventTypeExited: 157 return nil 158 case debugapi.EventTypeCoreDump: 159 return errors.New("the process exited due to core dump") 160 case debugapi.EventTypeTerminated: 161 return fmt.Errorf("the process exited due to signal %d", event.Data.(int)) 162 case debugapi.EventTypeTrapped: 163 trappedThreadIDs := event.Data.([]int) 164 event, err = c.handleTrapEvent(trappedThreadIDs) 165 if err == ErrInterrupted { 166 return err 167 } else if err != nil { 168 return fmt.Errorf("failed to trace: %v", err) 169 } 170 default: 171 return fmt.Errorf("unknown event: %v", event.Type) 172 } 173 } 174 } 175 176 // continueAndWait resumes the traced process and waits the process trapped again. 177 // It handles requests via channels before resuming. 178 func (c *Controller) continueAndWait() (debugapi.Event, error) { 179 select { 180 case <-c.interruptCh: 181 return debugapi.Event{}, ErrInterrupted 182 default: 183 if err := c.setPendingTracePoints(); err != nil { 184 return debugapi.Event{}, err 185 } 186 187 return c.process.ContinueAndWait() 188 } 189 } 190 191 func (c *Controller) setPendingTracePoints() error { 192 for { 193 select { 194 case startAddr := <-c.pendingStartTracePoint: 195 if c.tracingPoints.IsStartAddress(startAddr) { 196 continue // set already 197 } 198 199 if err := c.breakpoints.Set(startAddr); err != nil { 200 return err 201 } 202 c.tracingPoints.startAddressList = append(c.tracingPoints.startAddressList, startAddr) 203 204 case endAddr := <-c.pendingEndTracePoint: 205 if c.tracingPoints.IsEndAddress(endAddr) { 206 continue // set already 207 } 208 209 if err := c.breakpoints.Set(endAddr); err != nil { 210 return err 211 } 212 c.tracingPoints.endAddressList = append(c.tracingPoints.endAddressList, endAddr) 213 214 default: 215 return nil // no data 216 } 217 } 218 } 219 220 func (c *Controller) handleTrapEvent(trappedThreadIDs []int) (debugapi.Event, error) { 221 for i := 0; i < len(trappedThreadIDs); i++ { 222 threadID := trappedThreadIDs[i] 223 if err := c.handleTrapEventOfThread(threadID); err != nil { 224 return debugapi.Event{}, fmt.Errorf("failed to handle trap event (thread id: %d): %v", threadID, err) 225 } 226 } 227 228 return c.continueAndWait() 229 } 230 231 func (c *Controller) handleTrapEventOfThread(threadID int) error { 232 goRoutineInfo, err := c.process.CurrentGoRoutineInfo(threadID) 233 if err != nil || goRoutineInfo.ID == 0 { 234 return c.handleTrappedSystemRoutine(threadID) 235 } 236 237 breakpointAddr := goRoutineInfo.CurrentPC - 1 238 if !c.breakpoints.Hit(breakpointAddr, goRoutineInfo.ID) { 239 return c.handleTrapAtUnrelatedBreakpoint(threadID, breakpointAddr) 240 } 241 242 if err := c.updateTracingStatus(threadID, goRoutineInfo, breakpointAddr); err != nil { 243 return err 244 } 245 246 if !c.tracingGoRoutines.Tracing(goRoutineInfo.ID) { 247 return c.handleTrapAtUnrelatedBreakpoint(threadID, breakpointAddr) 248 } 249 250 status, _ := c.statusStore[goRoutineInfo.ID] 251 if status.usedStackSize() > goRoutineInfo.UsedStackSize { 252 if err := c.handleTrapAfterFunctionReturn(threadID, goRoutineInfo); err != nil { 253 return err 254 } 255 } 256 257 switch c.breakpointHints[breakpointAddr] { 258 case breakpointHintCall: 259 return c.handleTrapBeforeFunctionCall(threadID, goRoutineInfo) 260 case breakpointHintDeferredFunc: 261 return c.handleTrapAtDeferredFuncCall(threadID, goRoutineInfo) 262 default: 263 return c.handleTrapAtUnrelatedBreakpoint(threadID, breakpointAddr) 264 } 265 } 266 267 func (c *Controller) updateTracingStatus(threadID int, goRoutineInfo tracee.GoRoutineInfo, breakpointAddr uint64) error { 268 if c.tracingPoints.IsStartAddress(breakpointAddr) { 269 if err := c.enterTracepoint(threadID, goRoutineInfo); err != nil { 270 return err 271 } 272 } 273 if c.tracingPoints.IsEndAddress(breakpointAddr) { 274 return c.exitTracepoint(threadID, goRoutineInfo.ID, breakpointAddr) 275 } 276 return nil 277 } 278 279 func (c *Controller) enterTracepoint(threadID int, goRoutineInfo tracee.GoRoutineInfo) error { 280 goRoutineID := goRoutineInfo.ID 281 282 if err := c.setCallInstBreakpoints(goRoutineID, goRoutineInfo.CurrentPC); err != nil { 283 return err 284 } 285 286 c.tracingGoRoutines.Add(goRoutineID) 287 return nil 288 } 289 290 func (c *Controller) exitTracepoint(threadID int, goRoutineID int64, breakpointAddr uint64) error { 291 c.tracingGoRoutines.Remove(goRoutineID) 292 293 if !c.tracingGoRoutines.Tracing(goRoutineID) { 294 if err := c.breakpoints.ClearAllByGoRoutineID(goRoutineID); err != nil { 295 return err 296 } 297 } 298 299 return nil 300 } 301 302 func (c *Controller) setCallInstBreakpoints(goRoutineID int64, pc uint64) error { 303 return c.alterCallInstBreakpoints(true, goRoutineID, pc) 304 } 305 306 func (c *Controller) clearCallInstBreakpoints(goRoutineID int64, pc uint64) error { 307 return c.alterCallInstBreakpoints(false, goRoutineID, pc) 308 } 309 310 func (c *Controller) alterCallInstBreakpoints(enable bool, goRoutineID int64, pc uint64) error { 311 f, err := c.process.FindFunction(pc) 312 if err != nil { 313 return err 314 } 315 316 callInstAddresses, err := c.findCallInstAddresses(f) 317 if err != nil { 318 return err 319 } 320 321 for _, callInstAddr := range callInstAddresses { 322 if enable { 323 err = c.breakpoints.SetConditional(callInstAddr, goRoutineID) 324 c.breakpointHints[callInstAddr] = breakpointHintCall 325 } else { 326 err = c.breakpoints.ClearConditional(callInstAddr, goRoutineID) 327 } 328 if err != nil { 329 return err 330 } 331 } 332 333 return nil 334 } 335 336 func (c *Controller) handleTrappedSystemRoutine(threadID int) error { 337 threadInfo, err := c.process.CurrentThreadInfo(threadID) 338 if err != nil { 339 return err 340 } 341 342 breakpointAddr := threadInfo.CurrentPC - 1 343 return c.process.SingleStep(threadID, breakpointAddr) 344 } 345 346 func (c *Controller) handleTrapAtUnrelatedBreakpoint(threadID int, breakpointAddr uint64) error { 347 return c.process.SingleStep(threadID, breakpointAddr) 348 } 349 350 func (c *Controller) handleTrapBeforeFunctionCall(threadID int, goRoutineInfo tracee.GoRoutineInfo) error { 351 if err := c.process.SingleStep(threadID, goRoutineInfo.CurrentPC-1); err != nil { 352 return err 353 } 354 355 // Now the go routine jumped to the beginning of the function. 356 goRoutineInfo, err := c.process.CurrentGoRoutineInfo(threadID) 357 if err != nil { 358 return err 359 } 360 361 if err := c.updateTracingStatus(threadID, goRoutineInfo, goRoutineInfo.CurrentPC); err != nil { 362 return err 363 } 364 365 if !c.tracingGoRoutines.Tracing(goRoutineInfo.ID) { 366 return c.handleTrapAtUnrelatedBreakpoint(threadID, goRoutineInfo.CurrentPC) 367 } 368 369 return c.handleTrapAtFunctionCall(threadID, goRoutineInfo.CurrentPC, goRoutineInfo) 370 } 371 372 // handleTrapAtFunctionCall handles the trapped event at the function call. 373 // It needs `breakpointAddr` though it's usually same as the function's start address. 374 // It is because some function, such as runtime.duffzero, directly jumps to the middle of the function and 375 // the breakpoint address is not explicit in that case. 376 func (c *Controller) handleTrapAtFunctionCall(threadID int, breakpointAddr uint64, goRoutineInfo tracee.GoRoutineInfo) error { 377 stackFrame, err := c.currentStackFrame(goRoutineInfo) 378 if err != nil { 379 return err 380 } 381 382 // unwinded here in some cases: 383 // * just recovered from panic. 384 // * the last function used 'JMP' to call the next function and didn't change the SP. e.g. runtime.deferreturn 385 remainingFuncs, _, err := c.unwindFunctions(goRoutineInfo, goRoutineInfo.UsedStackSize) 386 if err != nil { 387 return err 388 } 389 390 currStackDepth := len(remainingFuncs) + 1 // add the currently calling function 391 callingFunc := callingFunction{ 392 Function: stackFrame.Function, 393 returnAddress: stackFrame.ReturnAddress, 394 usedStackSize: goRoutineInfo.UsedStackSize, 395 setCallInstBreakpoints: currStackDepth < c.traceLevel, 396 } 397 if err = c.addFunction(callingFunc, goRoutineInfo.ID); err != nil { 398 return err 399 } 400 401 if currStackDepth <= c.traceLevel && c.printableFunc(stackFrame.Function) { 402 if err := c.printFunctionInput(goRoutineInfo.ID, stackFrame, currStackDepth); err != nil { 403 return err 404 } 405 } 406 407 return c.process.SingleStep(threadID, breakpointAddr) 408 } 409 410 func (c *Controller) unwindFunctions(goRoutineInfo tracee.GoRoutineInfo, currUsedStackSize uint64) ([]callingFunction, []callingFunction, error) { 411 remainingFuncs, unwindedFuncs, err := c.doUnwindFunctions(goRoutineInfo, currUsedStackSize) 412 if err != nil { 413 return nil, nil, err 414 } 415 416 c.statusStore[goRoutineInfo.ID] = goRoutineStatus{callingFunctions: remainingFuncs} 417 return remainingFuncs, unwindedFuncs, nil 418 } 419 420 func (c *Controller) doUnwindFunctions(goRoutineInfo tracee.GoRoutineInfo, currUsedStackSize uint64) ([]callingFunction, []callingFunction, error) { 421 status, _ := c.statusStore[goRoutineInfo.ID] 422 callingFuncs := status.callingFunctions 423 424 for i := len(callingFuncs) - 1; i >= 0; i-- { 425 if callingFuncs[i].usedStackSize < currUsedStackSize { 426 return callingFuncs[0 : i+1], callingFuncs[i+1:], nil 427 428 } else if callingFuncs[i].usedStackSize == currUsedStackSize { 429 currFunction, err := c.process.FindFunction(goRoutineInfo.CurrentPC) 430 if err != nil { 431 return nil, nil, err 432 } 433 434 if callingFuncs[i].Name == currFunction.Name { 435 return callingFuncs[0 : i+1], callingFuncs[i+1:], nil 436 } 437 } 438 439 unwindFunc := callingFuncs[i] 440 if err := c.breakpoints.ClearConditional(unwindFunc.returnAddress, goRoutineInfo.ID); err != nil { 441 return nil, nil, err 442 } 443 444 if unwindFunc.setCallInstBreakpoints { 445 if err := c.clearCallInstBreakpoints(goRoutineInfo.ID, unwindFunc.StartAddr); err != nil { 446 return nil, nil, err 447 } 448 } 449 } 450 return nil, callingFuncs, nil 451 } 452 453 func (c *Controller) addFunction(newFunc callingFunction, goRoutineID int64) error { 454 status, _ := c.statusStore[goRoutineID] 455 c.statusStore[goRoutineID] = goRoutineStatus{callingFunctions: append(status.callingFunctions, newFunc)} 456 457 if err := c.breakpoints.SetConditional(newFunc.returnAddress, goRoutineID); err != nil { 458 return err 459 } 460 461 if newFunc.setCallInstBreakpoints { 462 return c.setCallInstBreakpoints(goRoutineID, newFunc.StartAddr) 463 } 464 return nil 465 } 466 467 func (c *Controller) handleTrapAtDeferredFuncCall(threadID int, goRoutineInfo tracee.GoRoutineInfo) error { 468 if goRoutineInfo.Panicking && goRoutineInfo.PanicHandler != nil { 469 _, _, err := c.unwindFunctions(goRoutineInfo, goRoutineInfo.PanicHandler.UsedStackSizeAtDefer) 470 if err != nil { 471 return err 472 } 473 } 474 475 if err := c.handleTrapAtFunctionCall(threadID, goRoutineInfo.CurrentPC-1, goRoutineInfo); err != nil { 476 return err 477 } 478 479 return c.breakpoints.ClearConditional(goRoutineInfo.CurrentPC-1, goRoutineInfo.ID) 480 } 481 482 func (c *Controller) handleTrapAfterFunctionReturn(threadID int, goRoutineInfo tracee.GoRoutineInfo) error { 483 remainingFuncs, unwindedFuncs, err := c.unwindFunctions(goRoutineInfo, goRoutineInfo.UsedStackSize) 484 if err != nil { 485 return err 486 } 487 returnedFunc := unwindedFuncs[0].Function 488 489 currStackDepth := len(remainingFuncs) + 1 // include returnedFunc for now 490 prevStackFrame, err := c.prevStackFrame(goRoutineInfo, returnedFunc.StartAddr) 491 if err != nil { 492 return err 493 } 494 495 if currStackDepth <= c.traceLevel && prevStackFrame.Function.Name == "runtime.deferproc" { 496 if err := c.setBreakpointToDeferredFunc(goRoutineInfo); err != nil { 497 return err 498 } 499 } 500 501 if currStackDepth <= c.traceLevel && c.printableFunc(returnedFunc) { 502 if err := c.printFunctionOutput(goRoutineInfo.ID, prevStackFrame, currStackDepth); err != nil { 503 return err 504 } 505 } 506 507 return nil 508 } 509 510 func (c *Controller) setBreakpointToDeferredFunc(goRoutineInfo tracee.GoRoutineInfo) error { 511 nextAddr := goRoutineInfo.NextDeferFuncAddr 512 if nextAddr == 0x0 /* no deferred func */ { 513 return nil 514 } 515 516 if err := c.breakpoints.SetConditional(nextAddr, goRoutineInfo.ID); err != nil { 517 return err 518 } 519 c.breakpointHints[nextAddr] = breakpointHintDeferredFunc 520 return nil 521 } 522 523 // It must be called at the beginning of the function due to the StackFrameAt's constraint. 524 func (c *Controller) currentStackFrame(goRoutineInfo tracee.GoRoutineInfo) (*tracee.StackFrame, error) { 525 return c.process.StackFrameAt(goRoutineInfo.CurrentStackAddr, goRoutineInfo.CurrentPC) 526 } 527 528 // It must be called at return address due to the StackFrameAt's constraint. 529 func (c *Controller) prevStackFrame(goRoutineInfo tracee.GoRoutineInfo, rip uint64) (*tracee.StackFrame, error) { 530 return c.process.StackFrameAt(goRoutineInfo.CurrentStackAddr-8, rip) 531 } 532 533 func (c *Controller) printableFunc(f *tracee.Function) bool { 534 const runtimePkgPrefix = "runtime." 535 if strings.HasPrefix(f.Name, runtimePkgPrefix) { 536 // it may be ok to print runtime unexported functions, but 537 // these functions tend to be verbose and confusing. 538 return f.IsExported() 539 } 540 541 return true 542 } 543 544 func (c *Controller) printFunctionInput(goRoutineID int64, stackFrame *tracee.StackFrame, depth int) error { 545 var inputArgs []string 546 for _, arg := range stackFrame.InputArguments { 547 inputArgs = append(inputArgs, arg.ParseValue(c.parseLevel)) 548 } 549 550 var outputArgs string 551 if len(stackFrame.OutputArguments) > 0 { 552 outputArgs = "..." 553 } 554 555 fmt.Fprintf(c.outputWriter, "%s\\ (#%02d) %s(%s) (%s)\n", strings.Repeat("|", depth-1), goRoutineID, stackFrame.Function.Name, strings.Join(inputArgs, ", "), outputArgs) 556 557 return nil 558 } 559 560 func (c *Controller) printFunctionOutput(goRoutineID int64, stackFrame *tracee.StackFrame, depth int) error { 561 var inputArgs []string 562 for _, arg := range stackFrame.InputArguments { 563 inputArgs = append(inputArgs, arg.ParseValue(c.parseLevel)) 564 } 565 566 var outputArgs []string 567 for _, arg := range stackFrame.OutputArguments { 568 outputArgs = append(outputArgs, arg.ParseValue(c.parseLevel)) 569 } 570 fmt.Fprintf(c.outputWriter, "%s/ (#%02d) %s(%s) (%s)\n", strings.Repeat("|", depth-1), goRoutineID, stackFrame.Function.Name, strings.Join(inputArgs, ", "), strings.Join(outputArgs, ", ")) 571 572 return nil 573 } 574 575 func (c *Controller) findCallInstAddresses(f *tracee.Function) ([]uint64, error) { 576 // this cache is not only efficient, but required because there are no call insts if breakpoints are set. 577 if cache, ok := c.callInstAddrCache[f.StartAddr]; ok { 578 return cache, nil 579 } 580 581 insts, err := c.process.ReadInstructions(f) 582 if err != nil { 583 return nil, err 584 } 585 586 var pos int 587 var addresses []uint64 588 for _, inst := range insts { 589 if inst.Op == x86asm.CALL || inst.Op == x86asm.LCALL { 590 addresses = append(addresses, f.StartAddr+uint64(pos)) 591 } 592 pos += inst.Len 593 } 594 595 c.callInstAddrCache[f.StartAddr] = addresses 596 return addresses, nil 597 } 598 599 // Interrupt interrupts the main loop. 600 func (c *Controller) Interrupt() { 601 c.interruptCh <- true 602 }