@henols/c64-re-tools 2.0.0-rc.2 → 2.0.0-rc.21
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 +28 -62
- package/distribution/package.ts +1 -16
- package/distribution/plugin.ts +5 -3
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +25 -6
- package/skills/c64-assembler/scripts/assemble.ts +45 -10
- package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
- package/skills/c64-basic/SKILL.md +15 -7
- package/skills/c64-basic/scripts/basic.ts +4 -4
- package/skills/c64-disk/SKILL.md +34 -11
- package/skills/c64-disk/scripts/disk.ts +65 -10
- package/skills/c64-disk/scripts/lib/containers.ts +124 -0
- package/skills/c64-docs/SKILL.md +138 -0
- package/skills/c64-docs/references/page-types.md +141 -0
- package/skills/c64-docs/references/style.md +43 -0
- package/skills/c64-docs/scripts/generate.ts +102 -0
- package/skills/c64-docs/scripts/lib/links.ts +155 -0
- package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
- package/skills/c64-docs/scripts/lib/reference.ts +406 -0
- package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
- package/skills/c64-docs/scripts/link.ts +207 -0
- package/skills/c64-emulator/SKILL.md +29 -8
- package/skills/c64-knowledge/SKILL.md +201 -15
- package/skills/c64-knowledge/scripts/knowledge.ts +927 -78
- package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
- package/skills/c64-memory-map/SKILL.md +14 -2
- package/skills/c64-memory-map/scripts/memmap.ts +15 -4
- package/skills/c64-provenance/SKILL.md +100 -11
- package/skills/c64-provenance/scripts/lib/compare.ts +152 -0
- package/skills/c64-provenance/scripts/lib/library.ts +584 -0
- package/skills/c64-provenance/scripts/lib/runtime.ts +191 -0
- package/skills/c64-provenance/scripts/lib/separate.ts +140 -0
- package/skills/c64-provenance/scripts/provenance.ts +477 -24
- package/skills/c64-reverse-engineering/SKILL.md +90 -11
- package/skills/c64-source/SKILL.md +174 -0
- package/skills/c64-source/scripts/generate.ts +788 -0
- package/skills/c64-source/scripts/lib/loader.ts +630 -0
- package/skills/c64-source/scripts/lib/source.ts +1121 -0
- package/skills/c64-static-analysis/SKILL.md +53 -13
- package/skills/c64-static-analysis/scripts/analyze.ts +260 -31
- package/skills/c64-static-analysis/scripts/lib/coverage.ts +72 -0
- package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +11 -5
- package/skills/c64-static-analysis/scripts/lib/idioms.ts +158 -0
- package/skills/c64-static-analysis/scripts/lib/instructions.ts +105 -0
- package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
- package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
- package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
- package/skills/c64-testing/SKILL.md +57 -4
- package/skills/c64-testing/references/scenario-format.md +50 -6
- package/skills/c64-testing/scripts/checklist.ts +3 -3
- package/skills/c64-testing/scripts/diff.ts +167 -0
- package/skills/c64-testing/scripts/lib/diff.ts +70 -0
- package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +183 -152
- package/skills/c64-testing/scripts/run.ts +241 -0
- package/skills/c64-testing/scripts/test.ts +20 -10
- package/skills/c64-unpacker/SKILL.md +88 -21
- package/skills/c64-unpacker/scripts/lib/evidence.ts +207 -0
- package/skills/c64-unpacker/scripts/lib/loads.ts +209 -0
- package/skills/c64-unpacker/scripts/lib/machine.ts +80 -0
- package/skills/c64-unpacker/scripts/lib/stages.ts +142 -0
- package/skills/c64-unpacker/scripts/unpack.ts +545 -61
- package/src/c64.ts +240 -12
- package/src/cli/cleanup.ts +1 -1
- package/src/cli/main.ts +14 -8
- package/src/cli/status.ts +105 -11
- package/src/graphics.ts +158 -0
- package/src/host/main.ts +57 -31
- package/src/host/server.ts +30 -47
- package/src/host/tools/c1541.ts +34 -5
- package/src/host/tools/index.ts +6 -2
- package/src/host/tools/petcat.ts +10 -3
- package/src/host/tools/status.ts +23 -6
- package/src/host/vice/adapter.ts +64 -20
- package/src/host/vice/chips.ts +11 -1
- package/src/host/vice/process.ts +19 -8
- package/src/host/vice/screen.ts +4 -44
- package/src/host/vice/session.ts +343 -60
- package/src/host-client/autostart.ts +55 -0
- package/src/host-client/connect.ts +14 -14
- package/src/host-client/coverage.ts +36 -0
- package/src/host-client/image.ts +63 -0
- package/src/host-client/steps.ts +175 -0
- package/src/host-client/tools.ts +7 -6
- package/src/host-client/transfer.ts +60 -10
- package/src/host-client/vice-session.ts +107 -28
- package/src/knowledge/conflicts.ts +161 -0
- package/src/knowledge/database.ts +150 -38
- package/src/knowledge/expression.ts +209 -0
- package/src/knowledge/graph.ts +104 -0
- package/src/knowledge/history.ts +105 -27
- package/src/knowledge/import.ts +141 -85
- package/src/knowledge/join.ts +211 -0
- package/src/knowledge/program.ts +258 -0
- package/src/knowledge/read.ts +445 -63
- package/src/knowledge/schema.ts +277 -65
- package/src/knowledge/text.ts +415 -0
- package/src/knowledge/write.ts +1145 -150
- package/src/mcp/main.ts +44 -19
- package/src/mcp/output.ts +39 -0
- package/src/mcp/server.ts +15 -4
- package/src/mcp/tools/debug.ts +202 -31
- package/src/mcp/tools/execution.ts +39 -5
- package/src/mcp/tools/input.ts +7 -4
- package/src/mcp/tools/machine.ts +14 -2
- package/src/mcp/tools/media.ts +44 -16
- package/src/mcp/tools/memory.ts +68 -12
- package/src/mcp/tools/video.ts +20 -9
- package/src/native/discover.ts +44 -6
- package/src/native/ghidra/analyze.ts +2 -2
- package/src/native/ghidra/index.ts +19 -3
- package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
- package/src/native/ghidra/scripts/C64Export.java +33 -1
- package/src/native/ghidra/scripts/C64Prepare.java +7 -1
- package/src/native/status.ts +15 -3
- package/src/native/types.ts +43 -8
- package/src/opcodes.ts +133 -0
- package/src/package.ts +21 -0
- package/{skills/c64-memory-map/scripts → src}/platform.ts +87 -7
- package/src/project.ts +166 -3
- package/src/protocol/messages.ts +17 -10
- package/src/protocol/tools.ts +27 -22
- package/src/protocol/vice.ts +255 -33
- package/src/script.ts +49 -9
- package/src/trace.ts +55 -23
- package/skills/c64-provenance/scripts/compare.ts +0 -92
- package/skills/c64-unpacker/scripts/evidence.ts +0 -135
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Runtime evidence for the differences between releases (D50): each release
|
|
2
|
+
// runs in its own emulator, and each differing range gets its first access
|
|
3
|
+
// (read, write, execute or none) and whether its bytes changed by the end.
|
|
4
|
+
// Watchpoints and range breakpoints stop the run at an access; the coverage
|
|
5
|
+
// map then tells which range and which access it was.
|
|
6
|
+
|
|
7
|
+
import { COVERAGE, formatC64Address, instructionLength } from "#src/c64.ts";
|
|
8
|
+
import { runProgram } from "#src/host-client/autostart.ts";
|
|
9
|
+
import { readCoverage } from "#src/host-client/coverage.ts";
|
|
10
|
+
import { runActionStep, type ActionStep } from "#src/host-client/steps.ts";
|
|
11
|
+
import { MAX_MEMORY_READ, MAX_MEMORY_WRITE } from "#src/host-client/tools.ts";
|
|
12
|
+
import { readProjectFile } from "#src/host-client/transfer.ts";
|
|
13
|
+
import type { ViceSessionClient } from "#src/host-client/vice-session.ts";
|
|
14
|
+
import type { Range } from "./compare.ts";
|
|
15
|
+
|
|
16
|
+
export type FirstAccess = "read" | "write" | "execute" | "none";
|
|
17
|
+
|
|
18
|
+
export interface RangeAccess {
|
|
19
|
+
first: FirstAccess;
|
|
20
|
+
/** True when the bytes at the end differ from the bytes of the release. */
|
|
21
|
+
changed: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** How a release starts in the emulator. */
|
|
25
|
+
export type Start =
|
|
26
|
+
| { kind: "program"; path: string }
|
|
27
|
+
| { kind: "image"; segments: Range[]; byteAt: (address: number) => number; state: ImageState };
|
|
28
|
+
|
|
29
|
+
export interface ImageState {
|
|
30
|
+
pc: number;
|
|
31
|
+
a: number;
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
sp: number;
|
|
35
|
+
flags: { n: boolean; v: boolean; d: boolean; i: boolean; z: boolean; c: boolean };
|
|
36
|
+
port: [number, number];
|
|
37
|
+
/** $0000-$03ff at the entry. */
|
|
38
|
+
pages: Uint8Array;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const MAX_WATCH = 256;
|
|
42
|
+
/** Frames until the KERNAL is at the READY prompt, before an image is written. */
|
|
43
|
+
const READY_FRAMES = 150;
|
|
44
|
+
|
|
45
|
+
const READ = COVERAGE.ramRead | COVERAGE.romRead | COVERAGE.ioRead;
|
|
46
|
+
const WRITE = COVERAGE.ramWrite | COVERAGE.ioWrite;
|
|
47
|
+
const EXECUTE = COVERAGE.ramExecute | COVERAGE.romExecute | COVERAGE.ioExecute;
|
|
48
|
+
|
|
49
|
+
/** The first access that coverage flags show; a read-modify-write reads first. */
|
|
50
|
+
export function accessOf(flags: number): FirstAccess {
|
|
51
|
+
if ((flags & EXECUTE) !== 0) return "execute";
|
|
52
|
+
if ((flags & READ) !== 0) return "read";
|
|
53
|
+
if ((flags & WRITE) !== 0) return "write";
|
|
54
|
+
return "none";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Starts the release; returns why it did not start, or undefined. */
|
|
58
|
+
async function start(client: ViceSessionClient, how: Start): Promise<string | undefined> {
|
|
59
|
+
if (how.kind === "program") {
|
|
60
|
+
const failed = await runProgram(client, how.path, readProjectFile(how.path).bytes);
|
|
61
|
+
if (failed === "not-loaded") return "The program was not in memory after autostart.";
|
|
62
|
+
if (failed === "not-started") return "The program did not start after RUN; it needs a BASIC start line.";
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
await client.reset({ mode: "hard", run: true });
|
|
66
|
+
await client.execution({ action: "advance-frames", count: READY_FRAMES, space: "c64" });
|
|
67
|
+
for (const segment of how.segments) {
|
|
68
|
+
for (let at = segment.start; at <= segment.end; at += MAX_MEMORY_WRITE) {
|
|
69
|
+
const last = Math.min(segment.end, at + MAX_MEMORY_WRITE - 1);
|
|
70
|
+
const bytes = Buffer.from(Array.from({ length: last - at + 1 }, (_, offset) => how.byteAt(at + offset)));
|
|
71
|
+
await client.memoryWrite({ address: at, data: bytes.toString("hex"), space: "c64", view: "ram" });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const { state } = how;
|
|
75
|
+
// Pages 0 to 3 from $0002, then the CPU port: the port bits switch what the CPU sees.
|
|
76
|
+
await client.memoryWrite({ address: 0x0002, data: Buffer.from(state.pages.subarray(2)).toString("hex"), space: "c64", view: "ram" });
|
|
77
|
+
await client.memoryWrite({ address: 0x0000, data: Buffer.from(state.port).toString("hex"), space: "c64", view: "cpu" });
|
|
78
|
+
await client.registersSet("c64", { pc: state.pc, a: state.a, x: state.x, y: state.y, sp: state.sp, flags: state.flags });
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Runs one release and watches the ranges (addresses of the release, after
|
|
84
|
+
* any shift is taken away). `release` gives the release's own bytes.
|
|
85
|
+
*/
|
|
86
|
+
export async function watchRelease(
|
|
87
|
+
client: ViceSessionClient,
|
|
88
|
+
how: Start,
|
|
89
|
+
ranges: Range[],
|
|
90
|
+
release: (address: number) => number,
|
|
91
|
+
run: { frames: number; steps: ActionStep[] },
|
|
92
|
+
): Promise<{ started: true; accesses: RangeAccess[] } | { started: false; reason: string }> {
|
|
93
|
+
await client.reset({ mode: "hard", run: false });
|
|
94
|
+
const failed = await start(client, how);
|
|
95
|
+
if (failed !== undefined) return { started: false, reason: failed };
|
|
96
|
+
if ((await client.status()).state === "running") await client.execution({ action: "pause", space: "c64" });
|
|
97
|
+
|
|
98
|
+
const first: FirstAccess[] = ranges.map(() => "none");
|
|
99
|
+
const points = new Map<number, Array<{ kind: "watch" | "break"; id: number }>>();
|
|
100
|
+
for (const [index, range] of ranges.entries()) {
|
|
101
|
+
const ids: Array<{ kind: "watch" | "break"; id: number }> = [];
|
|
102
|
+
for (let at = range.start; at <= range.end; at += MAX_WATCH) {
|
|
103
|
+
const size = Math.min(MAX_WATCH, range.end - at + 1);
|
|
104
|
+
ids.push({ kind: "watch", id: (await client.watchpoint({ action: "add", address: at, size, access: "read-write", space: "c64" })).id });
|
|
105
|
+
ids.push({ kind: "break", id: (await client.breakpoint({ action: "add", address: at, size, space: "c64" })).id });
|
|
106
|
+
}
|
|
107
|
+
points.set(index, ids);
|
|
108
|
+
}
|
|
109
|
+
await client.memmap({ action: "clear" });
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* After a stop: the ranges that the coverage shows accessed get their first
|
|
113
|
+
* access, and lose their points. VICE marks only the opcode byte of an
|
|
114
|
+
* executed instruction, so an instruction that starts up to two bytes before
|
|
115
|
+
* a range, or in it, executes its operand bytes too (F-27).
|
|
116
|
+
*/
|
|
117
|
+
const settle = async (): Promise<boolean> => {
|
|
118
|
+
const map = await readCoverage(client);
|
|
119
|
+
let found = false;
|
|
120
|
+
for (const [index, ids] of points) {
|
|
121
|
+
const range = ranges[index]!;
|
|
122
|
+
let flags = 0;
|
|
123
|
+
for (let address = range.start; address <= range.end; address++) flags |= map[address]!;
|
|
124
|
+
const from = Math.max(0, range.start - 2);
|
|
125
|
+
if ((flags & EXECUTE) === 0 && map.subarray(from, range.start).some((byte) => (byte & COVERAGE.ramExecute) !== 0)) {
|
|
126
|
+
const code = Buffer.from((await client.memoryRead({ address: from, size: range.start - from, space: "c64", view: "ram" })).data, "hex");
|
|
127
|
+
for (let address = from; address < range.start; address++) {
|
|
128
|
+
if ((map[address]! & COVERAGE.ramExecute) !== 0 && address + instructionLength(code[address - from]!) - 1 >= range.start) flags |= COVERAGE.ramExecute;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (accessOf(flags) === "none") continue;
|
|
132
|
+
first[index] = accessOf(flags);
|
|
133
|
+
for (const point of ids) {
|
|
134
|
+
if (point.kind === "watch") await client.watchpoint({ action: "remove", id: point.id });
|
|
135
|
+
else await client.breakpoint({ action: "remove", id: point.id });
|
|
136
|
+
}
|
|
137
|
+
points.delete(index);
|
|
138
|
+
found = true;
|
|
139
|
+
}
|
|
140
|
+
return found;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Each stop at a point settles at least one range, so the stops are bounded; the guard ends a run that stops without an access.
|
|
144
|
+
let guard = 4 * ranges.length + 16;
|
|
145
|
+
/** Runs frames; a stop at a point settles and goes on with the frames that are left. */
|
|
146
|
+
const runFrames = async (count: number) => {
|
|
147
|
+
let left = count;
|
|
148
|
+
while (left > 0 && guard-- > 0) {
|
|
149
|
+
const ran = await client.execution({ action: "advance-frames", count: left, space: "c64" });
|
|
150
|
+
left -= ran.advancedFrames ?? left;
|
|
151
|
+
if (left > 0 && !(await settle())) left--;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
for (const step of run.steps) {
|
|
155
|
+
if ("frames" in step) {
|
|
156
|
+
await runFrames(step.frames);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if ("runUntil" in step) {
|
|
160
|
+
// A stop at a point is not the target: settle and run to the target again.
|
|
161
|
+
while (guard-- > 0) {
|
|
162
|
+
const reached = await runActionStep(client, step, (location) => Number.parseInt(location.slice(1), 16));
|
|
163
|
+
if (reached === undefined || reached.stopReason !== "watchpoint" && reached.stopReason !== "breakpoint") break;
|
|
164
|
+
await settle();
|
|
165
|
+
}
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
await runActionStep(client, step, (location) => Number.parseInt(location.slice(1), 16));
|
|
169
|
+
}
|
|
170
|
+
await runFrames(run.frames);
|
|
171
|
+
if (points.size > 0) await settle();
|
|
172
|
+
|
|
173
|
+
const accesses: RangeAccess[] = [];
|
|
174
|
+
for (const [index, range] of ranges.entries()) {
|
|
175
|
+
const parts: Buffer[] = [];
|
|
176
|
+
for (let at = range.start; at <= range.end; at += MAX_MEMORY_READ) {
|
|
177
|
+
parts.push(Buffer.from((await client.memoryRead({ address: at, size: Math.min(MAX_MEMORY_READ, range.end - at + 1), space: "c64", view: "ram" })).data, "hex"));
|
|
178
|
+
}
|
|
179
|
+
const now = Buffer.concat(parts);
|
|
180
|
+
accesses.push({ first: first[index]!, changed: now.some((byte, offset) => byte !== release(range.start + offset)) });
|
|
181
|
+
}
|
|
182
|
+
for (const ids of points.values()) {
|
|
183
|
+
for (const point of ids) {
|
|
184
|
+
if (point.kind === "watch") await client.watchpoint({ action: "remove", id: point.id });
|
|
185
|
+
else await client.breakpoint({ action: "remove", id: point.id });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { started: true, accesses };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export const showRange = (range: Range) => `${formatC64Address(range.start)}-${formatC64Address(range.end)}`;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Evidence from one release (D51): for each range of the image, the unpack
|
|
2
|
+
// stage that wrote it and the access class from coverage; for each code
|
|
3
|
+
// region of knowledge, whether call and jump references reach it from the
|
|
4
|
+
// entry; and what the program reads or executes outside the image. Evidence
|
|
5
|
+
// only: nothing here decides what is original.
|
|
6
|
+
|
|
7
|
+
import { COVERAGE, executedRamCode } from "#src/c64.ts";
|
|
8
|
+
import type { Range } from "./compare.ts";
|
|
9
|
+
|
|
10
|
+
export type Access = "executed" | "read" | "written" | "read-written" | "untouched";
|
|
11
|
+
|
|
12
|
+
export interface SeparationInput {
|
|
13
|
+
segments: Range[];
|
|
14
|
+
/** 64 KiB with the image bytes at their addresses. */
|
|
15
|
+
memory: Uint8Array;
|
|
16
|
+
entry?: number;
|
|
17
|
+
/** The stages of an unpacker capture, in order; the last one is the program. */
|
|
18
|
+
stages?: Array<{ entry: number; wrote: Range[] }>;
|
|
19
|
+
/** Joined coverage files (D40). */
|
|
20
|
+
coverage?: Uint8Array;
|
|
21
|
+
codeRegions: Array<Range & { type: string }>;
|
|
22
|
+
references: Array<{ from: number; to: number; kind: string }>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface EvidenceRange extends Range {
|
|
26
|
+
/** The last stage that wrote the range; absent when no stage wrote it. */
|
|
27
|
+
stage?: number;
|
|
28
|
+
access?: Access;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CodeRegionEvidence extends Range {
|
|
32
|
+
type: string;
|
|
33
|
+
/** Absent when the image has no entry. */
|
|
34
|
+
reachable?: boolean;
|
|
35
|
+
/** Absent without coverage. */
|
|
36
|
+
executed?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Separation {
|
|
40
|
+
ranges: EvidenceRange[];
|
|
41
|
+
codeRegions: CodeRegionEvidence[];
|
|
42
|
+
/**
|
|
43
|
+
* RAM outside the image and outside pages 0-3, from the coverage: `read`
|
|
44
|
+
* and `executed` the program never wrote (a loader or an earlier stage put
|
|
45
|
+
* those bytes there), and `generated` it wrote and then executed, in
|
|
46
|
+
* pieces of code with gaps of up to two operand bytes.
|
|
47
|
+
*/
|
|
48
|
+
outside: { read: Range[]; executed: Range[]; generated: Range[] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const SYSTEM_PAGES_END = 0x03ff;
|
|
52
|
+
|
|
53
|
+
/** Joins addresses in order into ranges; `gap` is the most bytes between two addresses in one range. */
|
|
54
|
+
function merge(addresses: number[], gap = 0, joins: (from: number, to: number) => boolean = () => true): Range[] {
|
|
55
|
+
const ranges: Range[] = [];
|
|
56
|
+
for (const address of addresses) {
|
|
57
|
+
const last = ranges.at(-1);
|
|
58
|
+
if (last !== undefined && address - last.end - 1 <= gap && joins(last.end, address)) last.end = address;
|
|
59
|
+
else ranges.push({ start: address, end: address });
|
|
60
|
+
}
|
|
61
|
+
return ranges;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function separate(input: SeparationInput): Separation {
|
|
65
|
+
const inImage = (address: number) => input.segments.some((segment) => address >= segment.start && address <= segment.end);
|
|
66
|
+
const executed = new Uint8Array(0x10000);
|
|
67
|
+
if (input.coverage !== undefined) for (const range of executedRamCode(input.coverage, 0, input.memory)) executed.fill(1, range.start, range.end + 1);
|
|
68
|
+
const accessAt = (address: number): Access | undefined => {
|
|
69
|
+
if (input.coverage === undefined) return undefined;
|
|
70
|
+
if (executed[address] === 1) return "executed";
|
|
71
|
+
const flags = input.coverage[address]!;
|
|
72
|
+
const read = (flags & COVERAGE.ramRead) !== 0;
|
|
73
|
+
const written = (flags & COVERAGE.ramWrite) !== 0;
|
|
74
|
+
return read && written ? "read-written" : read ? "read" : written ? "written" : "untouched";
|
|
75
|
+
};
|
|
76
|
+
const stageAt = (address: number): number | undefined => {
|
|
77
|
+
const stages = input.stages ?? [];
|
|
78
|
+
for (let index = stages.length - 1; index >= 0; index--) if (stages[index]!.wrote.some((range) => address >= range.start && address <= range.end)) return index;
|
|
79
|
+
return undefined;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const ranges: EvidenceRange[] = [];
|
|
83
|
+
for (const segment of input.segments) {
|
|
84
|
+
for (let address = segment.start; address <= segment.end; address++) {
|
|
85
|
+
const stage = stageAt(address);
|
|
86
|
+
const access = accessAt(address);
|
|
87
|
+
const last = ranges.at(-1);
|
|
88
|
+
if (last !== undefined && last.end + 1 === address && last.stage === stage && last.access === access) {
|
|
89
|
+
last.end = address;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
ranges.push({ start: address, end: address, ...(stage === undefined ? {} : { stage }), ...(access === undefined ? {} : { access }) });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Reachability: from the region that holds the entry, through call and jump references, to a fixed point.
|
|
97
|
+
const regions = input.codeRegions.filter((region) => input.segments.some((segment) => region.start <= segment.end && segment.start <= region.end));
|
|
98
|
+
const holding = (address: number) => regions.findIndex((region) => address >= region.start && address <= region.end);
|
|
99
|
+
const reached = new Set<number>();
|
|
100
|
+
if (input.entry !== undefined && holding(input.entry) >= 0) reached.add(holding(input.entry));
|
|
101
|
+
const flows = input.references.filter((reference) => reference.kind === "call" || reference.kind === "jump");
|
|
102
|
+
for (let grown = true; grown; ) {
|
|
103
|
+
grown = false;
|
|
104
|
+
for (const flow of flows) {
|
|
105
|
+
const from = holding(flow.from);
|
|
106
|
+
const to = holding(flow.to);
|
|
107
|
+
if (from >= 0 && to >= 0 && reached.has(from) && !reached.has(to)) {
|
|
108
|
+
reached.add(to);
|
|
109
|
+
grown = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const codeRegions = regions.map((region, index): CodeRegionEvidence => {
|
|
114
|
+
let ran: boolean | undefined;
|
|
115
|
+
if (input.coverage !== undefined) {
|
|
116
|
+
ran = false;
|
|
117
|
+
for (let address = region.start; address <= region.end && !ran; address++) ran = executed[address] === 1;
|
|
118
|
+
}
|
|
119
|
+
return { start: region.start, end: region.end, type: region.type, ...(input.entry === undefined ? {} : { reachable: reached.has(index) }), ...(ran === undefined ? {} : { executed: ran }) };
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const read: number[] = [];
|
|
123
|
+
const ranOutside: number[] = [];
|
|
124
|
+
const generated: number[] = [];
|
|
125
|
+
if (input.coverage !== undefined) {
|
|
126
|
+
for (let address = SYSTEM_PAGES_END + 1; address < 0x10000; address++) {
|
|
127
|
+
if (inImage(address)) continue;
|
|
128
|
+
const written = (input.coverage[address]! & COVERAGE.ramWrite) !== 0;
|
|
129
|
+
if (executed[address] === 1) (written ? generated : ranOutside).push(address);
|
|
130
|
+
else if (!written && (input.coverage[address]! & COVERAGE.ramRead) !== 0) read.push(address);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// The image has no bytes of generated code, so only its opcode bytes count as executed. VICE marks only
|
|
134
|
+
// opcode bytes: a gap of at most two operand bytes outside the image stays inside one piece of code (F-28).
|
|
135
|
+
const outsideImage = (from: number, to: number) => {
|
|
136
|
+
for (let address = from + 1; address < to; address++) if (inImage(address)) return false;
|
|
137
|
+
return true;
|
|
138
|
+
};
|
|
139
|
+
return { ranges, codeRegions, outside: { read: merge(read), executed: merge(ranOutside), generated: merge(generated, 2, outsideImage) } };
|
|
140
|
+
}
|