diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 44cd7f9e..83e82a28 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -48,13 +48,8 @@ fn main() -> anyhow::Result<()> { let ctx = addr2line::Context::new(&object)?; - for (i, address) in cli.dump.split('-').enumerate() { - let mut address = u64::from_str_radix(address, 16)?; - if address <= 0xFFFF { - address += 0x0800_0000; - } - - print_address(&ctx, i, address, modification_time)?; + for (i, address) in gwilym_encoding::decode(&cli.dump).into_iter().enumerate() { + print_address(&ctx, i, address.into(), modification_time)?; } Ok(()) @@ -184,3 +179,72 @@ fn is_interesting_function(function_name: &str, path: &str) -> bool { true } + +mod gwilym_encoding { + use std::sync::OnceLock; + + const ALPHABET: &[u8] = b"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; + + // pub fn encode_16(input: u16) -> [u8; 3] { + // let input = input as usize; + // [ + // ALPHABET[input >> (16 - 5)], + // ALPHABET[(input >> (16 - 10)) & 0b11111], + // ALPHABET[input & 0b111111], + // ] + // } + + // pub fn encode_32(input: u32) -> [u8; 6] { + // let input = input as usize; + // let output_16 = encode_16(input as u16); + // [ + // ALPHABET[(input >> (32 - 5)) | 0b100000], + // ALPHABET[(input >> (32 - 10)) & 0b11111], + // ALPHABET[(input >> (32 - 16)) & 0b111111], + // output_16[0], + // output_16[1], + // output_16[2], + // ] + // } + + pub fn decode(input: &str) -> Vec { + let mut result = vec![]; + + let mut previous_value = None; + for chunk in input.as_bytes().chunks_exact(3) { + let value = decode_chunk(chunk); + + if value & (1 << 17) != 0 { + previous_value = Some(value << 16); + } else if let Some(upper_bits) = previous_value { + result.push(upper_bits | value); + previous_value = None; + } else { + result.push(value | 0x0800_0000); + } + } + + result + } + + fn decode_chunk(chunk: &[u8]) -> u32 { + let a = get_value_for_char(chunk[0]); + let b = get_value_for_char(chunk[1]); + let c = get_value_for_char(chunk[2]); + + (a << (16 - 5)) | (b << (16 - 10)) | c + } + + fn get_value_for_char(input: u8) -> u32 { + static REVERSE_ALHPABET: OnceLock<[u8; 128]> = OnceLock::new(); + + REVERSE_ALHPABET.get_or_init(|| { + let mut result = [0; 128]; + for (i, &c) in ALPHABET.iter().enumerate() { + result[c as usize] = i as u8; + } + + result + })[input as usize] as u32 + } +} diff --git a/agb/Cargo.toml b/agb/Cargo.toml index c682638d..e6d35195 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -21,6 +21,7 @@ agb_fixnum = { version = "0.19.1", path = "../agb-fixnum" } agb_hashmap = { version = "0.19.1", path = "../agb-hashmap" } bare-metal = "1" bilge = "0.2" +qrcodegen-no-heap = "1.8" [package.metadata.docs.rs] default-target = "thumbv4t-none-eabi" diff --git a/agb/src/backtrace.rs b/agb/src/backtrace.rs index 530fe845..aba7427c 100644 --- a/agb/src/backtrace.rs +++ b/agb/src/backtrace.rs @@ -87,23 +87,47 @@ pub(crate) fn unwind_exception() -> Frames { impl core::fmt::Display for Frames { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut is_first = true; - for frame in &self.frames { - if !is_first { - write!(f, "-")?; - } - if frame & 0xFFFF_0000 == 0x0800_0000 { - let frame = frame & 0xFFFF; - write!(f, "{frame:x}")?; - } else { - write!(f, "{frame:x}")?; - } + let frame = *frame as u16; // intentionally truncate + let frame_encoded = gwilym_encoding::encode_16(frame); + let frame_str = unsafe { core::str::from_utf8_unchecked(&frame_encoded) }; - is_first = false; + write!(f, "{frame_str}")?; + } else { + let frame_encoded = gwilym_encoding::encode_32(*frame); + let frame_str = unsafe { core::str::from_utf8_unchecked(&frame_encoded) }; + + write!(f, "{frame_str}")?; + } } Ok(()) } } + +mod gwilym_encoding { + const ALPHABET: &[u8] = b"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; + + pub fn encode_16(input: u16) -> [u8; 3] { + let input = input as usize; + [ + ALPHABET[input >> (16 - 5)], + ALPHABET[(input >> (16 - 10)) & 0b11111], + ALPHABET[input & 0b111111], + ] + } + + pub fn encode_32(input: u32) -> [u8; 6] { + let input = input as usize; + let output_16 = encode_16(input as u16); + [ + ALPHABET[(input >> (32 - 5)) | 0b100000], + ALPHABET[(input >> (32 - 10)) & 0b11111], + ALPHABET[(input >> (32 - 16)) & 0b111111], + output_16[0], + output_16[1], + output_16[2], + ] + } +} diff --git a/agb/src/dma.rs b/agb/src/dma.rs index 9e29ef61..8135a499 100644 --- a/agb/src/dma.rs +++ b/agb/src/dma.rs @@ -53,7 +53,7 @@ impl Dma { } } - fn disable(&mut self) { + pub(crate) fn disable(&mut self) { unsafe { MemoryMapped::new(dma_control_addr(self.number)) }.set(0); } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index a9743390..dcb3078f 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -167,6 +167,7 @@ pub mod mgba; pub use agb_fixnum as fixnum; /// Contains an implementation of a hashmap which suits the gameboy advance's hardware. pub use agb_hashmap as hash_map; +mod panics_render; /// Simple random number generator pub mod rng; pub mod save; @@ -289,6 +290,8 @@ impl Gba { /// You can run the tests using `cargo test`, but it will work better through `mgba-test-runner` by /// running something along the lines of `CARGO_TARGET_THUMBV4T_NONE_EABI_RUNNER=mgba-test-runner cargo test`. pub mod test_runner { + use self::panics_render::render_backtrace; + use super::*; #[doc(hidden)] @@ -327,11 +330,9 @@ pub mod test_runner { format_args!("debug data: {frames}"), mgba::DebugLevel::Error, ); - - let _ = mgba.print(format_args!("Error: {info}"), mgba::DebugLevel::Fatal); } - loop {} + render_backtrace(&frames, info); } static mut TEST_GBA: Option = None; diff --git a/agb/src/panics_render.rs b/agb/src/panics_render.rs new file mode 100644 index 00000000..ab9433f1 --- /dev/null +++ b/agb/src/panics_render.rs @@ -0,0 +1,70 @@ +use core::panic::PanicInfo; + +use alloc::{format, vec}; + +use crate::{ + backtrace, + display::{busy_wait_for_vblank, HEIGHT, WIDTH}, + dma::dma3_exclusive, + interrupt, mgba, syscall, +}; + +pub fn render_backtrace(trace: &backtrace::Frames, info: &PanicInfo) -> ! { + interrupt::free(|_cs| { + dma3_exclusive(|| { + // SAFETY: This is not fine, but we're crashing anyway. The loop at the end should stop anything bad happening + let mut gba = unsafe { crate::Gba::new_in_entry() }; + + gba.dma.dma().dma3.disable(); + draw_qr_code(&mut gba, trace); + + busy_wait_for_vblank(); + + if let Some(mut mgba) = mgba::Mgba::new() { + let _ = mgba.print(format_args!("Error: {info}"), mgba::DebugLevel::Fatal); + } + + loop { + syscall::halt(); + } + }) + }) +} + +fn draw_qr_code(gba: &mut crate::Gba, trace: &backtrace::Frames) { + let mut gfx = gba.display.video.bitmap3(); + + let qrcode_string_data = format!("https://agbrs.dev/crash#v1-{trace}"); + const MAX_VERSION: qrcodegen_no_heap::Version = qrcodegen_no_heap::Version::new(6); + + let mut temp_buffer = vec![0; MAX_VERSION.buffer_len()]; + let mut out_buffer = vec![0; MAX_VERSION.buffer_len()]; + + let qr_code = match qrcodegen_no_heap::QrCode::encode_text( + &qrcode_string_data, + &mut temp_buffer, + &mut out_buffer, + qrcodegen_no_heap::QrCodeEcc::Medium, + qrcodegen_no_heap::Version::MIN, + MAX_VERSION, + Some(qrcodegen_no_heap::Mask::new(0)), + true, + ) { + Ok(qr_code) => qr_code, + Err(e) => { + crate::println!("Error generating qr code: {e:?}"); + return; + } + }; + + for y in 0..HEIGHT { + for x in 0..WIDTH { + let colour = if qr_code.get_module(x / 2 - 4, y / 2 - 4) { + 0x0000 + } else { + 0xFFFF + }; + gfx.draw_point(x, y, colour); + } + } +}