mirror of
https://github.com/golang/go
synced 2024-10-01 22:08:32 -06:00
c5ed10f3be
This adds a mechanism for debuggers to safely inject calls to Go functions on amd64. Debuggers must participate in a protocol with the runtime, and need to know how to lay out a call frame, but the runtime support takes care of the details of handling live pointers in registers, stack growth, and detecting the trickier conditions when it is unsafe to inject a user function call. Fixes #21678. Updates derekparker/delve#119. Change-Id: I56d8ca67700f1f77e19d89e7fc92ab337b228834 Reviewed-on: https://go-review.googlesource.com/109699 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
95 lines
2.4 KiB
Go
95 lines
2.4 KiB
Go
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build amd64
|
|
|
|
package runtime
|
|
|
|
import "unsafe"
|
|
|
|
const (
|
|
debugCallSystemStack = "executing on Go runtime stack"
|
|
debugCallUnknownFunc = "call from unknown function"
|
|
debugCallRuntime = "call from within the Go runtime"
|
|
debugCallUnsafePoint = "call not at safe point"
|
|
)
|
|
|
|
func debugCallV1()
|
|
func debugCallPanicked(val interface{})
|
|
|
|
// debugCallCheck checks whether it is safe to inject a debugger
|
|
// function call with return PC pc. If not, it returns a string
|
|
// explaining why.
|
|
//
|
|
//go:nosplit
|
|
func debugCallCheck(pc uintptr) string {
|
|
// No user calls from the system stack.
|
|
if getg() != getg().m.curg {
|
|
return debugCallSystemStack
|
|
}
|
|
if sp := getcallersp(); !(getg().stack.lo < sp && sp <= getg().stack.hi) {
|
|
// Fast syscalls (nanotime) and racecall switch to the
|
|
// g0 stack without switching g. We can't safely make
|
|
// a call in this state. (We can't even safely
|
|
// systemstack.)
|
|
return debugCallSystemStack
|
|
}
|
|
|
|
// Switch to the system stack to avoid overflowing the user
|
|
// stack.
|
|
var ret string
|
|
systemstack(func() {
|
|
f := findfunc(pc)
|
|
if !f.valid() {
|
|
ret = debugCallUnknownFunc
|
|
return
|
|
}
|
|
|
|
// Disallow calls from the runtime. We could
|
|
// potentially make this condition tighter (e.g., not
|
|
// when locks are held), but there are enough tightly
|
|
// coded sequences (e.g., defer handling) that it's
|
|
// better to play it safe.
|
|
if name, pfx := funcname(f), "runtime."; len(name) > len(pfx) && name[:len(pfx)] == pfx {
|
|
ret = debugCallRuntime
|
|
return
|
|
}
|
|
|
|
// Look up PC's register map.
|
|
pcdata := int32(-1)
|
|
if pc != f.entry {
|
|
pc--
|
|
pcdata = pcdatavalue(f, _PCDATA_RegMapIndex, pc, nil)
|
|
}
|
|
if pcdata == -1 {
|
|
pcdata = 0 // in prologue
|
|
}
|
|
stkmap := (*stackmap)(funcdata(f, _FUNCDATA_RegPointerMaps))
|
|
if pcdata == -2 || stkmap == nil {
|
|
// Not at a safe point.
|
|
ret = debugCallUnsafePoint
|
|
return
|
|
}
|
|
})
|
|
return ret
|
|
}
|
|
|
|
// debugCallWrap pushes a defer to recover from panics in debug calls
|
|
// and then calls the dispatching function at PC dispatch.
|
|
func debugCallWrap(dispatch uintptr) {
|
|
var dispatchF func()
|
|
dispatchFV := funcval{dispatch}
|
|
*(*unsafe.Pointer)(unsafe.Pointer(&dispatchF)) = noescape(unsafe.Pointer(&dispatchFV))
|
|
|
|
var ok bool
|
|
defer func() {
|
|
if !ok {
|
|
err := recover()
|
|
debugCallPanicked(err)
|
|
}
|
|
}()
|
|
dispatchF()
|
|
ok = true
|
|
}
|