提交 5372588c 编写于 作者: A aarzilli 提交者: Derek Parker

proc: support cgo stacktraces

When creating a stack trace we should switch between the goroutine
stack and the system stack (where cgo code is executed) as appropriate
to reconstruct the logical stacktrace.

Goroutines that are currently executing on the system stack will have
the SystemStack flag set, frames of the goroutine stack will have a
negative FrameOffset (like always) and frames of the system stack will
have a positive FrameOffset (which is actually just the CFA value for
the frame).

Updates #935
上级 5fdcd2c9
#include <stdio.h>
#include "_cgo_export.h"
#define BREAKPOINT asm("int3;")
void helloworld_pt2(int x) {
BREAKPOINT;
helloWorld(x+1);
}
void helloworld(int x) {
helloworld_pt2(x+1);
}
void helloworld_pt4(int x) {
BREAKPOINT;
helloWorld2(x+1);
}
void helloworld_pt3(int x) {
helloworld_pt4(x+1);
}
#ifndef __HELLO_H__
#define __HELLO_H__
void helloworld(int);
void helloworld_pt3(int);
#endif
package main
// #include <hello.h>
import "C"
import (
"fmt"
"runtime"
)
func main() {
runtime.Breakpoint()
C.helloworld(2)
}
//export helloWorld
func helloWorld(x C.int) {
helloWorldS(x)
}
func helloWorldS(x C.int) {
runtime.Breakpoint()
C.helloworld_pt3(x - 1)
}
//export helloWorld2
func helloWorld2(x C.int) {
runtime.Breakpoint()
fmt.Printf("hello world %d\n", x)
}
...@@ -74,8 +74,8 @@ const ( ...@@ -74,8 +74,8 @@ const (
RuleExpression RuleExpression
RuleValExpression RuleValExpression
RuleArchitectural RuleArchitectural
RuleCFA // Value is rule.Reg + rule.Offset RuleCFA // Value is rule.Reg + rule.Offset
RuleRegOffset // Value is stored at address rule.Reg + rule.Offset RuleFramePointer // Value is stored at address rule.Reg + rule.Offset, but only if it's less than the current CFA, otherwise same value
) )
const low_6_offset = 0x3f const low_6_offset = 0x3f
......
...@@ -14,6 +14,7 @@ type DwarfRegisters struct { ...@@ -14,6 +14,7 @@ type DwarfRegisters struct {
ByteOrder binary.ByteOrder ByteOrder binary.ByteOrder
PCRegNum uint64 PCRegNum uint64
SPRegNum uint64 SPRegNum uint64
BPRegNum uint64
} }
type DwarfRegister struct { type DwarfRegister struct {
...@@ -61,6 +62,10 @@ func (regs *DwarfRegisters) SP() uint64 { ...@@ -61,6 +62,10 @@ func (regs *DwarfRegisters) SP() uint64 {
return regs.Uint64Val(regs.SPRegNum) return regs.Uint64Val(regs.SPRegNum)
} }
func (regs *DwarfRegisters) BP() uint64 {
return regs.Uint64Val(regs.BPRegNum)
}
// AddReg adds register idx to regs. // AddReg adds register idx to regs.
func (regs *DwarfRegisters) AddReg(idx uint64, reg *DwarfRegister) { func (regs *DwarfRegisters) AddReg(idx uint64, reg *DwarfRegister) {
if idx >= uint64(len(regs.Regs)) { if idx >= uint64(len(regs.Regs)) {
......
...@@ -15,7 +15,7 @@ type Arch interface { ...@@ -15,7 +15,7 @@ type Arch interface {
BreakpointInstruction() []byte BreakpointInstruction() []byte
BreakpointSize() int BreakpointSize() int
DerefTLS() bool DerefTLS() bool
FixFrameUnwindContext(*frame.FrameContext) *frame.FrameContext FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext
RegSize(uint64) int RegSize(uint64) int
RegistersToDwarfRegisters(Registers) op.DwarfRegisters RegistersToDwarfRegisters(Registers) op.DwarfRegisters
GoroutineToDwarfRegisters(*G) op.DwarfRegisters GoroutineToDwarfRegisters(*G) op.DwarfRegisters
...@@ -29,6 +29,12 @@ type AMD64 struct { ...@@ -29,6 +29,12 @@ type AMD64 struct {
gStructOffset uint64 gStructOffset uint64
hardwareBreakpointUsage []bool hardwareBreakpointUsage []bool
goos string goos string
// crosscall2fn is the DIE of crosscall2, a function used by the go runtime
// to call C functions. This function in go 1.9 (and previous versions) had
// a bad frame descriptor which needs to be fixed to generate good stack
// traces.
crosscall2fn *Function
} }
const ( const (
...@@ -75,9 +81,15 @@ func (a *AMD64) DerefTLS() bool { ...@@ -75,9 +81,15 @@ func (a *AMD64) DerefTLS() bool {
return a.goos == "windows" return a.goos == "windows"
} }
const (
crosscall2SPOffsetBad = 0x8
crosscall2SPOffsetWindows = 0x118
crosscall2SPOffsetNonWindows = 0x58
)
// FixFrameUnwindContext adds default architecture rules to fctxt or returns // FixFrameUnwindContext adds default architecture rules to fctxt or returns
// the default frame unwind context if fctxt is nil. // the default frame unwind context if fctxt is nil.
func (a *AMD64) FixFrameUnwindContext(fctxt *frame.FrameContext) *frame.FrameContext { func (a *AMD64) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext {
if fctxt == nil { if fctxt == nil {
// When there's no frame descriptor entry use BP (the frame pointer) instead // When there's no frame descriptor entry use BP (the frame pointer) instead
// - return register is [bp + a.PtrSize()] (i.e. [cfa-a.PtrSize()]) // - return register is [bp + a.PtrSize()] (i.e. [cfa-a.PtrSize()])
...@@ -109,13 +121,30 @@ func (a *AMD64) FixFrameUnwindContext(fctxt *frame.FrameContext) *frame.FrameCon ...@@ -109,13 +121,30 @@ func (a *AMD64) FixFrameUnwindContext(fctxt *frame.FrameContext) *frame.FrameCon
} }
} }
if a.crosscall2fn == nil {
a.crosscall2fn = bi.LookupFunc["crosscall2"]
}
if a.crosscall2fn != nil && pc >= a.crosscall2fn.Entry && pc < a.crosscall2fn.End {
rule := fctxt.CFA
if rule.Offset == crosscall2SPOffsetBad {
switch a.goos {
case "windows":
rule.Offset += crosscall2SPOffsetWindows
default:
rule.Offset += crosscall2SPOffsetNonWindows
}
}
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 // so that we can use it to unwind the stack even when we encounter frames
// without descriptor entries. // without descriptor entries.
// If there isn't a rule already we emit one. // If there isn't a rule already we emit one.
if fctxt.Regs[amd64DwarfBPRegNum].Rule == frame.RuleUndefined { if fctxt.Regs[amd64DwarfBPRegNum].Rule == frame.RuleUndefined {
fctxt.Regs[amd64DwarfBPRegNum] = frame.DWRule{ fctxt.Regs[amd64DwarfBPRegNum] = frame.DWRule{
Rule: frame.RuleRegOffset, Rule: frame.RuleFramePointer,
Reg: amd64DwarfBPRegNum, Reg: amd64DwarfBPRegNum,
Offset: 0, Offset: 0,
} }
...@@ -241,7 +270,7 @@ func (a *AMD64) RegistersToDwarfRegisters(regs Registers) op.DwarfRegisters { ...@@ -241,7 +270,7 @@ func (a *AMD64) RegistersToDwarfRegisters(regs Registers) op.DwarfRegisters {
} }
} }
return op.DwarfRegisters{Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum} return op.DwarfRegisters{Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum}
} }
// GoroutineToDwarfRegisters extract the saved DWARF registers from a parked // GoroutineToDwarfRegisters extract the saved DWARF registers from a parked
...@@ -251,5 +280,5 @@ func (a *AMD64) GoroutineToDwarfRegisters(g *G) op.DwarfRegisters { ...@@ -251,5 +280,5 @@ func (a *AMD64) GoroutineToDwarfRegisters(g *G) op.DwarfRegisters {
dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(g.PC) dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(g.PC)
dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(g.SP) dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(g.SP)
dregs[amd64DwarfBPRegNum] = op.DwarfRegisterFromUint64(g.BP) dregs[amd64DwarfBPRegNum] = op.DwarfRegisterFromUint64(g.BP)
return op.DwarfRegisters{Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum} return op.DwarfRegisters{Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum}
} }
...@@ -183,7 +183,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { ...@@ -183,7 +183,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
} }
return scope.Gvar.clone(), nil return scope.Gvar.clone(), nil
} else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" { } else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" {
return newConstant(constant.MakeInt64(scope.Regs.CFA-int64(scope.StackHi)), scope.Mem), nil return newConstant(constant.MakeInt64(scope.frameOffset), scope.Mem), nil
} else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil { } else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil {
return v, nil return v, nil
} }
......
...@@ -286,7 +286,7 @@ func StepOut(dbp Process) error { ...@@ -286,7 +286,7 @@ func StepOut(dbp Process) error {
} }
sameGCond := SameGoroutineCondition(selg) sameGCond := SameGoroutineCondition(selg)
retFrameCond := andFrameoffCondition(sameGCond, retframe.Regs.CFA-int64(retframe.StackHi)) retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset())
var deferpc uint64 = 0 var deferpc uint64 = 0
if filepath.Ext(topframe.Current.File) == ".go" { if filepath.Ext(topframe.Current.File) == ".go" {
...@@ -358,7 +358,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { ...@@ -358,7 +358,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
} }
var ( var (
threadg = map[int]Thread{} threadg = map[int]*G{}
allg []*G allg []*G
rdr = dbp.BinInfo().DwarfReader() rdr = dbp.BinInfo().DwarfReader()
) )
...@@ -370,7 +370,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { ...@@ -370,7 +370,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
} }
g, _ := GetG(th) g, _ := GetG(th)
if g != nil { if g != nil {
threadg[g.ID] = th threadg[g.ID] = g
} }
} }
...@@ -410,14 +410,15 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { ...@@ -410,14 +410,15 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if thread, allocated := threadg[g.ID]; allocated { if thg, allocated := threadg[g.ID]; allocated {
loc, err := thread.Location() loc, err := thg.Thread.Location()
if err != nil { if err != nil {
return nil, err return nil, err
} }
g.Thread = thread g.Thread = thg.Thread
// Prefer actual thread location information. // Prefer actual thread location information.
g.CurrentLoc = *loc g.CurrentLoc = *loc
g.SystemStack = thg.SystemStack
} }
if g.Status != Gdead { if g.Status != Gdead {
allg = append(allg, g) allg = append(allg, g)
...@@ -481,10 +482,10 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) { ...@@ -481,10 +482,10 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid) return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
} }
return &EvalScope{locs[frame].Current.PC, locs[frame].Regs, thread, g.variable, dbp.BinInfo(), locs[frame].StackHi}, nil return &EvalScope{locs[frame].Current.PC, locs[frame].Regs, thread, g.variable, dbp.BinInfo(), locs[frame].FrameOffset()}, nil
} }
// FrameToScope returns a new EvalScope for this frame // FrameToScope returns a new EvalScope for this frame
func FrameToScope(p Process, frame Stackframe) *EvalScope { func FrameToScope(p Process, frame Stackframe) *EvalScope {
return &EvalScope{frame.Current.PC, frame.Regs, p.CurrentThread(), nil, p.BinInfo(), frame.StackHi} return &EvalScope{frame.Current.PC, frame.Regs, p.CurrentThread(), nil, p.BinInfo(), frame.FrameOffset()}
} }
...@@ -3121,3 +3121,219 @@ func TestIssue844(t *testing.T) { ...@@ -3121,3 +3121,219 @@ func TestIssue844(t *testing.T) {
} }
}) })
} }
func logStacktrace(t *testing.T, frames []proc.Stackframe) {
for j := range frames {
name := "?"
if frames[j].Current.Fn != nil {
name = frames[j].Current.Fn.Name
}
t.Logf("\t%#x %#x %#x %s at %s:%d\n", frames[j].Call.PC, frames[j].FrameOffset(), frames[j].FramePointerOffset(), name, filepath.Base(frames[j].Call.File), frames[j].Call.Line)
}
}
// stacktraceCheck checks that all the functions listed in tc appear in
// frames in the same order.
// Checks that all the functions in tc starting with "C." or with "!" are in
// a systemstack frame.
// Returns a slice m where m[i] is the index in frames of the function tc[i]
// or nil if any check fails.
func stacktraceCheck(t *testing.T, tc []string, frames []proc.Stackframe) []int {
m := make([]int, len(tc))
i, j := 0, 0
for i < len(tc) {
tcname := tc[i]
tcsystem := strings.HasPrefix(tcname, "C.")
if tcname[0] == '!' {
tcsystem = true
tcname = tcname[1:]
}
for j < len(frames) {
name := "?"
if frames[j].Current.Fn != nil {
name = frames[j].Current.Fn.Name
}
if name == tcname {
m[i] = j
if tcsystem != frames[j].SystemStack {
t.Logf("system stack check failed for frame %d (expected %v got %v)", j, tcsystem, frames[j].SystemStack)
t.Logf("expected: %v\n", tc)
return nil
}
break
}
j++
}
if j >= len(frames) {
t.Logf("couldn't find frame %d %s", i, tc)
t.Logf("expected: %v\n", tc)
return nil
}
i++
}
return m
}
func frameInFile(frame proc.Stackframe, file string) bool {
for _, loc := range []proc.Location{frame.Current, frame.Call} {
if !strings.HasSuffix(loc.File, "/"+file) && !strings.HasSuffix(loc.File, "\\"+file) {
return false
}
if loc.Line <= 0 {
return false
}
}
return true
}
func TestCgoStacktrace(t *testing.T) {
if runtime.GOOS == "windows" {
ver, _ := goversion.Parse(runtime.Version())
if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
t.Skip("disabled on windows with go before version 1.9")
}
}
if runtime.GOOS == "darwin" {
ver, _ := goversion.Parse(runtime.Version())
if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 8, -1, 0, 0, ""}) {
t.Skip("disabled on macOS with go before version 1.8")
}
}
// Tests that:
// a) we correctly identify the goroutine while we are executing cgo code
// b) that we can stitch together the system stack (where cgo code
// executes) and the normal goroutine stack
// Each test case describes how the stack trace should appear after a
// continue. The first function on each test case is the topmost function
// that should be found on the stack, the actual stack trace can have more
// frame than those listed here but all the frames listed must appear in
// the specified order.
testCases := [][]string{
[]string{"main.main"},
[]string{"C.helloworld_pt2", "C.helloworld", "main.main"},
[]string{"main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"},
[]string{"C.helloworld_pt4", "C.helloworld_pt3", "main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"},
[]string{"main.helloWorld2", "C.helloworld_pt4", "C.helloworld_pt3", "main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"}}
var gid int
frameOffs := map[string]int64{}
framePointerOffs := map[string]int64{}
withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) {
for itidx, tc := range testCases {
assertNoError(proc.Continue(p), t, fmt.Sprintf("Continue at iteration step %d", itidx))
g, err := proc.GetG(p.CurrentThread())
assertNoError(err, t, fmt.Sprintf("GetG at iteration step %d", itidx))
if itidx == 0 {
gid = g.ID
} else {
if gid != g.ID {
t.Fatalf("wrong goroutine id at iteration step %d (expected %d got %d)", itidx, gid, g.ID)
}
}
frames, err := g.Stacktrace(100)
assertNoError(err, t, fmt.Sprintf("Stacktrace at iteration step %d", itidx))
t.Logf("iteration step %d", itidx)
logStacktrace(t, frames)
m := stacktraceCheck(t, tc, frames)
mismatch := (m == nil)
for i, j := range m {
if strings.HasPrefix(tc[i], "C.hellow") {
if !frameInFile(frames[j], "hello.c") {
t.Logf("position in %q is %s:%d (call %s:%d)", tc[i], frames[j].Current.File, frames[j].Current.Line, frames[j].Call.File, frames[j].Call.Line)
mismatch = true
break
}
}
if frameOff, ok := frameOffs[tc[i]]; ok {
if frameOff != frames[j].FrameOffset() {
t.Logf("frame %s offset mismatch", tc[i])
}
if framePointerOffs[tc[i]] != frames[j].FramePointerOffset() {
t.Logf("frame %s pointer offset mismatch", tc[i])
}
} else {
frameOffs[tc[i]] = frames[j].FrameOffset()
framePointerOffs[tc[i]] = frames[j].FramePointerOffset()
}
}
// also check that ThreadStacktrace produces the same list of frames
threadFrames, err := proc.ThreadStacktrace(p.CurrentThread(), 100)
assertNoError(err, t, fmt.Sprintf("ThreadStacktrace at iteration step %d", itidx))
if len(threadFrames) != len(frames) {
mismatch = true
} else {
for j := range frames {
if frames[j].Current.File != threadFrames[j].Current.File || frames[j].Current.Line != threadFrames[j].Current.Line {
t.Logf("stack mismatch between goroutine stacktrace and thread stacktrace")
t.Logf("thread stacktrace:")
logStacktrace(t, threadFrames)
mismatch = true
break
}
}
}
if mismatch {
t.Fatal("see previous loglines")
}
}
})
}
func TestCgoSources(t *testing.T) {
if runtime.GOOS == "windows" {
ver, _ := goversion.Parse(runtime.Version())
if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
t.Skip("disabled on windows with go before version 1.9")
}
}
withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) {
sources := p.BinInfo().Sources
for _, needle := range []string{"main.go", "hello.c"} {
found := false
for _, k := range sources {
if strings.HasSuffix(k, "/"+needle) || strings.HasSuffix(k, "\\"+needle) {
found = true
break
}
}
if !found {
t.Errorf("File %s not found", needle)
}
}
})
}
func TestSystemstackStacktrace(t *testing.T) {
// check that we can follow a stack switch initiated by runtime.systemstack()
withTestProcess("panic", t, func(p proc.Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "runtime.startpanic_m")
assertNoError(err, t, "setFunctionBreakpoint()")
assertNoError(proc.Continue(p), t, "first continue")
assertNoError(proc.Continue(p), t, "second continue")
g, err := proc.GetG(p.CurrentThread())
assertNoError(err, t, "GetG")
frames, err := g.Stacktrace(100)
assertNoError(err, t, "stacktrace")
logStacktrace(t, frames)
m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "!runtime.systemstack", "runtime.startpanic", "main.main"}, frames)
if m == nil {
t.Fatal("see previous loglines")
}
})
}
...@@ -12,8 +12,6 @@ import ( ...@@ -12,8 +12,6 @@ import (
// This code is partly adaped from runtime.gentraceback in // This code is partly adaped from runtime.gentraceback in
// $GOROOT/src/runtime/traceback.go // $GOROOT/src/runtime/traceback.go
const runtimeStackBarrier = "runtime.stackBarrier"
// NoReturnAddr is returned when return address // NoReturnAddr is returned when return address
// could not be found during stack trace. // could not be found during stack trace.
type NoReturnAddr struct { type NoReturnAddr struct {
...@@ -33,24 +31,50 @@ type Stackframe struct { ...@@ -33,24 +31,50 @@ type Stackframe struct {
// Frame registers. // Frame registers.
Regs op.DwarfRegisters Regs op.DwarfRegisters
// High address of the stack. // High address of the stack.
StackHi uint64 stackHi uint64
// Return address for this stack frame (as read from the stack frame itself). // Return address for this stack frame (as read from the stack frame itself).
Ret uint64 Ret uint64
// Address to the memory location containing the return address // Address to the memory location containing the return address
addrret uint64 addrret uint64
// Err is set if an error occoured during stacktrace // Err is set if an error occoured during stacktrace
Err error Err error
// SystemStack is true if this frame belongs to a system stack.
SystemStack bool
}
// FrameOffset returns the address of the stack frame, absolute for system
// stack frames or as an offset from stackhi for goroutine stacks (a
// negative value).
func (frame *Stackframe) FrameOffset() int64 {
if frame.SystemStack {
return frame.Regs.CFA
}
return frame.Regs.CFA - int64(frame.stackHi)
}
// FramePointerOffset returns the value of the frame pointer, absolute for
// system stack frames or as an offset from stackhi for goroutine stacks (a
// negative value).
func (frame *Stackframe) FramePointerOffset() int64 {
if frame.SystemStack {
return int64(frame.Regs.BP())
}
return int64(frame.Regs.BP()) - int64(frame.stackHi)
} }
// ThreadStacktrace returns the stack trace for thread. // ThreadStacktrace returns the stack trace for thread.
// Note the locations in the array are return addresses not call addresses. // Note the locations in the array are return addresses not call addresses.
func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) { func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
regs, err := thread.Registers(true) g, _ := GetG(thread)
if err != nil { if g == nil {
return nil, err regs, err := thread.Registers(true)
if err != nil {
return nil, err
}
it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs), 0, nil, -1, nil)
return it.stacktrace(depth)
} }
it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs), 0, nil, -1) return g.Stacktrace(depth)
return it.stacktrace(depth)
} }
func (g *G) stackIterator() (*stackIterator, error) { func (g *G) stackIterator() (*stackIterator, error) {
...@@ -58,14 +82,15 @@ func (g *G) stackIterator() (*stackIterator, error) { ...@@ -58,14 +82,15 @@ func (g *G) stackIterator() (*stackIterator, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if g.Thread != nil { if g.Thread != nil {
regs, err := g.Thread.Registers(true) regs, err := g.Thread.Registers(true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(regs), g.stackhi, stkbar, g.stkbarPos), nil return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(regs), g.stackhi, stkbar, g.stkbarPos, g), nil
} }
return newStackIterator(g.variable.bi, g.variable.mem, g.variable.bi.Arch.GoroutineToDwarfRegisters(g), g.stackhi, stkbar, g.stkbarPos), nil return newStackIterator(g.variable.bi, g.variable.mem, g.variable.bi.Arch.GoroutineToDwarfRegisters(g), g.stackhi, stkbar, g.stkbarPos, g), nil
} }
// Stacktrace returns the stack trace for a goroutine. // Stacktrace returns the stack trace for a goroutine.
...@@ -98,11 +123,15 @@ type stackIterator struct { ...@@ -98,11 +123,15 @@ type stackIterator struct {
err error err error
stackhi uint64 stackhi uint64
systemstack bool
stackBarrierPC uint64 stackBarrierPC uint64
stkbar []savedLR stkbar []savedLR
// regs is the register set for the current frame // regs is the register set for the current frame
regs op.DwarfRegisters regs op.DwarfRegisters
g *G // the goroutine being stacktraced, nil if we are stacktracing a goroutine-less thread
g0_sched_sp uint64 // value of g0.sched.sp (see comments around its use)
} }
type savedLR struct { type savedLR struct {
...@@ -110,13 +139,13 @@ type savedLR struct { ...@@ -110,13 +139,13 @@ type savedLR struct {
val uint64 val uint64
} }
func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, stkbar []savedLR, stkbarPos int) *stackIterator { func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegisters, stackhi uint64, stkbar []savedLR, stkbarPos int, g *G) *stackIterator {
stackBarrierFunc := bi.LookupFunc[runtimeStackBarrier] // stack barriers were removed in Go 1.9 stackBarrierFunc := bi.LookupFunc["runtime.stackBarrier"] // stack barriers were removed in Go 1.9
var stackBarrierPC uint64 var stackBarrierPC uint64
if stackBarrierFunc != nil && stkbar != nil { if stackBarrierFunc != nil && stkbar != nil {
stackBarrierPC = stackBarrierFunc.Entry stackBarrierPC = stackBarrierFunc.Entry
fn := bi.PCToFunc(regs.PC()) fn := bi.PCToFunc(regs.PC())
if fn != nil && fn.Name == runtimeStackBarrier { if fn != nil && fn.Name == "runtime.stackBarrier" {
// We caught the goroutine as it's executing the stack barrier, we must // We caught the goroutine as it's executing the stack barrier, we must
// determine whether or not g.stackPos has already been incremented or not. // determine whether or not g.stackPos has already been incremented or not.
if len(stkbar) > 0 && stkbar[stkbarPos].ptr < regs.SP() { if len(stkbar) > 0 && stkbar[stkbarPos].ptr < regs.SP() {
...@@ -130,7 +159,19 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegiste ...@@ -130,7 +159,19 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegiste
} }
stkbar = stkbar[stkbarPos:] stkbar = stkbar[stkbarPos:]
} }
return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar} var g0_sched_sp uint64
systemstack := true
if g != nil {
systemstack = g.SystemStack
g0var, _ := g.variable.fieldVariable("m").structMember("g0")
if g0var != nil {
g0, _ := g0var.parseG()
if g0 != nil {
g0_sched_sp = g0.SP
}
}
}
return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp}
} }
// Next points the iterator to the next stack frame. // Next points the iterator to the next stack frame.
...@@ -152,9 +193,7 @@ func (it *stackIterator) Next() bool { ...@@ -152,9 +193,7 @@ func (it *stackIterator) Next() bool {
it.stkbar = it.stkbar[1:] it.stkbar = it.stkbar[1:]
} }
// Look for "top of stack" functions. if it.switchStack() {
if it.frame.Current.Fn != nil && (it.frame.Current.Fn.Name == "runtime.goexit" || it.frame.Current.Fn.Name == "runtime.rt0_go" || it.frame.Current.Fn.Name == "runtime.mcall") {
it.atend = true
return true return true
} }
...@@ -164,6 +203,109 @@ func (it *stackIterator) Next() bool { ...@@ -164,6 +203,109 @@ func (it *stackIterator) Next() bool {
return true 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
}
// switch from system stack to 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.mstart":
if it.top || !it.systemstack || it.g == nil {
return false
}
// 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 fn := it.bi.PCToFunc(it.g.PC); fn == nil || fn.Name != "runtime.systemstack_switch" {
return false
}
it.systemstack = 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
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
default:
return false
}
}
// Frame returns the frame the iterator is pointing at. // Frame returns the frame the iterator is pointing at.
func (it *stackIterator) Frame() Stackframe { func (it *stackIterator) Frame() Stackframe {
return it.frame return it.frame
...@@ -199,14 +341,25 @@ func (it *stackIterator) newStackframe(ret, retaddr uint64) Stackframe { ...@@ -199,14 +341,25 @@ func (it *stackIterator) newStackframe(ret, retaddr uint64) Stackframe {
} else { } else {
it.regs.FrameBase = it.frameBase(fn) it.regs.FrameBase = it.frameBase(fn)
} }
r := Stackframe{Current: Location{PC: it.pc, File: f, Line: l, Fn: fn}, Regs: it.regs, Ret: ret, addrret: retaddr, StackHi: it.stackhi} r := Stackframe{Current: Location{PC: it.pc, File: f, Line: l, Fn: fn}, Regs: it.regs, Ret: ret, addrret: retaddr, stackHi: it.stackhi, SystemStack: it.systemstack}
if !it.top { if !it.top {
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(it.pc - 1) fnname := ""
if r.Call.Fn == nil { if r.Current.Fn != nil {
r.Call.File = "?" fnname = r.Current.Fn.Name
r.Call.Line = -1 }
switch fnname {
case "runtime.mstart", "runtime.systemstack_switch":
// these frames are inserted by runtime.systemstack and there is no CALL
// instruction to look for at pc - 1
r.Call = r.Current
default:
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 { } else {
r.Call = r.Current r.Call = r.Current
} }
...@@ -240,9 +393,9 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin ...@@ -240,9 +393,9 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
fde, err := it.bi.frameEntries.FDEForPC(it.pc) fde, err := it.bi.frameEntries.FDEForPC(it.pc)
var framectx *frame.FrameContext var framectx *frame.FrameContext
if _, nofde := err.(*frame.NoFDEForPCError); nofde { if _, nofde := err.(*frame.NoFDEForPCError); nofde {
framectx = it.bi.Arch.FixFrameUnwindContext(nil) framectx = it.bi.Arch.FixFrameUnwindContext(nil, it.pc, it.bi)
} else { } else {
framectx = it.bi.Arch.FixFrameUnwindContext(fde.EstablishFrame(it.pc)) framectx = it.bi.Arch.FixFrameUnwindContext(fde.EstablishFrame(it.pc), it.pc, it.bi)
} }
cfareg, err := it.executeFrameRegRule(0, framectx.CFA, 0) cfareg, err := it.executeFrameRegRule(0, framectx.CFA, 0)
...@@ -252,7 +405,7 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin ...@@ -252,7 +405,7 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
} }
it.regs.CFA = int64(cfareg.Uint64Val) it.regs.CFA = int64(cfareg.Uint64Val)
callFrameRegs = op.DwarfRegisters{ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum} callFrameRegs = op.DwarfRegisters{ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum}
// According to the standard the compiler should be responsible for emitting // 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, // rules for the RSP register so that it can then be used to calculate CFA,
...@@ -289,7 +442,8 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c ...@@ -289,7 +442,8 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c
case frame.RuleUndefined: case frame.RuleUndefined:
return nil, nil return nil, nil
case frame.RuleSameVal: case frame.RuleSameVal:
return it.regs.Reg(regnum), nil reg := *it.regs.Reg(regnum)
return &reg, nil
case frame.RuleOffset: case frame.RuleOffset:
return it.readRegisterAt(regnum, uint64(cfa+rule.Offset)) return it.readRegisterAt(regnum, uint64(cfa+rule.Offset))
case frame.RuleValOffset: case frame.RuleValOffset:
...@@ -315,11 +469,17 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c ...@@ -315,11 +469,17 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c
return nil, nil return nil, nil
} }
return op.DwarfRegisterFromUint64(uint64(int64(it.regs.Uint64Val(rule.Reg)) + rule.Offset)), nil return op.DwarfRegisterFromUint64(uint64(int64(it.regs.Uint64Val(rule.Reg)) + rule.Offset)), nil
case frame.RuleRegOffset: case frame.RuleFramePointer:
if it.regs.Reg(rule.Reg) == nil { curReg := it.regs.Reg(rule.Reg)
if curReg == nil {
return nil, nil return nil, nil
} }
return it.readRegisterAt(regnum, uint64(int64(it.regs.Uint64Val(rule.Reg))+rule.Offset)) if curReg.Uint64Val <= uint64(cfa) {
return it.readRegisterAt(regnum, curReg.Uint64Val)
} else {
newReg := *curReg
return &newReg, nil
}
} }
} }
......
...@@ -137,8 +137,8 @@ func next(dbp Process, stepInto bool) error { ...@@ -137,8 +137,8 @@ func next(dbp Process, stepInto bool) error {
} }
sameGCond := SameGoroutineCondition(selg) sameGCond := SameGoroutineCondition(selg)
retFrameCond := andFrameoffCondition(sameGCond, retframe.Regs.CFA-int64(retframe.StackHi)) retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset())
sameFrameCond := andFrameoffCondition(sameGCond, topframe.Regs.CFA-int64(topframe.StackHi)) sameFrameCond := andFrameoffCondition(sameGCond, topframe.FrameOffset())
var sameOrRetFrameCond ast.Expr var sameOrRetFrameCond ast.Expr
if sameGCond != nil { if sameGCond != nil {
sameOrRetFrameCond = &ast.BinaryExpr{ sameOrRetFrameCond = &ast.BinaryExpr{
...@@ -146,8 +146,8 @@ func next(dbp Process, stepInto bool) error { ...@@ -146,8 +146,8 @@ func next(dbp Process, stepInto bool) error {
X: sameGCond, X: sameGCond,
Y: &ast.BinaryExpr{ Y: &ast.BinaryExpr{
Op: token.LOR, Op: token.LOR,
X: frameoffCondition(topframe.Regs.CFA - int64(topframe.StackHi)), X: frameoffCondition(topframe.FrameOffset()),
Y: frameoffCondition(retframe.Regs.CFA - int64(retframe.StackHi)), Y: frameoffCondition(retframe.FrameOffset()),
}, },
} }
} }
...@@ -361,11 +361,27 @@ func GetG(thread Thread) (g *G, err error) { ...@@ -361,11 +361,27 @@ func GetG(thread Thread) (g *G, err error) {
} }
g, err = gaddr.parseG() g, err = gaddr.parseG()
if err == nil { if err != nil {
g.Thread = thread return
if loc, err := thread.Location(); err == nil { }
g.CurrentLoc = *loc if g.ID == 0 {
// The runtime uses a special goroutine with ID == 0 to mark that the
// current goroutine is executing on the system stack (sometimes also
// referred to as the g0 stack or scheduler stack, I'm not sure if there's
// actually any difference between those).
// For our purposes it's better if we always return the real goroutine
// since the rest of the code assumes the goroutine ID is univocal.
// The real 'current goroutine' is stored in g0.m.curg
curgvar, _ := g.variable.fieldVariable("m").structMember("curg")
g, err = curgvar.parseG()
if err != nil {
return
} }
g.SystemStack = true
}
g.Thread = thread
if loc, err := thread.Location(); err == nil {
g.CurrentLoc = *loc
} }
return return
} }
...@@ -395,7 +411,7 @@ func GoroutineScope(thread Thread) (*EvalScope, error) { ...@@ -395,7 +411,7 @@ func GoroutineScope(thread Thread) (*EvalScope, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &EvalScope{locations[0].Current.PC, locations[0].Regs, thread, g.variable, thread.BinInfo(), g.stackhi}, nil return &EvalScope{locations[0].Current.PC, locations[0].Regs, thread, g.variable, thread.BinInfo(), locations[0].FrameOffset()}, nil
} }
func onRuntimeBreakpoint(thread Thread) bool { func onRuntimeBreakpoint(thread Thread) bool {
......
...@@ -138,6 +138,8 @@ type G struct { ...@@ -138,6 +138,8 @@ type G struct {
stkbarPos int // stkbarPos field of g struct stkbarPos int // stkbarPos field of g struct
stackhi uint64 // value of stack.hi stackhi uint64 // value of stack.hi
SystemStack bool // SystemStack is true if this goroutine is currently executing on a system stack.
// Information on goroutine location // Information on goroutine location
CurrentLoc Location CurrentLoc Location
...@@ -155,7 +157,8 @@ type EvalScope struct { ...@@ -155,7 +157,8 @@ type EvalScope struct {
Mem MemoryReadWriter // Target's memory Mem MemoryReadWriter // Target's memory
Gvar *Variable Gvar *Variable
BinInfo *BinaryInfo BinInfo *BinaryInfo
StackHi uint64
frameOffset int64
} }
// IsNilErr is returned when a variable is nil. // IsNilErr is returned when a variable is nil.
......
...@@ -854,7 +854,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo ...@@ -854,7 +854,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
for i := range rawlocs { for i := range rawlocs {
frame := api.Stackframe{ frame := api.Stackframe{
Location: api.ConvertLocation(rawlocs[i].Call), Location: api.ConvertLocation(rawlocs[i].Call),
FrameOffset: rawlocs[i].Regs.CFA - int64(rawlocs[i].StackHi), FrameOffset: rawlocs[i].FrameOffset(),
} }
if rawlocs[i].Err != nil { if rawlocs[i].Err != nil {
frame.Err = rawlocs[i].Err.Error() frame.Err = rawlocs[i].Err.Error()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册