delve/pkg/proc/target_exec.go

1066 lines
30 KiB
Go
Raw Normal View History

2015-06-12 19:49:23 +00:00
package proc
import (
"bytes"
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
"debug/dwarf"
"errors"
"fmt"
"go/ast"
"go/token"
"path/filepath"
"strings"
*: Go 1.14 support branch (#1727) * tests: misc test fixes for go1.14 - math.go is now ambiguous due to changes to the go runtime so specify that we mean our own math.go in _fixtures - go list -m requires vendor-mode to be disabled so pass '-mod=' to it in case user has GOFLAGS=-mod=vendor - update version of go/packages, required to work with go 1.14 (and executed go mod vendor) - Increased goroutine migration in one development version of Go 1.14 revealed a problem with TestCheckpoints in command_test.go and rr_test.go. The tests were always wrong because Restart(checkpoint) doesn't change the current thread but we can't assume that when the checkpoint was taken the current goroutine was running on the same thread. * goversion: update maximum supported version * Makefile: disable testing lldb-server backend on linux with Go 1.14 There seems to be some incompatibility with lldb-server version 6.0.0 on linux and Go 1.14. * proc/gdbserial: better handling of signals - if multiple signals are received simultaneously propagate all of them to the target threads instead of only one. - debugserver will drop an interrupt request if a target thread simultaneously receives a signal, handle this situation. * dwarf/line: normalize backslashes for windows executables Starting with Go 1.14 the compiler sometimes emits backslashes as well as forward slashes in debug_line, normalize everything to / for conformity with the behavior of previous versions. * proc/native: partial support for Windows async preempt mechanism See https://github.com/golang/go/issues/36494 for a description of why full support for 1.14 under windows is problematic. * proc/native: disable Go 1.14 async preemption on Windows See https://github.com/golang/go/issues/36494
2020-02-11 01:31:54 +00:00
"github.com/go-delve/delve/pkg/astutil"
"github.com/go-delve/delve/pkg/dwarf/reader"
)
const maxSkipAutogeneratedWrappers = 5 // maximum recursion depth for skipAutogeneratedWrappers
// ErrNoSourceForPC is returned when the given address
// does not correspond with a source file location.
type ErrNoSourceForPC struct {
pc uint64
}
func (err *ErrNoSourceForPC) Error() string {
return fmt.Sprintf("no source for PC %#x", err.pc)
}
2016-01-10 08:57:52 +00:00
// Next continues execution until the next source line.
func (dbp *Target) Next() (err error) {
if _, err := dbp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting")
2015-04-25 02:56:57 +00:00
}
proc: support inlining Go 1.10 added inlined calls to debug_info, this commit adds support for DW_TAG_inlined_call to delve, both for stack traces (where inlined calls will appear as normal stack frames) and to correct the behavior of next, step and stepout. The calls to Next and Frame of stackIterator continue to work unchanged and only return real stack frames, after reading each line appendInlinedCalls is called to unpacked all the inlined calls that involve the current PC. The fake stack frames produced by appendInlinedCalls are distinguished from real stack frames by having the Inlined attribute set to true. Also their Current and Call locations are treated differently. The Call location will be changed to represent the position inside the inlined call, while the Current location will always reference the real stack frame. This is done because: * next, step and stepout need to access the debug_info entry of the real function they are stepping through * we are already manipulating Call in different ways while Current is just what we read from the call stack The strategy remains mostly the same, we disassemble the function and we set a breakpoint on each instruction corresponding to a different file:line. The function in question will be the one corresponding to the first real (i.e. non-inlined) stack frame. * If the current function contains inlined calls, 'next' will not set any breakpoints on instructions that belong to inlined calls. We do not do this for 'step'. * If we are inside an inlined call that makes other inlined functions, 'next' will not set any breakpoints that belong to inlined calls that are children of the current inlined call. * If the current function is inlined the breakpoint on the return address won't be set, because inlined frames don't have a return address. * The code we use for stepout doesn't work at all if we are inside an inlined call, instead we call 'next' but instruct it to remove all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
if err = next(dbp, false, false); err != nil {
dbp.ClearSteppingBreakpoints()
return
}
return dbp.Continue()
}
2016-01-10 08:57:52 +00:00
// Continue continues execution of the debugged
// process. It will continue until it hits a breakpoint
// or is otherwise stopped.
func (dbp *Target) Continue() error {
if _, err := dbp.Valid(); err != nil {
return err
}
for _, thread := range dbp.ThreadList() {
thread.Common().CallReturn = false
thread.Common().returnValues = nil
}
dbp.CheckAndClearManualStopRequest()
defer func() {
// Make sure we clear internal breakpoints if we simultaneously receive a
// manual stop request and hit a breakpoint.
if dbp.CheckAndClearManualStopRequest() {
dbp.StopReason = StopManual
dbp.ClearSteppingBreakpoints()
}
}()
for {
if dbp.CheckAndClearManualStopRequest() {
dbp.StopReason = StopManual
dbp.ClearSteppingBreakpoints()
return nil
}
2021-07-02 16:37:55 +00:00
dbp.ClearCaches()
trapthread, stopReason, contOnceErr := dbp.proc.ContinueOnce()
dbp.StopReason = stopReason
threads := dbp.ThreadList()
for _, thread := range threads {
if thread.Breakpoint().Breakpoint != nil {
thread.Breakpoint().Breakpoint.checkCondition(dbp, thread, thread.Breakpoint())
}
}
if contOnceErr != nil {
// Attempt to refresh status of current thread/current goroutine, see
// Issue #2078.
// Errors are ignored because depending on why ContinueOnce failed this
// might very well not work.
if valid, _ := dbp.Valid(); valid {
if trapthread != nil {
_ = dbp.SwitchThread(trapthread.ThreadID())
} else if curth := dbp.CurrentThread(); curth != nil {
dbp.selectedGoroutine, _ = GetG(curth)
}
}
if pe, ok := contOnceErr.(ErrProcessExited); ok {
dbp.exitStatus = pe.Status
}
return contOnceErr
}
if dbp.StopReason == StopLaunched {
dbp.ClearSteppingBreakpoints()
}
callInjectionDone, callErr := callInjectionProtocol(dbp, threads)
// callErr check delayed until after pickCurrentThread, which must always
// happen, otherwise the debugger could be left in an inconsistent
// state.
if err := pickCurrentThread(dbp, trapthread, threads); err != nil {
2015-07-10 19:48:45 +00:00
return err
}
if callErr != nil {
return callErr
}
curthread := dbp.CurrentThread()
curbp := curthread.Breakpoint()
2015-11-24 21:48:28 +00:00
switch {
case curbp.Breakpoint == nil:
// runtime.Breakpoint, manual stop or debugCallV1-related stop
recorded, _ := dbp.Recorded()
if recorded {
return conditionErrors(threads)
}
loc, err := curthread.Location()
if err != nil || loc.Fn == nil {
return conditionErrors(threads)
}
g, _ := GetG(curthread)
arch := dbp.BinInfo().Arch
switch {
case loc.Fn.Name == "runtime.breakpoint":
// In linux-arm64, PtraceSingleStep seems cannot step over BRK instruction
// (linux-arm64 feature or kernel bug maybe).
if !arch.BreakInstrMovesPC() {
setPC(curthread, loc.PC+uint64(arch.BreakpointSize()))
}
// Single-step current thread until we exit runtime.breakpoint and
// runtime.Breakpoint.
// On go < 1.8 it was sufficient to single-step twice on go1.8 a change
// to the compiler requires 4 steps.
if err := stepInstructionOut(dbp, curthread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil {
return err
}
dbp.StopReason = StopHardcodedBreakpoint
return conditionErrors(threads)
case g == nil || dbp.fncallForG[g.ID] == nil:
// a hardcoded breakpoint somewhere else in the code (probably cgo), or manual stop in cgo
if !arch.BreakInstrMovesPC() {
bpsize := arch.BreakpointSize()
bp := make([]byte, bpsize)
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
_, err = dbp.Memory().ReadMemory(bp, loc.PC)
if bytes.Equal(bp, arch.BreakpointInstruction()) {
setPC(curthread, loc.PC+uint64(bpsize))
}
}
return conditionErrors(threads)
2015-03-01 04:03:26 +00:00
}
case curbp.Active && curbp.Stepping:
if curbp.SteppingInto {
// See description of proc.(*Process).next for the meaning of StepBreakpoints
if err := conditionErrors(threads); err != nil {
return err
}
if dbp.GetDirection() == Forward {
text, err := disassembleCurrentInstruction(dbp, curthread, 0)
if err != nil {
return err
}
var fn *Function
if loc, _ := curthread.Location(); loc != nil {
fn = loc.Fn
}
// here we either set a breakpoint into the destination of the CALL
// instruction or we determined that the called function is hidden,
// either way we need to resume execution
if err = setStepIntoBreakpoint(dbp, fn, text, sameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
return err
}
} else {
if err := dbp.ClearSteppingBreakpoints(); err != nil {
return err
}
return dbp.StepInstruction()
}
} else {
2021-07-02 16:37:55 +00:00
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread)
if err := dbp.ClearSteppingBreakpoints(); err != nil {
return err
}
dbp.StopReason = StopNextFinished
return conditionErrors(threads)
}
case curbp.Active:
onNextGoroutine, err := onNextGoroutine(dbp, curthread, dbp.Breakpoints())
if err != nil {
return err
}
if onNextGoroutine {
err := dbp.ClearSteppingBreakpoints()
if err != nil {
return err
}
}
if curbp.Name == UnrecoveredPanic {
dbp.ClearSteppingBreakpoints()
}
dbp.StopReason = StopBreakpoint
if curbp.Breakpoint.WatchType != 0 {
dbp.StopReason = StopWatchpoint
}
return conditionErrors(threads)
2015-11-24 21:48:28 +00:00
default:
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
2015-03-01 04:03:26 +00:00
}
if callInjectionDone {
// a call injection was finished, don't let a breakpoint with a failed
// condition or a step breakpoint shadow this.
dbp.StopReason = StopCallReturned
return conditionErrors(threads)
}
2015-11-24 21:48:28 +00:00
}
}
func conditionErrors(threads []Thread) error {
var condErr error
for _, th := range threads {
if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil {
if condErr == nil {
condErr = bp.CondError
} else {
return fmt.Errorf("multiple errors evaluating conditions")
}
}
}
return condErr
}
2017-02-08 00:23:47 +00:00
// pick a new dbp.currentThread, with the following priority:
// - a thread with onTriggeredInternalBreakpoint() == true
2015-11-24 21:48:28 +00:00
// - a thread with onTriggeredBreakpoint() == true (prioritizing trapthread)
// - trapthread
func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error {
for _, th := range threads {
if bp := th.Breakpoint(); bp.Active && bp.Stepping {
return dbp.SwitchThread(th.ThreadID())
2015-11-24 21:48:28 +00:00
}
}
if bp := trapthread.Breakpoint(); bp.Active {
return dbp.SwitchThread(trapthread.ThreadID())
2015-11-24 21:48:28 +00:00
}
for _, th := range threads {
if bp := th.Breakpoint(); bp.Active {
return dbp.SwitchThread(th.ThreadID())
2015-11-24 21:48:28 +00:00
}
}
return dbp.SwitchThread(trapthread.ThreadID())
}
func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmInstruction, error) {
regs, err := thread.Registers()
if err != nil {
return nil, err
}
pc := regs.PC() + uint64(off)
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
return disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()), true)
}
// stepInstructionOut repeatedly calls StepInstruction until the current
// function is neither fnname1 or fnname2.
// This function is used to step out of runtime.Breakpoint as well as
// runtime.debugCallV1.
func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string) error {
2021-07-02 16:37:55 +00:00
defer dbp.ClearCaches()
for {
if err := curthread.StepInstruction(); err != nil {
return err
}
loc, err := curthread.Location()
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
var locFnName string
if loc.Fn != nil {
locFnName = loc.Fn.Name
// Calls to runtime.Breakpoint are inlined in some versions of Go when
// inlining is enabled. Here we attempt to resolve any inlining.
dwarfTree, _ := loc.Fn.cu.image.getDwarfTree(loc.Fn.offset)
if dwarfTree != nil {
inlstack := reader.InlineStack(dwarfTree, loc.PC)
if len(inlstack) > 0 {
if locFnName2, ok := inlstack[0].Val(dwarf.AttrName).(string); ok {
locFnName = locFnName2
}
}
}
}
if err != nil || loc.Fn == nil || (locFnName != fnname1 && locFnName != fnname2) {
g, _ := GetG(curthread)
selg := dbp.SelectedGoroutine()
if g != nil && selg != nil && g.ID == selg.ID {
selg.CurrentLoc = *loc
}
return curthread.SetCurrentBreakpoint(true)
}
}
}
// Step will continue until another source line is reached.
// Will step into functions.
func (dbp *Target) Step() (err error) {
if _, err := dbp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting")
}
proc: support inlining Go 1.10 added inlined calls to debug_info, this commit adds support for DW_TAG_inlined_call to delve, both for stack traces (where inlined calls will appear as normal stack frames) and to correct the behavior of next, step and stepout. The calls to Next and Frame of stackIterator continue to work unchanged and only return real stack frames, after reading each line appendInlinedCalls is called to unpacked all the inlined calls that involve the current PC. The fake stack frames produced by appendInlinedCalls are distinguished from real stack frames by having the Inlined attribute set to true. Also their Current and Call locations are treated differently. The Call location will be changed to represent the position inside the inlined call, while the Current location will always reference the real stack frame. This is done because: * next, step and stepout need to access the debug_info entry of the real function they are stepping through * we are already manipulating Call in different ways while Current is just what we read from the call stack The strategy remains mostly the same, we disassemble the function and we set a breakpoint on each instruction corresponding to a different file:line. The function in question will be the one corresponding to the first real (i.e. non-inlined) stack frame. * If the current function contains inlined calls, 'next' will not set any breakpoints on instructions that belong to inlined calls. We do not do this for 'step'. * If we are inside an inlined call that makes other inlined functions, 'next' will not set any breakpoints that belong to inlined calls that are children of the current inlined call. * If the current function is inlined the breakpoint on the return address won't be set, because inlined frames don't have a return address. * The code we use for stepout doesn't work at all if we are inside an inlined call, instead we call 'next' but instruct it to remove all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
if err = next(dbp, true, false); err != nil {
_ = dbp.ClearSteppingBreakpoints()
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
return err
}
if bpstate := dbp.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && dbp.GetDirection() == Backward {
dbp.ClearSteppingBreakpoints()
return dbp.StepInstruction()
}
return dbp.Continue()
}
// sameGoroutineCondition returns an expression that evaluates to true when
// the current goroutine is g.
func sameGoroutineCondition(g *G) ast.Expr {
if g == nil {
return nil
}
return astutil.Eql(astutil.Sel(astutil.PkgVar("runtime", "curg"), "goid"), astutil.Int(int64(g.ID)))
}
func frameoffCondition(frame *Stackframe) ast.Expr {
return astutil.Eql(astutil.PkgVar("runtime", "frameoff"), astutil.Int(frame.FrameOffset()))
}
// StepOut will continue until the current goroutine exits the
// function currently being executed or a deferred function is executed
func (dbp *Target) StepOut() error {
backward := dbp.GetDirection() == Backward
if _, err := dbp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting")
}
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
topframe, retframe, err := topframe(selg, curthread)
if err != nil {
return err
}
proc: support inlining Go 1.10 added inlined calls to debug_info, this commit adds support for DW_TAG_inlined_call to delve, both for stack traces (where inlined calls will appear as normal stack frames) and to correct the behavior of next, step and stepout. The calls to Next and Frame of stackIterator continue to work unchanged and only return real stack frames, after reading each line appendInlinedCalls is called to unpacked all the inlined calls that involve the current PC. The fake stack frames produced by appendInlinedCalls are distinguished from real stack frames by having the Inlined attribute set to true. Also their Current and Call locations are treated differently. The Call location will be changed to represent the position inside the inlined call, while the Current location will always reference the real stack frame. This is done because: * next, step and stepout need to access the debug_info entry of the real function they are stepping through * we are already manipulating Call in different ways while Current is just what we read from the call stack The strategy remains mostly the same, we disassemble the function and we set a breakpoint on each instruction corresponding to a different file:line. The function in question will be the one corresponding to the first real (i.e. non-inlined) stack frame. * If the current function contains inlined calls, 'next' will not set any breakpoints on instructions that belong to inlined calls. We do not do this for 'step'. * If we are inside an inlined call that makes other inlined functions, 'next' will not set any breakpoints that belong to inlined calls that are children of the current inlined call. * If the current function is inlined the breakpoint on the return address won't be set, because inlined frames don't have a return address. * The code we use for stepout doesn't work at all if we are inside an inlined call, instead we call 'next' but instruct it to remove all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
success := false
defer func() {
if !success {
dbp.ClearSteppingBreakpoints()
proc: support inlining Go 1.10 added inlined calls to debug_info, this commit adds support for DW_TAG_inlined_call to delve, both for stack traces (where inlined calls will appear as normal stack frames) and to correct the behavior of next, step and stepout. The calls to Next and Frame of stackIterator continue to work unchanged and only return real stack frames, after reading each line appendInlinedCalls is called to unpacked all the inlined calls that involve the current PC. The fake stack frames produced by appendInlinedCalls are distinguished from real stack frames by having the Inlined attribute set to true. Also their Current and Call locations are treated differently. The Call location will be changed to represent the position inside the inlined call, while the Current location will always reference the real stack frame. This is done because: * next, step and stepout need to access the debug_info entry of the real function they are stepping through * we are already manipulating Call in different ways while Current is just what we read from the call stack The strategy remains mostly the same, we disassemble the function and we set a breakpoint on each instruction corresponding to a different file:line. The function in question will be the one corresponding to the first real (i.e. non-inlined) stack frame. * If the current function contains inlined calls, 'next' will not set any breakpoints on instructions that belong to inlined calls. We do not do this for 'step'. * If we are inside an inlined call that makes other inlined functions, 'next' will not set any breakpoints that belong to inlined calls that are children of the current inlined call. * If the current function is inlined the breakpoint on the return address won't be set, because inlined frames don't have a return address. * The code we use for stepout doesn't work at all if we are inside an inlined call, instead we call 'next' but instruct it to remove all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
}
}()
if topframe.Inlined {
if err := next(dbp, false, true); err != nil {
return err
}
success = true
return dbp.Continue()
proc: support inlining Go 1.10 added inlined calls to debug_info, this commit adds support for DW_TAG_inlined_call to delve, both for stack traces (where inlined calls will appear as normal stack frames) and to correct the behavior of next, step and stepout. The calls to Next and Frame of stackIterator continue to work unchanged and only return real stack frames, after reading each line appendInlinedCalls is called to unpacked all the inlined calls that involve the current PC. The fake stack frames produced by appendInlinedCalls are distinguished from real stack frames by having the Inlined attribute set to true. Also their Current and Call locations are treated differently. The Call location will be changed to represent the position inside the inlined call, while the Current location will always reference the real stack frame. This is done because: * next, step and stepout need to access the debug_info entry of the real function they are stepping through * we are already manipulating Call in different ways while Current is just what we read from the call stack The strategy remains mostly the same, we disassemble the function and we set a breakpoint on each instruction corresponding to a different file:line. The function in question will be the one corresponding to the first real (i.e. non-inlined) stack frame. * If the current function contains inlined calls, 'next' will not set any breakpoints on instructions that belong to inlined calls. We do not do this for 'step'. * If we are inside an inlined call that makes other inlined functions, 'next' will not set any breakpoints that belong to inlined calls that are children of the current inlined call. * If the current function is inlined the breakpoint on the return address won't be set, because inlined frames don't have a return address. * The code we use for stepout doesn't work at all if we are inside an inlined call, instead we call 'next' but instruct it to remove all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
}
sameGCond := sameGoroutineCondition(selg)
if backward {
if err := stepOutReverse(dbp, topframe, retframe, sameGCond); err != nil {
return err
}
success = true
return dbp.Continue()
}
deferpc, err := setDeferBreakpoint(dbp, nil, topframe, sameGCond, false)
if err != nil {
return err
}
proc: support inlining Go 1.10 added inlined calls to debug_info, this commit adds support for DW_TAG_inlined_call to delve, both for stack traces (where inlined calls will appear as normal stack frames) and to correct the behavior of next, step and stepout. The calls to Next and Frame of stackIterator continue to work unchanged and only return real stack frames, after reading each line appendInlinedCalls is called to unpacked all the inlined calls that involve the current PC. The fake stack frames produced by appendInlinedCalls are distinguished from real stack frames by having the Inlined attribute set to true. Also their Current and Call locations are treated differently. The Call location will be changed to represent the position inside the inlined call, while the Current location will always reference the real stack frame. This is done because: * next, step and stepout need to access the debug_info entry of the real function they are stepping through * we are already manipulating Call in different ways while Current is just what we read from the call stack The strategy remains mostly the same, we disassemble the function and we set a breakpoint on each instruction corresponding to a different file:line. The function in question will be the one corresponding to the first real (i.e. non-inlined) stack frame. * If the current function contains inlined calls, 'next' will not set any breakpoints on instructions that belong to inlined calls. We do not do this for 'step'. * If we are inside an inlined call that makes other inlined functions, 'next' will not set any breakpoints that belong to inlined calls that are children of the current inlined call. * If the current function is inlined the breakpoint on the return address won't be set, because inlined frames don't have a return address. * The code we use for stepout doesn't work at all if we are inside an inlined call, instead we call 'next' but instruct it to remove all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
if topframe.Ret == 0 && deferpc == 0 {
return errors.New("nothing to stepout to")
}
if topframe.Ret != 0 {
topframe, retframe := skipAutogeneratedWrappersOut(selg, curthread, &topframe, &retframe)
retFrameCond := astutil.And(sameGCond, frameoffCondition(retframe))
bp, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(retframe.Current.PC, NextBreakpoint, retFrameCond))
if err != nil {
return err
}
if bp != nil {
configureReturnBreakpoint(dbp.BinInfo(), bp, topframe, retFrameCond)
}
}
if bp := curthread.Breakpoint(); bp.Breakpoint == nil {
curthread.SetCurrentBreakpoint(false)
}
proc: support inlining Go 1.10 added inlined calls to debug_info, this commit adds support for DW_TAG_inlined_call to delve, both for stack traces (where inlined calls will appear as normal stack frames) and to correct the behavior of next, step and stepout. The calls to Next and Frame of stackIterator continue to work unchanged and only return real stack frames, after reading each line appendInlinedCalls is called to unpacked all the inlined calls that involve the current PC. The fake stack frames produced by appendInlinedCalls are distinguished from real stack frames by having the Inlined attribute set to true. Also their Current and Call locations are treated differently. The Call location will be changed to represent the position inside the inlined call, while the Current location will always reference the real stack frame. This is done because: * next, step and stepout need to access the debug_info entry of the real function they are stepping through * we are already manipulating Call in different ways while Current is just what we read from the call stack The strategy remains mostly the same, we disassemble the function and we set a breakpoint on each instruction corresponding to a different file:line. The function in question will be the one corresponding to the first real (i.e. non-inlined) stack frame. * If the current function contains inlined calls, 'next' will not set any breakpoints on instructions that belong to inlined calls. We do not do this for 'step'. * If we are inside an inlined call that makes other inlined functions, 'next' will not set any breakpoints that belong to inlined calls that are children of the current inlined call. * If the current function is inlined the breakpoint on the return address won't be set, because inlined frames don't have a return address. * The code we use for stepout doesn't work at all if we are inside an inlined call, instead we call 'next' but instruct it to remove all PCs belonging to the current inlined call.
2017-11-13 15:54:08 +00:00
success = true
return dbp.Continue()
}
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// associated with the selected goroutine. All other
// threads will remain stopped.
func (dbp *Target) StepInstruction() (err error) {
thread := dbp.CurrentThread()
g := dbp.SelectedGoroutine()
if g != nil {
if g.Thread == nil {
// Step called on parked goroutine
if _, err := dbp.SetBreakpoint(g.PC, NextBreakpoint,
sameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
return err
}
return dbp.Continue()
}
thread = g.Thread
}
2021-07-02 16:37:55 +00:00
dbp.ClearCaches()
if ok, err := dbp.Valid(); !ok {
return err
}
err = thread.StepInstruction()
if err != nil {
return err
}
thread.Breakpoint().Clear()
err = thread.SetCurrentBreakpoint(true)
if err != nil {
return err
}
if tg, _ := GetG(thread); tg != nil {
dbp.selectedGoroutine = tg
}
return nil
}
// Set breakpoints at every line, and the return address. Also look for
// a deferred function and set a breakpoint there too.
// If stepInto is true it will also set breakpoints inside all
// functions called on the current source line, for non-absolute CALLs
// a breakpoint of kind StepBreakpoint is set on the CALL instruction,
// Continue will take care of setting a breakpoint to the destination
// once the CALL is reached.
//
// Regardless of stepInto the following breakpoints will be set:
// - a breakpoint on the first deferred function with NextDeferBreakpoint
// kind, the list of all the addresses to deferreturn calls in this function
// and condition checking that we remain on the same goroutine
// - a breakpoint on each line of the function, with a condition checking
// that we stay on the same stack frame and goroutine.
// - a breakpoint on the return address of the function, with a condition
// checking that we move to the previous stack frame and stay on the same
// goroutine.
//
// The breakpoint on the return address is *not* set if the current frame is
// an inlined call. For inlined calls topframe.Current.Fn is the function
// where the inlining happened and the second set of breakpoints will also
// cover the "return address".
//
// If inlinedStepOut is true this function implements the StepOut operation
// for an inlined function call. Everything works the same as normal except
// when removing instructions belonging to inlined calls we also remove all
// instructions belonging to the current inlined call.
func next(dbp *Target, stepInto, inlinedStepOut bool) error {
backward := dbp.GetDirection() == Backward
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
topframe, retframe, err := topframe(selg, curthread)
if err != nil {
return err
}
if topframe.Current.Fn == nil {
return &ErrNoSourceForPC{topframe.Current.PC}
}
if backward && retframe.Current.Fn == nil {
return &ErrNoSourceForPC{retframe.Current.PC}
}
// sanity check
if inlinedStepOut && !topframe.Inlined {
panic("next called with inlinedStepOut but topframe was not inlined")
}
success := false
defer func() {
if !success {
dbp.ClearSteppingBreakpoints()
}
}()
ext := filepath.Ext(topframe.Current.File)
csource := ext != ".go" && ext != ".s"
var regs Registers
if selg != nil && selg.Thread != nil {
regs, err = selg.Thread.Registers()
if err != nil {
return err
}
}
sameGCond := sameGoroutineCondition(selg)
var firstPCAfterPrologue uint64
if backward {
firstPCAfterPrologue, err = FirstPCAfterPrologue(dbp, topframe.Current.Fn, false)
if err != nil {
return err
}
if firstPCAfterPrologue == topframe.Current.PC {
// We don't want to step into the prologue so we just execute a reverse step out instead
if err := stepOutReverse(dbp, topframe, retframe, sameGCond); err != nil {
return err
}
success = true
return nil
}
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
topframe.Ret, err = findCallInstrForRet(dbp, dbp.Memory(), topframe.Ret, retframe.Current.Fn)
if err != nil {
return err
}
}
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
text, err := disassemble(dbp.Memory(), regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End, false)
if err != nil && stepInto {
return err
}
var sameFrameCond ast.Expr
if sameGCond != nil {
sameFrameCond = astutil.And(sameGCond, frameoffCondition(&topframe))
}
if stepInto && !backward {
err := setStepIntoBreakpoints(dbp, topframe.Current.Fn, text, topframe, sameGCond)
if err != nil {
return err
}
}
if !backward {
_, err = setDeferBreakpoint(dbp, text, topframe, sameGCond, stepInto)
if err != nil {
return err
}
}
// Add breakpoints on all the lines in the current function
pcs, err := topframe.Current.Fn.cu.lineInfo.AllPCsBetween(topframe.Current.Fn.Entry, topframe.Current.Fn.End-1, topframe.Current.File, topframe.Current.Line)
if err != nil {
return err
}
if backward {
// Ensure that pcs contains firstPCAfterPrologue when reverse stepping.
found := false
for _, pc := range pcs {
if pc == firstPCAfterPrologue {
found = true
break
}
}
if !found {
pcs = append(pcs, firstPCAfterPrologue)
}
}
if !stepInto {
// Removing any PC range belonging to an inlined call
frame := topframe
if inlinedStepOut {
frame = retframe
}
pcs, err = removeInlinedCalls(pcs, frame)
if err != nil {
return err
}
}
if !csource {
var covered bool
for i := range pcs {
if topframe.Current.Fn.Entry <= pcs[i] && pcs[i] < topframe.Current.Fn.End {
covered = true
break
}
}
if !covered {
fn := dbp.BinInfo().PCToFunc(topframe.Ret)
if selg != nil && fn != nil && fn.Name == "runtime.goexit" {
return nil
}
}
}
for _, pc := range pcs {
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(pc, NextBreakpoint, sameFrameCond)); err != nil {
dbp.ClearSteppingBreakpoints()
return err
}
}
if stepInto && backward {
err := setStepIntoBreakpointsReverse(dbp, text, topframe, sameGCond)
if err != nil {
return err
}
}
if !topframe.Inlined {
topframe, retframe := skipAutogeneratedWrappersOut(selg, curthread, &topframe, &retframe)
retFrameCond := astutil.And(sameGCond, frameoffCondition(retframe))
// Add a breakpoint on the return address for the current frame.
// For inlined functions there is no need to do this, the set of PCs
// returned by the AllPCsBetween call above already cover all instructions
// of the containing function.
bp, _ := dbp.SetBreakpoint(retframe.Current.PC, NextBreakpoint, retFrameCond)
// Return address could be wrong, if we are unable to set a breakpoint
// there it's ok.
if bp != nil {
configureReturnBreakpoint(dbp.BinInfo(), bp, topframe, retFrameCond)
}
}
if bp := curthread.Breakpoint(); bp.Breakpoint == nil {
curthread.SetCurrentBreakpoint(false)
}
success = true
return nil
}
func setStepIntoBreakpoints(dbp *Target, curfn *Function, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr) error {
for _, instr := range text {
if instr.Loc.File != topframe.Current.File || instr.Loc.Line != topframe.Current.Line || !instr.IsCall() {
continue
}
if instr.DestLoc != nil {
if err := setStepIntoBreakpoint(dbp, curfn, []AsmInstruction{instr}, sameGCond); err != nil {
return err
}
} else {
// Non-absolute call instruction, set a StepBreakpoint here
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, sameGCond)); err != nil {
return err
}
}
}
return nil
}
func setStepIntoBreakpointsReverse(dbp *Target, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr) error {
bpmap := dbp.Breakpoints()
// Set a breakpoint after every CALL instruction
for i, instr := range text {
if instr.Loc.File != topframe.Current.File || !instr.IsCall() || instr.DestLoc == nil || instr.DestLoc.Fn == nil {
continue
}
if instr.DestLoc.Fn.privateRuntime() {
continue
}
if nextIdx := i + 1; nextIdx < len(text) {
_, ok := bpmap.M[text[nextIdx].Loc.PC]
if !ok {
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(text[nextIdx].Loc.PC, StepBreakpoint, sameGCond)); err != nil {
return err
}
}
}
}
return nil
}
func FindDeferReturnCalls(text []AsmInstruction) []uint64 {
const deferreturn = "runtime.deferreturn"
deferreturns := []uint64{}
// Find all runtime.deferreturn locations in the function
// See documentation of Breakpoint.DeferCond for why this is necessary
for _, instr := range text {
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == deferreturn {
deferreturns = append(deferreturns, instr.Loc.PC)
}
}
return deferreturns
}
// Removes instructions belonging to inlined calls of topframe from pcs.
// If includeCurrentFn is true it will also remove all instructions
// belonging to the current function.
func removeInlinedCalls(pcs []uint64, topframe Stackframe) ([]uint64, error) {
dwarfTree, err := topframe.Call.Fn.cu.image.getDwarfTree(topframe.Call.Fn.offset)
if err != nil {
return pcs, err
}
for _, e := range reader.InlineStack(dwarfTree, 0) {
if e.Offset == topframe.Call.Fn.offset {
continue
}
for _, rng := range e.Ranges {
pcs = removePCsBetween(pcs, rng[0], rng[1])
}
}
return pcs, nil
}
func removePCsBetween(pcs []uint64, start, end uint64) []uint64 {
out := pcs[:0]
for _, pc := range pcs {
if pc < start || pc >= end {
out = append(out, pc)
}
}
return out
}
func setStepIntoBreakpoint(dbp *Target, curfn *Function, text []AsmInstruction, cond ast.Expr) error {
if len(text) <= 0 {
return nil
}
// If the current function is already a runtime function then
// setStepIntoBreakpoint is allowed to step into unexported runtime
// functions.
stepIntoUnexportedRuntime := curfn != nil && strings.HasPrefix(curfn.Name, "runtime.")
instr := text[0]
if instr.DestLoc == nil {
// Call destination couldn't be resolved because this was not the
// current instruction, therefore the step-into breakpoint can not be set.
return nil
}
fn := instr.DestLoc.Fn
// Skip unexported runtime functions
if !stepIntoUnexportedRuntime && fn != nil && fn.privateRuntime() {
return nil
}
//TODO(aarzilli): if we want to let users hide functions
// or entire packages from being stepped into with 'step'
// those extra checks should be done here.
pc := instr.DestLoc.PC
// Skip InhibitStepInto functions for different arch.
if dbp.BinInfo().Arch.inhibitStepInto(dbp.BinInfo(), pc) {
return nil
}
fn, pc = skipAutogeneratedWrappersIn(dbp, fn, pc)
// We want to skip the function prologue but we should only do it if the
// destination address of the CALL instruction is the entry point of the
// function.
// Calls to runtime.duffzero and duffcopy inserted by the compiler can
// sometimes point inside the body of those functions, well after the
// prologue.
if fn != nil && fn.Entry == pc {
pc, _ = FirstPCAfterPrologue(dbp, fn, false)
}
// Set a breakpoint after the function's prologue
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(pc, NextBreakpoint, cond)); err != nil {
return err
}
return nil
}
func allowDuplicateBreakpoint(bp *Breakpoint, err error) (*Breakpoint, error) {
if err != nil {
if _, isexists := err.(BreakpointExistsError); isexists {
return bp, nil
}
}
return bp, err
}
func isAutogenerated(loc Location) bool {
return loc.File == "<autogenerated>" && loc.Line == 1
}
// skipAutogeneratedWrappers skips autogenerated wrappers when setting a
// step-into breakpoint.
// See genwrapper in: $GOROOT/src/cmd/compile/internal/gc/subr.go
func skipAutogeneratedWrappersIn(p Process, startfn *Function, startpc uint64) (*Function, uint64) {
if startfn == nil {
return nil, startpc
}
fn := startfn
for count := 0; count < maxSkipAutogeneratedWrappers; count++ {
if !fn.cu.isgo {
// can't exit Go
return startfn, startpc
}
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
text, err := Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), fn.Entry, fn.End)
if err != nil {
break
}
if len(text) == 0 {
break
}
if !isAutogenerated(text[0].Loc) {
return fn, fn.Entry
}
tgtfns := []*Function{}
// collect all functions called by the current destination function
for _, instr := range text {
switch {
case instr.IsCall():
if instr.DestLoc == nil || instr.DestLoc.Fn == nil {
return startfn, startpc
}
// calls to non private runtime functions
if !instr.DestLoc.Fn.privateRuntime() {
tgtfns = append(tgtfns, instr.DestLoc.Fn)
}
case instr.IsJmp():
// unconditional jumps to a different function that isn't a private runtime function
if instr.DestLoc != nil && instr.DestLoc.Fn != fn && !instr.DestLoc.Fn.privateRuntime() {
tgtfns = append(tgtfns, instr.DestLoc.Fn)
}
}
}
if len(tgtfns) != 1 {
// too many or not enough function calls
break
}
tgtfn := tgtfns[0]
if tgtfn.BaseName() != fn.BaseName() {
return startfn, startpc
}
fn = tgtfn
}
return startfn, startpc
}
// skipAutogeneratedWrappersOut skip autogenerated wrappers when setting a
// step out breakpoint.
// See genwrapper in: $GOROOT/src/cmd/compile/internal/gc/subr.go
func skipAutogeneratedWrappersOut(g *G, thread Thread, startTopframe, startRetframe *Stackframe) (topframe, retframe *Stackframe) {
topframe, retframe = startTopframe, startRetframe
if startTopframe.Ret == 0 {
return
}
if !isAutogenerated(startRetframe.Current) {
return
}
retfn := thread.BinInfo().PCToFunc(startTopframe.Ret)
if retfn == nil {
return
}
if !retfn.cu.isgo {
return
}
var err error
var frames []Stackframe
if g == nil {
frames, err = ThreadStacktrace(thread, maxSkipAutogeneratedWrappers)
} else {
frames, err = g.Stacktrace(maxSkipAutogeneratedWrappers, 0)
}
if err != nil {
return
}
for i := 1; i < len(frames); i++ {
frame := frames[i]
if frame.Current.Fn == nil {
return
}
file, line := frame.Current.Fn.cu.lineInfo.PCToLine(frame.Current.Fn.Entry, frame.Current.Fn.Entry)
if !isAutogenerated(Location{File: file, Line: line, Fn: frame.Current.Fn}) {
return &frames[i-1], &frames[i]
}
}
return
}
// setDeferBreakpoint is a helper function used by next and StepOut to set a
// breakpoint on the first deferred function.
func setDeferBreakpoint(p *Target, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr, stepInto bool) (uint64, error) {
// Set breakpoint on the most recently deferred function (if any)
var deferpc uint64
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
if topframe.TopmostDefer != nil && topframe.TopmostDefer.DwrapPC != 0 {
_, _, deferfn := topframe.TopmostDefer.DeferredFunc(p)
var err error
deferpc, err = FirstPCAfterPrologue(p, deferfn, false)
if err != nil {
return 0, err
}
}
if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := allowDuplicateBreakpoint(p.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond))
if err != nil {
return 0, err
}
if bp != nil && stepInto {
// If DeferReturns is set then the breakpoint will also be triggered when
// called from runtime.deferreturn. We only do this for the step command,
// not for next or stepout.
for _, breaklet := range bp.Breaklets {
if breaklet.Kind == NextDeferBreakpoint {
breaklet.DeferReturns = FindDeferReturnCalls(text)
break
}
}
}
}
return deferpc, nil
}
// findCallInstrForRet returns the PC address of the CALL instruction
// immediately preceding the instruction at ret.
func findCallInstrForRet(p Process, mem MemoryReadWriter, ret uint64, fn *Function) (uint64, error) {
text, err := disassemble(mem, nil, p.Breakpoints(), p.BinInfo(), fn.Entry, fn.End, false)
if err != nil {
return 0, err
}
var prevInstr AsmInstruction
for _, instr := range text {
if instr.Loc.PC == ret {
return prevInstr.Loc.PC, nil
}
prevInstr = instr
}
return 0, fmt.Errorf("could not find CALL instruction for address %#x in %s", ret, fn.Name)
}
// stepOutReverse sets a breakpoint on the CALL instruction that created the current frame, this is either:
// - the CALL instruction immediately preceding the return address of the
// current frame
// - the return address of the current frame if the current frame was
// created by a runtime.deferreturn run
// - the return address of the runtime.gopanic frame if the current frame
// was created by a panic
// This function is used to implement reversed StepOut
func stepOutReverse(p *Target, topframe, retframe Stackframe, sameGCond ast.Expr) error {
curthread := p.CurrentThread()
selg := p.SelectedGoroutine()
if selg != nil && selg.Thread != nil {
curthread = selg.Thread
}
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
callerText, err := disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), retframe.Current.Fn.Entry, retframe.Current.Fn.End, false)
if err != nil {
return err
}
deferReturns := FindDeferReturnCalls(callerText)
var frames []Stackframe
if selg == nil {
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
frames, err = ThreadStacktrace(curthread, 3)
} else {
frames, err = selg.Stacktrace(3, 0)
}
if err != nil {
return err
}
var callpc uint64
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
if ok, panicFrame := isPanicCall(frames); ok {
if len(frames) < panicFrame+2 || frames[panicFrame+1].Current.Fn == nil {
if panicFrame < len(frames) {
return &ErrNoSourceForPC{frames[panicFrame].Current.PC}
} else {
return &ErrNoSourceForPC{frames[0].Current.PC}
}
}
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
callpc, err = findCallInstrForRet(p, p.Memory(), frames[panicFrame].Ret, frames[panicFrame+1].Current.Fn)
if err != nil {
return err
}
} else if ok, pc := isDeferReturnCall(frames, deferReturns); ok {
callpc = pc
} else {
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
callpc, err = findCallInstrForRet(p, p.Memory(), topframe.Ret, retframe.Current.Fn)
if err != nil {
return err
}
}
_, err = allowDuplicateBreakpoint(p.SetBreakpoint(callpc, NextBreakpoint, sameGCond))
return err
}
// onNextGoroutine returns true if this thread is on the goroutine requested by the current 'next' command
func onNextGoroutine(tgt *Target, thread Thread, breakpoints *BreakpointMap) (bool, error) {
var breaklet *Breaklet
breakletSearch:
for i := range breakpoints.M {
for _, blet := range breakpoints.M[i].Breaklets {
if blet.Kind&steppingMask != 0 && blet.Cond != nil {
breaklet = blet
break breakletSearch
}
}
}
if breaklet == nil {
return false, nil
}
// Internal breakpoint conditions can take multiple different forms:
// Step into breakpoints:
// runtime.curg.goid == X
// Next or StepOut breakpoints:
// runtime.curg.goid == X && runtime.frameoff == Y
// Breakpoints that can be hit either by stepping on a line in the same
// function or by returning from the function:
// runtime.curg.goid == X && (runtime.frameoff == Y || runtime.frameoff == Z)
// Here we are only interested in testing the runtime.curg.goid clause.
w := onNextGoroutineWalker{tgt: tgt, thread: thread}
ast.Walk(&w, breaklet.Cond)
return w.ret, w.err
}
type onNextGoroutineWalker struct {
tgt *Target
thread Thread
ret bool
err error
}
func (w *onNextGoroutineWalker) Visit(n ast.Node) ast.Visitor {
if binx, isbin := n.(*ast.BinaryExpr); isbin && binx.Op == token.EQL && exprToString(binx.X) == "runtime.curg.goid" {
w.ret, w.err = evalBreakpointCondition(w.tgt, w.thread, n.(ast.Expr))
return nil
}
return w
}