Frame int64 // size in bytes of local variable frame
Leaf bool // function omits save of link register (ARM)
NoSplit bool // function omits stack split prologue
+ TopFrame bool // function is the top of the call stack
Var []Var // detail about local variables
PCSP Data // PC → SP offset map
PCFile Data // PC → file number map (index into File)
f.Frame = r.readInt()
flags := r.readInt()
f.Leaf = flags&(1<<0) != 0
+ f.TopFrame = flags&(1<<4) != 0
f.NoSplit = r.readInt() != 0
f.Var = make([]Var, r.readInt())
for i := range f.Var {
// target of an inline during compilation
AttrWasInlined
+ // TopFrame means that this function is an entry point and unwinders should not
+ // keep unwinding beyond this frame.
+ AttrTopFrame
+
// attrABIBase is the value at which the ABI is encoded in
// Attribute. This must be last; all bits after this are
// assumed to be an ABI value.
func (a Attribute) NoFrame() bool { return a&AttrNoFrame != 0 }
func (a Attribute) Static() bool { return a&AttrStatic != 0 }
func (a Attribute) WasInlined() bool { return a&AttrWasInlined != 0 }
+func (a Attribute) TopFrame() bool { return a&AttrTopFrame != 0 }
func (a *Attribute) Set(flag Attribute, value bool) {
if value {
{bit: AttrNoFrame, s: "NOFRAME"},
{bit: AttrStatic, s: "STATIC"},
{bit: AttrWasInlined, s: ""},
+ {bit: AttrTopFrame, s: "TOPFRAME"},
}
// TextAttrString formats a for printing in as part of a TEXT prog.
if s.NoSplit() {
fmt.Fprintf(ctxt.Bso, "nosplit ")
}
+ if s.TopFrame() {
+ fmt.Fprintf(ctxt.Bso, "topframe ")
+ }
fmt.Fprintf(ctxt.Bso, "size=%d", s.Size)
if s.Type == objabi.STEXT {
fmt.Fprintf(ctxt.Bso, " args=%#x locals=%#x", uint64(s.Func.Args), uint64(s.Func.Locals))
if ctxt.Flag_shared {
flags |= 1 << 3
}
+ if s.TopFrame() {
+ flags |= 1 << 4
+ }
w.writeInt(flags)
w.writeInt(int64(len(s.Func.Autom)))
for _, a := range s.Func.Autom {
s.Set(AttrWrapper, flag&WRAPPER != 0)
s.Set(AttrNeedCtxt, flag&NEEDCTXT != 0)
s.Set(AttrNoFrame, flag&NOFRAME != 0)
+ s.Set(AttrTopFrame, flag&TOPFRAME != 0)
s.Type = objabi.STEXT
ctxt.Text = append(ctxt.Text, s)
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
REFLECTMETHOD = 1024
+
+ // Function is the top of the call stack. Call stack unwinders should stop
+ // at this function.
+ TOPFRAME = 2048
)
// Emit a FDE, Section 6.4.1.
// First build the section contents into a byte buffer.
deltaBuf = deltaBuf[:0]
+ if haslinkregister(ctxt) && s.Attr.TopFrame() {
+ // Mark the link register as having an undefined value.
+ // This stops call stack unwinders progressing any further.
+ // TODO: similar mark on non-LR architectures.
+ deltaBuf = append(deltaBuf, dwarf.DW_CFA_undefined)
+ deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
+ }
for pcsp.init(s.FuncInfo.Pcsp.P); !pcsp.done; pcsp.next() {
nextpc := pcsp.nextpc
}
}
- if haslinkregister(ctxt) {
+ spdelta := int64(pcsp.value)
+ if !haslinkregister(ctxt) {
+ // Return address has been pushed onto stack.
+ spdelta += int64(ctxt.Arch.PtrSize)
+ }
+
+ if haslinkregister(ctxt) && !s.Attr.TopFrame() {
// TODO(bryanpkc): This is imprecise. In general, the instruction
// that stores the return address to the stack frame is not the
// same one that allocates the frame.
// after a stack frame has been allocated.
deltaBuf = append(deltaBuf, dwarf.DW_CFA_offset_extended_sf)
deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
- deltaBuf = dwarf.AppendSleb128(deltaBuf, -int64(pcsp.value)/dataAlignmentFactor)
+ deltaBuf = dwarf.AppendSleb128(deltaBuf, -spdelta/dataAlignmentFactor)
} else {
// The return address is restored into the link register
// when a stack frame has been de-allocated.
deltaBuf = append(deltaBuf, dwarf.DW_CFA_same_value)
deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
}
- deltaBuf = appendPCDeltaCFA(ctxt.Arch, deltaBuf, int64(nextpc)-int64(pcsp.pc), int64(pcsp.value))
- } else {
- deltaBuf = appendPCDeltaCFA(ctxt.Arch, deltaBuf, int64(nextpc)-int64(pcsp.pc), int64(ctxt.Arch.PtrSize)+int64(pcsp.value))
}
+
+ deltaBuf = appendPCDeltaCFA(ctxt.Arch, deltaBuf, int64(nextpc)-int64(pcsp.pc), spdelta)
}
pad := int(Rnd(int64(len(deltaBuf)), int64(ctxt.Arch.PtrSize))) - len(deltaBuf)
deltaBuf = append(deltaBuf, zeros[:pad]...)
if flags&(1<<3) != 0 {
s.Attr |= sym.AttrShared
}
+ if flags&(1<<4) != 0 {
+ s.Attr |= sym.AttrTopFrame
+ }
n := r.readInt()
pc.Autom = r.autom[:n:n]
if !isdup {
// AttrContainer is set on text symbols that are present as the .Outer for some
// other symbol.
AttrContainer
- // 17 attributes defined so far.
+ // AttrTopFrame means that the function is an entry point and unwinders
+ // should stop when they hit this function.
+ AttrTopFrame
+ // 18 attributes defined so far.
)
func (a Attribute) DuplicateOK() bool { return a&AttrDuplicateOK != 0 }
func (a Attribute) VisibilityHidden() bool { return a&AttrVisibilityHidden != 0 }
func (a Attribute) SubSymbol() bool { return a&AttrSubSymbol != 0 }
func (a Attribute) Container() bool { return a&AttrContainer != 0 }
+func (a Attribute) TopFrame() bool { return a&AttrTopFrame != 0 }
func (a Attribute) CgoExport() bool {
return a.CgoExportDynamic() || a.CgoExportStatic()
// The top-most function running on a goroutine
// returns to goexit+PCQuantum.
-TEXT runtime·goexit(SB),NOSPLIT|NOFRAME,$0-0
+TEXT runtime·goexit(SB),NOSPLIT|NOFRAME|TOPFRAME,$0-0
MOVW R0, R0 // NOP
BL runtime·goexit1(SB) // does not return
// traceback from goexit1 must hit code range of goexit
// The top-most function running on a goroutine
// returns to goexit+PCQuantum.
-TEXT runtime·goexit(SB),NOSPLIT|NOFRAME,$0-0
+TEXT runtime·goexit(SB),NOSPLIT|NOFRAME|TOPFRAME,$0-0
MOVD R0, R0 // NOP
BL runtime·goexit1(SB) // does not return
// The top-most function running on a goroutine
// returns to goexit+PCQuantum.
-TEXT runtime·goexit(SB),NOSPLIT|NOFRAME,$0-0
+TEXT runtime·goexit(SB),NOSPLIT|NOFRAME|TOPFRAME,$0-0
NOR R0, R0 // NOP
JAL runtime·goexit1(SB) // does not return
// traceback from goexit1 must hit code range of goexit
// The top-most function running on a goroutine
// returns to goexit+PCQuantum.
-TEXT runtime·goexit(SB),NOSPLIT|NOFRAME,$0-0
+TEXT runtime·goexit(SB),NOSPLIT|NOFRAME|TOPFRAME,$0-0
NOR R0, R0 // NOP
JAL runtime·goexit1(SB) // does not return
// traceback from goexit1 must hit code range of goexit
// pointer in the correct place).
// goexit+_PCQuantum is halfway through the usual global entry point prologue
// that derives r2 from r12 which is a bit silly, but not harmful.
-TEXT runtime·goexit(SB),NOSPLIT|NOFRAME,$0-0
+TEXT runtime·goexit(SB),NOSPLIT|NOFRAME|TOPFRAME,$0-0
MOVD 24(R1), R2
BL runtime·goexit1(SB) // does not return
// traceback from goexit1 must hit code range of goexit
// The top-most function running on a goroutine
// returns to goexit+PCQuantum.
-TEXT runtime·goexit(SB),NOSPLIT|NOFRAME,$0-0
+TEXT runtime·goexit(SB),NOSPLIT|NOFRAME|TOPFRAME,$0-0
BYTE $0x07; BYTE $0x00; // 2-byte nop
BL runtime·goexit1(SB) // does not return
// traceback from goexit1 must hit code range of goexit
}
}
+// checkCleanBacktrace checks that the given backtrace is well formed and does
+// not contain any error messages from GDB.
+func checkCleanBacktrace(t *testing.T, backtrace string) {
+ backtrace = strings.TrimSpace(backtrace)
+ lines := strings.Split(backtrace, "\n")
+ if len(lines) == 0 {
+ t.Fatalf("empty backtrace")
+ }
+ for i, l := range lines {
+ if !strings.HasPrefix(l, fmt.Sprintf("#%v ", i)) {
+ t.Fatalf("malformed backtrace at line %v: %v", i, l)
+ }
+ }
+ // TODO(mundaym): check for unknown frames (e.g. "??").
+}
+
const helloSource = `
import "fmt"
import "runtime"
t.Fatalf("info locals failed: %s", bl)
}
+ // Check that the backtraces are well formed.
+ checkCleanBacktrace(t, blocks["goroutine 1 bt"])
+ checkCleanBacktrace(t, blocks["goroutine 2 bt"])
+ checkCleanBacktrace(t, blocks["goroutine 1 bt at the end"])
+
btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
t.Fatalf("goroutine 1 bt failed: %s", bl)
if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
t.Fatalf("goroutine 2 bt failed: %s", bl)
}
+
btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) {
t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
#define NOFRAME 512
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
#define REFLECTMETHOD 1024
+// Function is the top of the call stack. Call stack unwinders should stop
+// at this function.
+#define TOPFRAME 2048