proc: read context from sigtrampgo, fixes TestCgoStacktrace2 on 1.21 (#3401)

* logflags,proc: flag to log stacktrace execution

Add a log flag to write logs about what the stacktracer does.

* proc: read context from sigtrampgo, fixes TestCgoStacktrace2 on 1.21

Changes stacktrace code to read the signal context from the arguments
of sigtrampgo.
Also changes the automatic fatalthrow breakpoint for go 1.21.
In combination these two changes fix TestCgoStacktrace2 on Go 1.21 on
various platforms.
This commit is contained in:
Alessandro Arzilli
2023-06-27 18:33:07 +02:00
committed by GitHub
parent 1647fa6b5e
commit d963eb1057
20 changed files with 1004 additions and 148 deletions

View File

@ -6,10 +6,12 @@ import (
"fmt"
"go/constant"
"reflect"
"strings"
"github.com/go-delve/delve/pkg/dwarf/frame"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/go-delve/delve/pkg/logflags"
)
// This code is partly adapted from runtime.gentraceback in
@ -91,7 +93,7 @@ func (frame *Stackframe) FramePointerOffset() int64 {
// ThreadStacktrace returns the stack trace for thread.
// Note the locations in the array are return addresses not call addresses.
func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
func ThreadStacktrace(tgt *Target, thread Thread, depth int) ([]Stackframe, error) {
g, _ := GetG(thread)
if g == nil {
regs, err := thread.Registers()
@ -101,13 +103,13 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
so := thread.BinInfo().PCToImage(regs.PC())
dwarfRegs := *(thread.BinInfo().Arch.RegistersToDwarfRegisters(so.StaticBase, regs))
dwarfRegs.ChangeFunc = thread.SetReg
it := newStackIterator(thread.BinInfo(), thread.ProcessMemory(), dwarfRegs, 0, nil, 0)
it := newStackIterator(tgt, thread.BinInfo(), thread.ProcessMemory(), dwarfRegs, 0, nil, 0)
return it.stacktrace(depth)
}
return g.Stacktrace(depth, 0)
return GoroutineStacktrace(tgt, g, depth, 0)
}
func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
func goroutineStackIterator(tgt *Target, g *G, opts StacktraceOptions) (*stackIterator, error) {
bi := g.variable.bi
if g.Thread != nil {
regs, err := g.Thread.Registers()
@ -118,13 +120,13 @@ func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
dwarfRegs := *(bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs))
dwarfRegs.ChangeFunc = g.Thread.SetReg
return newStackIterator(
bi, g.variable.mem,
tgt, bi, g.variable.mem,
dwarfRegs,
g.stack.hi, g, opts), nil
}
so := g.variable.bi.PCToImage(g.PC)
return newStackIterator(
bi, g.variable.mem,
tgt, bi, g.variable.mem,
bi.Arch.addrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP, g.LR),
g.stack.hi, g, opts), nil
}
@ -145,10 +147,10 @@ const (
StacktraceG
)
// Stacktrace returns the stack trace for a goroutine.
// GoroutineStacktrace returns the stack trace for a goroutine.
// Note the locations in the array are return addresses not call addresses.
func (g *G) Stacktrace(depth int, opts StacktraceOptions) ([]Stackframe, error) {
it, err := g.stackIterator(opts)
func GoroutineStacktrace(tgt *Target, g *G, depth int, opts StacktraceOptions) ([]Stackframe, error) {
it, err := goroutineStackIterator(tgt, g, opts)
if err != nil {
return nil, err
}
@ -173,13 +175,14 @@ func (n NullAddrError) Error() string {
// required to iterate and walk the program
// stack.
type stackIterator struct {
pc uint64
top bool
atend bool
frame Stackframe
bi *BinaryInfo
mem MemoryReadWriter
err error
pc uint64
top bool
atend bool
frame Stackframe
target *Target
bi *BinaryInfo
mem MemoryReadWriter
err error
stackhi uint64
systemstack bool
@ -194,12 +197,12 @@ type stackIterator struct {
opts StacktraceOptions
}
func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, g *G, opts StacktraceOptions) *stackIterator {
func newStackIterator(tgt *Target, bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, g *G, opts StacktraceOptions) *stackIterator {
systemstack := true
if g != nil {
systemstack = g.SystemStack
}
return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, systemstack: systemstack, g: g, opts: opts}
return &stackIterator{pc: regs.PC(), regs: regs, top: true, target: tgt, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, systemstack: systemstack, g: g, opts: opts}
}
// Next points the iterator to the next stack frame.
@ -208,11 +211,54 @@ func (it *stackIterator) Next() bool {
return false
}
if logflags.Stack() {
logger := logflags.StackLogger()
w := &strings.Builder{}
fmt.Fprintf(w, "current pc = %#x CFA = %#x FrameBase = %#x ", it.pc, it.regs.CFA, it.regs.FrameBase)
for i := 0; i < it.regs.CurrentSize(); i++ {
reg := it.regs.Reg(uint64(i))
if reg == nil {
continue
}
name, _, _ := it.bi.Arch.DwarfRegisterToString(i, reg)
fmt.Fprintf(w, " %s = %#x", name, reg.Uint64Val)
}
logger.Debugf("%s", w.String())
}
callFrameRegs, ret, retaddr := it.advanceRegs()
it.frame = it.newStackframe(ret, retaddr)
if logflags.Stack() {
logger := logflags.StackLogger()
fnname := "?"
if it.frame.Call.Fn != nil {
fnname = it.frame.Call.Fn.Name
}
logger.Debugf("new frame %#x %s:%d at %s", it.frame.Call.PC, it.frame.Call.File, it.frame.Call.Line, fnname)
}
if it.frame.Current.Fn != nil && it.frame.Current.Fn.Name == "runtime.sigtrampgo" && it.target != nil {
regs, err := it.readSigtrampgoContext()
if err != nil {
logflags.DebuggerLogger().Errorf("could not read runtime.sigtrampgo context: %v", err)
} else {
so := it.bi.PCToImage(regs.PC())
regs.StaticBase = so.StaticBase
it.pc = regs.PC()
it.regs = *regs
it.top = false
if it.g != nil && it.g.ID != 0 {
it.systemstack = !(uint64(it.regs.SP()) >= it.g.stack.lo && uint64(it.regs.SP()) < it.g.stack.hi)
}
logflags.StackLogger().Debugf("sigtramp context read")
return true
}
}
if it.opts&StacktraceSimple == 0 {
if it.bi.Arch.switchStack(it, &callFrameRegs) {
logflags.StackLogger().Debugf("stack switched")
return true
}
}
@ -398,11 +444,18 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
framectx = it.bi.Arch.fixFrameUnwindContext(fde.EstablishFrame(it.pc), it.pc, it.bi)
}
logger := logflags.StackLogger()
logger.Debugf("advanceRegs at %#x", it.pc)
cfareg, err := it.executeFrameRegRule(0, framectx.CFA, 0)
if cfareg == nil {
it.err = fmt.Errorf("CFA becomes undefined at PC %#x: %v", it.pc, err)
return op.DwarfRegisters{}, 0, 0
}
if logflags.Stack() {
logger.Debugf("\tCFA rule %s -> %#x", ruleString(&framectx.CFA, it.bi.Arch.RegnumToString), cfareg.Uint64Val)
}
it.regs.CFA = int64(cfareg.Uint64Val)
callimage := it.bi.PCToImage(it.pc)
@ -426,7 +479,16 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
callFrameRegs.AddReg(callFrameRegs.SPRegNum, cfareg)
for i, regRule := range framectx.Regs {
if logflags.Stack() {
logger.Debugf("\t%s rule %s ", it.bi.Arch.RegnumToString(i), ruleString(&regRule, it.bi.Arch.RegnumToString))
}
reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA)
if reg != nil {
logger.Debugf("\t\t-> %#x", reg.Uint64Val)
} else {
logger.Debugf("\t\t-> nothing (%v)", err)
}
callFrameRegs.AddReg(i, reg)
if i == framectx.RetAddrReg {
if reg == nil {
@ -701,3 +763,34 @@ func (d *Defer) DeferredFunc(p *Target) (file string, line int, fn *Function) {
file, line = bi.EntryLineForFunc(fn)
return file, line, fn
}
func ruleString(rule *frame.DWRule, regnumToString func(uint64) string) string {
switch rule.Rule {
case frame.RuleUndefined:
return "undefined"
case frame.RuleSameVal:
return "sameval"
case frame.RuleOffset:
return fmt.Sprintf("[cfa+%d]", rule.Offset)
case frame.RuleValOffset:
return fmt.Sprintf("cfa+%d", rule.Offset)
case frame.RuleRegister:
return fmt.Sprintf("R(%d)", rule.Reg)
case frame.RuleExpression:
w := &strings.Builder{}
op.PrettyPrint(w, rule.Expression, regnumToString)
return fmt.Sprintf("[expr(%s)]", w.String())
case frame.RuleValExpression:
w := &strings.Builder{}
op.PrettyPrint(w, rule.Expression, regnumToString)
return fmt.Sprintf("expr(%s)", w.String())
case frame.RuleArchitectural:
return "architectural"
case frame.RuleCFA:
return fmt.Sprintf("R(%d)+%d", rule.Reg, rule.Offset)
case frame.RuleFramePointer:
return fmt.Sprintf("[R(%d)] framepointer", rule.Reg)
default:
return fmt.Sprintf("unknown_rule(%d)", rule.Rule)
}
}