@specy/x86 1.0.0

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 ADDED
@@ -0,0 +1,180 @@
1
+ # X86 Interpreter
2
+
3
+ A TypeScript library for x86 assembly interpretation, simulation, and debugging.
4
+
5
+ ## Overview
6
+
7
+ This library provides a JavaScript/TypeScript interface for interpreting and simulating x86 assembly code. It combines three powerful engines:
8
+
9
+ - **Unicorn Engine** - For CPU emulation
10
+ - **Keystone Engine** - For assembly
11
+ - **Capstone Engine** - For disassembly
12
+
13
+ With this library, you can assemble, execute, and debug x86 instructions, manipulate registers and memory, and analyze program behavior at the instruction level.
14
+
15
+ ## Features
16
+
17
+ - **Assembly/Disassembly**: Convert x86 assembly code to machine code and back
18
+ - **Full Simulation**: Execute assembled code with configurable limits and breakpoints
19
+ - **Register Access**: Read and write x86 registers (EAX, EBX, ECX, etc.)
20
+ - **Memory Management**: Allocate, read, and write memory
21
+ - **Debugging Support**: Single-step execution, breakpoints, and state inspection
22
+ - **Flag Monitoring**: Track condition flags (carry, zero, sign, overflow, etc.)
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @specy/x86
28
+ ```
29
+
30
+ ## Basic Usage
31
+
32
+ ```typescript
33
+ import { X86Interpreter } from '@specy/x86';
34
+
35
+ // Create an interpreter with assembly code
36
+ const code = `
37
+ mov eax, 42
38
+ add ebx, eax
39
+ `;
40
+
41
+ // Create and initialize the interpreter
42
+ const interpreter = X86Interpreter.create(code);
43
+ interpreter.assemble();
44
+ interpreter.initialize();
45
+
46
+ // Execute the code
47
+ interpreter.simulate();
48
+
49
+ // Read results
50
+ const eaxValue = interpreter.getRegisterValue(X86Register.EAX);
51
+ const ebxValue = interpreter.getRegisterValue(X86Register.EBX);
52
+
53
+ console.log(`EAX: ${eaxValue}, EBX: ${ebxValue}`);
54
+
55
+ // Clean up resources
56
+ interpreter.dispose();
57
+ ```
58
+
59
+ ## Step-by-Step Debugging
60
+
61
+ ```typescript
62
+ // Create and initialize
63
+ const interpreter = X86Interpreter.create(assemblyCode);
64
+ interpreter.assemble();
65
+ interpreter.initialize();
66
+
67
+ // Execute one instruction at a time
68
+ while (!interpreter.isTerminated()) {
69
+ const instruction = interpreter.getNextStatement();
70
+ console.log(`Executing: ${instruction?.text}`);
71
+
72
+ // Show register values
73
+ const eax = interpreter.getRegisterValue(X86Register.EAX);
74
+ console.log(`EAX: 0x${eax.toString(16)}`);
75
+
76
+ // Execute one instruction
77
+ interpreter.step();
78
+ }
79
+
80
+ interpreter.dispose();
81
+ ```
82
+
83
+ ## Breakpoint Support
84
+
85
+ ```typescript
86
+ // Set breakpoints at specific addresses
87
+ const breakpoints = [0x1010, 0x1020];
88
+ interpreter.simulateWithBreakpoints(breakpoints);
89
+
90
+ // Program execution will pause at each breakpoint
91
+ console.log("Execution paused at breakpoint");
92
+ console.log(`PC: 0x${interpreter.getProgramCounter().toString(16)}`);
93
+ ```
94
+
95
+ ## Memory Access
96
+
97
+ ```typescript
98
+ // Write bytes to memory
99
+ const bytes = [0x90, 0x90, 0x90]; // NOP instructions
100
+ interpreter.setMemoryBytes(0x2000, bytes);
101
+
102
+ // Read memory content
103
+ const memoryContent = interpreter.readMemoryBytes(0x2000, 3);
104
+ console.log("Memory content:", memoryContent);
105
+ ```
106
+
107
+ ## Flag Inspection
108
+
109
+ ```typescript
110
+ // After execution, check condition flags
111
+ const flags = interpreter.getConditionFlags();
112
+ console.log("Zero flag:", flags[X86ConditionFlags.Z]);
113
+ console.log("Carry flag:", flags[X86ConditionFlags.C]);
114
+ ```
115
+
116
+ ## API Reference
117
+
118
+ ### X86Interpreter Class
119
+
120
+ The main class for x86 assembly interpretation and simulation.
121
+
122
+ #### Constructor
123
+
124
+ - `constructor(code: string, assembler: any, interpreter: any, disambler: any)`
125
+
126
+ #### Static Methods
127
+
128
+ - `create(code: string): X86Interpreter` - Creates a new interpreter instance with the provided assembly code
129
+
130
+ #### Methods
131
+
132
+ - `assemble(): void` - Assembles the code and prepares for interpretation
133
+ - `initialize(): void` - Initializes memory and registers for the interpreter
134
+ - `simulate(limit?: number): void` - Simulates the execution of the code
135
+ - `simulateWithBreakpoints(breakpoints: number[], limit?: number): void` - Simulates execution with specified breakpoints
136
+ - `step(): void` - Executes a single instruction
137
+ - `getRegisterValue(register: X86Register): number` - Gets the value of a register
138
+ - `setRegisterValue(register: X86Register, value: number): void` - Sets the value of a register
139
+ - `getStackPointer(): number` - Gets the current stack pointer value
140
+ - `getProgramCounter(): number` - Gets the current program counter value
141
+ - `getRegistersValues(): number[]` - Gets the values of all registers
142
+ - `readMemoryBytes(address: number, length: number): number[]` - Reads bytes from memory
143
+ - `setMemoryBytes(address: number, bytes: number[]): void` - Writes bytes to memory
144
+ - `getNextStatement(): X86Instruction | null` - Gets the next instruction to be executed
145
+ - `isTerminated(): boolean` - Checks if the execution has terminated
146
+ - `getConditionFlags(): Record<X86ConditionFlags, number>` - Gets the current state of all condition flags
147
+ - `getStatementAtAddress(address: number): X86Instruction | null` - Gets the instruction at a specific address
148
+ - `getStatementAtSourceLine(lineIndex: number): X86Instruction | null` - Gets the instruction at a specific source line
149
+ - `getAssembledStatements(): X86Instruction[]` - Gets all disassembled instructions
150
+ - `dispose(): void` - Releases resources used by the interpreter
151
+
152
+ ### Enums
153
+
154
+ #### X86Register
155
+
156
+ ```typescript
157
+ enum X86Register {
158
+ EAX = 19,
159
+ EBX = 21,
160
+ ECX = 22,
161
+ ESP = 30,
162
+ EBP = 20,
163
+ EDI = 23,
164
+ ESI = 29,
165
+ EDX = 24,
166
+ }
167
+ ```
168
+
169
+ #### X86ConditionFlags
170
+
171
+ ```typescript
172
+ enum X86ConditionFlags {
173
+ C = 0, // Carry flag
174
+ Z = 1, // Zero flag
175
+ S = 2, // Sign flag
176
+ O = 3, // Overflow flag
177
+ P = 4, // Parity flag
178
+ A = 5, // Auxiliary carry flag
179
+ }
180
+ ```
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@specy/x86",
3
+ "version": "1.0.0",
4
+ "description": "An x86 interpreter for Javascript",
5
+ "homepage": "https://github.com/Specy/x86-js#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/Specy/x86-js/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Specy/x86-js.git"
12
+ },
13
+ "license": "GPL-2.0",
14
+ "author": "Specy",
15
+ "type": "module",
16
+ "module": "/dist/index.js",
17
+ "types": "/dist/index.d.ts",
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm --dts --clean --minify",
20
+ "start": "npx tsx src/index.ts",
21
+ "test": "npx tsx src/test.ts"
22
+ },
23
+ "devDependencies": {
24
+ "tsup": "^8.4.0"
25
+ },
26
+ "dependencies": {
27
+ "@types/node": "^22.13.14",
28
+ "typescript": "^5.8.3"
29
+ }
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,448 @@
1
+ import {uc} from './x86/deps/unicorn-x86.min'
2
+ import {ks} from './x86/deps/keystone-x86.min'
3
+ import {cs} from './x86/deps/capstone-x86.min'
4
+ import {X86InstructionCode, X86InstructionName, X86InstructionNames} from "./instructions";
5
+
6
+ export {
7
+ X86InstructionNames,
8
+ X86InstructionCode,
9
+ X86InstructionName,
10
+ }
11
+
12
+ /**
13
+ * Enumeration of x86 condition flags
14
+ */
15
+ export enum X86ConditionFlags {
16
+ C = 0, // Carry flag
17
+ Z = 1, // Zero flag
18
+ S = 2, // Sign flag
19
+ O = 3, // Overflow flag
20
+ P = 4, // Parity flag
21
+ A = 5, // Auxiliary carry flag
22
+ }
23
+
24
+ /**
25
+ * Enumeration of x86 registers and their corresponding values
26
+ */
27
+ export enum X86Register {
28
+ EAX = 19,
29
+ EBX = 21,
30
+ ECX = 22,
31
+ ESP = 30,
32
+ EBP = 20,
33
+ EDI = 23,
34
+ ESI = 29,
35
+ EDX = 24,
36
+ }
37
+
38
+ /**
39
+ * Returns the keys of an enum
40
+ * @template T - Enum type
41
+ * @param {T} e - Enum object
42
+ * @returns {string[]} Array of enum keys
43
+ */
44
+ function enumKeys<T extends object>(e: T) {
45
+ const keys = Object.keys(e)
46
+ const isStringEnum = isNaN(Number(keys[0]))
47
+ return isStringEnum ? keys : keys.slice(keys.length / 2)
48
+ }
49
+
50
+ /** Array of all x86 register names */
51
+ export const X86_REGISTERS = enumKeys(X86Register)
52
+
53
+ /** Stack pointer register identifier */
54
+ export const X86_STACK_POINTER_REGISTER = X86Register.ESP;
55
+
56
+ /** Program counter register identifier (EIP) */
57
+ export const X86_PROGRAM_COUNTER_REGISTER = 26 //X86_REG_EIP
58
+
59
+ /** Flags register identifier (EFLAGS) */
60
+ export const X86_FLAGS_REGISTER = 25; //X86_REG_EFLAGS
61
+
62
+ /**
63
+ * Converts a signed 32-bit integer to an unsigned 32-bit integer
64
+ * @param {number} value - Signed 32-bit integer
65
+ * @returns {number} Unsigned 32-bit integer
66
+ */
67
+ function signed32ToUnsigned32(value: number): number {
68
+ if (value < 0) {
69
+ return value + Math.pow(2, 32);
70
+ }
71
+ return value;
72
+ }
73
+
74
+ /**
75
+ * Represents a single x86 instruction
76
+ */
77
+ export type X86Instruction = {
78
+ /** Memory address of the instruction */
79
+ address: number;
80
+ /** Human-readable text representation of the instruction */
81
+ text: string;
82
+ /** Mnemonic name of the instruction */
83
+ name: string;
84
+ /** Instruction identifier */
85
+ id: X86InstructionCode;
86
+ /** Raw bytes of the instruction */
87
+ bytes: number[];
88
+ /** Line number in the source code */
89
+ line: number;
90
+ /** Size of the instruction in bytes */
91
+ size: number;
92
+ }
93
+
94
+ //TODO
95
+ type HistoryEntry = {}
96
+
97
+ /**
98
+ * Calculates the required page size for memory allocation
99
+ * @param {number} size - Required memory size
100
+ * @returns {number} Padded size aligned to page boundaries (4KB)
101
+ */
102
+ function calculatePageSize(size: number): number {
103
+ const pageSize = 4096; // 4KB
104
+ return Math.ceil(size / pageSize) * pageSize;
105
+ }
106
+
107
+ /**
108
+ * Simplifies error objects to string messages
109
+ * @param {any} e - Error object or string
110
+ * @returns {string} Simplified error message
111
+ */
112
+ function simplifyError(e: any) {
113
+ return typeof e === 'string' ? e : e.message
114
+ }
115
+
116
+ /**
117
+ * X86 assembly interpreter using Unicorn, Keystone, and Capstone engines
118
+ */
119
+ export class X86Interpreter {
120
+
121
+ /**
122
+ * Creates a new X86Interpreter instance
123
+ * @param {string} code - Assembly code to interpret
124
+ * @param {any} assembler - Keystone assembler instance
125
+ * @param {any} interpreter - Unicorn interpreter instance
126
+ * @param {any} disambler - Capstone disassembler instance
127
+ */
128
+ constructor(
129
+ private readonly code: string,
130
+ private readonly assembler: any,
131
+ private readonly interpreter: any,
132
+ private readonly disambler: any,
133
+ ) {
134
+ }
135
+
136
+ /** Starting address for the code segment */
137
+ static START_ADDRESS = 0x1000;
138
+ /** Starting address for the stack segment */
139
+ static STACK_START_ADDRESS = 0xf0000;
140
+ /** Size of the stack segment */
141
+ static STACK_SIZE = 0xf0000;
142
+ /** Buffer containing assembled code bytes */
143
+ private codeBuffer: number[] | null = null;
144
+ /** Array of disassembled instructions */
145
+ private instructions: X86Instruction[] = []
146
+ /** Address following the last instruction */
147
+ private endAddress: number = 0;
148
+
149
+ /**
150
+ * Hook callback for the interpreter
151
+ * @param {any} handle - Hook handle
152
+ * @param {number} addr_lo - Lower address bound
153
+ * @param {number} addr_hi - Higher address bound
154
+ * @param {number} size - Size of the region
155
+ * @param {any} user_data - User data
156
+ */
157
+ private hook = (handle, addr_lo, addr_hi, size, user_data) => {
158
+
159
+ }
160
+
161
+ /**
162
+ * Gets the disassembled instructions from the code buffer
163
+ * @returns {any[]} Array of disassembled instructions
164
+ * @throws {Error} If disassembly fails
165
+ */
166
+ private getInstructions() {
167
+ try {
168
+ return this.disambler.disasm(this.codeBuffer, X86Interpreter.START_ADDRESS) as any[]
169
+ } catch (e) {
170
+ throw new Error(simplifyError(e));
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Assembles the code and prepares for interpretation
176
+ * @throws {Error} If assembly fails
177
+ */
178
+ assemble() {
179
+ try {
180
+ this.codeBuffer = this.assembler.asm(this.code);
181
+ const instructions = this.getInstructions();
182
+ this.instructions = instructions.map((ins, i) => {
183
+ return {
184
+ address: ins.address,
185
+ bytes: ins.bytes,
186
+ size: ins.size,
187
+ id: ins.id,
188
+ text: `${ins.mnemonic} ${ins.op_str}`,
189
+ name: ins.mnemonic,
190
+ line: i,
191
+ }
192
+ })
193
+ this.endAddress = X86Interpreter.START_ADDRESS + this.codeBuffer.length;
194
+ } catch (e) {
195
+ throw new Error(simplifyError(e));
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Initializes memory and registers for the interpreter
201
+ * @throws {Error} If code buffer is not initialized
202
+ */
203
+ initialize() {
204
+ if (!this.codeBuffer) {
205
+ throw new Error('Code buffer is not initialized. Please call assemble() first.');
206
+ }
207
+ this.interpreter.mem_map(X86Interpreter.START_ADDRESS, calculatePageSize(this.codeBuffer.length), uc.PROT_ALL);
208
+ this.interpreter.mem_write(X86Interpreter.START_ADDRESS, this.codeBuffer);
209
+ this.interpreter.mem_map(X86Interpreter.STACK_START_ADDRESS, calculatePageSize(X86Interpreter.STACK_SIZE), uc.PROT_ALL);
210
+ this.interpreter.reg_write_i32(X86Register.ESP, X86Interpreter.STACK_START_ADDRESS + X86Interpreter.STACK_SIZE);
211
+ this.interpreter.reg_write_i32(X86Register.EBP, X86Interpreter.STACK_START_ADDRESS + X86Interpreter.STACK_SIZE);
212
+ this.interpreter.reg_write_i32(X86_PROGRAM_COUNTER_REGISTER, X86Interpreter.START_ADDRESS);
213
+ }
214
+
215
+ /**
216
+ * Simulates the execution of the code
217
+ * @param {number} [limit] - Optional instruction execution limit
218
+ * @throws {Error} If simulation fails
219
+ */
220
+ simulate(limit?: number) {
221
+ try {
222
+ //TODO add tracing
223
+ const address = this.getProgramCounter();
224
+ this.interpreter.emu_start(address, this.endAddress, 0, limit);
225
+ } catch (e) {
226
+ throw new Error(simplifyError(e));
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Simulates execution with specified breakpoints
232
+ * @param {number[]} breakpoints - Array of addresses to break at
233
+ * @param {number} [limit] - Optional instruction execution limit
234
+ * @throws {Error} If simulation fails
235
+ */
236
+ simulateWithBreakpoints(breakpoints: number[], limit?: number) {
237
+ const breakpointsMap = new Map(breakpoints.map((bp) => [bp, true]));
238
+ const handle = this.interpreter.hook_add(uc.HOOK_CODE, (_, address: number) => {
239
+ if (breakpointsMap.has(address)) {
240
+ this.interpreter.emu_stop();
241
+ }
242
+ });
243
+ try {
244
+ this.simulate(limit);
245
+ } catch (e) {
246
+ throw new Error(simplifyError(e));
247
+ } finally {
248
+ this.interpreter.hook_del(handle);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Executes a single instruction
254
+ */
255
+ step() {
256
+ this.simulate(1);
257
+ }
258
+
259
+ /**
260
+ * Gets the value of a register
261
+ * @param {X86Register} register - Register to read
262
+ * @returns {number} Unsigned 32-bit register value
263
+ * @throws {Error} If reading the register fails
264
+ */
265
+ getRegisterValue(register: X86Register) {
266
+ try {
267
+ const signed = this.interpreter.reg_read_i32(register);
268
+ return signed32ToUnsigned32(signed);
269
+ } catch (e) {
270
+ throw new Error(simplifyError(e));
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Sets the value of a register
276
+ * @param {X86Register} register - Register to write
277
+ * @param {number} value - Value to set
278
+ * @throws {Error} If writing to the register fails
279
+ */
280
+ setRegisterValue(register: X86Register, value: number) {
281
+ try {
282
+ this.interpreter.reg_write_i32(register, value);
283
+ } catch (e) {
284
+ throw new Error(simplifyError(e));
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Gets the current stack pointer value
290
+ * @returns {number} Stack pointer value
291
+ */
292
+ getStackPointer() {
293
+ return this.getRegisterValue(X86_STACK_POINTER_REGISTER);
294
+ }
295
+
296
+ /**
297
+ * Gets the current program counter value
298
+ * @returns {number} Program counter value
299
+ */
300
+ getProgramCounter() {
301
+ return this.getRegisterValue(X86_PROGRAM_COUNTER_REGISTER as X86Register);
302
+ }
303
+
304
+ /**
305
+ * Gets the values of all registers
306
+ * @returns {number[]} Array of register values
307
+ */
308
+ getRegistersValues(): number[] {
309
+ return X86_REGISTERS.map((reg) => {
310
+ return this.getRegisterValue(X86Register[reg as keyof typeof X86Register]);
311
+ })
312
+ }
313
+
314
+ /**
315
+ * Reads bytes from memory
316
+ * @param {number} address - Memory address to read from
317
+ * @param {number} length - Number of bytes to read
318
+ * @returns {number[]} Array of byte values
319
+ * @throws {Error} If reading memory fails
320
+ */
321
+ readMemoryBytes(address: number, length: number) {
322
+ try {
323
+ return [...this.interpreter.mem_read(address, length)];
324
+ } catch (e) {
325
+ throw new Error(simplifyError(e));
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Writes bytes to memory
331
+ * @param {number} address - Memory address to write to
332
+ * @param {number[]} bytes - Array of byte values to write
333
+ */
334
+ setMemoryBytes(address: number, bytes: number[]) {
335
+ try {
336
+ this.interpreter.mem_write(address, bytes);
337
+ } catch (e) {
338
+ // Error handling is missing here
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Gets the next instruction to be executed
344
+ * @returns {X86Instruction | null} Next instruction or null if no more instructions
345
+ */
346
+ getNextStatement(): X86Instruction | null {
347
+ const pc = this.getProgramCounter();
348
+ const instruction = this.instructions.find((ins) => ins.address === pc);
349
+ return instruction ?? null;
350
+ }
351
+
352
+ /**
353
+ * Checks if the execution has terminated (no more instructions)
354
+ * @returns {boolean} True if execution is terminated
355
+ */
356
+ isTerminated() {
357
+ return this.getNextStatement() === null;
358
+ }
359
+
360
+ /**
361
+ * Gets the current state of all condition flags
362
+ * @returns {Record<X86ConditionFlags, number>} Object mapping flags to their values (0 or 1)
363
+ */
364
+ getConditionFlags(): Record<X86ConditionFlags, number> {
365
+ const flags = this.interpreter.reg_read_i32(X86_FLAGS_REGISTER) as number;
366
+ return {
367
+ [X86ConditionFlags.C]: (flags >> 0) & 1,
368
+ [X86ConditionFlags.P]: (flags >> 2) & 1,
369
+ [X86ConditionFlags.A]: (flags >> 4) & 1,
370
+ [X86ConditionFlags.Z]: (flags >> 6) & 1,
371
+ [X86ConditionFlags.S]: (flags >> 7) & 1,
372
+ [X86ConditionFlags.O]: (flags >> 11) & 1,
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Gets the instruction at a specific address
378
+ * @param {number} address - Memory address
379
+ * @returns {X86Instruction | null} Instruction at the address or null if not found
380
+ */
381
+ getStatementAtAddress(address: number): X86Instruction | null {
382
+ const instruction = this.instructions.find((ins) => ins.address === address);
383
+ return instruction ?? null;
384
+ }
385
+
386
+ /**
387
+ * Gets the source code as a string
388
+ * @returns {string} Source code
389
+ */
390
+ getSource() {
391
+ this.instructions.map((ins) => ins.text).join('/n')
392
+ }
393
+
394
+ /**
395
+ * Gets the instruction at a specific source line
396
+ * @param {number} lineIndex - Line index in the source code
397
+ * @returns {X86Instruction | null} Instruction at the line or null if not found
398
+ */
399
+ getStatementAtSourceLine(lineIndex: number): X86Instruction | null {
400
+ return this.instructions[lineIndex] ?? null;
401
+ }
402
+
403
+ /*
404
+ getCallStack() {
405
+ throw new Error('Method not implemented.');
406
+ }
407
+ */
408
+
409
+ /**
410
+ * Gets all disassembled instructions
411
+ * @returns {X86Instruction[]} Array of all instructions
412
+ */
413
+ getAssembledStatements() {
414
+ return this.instructions
415
+ }
416
+
417
+ /**
418
+ * Creates a new X86Interpreter instance with the provided assembly code
419
+ * @param {string} code - Assembly code to interpret
420
+ * @returns {X86Interpreter} New interpreter instance
421
+ * @throws {Error} If creating the interpreter fails
422
+ */
423
+ static create(code: string) {
424
+ try {
425
+ const keystone = new ks.Keystone(ks.ARCH_X86, ks.MODE_32);
426
+ keystone.option(ks.OPT_SYNTAX, ks.OPT_SYNTAX_INTEL);
427
+ const interpreter = new X86Interpreter(
428
+ code,
429
+ keystone,
430
+ new uc.Unicorn(uc.ARCH_X86, uc.MODE_32),
431
+ //@ts-ignore
432
+ new cs.Capstone(cs.ARCH_X86, cs.MODE_32)
433
+ )
434
+ return interpreter;
435
+ } catch (e) {
436
+ throw new Error(simplifyError(e));
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Releases resources used by the interpreter
442
+ */
443
+ dispose() {
444
+ this.interpreter.close();
445
+ this.assembler.close()
446
+ this.disambler.close();
447
+ }
448
+ }