proc: handle new way of panic'ing in 1.11
This commit is contained in:
parent
ab799b9866
commit
5d26d333bf
@ -35,6 +35,11 @@ type AMD64 struct {
|
||||
// a bad frame descriptor which needs to be fixed to generate good stack
|
||||
// traces.
|
||||
crosscall2fn *Function
|
||||
|
||||
// sigreturnfn is the DIE of runtime.sigreturn, the return trampoline for
|
||||
// the signal handler. See comment in FixFrameUnwindContext for a
|
||||
// description of why this is needed.
|
||||
sigreturnfn *Function
|
||||
}
|
||||
|
||||
const (
|
||||
@ -53,7 +58,7 @@ func AMD64Arch(goos string) *AMD64 {
|
||||
breakInstruction: breakInstr,
|
||||
breakInstructionLen: len(breakInstr),
|
||||
hardwareBreakpointUsage: make([]bool, 4),
|
||||
goos: goos,
|
||||
goos: goos,
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,13 +95,30 @@ const (
|
||||
// FixFrameUnwindContext adds default architecture rules to fctxt or returns
|
||||
// the default frame unwind context if fctxt is nil.
|
||||
func (a *AMD64) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext {
|
||||
if fctxt == nil {
|
||||
if a.sigreturnfn == nil {
|
||||
a.sigreturnfn = bi.LookupFunc["runtime.sigreturn"]
|
||||
}
|
||||
|
||||
if fctxt == nil || (a.sigreturnfn != nil && pc >= a.sigreturnfn.Entry && pc < a.sigreturnfn.End) {
|
||||
//if true {
|
||||
// When there's no frame descriptor entry use BP (the frame pointer) instead
|
||||
// - return register is [bp + a.PtrSize()] (i.e. [cfa-a.PtrSize()])
|
||||
// - cfa is bp + a.PtrSize()*2
|
||||
// - bp is [bp] (i.e. [cfa-a.PtrSize()*2])
|
||||
// - sp is cfa
|
||||
|
||||
// When the signal handler runs it will move the execution to the signal
|
||||
// handling stack (installed using the sigaltstack system call).
|
||||
// This isn't a proper stack switch: the pointer to g in TLS will still
|
||||
// refer to whatever g was executing on that thread before the signal was
|
||||
// received.
|
||||
// Since go did not execute a stack switch the previous value of sp, pc
|
||||
// and bp is not saved inside g.sched, as it normally would.
|
||||
// The only way to recover is to either read sp/pc from the signal context
|
||||
// parameter (the ucontext_t* parameter) or to unconditionally follow the
|
||||
// frame pointer when we get to runtime.sigreturn (which is what we do
|
||||
// here).
|
||||
|
||||
return &frame.FrameContext{
|
||||
RetAddrReg: amd64DwarfIPRegNum,
|
||||
Regs: map[uint64]frame.DWRule{
|
||||
|
@ -184,11 +184,17 @@ func TestCore(t *testing.T) {
|
||||
var panicking *proc.G
|
||||
var panickingStack []proc.Stackframe
|
||||
for _, g := range gs {
|
||||
t.Logf("Goroutine %d", g.ID)
|
||||
stack, err := g.Stacktrace(10)
|
||||
if err != nil {
|
||||
t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
|
||||
}
|
||||
for _, frame := range stack {
|
||||
fnname := ""
|
||||
if frame.Call.Fn != nil {
|
||||
fnname = frame.Call.Fn.Name
|
||||
}
|
||||
t.Logf("\tframe %s:%d in %s %#x (systemstack: %v)", frame.Call.File, frame.Call.Line, fnname, frame.Call.PC, frame.SystemStack)
|
||||
if frame.Current.Fn != nil && strings.Contains(frame.Current.Fn.Name, "panic") {
|
||||
panicking = g
|
||||
panickingStack = stack
|
||||
|
@ -332,6 +332,8 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, foreground bool)
|
||||
|
||||
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
||||
|
||||
proc.CreateUnrecoveredPanicBreakpoint(p, p.writeBreakpoint, &p.breakpoints)
|
||||
|
||||
panicpc, err := proc.FindFunctionLocation(p, "runtime.startpanic", true, 0)
|
||||
if err == nil {
|
||||
bp, err := p.breakpoints.SetWithID(-1, panicpc, p.writeBreakpoint)
|
||||
|
@ -349,14 +349,7 @@ func initializeDebugProcess(dbp *Process, path string) (*Process, error) {
|
||||
// the offset of g struct inside TLS
|
||||
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
|
||||
|
||||
panicpc, err := proc.FindFunctionLocation(dbp, "runtime.startpanic", true, 0)
|
||||
if err == nil {
|
||||
bp, err := dbp.breakpoints.SetWithID(-1, panicpc, dbp.writeBreakpoint)
|
||||
if err == nil {
|
||||
bp.Name = proc.UnrecoveredPanic
|
||||
bp.Variables = []string{"runtime.curg._panic.arg"}
|
||||
}
|
||||
}
|
||||
proc.CreateUnrecoveredPanicBreakpoint(dbp, dbp.writeBreakpoint, &dbp.breakpoints)
|
||||
|
||||
return dbp, nil
|
||||
}
|
||||
|
@ -39,6 +39,14 @@ func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
type FunctionNotFoundError struct {
|
||||
FuncName string
|
||||
}
|
||||
|
||||
func (err *FunctionNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Could not find function %s\n", err.FuncName)
|
||||
}
|
||||
|
||||
// FindFunctionLocation finds address of a function's line
|
||||
// If firstLine == true is passed FindFunctionLocation will attempt to find the first line of the function
|
||||
// If lineOffset is passed FindFunctionLocation will return the address of that line
|
||||
@ -49,7 +57,7 @@ func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset
|
||||
bi := p.BinInfo()
|
||||
origfn := bi.LookupFunc[funcName]
|
||||
if origfn == nil {
|
||||
return 0, fmt.Errorf("Could not find function %s\n", funcName)
|
||||
return 0, &FunctionNotFoundError{funcName}
|
||||
}
|
||||
|
||||
if firstLine {
|
||||
@ -540,3 +548,20 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack
|
||||
s.PC = frames[0].lastpc
|
||||
return s
|
||||
}
|
||||
|
||||
// CreateUnrecoverablePanicBreakpoint creates the unrecoverable-panic breakpoint.
|
||||
// This function is meant to be called by implementations of the Process interface.
|
||||
func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpointFn, breakpoints *BreakpointMap) {
|
||||
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
|
||||
if _, isFnNotFound := err.(*FunctionNotFoundError); isFnNotFound {
|
||||
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
|
||||
}
|
||||
if err == nil {
|
||||
bp, err := breakpoints.SetWithID(-1, panicpc, writeBreakpoint)
|
||||
if err == nil {
|
||||
bp.Name = UnrecoveredPanic
|
||||
bp.Variables = []string{"runtime.curg._panic.arg"}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2588,9 +2588,21 @@ func TestNextInDeferReturn(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestProcess("defercall", t, func(p proc.Process, fixture protest.Fixture) {
|
||||
_, err := setFunctionBreakpoint(p, "runtime.deferreturn")
|
||||
assertNoError(err, t, "setFunctionBreakpoint()")
|
||||
assertNoError(err, t, "setFunctionBreakpoint(runtime.deferreturn)")
|
||||
assertNoError(proc.Continue(p), t, "First Continue()")
|
||||
|
||||
// Set a breakpoint on the deferred function so that the following loop
|
||||
// can not step out of the runtime.deferreturn and all the way to the
|
||||
// point where the target program panics.
|
||||
_, err = setFunctionBreakpoint(p, "main.sampleFunction")
|
||||
assertNoError(err, t, "setFunctionBreakpoint(main.sampleFunction)")
|
||||
for i := 0; i < 20; i++ {
|
||||
loc, err := p.CurrentThread().Location()
|
||||
assertNoError(err, t, "CurrentThread().Location()")
|
||||
t.Logf("at %#x %s:%d", loc.PC, loc.File, loc.Line)
|
||||
if loc.Fn != nil && loc.Fn.Name == "main.sampleFunction" {
|
||||
break
|
||||
}
|
||||
assertNoError(proc.Next(p), t, fmt.Sprintf("Next() %d", i))
|
||||
}
|
||||
})
|
||||
@ -3282,7 +3294,7 @@ func TestSystemstackStacktrace(t *testing.T) {
|
||||
frames, err := g.Stacktrace(100)
|
||||
assertNoError(err, t, "stacktrace")
|
||||
logStacktrace(t, frames)
|
||||
m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.startpanic", "main.main"}, frames)
|
||||
m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.gopanic", "main.main"}, frames)
|
||||
if m == nil {
|
||||
t.Fatal("see previous loglines")
|
||||
}
|
||||
|
@ -366,13 +366,22 @@ func (it *stackIterator) newStackframe(ret, retaddr uint64) Stackframe {
|
||||
// instruction to look for at pc - 1
|
||||
r.Call = r.Current
|
||||
default:
|
||||
r.lastpc = it.pc - 1
|
||||
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(it.pc - 1)
|
||||
if r.Call.Fn == nil {
|
||||
r.Call.File = "?"
|
||||
r.Call.Line = -1
|
||||
if r.Current.Fn != nil && it.pc == r.Current.Fn.Entry {
|
||||
// if the return address is the entry point of the function that
|
||||
// contains it then this is some kind of fake return frame (for example
|
||||
// runtime.sigreturn) that didn't actually call the current frame,
|
||||
// attempting to get the location of the CALL instruction would just
|
||||
// obfuscate what's going on, since there is no CALL instruction.
|
||||
r.Call = r.Current
|
||||
} else {
|
||||
r.lastpc = it.pc - 1
|
||||
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(it.pc - 1)
|
||||
if r.Call.Fn == nil {
|
||||
r.Call.File = "?"
|
||||
r.Call.Line = -1
|
||||
}
|
||||
r.Call.PC = r.Current.PC
|
||||
}
|
||||
r.Call.PC = r.Current.PC
|
||||
}
|
||||
} else {
|
||||
r.Call = r.Current
|
||||
|
Loading…
Reference in New Issue
Block a user