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(() => {