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

View as plain text