From 30fc1b07f9d4fda5f9c738505ba72798312f38fc Mon Sep 17 00:00:00 2001 From: Konstantin Sharlaimov Date: Sun, 10 May 2026 19:57:18 +0200 Subject: [PATCH 1/2] 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 | 15 +++++++++++++++ 5 files changed, 39 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..14ab16b35c 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,20 @@ 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() + + // heapEndSymbol is defined in baremetal.go; we also ensure that the goroutine exception frame is within addressable memory. + if pspVal >= 0x20000000 && pspVal < (uintptr(unsafe.Pointer(&heapEndSymbol))-uintptr(unsafe.Sizeof(interruptStack{}))) { + pspFrame := (*interruptStack)(unsafe.Pointer(pspVal)) + print(" psp=", pspFrame) + print(" psp_pc=", pspFrame.PC) + print(" psp_lr=", pspFrame.LR) + } + println() abort() } From 5cdb1f1a835d62750591f2935f0b4335772ecb7d Mon Sep 17 00:00:00 2001 From: Konstantin Sharlaimov Date: Mon, 1 Jun 2026 08:34:57 +0200 Subject: [PATCH 2/2] Fix sizes test --- builder/sizes_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 7f24a55106..305085acfb 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", 7150, 1534, 120, 7248}, + {"wioterminal", "examples/pininterrupt", 7154, 1534, 120, 7248}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the