diff --git a/agb/.cargo/config.toml b/agb/.cargo/config.toml index 62ebedb7..ce415d4a 100644 --- a/agb/.cargo/config.toml +++ b/agb/.cargo/config.toml @@ -6,9 +6,17 @@ build-std-features = ["compiler-builtins-mem"] target = "thumbv4t-none-eabi" [target.thumbv4t-none-eabi] -rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"] +rustflags = [ + "-Clink-arg=-Tgba.ld", + "-Ctarget-cpu=arm7tdmi", + "-Cforce-frame-pointers=yes", +] runner = "mgba-test-runner" [target.armv4t-none-eabi] -rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"] +rustflags = [ + "-Clink-arg=-Tgba.ld", + "-Ctarget-cpu=arm7tdmi", + "-Cforce-frame-pointers=yes", +] runner = "mgba-test-runner" diff --git a/agb/src/backtrace.rs b/agb/src/backtrace.rs new file mode 100644 index 00000000..6081c45c --- /dev/null +++ b/agb/src/backtrace.rs @@ -0,0 +1,86 @@ +use core::{arch::asm, ops::Index}; + +use alloc::vec::Vec; + +// only works for code compiled as THUMB +#[repr(C)] +#[derive(Clone, Default, Debug)] +struct Context { + registers: [u32; 11], +} + +pub struct Frame { + pub address: u32, +} + +#[allow(unused)] +enum Register { + R0, + R1, + R2, + R3, + R4, + R5, + R6, + FP, + SP, + LR, + PC, +} + +impl Index for Context { + type Output = u32; + + fn index(&self, index: Register) -> &Self::Output { + &self.registers[index as usize] + } +} + +#[inline(never)] +pub(crate) fn unwind_exception() -> Vec { + let mut context = Context::default(); + + unsafe { + let context_ptr = (&mut context) as *mut _; + + asm!( + " + str r0, [r0, #0x00] + str r1, [r0, #0x04] + str r2, [r0, #0x08] + str r3, [r0, #0x0C] + str r4, [r0, #0x10] + str r5, [r0, #0x14] + str r6, [r0, #0x18] + str r7, [r0, #0x1C] + mov r7, sp + str r7, [r0, #0x20] + mov r7, lr + str r7, [r0, #0x24] + mov r7, pc + str r7, [r0, #0x28] + ldr r7, [r0, #0x1C] + ", + in("r0") context_ptr + ); + } + + let mut frame_pointer = context[Register::FP]; + + let mut frames = Vec::new(); + + loop { + let sp = unsafe { *(frame_pointer as *const u32) }; + let lr = unsafe { *((frame_pointer as *const u32).add(1)) }; + + if sp == 0 { + break; + } + + frames.push(Frame { address: lr }); + + frame_pointer = sp; + } + + frames +} diff --git a/agb/src/lib.rs b/agb/src/lib.rs index cd1fa99e..03a29264 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -150,6 +150,7 @@ extern crate alloc; mod agb_alloc; mod agbabi; +mod backtrace; mod bitarray; /// Implements everything relating to things that are displayed on screen. pub mod display; @@ -317,11 +318,19 @@ pub mod test_runner { #[panic_handler] fn panic_implementation(info: &core::panic::PanicInfo) -> ! { + let frames = backtrace::unwind_exception(); + if let Some(mut mgba) = mgba::Mgba::new() { - mgba.print(format_args!("[failed]"), mgba::DebugLevel::Error) - .unwrap(); - mgba.print(format_args!("Error: {info}"), mgba::DebugLevel::Fatal) - .unwrap(); + let _ = mgba.print(format_args!("[failed]"), mgba::DebugLevel::Error); + + for frame in frames { + let _ = mgba.print( + format_args!("{:#08x}", frame.address), + mgba::DebugLevel::Error, + ); + } + + let _ = mgba.print(format_args!("Error: {info}"), mgba::DebugLevel::Fatal); } loop {}