Source file src/runtime/export_debug_test.go

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

View as plain text