Skip to content

Commit 771ac20

Browse files
committed
Fix PICO_PANIC_FUNCTION= imelementations
- stack should remain properly aligned - risc-v was actually completely broken for user defined handlers because GCC was auto-pushing vaargs for naked function even with no stack frame - new implementation supports full call stacks in gdb for Arm or RISC-V when hitting breakpoint in panic function - new config defines make it explicit that gdb full stgack trace comes at the cost of corrupting stack based vaargs 4th is corrupted on Arm, later on RiSC-V (7 maybe?)
1 parent bc54814 commit 771ac20

File tree

8 files changed

+189
-37
lines changed

8 files changed

+189
-37
lines changed

src/rp2_common/pico_platform_panic/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ cc_library(
2020

2121
cc_library(
2222
name = "pico_platform_panic",
23-
srcs = ["panic.c"],
23+
srcs = ["panic.c", "custom_panic_function.S"],
2424
hdrs = ["include/pico/platform/panic.h"],
2525
includes = ["include"],
2626
target_compatible_with = compatible_with_rp2(),

src/rp2_common/pico_platform_panic/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ if (NOT TARGET pico_platform_panic)
33

44
target_sources(pico_platform_panic INTERFACE
55
${CMAKE_CURRENT_LIST_DIR}/panic.c
6+
${CMAKE_CURRENT_LIST_DIR}/custom_panic_function.S
67
)
78

89
target_include_directories(pico_platform_panic_headers SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (c) 2026 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include "pico/asm_helper.S"
8+
9+
#ifdef PICO_PANIC_FUNCTION
10+
#define __CONCAT1(x,y) x ## y
11+
#define __CONCAT(x,y) __CONCAT1(x,y)
12+
13+
#define PICO_PANIC_FUNCTION_IS_EMPTY (__CONCAT(PICO_PANIC_FUNCTION, 1))
14+
.text
15+
// Use a forwarding method here as it is a little simpler than renaming the symbol as it is used from assembler
16+
.weak panic // also allow override
17+
.type panic,%function
18+
panic:
19+
#ifdef __riscv
20+
// we seem to need to help gdb out with call frame on RISC-V
21+
.cfi_sections .debug_frame
22+
.cfi_startproc
23+
#if PICO_PANIC_FUNCTION_IS_EMPTY
24+
1: ebreak
25+
j 1b
26+
#elif PICO_PANIC_FUNCTION_DOES_NOT_RETURN
27+
// if you get an undefined reference here, you didn't define your PICO_PANIC_FUNCTION!
28+
jal zero, PICO_PANIC_FUNCTION
29+
#else
30+
#if PICO_PANIC_FUNCTION_WITH_CALL_STACK
31+
#if PICO_PANIC_FUNCTION_WITH_ALL_VAARGS
32+
#error PICO_PANIC_FUNCTION_WITH_CALL_STACK & PICO_PANIC_FUNCTION_WITH_ALL_VAARGS are incompatible when PICO_PANIC_FUNCTION_DOES_NOT_RETURN is 0
33+
#endif
34+
// .insn 0xb842 // cm.push {ra}, -16: Save ultimate return address
35+
// use explicit for better compatibility with gdb stack unwinding
36+
addi sp,sp,-16
37+
.cfi_def_cfa_offset 16
38+
sw ra,12(sp)
39+
.cfi_offset ra, -4
40+
#endif
41+
// if you get an undefined reference here, you didn't define your PICO_PANIC_FUNCTION!
42+
jal PICO_PANIC_FUNCTION
43+
#if !PICO_PANIC_FUNCTION_DOES_NOT_RETURN
44+
1: ebreak
45+
j 1b
46+
#endif
47+
#endif
48+
.cfi_endproc
49+
#else
50+
#if PICO_PANIC_FUNCTION_IS_EMPTY
51+
1: bkpt #0
52+
b 1b
53+
#elif PICO_PANIC_FUNCTION_DOES_NOT_RETURN
54+
// if you get an undefined reference here, you didn't define your PICO_PANIC_FUNCTION!
55+
ldr r4, =PICO_PANIC_FUNCTION
56+
bx r4
57+
#else
58+
#if PICO_PANIC_FUNCTION_WITH_CALL_STACK
59+
#if PICO_PANIC_FUNCTION_WITH_ALL_VAARGS
60+
#error PICO_PANIC_FUNCTION_WITH_CALL_STACK & PICO_PANIC_FUNCTION_WITH_ALL_VAARGS are incompatible when PICO_PANIC_FUNCTION_DOES_NOT_RETURN is 0
61+
#endif
62+
push {r4, lr} // must keep 8 byte alignment for call to user function
63+
#endif
64+
// if you get an undefined reference here, you didn't define your PICO_PANIC_FUNCTION!
65+
bl PICO_PANIC_FUNCTION
66+
#if !PICO_PANIC_FUNCTION_DOES_NOT_RETURN
67+
1: bkpt #0
68+
b 1b
69+
#endif
70+
#endif
71+
#endif
72+
73+
#endif // PICO_PANIC_FUNCTION
74+
75+

src/rp2_common/pico_platform_panic/include/pico/platform/panic.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,20 @@
1111
extern "C" {
1212
#endif
1313

14-
#ifndef __ASSEMBLER__
14+
// PICO_CONFIG: PICO_PANIC_FUNCTION, Name of a function to use in place of the stock panic function or empty string to simply breakpoint on panic, group=pico_runtime
15+
16+
// PICO_CONFIG: PICO_PANIC_FUNCTION_WITH_CALL_STACK, Ensure the call stack is available when using a custom panic function via PICO_PANIC_FUNCTION=. When set to 1 it conflicts with PICO_PANIC_FUNCTION_WITH_ALL_VAARGS=1 as a stack fram is pushed onto the stack corrupting later vaargs. Note this defaults to 1 as most custom panic functions don't actually use the arguments and the call stack seems more useful in general, default=1group=pico_runtime
17+
#ifndef PICO_PANIC_FUNCTION_WITH_CALL_STACK
18+
#define PICO_PANIC_FUNCTION_WITH_CALL_STACK 1
19+
#endif
1520

21+
// PICO_CONFIG: PICO_PANIC_FUNCTION_WITH_ALL_VAARGS, Ensures that all vaargs are faithfully passed to the custom panic function via PICO_PANIC_FUNCTION=. When set to 1 it conflicts with PICO_PANIC_FUNCTION_WITH_CALL_STACK=1 because both the later vaargs and the stack frame would be expected at the top of the stack. Note this defaults to 0 as most custom panic functions don't actually use the arguments and the call stack seems more useful in general, default=1group=pico_runtime
22+
#ifndef PICO_PANIC_FUNCTION_WITH_ALL_VAARGS
23+
#define PICO_PANIC_FUNCTION_WITH_ALL_VAARGS 0
24+
#endif
25+
26+
27+
#ifndef __ASSEMBLER__
1628
/*! \brief Panics with the message "Unsupported"
1729
* \ingroup pico_platform
1830
* \see panic

src/rp2_common/pico_platform_panic/panic.c

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,41 +21,8 @@ void __attribute__((noreturn)) panic_unsupported(void) {
2121
panic("not supported");
2222
}
2323

24-
// PICO_CONFIG: PICO_PANIC_FUNCTION, Name of a function to use in place of the stock panic function or empty string to simply breakpoint on panic, group=pico_runtime
2524
// note the default is not "panic" it is undefined
26-
#ifdef PICO_PANIC_FUNCTION
27-
#define PICO_PANIC_FUNCTION_EMPTY (__CONCAT(PICO_PANIC_FUNCTION, 1) == 1)
28-
#if !PICO_PANIC_FUNCTION_EMPTY
29-
extern void __attribute__((noreturn)) __printflike(1, 0) PICO_PANIC_FUNCTION(__unused const char *fmt, ...);
30-
#endif
31-
// Use a forwarding method here as it is a little simpler than renaming the symbol as it is used from assembler
32-
void __attribute__((naked, noreturn)) __printflike(1, 0) panic(__unused const char *fmt, ...) {
33-
// if you get an undefined reference here, you didn't define your PICO_PANIC_FUNCTION!
34-
pico_default_asm (
35-
#ifdef __riscv
36-
37-
#if !PICO_PANIC_FUNCTION_EMPTY
38-
"jal " __XSTRING(PICO_PANIC_FUNCTION) "\n"
39-
#endif
40-
"1: ebreak\n"
41-
"j 1b\n"
42-
43-
#else
44-
45-
"push {lr}\n"
46-
#if !PICO_PANIC_FUNCTION_EMPTY
47-
"bl " __XSTRING(PICO_PANIC_FUNCTION) "\n"
48-
#endif
49-
"1: bkpt #0\n"
50-
"b 1b\n" // loop for ever as we are no return
51-
52-
#endif
53-
:
54-
:
55-
:
56-
);
57-
}
58-
#else
25+
#ifndef PICO_PANIC_FUNCTION
5926
// todo consider making this try harder to output if we panic early
6027
// right now, print mutex may be uninitialised (in which case it deadlocks - although after printing "PANIC")
6128
// more importantly there may be no stdout/UART initialized yet
@@ -75,7 +42,7 @@ void __attribute__((noreturn)) __printflike(1, 0) panic(const char *fmt, ...) {
7542
weak_raw_vprintf(fmt, args);
7643
#endif
7744
va_end(args);
78-
puts("\n");
45+
puts("");
7946
#endif
8047
}
8148

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_subdirectory(pico_stdlib_test)
44
add_subdirectory(pico_stdio_test)
55
add_subdirectory(pico_time_test)
66
add_subdirectory(pico_divider_test)
7+
add_subdirectory(panic_function_test)
78
if (PICO_ON_DEVICE)
89
add_subdirectory(pico_float_test)
910
add_subdirectory(kitchen_sink)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
add_executable(panic_function_test_default ${CMAKE_CURRENT_LIST_DIR}/panic_function_test.c)
2+
target_link_libraries(panic_function_test_default PRIVATE pico_stdlib)
3+
pico_add_extra_outputs(panic_function_test_default)
4+
5+
add_executable(panic_function_test_bkpt ${CMAKE_CURRENT_LIST_DIR}/panic_function_test.c)
6+
target_compile_definitions(panic_function_test_bkpt PRIVATE PICO_PANIC_FUNCTION=) # should just bkpt
7+
target_link_libraries(panic_function_test_bkpt PRIVATE pico_stdlib)
8+
pico_add_extra_outputs(panic_function_test_bkpt)
9+
10+
add_executable(panic_function_test_user ${CMAKE_CURRENT_LIST_DIR}/panic_function_test.c)
11+
target_compile_definitions(panic_function_test_user PRIVATE PICO_PANIC_FUNCTION=handle_panic)
12+
target_link_libraries(panic_function_test_user PRIVATE pico_stdlib)
13+
pico_add_extra_outputs(panic_function_test_user)
14+
15+
add_executable(panic_function_test_user_all_vaargs ${CMAKE_CURRENT_LIST_DIR}/panic_function_test.c)
16+
target_compile_definitions(panic_function_test_user_all_vaargs PRIVATE PICO_PANIC_FUNCTION=handle_panic PICO_PANIC_FUNCTION_WITH_ALL_VAARGS=1 PICO_PANIC_FUNCTION_WITH_CALL_STACK=0)
17+
target_link_libraries(panic_function_test_user_all_vaargs PRIVATE pico_stdlib)
18+
pico_add_extra_outputs(panic_function_test_user_all_vaargs)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include <stdio.h>
2+
#include <stdarg.h>
3+
4+
#include "pico/stdlib.h"
5+
6+
#define MAGIC1 ((const char *)0x87654321)
7+
#define MAGIC2 0x97654321
8+
#define MAGIC3 3.14159265358979323846
9+
#define MAGIC4 123
10+
#define MAGIC5 456
11+
#define MAGIC6 789
12+
#define MAGIC7 0xabc
13+
14+
#define PICO_PANIC_FUNCTION_IS_EMPTY (__CONCAT(PICO_PANIC_FUNCTION, 1))
15+
16+
void __printflike(1, 0) handle_panic(const char *magic1, ...)
17+
{
18+
printf("checking first arg...\n");
19+
if (magic1 != MAGIC1) {
20+
printf("magic1 (%08x) != 0x%08x\n", magic1, MAGIC1);
21+
return;
22+
}
23+
va_list args;
24+
va_start(args, magic1);
25+
printf("checking early vaargs...\n");
26+
int magic2 = va_arg(args, int);
27+
if (magic2 != MAGIC2) {
28+
printf("magic2 (%08x) != 0x%08x\n", magic2, MAGIC2);
29+
return;
30+
}
31+
double magic3 = va_arg(args, double);
32+
if (magic3 != MAGIC3) {
33+
printf("magic3 (%f) != 0x%f\n", magic3, MAGIC3);
34+
return;
35+
}
36+
#if PICO_PANIC_FUNCTION_WITH_ALL_VAARGS
37+
printf("checking remaining vaargs...\n");
38+
int magic4 = va_arg(args, int);
39+
if (magic4 != MAGIC4) {
40+
printf("magic4 (%08x) != 0x%08x\n", magic4, MAGIC4);
41+
return;
42+
}
43+
int magic5 = va_arg(args, int);
44+
if (magic5 != MAGIC5) {
45+
printf("magic5 (%08x) != 0x%08x\n", magic5, MAGIC5);
46+
return;
47+
}
48+
int magic6 = va_arg(args, int);
49+
if (magic6 != MAGIC6) {
50+
printf("magic6 (%08x) != 0x%08x\n", magic6, MAGIC6);
51+
return;
52+
}
53+
int magic7 = va_arg(args, int);
54+
if (magic7 != MAGIC7) {
55+
printf("magic7 (%08x) != 0x%08x\n", magic7, MAGIC7);
56+
return;
57+
}
58+
#endif
59+
va_end(args);
60+
puts("PASSED");
61+
}
62+
63+
void main() {
64+
stdio_init_all();
65+
#ifndef PICO_PANIC_FUNCTION
66+
printf("Using default panic function...\n");
67+
panic("PASSED"); // should be printed
68+
#else
69+
#if PICO_PANIC_FUNCTION_IS_EMPTY
70+
printf("Using empty (bkpt only) panic function...\n");
71+
printf("PASSED\n"); // we should breakpoint next
72+
panic(MAGIC1, MAGIC2, MAGIC3, MAGIC4, MAGIC5, MAGIC6, MAGIC7);
73+
#endif
74+
printf("Using custom panic function '" __XSTRING(PICO_PANIC_FUNCTION) "'...\n");
75+
panic(MAGIC1, MAGIC2, MAGIC3, MAGIC4, MAGIC5, MAGIC6, MAGIC7);
76+
#endif
77+
printf("FAILED: expected panic not to return");
78+
}

0 commit comments

Comments
 (0)