@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 +85 -151
- package/dist/assets/assemblers/fasm.1.73.32.elf +0 -0
- package/dist/assets/assemblers/gnu-as.2.43.50.elf +0 -0
- package/dist/assets/assemblers/gnu-ld.2.43.50.elf +0 -0
- package/dist/assets/assemblers/nasm.3.00.elf +0 -0
- package/dist/index.d.mts +566 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1541 -0
- package/dist/index.mjs.map +1 -0
- package/dist/wasm/blinkenlib.d.ts +3 -0
- package/dist/wasm/blinkenlib.js +2 -0
- package/package.json +27 -35
- package/LICENSE +0 -339
- package/dist/index.d.ts +0 -1558
- package/dist/index.js +0 -223
- package/dist/instructions.js +0 -1343
- package/dist/test.js +0 -10
- package/dist/utils.js +0 -5
- package/dist/x86/deps/capstone-x86.min.js +0 -1
- package/dist/x86/deps/keystone-x86.min.js +0 -1
- package/dist/x86/deps/unicorn-x86.min.js +0 -1
package/README.md
CHANGED
|
@@ -1,184 +1,118 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
```ts
|
|
38
|
+
// Assemble and link source, returns { ok, errors }
|
|
39
|
+
const result = await emulator.compile(sourceCode)
|
|
15
40
|
|
|
16
|
-
|
|
17
|
-
|
|
41
|
+
// Run until a breakpoint, syscall block, or instruction limit is hit
|
|
42
|
+
await emulator.run(limit, breakpoints)
|
|
18
43
|
|
|
19
|
-
|
|
44
|
+
// Convenience wrapper — runs with no limit and no breakpoints
|
|
45
|
+
await emulator.runUntilBlocked()
|
|
46
|
+
```
|
|
20
47
|
|
|
21
|
-
-
|
|
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
|
-
|
|
50
|
+
### Check code without running
|
|
29
51
|
|
|
30
|
-
```
|
|
31
|
-
|
|
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
|
-
|
|
57
|
+
### Read registers and memory
|
|
35
58
|
|
|
36
|
-
```
|
|
37
|
-
|
|
59
|
+
```ts
|
|
60
|
+
const rax = emulator.getRegisterValue('rax')
|
|
61
|
+
const bytes = emulator.readMemoryBytes(address, length)
|
|
62
|
+
```
|
|
38
63
|
|
|
39
|
-
|
|
40
|
-
const code = `
|
|
41
|
-
mov eax, 42
|
|
42
|
-
add ebx, eax
|
|
43
|
-
`;
|
|
64
|
+
### Undo history and call stack
|
|
44
65
|
|
|
45
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
//
|
|
54
|
-
|
|
55
|
-
const ebxValue = interpreter.getRegisterValue(X86Register.EBX);
|
|
72
|
+
// Keep the last 128 reversible instructions.
|
|
73
|
+
emulator.initialize(128)
|
|
56
74
|
|
|
57
|
-
|
|
75
|
+
await emulator.step()
|
|
76
|
+
await emulator.step()
|
|
58
77
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```
|
|
78
|
+
const [latestStep] = emulator.getUndoHistory(1)
|
|
79
|
+
console.log(latestStep?.pc, latestStep?.mutations)
|
|
62
80
|
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
console.log(
|
|
88
|
+
```ts
|
|
89
|
+
for (const step of emulator.getUndoHistory(10)) {
|
|
90
|
+
console.log(step.pc, step.mutations)
|
|
91
|
+
}
|
|
97
92
|
```
|
|
98
93
|
|
|
99
|
-
|
|
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
|
-
|
|
96
|
+
```ts
|
|
97
|
+
emulator.initialize(64)
|
|
98
|
+
await emulator.run(20)
|
|
112
99
|
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
+
### Events
|
|
135
109
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|