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  }