From 7708398981ad2013516fc2cc6472de30038c033e Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 13:05:39 +0100 Subject: [PATCH 01/35] Basic implementation of backtraces on panic --- agb/.cargo/config.toml | 12 +++++- agb/src/backtrace.rs | 86 ++++++++++++++++++++++++++++++++++++++++++ agb/src/lib.rs | 17 +++++++-- 3 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 agb/src/backtrace.rs 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 {} From 3ab6d08c7f7260e32ab27cd9557a9dcd740d0874 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 13:26:48 +0100 Subject: [PATCH 02/35] Start to implement an agb-addr2line --- agb-addr2line/Cargo.toml | 22 ++++++++++++++++++++ agb-addr2line/src/main.rs | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 agb-addr2line/Cargo.toml create mode 100644 agb-addr2line/src/main.rs diff --git a/agb-addr2line/Cargo.toml b/agb-addr2line/Cargo.toml new file mode 100644 index 00000000..82cfffa7 --- /dev/null +++ b/agb-addr2line/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "agb-addr2line" +version = "0.19.1" +edition = "2021" +authors = ["Gwilym Inzani "] +license = "MPL-2.0" +description = "CLI utility to convert agb stack trace dumps into human readable stack traces" +repository = "https://github.com/agbrs/agb" + +[dependencies] +anyhow = "1" +clap = { version = "4", features = ["derive"] } +addr2line = "0.21" + +[profile.dev] +opt-level = 3 +debug = true + +[profile.release] +opt-level = 3 +lto = "fat" +debug = true diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs new file mode 100644 index 00000000..4c5641a8 --- /dev/null +++ b/agb-addr2line/src/main.rs @@ -0,0 +1,43 @@ +use std::{fs, path::PathBuf, str::FromStr}; + +use addr2line::object; +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// The filename of the elf file + elf_path: PathBuf, + + /// The output of agb's dump + dump: String, +} + +fn main() -> anyhow::Result<()> { + let cli = Args::parse(); + + let file = fs::read(cli.elf_path)?; + let object = object::File::parse(file.as_slice())?; + + let ctx = addr2line::Context::new(&object)?; + + if let Some(location) = ctx.find_location(parse_address(&cli.dump)?)? { + let file = location.file.unwrap_or("unknown file"); + let line = location + .line + .map(|line| line.to_string()) + .unwrap_or_else(|| "??".to_owned()); + + println!("{file}:{line}"); + } + + Ok(()) +} + +fn parse_address(input: &str) -> Result::Err> { + if let Some(input) = input.strip_prefix("0x") { + u64::from_str_radix(input, 16) + } else { + input.parse() + } +} From 8453b46eab4038daaa17fc36baece9708026158a Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 13:43:08 +0100 Subject: [PATCH 03/35] Prettify the output a little --- agb-addr2line/Cargo.toml | 5 ++++- agb-addr2line/src/main.rs | 43 ++++++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/agb-addr2line/Cargo.toml b/agb-addr2line/Cargo.toml index 82cfffa7..1727ca6d 100644 --- a/agb-addr2line/Cargo.toml +++ b/agb-addr2line/Cargo.toml @@ -10,7 +10,10 @@ repository = "https://github.com/agbrs/agb" [dependencies] anyhow = "1" clap = { version = "4", features = ["derive"] } -addr2line = "0.21" +addr2line = { version = "0.21", default-features = false, features = [ + "rustc-demangle", + "std-object", +] } [profile.dev] opt-level = 3 diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 4c5641a8..85cbebcb 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -13,6 +13,20 @@ struct Args { dump: String, } +struct Location { + filename: String, + line: u32, +} + +impl Default for Location { + fn default() -> Self { + Self { + filename: "??".to_string(), + line: 0, + } + } +} + fn main() -> anyhow::Result<()> { let cli = Args::parse(); @@ -21,14 +35,29 @@ fn main() -> anyhow::Result<()> { let ctx = addr2line::Context::new(&object)?; - if let Some(location) = ctx.find_location(parse_address(&cli.dump)?)? { - let file = location.file.unwrap_or("unknown file"); - let line = location - .line - .map(|line| line.to_string()) - .unwrap_or_else(|| "??".to_owned()); + let mut frames = ctx + .find_frames(parse_address(&cli.dump)?) + .skip_all_loads()?; - println!("{file}:{line}"); + while let Some(frame) = frames.next()? { + let function_name = if let Some(func) = frame.function { + func.demangle()?.into_owned() + } else { + "unknown function".to_string() + }; + + let location = frame + .location + .map(|location| Location { + filename: location.file.unwrap_or("??").to_owned(), + line: location.line.unwrap_or(0), + }) + .unwrap_or_default(); + + println!( + "{}:{} ({})", + location.filename, location.line, function_name + ); } Ok(()) From 3f374f3e9c9b7936806b7e401d0eacf7b070bc8d Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 14:05:22 +0100 Subject: [PATCH 04/35] Print the entire backtrace --- agb-addr2line/src/main.rs | 34 +++++++++++++++++----------------- agb/src/backtrace.rs | 33 ++++++++++++++++++++++++++++----- agb/src/lib.rs | 10 ++++------ 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 85cbebcb..b13a9b6d 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -1,6 +1,6 @@ -use std::{fs, path::PathBuf, str::FromStr}; +use std::{fs, path::PathBuf}; -use addr2line::object; +use addr2line::{gimli, object}; use clap::Parser; #[derive(Parser, Debug)] @@ -35,9 +35,20 @@ fn main() -> anyhow::Result<()> { let ctx = addr2line::Context::new(&object)?; - let mut frames = ctx - .find_frames(parse_address(&cli.dump)?) - .skip_all_loads()?; + for address in cli.dump.split('-') { + let mut address = u64::from_str_radix(address, 16)?; + if address <= 0xFFFF { + address += 0x0800_0000; + } + + print_address(&ctx, address)?; + } + + Ok(()) +} + +fn print_address(ctx: &addr2line::Context, address: u64) -> anyhow::Result<()> { + let mut frames = ctx.find_frames(address).skip_all_loads()?; while let Some(frame) = frames.next()? { let function_name = if let Some(func) = frame.function { @@ -54,19 +65,8 @@ fn main() -> anyhow::Result<()> { }) .unwrap_or_default(); - println!( - "{}:{} ({})", - location.filename, location.line, function_name - ); + println!("{function_name} ({}:{})", location.filename, location.line); } Ok(()) } - -fn parse_address(input: &str) -> Result::Err> { - if let Some(input) = input.strip_prefix("0x") { - u64::from_str_radix(input, 16) - } else { - input.parse() - } -} diff --git a/agb/src/backtrace.rs b/agb/src/backtrace.rs index 6081c45c..530fe845 100644 --- a/agb/src/backtrace.rs +++ b/agb/src/backtrace.rs @@ -9,8 +9,8 @@ struct Context { registers: [u32; 11], } -pub struct Frame { - pub address: u32, +pub struct Frames { + frames: Vec, } #[allow(unused)] @@ -37,7 +37,7 @@ impl Index for Context { } #[inline(never)] -pub(crate) fn unwind_exception() -> Vec { +pub(crate) fn unwind_exception() -> Frames { let mut context = Context::default(); unsafe { @@ -77,10 +77,33 @@ pub(crate) fn unwind_exception() -> Vec { break; } - frames.push(Frame { address: lr }); + frames.push(lr); frame_pointer = sp; } - frames + Frames { 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}")?; + } + + is_first = false; + } + + Ok(()) + } } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 03a29264..a9743390 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -323,12 +323,10 @@ pub mod test_runner { if let Some(mut mgba) = mgba::Mgba::new() { 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!("debug data: {frames}"), + mgba::DebugLevel::Error, + ); let _ = mgba.print(format_args!("Error: {info}"), mgba::DebugLevel::Fatal); } From 726b400463107d343e5c028fbc525bce92e61a92 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 14:10:23 +0100 Subject: [PATCH 05/35] Slightly prettier output --- agb-addr2line/Cargo.toml | 1 + agb-addr2line/src/main.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/agb-addr2line/Cargo.toml b/agb-addr2line/Cargo.toml index 1727ca6d..63130c8b 100644 --- a/agb-addr2line/Cargo.toml +++ b/agb-addr2line/Cargo.toml @@ -14,6 +14,7 @@ addr2line = { version = "0.21", default-features = false, features = [ "rustc-demangle", "std-object", ] } +colored = "2" [profile.dev] opt-level = 3 diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index b13a9b6d..b71985fb 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -2,6 +2,7 @@ use std::{fs, path::PathBuf}; use addr2line::{gimli, object}; use clap::Parser; +use colored::Colorize; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -65,7 +66,12 @@ fn print_address(ctx: &addr2line::Context, address: u64) -> }) .unwrap_or_default(); - println!("{function_name} ({}:{})", location.filename, location.line); + println!( + "{}\n\t{}:{}", + function_name.white(), + location.filename.green(), + location.line.to_string().green() + ); } Ok(()) From fb9159d2cf5f76d1e6113ffc72596adc9d7a9adf Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 14:16:13 +0100 Subject: [PATCH 06/35] Prettify the output furter --- agb-addr2line/src/main.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index b71985fb..8b008502 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -36,21 +36,27 @@ fn main() -> anyhow::Result<()> { let ctx = addr2line::Context::new(&object)?; - for address in cli.dump.split('-') { + 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, address)?; + print_address(&ctx, i, address)?; } Ok(()) } -fn print_address(ctx: &addr2line::Context, address: u64) -> anyhow::Result<()> { +fn print_address( + ctx: &addr2line::Context, + index: usize, + address: u64, +) -> anyhow::Result<()> { let mut frames = ctx.find_frames(address).skip_all_loads()?; + let mut is_first = true; + while let Some(frame) = frames.next()? { let function_name = if let Some(func) = frame.function { func.demangle()?.into_owned() @@ -66,12 +72,19 @@ fn print_address(ctx: &addr2line::Context, address: u64) -> }) .unwrap_or_default(); + if is_first { + println!("{index}:\t{}", function_name.bold()); + } else { + println!("\t(inlined by) {function_name}"); + } + println!( - "{}\n\t{}:{}", - function_name.white(), + "\t{}:{}", location.filename.green(), location.line.to_string().green() ); + + is_first = false; } Ok(()) From ac49855937452d9bc71f8f630e25a948ac5ba6fc Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 14:22:41 +0100 Subject: [PATCH 07/35] Prettify the path --- agb-addr2line/src/main.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 8b008502..6fbbae7f 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -1,4 +1,4 @@ -use std::{fs, path::PathBuf}; +use std::{borrow::Cow, fs, path::PathBuf}; use addr2line::{gimli, object}; use clap::Parser; @@ -73,14 +73,14 @@ fn print_address( .unwrap_or_default(); if is_first { - println!("{index}:\t{}", function_name.bold()); + print!("{index}:\t{}", function_name.bold()); } else { - println!("\t(inlined by) {function_name}"); + print!("\t(inlined by) {function_name}"); } println!( - "\t{}:{}", - location.filename.green(), + " {}:{}", + prettify_path(&location.filename).green(), location.line.to_string().green() ); @@ -89,3 +89,16 @@ fn print_address( Ok(()) } + +fn prettify_path(path: &str) -> Cow<'_, str> { + if let Some(src_index) = path.rfind("/src/") { + let crate_name_start = path[0..src_index].rfind('/'); + let crate_name = crate_name_start + .map(|crate_name_start| &path[crate_name_start + 1..src_index]) + .unwrap_or(""); + + Cow::Owned(format!("<{crate_name}>/{}", &path[src_index + 5..])) + } else { + Cow::Borrowed(path) + } +} From d729591c0e6db7842aae3f04ac6b567c46653b31 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 16:30:36 +0100 Subject: [PATCH 08/35] Include the source code too --- agb-addr2line/src/main.rs | 59 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 6fbbae7f..e91946bd 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -1,4 +1,9 @@ -use std::{borrow::Cow, fs, path::PathBuf}; +use std::{ + borrow::Cow, + fs::{self, File}, + io::Read, + path::PathBuf, +}; use addr2line::{gimli, object}; use clap::Parser; @@ -12,11 +17,16 @@ struct Args { /// The output of agb's dump dump: String, + + /// Whether the actual code should be shown + #[arg(short, long)] + code: bool, } struct Location { filename: String, line: u32, + col: u32, } impl Default for Location { @@ -24,6 +34,7 @@ impl Default for Location { Self { filename: "??".to_string(), line: 0, + col: 0, } } } @@ -42,7 +53,7 @@ fn main() -> anyhow::Result<()> { address += 0x0800_0000; } - print_address(&ctx, i, address)?; + print_address(&ctx, i, address, cli.code)?; } Ok(()) @@ -52,13 +63,14 @@ fn print_address( ctx: &addr2line::Context, index: usize, address: u64, + include_code: bool, ) -> anyhow::Result<()> { let mut frames = ctx.find_frames(address).skip_all_loads()?; let mut is_first = true; while let Some(frame) = frames.next()? { - let function_name = if let Some(func) = frame.function { + let function_name = if let Some(ref func) = frame.function { func.demangle()?.into_owned() } else { "unknown function".to_string() @@ -66,9 +78,11 @@ fn print_address( let location = frame .location + .as_ref() .map(|location| Location { filename: location.file.unwrap_or("??").to_owned(), line: location.line.unwrap_or(0), + col: location.column.unwrap_or(0), }) .unwrap_or_default(); @@ -84,12 +98,51 @@ fn print_address( location.line.to_string().green() ); + if include_code && location.line != 0 { + print_line_of_code(&frame, location)?; + } + is_first = false; } Ok(()) } +fn print_line_of_code( + frame: &addr2line::Frame<'_, impl gimli::Reader>, + location: Location, +) -> anyhow::Result<()> { + let Some(filename) = frame.location.as_ref().and_then(|location| location.file) else { + return Ok(()); + }; + + let Ok(mut file) = File::open(filename) else { + return Ok(()); + }; + + let mut content = String::new(); + file.read_to_string(&mut content)?; + + let Some(line_of_code) = content.split('\n').nth(location.line as usize - 1) else { + eprintln!("File {filename} does not have line {}", location.line); + return Ok(()); + }; + + let trimmed = line_of_code.trim_start(); + let trimmed_len = line_of_code.len() - trimmed.len(); + println!("\t\t{}", trimmed); + + if location.col != 0 { + println!( + "\t\t{}{}", + " ".repeat(location.col as usize - trimmed_len - 1), + "^".bright_blue() + ); + } + + Ok(()) +} + fn prettify_path(path: &str) -> Cow<'_, str> { if let Some(src_index) = path.rfind("/src/") { let crate_name_start = path[0..src_index].rfind('/'); From 087228d9468d349c25230a7fa73c554ce45b20d9 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 16:36:05 +0100 Subject: [PATCH 09/35] Don't show as much information for uninteresting functions --- agb-addr2line/src/main.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index e91946bd..b23cae53 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -86,10 +86,17 @@ fn print_address( }) .unwrap_or_default(); - if is_first { - print!("{index}:\t{}", function_name.bold()); + let is_interesting = is_interesting_function(&function_name, &location.filename); + let function_name_to_print = if is_interesting { + function_name.bold() } else { - print!("\t(inlined by) {function_name}"); + function_name.normal() + }; + + if is_first { + print!("{index}:\t{function_name_to_print}"); + } else { + print!("\t(inlined by) {function_name_to_print}"); } println!( @@ -98,7 +105,7 @@ fn print_address( location.line.to_string().green() ); - if include_code && location.line != 0 { + if include_code && location.line != 0 && is_interesting { print_line_of_code(&frame, location)?; } @@ -155,3 +162,15 @@ fn prettify_path(path: &str) -> Cow<'_, str> { Cow::Borrowed(path) } } + +fn is_interesting_function(function_name: &str, path: &str) -> bool { + if function_name == "rust_begin_unwind" { + return false; // this is the unwind exception call + } + + if path.ends_with("panicking.rs") { + return false; // probably part of rust's internal panic mechanisms + } + + true +} From 4b0622f6fba5190e77d1a0d26af32a22eb85261d Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 16:37:06 +0100 Subject: [PATCH 10/35] Always include the code --- agb-addr2line/src/main.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index b23cae53..6dec8cf8 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -17,10 +17,6 @@ struct Args { /// The output of agb's dump dump: String, - - /// Whether the actual code should be shown - #[arg(short, long)] - code: bool, } struct Location { @@ -53,7 +49,7 @@ fn main() -> anyhow::Result<()> { address += 0x0800_0000; } - print_address(&ctx, i, address, cli.code)?; + print_address(&ctx, i, address)?; } Ok(()) @@ -63,7 +59,6 @@ fn print_address( ctx: &addr2line::Context, index: usize, address: u64, - include_code: bool, ) -> anyhow::Result<()> { let mut frames = ctx.find_frames(address).skip_all_loads()?; @@ -105,7 +100,7 @@ fn print_address( location.line.to_string().green() ); - if include_code && location.line != 0 && is_interesting { + if location.line != 0 && is_interesting { print_line_of_code(&frame, location)?; } From fc6e1d5e092f99f18ebe8e013fe2a823a15c91b9 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 16:41:05 +0100 Subject: [PATCH 11/35] Warn if the file was modified more recently than the elf file --- agb-addr2line/src/main.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 6dec8cf8..9720291f 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -3,6 +3,7 @@ use std::{ fs::{self, File}, io::Read, path::PathBuf, + time::SystemTime, }; use addr2line::{gimli, object}; @@ -38,7 +39,11 @@ impl Default for Location { fn main() -> anyhow::Result<()> { let cli = Args::parse(); - let file = fs::read(cli.elf_path)?; + let modification_time = fs::metadata(&cli.elf_path)? + .modified() + .unwrap_or(SystemTime::UNIX_EPOCH); + + let file = fs::read(&cli.elf_path)?; let object = object::File::parse(file.as_slice())?; let ctx = addr2line::Context::new(&object)?; @@ -49,7 +54,7 @@ fn main() -> anyhow::Result<()> { address += 0x0800_0000; } - print_address(&ctx, i, address)?; + print_address(&ctx, i, address, modification_time)?; } Ok(()) @@ -59,6 +64,7 @@ fn print_address( ctx: &addr2line::Context, index: usize, address: u64, + elf_modification_time: SystemTime, ) -> anyhow::Result<()> { let mut frames = ctx.find_frames(address).skip_all_loads()?; @@ -101,7 +107,7 @@ fn print_address( ); if location.line != 0 && is_interesting { - print_line_of_code(&frame, location)?; + print_line_of_code(&frame, location, elf_modification_time)?; } is_first = false; @@ -113,6 +119,7 @@ fn print_address( fn print_line_of_code( frame: &addr2line::Frame<'_, impl gimli::Reader>, location: Location, + elf_modification_time: SystemTime, ) -> anyhow::Result<()> { let Some(filename) = frame.location.as_ref().and_then(|location| location.file) else { return Ok(()); @@ -122,6 +129,14 @@ fn print_line_of_code( return Ok(()); }; + let modification_time = fs::metadata(filename)? + .modified() + .unwrap_or(SystemTime::UNIX_EPOCH); + + if modification_time > elf_modification_time { + eprintln!("Warning: File {filename} modified more recently than the binary, line info may be incorrect"); + } + let mut content = String::new(); file.read_to_string(&mut content)?; From 07a68573546a39989043f77196dba61d11fbc264 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 16:56:18 +0100 Subject: [PATCH 12/35] into feels better --- agb-addr2line/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 9720291f..44cd7f9e 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -97,7 +97,7 @@ fn print_address( if is_first { print!("{index}:\t{function_name_to_print}"); } else { - print!("\t(inlined by) {function_name_to_print}"); + print!("\t(inlined into) {function_name_to_print}"); } println!( From 71bdb085bec34b876a06bf28a3eea5bfefa8a9f2 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Mon, 1 Apr 2024 17:02:59 +0100 Subject: [PATCH 13/35] Include the new option in all the config.tomls --- book/games/pong/.cargo/config.toml | 12 ++++++++++-- examples/amplitude/.cargo/config.toml | 12 ++++++++++-- examples/combo/.cargo/config.toml | 12 ++++++++++-- examples/hyperspace-roll/.cargo/config.toml | 12 ++++++++++-- .../the-dungeon-puzzlers-lament/.cargo/config.toml | 12 ++++++++++-- .../the-hat-chooses-the-wizard/.cargo/config.toml | 12 ++++++++++-- examples/the-purple-night/.cargo/config.toml | 12 ++++++++++-- template/.cargo/config.toml | 12 ++++++++++-- tracker/agb-tracker/.cargo/config.toml | 12 ++++++++++-- 9 files changed, 90 insertions(+), 18 deletions(-) diff --git a/book/games/pong/.cargo/config.toml b/book/games/pong/.cargo/config.toml index b3276236..45ce46eb 100644 --- a/book/games/pong/.cargo/config.toml +++ b/book/games/pong/.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-qt" [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-qt" diff --git a/examples/amplitude/.cargo/config.toml b/examples/amplitude/.cargo/config.toml index b3276236..45ce46eb 100644 --- a/examples/amplitude/.cargo/config.toml +++ b/examples/amplitude/.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-qt" [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-qt" diff --git a/examples/combo/.cargo/config.toml b/examples/combo/.cargo/config.toml index b3276236..45ce46eb 100644 --- a/examples/combo/.cargo/config.toml +++ b/examples/combo/.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-qt" [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-qt" diff --git a/examples/hyperspace-roll/.cargo/config.toml b/examples/hyperspace-roll/.cargo/config.toml index b3276236..45ce46eb 100644 --- a/examples/hyperspace-roll/.cargo/config.toml +++ b/examples/hyperspace-roll/.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-qt" [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-qt" diff --git a/examples/the-dungeon-puzzlers-lament/.cargo/config.toml b/examples/the-dungeon-puzzlers-lament/.cargo/config.toml index b3276236..45ce46eb 100644 --- a/examples/the-dungeon-puzzlers-lament/.cargo/config.toml +++ b/examples/the-dungeon-puzzlers-lament/.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-qt" [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-qt" diff --git a/examples/the-hat-chooses-the-wizard/.cargo/config.toml b/examples/the-hat-chooses-the-wizard/.cargo/config.toml index b3276236..45ce46eb 100644 --- a/examples/the-hat-chooses-the-wizard/.cargo/config.toml +++ b/examples/the-hat-chooses-the-wizard/.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-qt" [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-qt" diff --git a/examples/the-purple-night/.cargo/config.toml b/examples/the-purple-night/.cargo/config.toml index b3276236..45ce46eb 100644 --- a/examples/the-purple-night/.cargo/config.toml +++ b/examples/the-purple-night/.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-qt" [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-qt" diff --git a/template/.cargo/config.toml b/template/.cargo/config.toml index 655e5a26..67382136 100644 --- a/template/.cargo/config.toml +++ b/template/.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-qt", "-C", "logToStdout=1", "-C", "logLevel.gba.debug=127"] [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-qt", "-C", "logToStdout=1", "-C", "logLevel.gba.debug=127"] diff --git a/tracker/agb-tracker/.cargo/config.toml b/tracker/agb-tracker/.cargo/config.toml index 62ebedb7..ce415d4a 100644 --- a/tracker/agb-tracker/.cargo/config.toml +++ b/tracker/agb-tracker/.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" From e36145552f820ef4f6e3785bf2d5386b5d2560bb Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Tue, 2 Apr 2024 22:24:58 +0100 Subject: [PATCH 14/35] gwilym_encode the stack trace and generate a qr code for debugging --- agb-addr2line/src/main.rs | 78 +++++++++++++++++++++++++++++++++++---- agb/Cargo.toml | 1 + agb/src/backtrace.rs | 48 ++++++++++++++++++------ agb/src/dma.rs | 2 +- agb/src/lib.rs | 7 ++-- agb/src/panics_render.rs | 70 +++++++++++++++++++++++++++++++++++ 6 files changed, 183 insertions(+), 23 deletions(-) create mode 100644 agb/src/panics_render.rs 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); + } + } +} From 6c9c23f79ea3d729e269e65b456a27d07fa04acd Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Tue, 2 Apr 2024 22:37:23 +0100 Subject: [PATCH 15/35] Change what's being printed slightly --- agb/src/lib.rs | 5 ----- agb/src/panics_render.rs | 11 +++++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index dcb3078f..0febf2b5 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -325,11 +325,6 @@ pub mod test_runner { if let Some(mut mgba) = mgba::Mgba::new() { let _ = mgba.print(format_args!("[failed]"), mgba::DebugLevel::Error); - - let _ = mgba.print( - format_args!("debug data: {frames}"), - mgba::DebugLevel::Error, - ); } render_backtrace(&frames, info); diff --git a/agb/src/panics_render.rs b/agb/src/panics_render.rs index ab9433f1..6bda8b38 100644 --- a/agb/src/panics_render.rs +++ b/agb/src/panics_render.rs @@ -16,7 +16,11 @@ pub fn render_backtrace(trace: &backtrace::Frames, info: &PanicInfo) -> ! { let mut gba = unsafe { crate::Gba::new_in_entry() }; gba.dma.dma().dma3.disable(); - draw_qr_code(&mut gba, trace); + + let qrcode_string_data = format!("https://agbrs.dev/crash#v1-{trace}"); + crate::println!("Stack trace: {qrcode_string_data}"); + + draw_qr_code(&mut gba, &qrcode_string_data); busy_wait_for_vblank(); @@ -31,17 +35,16 @@ pub fn render_backtrace(trace: &backtrace::Frames, info: &PanicInfo) -> ! { }) } -fn draw_qr_code(gba: &mut crate::Gba, trace: &backtrace::Frames) { +fn draw_qr_code(gba: &mut crate::Gba, qrcode_string_data: &str) { 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, + qrcode_string_data, &mut temp_buffer, &mut out_buffer, qrcodegen_no_heap::QrCodeEcc::Medium, From e5a8b399247b2b55d80c3c284deeae0361c7bc04 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Tue, 2 Apr 2024 23:19:09 +0100 Subject: [PATCH 16/35] Print some extra useful information --- agb-addr2line/src/main.rs | 16 ++- agb/fnt/OFL.txt | 94 +++++++++++++++++ agb/fnt/ark-pixel-10px-proportional-latin.ttf | Bin 0 -> 349400 bytes agb/src/backtrace.rs | 4 +- agb/src/panics_render.rs | 30 ++++-- agb/src/panics_render/text.rs | 97 ++++++++++++++++++ 6 files changed, 226 insertions(+), 15 deletions(-) create mode 100644 agb/fnt/OFL.txt create mode 100644 agb/fnt/ark-pixel-10px-proportional-latin.ttf create mode 100644 agb/src/panics_render/text.rs diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 83e82a28..cd667cf0 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -48,7 +48,7 @@ fn main() -> anyhow::Result<()> { let ctx = addr2line::Context::new(&object)?; - for (i, address) in gwilym_encoding::decode(&cli.dump).into_iter().enumerate() { + for (i, address) in gwilym_encoding::decode(&cli.dump)?.into_iter().enumerate() { print_address(&ctx, i, address.into(), modification_time)?; } @@ -183,7 +183,7 @@ fn is_interesting_function(function_name: &str, path: &str) -> bool { mod gwilym_encoding { use std::sync::OnceLock; - const ALPHABET: &[u8] = b"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; + const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; // pub fn encode_16(input: u16) -> [u8; 3] { // let input = input as usize; @@ -207,7 +207,15 @@ mod gwilym_encoding { // ] // } - pub fn decode(input: &str) -> Vec { + pub fn decode(input: &str) -> anyhow::Result> { + let Some((input, version)) = input.rsplit_once('v') else { + anyhow::bail!("Does not contain version"); + }; + + if version != "1" { + anyhow::bail!("Only version 1 is supported"); + } + let mut result = vec![]; let mut previous_value = None; @@ -224,7 +232,7 @@ mod gwilym_encoding { } } - result + Ok(result) } fn decode_chunk(chunk: &[u8]) -> u32 { diff --git a/agb/fnt/OFL.txt b/agb/fnt/OFL.txt new file mode 100644 index 00000000..be1dc1f3 --- /dev/null +++ b/agb/fnt/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2021, TakWolf (https://takwolf.com), +with Reserved Font Name 'Ark Pixel'. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/agb/fnt/ark-pixel-10px-proportional-latin.ttf b/agb/fnt/ark-pixel-10px-proportional-latin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b1249428afcb33613d5a7d7d6521530e73825db8 GIT binary patch literal 349400 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%0Dlt5(fiHDy(sRjpPf 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

TV6Urd!Lc?bdPYy7k=p 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 literal 0 HcmV?d00001 diff --git a/agb/src/backtrace.rs b/agb/src/backtrace.rs index aba7427c..1e3ea241 100644 --- a/agb/src/backtrace.rs +++ b/agb/src/backtrace.rs @@ -102,12 +102,12 @@ impl core::fmt::Display for Frames { } } - Ok(()) + write!(f, "v1") } } mod gwilym_encoding { - const ALPHABET: &[u8] = b"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; + const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; pub fn encode_16(input: u16) -> [u8; 3] { let input = input as usize; diff --git a/agb/src/panics_render.rs b/agb/src/panics_render.rs index 6bda8b38..f6bc4d7b 100644 --- a/agb/src/panics_render.rs +++ b/agb/src/panics_render.rs @@ -1,14 +1,16 @@ -use core::panic::PanicInfo; +use core::{fmt::Write, panic::PanicInfo}; use alloc::{format, vec}; use crate::{ backtrace, - display::{busy_wait_for_vblank, HEIGHT, WIDTH}, + display::{bitmap3::Bitmap3, busy_wait_for_vblank, HEIGHT, WIDTH}, dma::dma3_exclusive, interrupt, mgba, syscall, }; +mod text; + pub fn render_backtrace(trace: &backtrace::Frames, info: &PanicInfo) -> ! { interrupt::free(|_cs| { dma3_exclusive(|| { @@ -16,11 +18,20 @@ pub fn render_backtrace(trace: &backtrace::Frames, info: &PanicInfo) -> ! { let mut gba = unsafe { crate::Gba::new_in_entry() }; gba.dma.dma().dma3.disable(); + let mut gfx = gba.display.video.bitmap3(); - let qrcode_string_data = format!("https://agbrs.dev/crash#v1-{trace}"); + let qrcode_string_data = format!("https://agbrs.dev/crash#{trace}"); crate::println!("Stack trace: {qrcode_string_data}"); - draw_qr_code(&mut gba, &qrcode_string_data); + let location = draw_qr_code(&mut gfx, &qrcode_string_data); + + let mut trace_text_render = + text::BitmapTextRender::new(&mut gfx, (location, 8).into(), 0x0000); + let _ = write!(&mut trace_text_render, "https://agbrs.dev/crash\n{trace}"); + + let mut panic_text_render = + text::BitmapTextRender::new(&mut gfx, (8, location).into(), 0x0000); + let _ = write!(&mut panic_text_render, "{info}"); busy_wait_for_vblank(); @@ -35,9 +46,8 @@ pub fn render_backtrace(trace: &backtrace::Frames, info: &PanicInfo) -> ! { }) } -fn draw_qr_code(gba: &mut crate::Gba, qrcode_string_data: &str) { - let mut gfx = gba.display.video.bitmap3(); - +/// Returns the width / height of the QR code + padding in pixels +fn draw_qr_code(gfx: &mut Bitmap3<'_>, qrcode_string_data: &str) -> i32 { const MAX_VERSION: qrcodegen_no_heap::Version = qrcodegen_no_heap::Version::new(6); let mut temp_buffer = vec![0; MAX_VERSION.buffer_len()]; @@ -50,13 +60,13 @@ fn draw_qr_code(gba: &mut crate::Gba, qrcode_string_data: &str) { qrcodegen_no_heap::QrCodeEcc::Medium, qrcodegen_no_heap::Version::MIN, MAX_VERSION, - Some(qrcodegen_no_heap::Mask::new(0)), + None, true, ) { Ok(qr_code) => qr_code, Err(e) => { crate::println!("Error generating qr code: {e:?}"); - return; + return 8; } }; @@ -70,4 +80,6 @@ fn draw_qr_code(gba: &mut crate::Gba, qrcode_string_data: &str) { gfx.draw_point(x, y, colour); } } + + qr_code.size() * 2 + 8 * 2 } diff --git a/agb/src/panics_render/text.rs b/agb/src/panics_render/text.rs new file mode 100644 index 00000000..34eadcaf --- /dev/null +++ b/agb/src/panics_render/text.rs @@ -0,0 +1,97 @@ +use core::fmt::Write; + +use crate::{ + display::{bitmap3::Bitmap3, Font, HEIGHT, WIDTH}, + fixnum::Vector2D, + include_font, +}; + +static FONT: Font = include_font!("fnt/ark-pixel-10px-proportional-latin.ttf", 10); + +pub struct BitmapTextRender<'bitmap, 'gba> { + head_position: Vector2D, + start_x: i32, + bitmap: &'bitmap mut Bitmap3<'gba>, + colour: u16, + previous_char: Option, +} + +impl<'bitmap, 'gba> BitmapTextRender<'bitmap, 'gba> { + pub fn new( + bitmap: &'bitmap mut Bitmap3<'gba>, + position: Vector2D, + start_colour: u16, + ) -> Self { + Self { + head_position: position, + start_x: position.x, + bitmap, + colour: start_colour, + previous_char: None, + } + } + + fn render_letter(&mut self, c: char) { + let letter = FONT.letter(c); + + self.head_position.x += letter.xmin as i32 + + self + .previous_char + .take() + .map_or(0, |c| letter.kerning_amount(c)); + self.previous_char = Some(c); + + if self.head_position.x + letter.width as i32 >= WIDTH { + self.newline(); + } + + if self.head_position.y + letter.height as i32 >= HEIGHT { + return; + } + + let y_position_start = + self.head_position.y + FONT.ascent() - letter.height as i32 - letter.ymin as i32; + + for y in 0..letter.height as usize { + for x in 0..letter.width as usize { + let rendered = letter.bit_absolute(x, y); + if rendered { + self.bitmap.draw_point( + x as i32 + self.head_position.x, + y as i32 + y_position_start, + self.colour, + ); + } + } + } + + self.head_position.x += letter.advance_width as i32; + } + + fn render_char(&mut self, c: char) { + match c { + '\n' => { + self.newline(); + } + ' ' => { + self.head_position.x += FONT.letter(' ').advance_width as i32; + } + letter => self.render_letter(letter), + } + } + + fn newline(&mut self) { + self.head_position.x = self.start_x; + self.head_position.y += FONT.line_height(); + } +} + +impl<'bitmap, 'gba> Write for BitmapTextRender<'bitmap, 'gba> { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + for c in s.chars() { + self.render_char(c); + } + + Ok(()) + } +} From e0d68eec15e757ca92f0a2df9d2c97e1b271434c Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Tue, 2 Apr 2024 23:31:02 +0100 Subject: [PATCH 17/35] Correctly render in release mode --- agb/src/panics_render.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agb/src/panics_render.rs b/agb/src/panics_render.rs index f6bc4d7b..f1970ae5 100644 --- a/agb/src/panics_render.rs +++ b/agb/src/panics_render.rs @@ -33,6 +33,8 @@ pub fn render_backtrace(trace: &backtrace::Frames, info: &PanicInfo) -> ! { text::BitmapTextRender::new(&mut gfx, (8, location).into(), 0x0000); let _ = write!(&mut panic_text_render, "{info}"); + // need to wait 2 frames to ensure that mgba finishes rendering before the fatal call below + busy_wait_for_vblank(); busy_wait_for_vblank(); if let Some(mut mgba) = mgba::Mgba::new() { From 51de2ffa6010c9e699de9733b84898c586f041a6 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Tue, 2 Apr 2024 23:37:59 +0100 Subject: [PATCH 18/35] Don't need to import this macro --- agb/src/panics_render/text.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/agb/src/panics_render/text.rs b/agb/src/panics_render/text.rs index 34eadcaf..d817a06d 100644 --- a/agb/src/panics_render/text.rs +++ b/agb/src/panics_render/text.rs @@ -3,7 +3,6 @@ use core::fmt::Write; use crate::{ display::{bitmap3::Bitmap3, Font, HEIGHT, WIDTH}, fixnum::Vector2D, - include_font, }; static FONT: Font = include_font!("fnt/ark-pixel-10px-proportional-latin.ttf", 10); From 219fc1e0bc446c1bf4c9c18a9d4a605b7e4adfc4 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 10:35:05 +0100 Subject: [PATCH 19/35] Extract gwilym_encoding into its own file --- agb-addr2line/src/gwilym_encoding.rs | 91 ++++++++++++++++++++++++++++ agb-addr2line/src/main.rs | 81 +------------------------ 2 files changed, 94 insertions(+), 78 deletions(-) create mode 100644 agb-addr2line/src/gwilym_encoding.rs diff --git a/agb-addr2line/src/gwilym_encoding.rs b/agb-addr2line/src/gwilym_encoding.rs new file mode 100644 index 00000000..ae006582 --- /dev/null +++ b/agb-addr2line/src/gwilym_encoding.rs @@ -0,0 +1,91 @@ +use std::{slice::ChunksExact, sync::OnceLock}; + +const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_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 gwilym_decode(input: &str) -> anyhow::Result> { + GwilymDecodeIter::new(input) +} + +pub struct GwilymDecodeIter<'a> { + chunks: ChunksExact<'a, u8>, +} + +impl<'a> GwilymDecodeIter<'a> { + fn new(input: &'a str) -> anyhow::Result { + let Some((input, version)) = input.rsplit_once('v') else { + anyhow::bail!("Does not contain version"); + }; + + if version != "1" { + anyhow::bail!("Only version 1 is supported"); + } + + if input.len() % 3 != 0 { + anyhow::bail!("Input string must have length a multiple of 3"); + } + + Ok(Self { + chunks: input.as_bytes().chunks_exact(3), + }) + } +} + +impl<'a> Iterator for GwilymDecodeIter<'a> { + type Item = u32; + + fn next(&mut self) -> Option { + let Some(chunk) = self.chunks.next() else { + return None; + }; + + let value = decode_chunk(chunk); + if value & (1 << 17) != 0 { + return Some(self.next().unwrap_or(0) | (value << 16)); + } + + Some(value | 0x0800_0000) + } +} + +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-addr2line/src/main.rs b/agb-addr2line/src/main.rs index cd667cf0..35f5bc3b 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -10,6 +10,8 @@ use addr2line::{gimli, object}; use clap::Parser; use colored::Colorize; +mod gwilym_encoding; + #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { @@ -48,7 +50,7 @@ fn main() -> anyhow::Result<()> { let ctx = addr2line::Context::new(&object)?; - for (i, address) in gwilym_encoding::decode(&cli.dump)?.into_iter().enumerate() { + for (i, address) in gwilym_encoding::gwilym_decode(&cli.dump)?.enumerate() { print_address(&ctx, i, address.into(), modification_time)?; } @@ -179,80 +181,3 @@ fn is_interesting_function(function_name: &str, path: &str) -> bool { true } - -mod gwilym_encoding { - use std::sync::OnceLock; - - const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_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) -> anyhow::Result> { - let Some((input, version)) = input.rsplit_once('v') else { - anyhow::bail!("Does not contain version"); - }; - - if version != "1" { - anyhow::bail!("Only version 1 is supported"); - } - - 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); - } - } - - Ok(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 - } -} From e21d06994f7c750c7fe7448b28408076eb9cf4ce Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 10:57:34 +0100 Subject: [PATCH 20/35] Add some tests which showed issues with the decoder --- agb-addr2line/src/gwilym_encoding.rs | 77 +++++++++++++++++++++++++++- agb/src/backtrace.rs | 15 +++--- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/agb-addr2line/src/gwilym_encoding.rs b/agb-addr2line/src/gwilym_encoding.rs index ae006582..fca9896c 100644 --- a/agb-addr2line/src/gwilym_encoding.rs +++ b/agb-addr2line/src/gwilym_encoding.rs @@ -61,8 +61,11 @@ impl<'a> Iterator for GwilymDecodeIter<'a> { }; let value = decode_chunk(chunk); - if value & (1 << 17) != 0 { - return Some(self.next().unwrap_or(0) | (value << 16)); + if value & (1 << 16) != 0 { + let upper_bits = value << 16; + let lower_bits = self.next().unwrap_or(0) & 0xffff; + + return Some(upper_bits | lower_bits); } Some(value | 0x0800_0000) @@ -89,3 +92,73 @@ fn get_value_for_char(input: u8) -> u32 { result })[input as usize] as u32 } + +#[cfg(test)] +mod test { + use super::{gwilym_decode, ALPHABET}; + use std::fmt::Write; + + #[test] + fn should_correctly_decode_16s() -> anyhow::Result<()> { + assert_eq!( + &gwilym_decode("2QI65Q69306Kv1")?.collect::>(), + &[0x0800_16d3, 0x0800_315b, 0x0800_3243, 0x0800_0195] + ); + + Ok(()) + } + + fn encode_16(input: u16) -> [u8; 3] { + let input = input as usize; + [ + ALPHABET[input >> (16 - 5)], + ALPHABET[(input >> (16 - 10)) & 0b11111], + ALPHABET[input & 0b111111], + ] + } + + fn encode_32(input: u32) -> [u8; 6] { + let input = input as usize; + let output_lower_16 = encode_16(input as u16); + let input_upper_16 = input >> 16; + [ + ALPHABET[(input_upper_16 >> (16 - 5)) | (1 << 5)], + ALPHABET[(input_upper_16 >> (16 - 10)) & 0b11111], + ALPHABET[input_upper_16 & 0b111111], + output_lower_16[0], + output_lower_16[1], + output_lower_16[2], + ] + } + + #[test] + fn should_correctly_decode_16s_and_32s() -> anyhow::Result<()> { + let trace: &[u32] = &[ + 0x0300_2990, + 0x0800_3289, + 0x0500_2993, + 0x3829_2910, + 0xffff_ffff, + 0x0000_0000, + ]; + + let mut result = String::new(); + for &ip in trace { + if ip & 0xFFFF_0000 == 0x0800_0000 { + let encoded = encode_16(ip as u16); + let encoded_s = std::str::from_utf8(&encoded)?; + write!(&mut result, "{encoded_s}")? + } else { + let encoded = encode_32(ip); + let encoded_s = std::str::from_utf8(&encoded)?; + write!(&mut result, "{encoded_s}")? + } + } + + write!(&mut result, "v1")?; + + assert_eq!(&gwilym_decode(&result)?.collect::>(), trace); + + Ok(()) + } +} diff --git a/agb/src/backtrace.rs b/agb/src/backtrace.rs index 1e3ea241..f65e55e6 100644 --- a/agb/src/backtrace.rs +++ b/agb/src/backtrace.rs @@ -120,14 +120,15 @@ mod gwilym_encoding { pub fn encode_32(input: u32) -> [u8; 6] { let input = input as usize; - let output_16 = encode_16(input as u16); + let output_lower_16 = encode_16(input as u16); + let input_upper_16 = input >> 16; [ - ALPHABET[(input >> (32 - 5)) | 0b100000], - ALPHABET[(input >> (32 - 10)) & 0b11111], - ALPHABET[(input >> (32 - 16)) & 0b111111], - output_16[0], - output_16[1], - output_16[2], + ALPHABET[(input_upper_16 >> (16 - 5)) | (1 << 5)], + ALPHABET[(input_upper_16 >> (16 - 10)) & 0b11111], + ALPHABET[input_upper_16 & 0b111111], + output_lower_16[0], + output_lower_16[1], + output_lower_16[2], ] } } From fe787117362cec4ee748a83046f2016b9b364035 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 10:58:25 +0100 Subject: [PATCH 21/35] Alphabet should be static --- agb/src/backtrace.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/src/backtrace.rs b/agb/src/backtrace.rs index f65e55e6..0673761b 100644 --- a/agb/src/backtrace.rs +++ b/agb/src/backtrace.rs @@ -107,7 +107,7 @@ impl core::fmt::Display for Frames { } mod gwilym_encoding { - const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; + static ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; pub fn encode_16(input: u16) -> [u8; 3] { let input = input as usize; From f633b04d3c5ce139ba182ab42140bd2e221f7f83 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 11:08:31 +0100 Subject: [PATCH 22/35] Allow passing the URL too since that's easier to copy-paste --- agb-addr2line/src/gwilym_encoding.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/agb-addr2line/src/gwilym_encoding.rs b/agb-addr2line/src/gwilym_encoding.rs index fca9896c..16c83878 100644 --- a/agb-addr2line/src/gwilym_encoding.rs +++ b/agb-addr2line/src/gwilym_encoding.rs @@ -34,6 +34,10 @@ pub struct GwilymDecodeIter<'a> { impl<'a> GwilymDecodeIter<'a> { fn new(input: &'a str) -> anyhow::Result { + let input = input + .strip_prefix("https://agbrs.dev/crash#") + .unwrap_or(input); + let Some((input, version)) = input.rsplit_once('v') else { anyhow::bail!("Does not contain version"); }; @@ -161,4 +165,14 @@ mod test { Ok(()) } + + #[test] + fn should_strip_the_agbrsdev_prefix() -> anyhow::Result<()> { + assert_eq!( + &gwilym_decode("https://agbrs.dev/crash#2QI65Q69306Kv1")?.collect::>(), + &[0x0800_16d3, 0x0800_315b, 0x0800_3243, 0x0800_0195] + ); + + Ok(()) + } } From 5e298cb582e138fe10877f0ee4e6e85826c0dab5 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 11:10:55 +0100 Subject: [PATCH 23/35] Don't need the commented out encoding any more since it is in the test --- agb-addr2line/src/gwilym_encoding.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/agb-addr2line/src/gwilym_encoding.rs b/agb-addr2line/src/gwilym_encoding.rs index 16c83878..f6f643ec 100644 --- a/agb-addr2line/src/gwilym_encoding.rs +++ b/agb-addr2line/src/gwilym_encoding.rs @@ -2,28 +2,6 @@ use std::{slice::ChunksExact, sync::OnceLock}; const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_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 gwilym_decode(input: &str) -> anyhow::Result> { GwilymDecodeIter::new(input) } From 37ecd48a40104059040e383ce2def224d8d496a6 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 11:16:34 +0100 Subject: [PATCH 24/35] Add a message about the fact that the game crashed --- agb/src/panics_render.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/agb/src/panics_render.rs b/agb/src/panics_render.rs index f1970ae5..e8edd5ca 100644 --- a/agb/src/panics_render.rs +++ b/agb/src/panics_render.rs @@ -27,7 +27,10 @@ pub fn render_backtrace(trace: &backtrace::Frames, info: &PanicInfo) -> ! { let mut trace_text_render = text::BitmapTextRender::new(&mut gfx, (location, 8).into(), 0x0000); - let _ = write!(&mut trace_text_render, "https://agbrs.dev/crash\n{trace}"); + let _ = write!( + &mut trace_text_render, + "The game crashed :(\nhttps://agbrs.dev/crash\n{trace}" + ); let mut panic_text_render = text::BitmapTextRender::new(&mut gfx, (8, location).into(), 0x0000); From 4144a2be0ad493ba08a76e3c687546288e576996 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 11:16:44 +0100 Subject: [PATCH 25/35] Use the ? operator instead --- agb-addr2line/src/gwilym_encoding.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/agb-addr2line/src/gwilym_encoding.rs b/agb-addr2line/src/gwilym_encoding.rs index f6f643ec..1ce957f5 100644 --- a/agb-addr2line/src/gwilym_encoding.rs +++ b/agb-addr2line/src/gwilym_encoding.rs @@ -38,9 +38,7 @@ impl<'a> Iterator for GwilymDecodeIter<'a> { type Item = u32; fn next(&mut self) -> Option { - let Some(chunk) = self.chunks.next() else { - return None; - }; + let chunk = self.chunks.next()?; let value = decode_chunk(chunk); if value & (1 << 16) != 0 { From 2475b5676a126fdffaee05919c536baf4f69bca1 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 13:33:50 +0100 Subject: [PATCH 26/35] Include the debug info in the rom (optionally) --- agb-gbafix/Cargo.lock | 105 +++++++++++++++++++++++++++++++++++++++++ agb-gbafix/Cargo.toml | 1 + agb-gbafix/src/lib.rs | 44 ++++++++++++++++- agb-gbafix/src/main.rs | 11 ++++- 4 files changed, 158 insertions(+), 3 deletions(-) diff --git a/agb-gbafix/Cargo.lock b/agb-gbafix/Cargo.lock index aa5da1ab..74b92ff4 100644 --- a/agb-gbafix/Cargo.lock +++ b/agb-gbafix/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "anyhow", "clap", "elf", + "rmp-serde", ] [[package]] @@ -65,6 +66,18 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "clap" version = "4.5.3" @@ -104,12 +117,104 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "strsim" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/agb-gbafix/Cargo.toml b/agb-gbafix/Cargo.toml index c27d2b7b..153466f3 100644 --- a/agb-gbafix/Cargo.toml +++ b/agb-gbafix/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/agbrs/agb" elf = "0.7" anyhow = "1" clap = "4" +rmp-serde = "1" [profile.dev] opt-level = 3 diff --git a/agb-gbafix/src/lib.rs b/agb-gbafix/src/lib.rs index 36b0389f..ed86b1db 100644 --- a/agb-gbafix/src/lib.rs +++ b/agb-gbafix/src/lib.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, bail, ensure, Result}; -use std::io::Write; +use std::{collections::HashMap, io::Write}; const GBA_HEADER_SIZE: usize = 192; @@ -71,6 +71,7 @@ pub fn write_gba_file( input: &[u8], mut header: GbaHeader, padding_behaviour: PaddingBehaviour, + include_debug: bool, output: &mut W, ) -> Result<()> { let elf_file = elf::ElfBytes::::minimal_parse(input)?; @@ -80,7 +81,7 @@ pub fn write_gba_file( .ok_or_else(|| anyhow!("Failed to parse as elf file"))?; let mut bytes_written = 0; - for section_header in section_headers.iter() { + for section_header in section_headers { const SHT_NOBITS: u32 = 8; const SHT_NULL: u32 = 0; const SHF_ALLOC: u64 = 2; @@ -123,6 +124,10 @@ pub fn write_gba_file( bytes_written += data.len() as u64; } + if include_debug { + bytes_written += write_debug(&elf_file, output)?; + } + if !bytes_written.is_power_of_two() && padding_behaviour == PaddingBehaviour::Pad { let required_padding = bytes_written.next_power_of_two() - bytes_written; @@ -133,3 +138,38 @@ pub fn write_gba_file( Ok(()) } + +fn write_debug( + elf_file: &elf::ElfBytes<'_, elf::endian::AnyEndian>, + output: &mut W, +) -> Result { + let (Some(section_headers), Some(string_table)) = elf_file.section_headers_with_strtab()? + else { + bail!("Could not find either the section headers or the string table"); + }; + + let mut debug_sections = HashMap::new(); + + for section_header in section_headers { + let section_name = string_table.get(section_header.sh_name as usize)?; + if !section_name.starts_with(".debug") { + continue; + } + + let (data, compression) = elf_file.section_data(§ion_header)?; + if compression.is_some() { + bail!("Do not support compression in elf files, section {section_name} was compressed"); + } + + debug_sections.insert(section_name.to_owned(), data); + } + + let mut debug_data = vec![]; + rmp_serde::encode::write(&mut debug_data, &debug_sections)?; + + output.write_all(&debug_data)?; + output.write_all(&(debug_data.len() as u32).to_le_bytes())?; + output.write_all(&(1u32).to_le_bytes())?; + + Ok(debug_data.len() as u64 + 4) +} diff --git a/agb-gbafix/src/main.rs b/agb-gbafix/src/main.rs index 3ae0f25c..8b2db41d 100644 --- a/agb-gbafix/src/main.rs +++ b/agb-gbafix/src/main.rs @@ -19,6 +19,7 @@ fn main() -> Result<()> { .arg(arg!(-m --makercode "Set the maker code, 2 bytes")) .arg(arg!(-r --gameversion "Set the version of the game, 0-255").value_parser(value_parser!(u8))) .arg(arg!(-p --padding "Pad the ROM to the next power of 2 in size")) + .arg(arg!(-g --debug "Include debug information directly in the ROM")) .get_matches(); let input = matches.get_one::("INPUT").unwrap(); @@ -70,6 +71,8 @@ fn main() -> Result<()> { } } + let include_debug = matches.get_flag("debug"); + let pad = matches.get_flag("padding"); let pad = if pad { PaddingBehaviour::Pad @@ -80,7 +83,13 @@ fn main() -> Result<()> { let mut output = BufWriter::new(fs::File::create(output)?); let file_data = fs::read(input)?; - write_gba_file(file_data.as_slice(), header, pad, &mut output)?; + write_gba_file( + file_data.as_slice(), + header, + pad, + include_debug, + &mut output, + )?; output.flush()?; From 25e15baa2b77dd07cabb337f649673a9eed138bb Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 13:38:49 +0100 Subject: [PATCH 27/35] Compress the debug data for quite a large saving --- agb-gbafix/Cargo.lock | 32 ++++++++++++++++++++++++++++++++ agb-gbafix/Cargo.toml | 1 + agb-gbafix/src/lib.rs | 6 +++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/agb-gbafix/Cargo.lock b/agb-gbafix/Cargo.lock index 74b92ff4..8212dcdd 100644 --- a/agb-gbafix/Cargo.lock +++ b/agb-gbafix/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "anyhow", "clap", "elf", + "lz4_flex", "rmp-serde", ] @@ -78,6 +79,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.3" @@ -117,6 +124,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +dependencies = [ + "twox-hash", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -192,6 +208,12 @@ dependencies = [ "syn", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.0" @@ -209,6 +231,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/agb-gbafix/Cargo.toml b/agb-gbafix/Cargo.toml index 153466f3..a0099926 100644 --- a/agb-gbafix/Cargo.toml +++ b/agb-gbafix/Cargo.toml @@ -12,6 +12,7 @@ elf = "0.7" anyhow = "1" clap = "4" rmp-serde = "1" +lz4_flex = "0.11" [profile.dev] opt-level = 3 diff --git a/agb-gbafix/src/lib.rs b/agb-gbafix/src/lib.rs index ed86b1db..f2636550 100644 --- a/agb-gbafix/src/lib.rs +++ b/agb-gbafix/src/lib.rs @@ -165,7 +165,11 @@ fn write_debug( } let mut debug_data = vec![]; - rmp_serde::encode::write(&mut debug_data, &debug_sections)?; + { + let mut compressed_writer = lz4_flex::frame::FrameEncoder::new(&mut debug_data); + rmp_serde::encode::write(&mut compressed_writer, &debug_sections)?; + compressed_writer.flush()?; + } output.write_all(&debug_data)?; output.write_all(&(debug_data.len() as u32).to_le_bytes())?; From 89f6a2782ba45e5e1bc730fbba300008e85484aa Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 14:03:42 +0100 Subject: [PATCH 28/35] Allow loading of the dwarf information from the gba rom --- agb-addr2line/Cargo.toml | 2 + agb-addr2line/src/load_dwarf.rs | 74 +++++++++++++++++++++++++++++++++ agb-addr2line/src/main.rs | 8 ++-- 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 agb-addr2line/src/load_dwarf.rs diff --git a/agb-addr2line/Cargo.toml b/agb-addr2line/Cargo.toml index 63130c8b..22aee0ef 100644 --- a/agb-addr2line/Cargo.toml +++ b/agb-addr2line/Cargo.toml @@ -15,6 +15,8 @@ addr2line = { version = "0.21", default-features = false, features = [ "std-object", ] } colored = "2" +rmp-serde = "1" +lz4_flex = "0.11" [profile.dev] opt-level = 3 diff --git a/agb-addr2line/src/load_dwarf.rs b/agb-addr2line/src/load_dwarf.rs new file mode 100644 index 00000000..6282d531 --- /dev/null +++ b/agb-addr2line/src/load_dwarf.rs @@ -0,0 +1,74 @@ +use std::{borrow::Cow, collections::HashMap, io::Cursor, rc::Rc}; + +use addr2line::{ + gimli, + object::{self, Object}, +}; +use anyhow::bail; + +pub fn load_dwarf( + file_content: &[u8], +) -> anyhow::Result>> { + if let Ok(object) = object::File::parse(file_content) { + return load_from_object(&object); + } + + let last_8_bytes = &file_content[file_content.len() - 8..]; + let len = u32::from_le_bytes(last_8_bytes[0..4].try_into()?) as usize; + let version = u32::from_le_bytes(last_8_bytes[4..].try_into()?) as usize; + + if version != 1 { + bail!("Only version 1 of the debug info is supported"); + } + + let compressed_debug_data = &file_content[file_content.len() - len - 8..file_content.len() - 8]; + + let decompressing_reader = + lz4_flex::frame::FrameDecoder::new(Cursor::new(compressed_debug_data)); + let debug_info: HashMap> = rmp_serde::decode::from_read(decompressing_reader)?; + + let dwarf = gimli::Dwarf::load(|id| { + let data = debug_info + .get(id.name()) + .map(|data| Cow::Borrowed(data.as_slice())) + .unwrap_or(Cow::Borrowed(&[])); + + Result::<_, gimli::Error>::Ok(gimli::EndianRcSlice::new( + Rc::from(&*data), + gimli::RunTimeEndian::Little, + )) + })?; + + Ok(dwarf) +} + +fn load_from_object<'file>( + object: &object::File<'file, &'file [u8]>, +) -> anyhow::Result>> { + let endian = if object.is_little_endian() { + gimli::RunTimeEndian::Little + } else { + gimli::RunTimeEndian::Big + }; + + fn load_section<'data: 'file, 'file, O, Endian>( + id: gimli::SectionId, + file: &'file O, + endian: Endian, + ) -> Result, gimli::Error> + where + O: object::Object<'data, 'file>, + Endian: gimli::Endianity, + { + use object::ObjectSection; + + let data = file + .section_by_name(id.name()) + .and_then(|section| section.uncompressed_data().ok()) + .unwrap_or(Cow::Borrowed(&[])); + Ok(gimli::EndianRcSlice::new(Rc::from(&*data), endian)) + } + + let dwarf = gimli::Dwarf::load(|id| load_section(id, object, endian))?; + Ok(dwarf) +} diff --git a/agb-addr2line/src/main.rs b/agb-addr2line/src/main.rs index 35f5bc3b..efb5bd62 100644 --- a/agb-addr2line/src/main.rs +++ b/agb-addr2line/src/main.rs @@ -6,11 +6,13 @@ use std::{ time::SystemTime, }; -use addr2line::{gimli, object}; +use addr2line::gimli; use clap::Parser; use colored::Colorize; +use load_dwarf::load_dwarf; mod gwilym_encoding; +mod load_dwarf; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -46,9 +48,9 @@ fn main() -> anyhow::Result<()> { .unwrap_or(SystemTime::UNIX_EPOCH); let file = fs::read(&cli.elf_path)?; - let object = object::File::parse(file.as_slice())?; + let dwarf = load_dwarf(&file)?; - let ctx = addr2line::Context::new(&object)?; + let ctx = addr2line::Context::from_dwarf(dwarf)?; for (i, address) in gwilym_encoding::gwilym_decode(&cli.dump)?.enumerate() { print_address(&ctx, i, address.into(), modification_time)?; From e9455d6031be2e521a0257a8f126747d3f96c738 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 14:18:54 +0100 Subject: [PATCH 29/35] Fix justfile to produce a file in the correct place --- justfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/justfile b/justfile index ce2d3c50..f56a44d7 100644 --- a/justfile +++ b/justfile @@ -123,7 +123,10 @@ _build-rom folder name: cp -v "$GBA_FILE" "examples/target/examples/$GAME_NAME.gba" gbafix *args: - (cd agb-gbafix && cargo run --release -- {{args}}) + (cd agb-gbafix && cargo build --release && cd "{{invocation_directory()}}" && "$CARGO_TARGET_DIR/release/agb-gbafix" {{args}}) + +addr2line *args: + (cd agb-addr2line && cargo build --release && cd "{{invocation_directory()}}" && "$CARGO_TARGET_DIR/release/agb-addr2line" {{args}}) _all-crates target: for CARGO_PROJECT_FILE in agb-*/Cargo.toml agb/Cargo.toml tracker/agb-*/Cargo.toml; do \ From 5366fc252b9ef75ef2714b6adc893a1be2c279dc Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 14:19:09 +0100 Subject: [PATCH 30/35] Don't need special release stuff here to make local testing faster --- agb-addr2line/Cargo.toml | 9 --------- agb-gbafix/Cargo.toml | 9 --------- 2 files changed, 18 deletions(-) diff --git a/agb-addr2line/Cargo.toml b/agb-addr2line/Cargo.toml index 22aee0ef..25f4ca1d 100644 --- a/agb-addr2line/Cargo.toml +++ b/agb-addr2line/Cargo.toml @@ -17,12 +17,3 @@ addr2line = { version = "0.21", default-features = false, features = [ colored = "2" rmp-serde = "1" lz4_flex = "0.11" - -[profile.dev] -opt-level = 3 -debug = true - -[profile.release] -opt-level = 3 -lto = "fat" -debug = true diff --git a/agb-gbafix/Cargo.toml b/agb-gbafix/Cargo.toml index a0099926..b805e384 100644 --- a/agb-gbafix/Cargo.toml +++ b/agb-gbafix/Cargo.toml @@ -13,12 +13,3 @@ anyhow = "1" clap = "4" rmp-serde = "1" lz4_flex = "0.11" - -[profile.dev] -opt-level = 3 -debug = true - -[profile.release] -opt-level = 3 -lto = "fat" -debug = true From b3e18d130f01507ce8688f779307170149ea743f Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 14:19:28 +0100 Subject: [PATCH 31/35] Use a different footer to announce the file type --- agb-addr2line/src/load_dwarf.rs | 18 ++++++++++++++---- agb-gbafix/src/lib.rs | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/agb-addr2line/src/load_dwarf.rs b/agb-addr2line/src/load_dwarf.rs index 6282d531..6662f231 100644 --- a/agb-addr2line/src/load_dwarf.rs +++ b/agb-addr2line/src/load_dwarf.rs @@ -13,19 +13,29 @@ pub fn load_dwarf( return load_from_object(&object); } + // the file might have been padded, so ensure we skip any padding before continuing + let last_non_zero_byte = file_content + .iter() + .rposition(|&b| b != 0) + .ok_or_else(|| anyhow::anyhow!("Gba file is empty"))?; + + let file_content = &file_content[..last_non_zero_byte + 1]; + let last_8_bytes = &file_content[file_content.len() - 8..]; let len = u32::from_le_bytes(last_8_bytes[0..4].try_into()?) as usize; - let version = u32::from_le_bytes(last_8_bytes[4..].try_into()?) as usize; + let version = &last_8_bytes[4..]; - if version != 1 { - bail!("Only version 1 of the debug info is supported"); + if version != b"agb1" { + bail!("Failed to load debug information from ROM file, it might not have been included?"); } let compressed_debug_data = &file_content[file_content.len() - len - 8..file_content.len() - 8]; let decompressing_reader = lz4_flex::frame::FrameDecoder::new(Cursor::new(compressed_debug_data)); - let debug_info: HashMap> = rmp_serde::decode::from_read(decompressing_reader)?; + let debug_info: HashMap> = + rmp_serde::decode::from_read(decompressing_reader) + .map_err(|e| anyhow::anyhow!("Failed to load debug information: {e}"))?; let dwarf = gimli::Dwarf::load(|id| { let data = debug_info diff --git a/agb-gbafix/src/lib.rs b/agb-gbafix/src/lib.rs index f2636550..5cfe2d9d 100644 --- a/agb-gbafix/src/lib.rs +++ b/agb-gbafix/src/lib.rs @@ -173,7 +173,7 @@ fn write_debug( output.write_all(&debug_data)?; output.write_all(&(debug_data.len() as u32).to_le_bytes())?; - output.write_all(&(1u32).to_le_bytes())?; + output.write_all(b"agb1")?; Ok(debug_data.len() as u64 + 4) } From 87676b534dd87b23ebe95df02fe4105be87c9265 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 3 Apr 2024 14:32:30 +0100 Subject: [PATCH 32/35] Fix build error --- emulator/test-runner/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/emulator/test-runner/src/main.rs b/emulator/test-runner/src/main.rs index 55db72f4..7f693b64 100644 --- a/emulator/test-runner/src/main.rs +++ b/emulator/test-runner/src/main.rs @@ -148,10 +148,12 @@ fn load_rom>(path: P) -> anyhow::Result> { let mut elf_buffer = Vec::new(); + let inculde_debug_info = false; if agb_gbafix::write_gba_file( &input_file_buffer, Default::default(), agb_gbafix::PaddingBehaviour::DoNotPad, + inculde_debug_info, &mut elf_buffer, ) .is_ok() From eca511bc830031ed1fed26d4959254f9e9832def Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Tue, 9 Apr 2024 20:09:52 +0100 Subject: [PATCH 33/35] Add a changelog entry for the new backtraces --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 417d09c5..8b24e482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added a new crash screen which provides a mechanism for seeing a full stack trace of your program when it panics. + This requires a change to your `.cargo/config.toml`. You must add the rust flag `"-Cforce-frame-pointers=yes"` to + your rustflags field. - Initial unicode support for font rendering. - Kerning support for font rendering. From 966fda443a5bc81480d488801c94978515f2cc5f Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Tue, 9 Apr 2024 20:13:20 +0100 Subject: [PATCH 34/35] Rename agb-addr2line to agb-debug --- {agb-addr2line => agb-debug}/Cargo.toml | 2 +- {agb-addr2line => agb-debug}/src/gwilym_encoding.rs | 0 {agb-addr2line => agb-debug}/src/load_dwarf.rs | 0 {agb-addr2line => agb-debug}/src/main.rs | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename {agb-addr2line => agb-debug}/Cargo.toml (95%) rename {agb-addr2line => agb-debug}/src/gwilym_encoding.rs (100%) rename {agb-addr2line => agb-debug}/src/load_dwarf.rs (100%) rename {agb-addr2line => agb-debug}/src/main.rs (100%) diff --git a/agb-addr2line/Cargo.toml b/agb-debug/Cargo.toml similarity index 95% rename from agb-addr2line/Cargo.toml rename to agb-debug/Cargo.toml index 25f4ca1d..2dc09f65 100644 --- a/agb-addr2line/Cargo.toml +++ b/agb-debug/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "agb-addr2line" +name = "agb-debug" version = "0.19.1" edition = "2021" authors = ["Gwilym Inzani "] diff --git a/agb-addr2line/src/gwilym_encoding.rs b/agb-debug/src/gwilym_encoding.rs similarity index 100% rename from agb-addr2line/src/gwilym_encoding.rs rename to agb-debug/src/gwilym_encoding.rs diff --git a/agb-addr2line/src/load_dwarf.rs b/agb-debug/src/load_dwarf.rs similarity index 100% rename from agb-addr2line/src/load_dwarf.rs rename to agb-debug/src/load_dwarf.rs diff --git a/agb-addr2line/src/main.rs b/agb-debug/src/main.rs similarity index 100% rename from agb-addr2line/src/main.rs rename to agb-debug/src/main.rs From c77ed7fa566583a986e96106f8de6cf46b8b6eaa Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Tue, 9 Apr 2024 20:15:33 +0100 Subject: [PATCH 35/35] Update the justfile target --- justfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index f56a44d7..247c32cd 100644 --- a/justfile +++ b/justfile @@ -125,8 +125,8 @@ _build-rom folder name: gbafix *args: (cd agb-gbafix && cargo build --release && cd "{{invocation_directory()}}" && "$CARGO_TARGET_DIR/release/agb-gbafix" {{args}}) -addr2line *args: - (cd agb-addr2line && cargo build --release && cd "{{invocation_directory()}}" && "$CARGO_TARGET_DIR/release/agb-addr2line" {{args}}) +debug *args: + (cd agb-debug && cargo build --release && cd "{{invocation_directory()}}" && "$CARGO_TARGET_DIR/release/agb-debug" {{args}}) _all-crates target: for CARGO_PROJECT_FILE in agb-*/Cargo.toml agb/Cargo.toml tracker/agb-*/Cargo.toml; do \