From 217664ba0c3bf107898befcd613ca21e0b78df5c Mon Sep 17 00:00:00 2001 From: Konstantin Sharlaimov Date: Sun, 10 May 2026 19:57:18 +0200 Subject: [PATCH] runtime: improve hardfault handler and stack reporting on Cortex-M - Add reference to the current goroutine stack (PSP) when showing the hardfault info on Cortex-M. --- builder/sizes_test.go | 2 +- src/internal/task/task_none.go | 5 +++++ src/internal/task/task_stack_cortexm.c | 11 +++++++++++ src/internal/task/task_stack_cortexm.go | 7 +++++++ src/runtime/runtime_cortexm_hardfault_debug.go | 13 +++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index fd985a8977..7f24a55106 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 3680, 280, 0, 2252}, {"microbit", "examples/serial", 2694, 342, 8, 2248}, - {"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248}, + {"wioterminal", "examples/pininterrupt", 7150, 1534, 120, 7248}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/internal/task/task_none.go b/src/internal/task/task_none.go index 60bd867aeb..7abd7a6bfc 100644 --- a/src/internal/task/task_none.go +++ b/src/internal/task/task_none.go @@ -42,3 +42,8 @@ func SystemStack() uintptr { runtimePanic("scheduler is disabled") return 0 // unreachable } + +func GoroutineStack() uintptr { + // No separate goroutine stack without a scheduler. + return 0 +} diff --git a/src/internal/task/task_stack_cortexm.c b/src/internal/task/task_stack_cortexm.c index 3c7e957971..9beb1c81da 100644 --- a/src/internal/task/task_stack_cortexm.c +++ b/src/internal/task/task_stack_cortexm.c @@ -11,3 +11,14 @@ uintptr_t SystemStack() { ); return sp; } + +uintptr_t GoroutineStack() { + uintptr_t sp; + asm volatile( + "mrs %0, PSP" + : "=r"(sp) + : + : "memory" + ); + return sp; +} diff --git a/src/internal/task/task_stack_cortexm.go b/src/internal/task/task_stack_cortexm.go index 653dc06e17..38d44c88c8 100644 --- a/src/internal/task/task_stack_cortexm.go +++ b/src/internal/task/task_stack_cortexm.go @@ -70,3 +70,10 @@ func (s *state) pause() { // //export SystemStack func SystemStack() uintptr + +// GoroutineStack returns the PSP (goroutine stack pointer). When a fault +// occurs in goroutine context, the hardware saves the exception frame to PSP; +// reading PSP+0x18 gives the actual faulting PC. +// +//export GoroutineStack +func GoroutineStack() uintptr diff --git a/src/runtime/runtime_cortexm_hardfault_debug.go b/src/runtime/runtime_cortexm_hardfault_debug.go index 7e953a67de..8be3a66e93 100644 --- a/src/runtime/runtime_cortexm_hardfault_debug.go +++ b/src/runtime/runtime_cortexm_hardfault_debug.go @@ -4,6 +4,7 @@ package runtime import ( "device/arm" + "internal/task" "unsafe" ) @@ -106,6 +107,18 @@ func HardFault_Handler() { print(" pc=", sp.PC) } } + + // PSP holds the actual exception frame when the fault occurred in a + // goroutine (thread mode, PSP active). The MSP-based sp above is the + // scheduler's stack and its PC is garbage in that case. + pspVal := task.GoroutineStack() + if pspVal >= 0x20000000 && pspVal < 0x20040000 { + pspFrame := (*interruptStack)(unsafe.Pointer(pspVal)) + print(" psp=", pspFrame) + print(" psp_pc=", pspFrame.PC) + print(" psp_lr=", pspFrame.LR) + } + println() abort() }