@specy/x86 2.0.5 → 2.2.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/LICENSE.md +39 -0
- package/README.md +117 -117
- 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/index.d.mts +371 -224
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +548 -69
- package/dist/index.mjs.map +1 -1
- package/dist/wasm/blinkenlib.d.ts +2 -2
- package/dist/wasm/blinkenlib.js +0 -0
- package/dist/wasm/nasm.d.mts +47 -0
- package/dist/wasm/nasm.mjs +2 -0
- package/dist/wasm/nasm.wasm +0 -0
- package/package.json +8 -1
- package/dist/assets/assemblers/nasm.3.00.elf +0 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright 2022 Justine Alexandra Roberts Tunney
|
|
4
|
+
Copyright 2024 Alberto Ventafridda
|
|
5
|
+
Copyright 2026 Specy
|
|
6
|
+
|
|
7
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
8
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
9
|
+
copyright notice and this permission notice appear in all copies.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
12
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
13
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
14
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
15
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
16
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
17
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
The three notices above are all carried by this package. The wrapper is the
|
|
22
|
+
work of Specy; `dist/index.mjs` inlines blink compiled to WebAssembly, which
|
|
23
|
+
is the work of Justine Tunney (https://github.com/jart/blink) as extended by
|
|
24
|
+
Alberto Ventafridda's libblink target. All three are under the ISC license
|
|
25
|
+
above.
|
|
26
|
+
|
|
27
|
+
## Bundled binaries
|
|
28
|
+
|
|
29
|
+
`dist/assets/assemblers` holds static x86-64 executables that the emulator
|
|
30
|
+
runs as guest programs. They are separate works under their own licenses,
|
|
31
|
+
which the ISC license above does not cover:
|
|
32
|
+
|
|
33
|
+
- `nasm.3.00.elf` - NASM 3.00, 2-clause BSD.
|
|
34
|
+
https://www.nasm.us
|
|
35
|
+
- `gnu-as.2.43.50.elf`, `gnu-ld.2.43.50.elf` - GNU Binutils 2.43.50, GPL-3.0
|
|
36
|
+
or later. https://sourceware.org/binutils
|
|
37
|
+
- `fasm.1.73.32.elf` - flat assembler 1.73.32, Copyright (c) 1999-2022 Tomasz
|
|
38
|
+
Grysztar, under its own permissive license.
|
|
39
|
+
https://flatassembler.net
|
package/README.md
CHANGED
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
# @specy/x86
|
|
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 '@specy/x86'
|
|
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
|
-
global _start
|
|
19
|
-
section .text
|
|
20
|
-
_start:
|
|
21
|
-
mov rax, 60
|
|
22
|
-
xor rdi, rdi
|
|
23
|
-
syscall
|
|
24
|
-
`)
|
|
25
|
-
|
|
26
|
-
if (result.ok) {
|
|
27
|
-
await emulator.runUntilBlocked()
|
|
28
|
-
console.log(emulator.stopReason)
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
`createX86Emulator()` initializes and awaits the wasm module internally; no wasm URL or byte buffer needs to be passed by the caller. NASM is the default assembler; pass `mode: 'GNU_trunk'` to use GNU as instead.
|
|
33
|
-
|
|
34
|
-
### Compile and run
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
// Assemble and link source, returns { ok, errors }
|
|
38
|
-
const result = await emulator.compile(sourceCode)
|
|
39
|
-
|
|
40
|
-
// Run until a breakpoint, syscall block, or instruction limit is hit
|
|
41
|
-
await emulator.run(limit, breakpoints)
|
|
42
|
-
|
|
43
|
-
// Convenience wrapper — runs with no limit and no breakpoints
|
|
44
|
-
await emulator.runUntilBlocked()
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
`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 the assembler.
|
|
48
|
-
|
|
49
|
-
### Check code without running
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
// Assembles and links but does not execute; updates the loaded program
|
|
53
|
-
const result = await emulator.checkCode(sourceCode)
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Read registers and memory
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
const rax = emulator.getRegisterValue('rax')
|
|
60
|
-
const bytes = emulator.readMemoryBytes(address, length)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Undo history and call stack
|
|
64
|
-
|
|
65
|
-
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.
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
const result = await emulator.compile(sourceCode)
|
|
69
|
-
if (!result.ok) throw new Error(result.errors[0]?.error ?? 'Assembly failed')
|
|
70
|
-
|
|
71
|
-
// Keep the last 128 reversible instructions.
|
|
72
|
-
emulator.initialize(128)
|
|
73
|
-
|
|
74
|
-
await emulator.step()
|
|
75
|
-
await emulator.step()
|
|
76
|
-
|
|
77
|
-
const [latestStep] = emulator.getUndoHistory(1)
|
|
78
|
-
console.log(latestStep?.pc, latestStep?.mutations)
|
|
79
|
-
|
|
80
|
-
if (emulator.canUndo()) {
|
|
81
|
-
emulator.undo()
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
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.
|
|
86
|
-
|
|
87
|
-
```ts
|
|
88
|
-
for (const step of emulator.getUndoHistory(10)) {
|
|
89
|
-
console.log(step.pc, step.mutations)
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
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.
|
|
94
|
-
|
|
95
|
-
```ts
|
|
96
|
-
emulator.initialize(64)
|
|
97
|
-
await emulator.run(20)
|
|
98
|
-
|
|
99
|
-
const frames = emulator.getCallStack()
|
|
100
|
-
console.log(frames.map((frame) => frame.name))
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
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`.
|
|
104
|
-
|
|
105
|
-
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.
|
|
106
|
-
|
|
107
|
-
### Events
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
emulator.on('stateChange', (state) => { /* ... */ })
|
|
111
|
-
emulator.on('stdout', (charCode) => { /* ... */ })
|
|
112
|
-
emulator.on('stderr', (charCode) => { /* ... */ })
|
|
113
|
-
emulator.on('signal', (signal) => { /* ... */ })
|
|
114
|
-
emulator.on('inputRequest', () => { /* ... */ })
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
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.
|
|
1
|
+
# @specy/x86
|
|
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 '@specy/x86'
|
|
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
|
+
global _start
|
|
19
|
+
section .text
|
|
20
|
+
_start:
|
|
21
|
+
mov rax, 60
|
|
22
|
+
xor rdi, rdi
|
|
23
|
+
syscall
|
|
24
|
+
`)
|
|
25
|
+
|
|
26
|
+
if (result.ok) {
|
|
27
|
+
await emulator.runUntilBlocked()
|
|
28
|
+
console.log(emulator.stopReason)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`createX86Emulator()` initializes and awaits the wasm module internally; no wasm URL or byte buffer needs to be passed by the caller. NASM is the default assembler; pass `mode: 'GNU_trunk'` to use GNU as instead.
|
|
33
|
+
|
|
34
|
+
### Compile and run
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// Assemble and link source, returns { ok, errors }
|
|
38
|
+
const result = await emulator.compile(sourceCode)
|
|
39
|
+
|
|
40
|
+
// Run until a breakpoint, syscall block, or instruction limit is hit
|
|
41
|
+
await emulator.run(limit, breakpoints)
|
|
42
|
+
|
|
43
|
+
// Convenience wrapper — runs with no limit and no breakpoints
|
|
44
|
+
await emulator.runUntilBlocked()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`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 the assembler.
|
|
48
|
+
|
|
49
|
+
### Check code without running
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// Assembles and links but does not execute; updates the loaded program
|
|
53
|
+
const result = await emulator.checkCode(sourceCode)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Read registers and memory
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const rax = emulator.getRegisterValue('rax')
|
|
60
|
+
const bytes = emulator.readMemoryBytes(address, length)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Undo history and call stack
|
|
64
|
+
|
|
65
|
+
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.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const result = await emulator.compile(sourceCode)
|
|
69
|
+
if (!result.ok) throw new Error(result.errors[0]?.error ?? 'Assembly failed')
|
|
70
|
+
|
|
71
|
+
// Keep the last 128 reversible instructions.
|
|
72
|
+
emulator.initialize(128)
|
|
73
|
+
|
|
74
|
+
await emulator.step()
|
|
75
|
+
await emulator.step()
|
|
76
|
+
|
|
77
|
+
const [latestStep] = emulator.getUndoHistory(1)
|
|
78
|
+
console.log(latestStep?.pc, latestStep?.mutations)
|
|
79
|
+
|
|
80
|
+
if (emulator.canUndo()) {
|
|
81
|
+
emulator.undo()
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
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.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
for (const step of emulator.getUndoHistory(10)) {
|
|
89
|
+
console.log(step.pc, step.mutations)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
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.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
emulator.initialize(64)
|
|
97
|
+
await emulator.run(20)
|
|
98
|
+
|
|
99
|
+
const frames = emulator.getCallStack()
|
|
100
|
+
console.log(frames.map((frame) => frame.name))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
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`.
|
|
104
|
+
|
|
105
|
+
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.
|
|
106
|
+
|
|
107
|
+
### Events
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
emulator.on('stateChange', (state) => { /* ... */ })
|
|
111
|
+
emulator.on('stdout', (charCode) => { /* ... */ })
|
|
112
|
+
emulator.on('stderr', (charCode) => { /* ... */ })
|
|
113
|
+
emulator.on('signal', (signal) => { /* ... */ })
|
|
114
|
+
emulator.on('inputRequest', () => { /* ... */ })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|