Skip to content

runtime: improve hardfault handler and stack reporting on Cortex-M#5380

Open
digitalentity wants to merge 1 commit into
tinygo-org:devfrom
helvionics:de_better_hardfault_handler
Open

runtime: improve hardfault handler and stack reporting on Cortex-M#5380
digitalentity wants to merge 1 commit into
tinygo-org:devfrom
helvionics:de_better_hardfault_handler

Conversation

@digitalentity
Copy link
Copy Markdown

Add reference to the current goroutine stack (PSP) when showing the hardfault info on Cortex-M. The stack where the hardfault happens is usually the scheduler's stack (MSP), which provides no information about the goroutine that actually caused the fault.

This was required to debug random crashes (#5375). The reason was stack overflow, which trigered the udf instruction. The MSP only pointed to the HardFault handler and the PC initially suggested stack corruption and nil dereference, while in reality the stack canary was overwritten which triggered a panic.

@digitalentity
Copy link
Copy Markdown
Author

The TestBuild test is flaky - I've seen it failing randomly even locally.

- Add reference to the current goroutine stack (PSP) when showing the hardfault info on Cortex-M.
@digitalentity digitalentity force-pushed the de_better_hardfault_handler branch from 217664b to 3d78ac8 Compare May 16, 2026 09:06
@deadprogram deadprogram requested a review from aykevl May 17, 2026 05:57
Copy link
Copy Markdown
Member

@aykevl aykevl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this looks useful to me, though see some concerns below.

One possible improvement could be to recognize this line in the monitor, so that the source line is automatically printed in a panic:

tinygo/monitor.go

Lines 281 to 293 in de83d67

var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `)
// Extract the address from the "panic: runtime error at" message.
func extractPanicAddress(line []byte) uint64 {
matches := addressMatch.FindSubmatch(line)
if matches != nil {
address, err := strconv.ParseUint(string(matches[1]), 16, 64)
if err == nil {
return address
}
}
return 0
}

// 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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming 256kB of RAM seems kind of arbitrary?
I would suggest either using the range according to the Cortex-M memory map (0x2000_0000..0x3fff_ffff) or using the actual chip RAM (currently exposed as _heap_end).

// reading PSP+0x18 gives the actual faulting PC.
//
//export GoroutineStack
func GoroutineStack() uintptr
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect you'll also need a declaration of this for -scheduler=cores (try tinygo build -scheduler=cores -target=pico examples/blinky1).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants