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 { 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 (
<ContentBlock>
<h1>agbrs crash backtrace viewer</h1>
@ -22,7 +22,9 @@ export function BacktracePage() {
time.{" "}
<strong>Send these to the creator of the game you are playing.</strong>
</p>
<BacktraceDisplay />
{(backtrace && <BacktraceCopyDisplay backtrace={backtrace} />) || (
<p>No backtrace data in Url</p>
)}
<p>
<em>
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.
</p>
<Backtrace />
<h2>Backtrace</h2>
{backtrace && <Debug encodedBacktrace={backtrace} />}
</ContentBlock>
);
}
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<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) ?? "";
function BacktraceCopyDisplay({ backtrace }: { backtrace: string }) {
return (
<BacktraceWrapper>
<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 { BacktracePage } from "./backtrace";
import { ContentBlock } from "../contentBlock";
export const metadata: Metadata = {
title: "agbrs crash backtrace",

View file

@ -3,16 +3,19 @@ import debugInit, {
decode_backtrace,
DebugFile,
InitOutput,
} from "./vendor/agb_wasm/agb_wasm";
AddressInfo,
} from "./vendor/backtrace/backtrace";
let agbDebug: Promise<InitOutput> | 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<AgbDebug>();
useEffect(() => {