Skip to content

Commit

Permalink
tracer: Add a basic memory view
Browse files Browse the repository at this point in the history
Wired up in the disassembly view for potential addresses.
  • Loading branch information
oleavr committed Oct 6, 2024
1 parent c0bfb87 commit cdc13cc
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 42 deletions.
4 changes: 4 additions & 0 deletions apps/tracer/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@
.disassembly-view {
flex: 1;
}

.memory-view {
flex: 1;
}
13 changes: 12 additions & 1 deletion apps/tracer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DisassemblyView, { type DisassemblyTarget } from "./DisassemblyView.tsx";
import EventView from "./EventView.tsx";
import HandlerEditor from "./HandlerEditor.tsx";
import HandlerList from "./HandlerList.tsx";
import MemoryView from "./MemoryView.tsx";
import { useModel } from "./model.js";
import {
BlueprintProvider,
Expand Down Expand Up @@ -55,6 +56,7 @@ export default function App() {
const captureBacktracesSwitchRef = useRef<HTMLInputElement>(null);
const [selectedTabId, setSelectedTabId] = useState("events");
const [disassemblyTarget, setDisassemblyTarget] = useState<DisassemblyTarget>();
const [memoryLocation, setMemoryLocation] = useState<bigint | undefined>();

const connectionError = lostConnection
? <Callout
Expand Down Expand Up @@ -88,11 +90,19 @@ export default function App() {
handlers={handlers}
onSelectTarget={setDisassemblyTarget}
onSelectHandler={setSelectedHandlerId}
onSelectMemoryLocation={address => {
setSelectedTabId("memory");
setMemoryLocation(address);
}}
onAddInstructionHook={addInstructionHook}
onSymbolicate={symbolicate}
/>
);

const memoryView = (
<MemoryView address={memoryLocation} />
);

return (
<>
<Resplit.Root className="app-content" direction="vertical">
Expand Down Expand Up @@ -139,7 +149,7 @@ export default function App() {
setDisassemblyTarget({
type: (selectedHandler!.flavor === "insn") ? "instruction" : "function",
name: selectedHandler!.display_name,
address: selectedHandler!.address!
address: BigInt(selectedHandler!.address!)
});
}}
>
Expand Down Expand Up @@ -167,6 +177,7 @@ export default function App() {
<Tabs className="bottom-tabs" selectedTabId={selectedTabId} onChange={tabId => setSelectedTabId(tabId as string)} animate={false}>
<Tab id="events" title="Events" panel={eventView} panelClassName="bottom-tab-panel" />
<Tab id="disassembly" title="Disassembly" panel={disassemblyView} panelClassName="bottom-tab-panel" />
<Tab id="memory" title="Memory" panel={memoryView} panelClassName="bottom-tab-panel" />
</Tabs>
</Resplit.Pane>
</Resplit.Root>
Expand Down
137 changes: 99 additions & 38 deletions apps/tracer/src/DisassemblyView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface DisassemblyViewProps {
handlers: Handler[];
onSelectTarget: SelectTargetRequestHandler;
onSelectHandler: SelectHandlerRequestHandler;
onSelectMemoryLocation: SelectMemoryLocationRequestHandler;
onAddInstructionHook: AddInstructionHookRequestHandler;
onSymbolicate: SymbolicateRequestHandler;
}
Expand All @@ -18,23 +19,25 @@ export type DisassemblyTarget = FunctionTarget | InstructionTarget;
export interface FunctionTarget {
type: "function";
name?: string;
address: string;
address: bigint;
}

export interface InstructionTarget {
type: "instruction";
address: string;
address: bigint;
}

export type SelectTargetRequestHandler = (target: DisassemblyTarget) => void;
export type SelectHandlerRequestHandler = (id: HandlerId) => void;
export type SelectMemoryLocationRequestHandler = (address: bigint) => void;
export type AddInstructionHookRequestHandler = (address: bigint) => void;
export type SymbolicateRequestHandler = (addresses: bigint[]) => Promise<string[]>;

const ADDRESS_PATTERN = /\b0x[0-9a-f]+\b/;
const HEXLITERAL_PATTERN = /\b0x[0-9a-f]+\b/g;
const MINIMUM_PAGE_SIZE = 4096;

export default function DisassemblyView({ target, handlers, onSelectTarget, onSelectHandler, onAddInstructionHook, onSymbolicate }: DisassemblyViewProps) {
export default function DisassemblyView({ target, handlers, onSelectTarget, onSelectHandler, onSelectMemoryLocation, onAddInstructionHook,
onSymbolicate }: DisassemblyViewProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [data, setData] = useState<DisassemblyData | null>(null);
const seenAddressesRef = useRef(new Set<bigint>());
Expand Down Expand Up @@ -62,7 +65,7 @@ export default function DisassemblyView({ target, handlers, onSelectTarget, onSe
const addressesToResolve: bigint[] = [];
const seenAddresses = seenAddressesRef.current;
const { operations } = result;
for (const match of result.html.matchAll(new RegExp(ADDRESS_PATTERN, "g"))) {
for (const match of result.html.matchAll(HEXLITERAL_PATTERN)) {
const val = BigInt(match[0]);
if (val < MINIMUM_PAGE_SIZE || seenAddresses.has(val) || operations.has(val)) {
continue;
Expand Down Expand Up @@ -120,13 +123,37 @@ export default function DisassemblyView({ target, handlers, onSelectTarget, onSe
.split("<br />")
.map(line => {
let address: bigint | null = null;
line = line.replace(ADDRESS_PATTERN, rawAddress => {
address = BigInt(rawAddress);
const handler = handlerByAddress.get(address);
const attrs = (handler !== undefined)
? ` class="disassembly-address-has-handler" data-handler="${handler.id}"`
: "";
return `<a data-address="0x${address.toString(16)}" ${attrs}>${rawAddress}</a>`;
let branchTarget: { address: string, label: string } | null = null;

let n = 0;
line = line.replace(HEXLITERAL_PATTERN, rawValue => {
const value = BigInt(rawValue);
n++;

if (n === 1) {
address = value;

const op = operations.get(address);
if (op !== undefined) {
const jump = op.jump;
if (jump !== undefined) {
branchTarget = {
address: jump,
label: op.disasm.split("&nbsp;")[1]
};
}
}

const handler = handlerByAddress.get(value);
const attrs = (handler !== undefined)
? ` class="disassembly-address-has-handler" data-handler="${handler.id}"`
: "";
return `<a data-context="address" data-address="0x${value.toString(16)}" ${attrs}>${rawValue}</a>`;
} else if (branchTarget === null && value >= MINIMUM_PAGE_SIZE) {
return `<a data-context="value" data-value="0x${value.toString(16)}">${rawValue}</a>`;
}

return rawValue;
});

if (address !== null) {
Expand All @@ -136,7 +163,7 @@ export default function DisassemblyView({ target, handlers, onSelectTarget, onSe
if (targetAddress !== undefined) {
const targetLabel = op.disasm.split("&nbsp;")[1];
line = line.replace(targetLabel, _ => {
return `<a data-target="${targetAddress}" data-type="${op.type}">${targetLabel}</a>`;
return `<a data-context="branch" data-target="${targetAddress}" data-type="${op.type}">${targetLabel}</a>`;
});
}
}
Expand All @@ -148,7 +175,7 @@ export default function DisassemblyView({ target, handlers, onSelectTarget, onSe
setR2Output(lines);
}, [handlers, data]);

const handleAddressMenuClose = useCallback(() => {
const handleMenuClose = useCallback(() => {
hideContextMenu();

highlightedAddressAnchorRef.current!.classList.remove("disassembly-menu-open");
Expand Down Expand Up @@ -181,6 +208,30 @@ export default function DisassemblyView({ target, handlers, onSelectTarget, onSe
</Menu>
), [onSelectHandler]);

const unknownAddressMenu = useMemo(() => (
<Menu>
<MenuItem
icon="data-lineage"
text="View memory"
onClick={() => {
const address = BigInt(highlightedAddressAnchorRef.current!.getAttribute("data-value")!);
onSelectMemoryLocation(address);
}}
/>
<MenuItem
icon="code"
text="Disassemble"
onClick={() => {
const address = BigInt(highlightedAddressAnchorRef.current!.getAttribute("data-value")!);
onSelectTarget({
type: "instruction",
address
});
}}
/>
</Menu>
), [onSelectHandler]);

const handleAddressClick = useCallback((event: React.MouseEvent) => {
const target = event.target;
if (!(target instanceof HTMLAnchorElement)) {
Expand All @@ -189,33 +240,43 @@ export default function DisassemblyView({ target, handlers, onSelectTarget, onSe

event.preventDefault();

const branchTarget = target.getAttribute("data-target");
if (branchTarget !== null) {
const anchor = containerRef.current!.querySelector(`a[data-address="${branchTarget}"]`);
if (anchor !== null) {
anchor.scrollIntoView();
return;
const context = target.getAttribute("data-context")!;
switch (context) {
case "address":
case "value": {
showContextMenu({
content: (context === "address")
? (target.hasAttribute("data-handler") ? hookedAddressMenu : unhookedAddressMenu)
: unknownAddressMenu,
onClose: handleMenuClose,
targetOffset: {
left: event.clientX,
top: event.clientY
},
});

highlightedAddressAnchorRef.current = target;
target.classList.add("disassembly-menu-open");

break;
}
case "branch": {
const branchTarget = target.getAttribute("data-target")!;
const anchor = containerRef.current!.querySelector(`a[data-address="${branchTarget}"]`);
if (anchor !== null) {
anchor.scrollIntoView();
return;
}

onSelectTarget({
type: (target.getAttribute("data-type") === "call") ? "function" : "instruction",
address: branchTarget
});
return;
}
onSelectTarget({
type: (target.getAttribute("data-type") === "call") ? "function" : "instruction",
address: BigInt(branchTarget)
});

showContextMenu({
content: target.hasAttribute("data-handler") ? hookedAddressMenu : unhookedAddressMenu,
onClose: handleAddressMenuClose,
targetOffset: {
left: event.clientX,
top: event.clientY
},
});

highlightedAddressAnchorRef.current = target;
target.classList.add("disassembly-menu-open");
}, [handleAddressMenuClose, unhookedAddressMenu]);
break;
}
}
}, [handleMenuClose, unhookedAddressMenu]);

if (isLoading) {
return (
Expand Down
7 changes: 4 additions & 3 deletions apps/tracer/src/EventView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface EventViewProps {
}

export type EventActionHandler = (handlerId: HandlerId, eventIndex: number) => void;
export type DisassembleHandler = (address: string) => void;
export type DisassembleHandler = (address: bigint) => void;
export type SymbolicateHandler = (addresses: bigint[]) => Promise<string[]>;

const NON_BLOCKING_SPACE = "\u00A0";
Expand Down Expand Up @@ -106,7 +106,7 @@ export default function EventView({
<tr>
<td>Caller</td>
<td>
<Button onClick={() => onDisassemble(caller)}>{selectedCallerSymbol ?? caller}</Button>
<Button onClick={() => onDisassemble(BigInt(caller))}>{selectedCallerSymbol ?? caller}</Button>
</td>
</tr>
) : null
Expand All @@ -115,7 +115,8 @@ export default function EventView({
<tr>
<td>Backtrace</td>
<td>
{backtrace.map((address, i) => <Button key={address} alignText="left" onClick={() => onDisassemble(address)}>
{backtrace.map((address, i) =>
<Button key={address} alignText="left" onClick={() => onDisassemble(BigInt(address))}>
{(selectedBacktraceSymbols !== null) ? selectedBacktraceSymbols[i] : address}
</Button>)}
</td>
Expand Down
9 changes: 9 additions & 0 deletions apps/tracer/src/MemoryView.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.memory-view {
padding: 5px;
overflow: auto;
font-family: monospace;
font-size: 10px;
background-color: #1e1e1e;
color: #e4e4e4;
user-select: text;
}
49 changes: 49 additions & 0 deletions apps/tracer/src/MemoryView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import "./MemoryView.css";
import { Spinner } from "@blueprintjs/core";
import { useR2 } from "@frida/react-use-r2";
import { useEffect, useState } from "react";

export interface MemoryViewProps {
address?: bigint;
}

export default function MemoryView({ address }: MemoryViewProps) {
const [data, setData] = useState<string>("");
const [isLoading, setIsLoading] = useState(false);
const { executeR2Command } = useR2();

useEffect(() => {
if (address === undefined) {
return;
}

let ignore = false;
setIsLoading(true);

async function start() {
const data = await executeR2Command(`x @ 0x${address!.toString(16)}`);
if (ignore) {
return;
}

setData(data);
setIsLoading(false);
}

start();

return () => {
ignore = true;
};
}, [address, executeR2Command]);

if (isLoading) {
return (
<Spinner className="memory-view" />
);
}

return (
<div className="memory-view" dangerouslySetInnerHTML={{ __html: data }} />
);
}

0 comments on commit cdc13cc

Please sign in to comment.