mirror of
https://github.com/golang/go
synced 2024-11-24 01:50:11 -07:00
cmd/link, runtime: mark goexit as the top of the call stack
This CL adds a new attribute, TOPFRAME, which can be used to mark functions that should be treated as being at the top of the call stack. The function `runtime.goexit` has been marked this way on architectures that use a link register. This will stop programs that use DWARF to unwind the call stack from unwinding past `runtime.goexit` on architectures that use a link register. For example, it eliminates "corrupt stack?" warnings when generating a backtrace that hits `runtime.goexit` in GDB on s390x. Similar code should be added for non-link-register architectures (i.e. amd64, 386). They mark the top of the call stack slightly differently to link register architectures so I haven't added that code (they need to mark "rip" as undefined). Fixes #24385. Change-Id: I15b4c69ac75b491daa0acf0d981cb80eb06488de Reviewed-on: https://go-review.googlesource.com/c/go/+/169726 Run-TryBot: Michael Munday <mike.munday@ibm.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
parent
0bd101cecc
commit
aafe257390
@ -97,6 +97,7 @@ type Func struct {
|
|||||||
Frame int64 // size in bytes of local variable frame
|
Frame int64 // size in bytes of local variable frame
|
||||||
Leaf bool // function omits save of link register (ARM)
|
Leaf bool // function omits save of link register (ARM)
|
||||||
NoSplit bool // function omits stack split prologue
|
NoSplit bool // function omits stack split prologue
|
||||||
|
TopFrame bool // function is the top of the call stack
|
||||||
Var []Var // detail about local variables
|
Var []Var // detail about local variables
|
||||||
PCSP Data // PC → SP offset map
|
PCSP Data // PC → SP offset map
|
||||||
PCFile Data // PC → file number map (index into File)
|
PCFile Data // PC → file number map (index into File)
|
||||||
@ -576,6 +577,7 @@ func (r *objReader) parseObject(prefix []byte) error {
|
|||||||
f.Frame = r.readInt()
|
f.Frame = r.readInt()
|
||||||
flags := r.readInt()
|
flags := r.readInt()
|
||||||
f.Leaf = flags&(1<<0) != 0
|
f.Leaf = flags&(1<<0) != 0
|
||||||
|
f.TopFrame = flags&(1<<4) != 0
|
||||||
f.NoSplit = r.readInt() != 0
|
f.NoSplit = r.readInt() != 0
|
||||||
f.Var = make([]Var, r.readInt())
|
f.Var = make([]Var, r.readInt())
|
||||||
for i := range f.Var {
|
for i := range f.Var {
|
||||||
|
@ -489,6 +489,10 @@ const (
|
|||||||
// target of an inline during compilation
|
// target of an inline during compilation
|
||||||
AttrWasInlined
|
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
|
// attrABIBase is the value at which the ABI is encoded in
|
||||||
// Attribute. This must be last; all bits after this are
|
// Attribute. This must be last; all bits after this are
|
||||||
// assumed to be an ABI value.
|
// assumed to be an ABI value.
|
||||||
@ -511,6 +515,7 @@ func (a Attribute) NeedCtxt() bool { return a&AttrNeedCtxt != 0 }
|
|||||||
func (a Attribute) NoFrame() bool { return a&AttrNoFrame != 0 }
|
func (a Attribute) NoFrame() bool { return a&AttrNoFrame != 0 }
|
||||||
func (a Attribute) Static() bool { return a&AttrStatic != 0 }
|
func (a Attribute) Static() bool { return a&AttrStatic != 0 }
|
||||||
func (a Attribute) WasInlined() bool { return a&AttrWasInlined != 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) {
|
func (a *Attribute) Set(flag Attribute, value bool) {
|
||||||
if value {
|
if value {
|
||||||
@ -544,6 +549,7 @@ var textAttrStrings = [...]struct {
|
|||||||
{bit: AttrNoFrame, s: "NOFRAME"},
|
{bit: AttrNoFrame, s: "NOFRAME"},
|
||||||
{bit: AttrStatic, s: "STATIC"},
|
{bit: AttrStatic, s: "STATIC"},
|
||||||
{bit: AttrWasInlined, s: ""},
|
{bit: AttrWasInlined, s: ""},
|
||||||
|
{bit: AttrTopFrame, s: "TOPFRAME"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextAttrString formats a for printing in as part of a TEXT prog.
|
// TextAttrString formats a for printing in as part of a TEXT prog.
|
||||||
|
@ -230,6 +230,9 @@ func (w *objWriter) writeSymDebug(s *LSym) {
|
|||||||
if s.NoSplit() {
|
if s.NoSplit() {
|
||||||
fmt.Fprintf(ctxt.Bso, "nosplit ")
|
fmt.Fprintf(ctxt.Bso, "nosplit ")
|
||||||
}
|
}
|
||||||
|
if s.TopFrame() {
|
||||||
|
fmt.Fprintf(ctxt.Bso, "topframe ")
|
||||||
|
}
|
||||||
fmt.Fprintf(ctxt.Bso, "size=%d", s.Size)
|
fmt.Fprintf(ctxt.Bso, "size=%d", s.Size)
|
||||||
if s.Type == objabi.STEXT {
|
if s.Type == objabi.STEXT {
|
||||||
fmt.Fprintf(ctxt.Bso, " args=%#x locals=%#x", uint64(s.Func.Args), uint64(s.Func.Locals))
|
fmt.Fprintf(ctxt.Bso, " args=%#x locals=%#x", uint64(s.Func.Args), uint64(s.Func.Locals))
|
||||||
@ -342,6 +345,9 @@ func (w *objWriter) writeSym(s *LSym) {
|
|||||||
if ctxt.Flag_shared {
|
if ctxt.Flag_shared {
|
||||||
flags |= 1 << 3
|
flags |= 1 << 3
|
||||||
}
|
}
|
||||||
|
if s.TopFrame() {
|
||||||
|
flags |= 1 << 4
|
||||||
|
}
|
||||||
w.writeInt(flags)
|
w.writeInt(flags)
|
||||||
w.writeInt(int64(len(s.Func.Autom)))
|
w.writeInt(int64(len(s.Func.Autom)))
|
||||||
for _, a := range s.Func.Autom {
|
for _, a := range s.Func.Autom {
|
||||||
|
@ -132,6 +132,7 @@ func (ctxt *Link) InitTextSym(s *LSym, flag int) {
|
|||||||
s.Set(AttrWrapper, flag&WRAPPER != 0)
|
s.Set(AttrWrapper, flag&WRAPPER != 0)
|
||||||
s.Set(AttrNeedCtxt, flag&NEEDCTXT != 0)
|
s.Set(AttrNeedCtxt, flag&NEEDCTXT != 0)
|
||||||
s.Set(AttrNoFrame, flag&NOFRAME != 0)
|
s.Set(AttrNoFrame, flag&NOFRAME != 0)
|
||||||
|
s.Set(AttrTopFrame, flag&TOPFRAME != 0)
|
||||||
s.Type = objabi.STEXT
|
s.Type = objabi.STEXT
|
||||||
ctxt.Text = append(ctxt.Text, s)
|
ctxt.Text = append(ctxt.Text, s)
|
||||||
|
|
||||||
|
@ -47,4 +47,8 @@ const (
|
|||||||
|
|
||||||
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
|
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
|
||||||
REFLECTMETHOD = 1024
|
REFLECTMETHOD = 1024
|
||||||
|
|
||||||
|
// Function is the top of the call stack. Call stack unwinders should stop
|
||||||
|
// at this function.
|
||||||
|
TOPFRAME = 2048
|
||||||
)
|
)
|
||||||
|
@ -1451,6 +1451,13 @@ func writeframes(ctxt *Link, syms []*sym.Symbol) []*sym.Symbol {
|
|||||||
// Emit a FDE, Section 6.4.1.
|
// Emit a FDE, Section 6.4.1.
|
||||||
// First build the section contents into a byte buffer.
|
// First build the section contents into a byte buffer.
|
||||||
deltaBuf = deltaBuf[:0]
|
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() {
|
for pcsp.init(s.FuncInfo.Pcsp.P); !pcsp.done; pcsp.next() {
|
||||||
nextpc := pcsp.nextpc
|
nextpc := pcsp.nextpc
|
||||||
|
|
||||||
@ -1463,7 +1470,13 @@ func writeframes(ctxt *Link, syms []*sym.Symbol) []*sym.Symbol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// TODO(bryanpkc): This is imprecise. In general, the instruction
|
||||||
// that stores the return address to the stack frame is not the
|
// that stores the return address to the stack frame is not the
|
||||||
// same one that allocates the frame.
|
// same one that allocates the frame.
|
||||||
@ -1472,17 +1485,16 @@ func writeframes(ctxt *Link, syms []*sym.Symbol) []*sym.Symbol {
|
|||||||
// after a stack frame has been allocated.
|
// after a stack frame has been allocated.
|
||||||
deltaBuf = append(deltaBuf, dwarf.DW_CFA_offset_extended_sf)
|
deltaBuf = append(deltaBuf, dwarf.DW_CFA_offset_extended_sf)
|
||||||
deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
|
deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
|
||||||
deltaBuf = dwarf.AppendSleb128(deltaBuf, -int64(pcsp.value)/dataAlignmentFactor)
|
deltaBuf = dwarf.AppendSleb128(deltaBuf, -spdelta/dataAlignmentFactor)
|
||||||
} else {
|
} else {
|
||||||
// The return address is restored into the link register
|
// The return address is restored into the link register
|
||||||
// when a stack frame has been de-allocated.
|
// when a stack frame has been de-allocated.
|
||||||
deltaBuf = append(deltaBuf, dwarf.DW_CFA_same_value)
|
deltaBuf = append(deltaBuf, dwarf.DW_CFA_same_value)
|
||||||
deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
|
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)
|
pad := int(Rnd(int64(len(deltaBuf)), int64(ctxt.Arch.PtrSize))) - len(deltaBuf)
|
||||||
deltaBuf = append(deltaBuf, zeros[:pad]...)
|
deltaBuf = append(deltaBuf, zeros[:pad]...)
|
||||||
|
@ -282,6 +282,9 @@ overwrite:
|
|||||||
if flags&(1<<3) != 0 {
|
if flags&(1<<3) != 0 {
|
||||||
s.Attr |= sym.AttrShared
|
s.Attr |= sym.AttrShared
|
||||||
}
|
}
|
||||||
|
if flags&(1<<4) != 0 {
|
||||||
|
s.Attr |= sym.AttrTopFrame
|
||||||
|
}
|
||||||
n := r.readInt()
|
n := r.readInt()
|
||||||
pc.Autom = r.autom[:n:n]
|
pc.Autom = r.autom[:n:n]
|
||||||
if !isdup {
|
if !isdup {
|
||||||
|
@ -75,7 +75,10 @@ const (
|
|||||||
// AttrContainer is set on text symbols that are present as the .Outer for some
|
// AttrContainer is set on text symbols that are present as the .Outer for some
|
||||||
// other symbol.
|
// other symbol.
|
||||||
AttrContainer
|
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) DuplicateOK() bool { return a&AttrDuplicateOK != 0 }
|
||||||
@ -95,6 +98,7 @@ func (a Attribute) Shared() bool { return a&AttrShared != 0 }
|
|||||||
func (a Attribute) VisibilityHidden() bool { return a&AttrVisibilityHidden != 0 }
|
func (a Attribute) VisibilityHidden() bool { return a&AttrVisibilityHidden != 0 }
|
||||||
func (a Attribute) SubSymbol() bool { return a&AttrSubSymbol != 0 }
|
func (a Attribute) SubSymbol() bool { return a&AttrSubSymbol != 0 }
|
||||||
func (a Attribute) Container() bool { return a&AttrContainer != 0 }
|
func (a Attribute) Container() bool { return a&AttrContainer != 0 }
|
||||||
|
func (a Attribute) TopFrame() bool { return a&AttrTopFrame != 0 }
|
||||||
|
|
||||||
func (a Attribute) CgoExport() bool {
|
func (a Attribute) CgoExport() bool {
|
||||||
return a.CgoExportDynamic() || a.CgoExportStatic()
|
return a.CgoExportDynamic() || a.CgoExportStatic()
|
||||||
|
@ -864,7 +864,7 @@ TEXT _cgo_topofstack(SB),NOSPLIT,$8
|
|||||||
|
|
||||||
// The top-most function running on a goroutine
|
// The top-most function running on a goroutine
|
||||||
// returns to goexit+PCQuantum.
|
// 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
|
MOVW R0, R0 // NOP
|
||||||
BL runtime·goexit1(SB) // does not return
|
BL runtime·goexit1(SB) // does not return
|
||||||
// traceback from goexit1 must hit code range of goexit
|
// traceback from goexit1 must hit code range of goexit
|
||||||
|
@ -1124,7 +1124,7 @@ TEXT runtime·return0(SB), NOSPLIT, $0
|
|||||||
|
|
||||||
// The top-most function running on a goroutine
|
// The top-most function running on a goroutine
|
||||||
// returns to goexit+PCQuantum.
|
// 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
|
MOVD R0, R0 // NOP
|
||||||
BL runtime·goexit1(SB) // does not return
|
BL runtime·goexit1(SB) // does not return
|
||||||
|
|
||||||
|
@ -642,7 +642,7 @@ TEXT _cgo_topofstack(SB),NOSPLIT,$16
|
|||||||
|
|
||||||
// The top-most function running on a goroutine
|
// The top-most function running on a goroutine
|
||||||
// returns to goexit+PCQuantum.
|
// 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
|
NOR R0, R0 // NOP
|
||||||
JAL runtime·goexit1(SB) // does not return
|
JAL runtime·goexit1(SB) // does not return
|
||||||
// traceback from goexit1 must hit code range of goexit
|
// traceback from goexit1 must hit code range of goexit
|
||||||
|
@ -653,7 +653,7 @@ TEXT _cgo_topofstack(SB),NOSPLIT|NOFRAME,$0
|
|||||||
|
|
||||||
// The top-most function running on a goroutine
|
// The top-most function running on a goroutine
|
||||||
// returns to goexit+PCQuantum.
|
// 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
|
NOR R0, R0 // NOP
|
||||||
JAL runtime·goexit1(SB) // does not return
|
JAL runtime·goexit1(SB) // does not return
|
||||||
// traceback from goexit1 must hit code range of goexit
|
// traceback from goexit1 must hit code range of goexit
|
||||||
|
@ -880,7 +880,7 @@ TEXT _cgo_topofstack(SB),NOSPLIT|NOFRAME,$0
|
|||||||
// pointer in the correct place).
|
// pointer in the correct place).
|
||||||
// goexit+_PCQuantum is halfway through the usual global entry point prologue
|
// goexit+_PCQuantum is halfway through the usual global entry point prologue
|
||||||
// that derives r2 from r12 which is a bit silly, but not harmful.
|
// 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
|
MOVD 24(R1), R2
|
||||||
BL runtime·goexit1(SB) // does not return
|
BL runtime·goexit1(SB) // does not return
|
||||||
// traceback from goexit1 must hit code range of goexit
|
// traceback from goexit1 must hit code range of goexit
|
||||||
|
@ -775,7 +775,7 @@ TEXT _cgo_topofstack(SB),NOSPLIT|NOFRAME,$0
|
|||||||
|
|
||||||
// The top-most function running on a goroutine
|
// The top-most function running on a goroutine
|
||||||
// returns to goexit+PCQuantum.
|
// 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
|
BYTE $0x07; BYTE $0x00; // 2-byte nop
|
||||||
BL runtime·goexit1(SB) // does not return
|
BL runtime·goexit1(SB) // does not return
|
||||||
// traceback from goexit1 must hit code range of goexit
|
// traceback from goexit1 must hit code range of goexit
|
||||||
|
@ -82,6 +82,22 @@ func checkGdbPython(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 = `
|
const helloSource = `
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "runtime"
|
import "runtime"
|
||||||
@ -272,6 +288,11 @@ func testGdbPython(t *testing.T, cgo bool) {
|
|||||||
t.Fatalf("info locals failed: %s", bl)
|
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`)
|
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) {
|
if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
|
||||||
t.Fatalf("goroutine 1 bt failed: %s", bl)
|
t.Fatalf("goroutine 1 bt failed: %s", bl)
|
||||||
@ -281,6 +302,7 @@ func testGdbPython(t *testing.T, cgo bool) {
|
|||||||
if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
|
if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
|
||||||
t.Fatalf("goroutine 2 bt failed: %s", 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`)
|
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) {
|
if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) {
|
||||||
t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
|
t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
|
||||||
|
@ -32,3 +32,6 @@
|
|||||||
#define NOFRAME 512
|
#define NOFRAME 512
|
||||||
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
|
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
|
||||||
#define REFLECTMETHOD 1024
|
#define REFLECTMETHOD 1024
|
||||||
|
// Function is the top of the call stack. Call stack unwinders should stop
|
||||||
|
// at this function.
|
||||||
|
#define TOPFRAME 2048
|
||||||
|
Loading…
Reference in New Issue
Block a user