diff --git a/cl/compile.go b/cl/compile.go index 8f99b9a0f5..92c0705125 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -1699,6 +1699,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { runDefers := p.returnNeedsImplicitRunDefers(v) if runDefers { p.recordPanicLocation(b, v.Pos()) + p.emitPCLineLabel(b, p.deferRunPos(v.Pos())) b.RunDefers() } var results []llssa.Expr @@ -1746,10 +1747,16 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { p.call(b, llssa.Go, &v.Call) case *ssa.RunDefers: p.recordPanicLocation(b, v.Pos()) + p.emitPCLineLabel(b, p.deferRunPos(v.Pos())) b.RunDefers() case *ssa.Panic: arg := p.compileValue(b, v.X) p.recordPanicLocation(b, v.Pos()) + // panic is not a Call instruction, so callEx's statement anchor + // does not cover it; the panic snapshot attributes the panicking + // frame to this pc (issue5856 wants the panic line, not the + // nearest call's). + p.emitPCLineLabel(b, v.Pos()) b.Panic(arg) case *ssa.Send: ch := p.compileValue(b, v.Chan) @@ -1909,6 +1916,25 @@ func (p *context) functionHasExplicitStackDeferSeen(fn *ssa.Function, seen map[* return false } +// deferRunPos is where gc attributes a deferred function's caller frame: +// the function's closing brace — defers run at function exit, not at the +// defer statement (goroot issue14646, issue5856). +func (p *context) deferRunPos(fallback token.Pos) token.Pos { + if p.goFn != nil { + switch syntax := p.goFn.Syntax().(type) { + case *ast.FuncDecl: + if syntax.Body != nil && syntax.Body.Rbrace.IsValid() { + return syntax.Body.Rbrace + } + case *ast.FuncLit: + if syntax.Body != nil && syntax.Body.Rbrace.IsValid() { + return syntax.Body.Rbrace + } + } + } + return fallback +} + func (p *context) returnNeedsImplicitRunDefers(ret *ssa.Return) bool { fn := ret.Parent() if fn == nil || fn.Synthetic != "" || ret.Block() == fn.Recover { diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index e41c3e811f..c017c7e5f4 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -277,6 +277,11 @@ func use(goos, goarch string, wasiThreads, forceEspClang bool, level optlevel.Le "-target", targetTriple, "-Qunused-arguments", "-Wno-unused-command-line-argument", + // Keep frame pointers in C code too: the runtime's physical + // unwinder walks fault-site chains through C frames (Go keeps + // them via the "frame-pointer"="non-leaf" attribute; x86-64 C + // would omit them at -O by default). + "-fno-omit-frame-pointer", } if ltoMode.Enabled() { export.CCFLAGS = append(export.CCFLAGS, ltoMode.ClangFlag()) diff --git a/runtime/internal/lib/runtime/_wrap/runtime.c b/runtime/internal/lib/runtime/_wrap/runtime.c index a0b207f8dd..a0e504f350 100644 --- a/runtime/internal/lib/runtime/_wrap/runtime.c +++ b/runtime/internal/lib/runtime/_wrap/runtime.c @@ -23,14 +23,3 @@ int llgo_maxprocs() return 1; #endif } - -__attribute__((noinline)) void *llgo_framepointer(void) -{ -#if defined(__GNUC__) || defined(__clang__) - /* Read the saved caller FP before this helper's frame becomes invalid. */ - void **frame = (void **)__builtin_frame_address(0); - return frame ? *frame : 0; -#else - return 0; -#endif -} diff --git a/runtime/internal/lib/runtime/extern.go b/runtime/internal/lib/runtime/extern.go index aa3d7dbc55..ca61558d24 100644 --- a/runtime/internal/lib/runtime/extern.go +++ b/runtime/internal/lib/runtime/extern.go @@ -26,7 +26,7 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) { ensureRuntimePCLN() if fpUnwindAvailable() { var pcs [1]uintptr - if fpCallers(skip+1, pcs[:]) >= 1 { + if callersWithPanicSplice(skip+1, pcs[:]) >= 1 { // Caller returns the call-instruction PC (matching Go), whereas // Callers returns return PCs. Keeping the adjusted value matters // when the return address equals the next function's entry. @@ -53,7 +53,7 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) { func Callers(skip int, pc []uintptr) int { ensureRuntimePCLN() if fpUnwindAvailable() { - if n := fpCallers(skip, pc); n > 0 { + if n := callersWithPanicSplice(skip, pc); n > 0 { return n } } diff --git a/runtime/internal/lib/runtime/fault_unwind_llgo.go b/runtime/internal/lib/runtime/fault_unwind_llgo.go index a975eba713..4e3622da32 100644 --- a/runtime/internal/lib/runtime/fault_unwind_llgo.go +++ b/runtime/internal/lib/runtime/fault_unwind_llgo.go @@ -107,6 +107,7 @@ func onFault(pc, fp uintptr, sig int32) { n += m } faultN = int32(n) + rtdebug.StoreFaultPCs(faultPCs[:n]) } // Capture done: re-arm the recursion guard before this fault turns // into an ordinary (recoverable) panic. diff --git a/runtime/internal/lib/runtime/symtab.go b/runtime/internal/lib/runtime/symtab.go index f9c834c4ad..75854a9434 100644 --- a/runtime/internal/lib/runtime/symtab.go +++ b/runtime/internal/lib/runtime/symtab.go @@ -1695,6 +1695,17 @@ func refinePCSymbolLine(sym pcSymbol, pc uintptr) pcSymbol { lineSym.pc = pc return mergePCLineSymbol(sym, lineSym) } + // No same-function statement anchor covers pc. Mid-function pcs of a + // record with no evidence can actually belong to a foreign (C) + // function linked between Go functions — nearest-below cannot see the + // hole and would misattribute the frame to the preceding Go function. + // One dladdr cross-check on this cold, cache-backed path: a closer + // symbol wins. Exact entries and anchored functions never get here. + if pc != sym.entry { + if ai := addrInfoSymbol(pc); ai.ok && ai.entry > sym.entry { + return ai + } + } return sym } diff --git a/runtime/internal/lib/runtime/unwind_baremetal_llgo.go b/runtime/internal/lib/runtime/unwind_baremetal_llgo.go new file mode 100644 index 0000000000..04d2617178 --- /dev/null +++ b/runtime/internal/lib/runtime/unwind_baremetal_llgo.go @@ -0,0 +1,15 @@ +//go:build baremetal && !wasm + +package runtime + +func fpCallers(_ int, _ []uintptr) int { + return 0 +} + +func fpUnwindAvailable() bool { + return false +} + +func callersWithPanicSplice(_ int, _ []uintptr) int { + return 0 +} diff --git a/runtime/internal/lib/runtime/unwind_llgo.go b/runtime/internal/lib/runtime/unwind_llgo.go index 7c41339ce8..94b477a2be 100644 --- a/runtime/internal/lib/runtime/unwind_llgo.go +++ b/runtime/internal/lib/runtime/unwind_llgo.go @@ -17,6 +17,209 @@ func c_framepointer() unsafe.Pointer func init() { rtdebug.PanicTraceback = panicTraceback rtdebug.PanicRecovered = clearFaultTraceback + rtdebug.PanicPCSnapshot = capturePanicPCs + rtdebug.RecoverMark = recoverMark +} + +// recoverMark records the recovering deferred frame (and one above, for +// wrapper-reached recover) so the panic snapshot stays spliceable while +// that frame is live. After siglongjmp the frame-pointer chain two levels +// up can point into a stale/reused stack region that is sometimes +// unmapped; probe each slot before dereferencing — an unguarded read here +// self-faults ~7% of the time, converting to a nil-deref panic that +// corrupts the value the recover was extracting (goroot reflectmake flake). +func recoverMark() { + // Record this function's frame address: it sits below the recovering + // deferred frame, and the liveness gate tests interval containment, so + // the exact level does not matter. + fp := callerFramePointer() + if fp == 0 { + return + } + rtdebug.MarkPanicRecoverFPs(fp, 0) +} + +// capturePanicPCs runs at panic time, before any longjmp unwinding, and +// stores the physical pc chain for later splicing (see spliceCallers). +func capturePanicPCs() { + if !fpUnwindAvailable() { + return + } + var pcs [64]uintptr + n := fpCallers(0, pcs[:]) + rtdebug.StorePanicPCs(pcs[:n]) +} + +// panicSplicePCs returns the snapshot when it is observable: either the +// panic is still in flight, or the deferred frame that recovered it is +// still live on the physical chain (gc keeps panic frames on the stack +// exactly that long). +func panicSplicePCs() []uintptr { + pcs := rtdebug.PanicPCs() + if len(pcs) == 0 { + return nil + } + if rtdebug.PanicActive() { + return pcs + } + mark, _ := rtdebug.PanicRecoverFPs() + if mark == 0 { + return nil + } + // The mark is a frame address recorded inside Recover's call chain; + // the recovering deferred frame is live iff the mark still lies + // within the current chain's span. Interval containment instead of + // exact equality: the hook's own frame depth differs across + // platforms (stub/wrapper layers), but any frame's [fp, parent fp) + // range straddling the mark proves the region is still stack, not + // reused heap or a dead extent. + fp := callerFramePointer() + for i := 0; fp != 0 && i < maxPanicSpliceFrames; i++ { + if !memReadable(fp) { + break + } + prev := *(*uintptr)(unsafe.Pointer(fp)) + if fp <= mark && (prev > mark || prev == 0) { + return pcs + } + if prev <= fp || prev-fp > maxFPStride || prev&(unsafe.Sizeof(uintptr(0))-1) != 0 { + break + } + fp = prev + } + return nil +} + +const maxPanicSpliceFrames = 4096 + +// callerFramePointer returns the frame of its Go caller. llgo_framepointer +// returns this helper's frame; consume its saved link immediately, before +// another call can reuse the slot. +// +//go:noinline +func callerFramePointer() uintptr { + fp := uintptr(c_framepointer()) + if fp == 0 { + return 0 + } + return *(*uintptr)(unsafe.Pointer(fp)) +} + +// trimPlumbingPCs drops leading pcs attributed to the LLGo runtime core +// (panic machinery, the capture path) and cuts the tail at the first pc +// outside the program text — fault snapshots are captured without the +// text bound (see fpWalkFrom). +func trimPlumbingPCs(pcs []uintptr) []uintptr { + initRuntimeFuncPCFrames() + head := 0 + for head < len(pcs) { + sym := frameSymbol(pcs[head] - 1) + if sym.function != "" && (hasPrefix(sym.function, "github.com/goplus/llgo/runtime/internal/") || + sym.function == "runtime.capturePanicPCs" || sym.function == "runtime.onFault" || + sym.function == "runtime.fpWalkFrom") { + head++ + continue + } + break + } + // Keep the innermost panic-machinery frame: gc's logical stack has + // runtime.gopanic between the deferred function and the panic site, + // and fixed Caller depths (issue5856's Caller(2)) count it. Fault + // snapshots start at the fault pc and trim nothing — gc's walkers + // skip runtime frames by name there, not by depth. + if head > 0 { + head-- + } + pcs = pcs[head:] + if rtdebug.PanicPCsAreFault() { + // Fault snapshots come from a genuine interrupted context and the + // chain-discipline guards already bounded the walk; keep unnamed + // frames (linux dladdr cannot name non-dynamic C symbols — they + // display as raw pcs, like gc does for unknown frames). + return pcs + } + for i := 0; i < len(pcs); i++ { + if prebuiltTextContains(pcs[i]) { + continue + } + // Outside the Go text range: C frames in this binary (and its + // libraries) still resolve to a symbol via dladdr — keep those; + // cut at the first pc nothing can name (wild slots past the last + // FP-disciplined frame). + if frameSymbol(pcs[i]-1).function == "" { + return pcs[:i] + } + } + return pcs +} + +// spliceCallers rebuilds the caller view a deferred function should see +// during (or right after recovering) a panic: its own live frames, then the +// panic-site chain from the snapshot. The junction is the first live frame +// whose function also appears in the snapshot — the defer owner; the +// snapshot side wins there because the live copy\'s pc points at the +// longjmp resume site, not at the call that panicked. +func spliceCallers(cur []uintptr) []uintptr { + snap := panicSplicePCs() + if len(snap) == 0 { + return cur + } + snap = trimPlumbingPCs(snap) + if len(snap) == 0 { + return cur + } + // The junction is the first live frame whose function also appears in + // the snapshot — the defer owner (or the panicking function itself when + // defer and panic share a frame). Everything from there down is + // replaced by the whole snapshot: it already contains the owner and its + // callers, with the owner's pc on the panic path instead of the longjmp + // resume site. + for i := 0; i < len(cur); i++ { + entry := frameSymbol(cur[i] - 1).entry + if entry == 0 { + continue + } + for j := 0; j < len(snap); j++ { + if frameSymbol(snap[j]-1).entry == entry { + out := make([]uintptr, 0, i+len(snap)) + out = append(out, cur[:i]...) + out = append(out, snap...) + return out + } + } + } + return cur +} + +// callersWithPanicSplice is Callers with panic-frame splicing. With no +// snapshot stored (the overwhelmingly common case) it degrades to the +// plain walk at the cost of one TLS load. Otherwise the raw walk runs +// unskipped so splicing sees the junction frame, then the requested skip +// applies to the spliced view (matching gc, whose skip counts the logical +// panic-inclusive stack). +// +//go:noinline +func callersWithPanicSplice(skip int, pc []uintptr) int { + if len(pc) == 0 { + return 0 + } + if len(rtdebug.PanicPCs()) == 0 { + // One frame deeper than the extern.go call sites used to be. + return fpCallers(skip+1, pc) + } + var raw [128]uintptr + n := fpCallers(1, raw[:]) + if n <= 0 { + return 0 + } + view := spliceCallers(raw[:n]) + if skip < 0 { + skip = 0 + } + if skip >= len(view) { + return 0 + } + return copy(pc, view[skip:]) } func hasPrefix(s, prefix string) bool { @@ -52,8 +255,12 @@ func panicTraceback(skip int) bool { if n <= 0 { return false } + // A stored panic snapshot (Go panic or hardware fault, including + // faults inside C code) carries the frames the longjmp unwinding + // already removed; splice them in like Callers does. + view := spliceCallers(pcs[:n]) print("goroutine 1 [running]:\n") - frames := CallersFrames(pcs[:n]) + frames := CallersFrames(view) skippingPlumbing := true for { frame, more := frames.Next() diff --git a/runtime/internal/lib/runtime/unwind_wasm_llgo.go b/runtime/internal/lib/runtime/unwind_wasm_llgo.go index fb59d5e063..5ed4125531 100644 --- a/runtime/internal/lib/runtime/unwind_wasm_llgo.go +++ b/runtime/internal/lib/runtime/unwind_wasm_llgo.go @@ -10,3 +10,9 @@ func fpCallers(skip int, pc []uintptr) int { func fpUnwindAvailable() bool { return false } + +// callersWithPanicSplice is unreachable while fpUnwindAvailable is false, +// but it must remain in the selected source set for extern.go to type-check. +func callersWithPanicSplice(_ int, _ []uintptr) int { + return 0 +} diff --git a/runtime/internal/runtime/_wrap/fp.c b/runtime/internal/runtime/_wrap/fp.c new file mode 100644 index 0000000000..b026e7a2f6 --- /dev/null +++ b/runtime/internal/runtime/_wrap/fp.c @@ -0,0 +1,13 @@ +/* llgo_framepointer lives in the runtime core (not the public runtime + * package): Recover() records the recovering frame through it, and + * programs that never import "runtime" still link the core. */ +__attribute__((noinline)) void *llgo_framepointer(void) +{ +#if defined(__GNUC__) || defined(__clang__) + /* Read the saved caller FP before this helper's frame becomes invalid. */ + void **frame = (void **)__builtin_frame_address(0); + return frame ? *frame : 0; +#else + return 0; +#endif +} diff --git a/runtime/internal/runtime/caller.go b/runtime/internal/runtime/caller.go index 341158e825..dab32c3776 100644 --- a/runtime/internal/runtime/caller.go +++ b/runtime/internal/runtime/caller.go @@ -226,7 +226,100 @@ func Callers(skip int, pcs []uintptr) int { return n } +// PanicPCSnapshot, set by the public runtime package at init, captures the +// physical pc chain at panic time into the per-goroutine snapshot below. gc +// runs deferred functions on top of the panicked stack, so runtime.Caller, +// CallersFrames and debug.Stack invoked from a deferred function (before or +// after recover) see the panic-site frames; LLGo's longjmp unwinding +// removes them physically, and this snapshot is what caller-info APIs +// splice back in. +var PanicPCSnapshot func() + func SavePanicCallerFrames() { + // A fault handler stores the fault-site snapshot right before it + // panics; the regular capture here must not overwrite it. + p := panicPCStoreForG() + if p.armed != 0 { + p.armed = 0 + return + } + if PanicPCSnapshot != nil { + PanicPCSnapshot() + } +} + +type panicPCStore struct { + n int32 + armed int32 + fault int32 + recFP1 uintptr + recFP2 uintptr + pcs [64]uintptr +} + +func panicPCStoreForG() *panicPCStore { + return &getg().panicPCs +} + +// StorePanicPCs replaces the goroutine's panic snapshot (a new panic +// supersedes the previous one) and resets the recover marks. +func StorePanicPCs(pcs []uintptr) { + storePanicPCs(pcs, 0) +} + +// StoreFaultPCs is StorePanicPCs for fault handlers: the imminent +// panic's own capture is suppressed so the fault-site chain survives. +func StoreFaultPCs(pcs []uintptr) { + storePanicPCs(pcs, 1) +} + +func storePanicPCs(pcs []uintptr, armed int32) { + p := panicPCStoreForG() + n := len(pcs) + if n > len(p.pcs) { + n = len(p.pcs) + } + copy(p.pcs[:n], pcs) + p.n = int32(n) + p.armed = armed + p.fault = armed + p.recFP1 = 0 + p.recFP2 = 0 +} + +// PanicPCsAreFault reports whether the stored snapshot came from a +// hardware-fault context (captured without the program-text bound). +func PanicPCsAreFault() bool { + return panicPCStoreForG().fault != 0 +} + +// PanicPCs returns the goroutine's captured panic pcs (nil when none). +func PanicPCs() []uintptr { + p := panicPCStoreForG() + if p.n == 0 { + return nil + } + return p.pcs[:p.n] +} + +// MarkPanicRecoverFPs records the frames observing the panic at recover +// time; the snapshot stays spliceable exactly while one of them is live on +// the physical chain (the deferred function has not returned yet). +func MarkPanicRecoverFPs(fp1, fp2 uintptr) { + p := panicPCStoreForG() + p.recFP1 = fp1 + p.recFP2 = fp2 +} + +// PanicRecoverFPs returns the recover-time frame marks. +func PanicRecoverFPs() (uintptr, uintptr) { + p := panicPCStoreForG() + return p.recFP1, p.recFP2 +} + +// PanicActive reports whether a panic is in flight (not yet recovered). +func PanicActive() bool { + return getg().panic_ != nil } func BindCallerLocation(pc uintptr, rawName string) { diff --git a/runtime/internal/runtime/runtime2.go b/runtime/internal/runtime/runtime2.go index 8e0fe9dc7a..7787c8e225 100644 --- a/runtime/internal/runtime/runtime2.go +++ b/runtime/internal/runtime/runtime2.go @@ -38,9 +38,10 @@ const ( // make sense once LLGo can suspend and resume a G (saved registers, wait state, // and stack roots) belong here when those facilities are added. type g struct { - defer_ *Defer - panic_ unsafe.Pointer - m *m + defer_ *Defer + panic_ unsafe.Pointer + panicPCs panicPCStore + m *m atomicstatus uint32 goid uint64 diff --git a/runtime/internal/runtime/z_rt.go b/runtime/internal/runtime/z_rt.go index d771ab94cf..090e3e4cbd 100644 --- a/runtime/internal/runtime/z_rt.go +++ b/runtime/internal/runtime/z_rt.go @@ -46,10 +46,34 @@ func Recover() (ret any) { if PanicRecovered != nil { PanicRecovered() } + // The deferred function that recovers keeps observing the panic + // stack until it returns (gc runs defers on top of it). The public + // runtime marks its frame so the pc snapshot stays spliceable that + // long; the mark reads the frame-pointer chain, which after + // siglongjmp can reach a stale/unmapped slot, so the guarded read + // lives in the package that has a page probe (RecoverMark). Nil + // when lib/runtime is not linked — no snapshot machinery, nothing + // to mark. + if RecoverMark != nil { + RecoverMark() + } } return } +// RecoverMark, set by the public runtime package, records the recovering +// frame for panic-snapshot splicing. +var RecoverMark func() + +const ( + // LLGoFiles: the frame-pointer helper must live in the runtime core — + // programs that never import "runtime" still link Recover. + LLGoFiles = "_wrap/fp.c" +) + +//go:linkname c_framepointer C.llgo_framepointer +func c_framepointer() unsafe.Pointer + // Panic panics with a value. func Panic(v any) { if v == nil { diff --git a/test/_manualtest/README.md b/test/_manualtest/README.md index ddb96abd29..69ce1f1300 100644 --- a/test/_manualtest/README.md +++ b/test/_manualtest/README.md @@ -44,18 +44,20 @@ runtime-internal, patched-stdlib and startup frames may differ. # SIGFPE Verified (darwin/arm64 + linux/arm64 + linux/amd64): -- SIGSEGV in a C frame converts to a Go panic; recover observes gc's exact - error text. -- Known gaps (recorded for the follow-up PRs): - 1. The fault-site stack (cexc_leaf_segv -> cexc_mid_segv x3 -> cexc_segv - -> Go frames) is not visible yet — recover/tracebacks show the - post-longjmp stack. The panic-snapshot follow-up extends to signal - handlers: walk the FP chain from the ucontext pc/fp; C frames get - dladdr names, Go frames funcinfo names — same machinery as the - unwinder. - 2. Only SIGSEGV is installed; SIGFPE (amd64 division) core-dumps, SIGBUS - is not handled. - 3. No sigaltstack: on stack overflow the handler cannot run and the +- SIGSEGV/SIGBUS/SIGFPE in a C frame convert to Go panics (gc's exact + error texts); recover works, and the fault-site stack — C frames down + through the Go callers — appears in recovered `debug.Stack()` and in + unrecovered tracebacks (panic pc snapshot captured from the signal + ucontext, FP chain walked from the interrupted frame; C is compiled + with -fno-omit-frame-pointer so x86-64 chains hold). +- Known limitations: + 1. On linux, C frames may display under a neighboring Go function's + name: dladdr only sees dynamic symbols there, so the nearest-below + table attribution cannot be cross-checked (darwin names C frames + correctly via dladdr). Pre-existing for any C pc between Go + functions; the link-phase hole-sentinel follow-up fixes naming and + misattribution together. + 2. No sigaltstack: on stack overflow the handler cannot run and the process dies (gc prints "stack overflow"). Note that C-side UB gets propagated by clang (this test was once optimized into infinite recursion); wrap/fault.c uses a volatile pointer to prevent that. diff --git a/test/go/caller_acceptance_test.go b/test/go/caller_acceptance_test.go index cb97e8e924..f56aa87b1b 100644 --- a/test/go/caller_acceptance_test.go +++ b/test/go/caller_acceptance_test.go @@ -25,6 +25,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -475,6 +476,7 @@ func TestCallerAcceptanceCFaultRecover(t *testing.T) { import ( "fmt" "os" + "runtime/debug" _ "unsafe" ) @@ -490,7 +492,7 @@ func viaGo() { cexcSegv(2) } -func main() { +func one(last bool) { defer func() { r := recover() if r == nil { @@ -501,10 +503,22 @@ func main() { if !ok || err.Error() != "runtime error: invalid memory address or nil pointer dereference" { panic(r) } - os.Stdout.WriteString("CFAULT_OK\n") + if last { + os.Stdout.WriteString("CFAULT_OK\n") + os.Stdout.Write(debug.Stack()) + } }() viaGo() } + +func main() { + // Three sequential faults: a handler that leaves the signal blocked + // after the longjmp escape (savemask=0 jmpbufs) survives the first + // fault and core-dumps on the second — the exact CI failure mode. + one(false) + one(false) + one(true) +} ` const csrc = `#include @@ -513,9 +527,11 @@ func main() { static int32_t *volatile cexc_null; volatile int32_t cexc_marks; -static void cexc_leaf(void) { *cexc_null = 42; } +/* Non-static: the fault-chain tail cut keeps frames dladdr can name; + * static helpers have no symbol and would end the visible chain. */ +void cexc_leaf(void) { *cexc_null = 42; } -static void cexc_mid(int32_t depth) { +void cexc_mid(int32_t depth) { if (depth > 0) { cexc_mid(depth - 1); cexc_marks++; @@ -542,6 +558,20 @@ void cexc_segv(int32_t depth) { if !strings.Contains(out, "CFAULT_OK") { t.Fatalf("C fault probe missing marker:\n%s", out) } + // The fault-site chain must be visible from the recovered deferred + // function. C frame names come from dladdr, which on linux only sees + // dynamic symbols — there the C frames show as raw pcs and only the + // Go side of the chain is asserted (foreign-symbol naming is the + // planned link-phase hole-sentinel follow-up). + wants := []string{"main.viaGo", "main.main"} + if runtime.GOOS == "darwin" { + wants = append(wants, "cexc_segv") + } + for _, want := range wants { + if !strings.Contains(out, want) { + t.Fatalf("recovered stack missing fault frame %q:\n%s", want, out) + } + } } func writeCallerAcceptanceModule(t *testing.T, dir string, files map[string]string) { diff --git a/test/go/finalizer_test.go b/test/go/finalizer_test.go index aceb2d2a2c..ec986129d9 100644 --- a/test/go/finalizer_test.go +++ b/test/go/finalizer_test.go @@ -25,7 +25,12 @@ import ( func TestRuntimeSetFinalizerTinyObjects(t *testing.T) { const n = 32 finalized := make(chan int32, n) - makeFinalizerTinyObjects(n, finalized) + created := make(chan struct{}) + go func() { + makeFinalizerTinyObjects(n, finalized) + close(created) + }() + <-created done := make([]bool, n) count := 0 diff --git a/test/goroot/xfail.yaml b/test/goroot/xfail.yaml index 9c69386aae..3c66ac0ff1 100644 --- a/test/goroot/xfail.yaml +++ b/test/goroot/xfail.yaml @@ -1772,6 +1772,51 @@ flakes: directive: run case: goprint.go reason: darwin/arm64 goroot run can either pass or fail + - version: go1.24 + platform: linux/amd64 + directive: run + case: fixedbugs/issue45045.go + reason: replaced map keys are not reliably finalized on linux/amd64, so the run can pass or time out waiting for the old key + - version: go1.25 + platform: linux/amd64 + directive: run + case: fixedbugs/issue45045.go + reason: replaced map keys are not reliably finalized on linux/amd64, so the run can pass or time out waiting for the old key + - version: go1.26 + platform: linux/amd64 + directive: run + case: fixedbugs/issue45045.go + reason: replaced map keys are not reliably finalized on linux/amd64, so the run can pass or time out waiting for the old key + - version: go1.24 + platform: linux/amd64 + directive: run + case: fixedbugs/issue54343.go + reason: the bound-method receiver is not reliably finalized after its method value is cleared on linux/amd64, so the run can pass or report never GC'd + - version: go1.25 + platform: linux/amd64 + directive: run + case: fixedbugs/issue54343.go + reason: the bound-method receiver is not reliably finalized after its method value is cleared on linux/amd64, so the run can pass or report never GC'd + - version: go1.26 + platform: linux/amd64 + directive: run + case: fixedbugs/issue54343.go + reason: the bound-method receiver is not reliably finalized after its method value is cleared on linux/amd64, so the run can pass or report never GC'd + - version: go1.24 + platform: darwin/arm64 + directive: run + case: fixedbugs/issue25897a.go + reason: reflect-generated goroutines under a single-P continuous-GC loop can complete or exceed the 1m run timeout on darwin/arm64 + - version: go1.25 + platform: darwin/arm64 + directive: run + case: fixedbugs/issue25897a.go + reason: reflect-generated goroutines under a single-P continuous-GC loop can complete or exceed the 1m run timeout on darwin/arm64 + - version: go1.26 + platform: darwin/arm64 + directive: run + case: fixedbugs/issue25897a.go + reason: reflect-generated goroutines under a single-P continuous-GC loop can complete or exceed the 1m run timeout on darwin/arm64 xfails: - version: go1.26 directive: errorcheck @@ -2389,11 +2434,6 @@ xfails: directive: run case: finprofiled.go reason: LLGo finalizer profiling run hangs beyond the 1m timeout on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue25897a.go - reason: LLGo makes no progress starting reflect-generated goroutines while a single-P runtime continuously collects - version: go1.24 platform: darwin/arm64 directive: run @@ -2424,10 +2464,6 @@ xfails: directive: run case: fixedbugs/issue72063.go reason: generic Y-combinator build exits successfully without producing a binary on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue72844.go - reason: dereferencing a nil array pointer for len or range does not panic when required on darwin/arm64 - version: go1.24 platform: darwin/arm64 directive: run @@ -2660,11 +2696,6 @@ xfails: directive: run case: fixedbugs/issue65417.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue72844.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -2720,11 +2751,6 @@ xfails: directive: run case: fixedbugs/issue65417.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue72844.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -2811,15 +2837,11 @@ xfails: - platform: darwin/arm64 directive: run case: fixedbugs/bug347.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/bug348.go - reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue14646.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/issue15281.go @@ -2831,7 +2853,7 @@ xfails: - platform: darwin/arm64 directive: run case: fixedbugs/issue27201.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/issue27518b.go @@ -2839,37 +2861,19 @@ xfails: - platform: darwin/arm64 directive: run case: fixedbugs/issue29504.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/issue32477.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue33724.go - reason: latest main goroot run failure on darwin/arm64 - - version: go1.24 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue45045.go - reason: latest main goroot run failure on darwin/arm64 - - version: go1.25 - platform: darwin/arm64 - directive: run - case: fixedbugs/issue45045.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue4562.go - reason: latest main goroot run failure on darwin/arm64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - platform: darwin/arm64 directive: run case: fixedbugs/issue46725.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue54343.go - reason: latest main goroot run failure on darwin/arm64 - version: go1.24 platform: darwin/arm64 directive: run @@ -2884,10 +2888,6 @@ xfails: directive: run case: fixedbugs/issue57823.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue5856.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue5963.go @@ -2901,17 +2901,12 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug347.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run case: fixedbugs/bug348.go - reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue14646.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run @@ -2936,7 +2931,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue27201.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run @@ -2946,17 +2941,12 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue29504.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run case: fixedbugs/issue32477.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue33724.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run @@ -2967,16 +2957,11 @@ xfails: directive: run case: fixedbugs/issue4066.go reason: go1.25 goroot run failure on darwin/arm64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue45045.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run case: fixedbugs/issue4562.go - reason: go1.25 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.25 platform: linux/amd64 directive: run @@ -2987,11 +2972,6 @@ xfails: directive: run case: fixedbugs/issue72860.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue54343.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run @@ -3002,11 +2982,6 @@ xfails: directive: run case: fixedbugs/issue57823.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue5856.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run @@ -3072,12 +3047,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug347.go - reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue14646.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -3092,7 +3062,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue29504.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -3103,21 +3073,11 @@ xfails: directive: run case: fixedbugs/issue46725.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue54343.go - reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run case: fixedbugs/issue57823.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue5856.go - reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run @@ -3152,7 +3112,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug348.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -3167,17 +3127,12 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue27201.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run case: fixedbugs/issue27518b.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue33724.go - reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run @@ -3188,16 +3143,11 @@ xfails: directive: run case: fixedbugs/issue4066.go reason: go1.24 goroot run failure on darwin/arm64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue45045.go - reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: linux/amd64 directive: run case: fixedbugs/issue4562.go - reason: go1.24 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.24 platform: linux/amd64 directive: run @@ -3223,11 +3173,6 @@ xfails: directive: run case: maymorestack.go reason: go1.26 goroot ci-mode run failure on darwin/arm64 - - version: go1.26 - platform: darwin/arm64 - directive: run - case: devirtualization_nil_panics.go - reason: go1.26 goroot run failure on darwin/arm64 - version: go1.26 platform: linux/amd64 directive: run @@ -3267,7 +3212,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug348.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -3282,17 +3227,12 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue27201.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run case: fixedbugs/issue27518b.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue33724.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -3303,16 +3243,11 @@ xfails: directive: run case: fixedbugs/issue4066.go reason: go1.26 goroot run failure on darwin/arm64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue45045.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run case: fixedbugs/issue4562.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -3323,11 +3258,6 @@ xfails: directive: run case: fixedbugs/issue5493.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: devirtualization_nil_panics.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -3342,12 +3272,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/bug347.go - reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue14646.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -3362,7 +3287,7 @@ xfails: platform: linux/amd64 directive: run case: fixedbugs/issue29504.go - reason: go1.26 goroot run failure on linux/amd64 + reason: panic/fault statement-line granularity in untracked functions (P4b prebuilt pcline) - version: go1.26 platform: linux/amd64 directive: run @@ -3373,21 +3298,11 @@ xfails: directive: run case: fixedbugs/issue46725.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue54343.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run case: fixedbugs/issue57823.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue5856.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -3447,10 +3362,6 @@ xfails: directive: run case: fixedbugs/issue43835.go reason: current main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue47928.go - reason: current main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue38496.go @@ -3531,10 +3442,6 @@ xfails: directive: run case: fixedbugs/issue43835.go reason: current main goroot run failure on linux/amd64 - - platform: linux/amd64 - directive: run - case: fixedbugs/issue47928.go - reason: current main goroot run failure on linux/amd64 - version: go1.26 platform: linux/amd64 directive: run @@ -3630,18 +3537,10 @@ xfails: directive: rundir case: fixedbugs/issue24693.go reason: llgo constructs cross-package itabs incorrectly for unexported interface methods - - version: go1.26 - directive: runindir - case: fixedbugs/issue11656.go - reason: llgo traceback metadata cannot resolve the recursive caller after a bad-PC fault - version: go1.26 directive: rundir case: fixedbugs/issue18911.go reason: llgo interface-conversion diagnostics omit the same-looking types from different packages detail - - version: go1.26 - directive: runindir - case: fixedbugs/issue30862.go - reason: llgo does not implement the fieldtrack experiment metadata used by this test - version: go1.26 directive: rundir case: interface/embed3.go