From: Austin Clements Date: Thu, 14 Jul 2022 16:22:24 +0000 (-0400) Subject: runtime: implement traceback iterator X-Git-Tag: go1.21rc1~1320 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=f758d648b0aea95b10713864f20c957412ff9193;p=gostls13.git runtime: implement traceback iterator Currently, all stack walking logic is in one venerable, large, and very, very complicated function: runtime.gentraceback. This function has three distinct operating modes: printing, populating a PC buffer, or invoking a callback. And it has three different modes of unwinding: physical Go frames, inlined Go frames, and cgo frames. It also has several flags. All of this logic is very interwoven. This CL reimplements the monolithic gentraceback function as an "unwinder" type with an iterator API. It moves all of the logic for stack walking into this new type, and gentraceback is now a much-simplified wrapper around the new unwinder type that still implements printing, populating a PC buffer, and invoking a callback. Follow-up CLs will replace uses of gentraceback with direct uses of unwinder. Exposing traceback functionality as an iterator API will enable a lot of follow-up work such as simplifying the open-coded defer implementation (which should in turn help with #26813 and #37233), printing the bottom of deep stacks (#7181), and eliminating the small limit on CPU stacks in profiles (#56029). Fixes #54466. Change-Id: I36e046dc423c9429c4f286d47162af61aff49a0d Reviewed-on: https://go-review.googlesource.com/c/go/+/458218 Reviewed-by: Michael Pratt TryBot-Result: Gopher Robot Run-TryBot: Austin Clements --- diff --git a/src/runtime/crash_unix_test.go b/src/runtime/crash_unix_test.go index 29d9c476f9..a7d6624789 100644 --- a/src/runtime/crash_unix_test.go +++ b/src/runtime/crash_unix_test.go @@ -198,10 +198,12 @@ func TestPanicSystemstack(t *testing.T) { // Traceback should have two testPanicSystemstackInternal's // and two blockOnSystemStackInternal's. - if bytes.Count(tb, []byte("testPanicSystemstackInternal")) != 2 { - t.Fatal("traceback missing user stack:\n", string(tb)) - } else if bytes.Count(tb, []byte("blockOnSystemStackInternal")) != 2 { - t.Fatal("traceback missing system stack:\n", string(tb)) + userFunc := "testPanicSystemstackInternal" + sysFunc := "blockOnSystemStackInternal" + nUser := bytes.Count(tb, []byte(userFunc)) + nSys := bytes.Count(tb, []byte(sysFunc)) + if nUser != 2 || nSys != 2 { + t.Fatalf("want %d user stack frames in %s and %d system stack frames in %s, got %d and %d:\n%s", 2, userFunc, 2, sysFunc, nUser, nSys, string(tb)) } } diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index bb246193cb..93f6ee831e 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -7,6 +7,7 @@ package runtime import ( "internal/goarch" "runtime/internal/atomic" + "runtime/internal/sys" "unsafe" ) @@ -270,6 +271,11 @@ func (gp *guintptr) cas(old, new guintptr) bool { return atomic.Casuintptr((*uintptr)(unsafe.Pointer(gp)), uintptr(old), uintptr(new)) } +//go:nosplit +func (gp *g) guintptr() guintptr { + return guintptr(unsafe.Pointer(gp)) +} + // setGNoWB performs *gp = new without a write barrier. // For times when it's impractical to use a guintptr. // @@ -881,6 +887,8 @@ const ( // Keep in sync with linker (../cmd/link/internal/ld/pcln.go:/pclntab) // and with package debug/gosym and with symtab.go in package runtime. type _func struct { + sys.NotInHeap // Only in static data + entryOff uint32 // start pc, as offset from moduledata.text/pcHeader.textStart nameOff int32 // function name, as index into moduledata.funcnametab. diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index 67648a4ebc..e2533ae250 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -414,6 +414,8 @@ type pcHeader struct { // moduledata is stored in statically allocated non-pointer memory; // none of the pointers here are visible to the garbage collector. type moduledata struct { + sys.NotInHeap // Only in static data + pcHeader *pcHeader funcnametab []byte cutab []uint32 diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 17cd156f1d..02dff5ccdf 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -21,37 +21,118 @@ import ( const usesLR = sys.MinFrameSize > 0 -// Generic traceback. Handles runtime stack prints (pcbuf == nil), -// the runtime.Callers function (pcbuf != nil), as well as the garbage -// collector (callback != nil). A little clunky to merge these, but avoids -// duplicating the code and all its subtlety. +// unwindFlags control the behavior of various unwinders. +type unwindFlags uint8 + +const ( + // unwindPrintErrors indicates that if unwinding encounters an error, it + // should print a message and stop without throwing. This is used for things + // like stack printing, where it's better to get incomplete information than + // to crash. This is also used in situations where everything may not be + // stopped nicely and the stack walk may not be able to complete, such as + // during profiling signals or during a crash. + // + // If neither unwindPrintErrors or unwindSilentErrors are set, unwinding + // performs extra consistency checks and throws on any error. + // + // Note that there are a small number of fatal situations that will throw + // regardless of unwindPrintErrors or unwindSilentErrors. + unwindPrintErrors unwindFlags = 1 << iota + + // unwindSilentErrors silently ignores errors during unwinding. + unwindSilentErrors + + // unwindTrap indicates that the initial PC and SP are from a trap, not a + // return PC from a call. + // + // TODO: Distinguish frame.continpc, which is really the stack map PC, from + // the actual continuation PC, which is computed differently depending on + // this flag and a few other things. + unwindTrap + + // unwindJumpStack indicates that, if the traceback is on a system stack, it + // should resume tracing at the user stack when the system stack is + // exhausted. + unwindJumpStack +) + +// An unwinder iterates the physical stack frames of a Go sack. // -// The skip argument is only valid with pcbuf != nil and counts the number -// of logical frames to skip rather than physical frames (with inlining, a -// PC in pcbuf can represent multiple calls). -func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max int, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer, flags uint) int { - if skip > 0 && callback != nil { - throw("gentraceback callback cannot be used with non-zero skip") - } +// Typical use of an unwinder looks like: +// +// var u unwinder +// for u.init(gp, 0); u.valid(); u.next() { +// // ... use frame info in u ... +// } +// +// Implementation note: This is carefully structured to be pointer-free because +// tracebacks happen in places that disallow write barriers (e.g., signals). +// Even if this is stack-allocated, its pointer-receiver methods don't know that +// their receiver is on the stack, so they still emit write barriers. Here we +// address that by carefully avoiding any pointers in this type. Another +// approach would be to split this into a mutable part that's passed by pointer +// but contains no pointers itself and an immutable part that's passed and +// returned by value and can contain pointers. We could potentially hide that +// we're doing that in trivial methods that are inlined into the caller that has +// the stack allocation, but that's fragile. +type unwinder struct { + // frame is the current physical stack frame, or all 0s if + // there is no frame. + frame stkframe + + // g is the G who's stack is being unwound. If the + // unwindJumpStack flag is set and the unwinder jumps stacks, + // this will be different from the initial G. + g guintptr + + // cgoCtxt is the index into g.cgoCtxt of the next frame on the cgo stack. + // The cgo stack is unwound in tandem with the Go stack as we find marker frames. + cgoCtxt int + + // calleeFuncID is the function ID of the caller of the current + // frame. + calleeFuncID funcID + + // flags are the flags to this unwind. Some of these are updated as we + // unwind (see the flags documentation). + flags unwindFlags + + // cache is used to cache pcvalue lookups. + cache pcvalueCache +} +// init initializes u to start unwinding gp's stack and positions the +// iterator on gp's innermost frame. gp must not be the current G. +// +// A single unwinder can be reused for multiple unwinds. +func (u *unwinder) init(gp *g, flags unwindFlags) { + // Implementation note: This starts the iterator on the first frame and we + // provide a "valid" method. Alternatively, this could start in a "before + // the first frame" state and "next" could return whether it was able to + // move to the next frame, but that's both more awkward to use in a "for" + // loop and is harder to implement because we have to do things differently + // for the first frame. + u.initAt(^uintptr(0), ^uintptr(0), ^uintptr(0), gp, flags) +} + +func (u *unwinder) initAt(pc0, sp0, lr0 uintptr, gp *g, flags unwindFlags) { // Don't call this "g"; it's too easy get "g" and "gp" confused. if ourg := getg(); ourg == gp && ourg == ourg.m.curg { // The starting sp has been passed in as a uintptr, and the caller may // have other uintptr-typed stack references as well. // If during one of the calls that got us here or during one of the // callbacks below the stack must be grown, all these uintptr references - // to the stack will not be updated, and gentraceback will continue + // to the stack will not be updated, and traceback will continue // to inspect the old stack memory, which may no longer be valid. // Even if all the variables were updated correctly, it is not clear that // we want to expose a traceback that begins on one stack and ends // on another stack. That could confuse callers quite a bit. - // Instead, we require that gentraceback and any other function that + // Instead, we require that initAt and any other function that // accepts an sp for the current goroutine (typically obtained by // calling getcallersp) must not run on that goroutine's stack but // instead on the g0 stack. - throw("gentraceback cannot trace user goroutine on its own stack") + throw("cannot trace user goroutine on its own stack") } - level, _, _ := gotraceback() if pc0 == ^uintptr(0) && sp0 == ^uintptr(0) { // Signal to fetch saved values from gp. if gp.syscallsp != 0 { @@ -69,15 +150,12 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in } } - nprint := 0 var frame stkframe frame.pc = pc0 frame.sp = sp0 if usesLR { frame.lr = lr0 } - cgoCtxt := len(gp.cgoCtxt) - 1 // Index into gp.cgoCtxt - printing := pcbuf == nil && callback == nil // If the PC is zero, it's likely a nil function call. // Start in the caller's frame. @@ -107,225 +185,428 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in f := findfunc(frame.pc) if !f.valid() { - if callback != nil || printing { + if flags&unwindSilentErrors == 0 { print("runtime: g ", gp.goid, ": unknown pc ", hex(frame.pc), "\n") tracebackHexdump(gp.stack, &frame, 0) } - if callback != nil { + if flags&(unwindPrintErrors|unwindSilentErrors) == 0 { throw("unknown pc") } - return 0 + *u = unwinder{} + return } frame.fn = f - var cache pcvalueCache + // Populate the unwinder. + *u = unwinder{ + frame: frame, + g: gp.guintptr(), + cgoCtxt: len(gp.cgoCtxt) - 1, + calleeFuncID: funcID_normal, + flags: flags, + } - calleeFuncID := funcID_normal - n := 0 - for n < max { - // Typically: - // pc is the PC of the running function. - // sp is the stack pointer at that program counter. - // fp is the frame pointer (caller's stack pointer) at that program counter, or nil if unknown. - // stk is the stack containing sp. - // The caller's program counter is lr, unless lr is zero, in which case it is *(uintptr*)sp. - f = frame.fn - if f.pcsp == 0 { - // No frame information, must be external function, like race support. - // See golang.org/issue/13568. - break - } + isSyscall := frame.pc == pc0 && frame.sp == sp0 && pc0 == gp.syscallpc && sp0 == gp.syscallsp + u.resolveInternal(true, isSyscall) +} - // Compute function info flags. - flag := f.flag - if f.funcID == funcID_cgocallback { - // cgocallback does write SP to switch from the g0 to the curg stack, - // but it carefully arranges that during the transition BOTH stacks - // have cgocallback frame valid for unwinding through. - // So we don't need to exclude it with the other SP-writing functions. - flag &^= funcFlag_SPWRITE - } - if frame.pc == pc0 && frame.sp == sp0 && pc0 == gp.syscallpc && sp0 == gp.syscallsp { - // Some Syscall functions write to SP, but they do so only after - // saving the entry PC/SP using entersyscall. - // Since we are using the entry PC/SP, the later SP write doesn't matter. - flag &^= funcFlag_SPWRITE - } +func (u *unwinder) valid() bool { + return u.frame.pc != 0 +} - // Found an actual function. - // Derive frame pointer and link register. - if frame.fp == 0 { - // Jump over system stack transitions. If we're on g0 and there's a user - // goroutine, try to jump. Otherwise this is a regular call. - // We also defensively check that this won't switch M's on us, - // which could happen at critical points in the scheduler. - // This ensures gp.m doesn't change from a stack jump. - if flags&_TraceJumpStack != 0 && gp == gp.m.g0 && gp.m.curg != nil && gp.m.curg.m == gp.m { - switch f.funcID { - case funcID_morestack: - // morestack does not return normally -- newstack() - // gogo's to curg.sched. Match that. - // This keeps morestack() from showing up in the backtrace, - // but that makes some sense since it'll never be returned - // to. - gp = gp.m.curg - frame.pc = gp.sched.pc - frame.fn = findfunc(frame.pc) - f = frame.fn - flag = f.flag - frame.lr = gp.sched.lr - frame.sp = gp.sched.sp - cgoCtxt = len(gp.cgoCtxt) - 1 - case funcID_systemstack: - // systemstack returns normally, so just follow the - // stack transition. - if usesLR && funcspdelta(f, frame.pc, &cache) == 0 { - // We're at the function prologue and the stack - // switch hasn't happened, or epilogue where we're - // about to return. Just unwind normally. - // Do this only on LR machines because on x86 - // systemstack doesn't have an SP delta (the CALL - // instruction opens the frame), therefore no way - // to check. - flag &^= funcFlag_SPWRITE - break - } - gp = gp.m.curg - frame.sp = gp.sched.sp - cgoCtxt = len(gp.cgoCtxt) - 1 +// resolveInternal fills in u.frame based on u.frame.fn, pc, and sp. +// +// innermost indicates that this is the first resolve on this stack. If +// innermost is set, isSyscall indicates that the PC/SP was retrieved from +// gp.syscall*; this is otherwise ignored. +// +// On entry, u.frame contains: +// - fn is the running function. +// - pc is the PC in the running function. +// - sp is the stack pointer at that program counter. +// - For the innermost frame on LR machines, lr is the program counter that called fn. +// +// On return, u.frame contains: +// - fp is the stack pointer of the caller. +// - lr is the program counter that called fn. +// - varp, argp, and continpc are populated for the current frame. +// +// If fn is a stack-jumping function, resolveInternal can change the entire +// frame state to follow that stack jump. +// +// This is internal to unwinder. +func (u *unwinder) resolveInternal(innermost, isSyscall bool) { + frame := &u.frame + gp := u.g.ptr() + + f := frame.fn + if f.pcsp == 0 { + // No frame information, must be external function, like race support. + // See golang.org/issue/13568. + u.finishInternal() + return + } + + // Compute function info flags. + flag := f.flag + if f.funcID == funcID_cgocallback { + // cgocallback does write SP to switch from the g0 to the curg stack, + // but it carefully arranges that during the transition BOTH stacks + // have cgocallback frame valid for unwinding through. + // So we don't need to exclude it with the other SP-writing functions. + flag &^= funcFlag_SPWRITE + } + if isSyscall { + // Some Syscall functions write to SP, but they do so only after + // saving the entry PC/SP using entersyscall. + // Since we are using the entry PC/SP, the later SP write doesn't matter. + flag &^= funcFlag_SPWRITE + } + + // Found an actual function. + // Derive frame pointer. + if frame.fp == 0 { + // Jump over system stack transitions. If we're on g0 and there's a user + // goroutine, try to jump. Otherwise this is a regular call. + // We also defensively check that this won't switch M's on us, + // which could happen at critical points in the scheduler. + // This ensures gp.m doesn't change from a stack jump. + if u.flags&unwindJumpStack != 0 && gp == gp.m.g0 && gp.m.curg != nil && gp.m.curg.m == gp.m { + switch f.funcID { + case funcID_morestack: + // morestack does not return normally -- newstack() + // gogo's to curg.sched. Match that. + // This keeps morestack() from showing up in the backtrace, + // but that makes some sense since it'll never be returned + // to. + gp = gp.m.curg + u.g.set(gp) + frame.pc = gp.sched.pc + frame.fn = findfunc(frame.pc) + f = frame.fn + flag = f.flag + frame.lr = gp.sched.lr + frame.sp = gp.sched.sp + u.cgoCtxt = len(gp.cgoCtxt) - 1 + case funcID_systemstack: + // systemstack returns normally, so just follow the + // stack transition. + if usesLR && funcspdelta(f, frame.pc, &u.cache) == 0 { + // We're at the function prologue and the stack + // switch hasn't happened, or epilogue where we're + // about to return. Just unwind normally. + // Do this only on LR machines because on x86 + // systemstack doesn't have an SP delta (the CALL + // instruction opens the frame), therefore no way + // to check. flag &^= funcFlag_SPWRITE + break } - } - frame.fp = frame.sp + uintptr(funcspdelta(f, frame.pc, &cache)) - if !usesLR { - // On x86, call instruction pushes return PC before entering new function. - frame.fp += goarch.PtrSize + gp = gp.m.curg + u.g.set(gp) + frame.sp = gp.sched.sp + u.cgoCtxt = len(gp.cgoCtxt) - 1 + flag &^= funcFlag_SPWRITE } } - var lrPtr uintptr - if flag&funcFlag_TOPFRAME != 0 { - // This function marks the top of the stack. Stop the traceback. + frame.fp = frame.sp + uintptr(funcspdelta(f, frame.pc, &u.cache)) + if !usesLR { + // On x86, call instruction pushes return PC before entering new function. + frame.fp += goarch.PtrSize + } + } + + // Derive link register. + if flag&funcFlag_TOPFRAME != 0 { + // This function marks the top of the stack. Stop the traceback. + frame.lr = 0 + } else if flag&funcFlag_SPWRITE != 0 { + // The function we are in does a write to SP that we don't know + // how to encode in the spdelta table. Examples include context + // switch routines like runtime.gogo but also any code that switches + // to the g0 stack to run host C code. + if u.flags&(unwindPrintErrors|unwindSilentErrors) != 0 { + // We can't reliably unwind the SP (we might + // not even be on the stack we think we are), + // so stop the traceback here. frame.lr = 0 - } else if flag&funcFlag_SPWRITE != 0 && (callback == nil || n > 0) { - // The function we are in does a write to SP that we don't know - // how to encode in the spdelta table. Examples include context - // switch routines like runtime.gogo but also any code that switches - // to the g0 stack to run host C code. Since we can't reliably unwind - // the SP (we might not even be on the stack we think we are), - // we stop the traceback here. - // This only applies for profiling signals (callback == nil). - // - // For a GC stack traversal (callback != nil), we should only see - // a function when it has voluntarily preempted itself on entry + } else { + // For a GC stack traversal, we should only see + // an SPWRITE function when it has voluntarily preempted itself on entry // during the stack growth check. In that case, the function has // not yet had a chance to do any writes to SP and is safe to unwind. // isAsyncSafePoint does not allow assembly functions to be async preempted, // and preemptPark double-checks that SPWRITE functions are not async preempted. - // So for GC stack traversal we leave things alone (this if body does not execute for n == 0) - // at the bottom frame of the stack. But farther up the stack we'd better not - // find any. - if callback != nil { + // So for GC stack traversal, we can safely ignore SPWRITE for the innermost frame, + // but farther up the stack we'd better not find any. + if !innermost { println("traceback: unexpected SPWRITE function", funcname(f)) throw("traceback") } - frame.lr = 0 + } + } else { + var lrPtr uintptr + if usesLR { + if innermost && frame.sp < frame.fp || frame.lr == 0 { + lrPtr = frame.sp + frame.lr = *(*uintptr)(unsafe.Pointer(lrPtr)) + } } else { - if usesLR { - if n == 0 && frame.sp < frame.fp || frame.lr == 0 { - lrPtr = frame.sp - frame.lr = *(*uintptr)(unsafe.Pointer(lrPtr)) - } - } else { - if frame.lr == 0 { - lrPtr = frame.fp - goarch.PtrSize - frame.lr = uintptr(*(*uintptr)(unsafe.Pointer(lrPtr))) - } + if frame.lr == 0 { + lrPtr = frame.fp - goarch.PtrSize + frame.lr = *(*uintptr)(unsafe.Pointer(lrPtr)) } } + } - frame.varp = frame.fp - if !usesLR { - // On x86, call instruction pushes return PC before entering new function. - frame.varp -= goarch.PtrSize + frame.varp = frame.fp + if !usesLR { + // On x86, call instruction pushes return PC before entering new function. + frame.varp -= goarch.PtrSize + } + + // For architectures with frame pointers, if there's + // a frame, then there's a saved frame pointer here. + // + // NOTE: This code is not as general as it looks. + // On x86, the ABI is to save the frame pointer word at the + // top of the stack frame, so we have to back down over it. + // On arm64, the frame pointer should be at the bottom of + // the stack (with R29 (aka FP) = RSP), in which case we would + // not want to do the subtraction here. But we started out without + // any frame pointer, and when we wanted to add it, we didn't + // want to break all the assembly doing direct writes to 8(RSP) + // to set the first parameter to a called function. + // So we decided to write the FP link *below* the stack pointer + // (with R29 = RSP - 8 in Go functions). + // This is technically ABI-compatible but not standard. + // And it happens to end up mimicking the x86 layout. + // Other architectures may make different decisions. + if frame.varp > frame.sp && framepointer_enabled { + frame.varp -= goarch.PtrSize + } + + frame.argp = frame.fp + sys.MinFrameSize + + // Determine frame's 'continuation PC', where it can continue. + // Normally this is the return address on the stack, but if sigpanic + // is immediately below this function on the stack, then the frame + // stopped executing due to a trap, and frame.pc is probably not + // a safe point for looking up liveness information. In this panicking case, + // the function either doesn't return at all (if it has no defers or if the + // defers do not recover) or it returns from one of the calls to + // deferproc a second time (if the corresponding deferred func recovers). + // In the latter case, use a deferreturn call site as the continuation pc. + frame.continpc = frame.pc + if u.calleeFuncID == funcID_sigpanic { + if frame.fn.deferreturn != 0 { + frame.continpc = frame.fn.entry() + uintptr(frame.fn.deferreturn) + 1 + // Note: this may perhaps keep return variables alive longer than + // strictly necessary, as we are using "function has a defer statement" + // as a proxy for "function actually deferred something". It seems + // to be a minor drawback. (We used to actually look through the + // gp._defer for a defer corresponding to this function, but that + // is hard to do with defer records on the stack during a stack copy.) + // Note: the +1 is to offset the -1 that + // stack.go:getStackMap does to back up a return + // address make sure the pc is in the CALL instruction. + } else { + frame.continpc = 0 } + } +} - // For architectures with frame pointers, if there's - // a frame, then there's a saved frame pointer here. - // - // NOTE: This code is not as general as it looks. - // On x86, the ABI is to save the frame pointer word at the - // top of the stack frame, so we have to back down over it. - // On arm64, the frame pointer should be at the bottom of - // the stack (with R29 (aka FP) = RSP), in which case we would - // not want to do the subtraction here. But we started out without - // any frame pointer, and when we wanted to add it, we didn't - // want to break all the assembly doing direct writes to 8(RSP) - // to set the first parameter to a called function. - // So we decided to write the FP link *below* the stack pointer - // (with R29 = RSP - 8 in Go functions). - // This is technically ABI-compatible but not standard. - // And it happens to end up mimicking the x86 layout. - // Other architectures may make different decisions. - if frame.varp > frame.sp && framepointer_enabled { - frame.varp -= goarch.PtrSize +func (u *unwinder) next() { + frame := &u.frame + f := frame.fn + gp := u.g.ptr() + + // Do not unwind past the bottom of the stack. + if frame.lr == 0 { + u.finishInternal() + return + } + flr := findfunc(frame.lr) + if !flr.valid() { + // This happens if you get a profiling interrupt at just the wrong time. + // In that context it is okay to stop early. + // But if no error flags are set, we're doing a garbage collection and must + // get everything, so crash loudly. + fail := u.flags&(unwindPrintErrors|unwindSilentErrors) == 0 + doPrint := u.flags&unwindSilentErrors == 0 + if doPrint && gp.m.incgo && f.funcID == funcID_sigpanic { + // We can inject sigpanic + // calls directly into C code, + // in which case we'll see a C + // return PC. Don't complain. + doPrint = false + } + if fail || doPrint { + print("runtime: g ", gp.goid, ": unexpected return pc for ", funcname(f), " called from ", hex(frame.lr), "\n") + tracebackHexdump(gp.stack, frame, 0) } + if fail { + throw("unknown caller pc") + } + frame.lr = 0 + u.finishInternal() + return + } - frame.argp = frame.fp + sys.MinFrameSize - - // Determine frame's 'continuation PC', where it can continue. - // Normally this is the return address on the stack, but if sigpanic - // is immediately below this function on the stack, then the frame - // stopped executing due to a trap, and frame.pc is probably not - // a safe point for looking up liveness information. In this panicking case, - // the function either doesn't return at all (if it has no defers or if the - // defers do not recover) or it returns from one of the calls to - // deferproc a second time (if the corresponding deferred func recovers). - // In the latter case, use a deferreturn call site as the continuation pc. - frame.continpc = frame.pc - if calleeFuncID == funcID_sigpanic { - if frame.fn.deferreturn != 0 { - frame.continpc = frame.fn.entry() + uintptr(frame.fn.deferreturn) + 1 - // Note: this may perhaps keep return variables alive longer than - // strictly necessary, as we are using "function has a defer statement" - // as a proxy for "function actually deferred something". It seems - // to be a minor drawback. (We used to actually look through the - // gp._defer for a defer corresponding to this function, but that - // is hard to do with defer records on the stack during a stack copy.) - // Note: the +1 is to offset the -1 that - // stack.go:getStackMap does to back up a return - // address make sure the pc is in the CALL instruction. - } else { - frame.continpc = 0 - } + if frame.pc == frame.lr && frame.sp == frame.fp { + // If the next frame is identical to the current frame, we cannot make progress. + print("runtime: traceback stuck. pc=", hex(frame.pc), " sp=", hex(frame.sp), "\n") + tracebackHexdump(gp.stack, frame, frame.sp) + throw("traceback stuck") + } + + injectedCall := f.funcID == funcID_sigpanic || f.funcID == funcID_asyncPreempt || f.funcID == funcID_debugCallV2 + + // Unwind to next frame. + u.calleeFuncID = f.funcID + frame.fn = flr + frame.pc = frame.lr + frame.lr = 0 + frame.sp = frame.fp + frame.fp = 0 + + // On link register architectures, sighandler saves the LR on stack + // before faking a call. + if usesLR && injectedCall { + x := *(*uintptr)(unsafe.Pointer(frame.sp)) + frame.sp += alignUp(sys.MinFrameSize, sys.StackAlign) + f = findfunc(frame.pc) + frame.fn = f + if !f.valid() { + frame.pc = x + } else if funcspdelta(f, frame.pc, &u.cache) == 0 { + frame.lr = x } + } + + u.resolveInternal(false, false) +} + +// finishInternal is an unwinder-internal helper called after the stack has been +// exhausted. It sets the unwinder to an invalid state and checks that it +// successfully unwound the entire stack. +func (u *unwinder) finishInternal() { + u.frame.pc = 0 + + // Note that panic != nil is okay here: there can be leftover panics, + // because the defers on the panic stack do not nest in frame order as + // they do on the defer stack. If you have: + // + // frame 1 defers d1 + // frame 2 defers d2 + // frame 3 defers d3 + // frame 4 panics + // frame 4's panic starts running defers + // frame 5, running d3, defers d4 + // frame 5 panics + // frame 5's panic starts running defers + // frame 6, running d4, garbage collects + // frame 6, running d2, garbage collects + // + // During the execution of d4, the panic stack is d4 -> d3, which + // is nested properly, and we'll treat frame 3 as resumable, because we + // can find d3. (And in fact frame 3 is resumable. If d4 recovers + // and frame 5 continues running, d3, d3 can recover and we'll + // resume execution in (returning from) frame 3.) + // + // During the execution of d2, however, the panic stack is d2 -> d3, + // which is inverted. The scan will match d2 to frame 2 but having + // d2 on the stack until then means it will not match d3 to frame 3. + // This is okay: if we're running d2, then all the defers after d2 have + // completed and their corresponding frames are dead. Not finding d3 + // for frame 3 means we'll set frame 3's continpc == 0, which is correct + // (frame 3 is dead). At the end of the walk the panic stack can thus + // contain defers (d3 in this case) for dead frames. The inversion here + // always indicates a dead frame, and the effect of the inversion on the + // scan is to hide those dead frames, so the scan is still okay: + // what's left on the panic stack are exactly (and only) the dead frames. + // + // We require callback != nil here because only when callback != nil + // do we know that gentraceback is being called in a "must be correct" + // context as opposed to a "best effort" context. The tracebacks with + // callbacks only happen when everything is stopped nicely. + // At other times, such as when gathering a stack for a profiling signal + // or when printing a traceback during a crash, everything may not be + // stopped nicely, and the stack walk may not be able to complete. + gp := u.g.ptr() + if u.flags&(unwindPrintErrors|unwindSilentErrors) == 0 && u.frame.sp != gp.stktopsp { + print("runtime: g", gp.goid, ": frame.sp=", hex(u.frame.sp), " top=", hex(gp.stktopsp), "\n") + print("\tstack=[", hex(gp.stack.lo), "-", hex(gp.stack.hi), "\n") + throw("traceback did not unwind completely") + } +} + +// Generic traceback. Handles runtime stack prints (pcbuf == nil), +// the runtime.Callers function (pcbuf != nil), as well as the garbage +// collector (callback != nil). A little clunky to merge these, but avoids +// duplicating the code and all its subtlety. +// +// The skip argument is only valid with pcbuf != nil and counts the number +// of logical frames to skip rather than physical frames (with inlining, a +// PC in pcbuf can represent multiple calls). +func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max int, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer, flags uint) int { + if skip > 0 && callback != nil { + throw("gentraceback callback cannot be used with non-zero skip") + } + + // Translate flags + var uflags unwindFlags + printing := pcbuf == nil && callback == nil + if printing { + uflags |= unwindPrintErrors + } else if callback == nil { + uflags |= unwindSilentErrors + } + if flags&_TraceTrap != 0 { + uflags |= unwindTrap + } + if flags&_TraceJumpStack != 0 { + uflags |= unwindJumpStack + } + + // Initialize stack unwinder + var u unwinder + u.initAt(pc0, sp0, lr0, gp, uflags) + + level, _, _ := gotraceback() + + nprint := 0 + n := 0 + for ; n < max && u.valid(); u.next() { + frame := &u.frame + f := frame.fn if callback != nil { - if !callback((*stkframe)(noescape(unsafe.Pointer(&frame))), v) { + if !callback((*stkframe)(noescape(unsafe.Pointer(frame))), v) { return n } } + // Backup to the CALL instruction to read inlining info + // + // Normally, pc is a return address. In that case, we want to look up + // file/line information using pc-1, because that is the pc of the + // call instruction (more precisely, the last byte of the call instruction). + // When the pc is from a signal (e.g. profiler or segv) then pc is for + // the trapped instruction, not a return address, so we use pc unchanged. + // See issue 34123. + // The pc can be at function entry when the frame is initialized without + // actually running code, like runtime.mstart. + callPC := frame.pc + if (n > 0 || flags&_TraceTrap == 0) && frame.pc > f.entry() && u.calleeFuncID != funcID_sigpanic { + callPC-- + } + if pcbuf != nil { - // backup to CALL instruction to read inlining info (same logic as below) - tracepc := frame.pc - // Normally, pc is a return address. In that case, we want to look up - // file/line information using pc-1, because that is the pc of the - // call instruction (more precisely, the last byte of the call instruction). - // Callers expect the pc buffer to contain return addresses and do the - // same -1 themselves, so we keep pc unchanged. - // When the pc is from a signal (e.g. profiler or segv) then we want - // to look up file/line information using pc, and we store pc+1 in the - // pc buffer so callers can unconditionally subtract 1 before looking up. - // See issue 34123. - // The pc can be at function entry when the frame is initialized without - // actually running code, like runtime.mstart. - if !((n == 0 && flags&_TraceTrap != 0) || calleeFuncID == funcID_sigpanic || tracepc == f.entry()) { - tracepc-- - } // TODO: Why does cache escape? (Same below) - for iu, uf := newInlineUnwinder(f, tracepc, noEscapePtr(&cache)); uf.valid(); uf = iu.next(uf) { + for iu, uf := newInlineUnwinder(f, callPC, noEscapePtr(&u.cache)); uf.valid(); uf = iu.next(uf) { sf := iu.srcFunc(uf) - if sf.funcID == funcID_wrapper && elideWrapperCalling(calleeFuncID) { + if sf.funcID == funcID_wrapper && elideWrapperCalling(u.calleeFuncID) { // ignore wrappers } else if skip > 0 { skip-- @@ -336,7 +617,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in (*[1 << 20]uintptr)(unsafe.Pointer(pcbuf))[n] = uf.pc + 1 n++ } - calleeFuncID = sf.funcID + u.calleeFuncID = sf.funcID } n-- // offset n++ below } @@ -348,15 +629,9 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in // any frames. And don't elide wrappers that // called panic rather than the wrapped // function. Otherwise, leave them out. - - // backup to CALL instruction to read inlining info (same logic as below) - tracepc := frame.pc - if (n > 0 || flags&_TraceTrap == 0) && frame.pc > f.entry() && calleeFuncID != funcID_sigpanic { - tracepc-- - } - for iu, uf := newInlineUnwinder(f, tracepc, noEscapePtr(&cache)); uf.valid(); uf = iu.next(uf) { + for iu, uf := newInlineUnwinder(f, callPC, noEscapePtr(&u.cache)); uf.valid(); uf = iu.next(uf) { sf := iu.srcFunc(uf) - if (flags&_TraceRuntimeFrames) != 0 || showframe(sf, gp, nprint == 0, calleeFuncID) { + if (flags&_TraceRuntimeFrames) != 0 || showframe(sf, gp, nprint == 0, u.calleeFuncID) { name := sf.name() file, line := iu.fileLine(uf) if name == "runtime.gopanic" { @@ -371,7 +646,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in print("...") } else { argp := unsafe.Pointer(frame.argp) - printArgs(f, argp, tracepc) + printArgs(f, argp, callPC) } print(")\n") print("\t", file, ":", line) @@ -390,9 +665,9 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in } n++ - if f.funcID == funcID_cgocallback && cgoCtxt >= 0 { - ctxt := gp.cgoCtxt[cgoCtxt] - cgoCtxt-- + if f.funcID == funcID_cgocallback && u.cgoCtxt >= 0 { + ctxt := gp.cgoCtxt[u.cgoCtxt] + u.cgoCtxt-- // skip only applies to Go frames. // callback != nil only used when we only care @@ -401,117 +676,12 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in n = tracebackCgoContext(pcbuf, printing, ctxt, n, max) } } - - injectedCall := f.funcID == funcID_sigpanic || f.funcID == funcID_asyncPreempt || f.funcID == funcID_debugCallV2 - - // Do not unwind past the bottom of the stack. - if frame.lr == 0 { - break - } - flr := findfunc(frame.lr) - if !flr.valid() { - // This happens if you get a profiling interrupt at just the wrong time. - // In that context it is okay to stop early. - // But if callback is set, we're doing a garbage collection and must - // get everything, so crash loudly. - doPrint := printing - if doPrint && gp.m.incgo && f.funcID == funcID_sigpanic { - // We can inject sigpanic - // calls directly into C code, - // in which case we'll see a C - // return PC. Don't complain. - doPrint = false - } - if callback != nil || doPrint { - print("runtime: g ", gp.goid, ": unexpected return pc for ", funcname(f), " called from ", hex(frame.lr), "\n") - tracebackHexdump(gp.stack, &frame, lrPtr) - } - if callback != nil { - throw("unknown caller pc") - } - break - } - - if frame.pc == frame.lr && frame.sp == frame.fp { - // If the next frame is identical to the current frame, we cannot make progress. - print("runtime: traceback stuck. pc=", hex(frame.pc), " sp=", hex(frame.sp), "\n") - tracebackHexdump(gp.stack, &frame, frame.sp) - throw("traceback stuck") - } - - // Unwind to next frame. - calleeFuncID = f.funcID - frame.fn = flr - frame.pc = frame.lr - frame.lr = 0 - frame.sp = frame.fp - frame.fp = 0 - - // On link register architectures, sighandler saves the LR on stack - // before faking a call. - if usesLR && injectedCall { - x := *(*uintptr)(unsafe.Pointer(frame.sp)) - frame.sp += alignUp(sys.MinFrameSize, sys.StackAlign) - f = findfunc(frame.pc) - frame.fn = f - if !f.valid() { - frame.pc = x - } else if funcspdelta(f, frame.pc, &cache) == 0 { - frame.lr = x - } - } } if printing { n = nprint } - // Note that panic != nil is okay here: there can be leftover panics, - // because the defers on the panic stack do not nest in frame order as - // they do on the defer stack. If you have: - // - // frame 1 defers d1 - // frame 2 defers d2 - // frame 3 defers d3 - // frame 4 panics - // frame 4's panic starts running defers - // frame 5, running d3, defers d4 - // frame 5 panics - // frame 5's panic starts running defers - // frame 6, running d4, garbage collects - // frame 6, running d2, garbage collects - // - // During the execution of d4, the panic stack is d4 -> d3, which - // is nested properly, and we'll treat frame 3 as resumable, because we - // can find d3. (And in fact frame 3 is resumable. If d4 recovers - // and frame 5 continues running, d3, d3 can recover and we'll - // resume execution in (returning from) frame 3.) - // - // During the execution of d2, however, the panic stack is d2 -> d3, - // which is inverted. The scan will match d2 to frame 2 but having - // d2 on the stack until then means it will not match d3 to frame 3. - // This is okay: if we're running d2, then all the defers after d2 have - // completed and their corresponding frames are dead. Not finding d3 - // for frame 3 means we'll set frame 3's continpc == 0, which is correct - // (frame 3 is dead). At the end of the walk the panic stack can thus - // contain defers (d3 in this case) for dead frames. The inversion here - // always indicates a dead frame, and the effect of the inversion on the - // scan is to hide those dead frames, so the scan is still okay: - // what's left on the panic stack are exactly (and only) the dead frames. - // - // We require callback != nil here because only when callback != nil - // do we know that gentraceback is being called in a "must be correct" - // context as opposed to a "best effort" context. The tracebacks with - // callbacks only happen when everything is stopped nicely. - // At other times, such as when gathering a stack for a profiling signal - // or when printing a traceback during a crash, everything may not be - // stopped nicely, and the stack walk may not be able to complete. - if callback != nil && n < max && frame.sp != gp.stktopsp { - print("runtime: g", gp.goid, ": frame.sp=", hex(frame.sp), " top=", hex(gp.stktopsp), "\n") - print("\tstack=[", hex(gp.stack.lo), "-", hex(gp.stack.hi), "] n=", n, " max=", max, "\n") - throw("traceback did not unwind completely") - } - return n }