Breakpoints Starting to Work
I’m finally getting breakpoints to start working!
RST 20H (0xe7) is what I use to generate breakpoints. Since the code copies itself to RAM banks and executes from RAM, the breakpoint code can swap in e7 for whatever instruction is at a certain point. This causes the execution to run off to 0x0020.
Once there, it pushes all registers. It also pushes sp (i.e. ld hl, 0; add hl, sp; push hl) and then calls a function that loads hl with a pointer to a function and jumps to it. The default function just returns. But if the pointer is modified, execution can go someplace else.
And that place is bpBreak.
The bpBreak function handles loading a pointer to the registers contents struct over the stack where the the registers are stored. That struct pointer can be used to display and change register contents. It then it replaces all the RST 20H opcodes with the original contents. While doing that, it updates the breakpoint ignore counter, deletes delete-on-break breakpoints, and deletes break-on-hit breakpoints if the pc is where the break occurred. Finally, if any breakpoint had a count of 0, it will start the break prompt, otherwise skip straight to continuing.
The break prompt is where user can interactively set, delete, and change breakpoints, view and change register contents, and view and change memory.
Step is done with temporary breakpoints that delete on break. A breakpoint usually goes at the beginning of the next instruction. The exceptions are ret, jp, and jr instructions since execution never gets beyond. Except, of course, when they are conditional. In that case, the breakpoint must be set at the destination of the jump. There are 6 possibilities of which I deal with 4; call, jp: place breakpoint at (uint16_t)(bpRegs->pc+1) jr: place breakpoint at pc+(uint8_t)(bpRegs->pc+1) ret: place breakpoint at (uint16_t)(bpRegs->sp) jp (hl): place breakpoint at bpRegs->hl rst: this is not done but one could place breakpoints at 00, 08, 10, 18, 20, 28, 30, or 38 halt: this is not done but one could place a breakpoint at 66
Next is like step except if there is a call (i.e. breakpoint uses absolute addressing, may reach next instruction, and is not conditional), then the breakpoint is not placed at the destination. This fails to take into account that calls can be conditional, but the SDCC compiler is not generating conditional calls, at least at the optimization level I’m using.
Continue is a little tricky. It is assumed that the current instruction is a breakpoint. If it isn’t this is overkill, but if it is, we can’t just load the breakpoints and run–we’d break at the same point again. Rather, the breakpoint has be temporarily disabled, a step has to take place, and the breakpoint must be restored once past it. This is done by never loading any breakpoint at the current pc, and having the code that unloads the breakpoint determine if a break is necessary. If a deletion-break breakpoint is hit and has an ignore count greater than 0, it will not generate a break.
Here are two breakpoint files. The first is the basic breakpoint function:
1 // __asm__("rst 20h") == 0xe7
2 #define RST_20H ((uint8_t)(0xe7))
3
4 struct Regs* bpRegs;
5 struct BP bpInfo[BREAKPOINT_COUNT];
6
7
8 // declare fnRST_20H for telling bank 0 how to get to bpBreak
9 extern void(*fnRST_20H)();
10
11 // Initialize breakpoint system
12 void bpInit()
13 {
14 memset(bpInfo, 0, sizeof(bpInfo));
15 fnRST_20H = bpBreak;
16 }
17
18 // crt1 calls this instead of main. This calls main
19 void bpStart(void(*initBP)())
20 {
21 bpSet((uint8_t*)initBP, (1 << BP_FLAG_ENABLED) | (1 << BP_FLAG_DELETE_ON_HIT), 0);
22 bpLoadBreakPoints(NULL);
23 }
24
25 // Sets a breakpoint at location with behavior flags and an ignore count
26 uint8_t bpSet(uint8_t* location, uint8_t flags, uint8_t count)
27 {
28 uint8_t bp;
29
30 // get unusued and look for same
31 for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
32 {
33 struct BP* b = &bpInfo[bp];
34 if ((b->flags & (1 << BP_FLAG_ASSIGNED)) == 0)
35 {
36 b->flags |= (1 << BP_FLAG_ASSIGNED);
37 break;
38 }
39 else if (b->addr == location)
40 {
41 if (((b->flags ^ flags) & ~0x03) == 0)
42 {
43 // such a breakpoint already exists
44 return bp;
45 }
46 }
47 }
48
49 // Set the breakpoint
50 if (bp < BREAKPOINT_COUNT)
51 {
52 struct BP* b = &bpInfo[bp];
53 b->flags = flags | (1 << BP_FLAG_ASSIGNED);
54 b->addr = location;
55 b->count = count;
56 printf("Set BP%x @ %p (%x-%d)\r\n", bp, location, flags, count);
57 }
58 return bp;
59 }
60
61 // Delete a breakpoint
62 void bpUnset(uint8_t bp)
63 {
64 struct BP* b = &bpInfo[bp];
65 b->flags = 0x00;
66 printf("Unset BP%x @ %p (%x-%d)\r\n", bp, b->addr, b->flags, b->count);
67 }
68
69 // Enable or disable a breakpoint
70 void bpEnable(uint8_t bp, bool enable)
71 {
72 if (enable)
73 {
74 bpInfo[bp].flags |= (1 << BP_FLAG_ENABLED);
75 bpInfo[bp].count = 0;
76 }
77 else
78 {
79 bpInfo[bp].flags &= ~(1 << BP_FLAG_ENABLED);
80 }
81 }
82
83 // Find if a breakpoint is enabled
84 bool bpIsEnabled(uint8_t bp)
85 {
86 return (bpInfo[bp].flags & (1 << BP_FLAG_ENABLED)) != 0;
87 }
88
89 // Gets the first breakpoint at addr of 0xff
90 uint8_t bpAt(uint8_t* addr)
91 {
92 uint8_t bp;
93 for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
94 {
95 if (bpInfo[bp].addr == addr)
96 {
97 return bp;
98 }
99 }
100 return 0xff;
101 }
102
103 // Swaps in the RST 20H into the locations with breakpoints
104 // Breakpoints are not swapped at 'pc'
105 void bpLoadBreakPoints(uint8_t* pc)
106 {
107 uint8_t bp = 0;
108 for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
109 {
110 struct BP* b = &bpInfo[bp];
111 if ((b->flags & 0x03) == 0x03 && b->addr != pc) // assigned and enabled and not at PC
112 {
113 uint8_t swap = *(b->addr);
114 if (swap == RST_20H)
115 {
116 // already a breakpoint there
117 }
118 else
119 {
120 b->swap = swap;
121 *(b->addr) = RST_20H;
122 printf("RST 20 to %02x @ %p\r\n", b->swap, b->addr);
123 }
124 }
125 }
126 }
127
128 // Restores RST 20H with the original opcodes
129 // decrements ignore count for breakpoints who addr is 'pc'
130 // Returns whether any breakpoints at 'pc' were hit and had an ignore count of 0
131 bool bpUnloadBreakPoints(uint8_t* pc)
132 {
133 uint8_t bp = 0;
134 bool doBreak = false;
135 for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
136 {
137 struct BP* b = &bpInfo[bp];
138 if ((b->flags & (1 << BP_FLAG_ASSIGNED)) != 0)
139 {
140 if ((b->flags & (1 << BP_FLAG_ENABLED)) != 0)
141 {
142 uint8_t swap = *(b->addr);
143 if (swap == RST_20H)
144 {
145 printf("RST 20 from %02x @ %p\r\n", b->swap, b->addr);
146 *(b->addr) = b->swap;
147 }
148 if (b->addr == pc)
149 {
150 if ((b->count > 0))
151 {
152 b->count--;
153 }
154 else
155 {
156 doBreak = true;
157 }
158
159 // delete on hit
160 if ((b->flags & (1 << BP_FLAG_DELETE_ON_HIT)) != 0)
161 {
162 b->flags = 0;
163 }
164 }
165 }
166 // delete on break BP
167 if ((b->flags & (1 << BP_FLAG_DELETE_ON_BREAK)) != 0)
168 {
169 b->flags = 0;
170 }
171 }
172 }
173 return doBreak;
174 }
175
176 // This set breakpoints for certain types: 0-step 1-next 2-continue
177 // Step/Next: if !BRANCH_NO_STEP set bp @ pc+il
178 // Step: if BRANCH_ABS set @ (pc+1)
179 // Step: if BRANCH_REL set @ pc + (pc+1)
180 // Step: if BRANCH_RET set @ (sp-2)
181 // Step: if BRANCH_HL set @ hl
182 void bpStep(uint8_t type)
183 {
184 uint8_t* pc;
185 uint8_t branchInfo;
186 uint8_t instrLen;
187 bool breakOnNextInstr;
188 bool breakOnDest;
189 uint8_t count;
190 uint8_t nextFlags;
191
192 // get PC
193 pc = (uint8_t*)bpRegs->pc;
194
195 // get next instruction address
196 instrLen = z80InstructionLength(pc);
197 branchInfo = instrLen & 0xf8;
198 instrLen = instrLen & 0x07;
199
200 printf("branch %02x\r\n", branchInfo);
201
202 // figure out what to do
203 // if not branching away permanently, set a breakpoint there
204 breakOnNextInstr = (branchInfo & BRANCH_ALWAYS) == 0;
205
206 // count is 0 unless type is continue
207 // If count is 1, will not return to break point prompt--will just continue
208 count = (type == 2) ? 1 : 0;
209
210 // How flags work
211 // step: delete on hit for next and delete on break dest
212 // next: delete on hit for next
213 // continue: always delete on any break--restart immediately
214 nextFlags = (type == 2) ? (1 << BP_FLAG_DELETE_ON_BREAK) : (1 << BP_FLAG_DELETE_ON_HIT);
215 nextFlags |= (1 << BP_FLAG_ENABLED);
216 //nextFlags = (1 << BP_FLAG_DELETE_ON_BREAK) | (1 << BP_FLAG_ENABLED);
217
218 // place breakpoint if not NO_STEP branch (jp **, jp (hl), ret/i/n, jr)
219 if (breakOnNextInstr)
220 {
221 bpSet(pc + instrLen, nextFlags, count);
222 }
223
224 // BP on branch destination
225 breakOnDest = ((branchInfo & 0xf8) != 0);
226 if ((type == 1) && ((branchInfo & BRANCH_CALL) != 0))
227 {
228 breakOnDest = false; // if user is using 'next' on a call
229 }
230
231 if (breakOnDest)
232 {
233 uint8_t branchType = branchInfo & 0x30;
234 uint16_t target = 0;
235 // get branch addr (rel, abs, (hl), (sp-2)
236 if (branchType == BRANCH_ABS)
237 {
238 target = *(uint16_t*)(pc + 1);
239 }
240 else if (branchType == BRANCH_REL)
241 {
242 target = (uint16_t)pc + (int8_t)(pc[1]);
243 }
244 else if (branchType == BRANCH_RET)
245 {
246 target = *(uint16_t*)(bpRegs->sp);
247 }
248 else// if (branchType == BRANCH_HL)
249 {
250 target = bpRegs->hl;
251 }
252
253 // place breakpoint
254 bpSet((uint8_t*)target, (1 << BP_FLAG_DELETE_ON_BREAK) | (1 << BP_FLAG_ENABLED), count);
255 }
256 }
257
258 // RST 20H calls this after
259 void bpBreak() __naked
260 {
261 //RST_20H all registers pushed
262 __asm__("di");
263 __asm__("ld hl, #2");
264 __asm__("add hl, sp");
265 __asm__("push hl");
266 __asm__("ld (_bpRegs), hl");
267 __asm__("call _bpPrompt");
268 __asm__("pop hl ; toss");
269 __asm__("ret");
270 }
This file snippet is for breakpoint prompt features:
1 struct BpCmd {
2 const char* cmd;
3 const char* desc;
4 uint8_t(*fn)(const char* line);
5 };
6
7 void showMem(uint16_t address, uint16_t length)
8 {
9 uint8_t i;
10 char* ph;
11 char* pa;
12 char line[75];
13 w_bb shift;
14 address = address & 0xfff0;
15 length = (length + 15) & 0xfff0;
16 memset(line, ' ', sizeof(line) - 1);
17 line[sizeof(line) - 1] = 0;
18 line[4] = ':';
19 while (length)
20 {
21 shift.uw = address;
22 ph = &line[0];
23 pa = &line[4 + 1 + 1 + 1 + 16 * 3 + 1];
24 *ph++ = __ascii[shift.ubb.h >> 4];
25 *ph++ = __ascii[shift.ubb.h & 0x0f];
26 *ph++ = __ascii[shift.ubb.l >> 4];
27 *ph++ = __ascii[shift.ubb.l & 0x0f];
28 ph++;
29 ph++;
30 // 16 bytes
31 for (i = 0; i < 16; i++)
32 {
33 uint8_t b;
34 b = *(char*)(address + i);
35 *ph++ = __ascii[b >> 4];
36 *ph++ = __ascii[b & 0x0f];
37 ph++;
38
39 // unprintables to .
40 if (b < ' ' || b > 0x7e)
41 {
42 b = '.';
43 }
44 *pa++ = b;
45
46 // gap
47 if (i == 7)
48 {
49 ph++;
50 pa++;
51 }
52
53 }
54 puts(line);
55 address += 16;
56 length -= 16;
57 }
58 }
59
60 static void showRegs()
61 {
62 printf("af: %04x bc: %04x de: %04x hl=%04x\r\n", bpRegs->af, bpRegs->bc, bpRegs->de, bpRegs->hl);
63 printf("pc: %04x sp: %04x ix: %04x iy=%04x\r\n", bpRegs->pc, bpRegs->sp, bpRegs->ix, bpRegs->iy);
64 }
65
66 static void showBreakpoint(uint8_t bp)
67 {
68 struct BP* b = &bpInfo[bp];
69 printf("%x: addr=%04x en=%x count=%02x\r\n", bp, b->addr, (b->flags & (1 << BP_FLAG_ENABLED)) != 0, b->count);
70 }
71 static void showBreakpoints()
72 {
73 uint8_t i;
74 for (i = 0; i < BREAKPOINT_COUNT; i++)
75 {
76 struct BP* b = &bpInfo[i];
77 if ((b->flags & (1 << BP_FLAG_ASSIGNED)) != 0)
78 {
79 showBreakpoint(i);
80 }
81 }
82 }
83
84 static const struct BpCmd commands[] =
85 {
86 {
87 "?",
88 "list",
89 bpIntHelp
90 },
91 {
92 "l",
93 "list bp's",
94 bpIntList
95 },
96 {
97 "s",
98 "step",
99 bpIntStep
100 },
101 {
102 "n",
103 "next",
104 bpIntNext
105 },
106 {
107 "c",
108 "continue",
109 bpIntContinue
110 },
111 {
112 "b",
113 "set bp @ addr",
114 bpIntCreate
115 },
116 {
117 "d",
118 "delete bp # or all",
119 bpIntDelete
120 },
121 {
122 "t",
123 "toggle bp",
124 bpIntToggle
125 },
126 {
127 "r",
128 "see regs or set rr v",
129 bpIntRegs
130 },
131 {
132 "m",
133 "see RAM",
134 bpIntMem
135 },
136 };
137
138 const static uint8_t commandLength = sizeof(commands) / sizeof(commands[0]);
139
140
141 static uint8_t bpIntList(const char* line)
142 {
143 line; // suppress warning
144 showBreakpoints();
145 return 0;
146 }
147
148 static uint8_t bpIntHelp(const char* line)
149 {
150 uint8_t i;
151 line; // suppress warning
152 for (i = 0; i < commandLength; i++)
153 {
154 printf("%s - %s\r\n", commands[i].cmd, commands[i].desc);
155 }
156 return 0;
157 }
158
159 static uint8_t bpIntContinue(const char* line)
160 {
161 line; // suppress warning
162 bpStep(2);
163 return 0xff; // continue
164 }
165
166 static uint8_t bpIntStep(const char* line)
167 {
168 line; // suppress warning
169 bpStep(0);
170 return 0xff; // continue
171 }
172
173 static uint8_t bpIntNext(const char* line)
174 {
175 line; // suppress warning
176 bpStep(1);
177 return 0xff; // continue
178 }
179
180 static uint8_t bpIntCreate(const char* line)
181 {
182 if (*line++ == ' ')
183 {
184 uint16_t addr = extractHex(&line);
185 uint8_t count = 0;
186 if (*line == ' ')
187 {
188 line++;
189 count = extractHex(&line);
190 }
191 if (*line == 0)
192 {
193 uint8_t i = bpSet((uint8_t*)addr, (1<<BP_FLAG_ENABLED), count);
194 if (i == 0xff)
195 {
196 puts("BP full");
197 return 1;
198 }
199 else
200 {
201 showBreakpoint(i);
202 }
203 }
204 else
205 {
206 return 2;
207 }
208 }
209 else
210 {
211 return 3;
212 }
213 return 0;
214 }
215
216 static uint8_t bpIntDelete(const char* line)
217 {
218 uint8_t status = 0;
219 if (*line == ' ')
220 {
221 uint8_t i;
222 line++;
223 i = extractHex(&line);
224 if (*line == 0 && i < BREAKPOINT_COUNT)
225 {
226 bpUnset(i);
227 }
228 }
229 return 0;
230 }
231
232 static uint8_t bpIntToggle(const char* line)
233 {
234 //uint8_t status = 0;
235 uint8_t bp;
236 if (*line == ' ')
237 {
238 bp = extractHex(&line);
239 }
240 else if (*line == 0)
241 {
242 bp = bpAt((uint8_t*)bpRegs->pc);
243 }
244 else
245 {
246 return 1;
247 }
248 if (bp >= BREAKPOINT_COUNT)
249 {
250 return 2;
251 }
252 bpEnable(bp, !bpIsEnabled(bp));
253 showBreakpoint(bp);
254 return 0;
255 }
256
257 static const char* const regs = "SPiyixhldebcafpc";
258
259 static uint8_t bpIntRegs(const char* line)
260 {
261 //uint8_t status = 0;
262 if (*line == 0)
263 {
264 showRegs();
265 return 0;
266 }
267 else if (*line == ' ')
268 {
269 uint8_t i;
270 uint16_t search;
271 const uint16_t* reg = (const uint16_t*)regs;
272
273 line++;
274 search = *(uint16_t*)line;
275
276 for (i = 0; i < 7; i++)
277 {
278 if (search == *reg)
279 {
280 uint16_t val;
281 line += 3;
282 val = extractHex(&line);
283 if (*line == 0)
284 {
285 ((uint16_t*)bpRegs)[i] = val;
286 showRegs();
287 }
288 else
289 {
290 puts("?");
291 }
292 }
293 reg++;
294 }
295 }
296 return 0;
297 }
298
299 static uint8_t bpIntMem(const char* line)
300 {
301 uint16_t addr = 0;
302 uint16_t len = 16;
303 if (*line++ == ' ')
304 {
305 addr = extractHex(&line);
306 if (*line++ == ' ')
307 {
308 len = extractHex(&line);
309 }
310 if (*line == 0)
311 {
312 showMem(addr, len);
313 return 0;
314 }
315 }
316 return 1;
317 }
318
319 // do prompt for breaks
320 // ? list commands
321 // l list breakpoints
322 // c continue
323 // s step
324 // n next
325 // b xxxx (cc) break @ xxxx (optional countdown)
326 // d ## delete ##
327 // t ## toggle ## enable
328 // r show regs
329 // r rr xxxx set reg (except sp)
330 // m xxxx xxxx show mem
331 void bpPrompt()
332 {
333 char buffer[32];
334 char* p;
335 uint8_t i;
336 uint16_t pc = bpRegs->pc - 1; // source of RST_20
337 uint8_t interruptSave;
338 bool doBreak;
339
340 bpRegs->pc = pc; // restore to the instruction that was supposed to be there
341 doBreak = bpUnloadBreakPoints((uint8_t*)pc);
342 interruptSave = ei_save();
343
344 while (doBreak)
345 {
346 uint8_t result = 0;
347 char c;
348 printf("(brk @ %p)> ", pc);
349 gets(buffer);
350 p = &buffer[0];
351 c = *p;
352 for (i = 0; i < commandLength; i++)
353 {
354 const char* cmd = commands[i].cmd;
355 uint8_t len = strlen(cmd);
356 if (strncmp(commands[i].cmd, p, len) == 0)
357 {
358 result = commands[i].fn(&p[len]);
359 }
360 }
361
362 if (result == 0xff)
363 {
364 break;
365 }
366 else if (result != 0)
367 {
368 printf("error %d\r\n", result);
369 }
370 }
371 bpLoadBreakPoints((uint8_t*)pc);
372 di_restore(interruptSave);
373 }
This is the part of interrupt.c that is relevant:
1 static void ret() __naked
2 {
3 __asm__("ret");
4 }
5 // This is preset to 'ret' If the code runs frojmj flash, this is necessary.
6 // If the code runs from RAM, this can be overridden.
7 void(*fnRST_20H)() = ret;
8 static void call_hl() __naked
9 {
10 __asm__("jp (hl)");
11 }
12 void do_RST_20H() __naked
13 {
14 __asm__("push af");
15 __asm__("push bc");
16 __asm__("push de");
17 __asm__("push hl");
18 __asm__("push ix");
19 __asm__("push iy");
20 // push sp
21 __asm__("ld hl, #14");
22 __asm__("add hl, sp");
23 __asm__("push hl");
24
25 __asm__("ld hl, (_fnRST_20H)");
26 __asm__("call _call_hl");
27
28 __asm__("pop iy"); // toss SP
29 __asm__("pop iy");
30 __asm__("pop ix");
31 __asm__("pop hl");
32 __asm__("pop de");
33 __asm__("pop bc");
34 __asm__("pop af");
35 __asm__("ret");
36 }