OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_dbg_load.c
30.9 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Checks for runtime debug-info loading (src/dbg_load.c).
2
//
3
// This exists because an earlier version of the feature was pulled from its PR
4
// with four defects, every one of which came from reading globals at the end of
5
// a load rather than recording facts as it happened. The checks below are
6
// organised around those four, so a regression reintroduces a named failure
7
// rather than a vague one:
8
//
9
//   1. the bank was sampled when the load finished, but a load crossing $C000
10
//      moves the RAM bank on as it goes, so the end bank belongs to a different
11
//      segment than the one the program started in;
12
//   2. one set of globals could not describe two channels open at once;
13
//   3. any readable channel reaching EOF looked like a program load, including
14
//      a data file read a byte at a time;
15
//   4. the debug info was annotated with the bank BEFORE being loaded, and
16
//      loading discards the mappings in that range, so the annotation went with
17
//      them. (That ordering lives in dbg_load_poll(), which needs dbg_info and
18
//      is covered by the emulator-level check in the PR rather than here.)
19
20
#include "dbg_load.h"
21
#include "dbg_info.h"
22
23
#include <stdbool.h>
24
#include <stdint.h>
25
#include <stdio.h>
26
#include <string.h>
27
28
static int failures = 0;
29
30
static void
31
check(bool cond, const char *what)
32
{
33
	if (!cond) {
34
		failures++;
35
		printf("FAIL: %s\n", what);
36
	} else {
37
		printf("ok  : %s\n", what);
38
	}
39
}
40
41
// Drive a whole block transfer onto one channel, the way MACPTR does: the
42
// destination is reported for every byte but only the first is kept, and the
43
// RAM bank steps on whenever the address walks off the end of the window.
44
static void
45
transfer(int channel, uint16_t addr, uint8_t bank, uint32_t bytes, bool to_eof)
46
{
47
	for (uint32_t i = 0; i < bytes; i++) {
48
		bool last = to_eof && (i + 1 == bytes);
49
		dbg_load_note_byte(channel, addr, bank, last);
50
		addr++;
51
		if (addr == 0xC000) {
52
			addr = 0xA000;
53
			bank++;
54
		}
55
	}
56
}
57
58
// Write a file, returning false if it could not be created.
59
static bool
60
write_file(const char *path, const char *text, size_t len)
61
{
62
	FILE *f = NULL;
63
#ifdef _MSC_VER
64
	if (fopen_s(&f, path, "wb") != 0)
65
		f = NULL;
66
#else
67
	f = fopen(path, "wb");
68
#endif
69
	if (!f)
70
		return false;
71
	size_t wrote = fwrite(text, 1, len, f);
72
	fclose(f);
73
	return wrote == len;
74
}
75
76
// A minimal cc65 .dbg describing one 300-byte CODE segment at $A000, naming a
77
// given source file so two of them can be told apart.
78
static bool
79
write_dbg(const char *path, const char *source_name, unsigned seg_start)
80
{
81
	char text[1024];
82
	int n = snprintf(text, sizeof(text),
83
		"version\tmajor=2,minor=0\n"
84
		"file\tid=0,name=\"%s\",size=10,mtime=0x00000000,mod=0\n"
85
		"line\tid=0,file=0,line=1,span=0\n"
86
		"mod\tid=0,file=0\n"
87
		"scope\tid=0,mod=0,type=global,name=\"\",size=300,span=0\n"
88
		"seg\tid=0,name=\"CODE\",start=0x%06X,size=0x012C,addrsize=absolute,type=ro,oname=\"ovl.prg\"\n"
89
		"span\tid=0,seg=0,start=0,size=1,type=0\n"
90
		"sym\tid=0,name=\"OVL_START\",addrsize=absolute,size=1,scope=0,def=0,val=0x%04X,type=lab\n",
91
		source_name, seg_start, seg_start);
92
	if (n <= 0 || (size_t)n >= sizeof(text))
93
		return false;
94
	return write_file(path, text, (size_t)n);
95
}
96
97
int
98
main(void)
99
{
100
	// ── Off by default ──────────────────────────────────────────────────────
101
	// Acting on loads lets the emulated program choose which host files get
102
	// parsed, so it has to be asked for.
103
	{
104
		dbg_load_reset();
105
		dbg_load_set_enabled(false);
106
		check(!dbg_load_is_enabled(), "starts disabled");
107
108
		dbg_load_begin(1, "/host/GAME.PRG");
109
		transfer(1, 0x0801, 0, 64, true);
110
111
		dbg_load_event_t ev;
112
		check(!dbg_load_take(&ev), "collects nothing at all while disabled");
113
	}
114
115
	// ── A plain load ────────────────────────────────────────────────────────
116
	{
117
		dbg_load_reset();
118
		dbg_load_set_enabled(true);
119
		check(dbg_load_is_enabled(), "can be enabled");
120
121
		dbg_load_begin(1, "/host/GAME.PRG");
122
		transfer(1, 0x0801, 0, 100, true);
123
124
		dbg_load_event_t ev;
125
		check(dbg_load_take(&ev), "reports a completed load");
126
		check(strcmp(ev.path, "/host/GAME.PRG") == 0, "with the host path it came from");
127
		check(ev.addr == 0x0801, "and where it started");
128
		check(ev.size == 100, "and how much went into memory");
129
130
		check(!dbg_load_take(&ev), "and reports it only once");
131
	}
132
133
	// ── Defect 1: the bank must be the one it STARTED in ────────────────────
134
	// A load into banked RAM that overruns $BFFF continues at $A000 in the next
135
	// bank. Sampling the bank at the end names a segment the program did not
136
	// start in, and the debug info would then attribute it to the wrong bank.
137
	{
138
		dbg_load_reset();
139
		dbg_load_set_enabled(true);
140
141
		// Start near the top of bank 5 and run past the end of the window, so
142
		// the transfer finishes in bank 6.
143
		dbg_load_begin(2, "/host/OVERLAY.PRG");
144
		transfer(2, 0xBF00, 5, 0x200, true);  // 512 bytes: 256 in bank 5, 256 in bank 6
145
146
		dbg_load_event_t ev;
147
		check(dbg_load_take(&ev), "reports a load that crossed a bank boundary");
148
		check(ev.addr == 0xBF00, "starting where it started");
149
		check(ev.bank == 5, "in the bank it started in, not the one it ended in");
150
		check(ev.size == 0x200, "having counted every byte");
151
	}
152
153
	// ── Defect 2: two channels open at once ─────────────────────────────────
154
	// The second file to be opened must not overwrite what the first is still
155
	// transferring, or one load is reported with the other's name.
156
	{
157
		dbg_load_reset();
158
		dbg_load_set_enabled(true);
159
160
		dbg_load_begin(1, "/host/FIRST.PRG");
161
		transfer(1, 0x0801, 0, 9, false);       // first channel starts
162
163
		dbg_load_begin(2, "/host/SECOND.PRG");
164
		transfer(2, 0xA000, 3, 19, false);      // second opens and transfers too
165
166
		dbg_load_note_byte(1, 0x080A, 0, true);   // first finishes: 10 bytes
167
		dbg_load_event_t ev;
168
		check(dbg_load_take(&ev), "reports the first channel's load");
169
		check(strcmp(ev.path, "/host/FIRST.PRG") == 0, "with its own name");
170
		check(ev.addr == 0x0801 && ev.size == 10, "and its own address and size");
171
172
		dbg_load_note_byte(2, 0xA013, 3, true);   // then the second: 20 bytes
173
		check(dbg_load_take(&ev), "reports the second channel's load");
174
		check(strcmp(ev.path, "/host/SECOND.PRG") == 0, "with its own name too");
175
		check(ev.addr == 0xA000 && ev.bank == 3 && ev.size == 20,
176
		      "and its own address, bank and size");
177
	}
178
179
	// ── Defect 3: reading a data file is not loading a program ──────────────
180
	// A BASIC program reading a file a byte at a time opens a channel and
181
	// reaches EOF exactly like a load does. What distinguishes them is that it
182
	// never moves bytes into memory through a block transfer.
183
	//
184
	// The decision itself lives in ieee.c -- only MACPTR calls note_byte, and
185
	// GET#/INPUT# go through ACPTR, which does not -- so what is checked here
186
	// is the contract ieee.c relies on: reporting no bytes must never produce a
187
	// load, and reporting bytes must. The ieee.c side is covered by driving a
188
	// real LOAD through the emulator.
189
	{
190
		dbg_load_reset();
191
		dbg_load_set_enabled(true);
192
193
		dbg_load_begin(1, "/host/SCORES.DAT");
194
		dbg_load_abandon(1);             // read to EOF, no byte ever reported
195
196
		dbg_load_event_t ev;
197
		check(!dbg_load_take(&ev), "a channel that reports no bytes is not a load");
198
199
		// The same channel, same path, reporting bytes: this one is a load. The
200
		// two together are what make the distinction meaningful rather than
201
		// just asserting that nothing happened.
202
		dbg_load_begin(1, "/host/SCORES.DAT");
203
		transfer(1, 0x0801, 0, 8, true);
204
		check(dbg_load_take(&ev) && ev.size == 8,
205
		      "and the same channel reporting bytes is one");
206
	}
207
208
	// ── A channel closed without finishing ──────────────────────────────────
209
	{
210
		dbg_load_reset();
211
		dbg_load_set_enabled(true);
212
213
		dbg_load_begin(1, "/host/PARTIAL.PRG");
214
		transfer(1, 0x0801, 0, 50, false);
215
		dbg_load_abandon(1);             // closed early, never completed
216
217
		dbg_load_event_t ev;
218
		check(!dbg_load_take(&ev), "an abandoned transfer reports nothing");
219
220
		// The close has to actually forget the channel, not just fail to
221
		// publish: a byte arriving afterwards must not finish the load that was
222
		// abandoned.
223
		dbg_load_note_byte(1, 0x0801, 0, true);
224
		check(!dbg_load_take(&ev), "and a byte after the close starts nothing");
225
226
		// And the channel is reusable afterwards.
227
		dbg_load_begin(1, "/host/NEXT.PRG");
228
		transfer(1, 0x2000, 0, 8, true);
229
		check(dbg_load_take(&ev) && strcmp(ev.path, "/host/NEXT.PRG") == 0,
230
		      "and the channel still works afterwards");
231
	}
232
233
	// ── Bytes on a channel that never opened ────────────────────────────────
234
	{
235
		dbg_load_reset();
236
		dbg_load_set_enabled(true);
237
		dbg_load_note_byte(3, 0x1000, 0, true);
238
		dbg_load_event_t ev;
239
		check(!dbg_load_take(&ev), "a byte on an unopened channel reports nothing");
240
241
		// Nor a whole transfer's worth of them.
242
		transfer(4, 0x1000, 0, 100, true);
243
		check(!dbg_load_take(&ev), "nor can a transfer on one invent a load");
244
	}
245
246
	// ── Out-of-range channels are ignored, not written through ──────────────
247
	{
248
		dbg_load_reset();
249
		dbg_load_set_enabled(true);
250
		dbg_load_begin(-1, "/host/X.PRG");
251
		dbg_load_begin(DBG_LOAD_CHANNELS, "/host/X.PRG");
252
		dbg_load_begin(9999, "/host/X.PRG");
253
		dbg_load_note_byte(-1, 0, 0, false);
254
		dbg_load_note_byte(9999, 0, 0, true);
255
		dbg_load_note_byte(DBG_LOAD_CHANNELS, 0, 0, true);
256
		dbg_load_abandon(-1);
257
		dbg_load_abandon(9999);
258
		dbg_load_event_t ev;
259
		check(!dbg_load_take(&ev), "an out-of-range channel is ignored");
260
	}
261
262
	// ── Only the first destination is kept ──────────────────────────────────
263
	// MACPTR reports every byte; the one that says where the program begins is
264
	// the first.
265
	{
266
		dbg_load_reset();
267
		dbg_load_set_enabled(true);
268
		dbg_load_begin(1, "/host/P.PRG");
269
		dbg_load_note_byte(1, 0x0801, 0, false);
270
		dbg_load_note_byte(1, 0x9999, 7, true);   // later bytes must not move it
271
272
		dbg_load_event_t ev;
273
		check(dbg_load_take(&ev) && ev.addr == 0x0801 && ev.bank == 0,
274
		      "keeps the first destination, not the last");
275
	}
276
277
	// ── Reset clears everything in flight ───────────────────────────────────
278
	{
279
		dbg_load_reset();
280
		dbg_load_set_enabled(true);
281
		dbg_load_begin(1, "/host/A.PRG");
282
		transfer(1, 0x0801, 0, 10, true);   // a completed load is now pending
283
		dbg_load_begin(2, "/host/B.PRG");
284
		transfer(2, 0x2000, 0, 10, false);      // and another is in flight
285
286
		dbg_load_reset();
287
288
		dbg_load_event_t ev;
289
		check(!dbg_load_take(&ev), "reset drops a completed load");
290
		dbg_load_note_byte(2, 0x200A, 0, true);
291
		check(!dbg_load_take(&ev), "and one that was in flight");
292
	}
293
294
	// ── A very long path is refused, not truncated ──────────────────────────
295
	{
296
		dbg_load_reset();
297
		dbg_load_set_enabled(true);
298
		char longpath[DBG_LOAD_PATH_MAX + 64];
299
		memset(longpath, 'a', sizeof(longpath) - 1);
300
		longpath[sizeof(longpath) - 1] = '\0';
301
302
		dbg_load_begin(1, longpath);
303
		transfer(1, 0x0801, 0, 4, true);
304
305
		// Refused, not truncated. A truncated path names a *different* host
306
		// file, which the poll would then go and parse -- so a copy that merely
307
		// fits inside the buffer is not good enough.
308
		dbg_load_event_t ev;
309
		check(!dbg_load_take(&ev), "an over-long path is refused, not truncated");
310
311
		// One character shorter than the buffer still fits, so the boundary is
312
		// off-by-one safe in both directions.
313
		char fits[DBG_LOAD_PATH_MAX];
314
		memset(fits, 'a', sizeof(fits) - 1);
315
		fits[sizeof(fits) - 1] = '\0';
316
		dbg_load_begin(1, fits);
317
		transfer(1, 0x0801, 0, 4, true);
318
		check(dbg_load_take(&ev) && strcmp(ev.path, fits) == 0,
319
		      "a path that exactly fits is kept whole");
320
	}
321
322
	// ── Every channel works ─────────────────────────────────────────────────
323
	{
324
		dbg_load_reset();
325
		dbg_load_set_enabled(true);
326
		bool all_ok = true;
327
		for (int ch = 0; ch < DBG_LOAD_CHANNELS; ch++) {
328
			char p[64];
329
			snprintf(p, sizeof(p), "/host/CH%d.PRG", ch);
330
			dbg_load_begin(ch, p);
331
			transfer(ch, (uint16_t)(0x1000 + ch), 0, 4, true);
332
333
			dbg_load_event_t ev;
334
			if (!dbg_load_take(&ev) || strcmp(ev.path, p) != 0
335
			    || ev.addr != (uint16_t)(0x1000 + ch)) {
336
				all_ok = false;
337
			}
338
		}
339
		check(all_ok, "every channel reports its own load");
340
	}
341
342
	// ── The final byte is counted ───────────────────────────────────────────
343
	// This is the one that bit: the transfer loop closes its channel as soon as
344
	// it sees the end of the file, and when counting a byte and finishing the
345
	// load were separate calls, the close landed between them. Every load came
346
	// out a byte short. Reporting the last byte is now what finishes the load,
347
	// so the count must include it.
348
	{
349
		dbg_load_reset();
350
		dbg_load_set_enabled(true);
351
		dbg_load_begin(1, "/host/P.PRG");
352
		for (int i = 0; i < 9; i++)
353
			dbg_load_note_byte(1, (uint16_t)(0x0801 + i), 0, false);
354
		dbg_load_note_byte(1, 0x080A, 0, true);   // the tenth byte ends the file
355
356
		dbg_load_event_t ev;
357
		check(dbg_load_take(&ev) && ev.size == 10,
358
		      "the byte that ends the file is counted");
359
	}
360
361
	// ── Nothing is reported after the load finishes ─────────────────────────
362
	// The last byte closes the channel, so anything the file layer reports
363
	// afterwards belongs to no load and must not resurrect one.
364
	{
365
		dbg_load_reset();
366
		dbg_load_set_enabled(true);
367
		dbg_load_begin(1, "/host/P.PRG");
368
		dbg_load_note_byte(1, 0x0801, 0, true);
369
370
		dbg_load_event_t ev;
371
		check(dbg_load_take(&ev) && ev.size == 1, "a one-byte file is a load");
372
373
		dbg_load_note_byte(1, 0x0802, 0, true);   // stray report after the end
374
		check(!dbg_load_take(&ev), "a stray byte afterwards starts nothing");
375
	}
376
	// ── A .dbg loaded as if it were a program ───────────────────────────────
377
	// A wildcard like LOAD"GAME*" can match GAME.dbg instead of GAME.PRG, and
378
	// the machine will read that text file into memory at whatever its first
379
	// two bytes happen to spell out. Reading it back as debug info for itself
380
	// would attribute its segments to that nonsense address, so it is refused.
381
	{
382
		check(dbg_load_path_is_dbg("/host/GAME.dbg"), "recognises a .dbg path");
383
		check(dbg_load_path_is_dbg("/host/GAME.DBG"), "whatever its case");
384
		check(dbg_load_path_is_dbg("GAME.Dbg"),       "with no directory");
385
		check(!dbg_load_path_is_dbg("/host/GAME.PRG"), "a .prg is not one");
386
		check(!dbg_load_path_is_dbg("/host/GAME.dbginfo"), "nor a longer suffix");
387
		check(!dbg_load_path_is_dbg("/host/dbg"),      "nor a bare name");
388
		check(dbg_load_path_is_dbg(".dbg"),            "a bare extension counts");
389
		check(!dbg_load_path_is_dbg(""),               "an empty path is not");
390
		check(!dbg_load_path_is_dbg(NULL),             "nor a missing one");
391
	}
392
393
	// ── The -prg file ───────────────────────────────────────────────────────
394
	// -prg never goes through filename resolution: the KERNAL asks for ":*" and
395
	// the file layer hands back a handle that was opened before the machine
396
	// started. The path has to have been recorded up front, or the commonest
397
	// debugging workflow of all is the one that silently gets no symbols.
398
	{
399
		dbg_load_reset();
400
		dbg_load_set_enabled(true);
401
		dbg_load_set_prg_path("/host/GAME.PRG");
402
403
		dbg_load_begin_prg(1);
404
		transfer(1, 0xA000, 2, 128, true);
405
406
		dbg_load_event_t ev;
407
		check(dbg_load_take(&ev), "reports the -prg load");
408
		check(strcmp(ev.path, "/host/GAME.PRG") == 0, "with the path -prg was given");
409
		check(ev.addr == 0xA000 && ev.bank == 2 && ev.size == 128,
410
		      "and where it landed");
411
	}
412
413
	// ── No -prg file ────────────────────────────────────────────────────────
414
	{
415
		dbg_load_reset();
416
		dbg_load_set_enabled(true);
417
		dbg_load_set_prg_path(NULL);
418
419
		dbg_load_begin_prg(1);
420
		transfer(1, 0xA000, 2, 16, true);
421
422
		dbg_load_event_t ev;
423
		check(!dbg_load_take(&ev), "no -prg path means no load is reported");
424
	}
425
426
	// ── A -prg path too long to hold ────────────────────────────────────────
427
	// Truncating it would name a different file, so it is refused outright.
428
	{
429
		dbg_load_reset();
430
		dbg_load_set_enabled(true);
431
		char longpath[DBG_LOAD_PATH_MAX + 64];
432
		memset(longpath, 'b', sizeof(longpath) - 1);
433
		longpath[sizeof(longpath) - 1] = '\0';
434
		dbg_load_set_prg_path(longpath);
435
436
		dbg_load_begin_prg(1);
437
		transfer(1, 0xA000, 2, 16, true);
438
439
		dbg_load_event_t ev;
440
		check(!dbg_load_take(&ev), "an over-long -prg path is refused, not truncated");
441
	}
442
443
	// ── Setting a new -prg path replaces the old one ────────────────────────
444
	{
445
		dbg_load_reset();
446
		dbg_load_set_enabled(true);
447
		dbg_load_set_prg_path("/host/FIRST.PRG");
448
		dbg_load_set_prg_path("/host/SECOND.PRG");
449
450
		dbg_load_begin_prg(1);
451
		transfer(1, 0x0801, 0, 4, true);
452
453
		dbg_load_event_t ev;
454
		check(dbg_load_take(&ev) && strcmp(ev.path, "/host/SECOND.PRG") == 0,
455
		      "the most recent -prg path is the one used");
456
	}
457
458
	// ── A path is merged into the debug info only once ──────────────────────
459
	// dbg_info_load() is additive and dbg_info_unload_range() does not prune
460
	// file or equate records, so parsing the same .dbg twice appends a second
461
	// copy of every equate -- and lookups return the first match. An overlay
462
	// loader swapping the same module in and out would grow without bound and
463
	// keep resolving symbols to their first-seen values.
464
	//
465
	// dbg_load_poll() is what enforces this, and it needs real files on disk,
466
	// so what is pinned here is the part that decides: repeated loads of the
467
	// same path still each produce an event (the bank may differ), because the
468
	// de-duplication must happen at the merge, not by dropping the load.
469
	{
470
		dbg_load_reset();
471
		dbg_load_set_enabled(true);
472
473
		for (int i = 0; i < 3; i++) {
474
			dbg_load_begin(1, "/host/OVERLAY.PRG");
475
			transfer(1, 0xA000, (uint8_t)(4 + i), 64, true);
476
477
			dbg_load_event_t ev;
478
			check(dbg_load_take(&ev) && ev.bank == (uint8_t)(4 + i),
479
			      "reloading the same overlay still reports its new bank");
480
		}
481
	}
482
483
	// ── Merging the debug info, against real files ──────────────────────────
484
	// The only test that drives dbg_load_poll() end to end, because it needs a
485
	// .dbg on disk. It pins the thing the unit-level checks above cannot see:
486
	// the same overlay loaded repeatedly must keep being reported, so that its
487
	// (possibly new) bank is re-applied every time -- the de-duplication has to
488
	// happen at the merge, not by dropping the load.
489
	{
490
		const char *prg = "dbgload_test_tmp.prg";
491
		const char *dbg = "dbgload_test_tmp.dbg";
492
493
		if (!write_dbg(dbg, "ovl.s", 0xA000)) {
494
			check(false, "could not write the temporary .dbg");
495
		} else {
496
			dbg_load_reset();
497
			dbg_load_set_enabled(true);
498
499
			bool all_reported = true;
500
			for (int i = 0; i < 3; i++) {
501
				dbg_load_begin(1, prg);
502
				transfer(1, 0xA000, (uint8_t)(4 + i), 0x12C, true);
503
				if (!dbg_load_poll())
504
					all_reported = false;
505
			}
506
			check(all_reported, "every load of the same overlay is acted on");
507
508
			// The .dbg names one source file. Merging it three times would
509
			// append three copies, because dbg_info_load() is additive and
510
			// nothing prunes its file records -- which is also how stale
511
			// equates come to shadow live ones.
512
			check(dbg_info_file_count() == 1,
513
			      "but its debug info is merged only once");
514
515
			// The bank from the LAST load is the one in force: the same
516
			// overlay can legitimately move to a different bank, so the
517
			// annotation has to be re-applied even when the merge is skipped.
518
			const char *src_file = NULL;
519
			int         src_line = 0;
520
			check(dbg_info_addr_to_source_banked_ex(0xA000, 6, &src_file, &src_line)
521
			          == DBG_BANK_RESOLVED,
522
			      "and the bank from the most recent load is the one that sticks");
523
			// The bank it was FIRST loaded into no longer resolves. It reports
524
			// DBG_BANK_UNKNOWN rather than NO_MATCH, because the address is
525
			// still described -- just not as belonging to that bank.
526
			check(dbg_info_addr_to_source_banked_ex(0xA000, 4, &src_file, &src_line)
527
			          != DBG_BANK_RESOLVED,
528
			      "not the bank it was first loaded into");
529
530
			// A load with no .dbg beside it is simply not acted on.
531
			dbg_load_begin(1, "dbgload_test_missing.prg");
532
			transfer(1, 0xA000, 4, 0x12C, true);
533
			check(!dbg_load_poll(), "a program with no .dbg beside it is skipped");
534
535
			// The .dbg itself, read as if it were a program, which a wildcard
536
			// LOAD can genuinely do. It exists on disk and would parse happily
537
			// as debug info for itself, so only the guard stops it.
538
			dbg_load_begin(1, dbg);
539
			transfer(1, 0xA000, 4, 0x12C, true);
540
			check(!dbg_load_poll(), "a .dbg read as a program is refused");
541
542
			// And polling with nothing pending does nothing.
543
			check(!dbg_load_poll(), "polling with no load pending does nothing");
544
545
			remove(dbg);
546
		}
547
		remove(prg);
548
	}
549
550
	// ── A displaced module comes back ───────────────────────────────────────
551
	// Two overlays living at the same address, which is what overlays ARE.
552
	// Merging one destroys the other's records, because dbg_info_load_for_file()
553
	// unloads the incoming file's range first. So re-merging is how a module
554
	// that was swapped out gets restored, and anything that remembers "this was
555
	// merged once" and skips it forever leaves the debugger showing the wrong
556
	// module's source as authoritative for the code that is actually running.
557
	{
558
		const char *dbg1 = "dbgload_test_ovl1.dbg";
559
		const char *dbg2 = "dbgload_test_ovl2.dbg";
560
		const char *prg1 = "dbgload_test_ovl1.prg";
561
		const char *prg2 = "dbgload_test_ovl2.prg";
562
563
		if (!write_dbg(dbg1, "ovl1.s", 0xA000) || !write_dbg(dbg2, "ovl2.s", 0xA000)) {
564
			check(false, "could not write the temporary overlay .dbg files");
565
		} else {
566
			dbg_load_reset();
567
			dbg_load_set_enabled(true);
568
569
			const char *src  = NULL;
570
			int         line = 0;
571
572
			dbg_load_begin(1, prg1);
573
			transfer(1, 0xA000, 5, 0x12C, true);
574
			dbg_load_poll();
575
			check(dbg_info_addr_to_source(0xA000, &src, &line)
576
			      && strstr(src, "ovl1") != NULL,
577
			      "the first overlay describes its own code");
578
579
			dbg_load_begin(1, prg2);
580
			transfer(1, 0xA000, 5, 0x12C, true);
581
			dbg_load_poll();
582
			check(dbg_info_addr_to_source(0xA000, &src, &line)
583
			      && strstr(src, "ovl2") != NULL,
584
			      "loading the second one takes over that address");
585
586
			// Back to the first. Its records were destroyed by the second, so
587
			// this has to merge again rather than assume it is still there.
588
			dbg_load_begin(1, prg1);
589
			transfer(1, 0xA000, 5, 0x12C, true);
590
			dbg_load_poll();
591
			check(dbg_info_addr_to_source(0xA000, &src, &line)
592
			      && strstr(src, "ovl1") != NULL,
593
			      "and swapping the first one back restores it");
594
595
			remove(dbg1);
596
			remove(dbg2);
597
		}
598
	}
599
600
	// ── A module somewhere else is left alone ───────────────────────────────
601
	// Only the modules a merge actually displaces should be forgotten. Loading
602
	// something at a different address must not cause every other module to be
603
	// re-merged next time it loads, which would put the unbounded growth and
604
	// the stale-equate shadowing straight back.
605
	{
606
		const char *dbg_lo  = "dbgload_test_lo.dbg";
607
		const char *dbg_hi  = "dbgload_test_hi.dbg";
608
		const char *prg_lo  = "dbgload_test_lo.prg";
609
		const char *prg_hi  = "dbgload_test_hi.prg";
610
611
		if (!write_dbg(dbg_lo, "lo.s", 0x6000) || !write_dbg(dbg_hi, "hi.s", 0xA000)) {
612
			check(false, "could not write the temporary .dbg files");
613
		} else {
614
			dbg_load_reset();
615
			dbg_load_set_enabled(true);
616
617
			dbg_load_begin(1, prg_lo);
618
			transfer(1, 0x6000, 0, 0x12C, true);
619
			dbg_load_poll();
620
621
			dbg_load_begin(1, prg_hi);
622
			transfer(1, 0xA000, 5, 0x12C, true);
623
			dbg_load_poll();
624
625
			// Both are live now. Re-loading the low one must not merge again:
626
			// the high one never touched its range.
627
			int before = dbg_info_file_count();
628
			dbg_load_begin(1, prg_lo);
629
			transfer(1, 0x6000, 0, 0x12C, true);
630
			dbg_load_poll();
631
			check(dbg_info_file_count() == before,
632
			      "a module the others never displaced is not merged twice");
633
634
			// And both still describe their own code.
635
			const char *src = NULL;
636
			int line = 0;
637
			check(dbg_info_addr_to_source(0x6000, &src, &line)
638
			      && strstr(src, "lo") != NULL, "the low module is intact");
639
			check(dbg_info_addr_to_source(0xA000, &src, &line)
640
			      && strstr(src, "hi") != NULL, "and so is the high one");
641
642
			remove(dbg_lo);
643
			remove(dbg_hi);
644
		}
645
	}
646
647
	// ── Following the debugger, not a snapshot of it ────────────────────────
648
	// The running machine can switch the debugger on itself through emu_write,
649
	// so a decision made once at startup would be wrong for the rest of the
650
	// session.
651
	{
652
		dbg_load_reset();
653
654
		dbg_load_set_policy(-1);          // follow
655
		dbg_load_note_debugger(false);
656
		check(!dbg_load_is_enabled(), "follows the debugger: off when it is off");
657
		dbg_load_note_debugger(true);
658
		check(dbg_load_is_enabled(), "and on once it is switched on");
659
		dbg_load_note_debugger(false);
660
		check(!dbg_load_is_enabled(), "and off again when it is switched off");
661
662
		dbg_load_set_policy(1);           // forced on
663
		dbg_load_note_debugger(false);
664
		check(dbg_load_is_enabled(), "forced on ignores the debugger");
665
666
		dbg_load_set_policy(0);           // forced off
667
		dbg_load_note_debugger(true);
668
		check(!dbg_load_is_enabled(), "and forced off ignores it too");
669
670
		// Order does not matter: the policy can be set before or after the
671
		// debugger state is known.
672
		dbg_load_note_debugger(true);
673
		dbg_load_set_policy(-1);
674
		check(dbg_load_is_enabled(), "the two can be set in either order");
675
	}
676
677
	// ── A load we cannot describe drops what described that range ───────────
678
	// If B overwrites A's address range and B has no .dbg, A's debug info now
679
	// describes bytes that are gone. A debugger confidently showing the wrong
680
	// source is worse than one showing none, so those records go.
681
	{
682
		const char *dbg_a = "dbgload_test_a.dbg";
683
		const char *prg_a = "dbgload_test_a.prg";
684
		const char *prg_b = "dbgload_test_b.prg";   // deliberately has no .dbg
685
686
		if (!write_dbg(dbg_a, "a.s", 0xA000)) {
687
			check(false, "could not write the temporary .dbg");
688
		} else {
689
			dbg_load_reset();
690
			dbg_load_set_enabled(true);
691
692
			dbg_load_begin(1, prg_a);
693
			transfer(1, 0xA000, 5, 0x12C, true);
694
			dbg_load_poll();
695
696
			const char *src = NULL;
697
			int line = 0;
698
			check(dbg_info_addr_to_source(0xA000, &src, &line),
699
			      "the described module resolves to start with");
700
701
			// Something with no debug info lands on top of it.
702
			dbg_load_begin(1, prg_b);
703
			transfer(1, 0xA000, 5, 0x12C, true);
704
			check(!dbg_load_poll(), "a load with no .dbg reports nothing");
705
			check(!dbg_info_addr_to_source(0xA000, &src, &line),
706
			      "and takes the overwritten module's source with it");
707
708
			// And the displaced module can come back.
709
			dbg_load_begin(1, prg_a);
710
			transfer(1, 0xA000, 5, 0x12C, true);
711
			dbg_load_poll();
712
			check(dbg_info_addr_to_source(0xA000, &src, &line),
713
			      "the described module can be loaded again afterwards");
714
715
			remove(dbg_a);
716
		}
717
	}
718
719
	// ── A load somewhere else leaves it alone ───────────────────────────────
720
	// The rule above must apply only to the range actually overwritten.
721
	{
722
		const char *dbg_a = "dbgload_test_keep.dbg";
723
		const char *prg_a = "dbgload_test_keep.prg";
724
		const char *prg_x = "dbgload_test_elsewhere.prg";  // no .dbg
725
726
		if (!write_dbg(dbg_a, "keep.s", 0xA000)) {
727
			check(false, "could not write the temporary .dbg");
728
		} else {
729
			dbg_load_reset();
730
			dbg_load_set_enabled(true);
731
732
			dbg_load_begin(1, prg_a);
733
			transfer(1, 0xA000, 5, 0x12C, true);
734
			dbg_load_poll();
735
736
			// An undescribed load well clear of it.
737
			dbg_load_begin(1, prg_x);
738
			transfer(1, 0x2000, 0, 0x100, true);
739
			dbg_load_poll();
740
741
			const char *src = NULL;
742
			int line = 0;
743
			check(dbg_info_addr_to_source(0xA000, &src, &line),
744
			      "an undescribed load elsewhere leaves other modules alone");
745
746
			remove(dbg_a);
747
		}
748
	}
749
750
	// ── Loading data into your own buffer keeps your debug info ─────────────
751
	// The envelope recorded for a module is the min/max across every segment in
752
	// its .dbg, including BSS, so for a real cc65 program it covers most of low
753
	// RAM. A program loading a level or a font into one of its own buffers
754
	// therefore lands *inside* its own envelope while invalidating nothing. If
755
	// a partial overlap discarded debug info, the commonest thing a program can
756
	// do would throw away the source mapping for the code that is running, and
757
	// nothing would bring it back -- the program is already resident and is
758
	// never re-loaded.
759
	{
760
		const char *dbg_p = "dbgload_test_partial.dbg";
761
		const char *prg_p = "dbgload_test_partial.prg";
762
		const char *dat_p = "dbgload_test_level.dat";   // no .dbg beside it
763
764
		if (!write_dbg(dbg_p, "partial.s", 0xA000)) {
765
			check(false, "could not write the temporary .dbg");
766
		} else {
767
			dbg_load_reset();
768
			dbg_load_set_enabled(true);
769
770
			dbg_load_begin(1, prg_p);
771
			transfer(1, 0xA000, 5, 0x12C, true);   // envelope $A000-$A12B
772
			dbg_load_poll();
773
774
			const char *src = NULL;
775
			int line = 0;
776
			check(dbg_info_addr_to_source(0xA000, &src, &line),
777
			      "the program describes itself once loaded");
778
779
			// Data lands inside its envelope, but nowhere near covering it.
780
			dbg_load_begin(1, dat_p);
781
			transfer(1, 0xA100, 5, 0x10, true);
782
			dbg_load_poll();
783
			check(dbg_info_addr_to_source(0xA000, &src, &line),
784
			      "loading data into its own range does not discard it");
785
786
			// Data starting before it but still not covering it.
787
			dbg_load_begin(1, dat_p);
788
			transfer(1, 0x9F00, 5, 0x200, true);
789
			dbg_load_poll();
790
			check(dbg_info_addr_to_source(0xA000, &src, &line),
791
			      "nor does a load that only partly covers it");
792
793
			// A load that covers it whole genuinely does replace it.
794
			dbg_load_begin(1, dat_p);
795
			transfer(1, 0x9F00, 5, 0x400, true);
796
			dbg_load_poll();
797
			check(!dbg_info_addr_to_source(0xA000, &src, &line),
798
			      "but a load covering it entirely does replace it");
799
800
			remove(dbg_p);
801
		}
802
	}
803
804
	if (failures) {
805
		printf("\nFAILED (%d failures)\n", failures);
806
		return 1;
807
	}
808
	printf("\nPASSED (0 failures)\n");
809
	return 0;
810
}