Skip to content

Commit

Permalink
Polishing disassembler.
Browse files Browse the repository at this point in the history
  • Loading branch information
tomusdrw committed Nov 30, 2024
1 parent 62d86ec commit ecdc088
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 37 deletions.
10 changes: 7 additions & 3 deletions assembly/api-generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { INSTRUCTIONS, MISSING_INSTRUCTION } from "./instructions";
import { Interpreter, Status } from "./interpreter";
import { Memory, MemoryBuilder } from "./memory";
import { Access, PAGE_SIZE } from "./memory-page";
import { Program, decodeArguments, decodeProgram } from "./program";
import { Program, decodeArguments, decodeProgram, liftBytes } from "./program";
import { NO_OF_REGISTERS, Registers } from "./registers";

export class InitialPage {
Expand Down Expand Up @@ -34,8 +34,12 @@ export class VmOutput {
}

export function getAssembly(p: Program): string {
let v = "";
const len = p.code.length;
if (len === 0) {
return "<seems that there is no code>";
}

let v = "";
for (let i = 0; i < len; i++) {
if (!p.mask.isInstruction(i)) {
throw new Error("We should iterate only over instructions!");
Expand Down Expand Up @@ -69,7 +73,7 @@ export function getAssembly(p: Program): string {
}

export function runVm(input: VmInput, logs: boolean = false): VmOutput {
const p = decodeProgram(input.program);
const p = decodeProgram(liftBytes(input.program));

const registers: Registers = new StaticArray(NO_OF_REGISTERS);
for (let r = 0; r < registers.length; r++) {
Expand Down
6 changes: 3 additions & 3 deletions assembly/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { Decoder } from "./codec";
import { Gas } from "./gas";
import { Interpreter, Status } from "./interpreter";
import { Access, PAGE_SIZE } from "./memory-page";
import { decodeProgram } from "./program";
import { decodeProgram, liftBytes } from "./program";
import { NO_OF_REGISTERS, Registers } from "./registers";

let interpreter: Interpreter | null = null;

export function resetGeneric(program: u8[], flatRegisters: u8[], initialGas: Gas): void {
const p = decodeProgram(program);
const p = decodeProgram(liftBytes(program));
const registers: Registers = new StaticArray(NO_OF_REGISTERS);
fillRegisters(registers, flatRegisters);
const int = new Interpreter(p, registers);
Expand All @@ -24,7 +24,7 @@ export function resetGenericWithMemory(
chunks: Uint8Array,
initialGas: Gas,
): void {
const p = decodeProgram(program);
const p = decodeProgram(liftBytes(program));
const registers: Registers = new StaticArray(NO_OF_REGISTERS);
fillRegisters(registers, flatRegisters);

Expand Down
25 changes: 24 additions & 1 deletion assembly/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ export class Decoder {
return this.offset >= this.source.length;
}

ensureBytes(need: u32): void {
private ensureBytes(need: u32): void {
if (this.offset + need > this.source.length) {
throw new Error(`Not enough bytes left. Need: ${need}, left: ${this.source.length - this.offset}`);
}
}

finish(): void {
if (!this.isExhausted()) {
throw new Error(`Expecting to use all bytes from the decoder. Left: ${this.source.length - this.offset}`);
}
}

varU32(): u32 {
this.ensureBytes(1);
const v = readVarU32(this.source.subarray(this.offset));
Expand All @@ -40,6 +46,23 @@ export class Decoder {
return v;
}

u16(): u16 {
this.ensureBytes(2);
let v: u16 = this.source[this.offset];
v |= u16(this.source[this.offset + 1]) << 8;
this.offset += 2;
return v;
}

u24(): u32 {
this.ensureBytes(3);
let v: u32 = this.source[this.offset];
v |= u32(this.source[this.offset + 1]) << 8;
v |= u32(this.source[this.offset + 2]) << 16;
this.offset += 3;
return v;
}

u32(): u32 {
this.ensureBytes(4);
let v: u32 = this.source[this.offset];
Expand Down
47 changes: 36 additions & 11 deletions assembly/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import { VmInput, getAssembly, runVm } from "./api-generic";
import { decodeProgram } from "./program";
import { decodeProgram, decodeSpi, liftBytes } from "./program";

export * from "./api";
export { runVm } from "./api-generic";

export function exampleGetAssembly(program: u8[]): string {
const p = decodeProgram(program);
return getAssembly(p);
export enum InputKind {
Generic = 0,
SPI = 1,
}

export function exampleRun(program: u8[]): void {
const input = new VmInput();
input.registers[7] = 9;
input.gas = 10_000;
input.program = program;
const output = runVm(input, true);
console.log(`Finished with status: ${output.status}`);
export function disassemble(input: u8[], kind: InputKind): string {
const program = liftBytes(input);
if (kind === InputKind.Generic) {
const p = decodeProgram(program);
return getAssembly(p);
}

if (kind === InputKind.SPI) {
const p = decodeSpi(program);
return getAssembly(p);
}

return `Unknown kind: ${kind}`;
}

export function run(input: u8[], kind: InputKind): void {
if (kind === InputKind.Generic) {
const vmInput = new VmInput();
vmInput.registers[7] = 9;
vmInput.gas = 10_000;
vmInput.program = input;

const output = runVm(vmInput, true);
console.log(`Finished with status: ${output.status}`);
return;
}

if (kind === InputKind.SPI) {
throw new Error("SPI running not supported yet");
}

throw new Error(`Unknown kind: ${kind}`);
}
38 changes: 34 additions & 4 deletions assembly/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,40 @@ import { INSTRUCTIONS, MISSING_INSTRUCTION } from "./instructions";

export type ProgramCounter = u32;

export function decodeProgram(program: u8[]): Program {
const p = new Uint8Array(program.length);
p.set(program, 0);
const decoder = new Decoder(p);
export function decodeSpi(data: Uint8Array): Program {
const decoder = new Decoder(data);

const roLength = decoder.u24();
const rwLength = decoder.u24();
const _heapPages = decoder.u16();
const _stackSize = decoder.u24();

const _roMem = decoder.bytes(roLength);
const _rwMem = decoder.bytes(rwLength);

const codeLength = decoder.u32();
const code = decoder.bytes(codeLength);
decoder.finish();

return decodeProgram(code);
}

export function liftBytes(data: u8[]): Uint8Array {
const p = new Uint8Array(data.length);
p.set(data, 0);
return p;
}

export function lowerBytes(data: Uint8Array): u8[] {
const r = new Array<u8>(data.length);
for (let i = 0; i < data.length; i++) {
r[i] = data[i];
}
return r;
}

export function decodeProgram(program: Uint8Array): Program {
const decoder = new Decoder(program);

// number of items in the jump table
const jumpTableLength = decoder.varU32();
Expand Down
29 changes: 25 additions & 4 deletions bin/disassemble.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function main() {

if (args.length === 0) {
console.error("Error: No JSON files provided.");
console.error("Usage: index.js <file1.json> [file2.json ...]");
console.error("Usage: index.js [--debug] <file1.json> [file2.json ...]");
process.exit(1);
}

Expand Down
100 changes: 90 additions & 10 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,110 @@
flex-direction: column;
gap: 1rem;
}
#file {
display: none;
}
button {
padding: 1rem;
cursor: pointer;
}
textarea {
padding: 1rem;
}
textarea.error {
border: 2px solid #e22;
}
pre {
border: 1px solid #ccc;
background: #ddd;
padding: 1rem;
}
#run {
background-color: #5c7;
}
.actions {
display: flex;
gap: 1rem;
flex-direction: row;
}
.actions > * {
flex: 1;
}
</style>
<script type="module" defer>
import { exampleGetAssembly, exampleRun } from "./build/release.js";
import { disassemble, run, InputKind } from "./build/release.js";
const $upload = document.querySelector('#upload');
const $code = document.querySelector('#code');
const $file = document.querySelector('#file');
const $run = document.querySelector('#run');
const $dis = document.querySelector('#disassemble');
const $out = document.querySelector('#output');
const $spi = document.querySelector('#spi');

$file.addEventListener('change', (ev) => {
if (!ev.target?.files?.length) {
return;
}

const f = new FileReader();
f.onload = (e) => {
const data = Array.from(new Uint8Array(e.target?.result));
$code.value = '0x' + data.map(b => b.toString(16).padStart(2, '0')).join('');
$spi.checked = true;
$run.click();
};
f.readAsArrayBuffer(ev.target.files[0]);
});

$upload.addEventListener('click', () => {
$file.click();
});
$run.addEventListener('click', () => {
$out.classList.remove('error');
execute(true);
});
$dis.addEventListener('click', () => {
execute(false);
});
$run.click();

function parseCode(code) {
if (code.startsWith('0x')) {
if (code.length % 2 !== 0) {
throw new Error('uneven number of nibbles');
}
const program = [];
for (let i = 2; i < code.length; i += 2) {
const v = `0x${code[i]}${code[i+1]}`;
const d = Number(v);
if (Number.isNaN(d)) {
throw new Error(`invalid hex value: ${v}`);
}
program.push(d);
}
return program;
}

return JSON.parse(code);
}

function execute(shouldRun = false) {
$code.classList.remove('error');
const markError = () => {
$out.classList.add('error');
$code.classList.add('error');
};
const isSpi = $spi.checked;
const kind = isSpi ? InputKind.SPI : InputKind.Generic;
let program;
try {
program = JSON.parse($code.value);
program = parseCode($code.value);
} catch (e) {
console.error(e);
$out.innerHTML = `Not a valid JSON: ${e}`;
$out.innerHTML = `Not a valid JSON or HEX: ${e}`;
markError();
return;
}
try {
const asm = exampleGetAssembly(program);
const asm = disassemble(program, kind);
$out.innerHTML = asm;
} catch (e) {
console.error(e);
Expand All @@ -67,16 +136,19 @@
return;
}

if (!shouldRun) {
return;
}

try {
exampleRun(program);
run(program, kind);
$out.innerHTML += `\n\n Run OK!`;
} catch (e) {
console.error(e);
$out.innerHTML += `\n\n Run error: ${e}`;
markError();
}
});
$run.click();
}
</script>
</head>
<body>
Expand All @@ -87,12 +159,20 @@ <h1>🍍 Anan-AS</h1>
or
<a href="./build/debug.wasm">debug.wasm</a>|<a href="./build/debug.wat">wat</a>
</p>
<label>
<input type="checkbox" id="spi"> JAM SPI
</label>
<textarea id="code" rows="10">
[
0, 0, 33, 4, 8, 1, 4, 9, 1, 5, 3, 0, 2, 119, 255, 7, 7, 12, 82, 138, 8, 152, 8, 82, 169, 5, 243, 82, 135, 4, 8, 4, 9, 17, 19, 0, 73, 147, 82, 213, 0
]
</textarea>
<button id="run">Disassemble &amp; Run</button>
<div class="actions">
<button id="upload">📂 Upload PVM file</button>
<input id="file" type="file" placeholder="Upload a PVM file" />
<button id="disassemble">🤖 Disassemble</button>
<button id="run">💻 Disassemble &amp; Run</button>
</div>
<pre id="output">
</pre>
<a href="https://pvm.fluffylabs.dev">Looking for a better disassembler for PVM?</a>
Expand Down

0 comments on commit ecdc088

Please sign in to comment.