You've already forked retrowin32
mirror of
https://github.com/encounter/retrowin32.git
synced 2026-03-30 11:35:51 -07:00
308 lines
8.9 KiB
TypeScript
308 lines
8.9 KiB
TypeScript
import * as preact from 'preact';
|
|
import * as emulator from '../emulator';
|
|
import { Instruction } from '../glue/pkg/glue';
|
|
import { EmulatorComponent } from '../web';
|
|
import { BreakpointsComponent } from './break';
|
|
import { Code } from './code';
|
|
import { CPUs } from './cpus';
|
|
import { DirectDraw } from './ddraw';
|
|
import { Labels, parseCSV } from './labels';
|
|
import { Mappings } from './mappings';
|
|
import { Memory, MemoryView, Number } from './memory';
|
|
import { RegistersComponent } from './registers';
|
|
import { Stack } from './stack';
|
|
import { Tabs } from './tabs';
|
|
import { TraceComponent } from './trace';
|
|
|
|
namespace StartStop {
|
|
export interface Props {
|
|
running: boolean;
|
|
start(): void;
|
|
stop(): void;
|
|
step(): void;
|
|
stepOver(): void;
|
|
}
|
|
}
|
|
class StartStop extends preact.Component<StartStop.Props> {
|
|
render() {
|
|
const { running, start, stop, step, stepOver } = this.props;
|
|
return (
|
|
<span>
|
|
<button
|
|
onClick={() => running ? stop() : start()}
|
|
>
|
|
{running ? 'stop' : 'run'}
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => step()}
|
|
>
|
|
step
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => stepOver()}
|
|
>
|
|
step over
|
|
</button>
|
|
</span>
|
|
);
|
|
}
|
|
}
|
|
|
|
namespace Debugger {
|
|
export interface Props {
|
|
}
|
|
export interface State {
|
|
emulator?: emulator.Emulator;
|
|
stdout?: string;
|
|
error: string;
|
|
/** Initial address to show in memory pane. */
|
|
memBase: number;
|
|
/** Address to highlight in memory pane. */
|
|
memHighlight?: number;
|
|
/** When running, the setInterval id that is updating the UI. Note that Emulator.running has slightly different semantics. */
|
|
running?: number;
|
|
selectedTab: string;
|
|
labels: Labels;
|
|
}
|
|
}
|
|
export class Debugger extends preact.Component<Debugger.Props, Debugger.State> {
|
|
state: Debugger.State = { error: '', memBase: 0, labels: new Labels(), selectedTab: 'output' };
|
|
|
|
private async load() {
|
|
this.print('Loading...\n');
|
|
const host: emulator.Host = {
|
|
onWindowChanged: () => {
|
|
this.forceUpdate();
|
|
},
|
|
onError: (msg: string) => {
|
|
this.print(msg + '\n');
|
|
// Note: if this was a Rust panic, then the error will never display because
|
|
// rendering will fail due to the debugger UI accessing the Rust object after a panic.
|
|
//
|
|
// Even rendering synchronously as in the following won't work due to rendering
|
|
// accessing the object in a callback:
|
|
// preact.options.debounceRendering = (cb) => { cb(); };
|
|
// this.forceUpdate();
|
|
},
|
|
onStdOut: (stdout: string) => {
|
|
this.print(stdout);
|
|
},
|
|
onStopped: (status) => {
|
|
switch (status) {
|
|
case emulator.Status.Blocked:
|
|
return; // don't stop the UI on this
|
|
case emulator.Status.DebugBreak:
|
|
const bp = emu.breakpoints.isAtBreakpoint(emu.emu.cpu().eip);
|
|
if (bp) {
|
|
if (!bp.oneShot) {
|
|
this.setState({ selectedTab: 'breakpoints' });
|
|
}
|
|
}
|
|
break;
|
|
case emulator.Status.Exit:
|
|
this.print(`\nexited with code ${emu.emu.exit_code}`);
|
|
break;
|
|
}
|
|
this.stop(); // update the run/stop button etc.
|
|
},
|
|
onEvent: (event) => {
|
|
if (!this.state.running) {
|
|
console.warn('dropping event while not running');
|
|
return;
|
|
}
|
|
emu.enqueueEvent(event);
|
|
},
|
|
};
|
|
const emu = await emulator.load(host);
|
|
emu.emu.set_tracing_scheme('*');
|
|
|
|
const labels = emu.labels();
|
|
this.state.labels.load(labels);
|
|
|
|
this.setState({ stdout: undefined, emulator: emu });
|
|
|
|
this.loadLabelsCSV(emu).catch((e) => this.print(e.stack ?? e.toString()));
|
|
}
|
|
|
|
/** Load ghidra CSV, if any exists, into the label list. */
|
|
async loadLabelsCSV(emulator: emulator.Emulator) {
|
|
const resp = await fetch(emulator.storageKey + '.csv');
|
|
if (!resp.ok) {
|
|
return;
|
|
}
|
|
const labels = parseCSV(await resp.text());
|
|
this.state.labels.load(labels);
|
|
this.setState({ labels: this.state.labels });
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
this.load().catch((e) => this.print(e.stack ?? e.toString()));
|
|
}
|
|
|
|
private print(text: string) {
|
|
this.setState((state) => ({ stdout: (state.stdout ?? '') + text }));
|
|
}
|
|
|
|
private step = () => {
|
|
try {
|
|
this.state.emulator!.step();
|
|
} finally {
|
|
this.forceUpdate();
|
|
}
|
|
};
|
|
|
|
private start = () => {
|
|
if (this.state.running) return;
|
|
this.setState({
|
|
running: setInterval(() => {
|
|
this.forceUpdate();
|
|
}, 500),
|
|
});
|
|
this.state.emulator!.start();
|
|
};
|
|
|
|
private stop = () => {
|
|
if (!this.state.running) return;
|
|
this.state.emulator!.stop();
|
|
clearInterval(this.state.running);
|
|
this.setState({ running: undefined });
|
|
};
|
|
|
|
private runTo = (addr: number) => {
|
|
this.state.emulator!.breakpoints.addBreak({ addr, oneShot: true });
|
|
this.start();
|
|
};
|
|
|
|
private memoryView: MemoryView = {
|
|
highlightMemory: (addr: number) => this.setState({ memHighlight: addr }),
|
|
showMemory: (memBase: number) => this.setState({ selectedTab: 'memory', memBase }),
|
|
};
|
|
|
|
render() {
|
|
const { emulator, labels } = this.state;
|
|
|
|
const output = (
|
|
<code>
|
|
{this.state.stdout}
|
|
{this.state.error ? <div class='error'>ERROR: {this.state.error}</div> : null}
|
|
</code>
|
|
);
|
|
|
|
if (!emulator) {
|
|
return output;
|
|
}
|
|
|
|
// Note: disassemble() may cause allocations, invalidating any existing .memory()!
|
|
let instrs: Instruction[] = [];
|
|
const eip = emulator.emu.cpu().eip;
|
|
if (eip == 0xffff_fff0) {
|
|
instrs = [];
|
|
} else {
|
|
instrs = emulator.disassemble(eip);
|
|
}
|
|
return (
|
|
<>
|
|
<EmulatorComponent emulator={emulator} />
|
|
<section class='panel' style={{ display: 'flex', alignItems: 'baseline' }}>
|
|
<StartStop
|
|
running={this.state.running != null}
|
|
start={this.start}
|
|
stop={this.stop}
|
|
step={this.step}
|
|
stepOver={() => instrs ? this.runTo(instrs[1].addr) : this.step()}
|
|
/>
|
|
|
|
<div>
|
|
{emulator.emu.instr_count} instrs executed | {Math.floor(emulator.looper.stepsPerMs)}/ms
|
|
</div>
|
|
</section>
|
|
<div style={{ display: 'flex' }}>
|
|
<section class='code'>
|
|
<Code
|
|
instrs={instrs}
|
|
labels={labels}
|
|
{...this.memoryView}
|
|
runTo={this.runTo}
|
|
/>
|
|
</section>
|
|
<section>
|
|
<RegistersComponent
|
|
cpu={emulator.emu.cpu()}
|
|
{...this.memoryView}
|
|
/>
|
|
</section>
|
|
<section style={{ flex: 1, minWidth: 0, overflow: 'auto' }}>
|
|
<Stack
|
|
{...this.memoryView}
|
|
labels={labels}
|
|
emu={emulator.emu}
|
|
/>
|
|
</section>
|
|
</div>
|
|
<section style={{ flex: 1, minHeight: 0, display: 'flex' }}>
|
|
<Tabs
|
|
style={{ flex: 1 }}
|
|
tabs={{
|
|
output: () => output,
|
|
|
|
trace: () => <TraceComponent trace={emulator.trace} />,
|
|
|
|
cpus: () => <CPUs cpus={emulator.emu.cpus()} />,
|
|
|
|
memory: () => (
|
|
<Memory
|
|
mem={emulator.emu.memory()}
|
|
base={this.state.memBase}
|
|
highlight={this.state.memHighlight}
|
|
jumpTo={(addr) => this.setState({ memBase: Math.max(0, addr) })}
|
|
/>
|
|
),
|
|
mappings: () => (
|
|
<Mappings
|
|
mappings={emulator.mappings()}
|
|
highlight={this.state.memHighlight}
|
|
{...this.memoryView}
|
|
/>
|
|
),
|
|
|
|
imports: () => {
|
|
const labels = emulator.labels();
|
|
return (
|
|
<div style={{ minHeight: 0, flex: 1, overflow: 'auto' }}>
|
|
{labels.map(([addr, name]) => (
|
|
<div>
|
|
<code>
|
|
<Number digits={8} {...this.memoryView}>{addr}</Number>: {name}
|
|
</code>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
},
|
|
|
|
breakpoints: () => (
|
|
<BreakpointsComponent
|
|
breakpoints={emulator.breakpoints}
|
|
labels={labels}
|
|
highlight={eip}
|
|
{...this.memoryView}
|
|
/>
|
|
),
|
|
|
|
directdraw: () => <DirectDraw surfaces={emulator.emu.direct_draw_surfaces()} {...this.memoryView} />,
|
|
}}
|
|
selected={this.state.selectedTab}
|
|
switchTab={(selectedTab) => this.setState({ selectedTab })}
|
|
/>
|
|
</section>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
export function main() {
|
|
preact.render(<Debugger />, document.body);
|
|
}
|