From 72e7850152a74b33b6bbfb1889599603fc52ebbb Mon Sep 17 00:00:00 2001 From: Corwin Date: Wed, 17 Apr 2024 19:44:31 +0100 Subject: [PATCH 01/26] backtrace page with more detail --- website/agb/src/app/crash/backtrace.tsx | 79 ++++++++++++++++++++----- website/agb/src/app/crash/page.tsx | 16 +---- 2 files changed, 68 insertions(+), 27 deletions(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 30570fca..28185f76 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -1,33 +1,84 @@ "use client"; -import { FC } from "react"; +import { ContentBlock } from "../contentBlock"; import { useClientValue } from "../useClientValue.hook"; import { styled } from "styled-components"; -const BacktraceWrapper = styled.section` - display: flex; - gap: 10px; - justify-content: center; -`; - -function getBacktrace() { - return window.location.hash.slice(1); +export function BacktracePage() { + return ( + +

agbrs crash backtrace viewer

+

+ You likely got here from the link / QR code that was displayed when a + game you were playing crashed. This is the default crash page for games + made using the agb library. +

+

+ The creator of the game is very likely interested in the code + below along with a description of what you were doing at the + time.{" "} + Send these to the creator of the game you are playing. +

+ +

+ + The owners of this website are not necessarily the creators of the + game you are playing. + +

+

For game developers

+

This page will eventually let you view backtraces in the browser.

+

+ For now you can copy the backtrace code here and use it with{" "} + agb-addr2line. +

+

If you don't want players to be sent to this page, you can:

+
    +
  1. Configure the backtrace page to point to your own site
  2. +
  3. Configure the backtrace page to not point to a site at all
  4. +
  5. Not use the backtrace feature
  6. +
+
+ ); } -export function BacktraceDisplay() { +function BacktraceDisplay() { const backtrace = useClientValue(getBacktrace) ?? ""; return ( - - - + ); } + +const BacktraceCodeBlock = styled.code` + font-size: 3rem; + background-color: #dddddd; + padding: 0px 40px; + overflow-x: scroll; +`; + +const BacktraceWrapper = styled.section` + display: flex; + gap: 10px; + justify-content: center; + align-items: center; + flex-wrap: wrap; +`; + +const BacktraceCopyButton = styled.button` + padding: 10px; + overflow-x: scroll; +`; + +function getBacktrace() { + return window.location.hash.slice(1); +} diff --git a/website/agb/src/app/crash/page.tsx b/website/agb/src/app/crash/page.tsx index 3b97c492..05283ac3 100644 --- a/website/agb/src/app/crash/page.tsx +++ b/website/agb/src/app/crash/page.tsx @@ -1,21 +1,11 @@ import { Metadata } from "next"; -import { BacktraceDisplay } from "./backtrace"; +import { BacktracePage } from "./backtrace"; import { ContentBlock } from "../contentBlock"; export const metadata: Metadata = { title: "agbrs crash backtrace", }; -export default function Crash() { - return ( - -

agbrs crash backtrace viewer

-

This page will eventually let you view backtraces in the browser.

-

- For now you can copy the backtrace code here and use it with{" "} - agb-addr2line -

- -
- ); +export default function Backtrace() { + return ; } From f269e6364a9fcf18d6f655616f0a97a86806f862 Mon Sep 17 00:00:00 2001 From: Corwin Date: Thu, 18 Apr 2024 00:43:47 +0100 Subject: [PATCH 02/26] thiserror + expose some as lib --- agb-debug/Cargo.toml | 2 +- agb-debug/src/gwilym_encoding.rs | 30 +++++++---- agb-debug/src/lib.rs | 93 ++++++++++++++++++++++++++++++++ agb-debug/src/load_dwarf.rs | 38 ++++++++----- agb-debug/src/main.rs | 32 +++-------- 5 files changed, 148 insertions(+), 47 deletions(-) create mode 100644 agb-debug/src/lib.rs diff --git a/agb-debug/Cargo.toml b/agb-debug/Cargo.toml index 4c3f41f9..12977469 100644 --- a/agb-debug/Cargo.toml +++ b/agb-debug/Cargo.toml @@ -8,7 +8,7 @@ description = "CLI utility to convert agb stack trace dumps into human readable repository = "https://github.com/agbrs/agb" [dependencies] -anyhow = "1" +thiserror = "1" clap = { version = "4", features = ["derive"] } addr2line = { version = "0.22", default-features = false, features = [ "rustc-demangle", diff --git a/agb-debug/src/gwilym_encoding.rs b/agb-debug/src/gwilym_encoding.rs index 1ce957f5..f192c28a 100644 --- a/agb-debug/src/gwilym_encoding.rs +++ b/agb-debug/src/gwilym_encoding.rs @@ -1,8 +1,20 @@ use std::{slice::ChunksExact, sync::OnceLock}; +use thiserror::Error; + const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; -pub fn gwilym_decode(input: &str) -> anyhow::Result> { +#[derive(Debug, Error)] +pub enum GwilymDecodeError { + #[error("Does not contain version")] + NoVersion, + #[error("Only version 1 is supported")] + WrongVersion, + #[error("Input must be a multiple of 3 but have {0}")] + LengthWrong(usize), +} + +pub fn gwilym_decode(input: &str) -> Result, GwilymDecodeError> { GwilymDecodeIter::new(input) } @@ -11,21 +23,21 @@ pub struct GwilymDecodeIter<'a> { } impl<'a> GwilymDecodeIter<'a> { - fn new(input: &'a str) -> anyhow::Result { + fn new(input: &'a str) -> 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"); + return Err(GwilymDecodeError::NoVersion); }; if version != "1" { - anyhow::bail!("Only version 1 is supported"); + return Err(GwilymDecodeError::WrongVersion); } if input.len() % 3 != 0 { - anyhow::bail!("Input string must have length a multiple of 3"); + return Err(GwilymDecodeError::LengthWrong(input.len())); } Ok(Self { @@ -75,11 +87,11 @@ fn get_value_for_char(input: u8) -> u32 { #[cfg(test)] mod test { - use super::{gwilym_decode, ALPHABET}; + use super::*; use std::fmt::Write; #[test] - fn should_correctly_decode_16s() -> anyhow::Result<()> { + fn should_correctly_decode_16s() -> Result<(), GwilymDecodeError> { assert_eq!( &gwilym_decode("2QI65Q69306Kv1")?.collect::>(), &[0x0800_16d3, 0x0800_315b, 0x0800_3243, 0x0800_0195] @@ -112,7 +124,7 @@ mod test { } #[test] - fn should_correctly_decode_16s_and_32s() -> anyhow::Result<()> { + fn should_correctly_decode_16s_and_32s() -> Result<(), Box> { let trace: &[u32] = &[ 0x0300_2990, 0x0800_3289, @@ -143,7 +155,7 @@ mod test { } #[test] - fn should_strip_the_agbrsdev_prefix() -> anyhow::Result<()> { + fn should_strip_the_agbrsdev_prefix() -> Result<(), Box> { assert_eq!( &gwilym_decode("https://agbrs.dev/crash#2QI65Q69306Kv1")?.collect::>(), &[0x0800_16d3, 0x0800_315b, 0x0800_3243, 0x0800_0195] diff --git a/agb-debug/src/lib.rs b/agb-debug/src/lib.rs new file mode 100644 index 00000000..e353a0ca --- /dev/null +++ b/agb-debug/src/lib.rs @@ -0,0 +1,93 @@ +mod gwilym_encoding; +mod load_dwarf; + +use addr2line::gimli::{self, EndianReader}; +pub use gwilym_encoding::{gwilym_decode, GwilymDecodeError}; +pub use load_dwarf::{load_dwarf, GimliDwarf, LoadDwarfError}; +use thiserror::Error; + +pub use addr2line; + +pub struct AddressInfo { + pub location: Location, + pub is_interesting: bool, + pub is_inline: bool, + pub function: String, +} + +#[derive(Debug, Error)] +pub enum AddressInfoError { + #[error(transparent)] + Gimli(#[from] gimli::Error), +} + +pub struct Location { + pub filename: String, + pub line: u32, + pub col: u32, +} + +pub type Addr2LineContext = addr2line::Context>; + +impl Default for Location { + fn default() -> Self { + Self { + filename: "??".to_string(), + line: 0, + col: 0, + } + } +} + +pub fn address_info( + ctx: &Addr2LineContext, + address: u64, +) -> Result, AddressInfoError> { + let mut frames = ctx.find_frames(address).skip_all_loads()?; + + let mut is_first = true; + + let mut infos = Vec::new(); + + while let Some(frame) = frames.next()? { + let function_name = if let Some(ref func) = frame.function { + func.demangle()?.into_owned() + } else { + "unknown function".to_string() + }; + + 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(); + + let is_interesting = is_interesting_function(&function_name, &location.filename); + + infos.push(AddressInfo { + location, + is_interesting, + is_inline: is_first, + function: function_name, + }); + is_first = false; + } + + Ok(infos) +} + +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 +} diff --git a/agb-debug/src/load_dwarf.rs b/agb-debug/src/load_dwarf.rs index 394e5e57..d4b13ab1 100644 --- a/agb-debug/src/load_dwarf.rs +++ b/agb-debug/src/load_dwarf.rs @@ -4,38 +4,52 @@ use addr2line::{ gimli, object::{self, Object}, }; -use anyhow::bail; +use thiserror::Error; -pub fn load_dwarf( - file_content: &[u8], -) -> anyhow::Result>> { +#[derive(Debug, Error)] +pub enum LoadDwarfError { + #[error("Gba file is empty")] + GbaFileEmpty, + #[error("Failed to load debug information from ROM file, it might not have been included?")] + NoDebugInformation, + #[error("Failed to load debug information: {0}")] + DeserializationError(#[from] rmp_serde::decode::Error), + #[error(transparent)] + GimliError(#[from] gimli::Error), +} + +pub type GimliDwarf = gimli::Dwarf>; + +pub fn load_dwarf(file_content: &[u8]) -> Result { if let Ok(object) = object::File::parse(file_content) { - return load_from_object(&object); + return Ok(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"))?; + .ok_or_else(|| LoadDwarfError::GbaFileEmpty)?; 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 len = u32::from_le_bytes( + last_8_bytes[0..4] + .try_into() + .or(Err(LoadDwarfError::NoDebugInformation))?, + ) as usize; let version = &last_8_bytes[4..]; if version != b"agb1" { - bail!("Failed to load debug information from ROM file, it might not have been included?"); + return Err(LoadDwarfError::NoDebugInformation); } 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) - .map_err(|e| anyhow::anyhow!("Failed to load debug information: {e}"))?; + let debug_info: HashMap> = rmp_serde::decode::from_read(decompressing_reader)?; let dwarf = gimli::Dwarf::load(|id| { let data = debug_info @@ -54,7 +68,7 @@ pub fn load_dwarf( fn load_from_object<'file>( object: &object::File<'file, &'file [u8]>, -) -> anyhow::Result>> { +) -> Result { let endian = if object.is_little_endian() { gimli::RunTimeEndian::Little } else { diff --git a/agb-debug/src/main.rs b/agb-debug/src/main.rs index efb5bd62..00611148 100644 --- a/agb-debug/src/main.rs +++ b/agb-debug/src/main.rs @@ -1,5 +1,6 @@ use std::{ borrow::Cow, + error::Error, fs::{self, File}, io::Read, path::PathBuf, @@ -7,12 +8,9 @@ use std::{ }; use addr2line::gimli; +use agb_debug::Location; 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)] @@ -24,23 +22,7 @@ struct Args { dump: String, } -struct Location { - filename: String, - line: u32, - col: u32, -} - -impl Default for Location { - fn default() -> Self { - Self { - filename: "??".to_string(), - line: 0, - col: 0, - } - } -} - -fn main() -> anyhow::Result<()> { +fn main() -> Result<(), Box> { let cli = Args::parse(); let modification_time = fs::metadata(&cli.elf_path)? @@ -48,11 +30,11 @@ fn main() -> anyhow::Result<()> { .unwrap_or(SystemTime::UNIX_EPOCH); let file = fs::read(&cli.elf_path)?; - let dwarf = load_dwarf(&file)?; + let dwarf = agb_debug::load_dwarf(&file)?; let ctx = addr2line::Context::from_dwarf(dwarf)?; - for (i, address) in gwilym_encoding::gwilym_decode(&cli.dump)?.enumerate() { + for (i, address) in agb_debug::gwilym_decode(&cli.dump)?.enumerate() { print_address(&ctx, i, address.into(), modification_time)?; } @@ -64,7 +46,7 @@ fn print_address( index: usize, address: u64, elf_modification_time: SystemTime, -) -> anyhow::Result<()> { +) -> Result<(), Box> { let mut frames = ctx.find_frames(address).skip_all_loads()?; let mut is_first = true; @@ -119,7 +101,7 @@ fn print_line_of_code( frame: &addr2line::Frame<'_, impl gimli::Reader>, location: Location, elf_modification_time: SystemTime, -) -> anyhow::Result<()> { +) -> Result<(), Box> { let Some(filename) = frame.location.as_ref().and_then(|location| location.file) else { return Ok(()); }; From 1d9a7d51a3bfa53717b0e1146c7c1e2d2eda9288 Mon Sep 17 00:00:00 2001 From: Corwin Date: Thu, 18 Apr 2024 00:44:13 +0100 Subject: [PATCH 03/26] expose as wasm --- Cargo.toml | 1 + agb-wasm/Cargo.toml | 29 +++++++++++++++++++++++++++ agb-wasm/src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 agb-wasm/Cargo.toml create mode 100644 agb-wasm/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 9334e895..7c273d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "emulator/mgba", "emulator/mgba-sys", "emulator/test-runner", + "agb-wasm", ] exclude = [ diff --git a/agb-wasm/Cargo.toml b/agb-wasm/Cargo.toml new file mode 100644 index 00000000..ee56b986 --- /dev/null +++ b/agb-wasm/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "agb-wasm" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +wasm-bindgen = "0.2.84" +agb-debug = { path="../agb-debug" } + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } + +[dev-dependencies] +wasm-bindgen-test = "0.3.34" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" \ No newline at end of file diff --git a/agb-wasm/src/lib.rs b/agb-wasm/src/lib.rs new file mode 100644 index 00000000..c334fc95 --- /dev/null +++ b/agb-wasm/src/lib.rs @@ -0,0 +1,49 @@ +use agb_debug::{addr2line::Context, load_dwarf, Addr2LineContext}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn decode_backtrace(backtrace: &str) -> Result, JsError> { + Ok(agb_debug::gwilym_decode(backtrace)?.collect()) +} + +#[wasm_bindgen] +pub struct DebugFile { + dwarf: Addr2LineContext, +} + +#[wasm_bindgen(getter_with_clone)] +pub struct AddressInfo { + pub filename: String, + pub function_name: String, + pub line_number: u32, + pub column: u32, + pub is_interesting: bool, + pub is_inline: bool, +} + +#[wasm_bindgen] +impl DebugFile { + #[wasm_bindgen(constructor)] + pub fn new(data: &[u8]) -> Result { + Ok(DebugFile { + dwarf: Context::from_dwarf(load_dwarf(data)?)?, + }) + } + + pub fn address_info(&self, address: u32) -> Result, JsError> { + let info = agb_debug::address_info(&self.dwarf, address.into())?; + let address_infos = info + .into_iter() + .map(|x| AddressInfo { + filename: x.location.filename, + line_number: x.location.line, + column: x.location.col, + is_interesting: x.is_interesting, + is_inline: x.is_inline, + function_name: x.function, + }) + .collect(); + + Ok(address_infos) + } +} From 121ebe312cf81e45c42911f6fce1afd9c66c5666 Mon Sep 17 00:00:00 2001 From: Corwin Date: Thu, 18 Apr 2024 00:50:13 +0100 Subject: [PATCH 04/26] add debug information to the crash page --- justfile | 19 +++++- website/agb/src/app/crash/backtrace.tsx | 86 +++++++++++++++++++++++++ website/agb/src/app/useAgbDebug.hook.ts | 35 ++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 website/agb/src/app/useAgbDebug.hook.ts diff --git a/justfile b/justfile index a2b42bdf..eab6cfba 100644 --- a/justfile +++ b/justfile @@ -87,6 +87,15 @@ release +args: (_run-tool "release" args) miri: (cd agb-hashmap && cargo miri test) +setup-cargo-wasm: + cargo install wasm-pack + +build-agb-wasm: + (cd agb-wasm && wasm-pack build --target web) + rm -rf website/agb/src/app/vendor/agb_wasm + mkdir website/agb/src/app/vendor + cp agb-wasm/pkg website/agb/src/app/vendor/agb_wasm -r + build-mgba-wasm: rm -rf website/agb/src/app/mgba/vendor mkdir website/agb/src/app/mgba/vendor @@ -94,13 +103,19 @@ build-mgba-wasm: build-combo-rom-site: just _build-rom "examples/combo" "AGBGAMES" - -build-site-app: build-mgba-wasm build-combo-rom-site mkdir -p website/agb/public gzip -9 -c examples/target/examples/combo.gba > website/agb/public/combo.gba.gz + + +setup-app-build: build-mgba-wasm build-combo-rom-site build-agb-wasm (cd website/agb && npm install --no-save --prefer-offline --no-audit) + +build-site-app: setup-app-build (cd website/agb && npm run build) +serve-site-dev: setup-app-build + (cd website/agb && npm run dev) + build-site: build-site-app build-book rm -rf website/build cp website/agb/out website/build -r diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 28185f76..80430ec7 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -3,6 +3,9 @@ import { ContentBlock } from "../contentBlock"; import { useClientValue } from "../useClientValue.hook"; import { styled } from "styled-components"; +import { useEffect, useMemo, useState } from "react"; +import { useAgbDebug } from "../useAgbDebug.hook"; +import { AddressInfo } from "../vendor/agb_wasm/agb_wasm"; export function BacktracePage() { return ( @@ -38,10 +41,93 @@ export function BacktracePage() {
  • Configure the backtrace page to not point to a site at all
  • Not use the backtrace feature
  • + ); } +function Backtrace() { + const backtrace = useClientValue(getBacktrace) ?? ""; + const backtraceAddresses = useBacktraceData(backtrace); + + const [files, setFile] = useState([]); + const backtraceLocations = useBacktraceLocations( + backtraceAddresses ?? [], + files ?? [] + ); + + return ( +
    + Addresses in the backtrace + { + const files = evt.target.files; + const filesArr = (files && Array.from(files)) ?? []; + setFile(filesArr); + }} + /> +
      + {backtraceAddresses && + backtraceAddresses.map((x, idx) => ( +
    1. + 0x{x.toString(16).padStart(8, "0")} + +
    2. + ))} +
    +
    + ); +} + +function BacktraceAddressInfo({ info }: { info: AddressInfo[] | undefined }) { + if (!info) return; + + return ( +
      + {info.map((x, idx) => ( +
    1. + {x.is_inline && "(inlined into)"} {x.function_name}:{x.column}{" "} + {x.filename}:{x.line_number} +
    2. + ))} +
    + ); +} + +function useBacktraceLocations(addresses: number[], file: File[]) { + const debug = useAgbDebug(); + const [debugInfo, setDebugInfo] = useState([]); + + useEffect(() => { + const f = file[0]; + if (!f) return; + if (!debug) return; + (async () => { + const buf = await f.arrayBuffer(); + const view = new Uint8Array(buf); + + const agbDebugFile = debug.debug_file(view); + const debugInfo = addresses.map((x) => agbDebugFile.address_info(x)); + setDebugInfo(debugInfo); + })(); + }, [addresses, debug, file]); + + return debugInfo; +} + +function useBacktraceData(trace?: string) { + const debug = useAgbDebug(); + + return useMemo(() => { + try { + if (!trace) return; + const addresses = debug?.decode_backtrace(trace); + return addresses && Array.from(addresses); + } catch {} + }, [debug, trace]); +} + function BacktraceDisplay() { const backtrace = useClientValue(getBacktrace) ?? ""; diff --git a/website/agb/src/app/useAgbDebug.hook.ts b/website/agb/src/app/useAgbDebug.hook.ts new file mode 100644 index 00000000..6e6217e8 --- /dev/null +++ b/website/agb/src/app/useAgbDebug.hook.ts @@ -0,0 +1,35 @@ +import { useEffect, useState } from "react"; +import debugInit, { + decode_backtrace, + DebugFile, + InitOutput, +} from "./vendor/agb_wasm/agb_wasm"; + +let agbDebug: Promise | undefined; + +interface AgbDebug { + decode_backtrace: (backtrace: string) => Uint32Array; + debug_file: (file: Uint8Array) => DebugFile; +} + +export function useAgbDebug() { + const [debug, setDebug] = useState(); + + useEffect(() => { + (async () => { + if (agbDebug === undefined) { + agbDebug = debugInit(); + } + + await agbDebug; + console.log("Loaded agb debug"); + + setDebug({ + decode_backtrace, + debug_file: (file: Uint8Array) => new DebugFile(file), + }); + })(); + }, []); + + return debug; +} From 1cdbf13814b83f64ad46df950ee970063952da9c Mon Sep 17 00:00:00 2001 From: Corwin Date: Thu, 18 Apr 2024 00:53:15 +0100 Subject: [PATCH 05/26] correct the text --- website/agb/src/app/crash/backtrace.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 80430ec7..2d0d8d3e 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -30,17 +30,17 @@ export function BacktracePage() {

    For game developers

    -

    This page will eventually let you view backtraces in the browser.

    -

    - For now you can copy the backtrace code here and use it with{" "} - agb-addr2line. -

    If you don't want players to be sent to this page, you can:

    1. Configure the backtrace page to point to your own site
    2. Configure the backtrace page to not point to a site at all
    3. Not use the backtrace feature
    +

    + Here you can see the debug information contained in the crash log. Given + additional debug files it can present you with the corresponding + function names, file names, line numbers, and column numbers. +

    ); From 1a3b4319bc46f78361b9861b5da378590df771ee Mon Sep 17 00:00:00 2001 From: Corwin Date: Thu, 18 Apr 2024 00:53:20 +0100 Subject: [PATCH 06/26] correct justfile --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index eab6cfba..ef7f8e39 100644 --- a/justfile +++ b/justfile @@ -93,7 +93,7 @@ setup-cargo-wasm: build-agb-wasm: (cd agb-wasm && wasm-pack build --target web) rm -rf website/agb/src/app/vendor/agb_wasm - mkdir website/agb/src/app/vendor + mkdir -p website/agb/src/app/vendor cp agb-wasm/pkg website/agb/src/app/vendor/agb_wasm -r build-mgba-wasm: From 55394640b098ba572c6b3e82369b082b80190b24 Mon Sep 17 00:00:00 2001 From: Corwin Date: Thu, 18 Apr 2024 19:10:39 +0100 Subject: [PATCH 07/26] correct inline logic --- agb-debug/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agb-debug/src/lib.rs b/agb-debug/src/lib.rs index e353a0ca..5c2c69e2 100644 --- a/agb-debug/src/lib.rs +++ b/agb-debug/src/lib.rs @@ -1,7 +1,7 @@ mod gwilym_encoding; mod load_dwarf; -use addr2line::gimli::{self, EndianReader}; +use addr2line::gimli; pub use gwilym_encoding::{gwilym_decode, GwilymDecodeError}; pub use load_dwarf::{load_dwarf, GimliDwarf, LoadDwarfError}; use thiserror::Error; @@ -71,7 +71,7 @@ pub fn address_info( infos.push(AddressInfo { location, is_interesting, - is_inline: is_first, + is_inline: !is_first, function: function_name, }); is_first = false; From 0d4005f163490280ae2009166bd5abad678d563e Mon Sep 17 00:00:00 2001 From: Corwin Date: Thu, 18 Apr 2024 19:10:53 +0100 Subject: [PATCH 08/26] make backtrace view fancy --- website/agb/src/app/crash/backtrace.tsx | 121 +++++++++++++++++++----- 1 file changed, 96 insertions(+), 25 deletions(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 2d0d8d3e..2178673b 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -46,52 +46,121 @@ export function BacktracePage() { ); } +const BacktraceListWrapper = styled.div` + font-size: 1rem; + position: relative; + width: calc(100vw - 20px); + margin-left: calc(-1 * (100vw - 20px) / 2); + left: 50%; +`; + +const BacktraceList = styled.ol` + overflow-x: scroll; + white-space: nowrap; +`; + function Backtrace() { const backtrace = useClientValue(getBacktrace) ?? ""; const backtraceAddresses = useBacktraceData(backtrace); const [files, setFile] = useState([]); + const backtraceAddressesList = + typeof backtraceAddresses === "object" ? backtraceAddresses : []; + const backtraceError = + typeof backtraceAddresses === "string" ? backtraceAddresses : undefined; + const backtraceLocations = useBacktraceLocations( - backtraceAddresses ?? [], + backtraceAddressesList, files ?? [] ); return (
    Addresses in the backtrace - { - const files = evt.target.files; - const filesArr = (files && Array.from(files)) ?? []; - setFile(filesArr); - }} - /> -
      - {backtraceAddresses && - backtraceAddresses.map((x, idx) => ( + + + + {backtraceError} + {backtraceAddressesList.map((x, idx) => (
    1. - 0x{x.toString(16).padStart(8, "0")} - + {backtraceLocations[idx] ? ( + + ) : ( + 0x{x.toString(16).padStart(8, "0")} + )}
    2. ))} -
    + +
    ); } +function makeNicePath(path: string) { + const srcIndex = path.lastIndexOf("/src/"); + if (srcIndex < 0) return path; + + const crateNameStartIndex = path.slice(0, srcIndex).lastIndexOf("/"); + const crateName = + crateNameStartIndex < 0 + ? "" + : path.slice(crateNameStartIndex + 1, srcIndex); + + return `<${crateName}>/${path.slice(srcIndex + 5)}`; +} + +const GreenSpan = styled.span` + color: green; +`; + +const BacktraceAddressLine = styled.ul` + list-style-type: none; + padding-left: 20px; +`; + function BacktraceAddressInfo({ info }: { info: AddressInfo[] | undefined }) { if (!info) return; + function FunctionName({ + interesting, + functionName, + }: { + interesting: boolean; + functionName: string; + }) { + if (interesting) { + return {functionName}; + } + return functionName; + } + return ( -
      + {info.map((x, idx) => (
    1. - {x.is_inline && "(inlined into)"} {x.function_name}:{x.column}{" "} - {x.filename}:{x.line_number} + + {x.is_inline && "(inlined into)"}{" "} + {" "} + + {makeNicePath(x.filename)}:{x.line_number}:{x.column} + +
    2. ))} -
    + ); } @@ -100,17 +169,17 @@ function useBacktraceLocations(addresses: number[], file: File[]) { const [debugInfo, setDebugInfo] = useState([]); useEffect(() => { - const f = file[0]; - if (!f) return; - if (!debug) return; (async () => { + const f = file[0]; + if (!f) return; + if (!debug) return; const buf = await f.arrayBuffer(); const view = new Uint8Array(buf); const agbDebugFile = debug.debug_file(view); const debugInfo = addresses.map((x) => agbDebugFile.address_info(x)); - setDebugInfo(debugInfo); - })(); + return debugInfo; + })().then((x) => setDebugInfo(x ?? [])); }, [addresses, debug, file]); return debugInfo; @@ -124,7 +193,9 @@ function useBacktraceData(trace?: string) { if (!trace) return; const addresses = debug?.decode_backtrace(trace); return addresses && Array.from(addresses); - } catch {} + } catch (e: unknown) { + return `${e}`; + } }, [debug, trace]); } From 9b773524d6fabdcb54e54c29b502ed965a68d505 Mon Sep 17 00:00:00 2001 From: Corwin Date: Thu, 18 Apr 2024 19:39:27 +0100 Subject: [PATCH 09/26] setup wasm --- .github/workflows/build-site.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-site.yml b/.github/workflows/build-site.yml index cdf7826f..7d2b0495 100644 --- a/.github/workflows/build-site.yml +++ b/.github/workflows/build-site.yml @@ -33,6 +33,8 @@ jobs: uses: peaceiris/actions-mdbook@v2 with: mdbook-version: "0.4.13" + - name: Setup wasm + run: just setup-cargo-wasm - name: Build website run: just build-site - name: Upload artifact From f5bbee01b7b788b77beb01c6ee00dc3e6fff8657 Mon Sep 17 00:00:00 2001 From: Corwin Date: Fri, 19 Apr 2024 21:12:46 +0100 Subject: [PATCH 10/26] move agb-wasm to website directory --- Cargo.toml | 2 +- justfile | 8 ++++---- {agb-wasm => website/backtrace}/Cargo.toml | 4 ++-- {agb-wasm => website/backtrace}/src/lib.rs | 0 4 files changed, 7 insertions(+), 7 deletions(-) rename {agb-wasm => website/backtrace}/Cargo.toml (92%) rename {agb-wasm => website/backtrace}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 7c273d76..6d75a68f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ members = [ "emulator/mgba", "emulator/mgba-sys", "emulator/test-runner", - "agb-wasm", + "website/backtrace", ] exclude = [ diff --git a/justfile b/justfile index ef7f8e39..cbe8bdc4 100644 --- a/justfile +++ b/justfile @@ -90,11 +90,11 @@ miri: setup-cargo-wasm: cargo install wasm-pack -build-agb-wasm: - (cd agb-wasm && wasm-pack build --target web) +build-website-backtrace: + (cd website/backtrace && wasm-pack build --target web) rm -rf website/agb/src/app/vendor/agb_wasm mkdir -p website/agb/src/app/vendor - cp agb-wasm/pkg website/agb/src/app/vendor/agb_wasm -r + cp website/backtrace/pkg website/agb/src/app/vendor/agb_wasm -r build-mgba-wasm: rm -rf website/agb/src/app/mgba/vendor @@ -107,7 +107,7 @@ build-combo-rom-site: gzip -9 -c examples/target/examples/combo.gba > website/agb/public/combo.gba.gz -setup-app-build: build-mgba-wasm build-combo-rom-site build-agb-wasm +setup-app-build: build-mgba-wasm build-combo-rom-site build-website-backtrace (cd website/agb && npm install --no-save --prefer-offline --no-audit) build-site-app: setup-app-build diff --git a/agb-wasm/Cargo.toml b/website/backtrace/Cargo.toml similarity index 92% rename from agb-wasm/Cargo.toml rename to website/backtrace/Cargo.toml index ee56b986..222c93f5 100644 --- a/agb-wasm/Cargo.toml +++ b/website/backtrace/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "agb-wasm" +name = "backtrace" version = "0.1.0" edition = "2021" @@ -13,7 +13,7 @@ default = ["console_error_panic_hook"] [dependencies] wasm-bindgen = "0.2.84" -agb-debug = { path="../agb-debug" } +agb-debug = { path="../../agb-debug" } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/agb-wasm/src/lib.rs b/website/backtrace/src/lib.rs similarity index 100% rename from agb-wasm/src/lib.rs rename to website/backtrace/src/lib.rs From e82d7f7ce5806f0e111161d8d9e2870d92b270fd Mon Sep 17 00:00:00 2001 From: Corwin Date: Fri, 19 Apr 2024 21:12:50 +0100 Subject: [PATCH 11/26] remove log --- website/agb/src/app/useAgbDebug.hook.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/website/agb/src/app/useAgbDebug.hook.ts b/website/agb/src/app/useAgbDebug.hook.ts index 6e6217e8..3a8eba1a 100644 --- a/website/agb/src/app/useAgbDebug.hook.ts +++ b/website/agb/src/app/useAgbDebug.hook.ts @@ -22,7 +22,6 @@ export function useAgbDebug() { } await agbDebug; - console.log("Loaded agb debug"); setDebug({ decode_backtrace, From 8433b239672d2d9cc720c22587e2b28e65fafd2e Mon Sep 17 00:00:00 2001 From: Corwin Date: Fri, 19 Apr 2024 21:49:00 +0100 Subject: [PATCH 12/26] copy to backtrace --- justfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index cbe8bdc4..97de6337 100644 --- a/justfile +++ b/justfile @@ -92,9 +92,9 @@ setup-cargo-wasm: build-website-backtrace: (cd website/backtrace && wasm-pack build --target web) - rm -rf website/agb/src/app/vendor/agb_wasm + rm -rf website/agb/src/app/vendor/backtrace mkdir -p website/agb/src/app/vendor - cp website/backtrace/pkg website/agb/src/app/vendor/agb_wasm -r + cp website/backtrace/pkg website/agb/src/app/vendor/backtrace -r build-mgba-wasm: rm -rf website/agb/src/app/mgba/vendor From 04d368edf0f2aedf62101a257d6e48e4f0791026 Mon Sep 17 00:00:00 2001 From: Corwin Date: Fri, 19 Apr 2024 21:49:16 +0100 Subject: [PATCH 13/26] move out backtrace decoding --- website/agb/src/app/crash/backtrace.tsx | 170 ++-------------------- website/agb/src/app/crash/debug.tsx | 184 ++++++++++++++++++++++++ website/agb/src/app/crash/page.tsx | 1 - website/agb/src/app/useAgbDebug.hook.ts | 9 +- 4 files changed, 199 insertions(+), 165 deletions(-) create mode 100644 website/agb/src/app/crash/debug.tsx diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 2178673b..4c393688 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -3,11 +3,11 @@ import { ContentBlock } from "../contentBlock"; import { useClientValue } from "../useClientValue.hook"; import { styled } from "styled-components"; -import { useEffect, useMemo, useState } from "react"; -import { useAgbDebug } from "../useAgbDebug.hook"; -import { AddressInfo } from "../vendor/agb_wasm/agb_wasm"; +import { Debug } from "./debug"; export function BacktracePage() { + const backtrace = useClientValue(getBacktrace); + return (

    agbrs crash backtrace viewer

    @@ -22,7 +22,9 @@ export function BacktracePage() { time.{" "} Send these to the creator of the game you are playing.

    - + {(backtrace && ) || ( +

    No backtrace data in Url

    + )}

    The owners of this website are not necessarily the creators of the @@ -41,167 +43,13 @@ export function BacktracePage() { additional debug files it can present you with the corresponding function names, file names, line numbers, and column numbers.

    - +

    Backtrace

    + {backtrace && }
    ); } -const BacktraceListWrapper = styled.div` - font-size: 1rem; - position: relative; - width: calc(100vw - 20px); - margin-left: calc(-1 * (100vw - 20px) / 2); - left: 50%; -`; - -const BacktraceList = styled.ol` - overflow-x: scroll; - white-space: nowrap; -`; - -function Backtrace() { - const backtrace = useClientValue(getBacktrace) ?? ""; - const backtraceAddresses = useBacktraceData(backtrace); - - const [files, setFile] = useState([]); - const backtraceAddressesList = - typeof backtraceAddresses === "object" ? backtraceAddresses : []; - const backtraceError = - typeof backtraceAddresses === "string" ? backtraceAddresses : undefined; - - const backtraceLocations = useBacktraceLocations( - backtraceAddressesList, - files ?? [] - ); - - return ( -
    - Addresses in the backtrace - - - - {backtraceError} - {backtraceAddressesList.map((x, idx) => ( -
  • - {backtraceLocations[idx] ? ( - - ) : ( - 0x{x.toString(16).padStart(8, "0")} - )} -
  • - ))} -
    -
    -
    - ); -} - -function makeNicePath(path: string) { - const srcIndex = path.lastIndexOf("/src/"); - if (srcIndex < 0) return path; - - const crateNameStartIndex = path.slice(0, srcIndex).lastIndexOf("/"); - const crateName = - crateNameStartIndex < 0 - ? "" - : path.slice(crateNameStartIndex + 1, srcIndex); - - return `<${crateName}>/${path.slice(srcIndex + 5)}`; -} - -const GreenSpan = styled.span` - color: green; -`; - -const BacktraceAddressLine = styled.ul` - list-style-type: none; - padding-left: 20px; -`; - -function BacktraceAddressInfo({ info }: { info: AddressInfo[] | undefined }) { - if (!info) return; - - function FunctionName({ - interesting, - functionName, - }: { - interesting: boolean; - functionName: string; - }) { - if (interesting) { - return {functionName}; - } - return functionName; - } - - return ( - - {info.map((x, idx) => ( -
  • - - {x.is_inline && "(inlined into)"}{" "} - {" "} - - {makeNicePath(x.filename)}:{x.line_number}:{x.column} - - -
  • - ))} -
    - ); -} - -function useBacktraceLocations(addresses: number[], file: File[]) { - const debug = useAgbDebug(); - const [debugInfo, setDebugInfo] = useState([]); - - useEffect(() => { - (async () => { - const f = file[0]; - if (!f) return; - if (!debug) return; - const buf = await f.arrayBuffer(); - const view = new Uint8Array(buf); - - const agbDebugFile = debug.debug_file(view); - const debugInfo = addresses.map((x) => agbDebugFile.address_info(x)); - return debugInfo; - })().then((x) => setDebugInfo(x ?? [])); - }, [addresses, debug, file]); - - return debugInfo; -} - -function useBacktraceData(trace?: string) { - const debug = useAgbDebug(); - - return useMemo(() => { - try { - if (!trace) return; - const addresses = debug?.decode_backtrace(trace); - return addresses && Array.from(addresses); - } catch (e: unknown) { - return `${e}`; - } - }, [debug, trace]); -} - -function BacktraceDisplay() { - const backtrace = useClientValue(getBacktrace) ?? ""; - +function BacktraceCopyDisplay({ backtrace }: { backtrace: string }) { return ( {backtrace} diff --git a/website/agb/src/app/crash/debug.tsx b/website/agb/src/app/crash/debug.tsx new file mode 100644 index 00000000..a403f431 --- /dev/null +++ b/website/agb/src/app/crash/debug.tsx @@ -0,0 +1,184 @@ +import { styled } from "styled-components"; +import { AddressInfo, AgbDebug, useAgbDebug } from "../useAgbDebug.hook"; +import { useMemo, useState } from "react"; + +const BacktraceListWrapper = styled.div` + font-size: 1rem; + position: relative; + width: calc(100vw - 20px); + margin-left: calc(-1 * (100vw - 20px) / 2); + left: 50%; +`; + +const BacktraceList = styled.ol` + overflow-x: scroll; + white-space: nowrap; +`; + +interface DebugProps { + encodedBacktrace: string; +} + +export function Debug(props: DebugProps) { + const debug = useAgbDebug(); + if (debug) { + return ; + } else { + return

    Loading debug viewer...

    ; + } +} + +interface DebugBacktraceDecodeProps extends DebugProps { + debug: AgbDebug; +} + +const NonWrapCode = styled.code` + white-space: nowrap; +`; + +function DebugBacktraceDecode({ + encodedBacktrace, + debug, +}: DebugBacktraceDecodeProps) { + const backtraceAddresses = useBacktraceData(debug, encodedBacktrace); + const [backtraceLocations, setBacktraceLocations] = useState( + [] + ); + + if (typeof backtraceAddresses === "string") { + return ; + } + + return ( + <> +

    + If you add the elf file used to make the GBA file, or the GBA file + itself if it was made with agb-gbafix --debug + , you can see: function names, file names, line numbers, and column + numbers. +

    + + + + {backtraceAddresses.map((x, idx) => ( +
  • + +
  • + ))} +
    +
    + + ); +} + +function DebugError({ error }: { error: string }) { + return ( + <> +

    Something went wrong decoding the backtrace

    +

    {error}

    + + ); +} + +function makeNicePath(path: string) { + const srcIndex = path.lastIndexOf("/src/"); + if (srcIndex < 0) return path; + + const crateNameStartIndex = path.slice(0, srcIndex).lastIndexOf("/"); + const crateName = + crateNameStartIndex < 0 + ? "" + : path.slice(crateNameStartIndex + 1, srcIndex); + + return `<${crateName}>/${path.slice(srcIndex + 5)}`; +} + +const GreenSpan = styled.span` + color: green; +`; + +const BacktraceAddressLine = styled.ul` + list-style-type: none; + padding-left: 20px; +`; + +function BacktraceAddressInfo({ + address, + info, +}: { + address: number; + info: AddressInfo[] | undefined; +}) { + if (!info) { + return 0x{address.toString(16).padStart(8, "0")}; + } + + function FunctionName({ + interesting, + functionName, + }: { + interesting: boolean; + functionName: string; + }) { + if (interesting) { + return {functionName}; + } + return functionName; + } + + return ( + + {info.map((x, idx) => ( +
  • + + {x.is_inline && "(inlined into)"}{" "} + {" "} + + {makeNicePath(x.filename)}:{x.line_number}:{x.column} + + +
  • + ))} +
    + ); +} + +async function loadLocations(debug: AgbDebug, addresses: number[], file: File) { + const buf = await file.arrayBuffer(); + const view = new Uint8Array(buf); + + const agbDebugFile = debug.debug_file(view); + const debugInfo = addresses.map((x) => agbDebugFile.address_info(x)); + return debugInfo; +} + +function useBacktraceData(debug: AgbDebug, trace: string) { + return useMemo(() => { + try { + const addresses = debug?.decode_backtrace(trace); + return addresses && Array.from(addresses); + } catch (e: unknown) { + return `${e}`; + } + }, [debug, trace]); +} diff --git a/website/agb/src/app/crash/page.tsx b/website/agb/src/app/crash/page.tsx index 05283ac3..e9d4c70d 100644 --- a/website/agb/src/app/crash/page.tsx +++ b/website/agb/src/app/crash/page.tsx @@ -1,6 +1,5 @@ import { Metadata } from "next"; import { BacktracePage } from "./backtrace"; -import { ContentBlock } from "../contentBlock"; export const metadata: Metadata = { title: "agbrs crash backtrace", diff --git a/website/agb/src/app/useAgbDebug.hook.ts b/website/agb/src/app/useAgbDebug.hook.ts index 3a8eba1a..4ed74e4e 100644 --- a/website/agb/src/app/useAgbDebug.hook.ts +++ b/website/agb/src/app/useAgbDebug.hook.ts @@ -3,16 +3,19 @@ import debugInit, { decode_backtrace, DebugFile, InitOutput, -} from "./vendor/agb_wasm/agb_wasm"; + AddressInfo, +} from "./vendor/backtrace/backtrace"; let agbDebug: Promise | undefined; -interface AgbDebug { +export { AddressInfo }; + +export interface AgbDebug { decode_backtrace: (backtrace: string) => Uint32Array; debug_file: (file: Uint8Array) => DebugFile; } -export function useAgbDebug() { +export function useAgbDebug(): AgbDebug | undefined { const [debug, setDebug] = useState(); useEffect(() => { From 853c008ff71b38d30d4f35f85e13fd3a7c53aeef Mon Sep 17 00:00:00 2001 From: Corwin Date: Fri, 19 Apr 2024 21:49:34 +0100 Subject: [PATCH 14/26] cleanup duplicate text --- website/agb/src/app/crash/backtrace.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 4c393688..1d436348 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -38,11 +38,6 @@ export function BacktracePage() {
  • Configure the backtrace page to not point to a site at all
  • Not use the backtrace feature
  • -

    - Here you can see the debug information contained in the crash log. Given - additional debug files it can present you with the corresponding - function names, file names, line numbers, and column numbers. -

    Backtrace

    {backtrace && } From 162bc17addeba2b620057aaa9ecf607f8eeb9fe1 Mon Sep 17 00:00:00 2001 From: Corwin Date: Fri, 19 Apr 2024 23:55:41 +0100 Subject: [PATCH 15/26] shift content around to make backtrace more apparent --- website/agb/src/app/crash/backtrace.tsx | 4 ++-- website/agb/src/app/crash/debug.tsx | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 1d436348..051c1606 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -31,6 +31,8 @@ export function BacktracePage() { game you are playing.

    +

    Backtrace

    + {backtrace && }

    For game developers

    If you don't want players to be sent to this page, you can:

      @@ -38,8 +40,6 @@ export function BacktracePage() {
    1. Configure the backtrace page to not point to a site at all
    2. Not use the backtrace feature
    -

    Backtrace

    - {backtrace && } ); } diff --git a/website/agb/src/app/crash/debug.tsx b/website/agb/src/app/crash/debug.tsx index a403f431..27366b2d 100644 --- a/website/agb/src/app/crash/debug.tsx +++ b/website/agb/src/app/crash/debug.tsx @@ -51,6 +51,18 @@ function DebugBacktraceDecode({ return ( <> + + + {backtraceAddresses.map((x, idx) => ( +
  • + +
  • + ))} +
    +

    If you add the elf file used to make the GBA file, or the GBA file itself if it was made with agb-gbafix --debug @@ -72,18 +84,6 @@ function DebugBacktraceDecode({ }} /> - - - {backtraceAddresses.map((x, idx) => ( -

  • - -
  • - ))} - - ); } From 911ea848df31f6950b66399d32395e616786f7aa Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Sat, 20 Apr 2024 12:31:20 +0100 Subject: [PATCH 16/26] Allow typing in the backtrace --- website/agb/src/app/crash/backtrace.tsx | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 051c1606..3dbe3e91 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -1,12 +1,13 @@ "use client"; +import { useState } from 'react'; + import { ContentBlock } from "../contentBlock"; -import { useClientValue } from "../useClientValue.hook"; import { styled } from "styled-components"; import { Debug } from "./debug"; export function BacktracePage() { - const backtrace = useClientValue(getBacktrace); + const [backtrace, setBacktrace] = useState(getBacktrace); return ( @@ -22,9 +23,7 @@ export function BacktracePage() { time.{" "} Send these to the creator of the game you are playing.

    - {(backtrace && ) || ( -

    No backtrace data in Url

    - )} +

    The owners of this website are not necessarily the creators of the @@ -44,10 +43,10 @@ export function BacktracePage() { ); } -function BacktraceCopyDisplay({ backtrace }: { backtrace: string }) { +function BacktraceCopyDisplay({ backtrace, setBacktrace }: { backtrace: string , setBacktrace: (newValue: string) => void}) { return ( - {backtrace} + setBacktrace(e.target.value)} value={backtrace} /> { navigator.clipboard.writeText(backtrace); @@ -59,11 +58,10 @@ function BacktraceCopyDisplay({ backtrace }: { backtrace: string }) { ); } -const BacktraceCodeBlock = styled.code` - font-size: 3rem; +const BacktraceInputBox = styled.input` + font-size: larger; background-color: #dddddd; - padding: 0px 40px; - overflow-x: scroll; + flex-grow: 999; `; const BacktraceWrapper = styled.section` @@ -76,7 +74,6 @@ const BacktraceWrapper = styled.section` const BacktraceCopyButton = styled.button` padding: 10px; - overflow-x: scroll; `; function getBacktrace() { From dcdbf1069e30435a7d96ea6b143707cc82d4747e Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Sat, 20 Apr 2024 12:33:24 +0100 Subject: [PATCH 17/26] The address isn't necessarily unique --- website/agb/src/app/crash/debug.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/website/agb/src/app/crash/debug.tsx b/website/agb/src/app/crash/debug.tsx index 27366b2d..4e33ce98 100644 --- a/website/agb/src/app/crash/debug.tsx +++ b/website/agb/src/app/crash/debug.tsx @@ -4,10 +4,6 @@ import { useMemo, useState } from "react"; const BacktraceListWrapper = styled.div` font-size: 1rem; - position: relative; - width: calc(100vw - 20px); - margin-left: calc(-1 * (100vw - 20px) / 2); - left: 50%; `; const BacktraceList = styled.ol` @@ -54,7 +50,7 @@ function DebugBacktraceDecode({ {backtraceAddresses.map((x, idx) => ( -

  • +
  • Date: Sat, 20 Apr 2024 12:38:44 +0100 Subject: [PATCH 18/26] Put the game developer summary in a details block --- website/agb/src/app/crash/backtrace.tsx | 10 +++------ .../src/app/crash/gameDeveloperSummary.tsx | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 website/agb/src/app/crash/gameDeveloperSummary.tsx diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index 3dbe3e91..c572252e 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import { ContentBlock } from "../contentBlock"; +import { GameDeveloperSummary } from './gameDeveloperSummary'; import { styled } from "styled-components"; import { Debug } from "./debug"; @@ -32,13 +33,7 @@ export function BacktracePage() {

    Backtrace

    {backtrace && } -

    For game developers

    -

    If you don't want players to be sent to this page, you can:

    -
      -
    1. Configure the backtrace page to point to your own site
    2. -
    3. Configure the backtrace page to not point to a site at all
    4. -
    5. Not use the backtrace feature
    6. -
    + ); } @@ -76,6 +71,7 @@ const BacktraceCopyButton = styled.button` padding: 10px; `; + function getBacktrace() { return window.location.hash.slice(1); } diff --git a/website/agb/src/app/crash/gameDeveloperSummary.tsx b/website/agb/src/app/crash/gameDeveloperSummary.tsx new file mode 100644 index 00000000..aca73d86 --- /dev/null +++ b/website/agb/src/app/crash/gameDeveloperSummary.tsx @@ -0,0 +1,21 @@ +import { styled } from "styled-components"; + +export const GameDeveloperSummary = () => { + return
    + For game developers +

    If you don't want players to be sent to this page, you can:

    +
      +
    1. Configure the backtrace page to point to your own site
    2. +
    3. Configure the backtrace page to not point to a site at all
    4. +
    5. Not use the backtrace feature
    6. +
    +
    +} + +const Details = styled.details` + margin-top: 10px; +`; + +const Summary = styled.summary` + font-weight: bold; +`; \ No newline at end of file From 141a77af80fe1662a5928b4833d02078350798b8 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Sat, 20 Apr 2024 12:40:07 +0100 Subject: [PATCH 19/26] Prettier --- website/agb/src/app/crash/backtrace.tsx | 25 +++++++++++++------ .../src/app/crash/gameDeveloperSummary.tsx | 24 ++++++++++-------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index c572252e..c67a906f 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -1,9 +1,9 @@ "use client"; -import { useState } from 'react'; +import { useState } from "react"; import { ContentBlock } from "../contentBlock"; -import { GameDeveloperSummary } from './gameDeveloperSummary'; +import { GameDeveloperSummary } from "./gameDeveloperSummary"; import { styled } from "styled-components"; import { Debug } from "./debug"; @@ -24,7 +24,7 @@ export function BacktracePage() { time.{" "} Send these to the creator of the game you are playing.

    - +

    The owners of this website are not necessarily the creators of the @@ -38,10 +38,21 @@ export function BacktracePage() { ); } -function BacktraceCopyDisplay({ backtrace, setBacktrace }: { backtrace: string , setBacktrace: (newValue: string) => void}) { +function BacktraceCopyDisplay({ + backtrace, + setBacktrace, +}: { + backtrace: string; + setBacktrace: (newValue: string) => void; +}) { return ( - setBacktrace(e.target.value)} value={backtrace} /> + setBacktrace(e.target.value)} + value={backtrace} + /> { navigator.clipboard.writeText(backtrace); @@ -55,7 +66,8 @@ function BacktraceCopyDisplay({ backtrace, setBacktrace }: { backtrace: string , const BacktraceInputBox = styled.input` font-size: larger; - background-color: #dddddd; + background-color: #eee; + flex-grow: 999; `; @@ -71,7 +83,6 @@ const BacktraceCopyButton = styled.button` padding: 10px; `; - function getBacktrace() { return window.location.hash.slice(1); } diff --git a/website/agb/src/app/crash/gameDeveloperSummary.tsx b/website/agb/src/app/crash/gameDeveloperSummary.tsx index aca73d86..d57c2746 100644 --- a/website/agb/src/app/crash/gameDeveloperSummary.tsx +++ b/website/agb/src/app/crash/gameDeveloperSummary.tsx @@ -1,16 +1,18 @@ import { styled } from "styled-components"; export const GameDeveloperSummary = () => { - return

    - For game developers -

    If you don't want players to be sent to this page, you can:

    -
      -
    1. Configure the backtrace page to point to your own site
    2. -
    3. Configure the backtrace page to not point to a site at all
    4. -
    5. Not use the backtrace feature
    6. -
    -
    -} + return ( +
    + For game developers +

    If you don't want players to be sent to this page, you can:

    +
      +
    1. Configure the backtrace page to point to your own site
    2. +
    3. Configure the backtrace page to not point to a site at all
    4. +
    5. Not use the backtrace feature
    6. +
    +
    + ); +}; const Details = styled.details` margin-top: 10px; @@ -18,4 +20,4 @@ const Details = styled.details` const Summary = styled.summary` font-weight: bold; -`; \ No newline at end of file +`; From 4899ccd5aa43df9f2c421f74460cd60fc47872f6 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Sat, 20 Apr 2024 12:48:31 +0100 Subject: [PATCH 20/26] Avoid awkward wrapping --- website/agb/src/app/crash/backtrace.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index c67a906f..af89b9ac 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -67,6 +67,9 @@ function BacktraceCopyDisplay({ const BacktraceInputBox = styled.input` font-size: larger; background-color: #eee; + border: 1px solid #aaa; + border-radius: 4px; + min-width: 0; flex-grow: 999; `; @@ -76,7 +79,6 @@ const BacktraceWrapper = styled.section` gap: 10px; justify-content: center; align-items: center; - flex-wrap: wrap; `; const BacktraceCopyButton = styled.button` From 8352d20d73ac78ee734e78d1e8b8ac38cf4ba609 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 20 Apr 2024 13:27:00 +0100 Subject: [PATCH 21/26] add errors for file decoding --- website/agb/src/app/crash/debug.tsx | 41 ++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/website/agb/src/app/crash/debug.tsx b/website/agb/src/app/crash/debug.tsx index 4e33ce98..5e036640 100644 --- a/website/agb/src/app/crash/debug.tsx +++ b/website/agb/src/app/crash/debug.tsx @@ -1,6 +1,6 @@ import { styled } from "styled-components"; import { AddressInfo, AgbDebug, useAgbDebug } from "../useAgbDebug.hook"; -import { useMemo, useState } from "react"; +import { ReactNode, useMemo, useState } from "react"; const BacktraceListWrapper = styled.div` font-size: 1rem; @@ -41,8 +41,15 @@ function DebugBacktraceDecode({ [] ); + const [backtraceLocationsError, setBacktraceLocationsError] = + useState(""); + if (typeof backtraceAddresses === "string") { - return ; + return ( + + Something went wrong decoding the backtrace: {backtraceAddresses} + + ); } return ( @@ -74,23 +81,33 @@ function DebugBacktraceDecode({ if (!files) return; const file = files[0]; if (!file) return; - loadLocations(debug, backtraceAddresses, file).then((data) => - setBacktraceLocations(data) - ); + setBacktraceLocationsError(""); + loadLocations(debug, backtraceAddresses, file) + .then((data) => setBacktraceLocations(data)) + .catch((e) => setBacktraceLocationsError(`${e}`)); }} /> + {backtraceLocationsError && ( + + Something went wrong looking up the addresses in the file provided:{" "} + {backtraceLocationsError} + + )} ); } -function DebugError({ error }: { error: string }) { - return ( - <> -

    Something went wrong decoding the backtrace

    -

    {error}

    - - ); +const ErrorBlock = styled.div` + background-color: #f78f8f; + border: 2px solid #9c0a0a; + border-radius: 8px; + padding: 20px; + margin-top: 10px; +`; + +function DebugError({ children }: { children: ReactNode }) { + return {children}; } function makeNicePath(path: string) { From 1adf31d140cbad1bff75ceb53fe674ada395c3ec Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 20 Apr 2024 13:30:14 +0100 Subject: [PATCH 22/26] report when there's no information for an address --- website/agb/src/app/crash/debug.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/website/agb/src/app/crash/debug.tsx b/website/agb/src/app/crash/debug.tsx index 5e036640..3765f14f 100644 --- a/website/agb/src/app/crash/debug.tsx +++ b/website/agb/src/app/crash/debug.tsx @@ -139,8 +139,19 @@ function BacktraceAddressInfo({ address: number; info: AddressInfo[] | undefined; }) { + const formattedAddress = `0x${address.toString(16).padStart(8, "0")}`; if (!info) { - return 0x{address.toString(16).padStart(8, "0")}; + return {formattedAddress}; + } + + if (info.length === 0) { + return ( + +
  • + (no info) {formattedAddress} +
  • + + ); } function FunctionName({ @@ -156,6 +167,8 @@ function BacktraceAddressInfo({ return functionName; } + console.log(info); + return ( {info.map((x, idx) => ( From bc689cbbd200900d53332f1291cb3fd947e2f122 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 20 Apr 2024 13:35:33 +0100 Subject: [PATCH 23/26] function style --- website/agb/src/app/crash/gameDeveloperSummary.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/agb/src/app/crash/gameDeveloperSummary.tsx b/website/agb/src/app/crash/gameDeveloperSummary.tsx index d57c2746..e67d9044 100644 --- a/website/agb/src/app/crash/gameDeveloperSummary.tsx +++ b/website/agb/src/app/crash/gameDeveloperSummary.tsx @@ -1,6 +1,6 @@ import { styled } from "styled-components"; -export const GameDeveloperSummary = () => { +export function GameDeveloperSummary() { return (
    For game developers @@ -12,7 +12,7 @@ export const GameDeveloperSummary = () => {
    ); -}; +} const Details = styled.details` margin-top: 10px; From 103085684a056be8a80d9ff4e1ee1c94976b83dd Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 20 Apr 2024 13:43:42 +0100 Subject: [PATCH 24/26] get value that depends on being run in the client in an effect --- website/agb/src/app/crash/backtrace.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index af89b9ac..fca85a71 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { ContentBlock } from "../contentBlock"; import { GameDeveloperSummary } from "./gameDeveloperSummary"; @@ -8,7 +8,10 @@ import { styled } from "styled-components"; import { Debug } from "./debug"; export function BacktracePage() { - const [backtrace, setBacktrace] = useState(getBacktrace); + const [backtrace, setBacktrace] = useState(""); + useEffect(() => { + setBacktrace(getBacktrace()); + }, []); return ( From 6aee9ba1544601152279aa726ea78c24985f1669 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 20 Apr 2024 15:38:55 +0100 Subject: [PATCH 25/26] placeholder --- website/agb/src/app/crash/backtrace.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/agb/src/app/crash/backtrace.tsx b/website/agb/src/app/crash/backtrace.tsx index fca85a71..589d1137 100644 --- a/website/agb/src/app/crash/backtrace.tsx +++ b/website/agb/src/app/crash/backtrace.tsx @@ -52,7 +52,7 @@ function BacktraceCopyDisplay({ setBacktrace(e.target.value)} value={backtrace} /> From 04f1d5c2ffe1752bf63ec60b3281c1c7b0507ad5 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 20 Apr 2024 21:49:05 +0100 Subject: [PATCH 26/26] use library functions to get address info --- agb-debug/src/main.rs | 88 +++++++++++-------------------------------- 1 file changed, 22 insertions(+), 66 deletions(-) diff --git a/agb-debug/src/main.rs b/agb-debug/src/main.rs index 00611148..9364a018 100644 --- a/agb-debug/src/main.rs +++ b/agb-debug/src/main.rs @@ -7,8 +7,7 @@ use std::{ time::SystemTime, }; -use addr2line::gimli; -use agb_debug::Location; +use agb_debug::{address_info, AddressInfo, Location}; use clap::Parser; use colored::Colorize; @@ -35,77 +34,46 @@ fn main() -> Result<(), Box> { let ctx = addr2line::Context::from_dwarf(dwarf)?; for (i, address) in agb_debug::gwilym_decode(&cli.dump)?.enumerate() { - print_address(&ctx, i, address.into(), modification_time)?; + let infos = address_info(&ctx, address.into())?; + for info in infos { + print_address_info(&info, i, modification_time)?; + } } Ok(()) } -fn print_address( - ctx: &addr2line::Context, +fn print_address_info( + info: &AddressInfo, index: usize, - address: u64, elf_modification_time: SystemTime, ) -> Result<(), Box> { - let mut frames = ctx.find_frames(address).skip_all_loads()?; + let function_name_to_print = &info.function; - let mut is_first = true; + if !info.is_inline { + print!("{index}:\t{function_name_to_print}"); + } else { + print!("\t(inlined into) {function_name_to_print}"); + } - while let Some(frame) = frames.next()? { - let function_name = if let Some(ref func) = frame.function { - func.demangle()?.into_owned() - } else { - "unknown function".to_string() - }; + println!( + " {}:{}", + prettify_path(&info.location.filename).green(), + info.location.line.to_string().green() + ); - 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(); - - let is_interesting = is_interesting_function(&function_name, &location.filename); - let function_name_to_print = if is_interesting { - function_name.bold() - } else { - function_name.normal() - }; - - if is_first { - print!("{index}:\t{function_name_to_print}"); - } else { - print!("\t(inlined into) {function_name_to_print}"); - } - - println!( - " {}:{}", - prettify_path(&location.filename).green(), - location.line.to_string().green() - ); - - if location.line != 0 && is_interesting { - print_line_of_code(&frame, location, elf_modification_time)?; - } - - is_first = false; + if info.location.line != 0 && info.is_interesting { + print_line_of_code(&info.location, elf_modification_time)?; } Ok(()) } fn print_line_of_code( - frame: &addr2line::Frame<'_, impl gimli::Reader>, - location: Location, + location: &Location, elf_modification_time: SystemTime, ) -> Result<(), Box> { - let Some(filename) = frame.location.as_ref().and_then(|location| location.file) else { - return Ok(()); - }; - + let filename = &location.filename; let Ok(mut file) = File::open(filename) else { return Ok(()); }; @@ -153,15 +121,3 @@ 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 -}