Black Lives Matter. Support the Equal Justice Initiative.

Source file src/runtime/export_debug_test.go

Documentation: runtime

     1  // Copyright 2018 The Go 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  //go:build amd64 && linux
     6  // +build amd64,linux
     7  
     8  package runtime
     9  
    10  import (
    11  	"internal/abi"
    12  	"runtime/internal/sys"
    13  	"unsafe"
    14  )
    15  
    16  // InjectDebugCall injects a debugger call to fn into g. regArgs must
    17  // contain any arguments to fn that are passed in registers, according
    18  // to the internal Go ABI. It may be nil if no arguments are passed in
    19  // registers to fn. args must be a pointer to a valid call frame (including
    20  // arguments and return space) for fn, or nil. tkill must be a function that
    21  // will send SIGTRAP to thread ID tid. gp must be locked to its OS thread and
    22  // running.
    23  //
    24  // On success, InjectDebugCall returns the panic value of fn or nil.
    25  // If fn did not panic, its results will be available in args.
    26  func InjectDebugCall(gp *g, fn interface{}, regArgs *abi.RegArgs, stackArgs interface{}, tkill func(tid int) error, returnOnUnsafePoint bool) (interface{}, error) {
    27  	if gp.lockedm == 0 {
    28  		return nil, plainError("goroutine not locked to thread")
    29  	}
    30  
    31  	tid := int(gp.lockedm.ptr().procid)
    32  	if tid == 0 {
    33  		return nil, plainError("missing tid")
    34  	}
    35  
    36  	f := efaceOf(&fn)
    37  	if f._type == nil || f._type.kind&kindMask != kindFunc {
    38  		return nil, plainError("fn must be a function")
    39  	}
    40  	fv := (*funcval)(f.data)
    41  
    42  	a := efaceOf(&stackArgs)
    43  	if a._type != nil && a._type.kind&kindMask != kindPtr {
    44  		return nil, plainError("args must be a pointer or nil")
    45  	}
    46  	argp := a.data
    47  	var argSize uintptr
    48  	if argp != nil {
    49  		argSize = (*ptrtype)(unsafe.Pointer(a._type)).elem.size
    50  	}
    51  
    52  	h := new(debugCallHandler)
    53  	h.gp = gp
    54  	// gp may not be running right now, but we can still get the M
    55  	// it will run on since it's locked.
    56  	h.mp = gp.lockedm.ptr()
    57  	h.fv, h.regArgs, h.argp, h.argSize = fv, regArgs, argp, argSize
    58  	h.handleF = h.handle // Avoid allocating closure during signal
    59  
    60  	defer func() { testSigtrap = nil }()
    61  	for i := 0; ; i++ {
    62  		testSigtrap = h.inject
    63  		noteclear(&h.done)
    64  		h.err = ""
    65  
    66  		if err := tkill(tid); err != nil {
    67  			return nil, err
    68  		}
    69  		// Wait for completion.
    70  		notetsleepg(&h.done, -1)
    71  		if h.err != "" {
    72  			switch h.err {
    73  			case "call not at safe point":
    74  				if returnOnUnsafePoint {
    75  					// This is for TestDebugCallUnsafePoint.
    76  					return nil, h.err
    77  				}
    78  				fallthrough
    79  			case "retry _Grunnable", "executing on Go runtime stack", "call from within the Go runtime":
    80  				// These are transient states. Try to get out of them.
    81  				if i < 100 {
    82  					usleep(100)
    83  					Gosched()
    84  					continue
    85  				}
    86  			}
    87  			return nil, h.err
    88  		}
    89  		return h.panic, nil
    90  	}
    91  }
    92  
    93  type debugCallHandler struct {
    94  	gp      *g
    95  	mp      *m
    96  	fv      *funcval
    97  	regArgs *abi.RegArgs
    98  	argp    unsafe.Pointer
    99  	argSize uintptr
   100  	panic   interface{}
   101  
   102  	handleF func(info *siginfo, ctxt *sigctxt, gp2 *g) bool
   103  
   104  	err       plainError
   105  	done      note
   106  	savedRegs sigcontext
   107  	savedFP   fpstate1
   108  }
   109  
   110  func (h *debugCallHandler) inject(info *siginfo, ctxt *sigctxt, gp2 *g) bool {
   111  	switch h.gp.atomicstatus {
   112  	case _Grunning:
   113  		if getg().m != h.mp {
   114  			println("trap on wrong M", getg().m, h.mp)
   115  			return false
   116  		}
   117  		// Push current PC on the stack.
   118  		rsp := ctxt.rsp() - sys.PtrSize
   119  		*(*uint64)(unsafe.Pointer(uintptr(rsp))) = ctxt.rip()
   120  		ctxt.set_rsp(rsp)
   121  		// Write the argument frame size.
   122  		*(*uintptr)(unsafe.Pointer(uintptr(rsp - 16))) = h.argSize
   123  		// Save current registers.
   124  		h.savedRegs = *ctxt.regs()
   125  		h.savedFP = *h.savedRegs.fpstate
   126  		h.savedRegs.fpstate = nil
   127  		// Set PC to debugCallV2.
   128  		ctxt.set_rip(uint64(funcPC(debugCallV2)))
   129  		// Call injected. Switch to the debugCall protocol.
   130  		testSigtrap = h.handleF
   131  	case _Grunnable:
   132  		// Ask InjectDebugCall to pause for a bit and then try
   133  		// again to interrupt this goroutine.
   134  		h.err = plainError("retry _Grunnable")
   135  		notewakeup(&h.done)
   136  	default:
   137  		h.err = plainError("goroutine in unexpected state at call inject")
   138  		notewakeup(&h.done)
   139  	}
   140  	// Resume execution.
   141  	return true
   142  }
   143  
   144  func (h *debugCallHandler) handle(info *siginfo, ctxt *sigctxt, gp2 *g) bool {
   145  	// Sanity check.
   146  	if getg().m != h.mp {
   147  		println("trap on wrong M", getg().m, h.mp)
   148  		return false
   149  	}
   150  	f := findfunc(uintptr(ctxt.rip()))
   151  	if !(hasPrefix(funcname(f), "runtime.debugCall") || hasPrefix(funcname(f), "debugCall")) {
   152  		println("trap in unknown function", funcname(f))
   153  		return false
   154  	}
   155  	if *(*byte)(unsafe.Pointer(uintptr(ctxt.rip() - 1))) != 0xcc {
   156  		println("trap at non-INT3 instruction pc =", hex(ctxt.rip()))
   157  		return false
   158  	}
   159  
   160  	switch status := ctxt.r12(); status {
   161  	case 0:
   162  		// Frame is ready. Copy the arguments to the frame and to registers.
   163  		sp := ctxt.rsp()
   164  		memmove(unsafe.Pointer(uintptr(sp)), h.argp, h.argSize)
   165  		if h.regArgs != nil {
   166  			storeRegArgs(ctxt.regs(), h.regArgs)
   167  		}
   168  		// Push return PC.
   169  		sp -= sys.PtrSize
   170  		ctxt.set_rsp(sp)
   171  		*(*uint64)(unsafe.Pointer(uintptr(sp))) = ctxt.rip()
   172  		// Set PC to call and context register.
   173  		ctxt.set_rip(uint64(h.fv.fn))
   174  		ctxt.regs().rdx = uint64(uintptr(unsafe.Pointer(h.fv)))
   175  	case 1:
   176  		// Function returned. Copy frame and result registers back out.
   177  		sp := ctxt.rsp()
   178  		memmove(h.argp, unsafe.Pointer(uintptr(sp)), h.argSize)
   179  		if h.regArgs != nil {
   180  			loadRegArgs(h.regArgs, ctxt.regs())
   181  		}
   182  	case 2:
   183  		// Function panicked. Copy panic out.
   184  		sp := ctxt.rsp()
   185  		memmove(unsafe.Pointer(&h.panic), unsafe.Pointer(uintptr(sp)), 2*sys.PtrSize)
   186  	case 8:
   187  		// Call isn't safe. Get the reason.
   188  		sp := ctxt.rsp()
   189  		reason := *(*string)(unsafe.Pointer(uintptr(sp)))
   190  		h.err = plainError(reason)
   191  		// Don't wake h.done. We need to transition to status 16 first.
   192  	case 16:
   193  		// Restore all registers except RIP and RSP.
   194  		rip, rsp := ctxt.rip(), ctxt.rsp()
   195  		fp := ctxt.regs().fpstate
   196  		*ctxt.regs() = h.savedRegs
   197  		ctxt.regs().fpstate = fp
   198  		*fp = h.savedFP
   199  		ctxt.set_rip(rip)
   200  		ctxt.set_rsp(rsp)
   201  		// Done
   202  		notewakeup(&h.done)
   203  	default:
   204  		h.err = plainError("unexpected debugCallV2 status")
   205  		notewakeup(&h.done)
   206  	}
   207  	// Resume execution.
   208  	return true
   209  }
   210  

View as plain text