A TypeScript library for x86 assembly interpretation, simulation, and debugging. It is part of a family of javascript assembly interpreters/simulators:
- MIPS: git repo, npm package
- RISC-V: git repo, npm package
- X86: git repo, npm package
- M68K: git repo, npm package
This library provides a JavaScript/TypeScript interface for interpreting and simulating x86 assembly code. It combines three powerful engines:
- Unicorn.js - For CPU emulation
- Keystone.js - For assembly
- Capstone.js - For disassembly
With this library, you can assemble, execute, and debug x86 instructions, manipulate registers and memory, and analyze program behavior at the instruction level.
The library has a Gzipped bundle size of around 1.5MB, uncompressed is around 9MB
- Assembly/Disassembly: Convert x86 assembly code to machine code and back
- Full Simulation: Execute assembled code with configurable limits and breakpoints
- Register Access: Read and write x86 registers (EAX, EBX, ECX, etc.)
- Memory Management: Allocate, read, and write memory
- Debugging Support: Single-step execution, breakpoints, and state inspection
- Flag Monitoring: Track condition flags (carry, zero, sign, overflow, etc.)
npm install @specy/x86import { X86Interpreter } from '@specy/x86';
// Create an interpreter with assembly code
const code = `
mov eax, 42
add ebx, eax
`;
// Create and initialize the interpreter
const interpreter = X86Interpreter.create(code);
interpreter.assemble();
interpreter.initialize();
// Execute the code
interpreter.simulate();
// Read results
const eaxValue = interpreter.getRegisterValue(X86Register.EAX);
const ebxValue = interpreter.getRegisterValue(X86Register.EBX);
console.log(`EAX: ${eaxValue}, EBX: ${ebxValue}`);
// Clean up resources
interpreter.dispose();// Create and initialize
const interpreter = X86Interpreter.create(assemblyCode);
interpreter.assemble();
interpreter.initialize();
// Execute one instruction at a time
while (!interpreter.isTerminated()) {
const instruction = interpreter.getNextStatement();
console.log(`Executing: ${instruction?.text}`);
// Show register values
const eax = interpreter.getRegisterValue(X86Register.EAX);
console.log(`EAX: 0x${eax.toString(16)}`);
// Execute one instruction
interpreter.step();
}
interpreter.dispose();// Set breakpoints at specific addresses
const breakpoints = [0x1010, 0x1020];
interpreter.simulateWithBreakpoints(breakpoints);
// Program execution will pause at each breakpoint
console.log("Execution paused at breakpoint");
console.log(`PC: 0x${interpreter.getProgramCounter().toString(16)}`);// Write bytes to memory
const bytes = [0x90, 0x90, 0x90]; // NOP instructions
interpreter.setMemoryBytes(0x2000, bytes);
// Read memory content
const memoryContent = interpreter.readMemoryBytes(0x2000, 3);
console.log("Memory content:", memoryContent);// After execution, check condition flags
const flags = interpreter.getConditionFlags();
console.log("Zero flag:", flags[X86ConditionFlags.Z]);
console.log("Carry flag:", flags[X86ConditionFlags.C]);The main class for x86 assembly interpretation and simulation.
constructor(code: string, assembler: any, interpreter: any, disambler: any)
create(code: string): X86Interpreter- Creates a new interpreter instance with the provided assembly code
assemble(): void- Assembles the code and prepares for interpretationinitialize(): void- Initializes memory and registers for the interpretersimulate(limit?: number): void- Simulates the execution of the codesimulateWithBreakpoints(breakpoints: number[], limit?: number): void- Simulates execution with specified breakpointsstep(): void- Executes a single instructiongetRegisterValue(register: X86Register): number- Gets the value of a registersetRegisterValue(register: X86Register, value: number): void- Sets the value of a registergetStackPointer(): number- Gets the current stack pointer valuegetProgramCounter(): number- Gets the current program counter valuegetRegistersValues(): number[]- Gets the values of all registersreadMemoryBytes(address: number, length: number): number[]- Reads bytes from memorysetMemoryBytes(address: number, bytes: number[]): void- Writes bytes to memorygetNextStatement(): X86Instruction | null- Gets the next instruction to be executedisTerminated(): boolean- Checks if the execution has terminatedgetConditionFlags(): Record<X86ConditionFlags, number>- Gets the current state of all condition flagsgetStatementAtAddress(address: number): X86Instruction | null- Gets the instruction at a specific addressgetStatementAtSourceLine(lineIndex: number): X86Instruction | null- Gets the instruction at a specific source linegetAssembledStatements(): X86Instruction[]- Gets all disassembled instructionsdispose(): void- Releases resources used by the interpreter
enum X86Register {
EAX = 19,
EBX = 21,
ECX = 22,
ESP = 30,
EBP = 20,
EDI = 23,
ESI = 29,
EDX = 24,
}enum X86ConditionFlags {
C = 0, // Carry flag
Z = 1, // Zero flag
S = 2, // Sign flag
O = 3, // Overflow flag
P = 4, // Parity flag
A = 5, // Auxiliary carry flag
}