Description
So I've got a project I'm trying to debug, but I'm running into something of an issue. The setup is that I have RISC-V CPUs (riscv32imc-unknown-none-elf
) debuggable over JTAG using OpenOCD + GDB. I would like to be able to get a backtrace out of GDB in the case that a program panics, but previously I was never able to guarantee that such a trace would be there since the stack would collapse as soon as execution entered into core::panicking::panic
or the like. The way I was attempting to get backtraces from GDB was by doing something to the effect of
file target/riscv32imc-unknown-none-elf/release/crate
set remotetimeout unlimited
break crate::panic_handler
target extended-remote :3333
load
define hook-stop
printf "!!!!! hit panic, execution has stopped !!!!!\n"
backtrace
disconnect
quit 1
end
continue
I figured that if I:
- Added
panic = "abort"
to the release profile inCargo.toml
- Switched to nightly
- Added the following to
.cargo/config.toml
:
[build]
# ...
# added:
rustflags = [
# ...
"-C", "force-frame-pointers=yes",
]
# ...
[unstable]
build-std = ["core", "panic_abort"]
Then in theory, I should be guaranteed to get backtraces. So I wrote a simple program:
#![no_std]
#![no_main]
// The particular impl here doesn't matter.
use some_hal_crate::uart::Uart;
use riscv_rt::entry;
const UART_ADDR: *const () = (0b11 << 30) as *const ();
// And neither does the implementation here - I'm just using it for some extra debugging output.
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
let mut uart = Uart::new(UART_ADDR);
writeln!(uart, "{info:?}").unwrap();
}
#[entry]
fn main() -> ! {
skooks();
loop {}
}
misadventures! {
am i glad hes frozen in_ there and that were out here and_ that_ hes_ the
sheriff and__ that__ were_ frozen_ out_ here_ and___ that___ were__ in__
there__ and____ i_ just remembered were___ out__ here__ what i__ want to know
is wheres the_ caveman
}
macro_rules! misadventures {
(@rev [] [$($rev:ident)*]) => {
misadventures! {
@defs [skooks $($rev)*]
}
};
(@rev [$first:ident $($rest:ident)*] [$($rev:ident)*]) => {
misadventures! {
@rev [$($rest)*] [$first $($rev)*]
}
};
(@defs [$last:ident]) => {
#[inline(never)]
fn $last() {
panic!();
}
};
(@defs [$n0:ident $n1:ident $($rest:ident)*]) => {
#[inline(never)]
fn $n0() {
$n1();
}
misadventures! {
@defs [$n1 $($rest)*]
}
};
($($words:ident)+) => {
misadventures! {
@rev [$($words)+] []
}
}
}
pub(crate) use misadventures;
However, I have two problem cases:
- In debug builds, the stack trace disappears as soon as execution enters the panicking code (
crate::am::panic_cold_explicit
I believe). Until then, I get a full trace (very nice!). - In release builds (with
debug = true
,split-debuginfo = "unpacked"
, andopt-level = "z"
), the stack trace is never deeper than#0
and#1
in GDB. It reportsBacktrace stopped: frame did not save the PC
.
panic = "abort"
was used for all of my testing.
Meta
This was done on nightly-2024-11-18
.