mirror of
https://github.com/go-delve/delve.git
synced 2025-10-29 01:27:16 +08:00
proc: implement stacktrace of arm64 (#1780)
* proc: separate amd64-arch code separate amd64 code about stacktrace, so we can add arm64 stacktrace code. * proc: implemente stacktrace of arm64 * delve now can use stack, frame commands on arm64-arch debug. Co-authored-by: tykcd996 <tang.yuke@zte.com.cn> Co-authored-by: hengwu0 <wu.heng@zte.com.cn> * test: remove skip-code of stacktrace on arm64 * add LR DWARF register and remove skip-code for fixed tests * proc: fix the Continue command after the hardcoded breakpoint on arm64 Arm64 use hardware breakpoint, and it will not set PC to the next instruction like amd64. We should move PC in both runtime.breakpoints and hardcoded breakpoints(probably cgo). * proc: implement cgo stacktrace on arm64 * proc: combine amd64_stack.go and arm64_stack.go file * proc: reorganize the stacktrace code * move SwitchStack function arch-related * fix Continue command after manual stop on arm64 * add timeout flag to make.go to enable infinite timeouts Co-authored-by: aarzilli <alessandro.arzilli@gmail.com> Co-authored-by: hengwu0 <wu.heng@zte.com.cn> Co-authored-by: tykcd996 <56993522+tykcd996@users.noreply.github.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com>
This commit is contained in:
@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
@ -127,7 +126,7 @@ func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
|
||||
so := g.variable.bi.PCToImage(g.PC)
|
||||
return newStackIterator(
|
||||
g.variable.bi, g.variable.mem,
|
||||
g.variable.bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP),
|
||||
g.variable.bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP, g.LR),
|
||||
g.stackhi, stkbar, g.stkbarPos, g, opts), nil
|
||||
}
|
||||
|
||||
@ -242,6 +241,7 @@ func (it *stackIterator) Next() bool {
|
||||
if it.err != nil || it.atend {
|
||||
return false
|
||||
}
|
||||
|
||||
callFrameRegs, ret, retaddr := it.advanceRegs()
|
||||
it.frame = it.newStackframe(ret, retaddr)
|
||||
|
||||
@ -252,7 +252,7 @@ func (it *stackIterator) Next() bool {
|
||||
}
|
||||
|
||||
if it.opts&StacktraceSimple == 0 {
|
||||
if it.switchStack() {
|
||||
if it.bi.Arch.SwitchStack(it, &callFrameRegs) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -268,131 +268,15 @@ func (it *stackIterator) Next() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// asmcgocallSPOffsetSaveSlot is the offset from systemstack.SP where
|
||||
// (goroutine.SP - StackHi) is saved in runtime.asmcgocall after the stack
|
||||
// switch happens.
|
||||
const asmcgocallSPOffsetSaveSlot = 0x28
|
||||
|
||||
// switchStack will use the current frame to determine if it's time to
|
||||
// switch between the system stack and the goroutine stack or vice versa.
|
||||
// Sets it.atend when the top of the stack is reached.
|
||||
func (it *stackIterator) switchStack() bool {
|
||||
if it.frame.Current.Fn == nil {
|
||||
return false
|
||||
}
|
||||
switch it.frame.Current.Fn.Name {
|
||||
case "runtime.asmcgocall":
|
||||
if it.top || !it.systemstack {
|
||||
return false
|
||||
}
|
||||
|
||||
// This function is called by a goroutine to execute a C function and
|
||||
// switches from the goroutine stack to the system stack.
|
||||
// Since we are unwinding the stack from callee to caller we have switch
|
||||
// from the system stack to the goroutine stack.
|
||||
|
||||
off, _ := readIntRaw(it.mem, uintptr(it.regs.SP()+asmcgocallSPOffsetSaveSlot), int64(it.bi.Arch.PtrSize())) // reads "offset of SP from StackHi" from where runtime.asmcgocall saved it
|
||||
oldsp := it.regs.SP()
|
||||
it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(int64(it.stackhi) - off)
|
||||
|
||||
// runtime.asmcgocall can also be called from inside the system stack,
|
||||
// in that case no stack switch actually happens
|
||||
if it.regs.SP() == oldsp {
|
||||
return false
|
||||
}
|
||||
it.systemstack = false
|
||||
|
||||
// advances to the next frame in the call stack
|
||||
it.frame.addrret = uint64(int64(it.regs.SP()) + int64(it.bi.Arch.PtrSize()))
|
||||
it.frame.Ret, _ = readUintRaw(it.mem, uintptr(it.frame.addrret), int64(it.bi.Arch.PtrSize()))
|
||||
it.pc = it.frame.Ret
|
||||
|
||||
it.top = false
|
||||
return true
|
||||
|
||||
case "runtime.cgocallback_gofunc":
|
||||
// For a detailed description of how this works read the long comment at
|
||||
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
|
||||
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_amd64.s
|
||||
//
|
||||
// When a C functions calls back into go it will eventually call into
|
||||
// runtime.cgocallback_gofunc which is the function that does the stack
|
||||
// switch from the system stack back into the goroutine stack
|
||||
// Since we are going backwards on the stack here we see the transition
|
||||
// as goroutine stack -> system stack.
|
||||
|
||||
if it.top || it.systemstack {
|
||||
return false
|
||||
}
|
||||
|
||||
if it.g0_sched_sp <= 0 {
|
||||
return false
|
||||
}
|
||||
// entering the system stack
|
||||
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g0_sched_sp
|
||||
// reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack
|
||||
it.g0_sched_sp, _ = readUintRaw(it.mem, uintptr(it.regs.SP()), int64(it.bi.Arch.PtrSize()))
|
||||
it.top = false
|
||||
callFrameRegs, ret, retaddr := it.advanceRegs()
|
||||
frameOnSystemStack := it.newStackframe(ret, retaddr)
|
||||
it.pc = frameOnSystemStack.Ret
|
||||
it.regs = callFrameRegs
|
||||
it.systemstack = true
|
||||
return true
|
||||
|
||||
case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
|
||||
// Look for "top of stack" functions.
|
||||
it.atend = true
|
||||
return true
|
||||
|
||||
case "runtime.mstart":
|
||||
// Calls to runtime.systemstack will switch to the systemstack then:
|
||||
// 1. alter the goroutine stack so that it looks like systemstack_switch
|
||||
// was called
|
||||
// 2. alter the system stack so that it looks like the bottom-most frame
|
||||
// belongs to runtime.mstart
|
||||
// If we find a runtime.mstart frame on the system stack of a goroutine
|
||||
// parked on runtime.systemstack_switch we assume runtime.systemstack was
|
||||
// called and continue tracing from the parked position.
|
||||
|
||||
if it.top || !it.systemstack || it.g == nil {
|
||||
return false
|
||||
}
|
||||
if fn := it.bi.PCToFunc(it.g.PC); fn == nil || fn.Name != "runtime.systemstack_switch" {
|
||||
return false
|
||||
}
|
||||
|
||||
it.switchToGoroutineStack()
|
||||
return true
|
||||
|
||||
default:
|
||||
if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") && it.frame.Current.Fn.Name != "runtime.fatalthrow" {
|
||||
// The runtime switches to the system stack in multiple places.
|
||||
// This usually happens through a call to runtime.systemstack but there
|
||||
// are functions that switch to the system stack manually (for example
|
||||
// runtime.morestack).
|
||||
// Since we are only interested in printing the system stack for cgo
|
||||
// calls we switch directly to the goroutine stack if we detect that the
|
||||
// function at the top of the stack is a runtime function.
|
||||
//
|
||||
// The function "runtime.fatalthrow" is deliberately excluded from this
|
||||
// because it can end up in the stack during a cgo call and switching to
|
||||
// the goroutine stack will exclude all the C functions from the stack
|
||||
// trace.
|
||||
it.switchToGoroutineStack()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (it *stackIterator) switchToGoroutineStack() {
|
||||
it.systemstack = false
|
||||
it.top = false
|
||||
it.pc = it.g.PC
|
||||
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g.SP
|
||||
it.regs.Reg(it.regs.BPRegNum).Uint64Val = it.g.BP
|
||||
if _, ok := it.bi.Arch.(*ARM64); ok {
|
||||
it.regs.Reg(it.regs.LRRegNum).Uint64Val = it.g.LR
|
||||
}
|
||||
}
|
||||
|
||||
// Frame returns the frame the iterator is pointing at.
|
||||
@ -552,7 +436,7 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
|
||||
|
||||
callimage := it.bi.PCToImage(it.pc)
|
||||
|
||||
callFrameRegs = op.DwarfRegisters{StaticBase: callimage.StaticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum}
|
||||
callFrameRegs = op.DwarfRegisters{StaticBase: callimage.StaticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum, LRRegNum: it.regs.LRRegNum}
|
||||
|
||||
// According to the standard the compiler should be responsible for emitting
|
||||
// rules for the RSP register so that it can then be used to calculate CFA,
|
||||
@ -561,7 +445,11 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
|
||||
// implicit.
|
||||
// See also the comment in dwarf2_frame_default_init in
|
||||
// $GDB_SOURCE/dwarf2-frame.c
|
||||
callFrameRegs.AddReg(uint64(amd64DwarfSPRegNum), cfareg)
|
||||
if _, ok := it.bi.Arch.(*ARM64); ok {
|
||||
callFrameRegs.AddReg(uint64(arm64DwarfSPRegNum), cfareg)
|
||||
} else {
|
||||
callFrameRegs.AddReg(uint64(amd64DwarfSPRegNum), cfareg)
|
||||
}
|
||||
|
||||
for i, regRule := range framectx.Regs {
|
||||
reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA)
|
||||
@ -579,6 +467,12 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := it.bi.Arch.(*ARM64); ok {
|
||||
if ret == 0 && it.regs.Regs[it.regs.LRRegNum] != nil {
|
||||
ret = it.regs.Regs[it.regs.LRRegNum].Uint64Val
|
||||
}
|
||||
}
|
||||
|
||||
return callFrameRegs, ret, retaddr
|
||||
}
|
||||
|
||||
@ -589,6 +483,9 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c
|
||||
case frame.RuleUndefined:
|
||||
return nil, nil
|
||||
case frame.RuleSameVal:
|
||||
if it.regs.Reg(regnum) == nil {
|
||||
return nil, nil
|
||||
}
|
||||
reg := *it.regs.Reg(regnum)
|
||||
return ®, nil
|
||||
case frame.RuleOffset:
|
||||
|
||||
Reference in New Issue
Block a user