@specy/x86 2.0.4 → 2.1.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/dist/index.mjs CHANGED
@@ -49,12 +49,13 @@ const DEFAULT_ASSEMBLER_ID = "NASM_trunk";
49
49
  function nasmDiagnostics(str) {
50
50
  const diagnostics = [];
51
51
  const lines = str.split(/\r?\n/);
52
- const regex = /^.*:(\d+): (error|warning): (.*)$/;
52
+ const regex = /^(.*):(\d+): (error|warning): (.*)$/;
53
53
  for (const line of lines) {
54
54
  const match = line.match(regex);
55
55
  if (match) diagnostics.push({
56
- line: Number.parseInt(match[1] ?? "0", 10),
57
- error: (match[3] ?? "").trim()
56
+ file: match[1] || void 0,
57
+ line: Number.parseInt(match[2] ?? "0", 10),
58
+ error: (match[4] ?? "").trim()
58
59
  });
59
60
  }
60
61
  return diagnostics;
@@ -62,32 +63,39 @@ function nasmDiagnostics(str) {
62
63
  function gnuDiagnostics(str) {
63
64
  const diagnostics = [];
64
65
  const lines = str.split("\n");
65
- const regex = /\/assembly\.s:(\d+): (Error: .+)/;
66
+ const regex = /^(.*):(\d+): (Error: .+)$/;
66
67
  for (const line of lines) {
67
68
  const match = line.match(regex);
68
69
  if (match) diagnostics.push({
69
- line: Number.parseInt(match[1] ?? "0", 10),
70
- error: match[2] ?? ""
70
+ file: match[1] || void 0,
71
+ line: Number.parseInt(match[2] ?? "0", 10),
72
+ error: match[3] ?? ""
71
73
  });
72
74
  }
73
75
  return diagnostics;
74
76
  }
75
77
  function fasmDiagnostics(str) {
76
78
  const diagnostics = [];
77
- const lineRegex = /\/assembly\.s \[(\d+)\]:/;
79
+ const lineRegex = /^(.*) \[(\d+)\]:/;
78
80
  const errorRegex = /error: .+/;
79
81
  const lines = str.split("\n");
80
82
  let lineNumber = null;
83
+ let file;
81
84
  for (const line of lines) {
82
85
  const lineMatch = line.match(lineRegex);
83
86
  const errorMatch = line.match(errorRegex);
84
- if (lineMatch) lineNumber = Number.parseInt(lineMatch[1] ?? "0", 10);
87
+ if (lineMatch) {
88
+ file = lineMatch[1] || void 0;
89
+ lineNumber = Number.parseInt(lineMatch[2] ?? "0", 10);
90
+ }
85
91
  if (errorMatch && lineNumber !== null) {
86
92
  diagnostics.push({
93
+ file,
87
94
  line: lineNumber,
88
95
  error: errorMatch[0]
89
96
  });
90
97
  lineNumber = null;
98
+ file = void 0;
91
99
  }
92
100
  }
93
101
  return diagnostics;
@@ -170,6 +178,9 @@ var SourceMap = class {
170
178
  return this.entries.length;
171
179
  }
172
180
  getLineIndex(address) {
181
+ return this.getLocation(address)?.lineIndex ?? null;
182
+ }
183
+ getLocation(address) {
173
184
  let left = 0;
174
185
  let right = this.entries.length - 1;
175
186
  let match = null;
@@ -182,13 +193,19 @@ var SourceMap = class {
182
193
  left = middle + 1;
183
194
  } else right = middle - 1;
184
195
  }
185
- return match?.lineIndex ?? null;
196
+ return match ? { ...match } : null;
186
197
  }
187
198
  getAddressesForLine(lineIndex) {
199
+ return this.getAddressesForLocation(lineIndex);
200
+ }
201
+ getAddressesForLocation(lineIndex, file) {
202
+ return this.getAddressesMatching(lineIndex, (candidate) => file === void 0 || candidate === file);
203
+ }
204
+ getAddressesMatching(lineIndex, matchesFile) {
188
205
  const addresses = [];
189
206
  const seen = /* @__PURE__ */ new Set();
190
207
  for (const entry of this.entries) {
191
- if (entry.lineIndex !== lineIndex) continue;
208
+ if (entry.lineIndex !== lineIndex || !matchesFile(entry.file)) continue;
192
209
  const key = entry.address.toString();
193
210
  if (seen.has(key)) continue;
194
211
  seen.add(key);
@@ -196,6 +213,16 @@ var SourceMap = class {
196
213
  }
197
214
  return addresses;
198
215
  }
216
+ getAddresses() {
217
+ const addresses = [];
218
+ let previous;
219
+ for (const entry of this.entries) {
220
+ if (entry.address === previous) continue;
221
+ addresses.push(entry.address);
222
+ previous = entry.address;
223
+ }
224
+ return addresses;
225
+ }
199
226
  };
200
227
  function parseSourceMap(bytes) {
201
228
  const section = findElfSection(bytes, ".debug_line");
@@ -302,7 +329,7 @@ function parseDebugLine(bytes) {
302
329
  entries.push({
303
330
  address,
304
331
  lineIndex: Math.max(0, line - 1),
305
- file: sourceFile?.name
332
+ file: dwarfSourcePath(sourceFile)
306
333
  });
307
334
  };
308
335
  const advanceAddress = (operationAdvance) => {
@@ -392,6 +419,11 @@ function parseDebugLine(bytes) {
392
419
  }
393
420
  return entries;
394
421
  }
422
+ function dwarfSourcePath(sourceFile) {
423
+ if (!sourceFile?.name) return void 0;
424
+ if (!sourceFile.directory || sourceFile.name.startsWith("/")) return sourceFile.name;
425
+ return `${sourceFile.directory.replace(/\/$/, "")}/${sourceFile.name}`;
426
+ }
395
427
  function readU64Number(view, offset) {
396
428
  const value = view.getBigUint64(offset, true);
397
429
  if (value > BigInt(Number.MAX_SAFE_INTEGER)) return Number.MAX_SAFE_INTEGER;
@@ -438,6 +470,87 @@ function compareBigInt(left, right) {
438
470
  return 0;
439
471
  }
440
472
  //#endregion
473
+ //#region src/project.ts
474
+ const X86_PROJECT_ROOT = "/__x86_project";
475
+ const stagedFiles = /* @__PURE__ */ new WeakMap();
476
+ function validateX86Project(project) {
477
+ if (!isProjectPath(project.entry)) throw new Error(`Invalid x86 Project entry path: ${project.entry}`);
478
+ if (!Object.hasOwn(project.files, project.entry)) throw new Error(`x86 Project entry file not found: ${project.entry}`);
479
+ if (typeof project.files[project.entry] !== "string") throw new Error(`x86 Project entry must be a text file: ${project.entry}`);
480
+ for (const [path, contents] of Object.entries(project.files)) {
481
+ if (!isProjectPath(path)) throw new Error(`Invalid x86 Project file path: ${path}`);
482
+ if (typeof contents !== "string" && !(contents instanceof Uint8Array)) throw new Error(`Invalid x86 Project file contents: ${path}`);
483
+ }
484
+ }
485
+ /**
486
+ * Writes a Project into the emulator filesystem and selects the Entry directory as NASM's working
487
+ * directory. NASM consequently owns `%include` and `incbin` resolution, including paths produced by
488
+ * preprocessor macros.
489
+ */
490
+ function stageX86Project(fs, project) {
491
+ validateX86Project(project);
492
+ const previous = stagedFiles.get(fs) ?? /* @__PURE__ */ new Set();
493
+ const current = /* @__PURE__ */ new Set();
494
+ fs.mkdirTree(X86_PROJECT_ROOT);
495
+ for (const [path, contents] of Object.entries(project.files)) {
496
+ const absolutePath = `${X86_PROJECT_ROOT}/${path}`;
497
+ const separator = absolutePath.lastIndexOf("/");
498
+ if (separator > 0) fs.mkdirTree(absolutePath.slice(0, separator));
499
+ fs.writeFile(absolutePath, contents);
500
+ current.add(absolutePath);
501
+ }
502
+ for (const path of previous) {
503
+ if (current.has(path)) continue;
504
+ try {
505
+ fs.unlink(path);
506
+ } catch {}
507
+ }
508
+ fs.writeFile("/assembly.s", project.files[project.entry]);
509
+ fs.chdir(projectDirectory(project.entry));
510
+ stagedFiles.set(fs, current);
511
+ }
512
+ /** Maps a path emitted by NASM or DWARF back to the exact Project path. */
513
+ function x86ProjectSourcePath(sourcePath, project) {
514
+ if (!sourcePath || sourcePath === "/assembly.s" || sourcePath === "assembly.s") return project.entry;
515
+ const withoutLeadingSlash = (sourcePath.startsWith(`/__x86_project/`) ? sourcePath.slice(15) : sourcePath).replace(/^\/+/, "");
516
+ const relativeToEntry = resolvePath(parentPath(project.entry), withoutLeadingSlash);
517
+ if (relativeToEntry && relativeToEntry in project.files) return relativeToEntry;
518
+ const relativeToRoot = resolvePath("", withoutLeadingSlash);
519
+ if (relativeToRoot && relativeToRoot in project.files) return relativeToRoot;
520
+ const suffix = `/${withoutLeadingSlash}`;
521
+ const suffixMatches = Object.keys(project.files).filter((path) => path === withoutLeadingSlash || path.endsWith(suffix));
522
+ return suffixMatches.length === 1 ? suffixMatches[0] : project.entry;
523
+ }
524
+ function projectDirectory(entry) {
525
+ const parent = parentPath(entry);
526
+ return parent ? `${X86_PROJECT_ROOT}/${parent}` : X86_PROJECT_ROOT;
527
+ }
528
+ function parentPath(path) {
529
+ const separator = path.lastIndexOf("/");
530
+ return separator < 0 ? "" : path.slice(0, separator);
531
+ }
532
+ function isProjectPath(path) {
533
+ return typeof path === "string" && path.length > 0 && !path.includes("\\") && !path.startsWith("/") && path.split("/").every((part) => part !== "" && part !== "." && part !== "..") && !Array.from(path).some((character) => {
534
+ const code = character.charCodeAt(0);
535
+ return code <= 31 || code >= 127 && code <= 159;
536
+ });
537
+ }
538
+ function resolvePath(base, written) {
539
+ const parts = base ? base.split("/") : [];
540
+ for (const part of written.split("/")) {
541
+ if (!part || part === ".") continue;
542
+ if (part === "..") {
543
+ if (!parts.length) return null;
544
+ parts.pop();
545
+ } else parts.push(part);
546
+ }
547
+ return parts.join("/") || null;
548
+ }
549
+ function x86ProjectText(project, path) {
550
+ const contents = project.files[path];
551
+ return typeof contents === "string" ? contents : "";
552
+ }
553
+ //#endregion
441
554
  //#region src/types.ts
442
555
  const X86_REGISTER_NAMES = [
443
556
  "rax",
@@ -575,6 +688,7 @@ var BlinkRuntime = class BlinkRuntime {
575
688
  stateWaiters = [];
576
689
  stdinBytes = [];
577
690
  sourceMap = null;
691
+ sourceProject = null;
578
692
  defaultArgc = "/program";
579
693
  defaultArgv = "";
580
694
  constructor(module, mode, callbacks, scheduler) {
@@ -626,17 +740,32 @@ var BlinkRuntime = class BlinkRuntime {
626
740
  this.setState("READY");
627
741
  }
628
742
  async compileAssembly(code) {
743
+ return this.compileProject({
744
+ entry: "assembly.s",
745
+ files: { "assembly.s": code }
746
+ });
747
+ }
748
+ async compileProject(project) {
629
749
  this.assertReadyForCompile();
750
+ const sourceProject = {
751
+ entry: project.entry,
752
+ files: Object.fromEntries(Object.entries(project.files).map(([path, contents]) => [path, contents instanceof Uint8Array ? contents.slice() : contents]))
753
+ };
754
+ stageX86Project(this.module.FS, sourceProject);
755
+ this.sourceProject = sourceProject;
630
756
  this.stopReason = null;
631
757
  this.assemblerLogs = "";
632
758
  this.assemblerErrors = [];
633
759
  this.sourceMap = null;
634
- this.module.FS.writeFile("/assembly.s", code);
635
760
  this.setState("ASSEMBLING");
636
- await defer();
637
- this.setEmulationArgs("/assembler", this.mode.binaries.assembler.commands, "");
638
- this.module._blinkenlib_run_fast();
639
- await this.waitForState((state) => state !== "ASSEMBLING" && state !== "LINKING");
761
+ try {
762
+ await defer();
763
+ this.setEmulationArgs("/assembler", this.mode.binaries.assembler.commands, "");
764
+ this.module._blinkenlib_run_fast();
765
+ await this.waitForState((state) => state !== "ASSEMBLING" && state !== "LINKING");
766
+ } finally {
767
+ this.module.FS.chdir("/");
768
+ }
640
769
  if (this.state === "PROGRAM_LOADED") {
641
770
  this.sourceMap = this.tryReadSourceMap();
642
771
  return {
@@ -646,7 +775,10 @@ var BlinkRuntime = class BlinkRuntime {
646
775
  }
647
776
  return {
648
777
  ok: false,
649
- errors: this.assemblerErrors,
778
+ errors: this.assemblerErrors.map((error) => ({
779
+ ...error,
780
+ file: x86ProjectSourcePath(error.file, sourceProject)
781
+ })),
650
782
  report: this.assemblerLogs
651
783
  };
652
784
  }
@@ -655,6 +787,7 @@ var BlinkRuntime = class BlinkRuntime {
655
787
  this.writeExecutableSync("/program", data instanceof Uint8Array ? data : new Uint8Array(data));
656
788
  this.stopReason = null;
657
789
  this.sourceMap = null;
790
+ this.sourceProject = null;
658
791
  this.setState("PROGRAM_LOADED");
659
792
  }
660
793
  starti() {
@@ -745,30 +878,48 @@ var BlinkRuntime = class BlinkRuntime {
745
878
  return this.module.blinkenlibResolveSymbol(address);
746
879
  }
747
880
  getSourceLineForAddress(address) {
748
- return this.sourceMap?.getLineIndex(address) ?? null;
881
+ return this.getSourceLocationForAddress(address)?.line ?? null;
882
+ }
883
+ getSourceLocationForAddress(address) {
884
+ const location = this.sourceMap?.getLocation(address);
885
+ if (!location) return null;
886
+ return {
887
+ path: this.sourceProject ? x86ProjectSourcePath(location.file, this.sourceProject) : location.file ?? "assembly.s",
888
+ line: location.lineIndex
889
+ };
749
890
  }
750
891
  getAddressesForSourceLine(lineIndex) {
751
892
  return this.sourceMap?.getAddressesForLine(lineIndex) ?? [];
752
893
  }
753
- pauseForBreakpoint(address, lineNumber) {
894
+ getAddressesForSourceLocation(location) {
895
+ if (!this.sourceMap || !this.sourceProject) return [];
896
+ return this.sourceMap.getAddressesMatching(location.line, (file) => x86ProjectSourcePath(file, this.sourceProject) === location.path);
897
+ }
898
+ getSourceMappedAddresses() {
899
+ return this.sourceMap?.getAddresses() ?? [];
900
+ }
901
+ pauseForBreakpoint(address, location) {
754
902
  this.stopReason = {
755
903
  loadFail: false,
756
904
  exitCode: 0,
757
905
  kind: "breakpoint",
758
906
  details: `execution paused at breakpoint 0x${address.toString(16)}`,
759
907
  address,
760
- lineNumber
908
+ lineNumber: location?.line,
909
+ file: location?.path
761
910
  };
762
911
  this.setState("PROGRAM_PAUSED");
763
912
  }
764
913
  pauseForLimit(address, executedInstructions) {
914
+ const location = this.getSourceLocationForAddress(address);
765
915
  this.stopReason = {
766
916
  loadFail: false,
767
917
  exitCode: 0,
768
918
  kind: "limit",
769
919
  details: `execution paused after ${executedInstructions.toString()} instructions`,
770
920
  address,
771
- lineNumber: this.getSourceLineForAddress(address) ?? void 0,
921
+ lineNumber: location?.line,
922
+ file: location?.path,
772
923
  executedInstructions
773
924
  };
774
925
  this.setState("PROGRAM_PAUSED");
@@ -842,14 +993,15 @@ var BlinkRuntime = class BlinkRuntime {
842
993
  }
843
994
  if (code === SIGTRAP_CODES.BLINK_BREAKPOINT || code === SIGTRAP_CODES.BLINK_RUN_LIMIT) {
844
995
  const stop = this.module.blinkenlibGetRunStop();
845
- const lineNumber = this.getSourceLineForAddress(stop.address) ?? void 0;
996
+ const location = this.getSourceLocationForAddress(stop.address);
846
997
  this.stopReason = {
847
998
  loadFail: false,
848
999
  exitCode: 0,
849
1000
  kind: stop.kind === "limit" ? "limit" : "breakpoint",
850
1001
  details: stop.kind === "limit" ? `execution paused after ${stop.executedInstructions.toString()} instructions` : `execution paused at breakpoint 0x${stop.address.toString(16)}`,
851
1002
  address: stop.address,
852
- lineNumber,
1003
+ lineNumber: location?.line,
1004
+ file: location?.path,
853
1005
  executedInstructions: stop.executedInstructions
854
1006
  };
855
1007
  this.setState("PROGRAM_PAUSED");
@@ -1101,14 +1253,15 @@ function stripPrivateHistory(entry) {
1101
1253
  pc: entry.pc,
1102
1254
  old_ccr: entry.old_ccr,
1103
1255
  new_ccr: entry.new_ccr,
1104
- line: entry.line
1256
+ line: entry.line,
1257
+ file: entry.file
1105
1258
  };
1106
1259
  }
1107
1260
  function toHistoryPc(address) {
1108
1261
  return Number(address);
1109
1262
  }
1110
1263
  function makeFrameColor(index, address) {
1111
- return `hsl(${(index + Number(address & 255n) * 137) % 360}, 40%, 60%)`;
1264
+ return `hsl(${index * 137 % 360}, 40%, 60%)`;
1112
1265
  }
1113
1266
  function maskForSize(size) {
1114
1267
  return (1n << BigInt(size * 8)) - 1n;
@@ -1201,9 +1354,15 @@ var X86Emulator = class X86Emulator extends BaseEmulator {
1201
1354
  await this.runtime.setMode(mode);
1202
1355
  }
1203
1356
  async compile(code) {
1357
+ return this.compileProject({
1358
+ entry: "assembly.s",
1359
+ files: { "assembly.s": code }
1360
+ });
1361
+ }
1362
+ async compileProject(project) {
1204
1363
  this.clearExecutionTrace();
1205
- this.lastSourceCode = code;
1206
- this.lastCompileResult = await this.runtime.compileAssembly(code);
1364
+ this.lastSourceCode = x86ProjectText(project, project.entry);
1365
+ this.lastCompileResult = await this.runtime.compileProject(project);
1207
1366
  return this.lastCompileResult;
1208
1367
  }
1209
1368
  loadElf(data) {
@@ -1244,19 +1403,28 @@ var X86Emulator = class X86Emulator extends BaseEmulator {
1244
1403
  return String(error);
1245
1404
  }
1246
1405
  async checkCode(code) {
1247
- const result = await this.compile(code);
1248
- const errors = result.ok === false ? result.errors : [];
1249
- const lines = code.split(/\r?\n/);
1250
- return errors.map((error) => ({
1251
- lineIndex: Math.max(0, error.line - 1),
1252
- column: 0,
1253
- line: {
1254
- line: lines[Math.max(0, error.line - 1)] ?? "",
1255
- line_index: Math.max(0, error.line - 1)
1256
- },
1257
- message: error.error,
1258
- formatted: error.error
1259
- }));
1406
+ return this.checkProject({
1407
+ entry: "assembly.s",
1408
+ files: { "assembly.s": code }
1409
+ });
1410
+ }
1411
+ async checkProject(project) {
1412
+ const result = await this.compileProject(project);
1413
+ return (result.ok === false ? result.errors : []).map((error) => {
1414
+ const path = error.file ?? project.entry;
1415
+ const lineIndex = Math.max(0, error.line - 1);
1416
+ return {
1417
+ file: path,
1418
+ lineIndex,
1419
+ column: 0,
1420
+ line: {
1421
+ line: x86ProjectText(project, path).split(/\r?\n/)[lineIndex] ?? "",
1422
+ line_index: lineIndex
1423
+ },
1424
+ message: error.error,
1425
+ formatted: error.error
1426
+ };
1427
+ });
1260
1428
  }
1261
1429
  undo() {
1262
1430
  const entry = this.history.pop();
@@ -1329,12 +1497,24 @@ var X86Emulator = class X86Emulator extends BaseEmulator {
1329
1497
  getInstructionAt(address) {
1330
1498
  const instruction = this.runtime.getInstructionAt(address);
1331
1499
  if (!instruction) return null;
1500
+ const location = this.runtime.getSourceLocationForAddress(instruction.address);
1332
1501
  return {
1333
1502
  address: instruction.address,
1334
- lineNumber: this.runtime.getSourceLineForAddress(instruction.address) ?? -1,
1503
+ lineNumber: location?.line ?? -1,
1504
+ file: location?.path,
1505
+ size: instruction.size,
1506
+ bytes: this.runtime.readMemoryBytes(instruction.address, BigInt(instruction.size)),
1335
1507
  code: instruction.code
1336
1508
  };
1337
1509
  }
1510
+ /** Every instruction address represented in the linked program's DWARF source map. */
1511
+ getCompiledInstructions() {
1512
+ if (this.runtime.state === "PROGRAM_LOADED") this.runtime.pauseAtEntry();
1513
+ return this.runtime.getSourceMappedAddresses().flatMap((address) => {
1514
+ const instruction = this.getInstructionAt(address);
1515
+ return instruction ? [instruction] : [];
1516
+ });
1517
+ }
1338
1518
  getRegisterValues() {
1339
1519
  return X86_REGISTER_NAMES.map((register) => this.getRegisterValue(register));
1340
1520
  }
@@ -1370,9 +1550,11 @@ var X86Emulator = class X86Emulator extends BaseEmulator {
1370
1550
  }
1371
1551
  resolveBreakpointAddresses(breakpoints) {
1372
1552
  const addresses = /* @__PURE__ */ new Map();
1373
- for (const lineIndex of breakpoints) {
1553
+ for (const breakpoint of breakpoints) {
1554
+ const lineIndex = typeof breakpoint === "number" ? breakpoint : breakpoint.line;
1374
1555
  if (!Number.isSafeInteger(lineIndex) || lineIndex < 0) throw new Error(`Invalid breakpoint line index: ${lineIndex}`);
1375
- for (const address of this.runtime.getAddressesForSourceLine(lineIndex)) addresses.set(address.toString(), address);
1556
+ const resolved = typeof breakpoint === "number" ? this.runtime.getAddressesForSourceLine(lineIndex) : this.runtime.getAddressesForSourceLocation(breakpoint);
1557
+ for (const address of resolved) addresses.set(address.toString(), address);
1376
1558
  }
1377
1559
  return [...addresses.values()];
1378
1560
  }
@@ -1384,7 +1566,7 @@ var X86Emulator = class X86Emulator extends BaseEmulator {
1384
1566
  while (this.runtime.state === "PROGRAM_RUNNING") {
1385
1567
  const pc = this.getPc();
1386
1568
  if (breakpointSet.has(pc.toString())) {
1387
- this.runtime.pauseForBreakpoint(pc, this.runtime.getSourceLineForAddress(pc) ?? void 0);
1569
+ this.runtime.pauseForBreakpoint(pc, this.runtime.getSourceLocationForAddress(pc) ?? void 0);
1388
1570
  return this.getStatus();
1389
1571
  }
1390
1572
  if (hasLimit && executedInstructions >= limit) {
@@ -1405,7 +1587,7 @@ var X86Emulator = class X86Emulator extends BaseEmulator {
1405
1587
  while (this.runtime.state === "PROGRAM_RUNNING") {
1406
1588
  const pc = this.getPc();
1407
1589
  if (breakpointSet.has(pc.toString())) {
1408
- this.runtime.pauseForBreakpoint(pc, this.runtime.getSourceLineForAddress(pc) ?? void 0);
1590
+ this.runtime.pauseForBreakpoint(pc, this.runtime.getSourceLocationForAddress(pc) ?? void 0);
1409
1591
  return this.getStatus();
1410
1592
  }
1411
1593
  if (hasLimit && executedInstructions >= limit) {
@@ -1463,12 +1645,14 @@ var X86Emulator = class X86Emulator extends BaseEmulator {
1463
1645
  }
1464
1646
  const memoryWrites = nativeStep.valid ? this.recordMemoryMutations(nativeStep.memoryWrites, mutations) : [];
1465
1647
  if (nativeStep.valid) this.recordControlFlowMutation(nativeStep, instruction, mutations);
1648
+ const location = this.runtime.getSourceLocationForAddress(pcBefore);
1466
1649
  const entry = {
1467
1650
  mutations,
1468
1651
  pc: toHistoryPc(pcBefore),
1469
1652
  old_ccr: { bits: flagsBefore },
1470
1653
  new_ccr: { bits: flagsAfter },
1471
- line: this.runtime.getSourceLineForAddress(pcBefore) ?? -1,
1654
+ line: location?.line ?? -1,
1655
+ file: location?.path,
1472
1656
  registersBefore,
1473
1657
  flagsBefore,
1474
1658
  callStackBefore,
@@ -1506,12 +1690,14 @@ var X86Emulator = class X86Emulator extends BaseEmulator {
1506
1690
  const returnAddress = instruction ? instruction.address + BigInt(instruction.size) : step.pcBefore;
1507
1691
  const symbol = this.runtime.resolveSymbol(step.pcAfter);
1508
1692
  const frameAddress = symbol?.address ?? step.pcAfter;
1693
+ const location = this.runtime.getSourceLocationForAddress(frameAddress);
1509
1694
  this.callStack.push({
1510
1695
  name: symbol?.name ?? "",
1511
1696
  address: frameAddress,
1512
1697
  destination: returnAddress,
1513
1698
  sp: step.spAfter,
1514
- line: this.runtime.getSourceLineForAddress(frameAddress) ?? -1,
1699
+ line: location?.line ?? -1,
1700
+ file: location?.path,
1515
1701
  color: makeFrameColor(this.callStack.length, frameAddress)
1516
1702
  });
1517
1703
  mutations.push({
@@ -1541,6 +1727,6 @@ async function createX86Emulator(options = {}) {
1541
1727
  return X86Emulator.create(options);
1542
1728
  }
1543
1729
  //#endregion
1544
- export { BaseEmulator, BlinkRuntime, BlinkState, DEFAULT_ASSEMBLER_ID, EmulatorStatus, RegisterSize, X86Emulator, X86_REGISTER_NAMES, assemblers, createX86Emulator, defaultResourceUrl, fasmDiagnostics, gnuDiagnostics, isNodeRuntime, nasmDiagnostics, observeCallbackResult, readResourceBytes, toResourceUrl };
1730
+ export { BaseEmulator, BlinkRuntime, BlinkState, DEFAULT_ASSEMBLER_ID, EmulatorStatus, RegisterSize, X86Emulator, X86_PROJECT_ROOT, X86_REGISTER_NAMES, assemblers, createX86Emulator, defaultResourceUrl, fasmDiagnostics, gnuDiagnostics, isNodeRuntime, nasmDiagnostics, observeCallbackResult, readResourceBytes, stageX86Project, toResourceUrl, validateX86Project, x86ProjectSourcePath, x86ProjectText };
1545
1731
 
1546
1732
  //# sourceMappingURL=index.mjs.map