mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
move out backtrace decoding
This commit is contained in:
parent
8433b23967
commit
04d368edf0
|
@ -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>
|
||||||
|
|
184
website/agb/src/app/crash/debug.tsx
Normal file
184
website/agb/src/app/crash/debug.tsx
Normal 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]);
|
||||||
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
Loading…
Reference in a new issue