proc: handle new way of panic'ing in 1.11

This commit is contained in:
aarzilli 2018-03-20 12:34:02 +01:00 committed by Derek Parker
parent ab799b9866
commit 5d26d333bf
7 changed files with 88 additions and 19 deletions

@ -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