@specy/x86 1.1.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,184 +1,118 @@
1
- # X86 Interpreter
2
- [![npm](https://img.shields.io/npm/v/@specy/x86.svg)](https://www.npmjs.com/package/@specy/x86)
3
-
4
- A TypeScript library for x86 assembly interpretation, simulation, and debugging.
5
-
6
- ## Overview
1
+ # blink-js
2
+
3
+ TypeScript wrapper around the [blink](https://github.com/jart/blink) x86-64 emulator, compiled to WebAssembly via Emscripten.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { createX86Emulator } from 'blink-js'
9
+
10
+ const emulator = await createX86Emulator({
11
+ callbacks: {
12
+ stdout: (charCode) => process.stdout.write(String.fromCharCode(charCode)),
13
+ stderr: (charCode) => process.stderr.write(String.fromCharCode(charCode)),
14
+ },
15
+ })
16
+
17
+ const result = await emulator.compile(`
18
+ .intel_syntax noprefix
19
+ .global _start
20
+ .text
21
+ _start:
22
+ mov rax, 60
23
+ xor rdi, rdi
24
+ syscall
25
+ `)
26
+
27
+ if (result.ok) {
28
+ await emulator.runUntilBlocked()
29
+ console.log(emulator.stopReason)
30
+ }
31
+ ```
7
32
 
8
- This library provides a JavaScript/TypeScript interface for interpreting and simulating x86 assembly code. It combines three powerful engines:
33
+ `createX86Emulator()` initializes and awaits the wasm module internally; no wasm URL or byte buffer needs to be passed by the caller.
9
34
 
10
- - **[Unicorn.js](https://github.com/AlexAltea/unicorn.js)** - For CPU emulation
11
- - **[Keystone.js](https://github.com/AlexAltea/keystone.js)** - For assembly
12
- - **[Capstone.js](https://github.com/AlexAltea/capstone.js)** - For disassembly
35
+ ### Compile and run
13
36
 
14
- With this library, you can assemble, execute, and debug x86 instructions, manipulate registers and memory, and analyze program behavior at the instruction level.
37
+ ```ts
38
+ // Assemble and link source, returns { ok, errors }
39
+ const result = await emulator.compile(sourceCode)
15
40
 
16
- ## Bundle size
17
- The library has a Gzipped bundle size of around 1.5MB, uncompressed is around 9MB
41
+ // Run until a breakpoint, syscall block, or instruction limit is hit
42
+ await emulator.run(limit, breakpoints)
18
43
 
19
- ## Features
44
+ // Convenience wrapper — runs with no limit and no breakpoints
45
+ await emulator.runUntilBlocked()
46
+ ```
20
47
 
21
- - **Assembly/Disassembly**: Convert x86 assembly code to machine code and back
22
- - **Full Simulation**: Execute assembled code with configurable limits and breakpoints
23
- - **Register Access**: Read and write x86 registers (EAX, EBX, ECX, etc.)
24
- - **Memory Management**: Allocate, read, and write memory
25
- - **Debugging Support**: Single-step execution, breakpoints, and state inspection
26
- - **Flag Monitoring**: Track condition flags (carry, zero, sign, overflow, etc.)
48
+ `run(limit, breakpoints)` accepts an optional maximum instruction count and an array of 0-based source-line breakpoint indices. Breakpoints are resolved to native instruction addresses using the DWARF line information emitted by GNU as.
27
49
 
28
- ## Installation
50
+ ### Check code without running
29
51
 
30
- ```bash
31
- npm install @specy/x86
52
+ ```ts
53
+ // Assembles and links but does not execute; updates the loaded program
54
+ const result = await emulator.checkCode(sourceCode)
32
55
  ```
33
56
 
34
- ## Basic Usage
57
+ ### Read registers and memory
35
58
 
36
- ```typescript
37
- import { X86Interpreter } from '@specy/x86';
59
+ ```ts
60
+ const rax = emulator.getRegisterValue('rax')
61
+ const bytes = emulator.readMemoryBytes(address, length)
62
+ ```
38
63
 
39
- // Create an interpreter with assembly code
40
- const code = `
41
- mov eax, 42
42
- add ebx, eax
43
- `;
64
+ ### Undo history and call stack
44
65
 
45
- // Create and initialize the interpreter
46
- const interpreter = X86Interpreter.create(code);
47
- interpreter.assemble();
48
- interpreter.initialize();
66
+ Undo recording is opt-in. Call `initialize(undoSize)` after loading or compiling a program and before execution. `undoSize` is the maximum number of reversible instructions kept in history; internally this is a fixed-size circular buffer, so older entries are discarded when the buffer fills.
49
67
 
50
- // Execute the code
51
- interpreter.simulate();
68
+ ```ts
69
+ const result = await emulator.compile(sourceCode)
70
+ if (!result.ok) throw new Error(result.errors[0]?.error ?? 'Assembly failed')
52
71
 
53
- // Read results
54
- const eaxValue = interpreter.getRegisterValue(X86Register.EAX);
55
- const ebxValue = interpreter.getRegisterValue(X86Register.EBX);
72
+ // Keep the last 128 reversible instructions.
73
+ emulator.initialize(128)
56
74
 
57
- console.log(`EAX: ${eaxValue}, EBX: ${ebxValue}`);
75
+ await emulator.step()
76
+ await emulator.step()
58
77
 
59
- // Clean up resources
60
- interpreter.dispose();
61
- ```
78
+ const [latestStep] = emulator.getUndoHistory(1)
79
+ console.log(latestStep?.pc, latestStep?.mutations)
62
80
 
63
- ## Step-by-Step Debugging
64
-
65
- ```typescript
66
- // Create and initialize
67
- const interpreter = X86Interpreter.create(assemblyCode);
68
- interpreter.assemble();
69
- interpreter.initialize();
70
-
71
- // Execute one instruction at a time
72
- while (!interpreter.isTerminated()) {
73
- const instruction = interpreter.getNextStatement();
74
- console.log(`Executing: ${instruction?.text}`);
75
-
76
- // Show register values
77
- const eax = interpreter.getRegisterValue(X86Register.EAX);
78
- console.log(`EAX: 0x${eax.toString(16)}`);
79
-
80
- // Execute one instruction
81
- interpreter.step();
81
+ if (emulator.canUndo()) {
82
+ emulator.undo()
82
83
  }
83
-
84
- interpreter.dispose();
85
84
  ```
86
85
 
87
- ## Breakpoint Support
88
-
89
- ```typescript
90
- // Set breakpoints at specific addresses
91
- const breakpoints = [0x1010, 0x1020];
92
- interpreter.simulateWithBreakpoints(breakpoints);
86
+ History entries include register writes, memory writes, flag changes, and call-stack mutations. `getUndoHistory(max)` returns the newest entries first, as `ExecutionStep[]`, which is useful for instruction-history UIs.
93
87
 
94
- // Program execution will pause at each breakpoint
95
- console.log("Execution paused at breakpoint");
96
- console.log(`PC: 0x${interpreter.getProgramCounter().toString(16)}`);
88
+ ```ts
89
+ for (const step of emulator.getUndoHistory(10)) {
90
+ console.log(step.pc, step.mutations)
91
+ }
97
92
  ```
98
93
 
99
- ## Memory Access
100
-
101
- ```typescript
102
- // Write bytes to memory
103
- const bytes = [0x90, 0x90, 0x90]; // NOP instructions
104
- interpreter.setMemoryBytes(0x2000, bytes);
105
-
106
- // Read memory content
107
- const memoryContent = interpreter.readMemoryBytes(0x2000, 3);
108
- console.log("Memory content:", memoryContent);
109
- ```
94
+ The call stack is tracked while history recording is enabled. Calls push frames and returns pop them; undo restores the previous call-stack snapshot along with registers, flags, PC, SP, and captured memory bytes.
110
95
 
111
- ## Flag Inspection
96
+ ```ts
97
+ emulator.initialize(64)
98
+ await emulator.run(20)
112
99
 
113
- ```typescript
114
- // After execution, check condition flags
115
- const flags = interpreter.getConditionFlags();
116
- console.log("Zero flag:", flags[X86ConditionFlags.Z]);
117
- console.log("Carry flag:", flags[X86ConditionFlags.C]);
100
+ const frames = emulator.getCallStack()
101
+ console.log(frames.map((frame) => frame.name))
118
102
  ```
119
103
 
120
- ## API Reference
121
-
122
- ### X86Interpreter Class
123
-
124
- The main class for x86 assembly interpretation and simulation.
125
-
126
- #### Constructor
127
-
128
- - `constructor(code: string, assembler: any, interpreter: any, disambler: any)`
129
-
130
- #### Static Methods
104
+ When history is enabled, `run()` uses traced stepping so each executed instruction can be undone. If history is disabled with `initialize(0)` or by never calling `initialize`, normal fast execution remains available and `canUndo()` returns `false`.
131
105
 
132
- - `create(code: string): X86Interpreter` - Creates a new interpreter instance with the provided assembly code
106
+ Very large or truncated memory writes may not be reversible. In that case `canUndo()` returns `false` for the latest step, and calling `undo()` throws instead of restoring a partial state.
133
107
 
134
- #### Methods
108
+ ### Events
135
109
 
136
- - `assemble(): void` - Assembles the code and prepares for interpretation
137
- - `initialize(): void` - Initializes memory and registers for the interpreter
138
- - `simulate(limit?: number): void` - Simulates the execution of the code
139
- - `simulateWithBreakpoints(breakpoints: number[], limit?: number): void` - Simulates execution with specified breakpoints
140
- - `step(): void` - Executes a single instruction
141
- - `getRegisterValue(register: X86Register): number` - Gets the value of a register
142
- - `setRegisterValue(register: X86Register, value: number): void` - Sets the value of a register
143
- - `getStackPointer(): number` - Gets the current stack pointer value
144
- - `getProgramCounter(): number` - Gets the current program counter value
145
- - `getRegistersValues(): number[]` - Gets the values of all registers
146
- - `readMemoryBytes(address: number, length: number): number[]` - Reads bytes from memory
147
- - `setMemoryBytes(address: number, bytes: number[]): void` - Writes bytes to memory
148
- - `getNextStatement(): X86Instruction | null` - Gets the next instruction to be executed
149
- - `isTerminated(): boolean` - Checks if the execution has terminated
150
- - `getConditionFlags(): Record<X86ConditionFlags, number>` - Gets the current state of all condition flags
151
- - `getStatementAtAddress(address: number): X86Instruction | null` - Gets the instruction at a specific address
152
- - `getStatementAtSourceLine(lineIndex: number): X86Instruction | null` - Gets the instruction at a specific source line
153
- - `getAssembledStatements(): X86Instruction[]` - Gets all disassembled instructions
154
- - `dispose(): void` - Releases resources used by the interpreter
155
-
156
- ### Enums
157
-
158
- #### X86Register
159
-
160
- ```typescript
161
- enum X86Register {
162
- EAX = 19,
163
- EBX = 21,
164
- ECX = 22,
165
- ESP = 30,
166
- EBP = 20,
167
- EDI = 23,
168
- ESI = 29,
169
- EDX = 24,
170
- }
110
+ ```ts
111
+ emulator.on('stateChange', (state) => { /* ... */ })
112
+ emulator.on('stdout', (charCode) => { /* ... */ })
113
+ emulator.on('stderr', (charCode) => { /* ... */ })
114
+ emulator.on('signal', (signal) => { /* ... */ })
115
+ emulator.on('inputRequest', () => { /* ... */ })
171
116
  ```
172
117
 
173
- #### X86ConditionFlags
174
-
175
- ```typescript
176
- enum X86ConditionFlags {
177
- C = 0, // Carry flag
178
- Z = 1, // Zero flag
179
- S = 2, // Sign flag
180
- O = 3, // Overflow flag
181
- P = 4, // Parity flag
182
- A = 5, // Auxiliary carry flag
183
- }
184
- ```
118
+ Callbacks passed to `createX86Emulator()` and handlers registered with `on()` may return either `void` or `Promise<void>`. Async callbacks are observed but not awaited by the emulator, so UI work can be scheduled without blocking execution. `stdin` remains synchronous because it is called directly by Emscripten's filesystem; for non-blocking UI input, listen for `inputRequest` and call `provideInput()` when the user submits text.