github.com/x04/go/src@v0.0.0-20200202162449-3d481ceb3525/runtime/export_debug_test.go (about)

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