move out backtrace decoding

This commit is contained in:
Corwin 2024-04-19 21:49:16 +01:00
parent 8433b23967
commit 04d368edf0
No known key found for this signature in database
4 changed files with 199 additions and 165 deletions

View file

@ -3,11 +3,11 @@
import { ContentBlock } from "../contentBlock"; import { ContentBlock } from "../contentBlock";
import { useClientValue } from "../useClientValue.hook"; import { useClientValue } from "../useClientValue.hook";
import { styled } from "styled-components"; import { styled } from "styled-components";
import { useEffect, useMemo, useState } from "react"; import { Debug } from "./debug";
import { useAgbDebug } from "../useAgbDebug.hook";
import { AddressInfo } from "../vendor/agb_wasm/agb_wasm";
export function BacktracePage() { export function BacktracePage() {
const backtrace = useClientValue(getBacktrace);
return ( return (
<ContentBlock> <ContentBlock>
<h1>agbrs crash backtrace viewer</h1> <h1>agbrs crash backtrace viewer</h1>
@ -22,7 +22,9 @@ export function BacktracePage() {
time.{" "} time.{" "}
<strong>Send these to the creator of the game you are playing.</strong> <strong>Send these to the creator of the game you are playing.</strong>
</p> </p>
<BacktraceDisplay /> {(backtrace && <BacktraceCopyDisplay backtrace={backtrace} />) || (
<p>No backtrace data in Url</p>
)}
<p> <p>
<em> <em>
The owners of this website are not necessarily the creators of the 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 additional debug files it can present you with the corresponding
function names, file names, line numbers, and column numbers. function names, file names, line numbers, and column numbers.
</p> </p>
<Backtrace /> <h2>Backtrace</h2>
{backtrace && <Debug encodedBacktrace={backtrace} />}
</ContentBlock> </ContentBlock>
); );
} }
const BacktraceListWrapper = styled.div` function BacktraceCopyDisplay({ backtrace }: { backtrace: string }) {
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<File[]>([]);
const backtraceAddressesList =
typeof backtraceAddresses === "object" ? backtraceAddresses : [];
const backtraceError =
typeof backtraceAddresses === "string" ? backtraceAddresses : undefined;
const backtraceLocations = useBacktraceLocations(
backtraceAddressesList,
files ?? []
);
return (
<details>
<summary>Addresses in the backtrace</summary>
<label>
Elf file or GBA file with debug information:
<input
type="file"
onChange={(evt) => {
const files = evt.target.files;
const filesArr = (files && Array.from(files)) ?? [];
setFile(filesArr);
}}
/>
</label>
<BacktraceListWrapper>
<BacktraceList>
{backtraceError}
{backtraceAddressesList.map((x, idx) => (
<li key={x}>
{backtraceLocations[idx] ? (
<BacktraceAddressInfo info={backtraceLocations[idx]} />
) : (
<code>0x{x.toString(16).padStart(8, "0")}</code>
)}
</li>
))}
</BacktraceList>
</BacktraceListWrapper>
</details>
);
}
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
? "<crate>"
: 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 <strong>{functionName}</strong>;
}
return functionName;
}
return (
<BacktraceAddressLine>
{info.map((x, idx) => (
<li key={idx}>
<code>
{x.is_inline && "(inlined into)"}{" "}
<FunctionName
interesting={x.is_interesting}
functionName={x.function_name}
/>{" "}
<GreenSpan>
{makeNicePath(x.filename)}:{x.line_number}:{x.column}
</GreenSpan>
</code>
</li>
))}
</BacktraceAddressLine>
);
}
function useBacktraceLocations(addresses: number[], file: File[]) {
const debug = useAgbDebug();
const [debugInfo, setDebugInfo] = useState<AddressInfo[][]>([]);
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) ?? "";
return ( return (
<BacktraceWrapper> <BacktraceWrapper>
<BacktraceCodeBlock>{backtrace}</BacktraceCodeBlock> <BacktraceCodeBlock>{backtrace}</BacktraceCodeBlock>

View file

@ -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 <DebugBacktraceDecode debug={debug} {...props} />;
} else {
return <p>Loading debug viewer...</p>;
}
}
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<AddressInfo[][]>(
[]
);
if (typeof backtraceAddresses === "string") {
return <DebugError error={backtraceAddresses} />;
}
return (
<>
<p>
If you add the elf file used to make the GBA file, or the GBA file
itself if it was made with <NonWrapCode>agb-gbafix --debug</NonWrapCode>
, you can see: function names, file names, line numbers, and column
numbers.
</p>
<label>
Elf file or GBA file with debug information:{" "}
<input
type="file"
onChange={(evt) => {
const files = evt.target.files;
if (!files) return;
const file = files[0];
if (!file) return;
loadLocations(debug, backtraceAddresses, file).then((data) =>
setBacktraceLocations(data)
);
}}
/>
</label>
<BacktraceListWrapper>
<BacktraceList>
{backtraceAddresses.map((x, idx) => (
<li key={x}>
<BacktraceAddressInfo
address={x}
info={backtraceLocations[idx]}
/>
</li>
))}
</BacktraceList>
</BacktraceListWrapper>
</>
);
}
function DebugError({ error }: { error: string }) {
return (
<>
<p>Something went wrong decoding the backtrace</p>
<p>{error}</p>
</>
);
}
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
? "<crate>"
: 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 <code>0x{address.toString(16).padStart(8, "0")}</code>;
}
function FunctionName({
interesting,
functionName,
}: {
interesting: boolean;
functionName: string;
}) {
if (interesting) {
return <strong>{functionName}</strong>;
}
return functionName;
}
return (
<BacktraceAddressLine>
{info.map((x, idx) => (
<li key={idx}>
<code>
{x.is_inline && "(inlined into)"}{" "}
<FunctionName
interesting={x.is_interesting}
functionName={x.function_name}
/>{" "}
<GreenSpan>
{makeNicePath(x.filename)}:{x.line_number}:{x.column}
</GreenSpan>
</code>
</li>
))}
</BacktraceAddressLine>
);
}
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]);
}

View file

@ -1,6 +1,5 @@
import { Metadata } from "next"; import { Metadata } from "next";
import { BacktracePage } from "./backtrace"; import { BacktracePage } from "./backtrace";
import { ContentBlock } from "../contentBlock";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "agbrs crash backtrace", title: "agbrs crash backtrace",

View file

@ -3,16 +3,19 @@ import debugInit, {
decode_backtrace, decode_backtrace,
DebugFile, DebugFile,
InitOutput, InitOutput,
} from "./vendor/agb_wasm/agb_wasm"; AddressInfo,
} from "./vendor/backtrace/backtrace";
let agbDebug: Promise<InitOutput> | undefined; let agbDebug: Promise<InitOutput> | undefined;
interface AgbDebug { export { AddressInfo };
export interface AgbDebug {
decode_backtrace: (backtrace: string) => Uint32Array; decode_backtrace: (backtrace: string) => Uint32Array;
debug_file: (file: Uint8Array) => DebugFile; debug_file: (file: Uint8Array) => DebugFile;
} }
export function useAgbDebug() { export function useAgbDebug(): AgbDebug | undefined {
const [debug, setDebug] = useState<AgbDebug>(); const [debug, setDebug] = useState<AgbDebug>();
useEffect(() => { useEffect(() => {