OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / tests / test_watchpoint_purity.c
5.7 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Regression: watching memory must not change what the machine does.
2
//
3
// The watchpoint hook sits in write6502(), on the path every store takes. It
4
// runs before the write and does not return early, which is what lets a store
5
// still land while the debugger is told about it -- but nothing pinned that,
6
// and "the debugger swallowed my write" is a bug that would look like the
7
// emulated program being wrong.
8
//
9
// The wider claim is the one that matters: setting a watchpoint changes nothing
10
// except that a break is reported. These compare a whole machine signature
11
// either side of it rather than a list of fields, because the failures worth
12
// catching are the ones nobody thought to name.
13
//
14
// memory.c, debug_core.c and the CPU core are real here; only the devices
15
// behind the I/O page are fakes.
16
17
#include "support/fake_devices.h"
18
#include "support/harness.h"
19
#include "support/signature.h"
20
21
#include "debug_core.h"
22
#include "memory.h"
23
24
#include <stdint.h>
25
26
// Declared locally for the same reason the other tests do it: the only
27
// declarations live in headers that reach SDL.
28
extern uint8_t *RAM;
29
extern uint32_t clockticks6502;
30
31
#define SCRATCH 0x0400
32
#define VERA_REG 0x9F23
33
34
// fake_devices.c counts these for us.
35
extern int fake_break_count;
36
37
static void
38
setup(void)
39
{
40
	fake_devices_reset();
41
	debug_wp_clear_all();
42
	fake_break_count = 0;
43
}
44
45
// ---- A watched store still reaches memory ----------------------------------
46
47
static void
48
test_watched_store_still_writes(void)
49
{
50
	setup();
51
	RAM[SCRATCH] = 0x00;
52
	debug_wp_add_for(SCRATCH, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
53
54
	write6502(SCRATCH, 0, 0x5A);
55
56
	check_eq(RAM[SCRATCH], 0x5A, "a watched address still receives the store");
57
	check(fake_break_count > 0, "and the watchpoint reported a break");
58
}
59
60
// ---- A watched I/O store still reaches the device --------------------------
61
// The one most likely to break, because the device is behind another layer.
62
63
static void
64
test_watched_io_store_still_reaches_device(void)
65
{
66
	setup();
67
	debug_wp_add_for(VERA_REG, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
68
69
	write6502(VERA_REG, 0, 0x77);
70
71
	check_eq(fake_dev[FAKE_VERA].writes, 1, "a watched I/O store still reaches VERA");
72
	check_eq(fake_dev[FAKE_VERA].last_value, 0x77, "with the value the program wrote");
73
	check(fake_break_count > 0, "and the watchpoint reported a break");
74
}
75
76
// ---- Watching an address does not change the store itself ------------------
77
78
static void
79
test_watch_does_not_alter_the_machine(void)
80
{
81
	// Run the same store with and without a watchpoint, and compare everything
82
	// except the break count, which is the one thing that should differ.
83
	setup();
84
	write6502(SCRATCH, 0, 0x11);
85
	write6502(VERA_REG, 0, 0x22);
86
	machine_sig_t unwatched = machine_signature();
87
88
	setup();
89
	debug_wp_add_for(SCRATCH, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
90
	write6502(SCRATCH, 0, 0x11);
91
	write6502(VERA_REG, 0, 0x22);
92
	machine_sig_t watched = machine_signature();
93
94
	check(machine_sig_equal(unwatched, watched),
95
	      "a watchpoint changes nothing about the stores themselves");
96
}
97
98
// ---- A watchpoint elsewhere is inert ---------------------------------------
99
100
static void
101
test_unrelated_watch_is_inert(void)
102
{
103
	setup();
104
	write6502(SCRATCH, 0, 0x33);
105
	machine_sig_t clean = machine_signature();
106
107
	setup();
108
	debug_wp_add_for(0x0500, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
109
	write6502(SCRATCH, 0, 0x33);
110
	machine_sig_t with_other = machine_signature();
111
112
	check(machine_sig_equal(clean, with_other),
113
	      "a watchpoint on another address leaves the machine alone");
114
	check_eq(fake_break_count, 0, "and does not report a break");
115
}
116
117
// ---- Watching costs no cycles ----------------------------------------------
118
// The scan runs on every store, so if it charged anything the whole machine
119
// would run slower whenever a watchpoint was set.
120
121
static void
122
test_watching_costs_no_cycles(void)
123
{
124
	setup();
125
	write6502(SCRATCH, 0, 0x44);
126
	uint32_t plain = clockticks6502;
127
128
	setup();
129
	debug_wp_add_for(SCRATCH, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
130
	write6502(SCRATCH, 0, 0x44);
131
	check_eq(clockticks6502, plain, "a watched store costs the same as any other");
132
133
	// And on a slow I/O address, where a store does cost cycles.
134
	setup();
135
	write6502(0x9FA0, 0, 0x55);
136
	uint32_t io_plain = clockticks6502;
137
138
	setup();
139
	debug_wp_add_for(0x9FA0, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
140
	write6502(0x9FA0, 0, 0x55);
141
	check_eq(clockticks6502, io_plain, "and a watched slow-I/O store costs the same");
142
}
143
144
// ---- A value filter decides when to break, not whether to write ------------
145
146
static void
147
test_value_filter_still_writes(void)
148
{
149
	setup();
150
	debug_wp_add_for(SCRATCH, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
151
	debug_wp_set_value(SCRATCH, DEBUG_BANK_ANY, BPCMP_EQ, 0x99);
152
153
	write6502(SCRATCH, 0, 0x01);
154
	check_eq(RAM[SCRATCH], 0x01, "a store that fails the filter still writes");
155
	check_eq(fake_break_count, 0, "and reports no break");
156
157
	write6502(SCRATCH, 0, 0x99);
158
	check_eq(RAM[SCRATCH], 0x99, "a store that matches the filter also writes");
159
	check(fake_break_count > 0, "and reports a break");
160
}
161
162
// ---- An inactive watchpoint is inert ---------------------------------------
163
164
static void
165
test_inactive_watch_is_inert(void)
166
{
167
	setup();
168
	debug_wp_add_for(SCRATCH, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
169
	debug_wp_set_active(SCRATCH, DEBUG_BANK_ANY, false);
170
171
	write6502(SCRATCH, 0, 0x66);
172
	check_eq(RAM[SCRATCH], 0x66, "a disabled watchpoint still lets the store through");
173
	check_eq(fake_break_count, 0, "and reports no break");
174
}
175
176
int
177
main(void)
178
{
179
	test_watched_store_still_writes();
180
	test_watched_io_store_still_reaches_device();
181
	test_watch_does_not_alter_the_machine();
182
	test_unrelated_watch_is_inert();
183
	test_watching_costs_no_cycles();
184
	test_value_filter_still_writes();
185
	test_inactive_watch_is_inert();
186
	return x16_test_summary("watchpoint_purity");
187
}