OXYGENHUB
◆ main enhanced-x16-emulator / src / dbg_load.c
13.6 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator — debug info for programs the guest loads. See dbg_load.h.
2
3
#include "dbg_load.h"
4
#include "dbg_info.h"
5
#include "debug_server.h"
6
#include "source_view.h"
7
8
#include <stdlib.h>
9
#include <string.h>
10
11
// What a channel has told us so far. Kept per channel because two can be open
12
// at once, and the second one to be opened must not overwrite what the first is
13
// still in the middle of doing.
14
struct channel_load {
15
	char     path[DBG_LOAD_PATH_MAX];
16
	uint16_t addr;
17
	uint8_t  bank;
18
	uint32_t size;       // bytes reported; zero means no byte has landed yet
19
	bool     open;       // a path has been recorded
20
};
21
22
static struct channel_load channels[DBG_LOAD_CHANNELS];
23
static dbg_load_event_t    pending;
24
static bool                enabled;
25
26
// See dbg_load_set_policy(): the debugger can be switched on by the running
27
// machine, so what the command line asked for is kept separately from what the
28
// debugger is currently doing, and the two are resolved on every change.
29
static int  policy = -1;
30
static bool debugger_on;
31
32
static void
33
apply_policy(void)
34
{
35
	enabled = (policy >= 0) ? (policy > 0) : debugger_on;
36
}
37
38
// Configuration rather than state, so a machine reset does not clear it: the
39
// -prg file is still the -prg file afterwards.
40
static char prg_path[DBG_LOAD_PATH_MAX];
41
42
// Which .dbg files are currently merged into dbg_info, and over what range.
43
//
44
// dbg_info_load() is additive and dbg_info_unload_range() prunes only some of
45
// what it builds. File and equate records are never pruned by address; they are
46
// instead reused or replaced when the same .dbg is parsed again, so those no
47
// longer accumulate. Segment, span, line and label records still append, and
48
// they are pruned only by the address range handed to the unload -- so tracking
49
// which .dbg is live over which range is what keeps the two in step.
50
//
51
// But merging is also destructive: dbg_info_load_for_file() unloads everything
52
// in the incoming file's address range before reading it. Re-parsing is
53
// therefore how a module that was displaced gets its records back. So this
54
// tracks what is *live*, not what has *ever* been merged: when a new file
55
// evicts a range, the modules it overlaps are forgotten and will merge again
56
// the next time they load.
57
//
58
// Not cleared by dbg_load_reset(): a machine reset does not free dbg_info, so
59
// those records are still live afterwards.
60
// The registry grows rather than evicting. Evicting a live module would be
61
// unsound: dropping its entry does not drop its records, so the next load of
62
// that path would look like a first sighting and merge a second copy -- the
63
// exact duplication this exists to prevent. It stays small in practice because
64
// every merge forgets the modules it displaces, so entries only ever track
65
// modules that are genuinely resident at the same time.
66
struct parsed_entry {
67
	char       path[DBG_LOAD_PATH_MAX];
68
	dbg_addr_t start;
69
	dbg_addr_t end;
70
};
71
72
static struct parsed_entry *parsed;
73
static int                  parsed_count;
74
static int                  parsed_cap;
75
76
static bool
77
already_parsed(const char *path)
78
{
79
	for (int i = 0; i < parsed_count; i++) {
80
		if (strcmp(parsed[i].path, path) == 0)
81
			return true;
82
	}
83
	return false;
84
}
85
86
// Drop the tracking entries for every module overlapping [start,end], and
87
// return how many there were. The caller unloads the records, because what
88
// needs unloading differs between "something else is taking this range" and
89
// "this range is being overwritten by something we cannot describe at all".
90
static int
91
forget_overlapping(dbg_addr_t start, dbg_addr_t end)
92
{
93
	int keep    = 0;
94
	int dropped = 0;
95
96
	for (int i = 0; i < parsed_count; i++) {
97
		bool overlaps = parsed[i].start <= end && start <= parsed[i].end;
98
		if (overlaps) {
99
			dropped++;
100
		} else {
101
			if (keep != i)
102
				parsed[keep] = parsed[i];
103
			keep++;
104
		}
105
	}
106
	parsed_count = keep;
107
	return dropped;
108
}
109
110
// Make room for one more entry without adding it, so a merge is never done
111
// unless its result can be tracked. A merged-but-untracked module would be
112
// merged a second time on its next load, which is the duplication all this
113
// exists to prevent.
114
static bool
115
reserve_parsed_slot(const char *path)
116
{
117
	if (strlen(path) >= DBG_LOAD_PATH_MAX)
118
		return false;
119
120
	if (parsed_count < parsed_cap)
121
		return true;
122
123
	int new_cap = parsed_cap ? parsed_cap * 2 : 8;
124
	struct parsed_entry *grown = realloc(parsed, (size_t)new_cap * sizeof(*grown));
125
	if (!grown)
126
		return false;
127
128
	parsed     = grown;
129
	parsed_cap = new_cap;
130
	return true;
131
}
132
133
// Record a module as live. The caller must have reserved a slot first, so this
134
// cannot fail.
135
static void
136
remember_parsed(const char *path, dbg_addr_t start, dbg_addr_t end)
137
{
138
	size_t len = strlen(path);
139
140
	memcpy(parsed[parsed_count].path, path, len + 1);
141
	parsed[parsed_count].start = start;
142
	parsed[parsed_count].end   = end;
143
	parsed_count++;
144
}
145
static bool
146
valid_channel(int channel)
147
{
148
	return channel >= 0 && channel < DBG_LOAD_CHANNELS;
149
}
150
151
// Something we cannot describe landed at [addr, addr+size). Unload the debug
152
// info of any module it completely replaced, because that info now describes
153
// bytes which are gone, and a debugger confidently showing the wrong source is
154
// worse than one showing none.
155
//
156
// Only when the load covers a module *whole*. The envelope recorded for a
157
// module is the min/max across every segment in its .dbg -- including ZEROPAGE,
158
// DATA and BSS -- so for a typical cc65 program it spans most of low RAM. A
159
// partial overlap is therefore far more likely to be that same program loading
160
// data into one of its own buffers, which invalidates nothing; discarding its
161
// debug info for that would throw away the mapping for the code that is running.
162
//
163
// Modules this registry does not know about are left alone, so debug info the
164
// user named with -dbgfile survives: they asked for it explicitly, and guessing
165
// that a guest load invalidated it is a worse failure than leaving it.
166
static void
167
discard_range(uint16_t addr, uint32_t size)
168
{
169
	if (size == 0)
170
		return;
171
172
	dbg_addr_t start = addr;
173
	dbg_addr_t end   = addr + (size - 1);
174
	if (end > 0xFFFF)
175
		end = 0xFFFF;  // a transfer running off the top wraps; it cannot cover more
176
177
	int keep = 0;
178
	for (int i = 0; i < parsed_count; i++) {
179
		bool covered = start <= parsed[i].start && parsed[i].end <= end;
180
		if (covered) {
181
			dbg_info_unload_range(parsed[i].start, parsed[i].end);
182
		} else {
183
			if (keep != i)
184
				parsed[keep] = parsed[i];
185
			keep++;
186
		}
187
	}
188
	parsed_count = keep;
189
}
190
191
// Whether a host path names a .dbg file.
192
bool
193
dbg_load_path_is_dbg(const char *path)
194
{
195
	if (!path)
196
		return false;
197
198
	size_t n = strlen(path);
199
	if (n < 4)
200
		return false;
201
202
	const char *ext = path + n - 4;
203
	return ext[0] == '.'
204
	    && (ext[1] == 'd' || ext[1] == 'D')
205
	    && (ext[2] == 'b' || ext[2] == 'B')
206
	    && (ext[3] == 'g' || ext[3] == 'G');
207
}
208
209
void
210
dbg_load_set_policy(int new_policy)
211
{
212
	policy = new_policy;
213
	apply_policy();
214
}
215
216
void
217
dbg_load_note_debugger(bool on)
218
{
219
	debugger_on = on;
220
	apply_policy();
221
}
222
223
void
224
dbg_load_set_enabled(bool on)
225
{
226
	dbg_load_set_policy(on ? 1 : 0);
227
}
228
229
bool
230
dbg_load_is_enabled(void)
231
{
232
	return enabled;
233
}
234
235
void
236
dbg_load_begin(int channel, const char *host_path)
237
{
238
	if (!valid_channel(channel))
239
		return;
240
241
	struct channel_load *c = &channels[channel];
242
	memset(c, 0, sizeof(*c));
243
244
	// Nothing is collected at all unless auto-loading was asked for, which
245
	// leaves the per-byte reporting below doing nothing but testing a flag.
246
	if (!enabled || !host_path || !host_path[0])
247
		return;
248
249
	// A truncated path names a different file, or none. Rather than parse
250
	// whatever that turns out to be, ignore the load.
251
	size_t len = strlen(host_path);
252
	if (len >= sizeof(c->path))
253
		return;
254
255
	memcpy(c->path, host_path, len + 1);
256
	c->open = true;
257
}
258
259
// Publish a finished load and clear the channel. Static, and reached only from
260
// dbg_load_note_byte() below with an open channel whose final byte has already
261
// been counted -- which is what keeps the byte and the completion in the right
262
// order, and why there is nothing to check here.
263
static void
264
complete(int channel)
265
{
266
	struct channel_load *c = &channels[channel];
267
268
	memcpy(pending.path, c->path, sizeof(pending.path));
269
	pending.addr  = c->addr;
270
	pending.bank  = c->bank;
271
	pending.size  = c->size;
272
	pending.valid = true;
273
274
	memset(c, 0, sizeof(*c));
275
}
276
277
void
278
dbg_load_set_prg_path(const char *path)
279
{
280
	prg_path[0] = '\0';
281
	if (!path)
282
		return;
283
284
	size_t len = strlen(path);
285
	if (len >= sizeof(prg_path))
286
		return;  // truncation would name a different file; better to say nothing
287
288
	memcpy(prg_path, path, len + 1);
289
}
290
291
void
292
dbg_load_begin_prg(int channel)
293
{
294
	dbg_load_begin(channel, prg_path);
295
}
296
297
void
298
dbg_load_note_byte(int channel, uint16_t addr, uint8_t bank, bool last)
299
{
300
	if (!valid_channel(channel))
301
		return;
302
303
	struct channel_load *c = &channels[channel];
304
	if (!c->open)
305
		return;
306
307
	// The first byte is the one that says where the program starts and which
308
	// bank it started in. A load crossing $C000 moves the RAM bank on as it
309
	// goes, so a later byte would name a different segment.
310
	if (c->size == 0) {
311
		c->addr = addr;
312
		c->bank = bank;
313
	}
314
	c->size++;
315
316
	// Counted above before this runs, which is the whole point of the two being
317
	// a single call. A channel that never reports a byte -- a data file read
318
	// through GET#, or a stream-mode transfer into a hardware register -- never
319
	// reaches here at all, and so is never mistaken for a program load.
320
	if (last)
321
		complete(channel);
322
}
323
324
void
325
dbg_load_abandon(int channel)
326
{
327
	if (!valid_channel(channel))
328
		return;
329
	memset(&channels[channel], 0, sizeof(channels[channel]));
330
}
331
332
bool
333
dbg_load_take(dbg_load_event_t *out)
334
{
335
	if (!pending.valid)
336
		return false;
337
	if (out) {
338
		*out = pending;
339
	}
340
	memset(&pending, 0, sizeof(pending));
341
	return true;
342
}
343
344
bool
345
dbg_load_poll(void)
346
{
347
	dbg_load_event_t ev;
348
	if (!dbg_load_take(&ev))
349
		return false;
350
	if (!enabled || !ev.path[0])
351
		return false;
352
353
	// A .dbg loaded as if it were a program. A wildcard like LOAD"GAME*" can
354
	// match the debug info sitting beside GAME.PRG, and the machine will
355
	// happily read that text file into memory at whatever its first two bytes
356
	// spell out. Reading it as debug info for itself would then attribute its
357
	// segments to that nonsense address -- but the bytes did land, so anything
358
	// they completely replaced is still invalidated.
359
	if (dbg_load_path_is_dbg(ev.path)) {
360
		discard_range(ev.addr, ev.size);
361
		return false;
362
	}
363
364
	// Load first. dbg_info_load_for_file() unloads whatever already describes
365
	// the address range before reading the new file, so a bank annotation made
366
	// beforehand would be thrown away with the mappings it was attached to.
367
	//
368
	// Only if this file's records are not already live -- see the note on
369
	// `parsed` above. Note also that dbg_info_load_for_file() ignores the
370
	// address it is given, so a relocating load -- secondary address 0, which
371
	// lands at BASIC start rather than the address in the file's own header --
372
	// still describes the program at its link-time addresses. That is a
373
	// limitation of the debug info, not of the destination recorded here.
374
	if (!already_parsed(ev.path)) {
375
		dbg_addr_t new_start, new_end;
376
		if (!dbg_info_peek_file_range(ev.path, &new_start, &new_end)) {
377
			// Nothing describes this file, but it still landed in memory.
378
			discard_range(ev.addr, ev.size);
379
			return false;
380
		}
381
382
		// Make room before merging, so the result can definitely be tracked.
383
		if (!reserve_parsed_slot(ev.path)) {
384
			discard_range(ev.addr, ev.size);
385
			return false;
386
		}
387
388
		int rc = dbg_info_load_for_file(ev.path, ev.addr);
389
390
		// A DAP client's breakpoints are addresses resolved from debug info
391
		// that has just been replaced, so anything inside the range it covered
392
		// is now pointing at whatever used to be there.
393
		debug_server_invalidate_breakpoints_in_range(new_start, new_end);
394
395
		// Forgotten whether or not that succeeded. It unloads the range before
396
		// reading the file, so a failure part-way through still leaves those
397
		// modules' records gone -- and a registry claiming they are live would
398
		// then skip the merge that would bring them back. Forgetting one whose
399
		// records did survive costs only a re-merge next time, which replaces
400
		// them rather than duplicating them.
401
		forget_overlapping(new_start, new_end);
402
403
		if (rc != 0)
404
			return false;
405
		remember_parsed(ev.path, new_start, new_end);
406
407
		// Breakpoints set before the program was loaded could not be resolved
408
		// then. The debug info describing them has just arrived, so this is the
409
		// moment they can be armed.
410
		debug_server_retry_unverified_breakpoints();
411
	}
412
413
	// Now say which bank the program went into, every time, because the same
414
	// overlay can legitimately be loaded into a different bank than last time.
415
	// Several segments commonly start at $A000, one per bank, and the .dbg
416
	// records no bank at all, so this is the only thing that can tell them
417
	// apart. The bank is the one that was mapped when the first byte landed --
418
	// a load crossing $C000 moves the bank on as it goes, so the bank at the
419
	// end belongs to a different segment.
420
	if (ev.addr >= 0xA000 && ev.addr <= 0xBFFF && ev.size > 0) {
421
		dbg_info_note_bank_load(ev.addr, ev.size, ev.bank);
422
	}
423
424
	// Both the .dbg's own directory and the loaded file's are host paths worth
425
	// searching for the sources it names.
426
	source_view_add_path(dbg_info_get_dbg_dir());
427
	{
428
		char dir[DBG_LOAD_PATH_MAX];
429
		strncpy(dir, ev.path, sizeof(dir) - 1);
430
		dir[sizeof(dir) - 1] = '\0';
431
		char *s1  = strrchr(dir, '/');
432
		char *s2  = strrchr(dir, '\\');
433
		char *sep = (s2 > s1) ? s2 : s1;
434
		if (sep) {
435
			*sep = '\0';
436
			source_view_add_path(dir);
437
		}
438
	}
439
440
	// An overlay can reuse a file name at a different address, so anything
441
	// cached under that name has to be re-read rather than trusted.
442
	source_view_invalidate();
443
	return true;
444
}
445
446
void
447
dbg_load_reset(void)
448
{
449
	memset(channels, 0, sizeof(channels));
450
	memset(&pending, 0, sizeof(pending));
451
}