pkg/proc: improve support unwinding from sigpanic (#3559)

See: https://github.com/golang/go/issues/63862

Fixes #3545
This commit is contained in:
Derek Parker 2023-11-10 08:32:20 -06:00 committed by GitHub
parent 438d036fed
commit d186e14fd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 50 additions and 3 deletions

6
_fixtures/nilptr.go Normal file

@ -0,0 +1,6 @@
package main
func main() {
var p_value *int = nil
*p_value = 1
}

@ -112,7 +112,7 @@ func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
fctxt.CFA = rule
}
// We assume that RBP is the frame pointer and we want to keep it updated,
// We assume that RBP is the frame pointer, and we want to keep it updated,
// so that we can use it to unwind the stack even when we encounter frames
// without descriptor entries.
// If there isn't a rule already we emit one.

@ -117,7 +117,7 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
fctxt.CFA = rule
}
// We assume that RBP is the frame pointer and we want to keep it updated,
// We assume that RBP is the frame pointer, and we want to keep it updated,
// so that we can use it to unwind the stack even when we encounter frames
// without descriptor entries.
// If there isn't a rule already we emit one.

@ -6358,3 +6358,27 @@ func TestNextGenericMethodThroughInterface(t *testing.T) {
})
}
}
func TestIssue3545(t *testing.T) {
protest.AllowRecording(t)
withTestProcessArgs("nilptr", t, "", []string{}, protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
err := grp.Continue()
if err != nil && err.Error() == "bad access" {
grp.Continue()
}
locations, err := proc.ThreadStacktrace(p, p.CurrentThread(), 40)
assertNoError(err, t, "Stacktrace()")
var foundMain bool
for _, loc := range locations {
if loc.Call.Fn != nil && loc.Call.Fn.Name == "main.main" {
if foundMain {
t.Fatal("main.main found more than once in the stacktrace")
}
foundMain = true
}
}
if !foundMain {
t.Fatal("did not find main.main in stack trace")
}
})
}

@ -1,7 +1,9 @@
package proc
import (
"bytes"
"debug/dwarf"
"encoding/binary"
"errors"
"fmt"
"go/constant"
@ -481,7 +483,6 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
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 {
@ -499,6 +500,22 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
it.err = err
} else {
ret = reg.Uint64Val
// On systems which use a link register to store the return address of a function,
// certain leaf functions may not have correct DWARF information present in the
// .debug_frame FDE when unwinding after a fatal signal. This is due to the fact
// that runtime.sigpanic inserts a frame to make it look like the function which
// triggered the signal called runtime.sigpanic directly, making the value of the
// link register unreliable. Instead, treat it as a non-leaf function and read the
// return address from the stack. For more details, see:
// https://github.com/golang/go/issues/63862#issuecomment-1802672629.
if it.frame.Call.Fn != nil && it.frame.Call.Fn.Name == "runtime.sigpanic" && it.bi.Arch.usesLR {
buf := make([]byte, 8)
_, err := it.mem.ReadMemory(buf, uint64(it.regs.CFA))
if err != nil {
it.err = err
}
binary.Read(bytes.NewReader(buf), binary.LittleEndian, &ret)
}
}
retaddr = uint64(it.regs.CFA + regRule.Offset)
}