main
gdb_remote.c
1/*
2 Copyright 2013 Michael Pavone
3 This file is part of BlastEm.
4 BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
5*/
6#define GDB_IN_FD STDIN_FILENO
7#define GDB_OUT_FD STDOUT_FILENO
8#define GDB_READ read
9#define GDB_WRITE write
10
11#include "gdb_remote.h"
12#include "68kinst.h"
13#include "debug.h"
14#include "util.h"
15#include <unistd.h>
16#include <fcntl.h>
17#include <stddef.h>
18#include <stdlib.h>
19#include <stdio.h>
20#include <string.h>
21
22
23
24#define INITIAL_BUFFER_SIZE (16*1024)
25
26#ifdef DO_DEBUG_PRINT
27#define dfprintf fprintf
28#else
29#define dfprintf
30#endif
31
32char * buf = NULL;
33char * curbuf = NULL;
34char * end = NULL;
35size_t bufsize;
36int cont = 0;
37int expect_break_response=0;
38uint32_t resume_pc;
39
40
41static uint16_t branch_t;
42static uint16_t branch_f;
43
44static bp_def * breakpoints = NULL;
45static uint32_t bp_index = 0;
46
47
48void hex_32(uint32_t num, char * out)
49{
50 for (int32_t shift = 28; shift >= 0; shift -= 4)
51 {
52 uint8_t nibble = num >> shift & 0xF;
53 *(out++) = nibble > 9 ? nibble - 0xA + 'A' : nibble + '0';
54 }
55}
56
57void hex_16(uint16_t num, char * out)
58{
59 for (int16_t shift = 14; shift >= 0; shift -= 4)
60 {
61 uint8_t nibble = num >> shift & 0xF;
62 *(out++) = nibble > 9 ? nibble - 0xA + 'A' : nibble + '0';
63 }
64}
65
66void hex_8(uint8_t num, char * out)
67{
68 uint8_t nibble = num >> 4;
69 *(out++) = nibble > 9 ? nibble - 0xA + 'A' : nibble + '0';
70 nibble = num & 0xF;
71 *out = nibble > 9 ? nibble - 0xA + 'A' : nibble + '0';
72}
73
74void gdb_calc_checksum(char * command, char *out)
75{
76 uint8_t checksum = 0;
77 while (*command)
78 {
79 checksum += *(command++);
80 }
81 hex_8(checksum, out);
82}
83
84void write_or_die(int fd, const void *buf, size_t count)
85{
86 if (GDB_WRITE(fd, buf, count) < count) {
87 fatal_error("Error writing to stdout\n");
88 }
89}
90
91void gdb_send_command(char * command)
92{
93 char end[3];
94 write_or_die(GDB_OUT_FD, "$", 1);
95 write_or_die(GDB_OUT_FD, command, strlen(command));
96 end[0] = '#';
97 gdb_calc_checksum(command, end+1);
98 write_or_die(GDB_OUT_FD, end, 3);
99 dfprintf(stderr, "Sent $%s#%c%c\n", command, end[1], end[2]);
100}
101
102uint32_t calc_status(m68k_context * context)
103{
104 uint32_t status = context->status << 3;
105 for (int i = 0; i < 5; i++)
106 {
107 status <<= 1;
108 status |= context->flags[i];
109 }
110 return status;
111}
112
113void update_status(m68k_context * context, uint16_t value)
114{
115 context->status = value >> 8;
116 for (int i = 4; i >= 0; i--)
117 {
118 context->flags[i] = value & 1;
119 value >>= 1;
120 }
121}
122
123uint8_t m68k_read_byte(m68k_context * context, uint32_t address)
124{
125
126 genesis_context *gen = context->system;
127 //TODO: Use generated read/write functions to support access to hardware that is not ROM or RAM
128 uint16_t * word = get_native_pointer(address & 0xFFFFFFFE, (void **)context->mem_pointers, &context->options->gen);
129 if (word) {
130 if (address & 1) {
131 return *word;
132 }
133 return *word >> 8;
134}
135 if (address >= 0xA00000 && address < 0xA04000) {
136 return gen->zram[address & 0x1FFF];
137 }
138 return 0;
139}
140
141void m68k_write_byte(m68k_context * context, uint32_t address, uint8_t value)
142{
143 genesis_context *gen = context->system;
144 //TODO: Use generated read/write functions so that memory map is properly respected
145 uint16_t * word = get_native_pointer(address & 0xFFFFFFFE, (void **)context->mem_pointers, &context->options->gen);
146 if (word) {
147 if (address & 1) {
148 *word = (*word & 0xFF00) | value;
149 } else {
150 *word = (*word & 0xFF) | value << 8;
151 }
152 //TODO: Deal with this more generally once m68k_handle_code_write can handle it
153 if (address >= 0xE00000) {
154 m68k_handle_code_write(address, context);
155 }
156 return;
157 }
158 if (address >= 0xA00000 && address < 0xA04000) {
159 gen->zram[address & 0x1FFF] = value;
160 genesis_context * gen = context->system;
161 z80_handle_code_write(address & 0x1FFF, gen->z80);
162 return;
163 } else {
164 return;
165 }
166}
167
168void gdb_run_command(m68k_context * context, uint32_t pc, char * command)
169{
170 char send_buf[512];
171 dfprintf(stderr, "Received command %s\n", command);
172 switch(*command)
173 {
174
175 case 'c':
176 if (*(command+1) != 0) {
177 //TODO: implement resuming at an arbitrary address
178 goto not_impl;
179 }
180 cont = 1;
181 expect_break_response = 1;
182 break;
183 case 's': {
184 if (*(command+1) != 0) {
185 //TODO: implement resuming at an arbitrary address
186 goto not_impl;
187 }
188 m68kinst inst;
189 genesis_context *gen = context->system;
190 uint16_t * pc_ptr = get_native_pointer(pc, (void **)context->mem_pointers, &context->options->gen);
191 if (!pc_ptr) {
192 fatal_error("Entered gdb remote debugger stub at address %X\n", pc);
193 }
194 uint16_t * after_pc = m68k_decode(pc_ptr, &inst, pc & 0xFFFFFF);
195 uint32_t after = pc + (after_pc-pc_ptr)*2;
196
197 if (inst.op == M68K_RTS) {
198 after = (read_dma_value(context->aregs[7]/2) << 16) | read_dma_value(context->aregs[7]/2 + 1);
199 } else if (inst.op == M68K_RTE || inst.op == M68K_RTR) {
200 after = (read_dma_value((context->aregs[7]+2)/2) << 16) | read_dma_value((context->aregs[7]+2)/2 + 1);
201 } else if(m68k_is_branch(&inst)) {
202 if (inst.op == M68K_BCC && inst.extra.cond != COND_TRUE) {
203 branch_f = after;
204 branch_t = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
205 insert_breakpoint(context, branch_t, gdb_debug_enter);
206 } else if(inst.op == M68K_DBCC && inst.extra.cond != COND_FALSE) {
207 branch_t = after;
208 branch_f = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
209 insert_breakpoint(context, branch_f, gdb_debug_enter);
210 } else {
211 after = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
212 }
213 }
214 insert_breakpoint(context, after, gdb_debug_enter);
215
216 cont = 1;
217 expect_break_response = 1;
218 break;
219 }
220 case 'H':
221 if (command[1] == 'g' || command[1] == 'c') {;
222 //no thread suport, just acknowledge
223 gdb_send_command("OK");
224 } else {
225 goto not_impl;
226 }
227 break;
228 case 'Z': {
229 uint8_t type = command[1];
230 if (type < '2') {
231 uint32_t address = strtoul(command+3, NULL, 16);
232 insert_breakpoint(context, address, gdb_debug_enter);
233 bp_def *new_bp = malloc(sizeof(bp_def));
234 new_bp->next = breakpoints;
235 new_bp->address = address;
236 new_bp->index = bp_index++;
237 breakpoints = new_bp;
238 gdb_send_command("OK");
239 } else {
240 //watchpoints are not currently supported
241 gdb_send_command("");
242 }
243 break;
244 }
245 case 'z': {
246 uint8_t type = command[1];
247 if (type < '2') {
248 uint32_t address = strtoul(command+3, NULL, 16);
249 remove_breakpoint(context, address);
250 bp_def **found = find_breakpoint(&breakpoints, address);
251 if (*found)
252 {
253 bp_def * to_remove = *found;
254 *found = to_remove->next;
255 free(to_remove);
256 }
257 gdb_send_command("OK");
258 } else {
259 //watchpoints are not currently supported
260 gdb_send_command("");
261 }
262 break;
263 }
264 case 'g': {
265 char * cur = send_buf;
266 for (int i = 0; i < 8; i++)
267 {
268 hex_32(context->dregs[i], cur);
269 cur += 8;
270 }
271 for (int i = 0; i < 8; i++)
272 {
273 hex_32(context->aregs[i], cur);
274 cur += 8;
275 }
276 hex_32(calc_status(context), cur);
277 cur += 8;
278 hex_32(pc, cur);
279 cur += 8;
280 *cur = 0;
281 gdb_send_command(send_buf);
282 break;
283 }
284 case 'm': {
285 char * rest;
286 uint32_t address = strtoul(command+1, &rest, 16);
287 uint32_t size = strtoul(rest+1, NULL, 16);
288 if (size > (sizeof(send_buf)-1)/2) {
289 size = (sizeof(send_buf)-1)/2;
290 }
291 char *cur = send_buf;
292 while (size)
293 {
294 hex_8(m68k_read_byte(context, address), cur);
295 cur += 2;
296 address++;
297 size--;
298 }
299 *cur = 0;
300 gdb_send_command(send_buf);
301 break;
302 }
303 case 'M': {
304 char * rest;
305 uint32_t address = strtoul(command+1, &rest, 16);
306 uint32_t size = strtoul(rest+1, &rest, 16);
307
308 char *cur = rest+1;
309 while (size)
310 {
311 char tmp[3];
312 tmp[0] = *(cur++);
313 tmp[1] = *(cur++);
314 tmp[2] = 0;
315 m68k_write_byte(context, address, strtoul(tmp, NULL, 16));
316 address++;
317 size--;
318 }
319 gdb_send_command("OK");
320 break;
321 }
322 case 'X':
323 //binary transfers aren't supported currently as I don't feel like dealing with the escaping
324 gdb_send_command("");
325 break;
326 case 'p': {
327 unsigned long reg = strtoul(command+1, NULL, 16);
328
329 if (reg < 8) {
330 hex_32(context->dregs[reg], send_buf);
331 } else if (reg < 16) {
332 hex_32(context->aregs[reg-8], send_buf);
333 } else if (reg == 16) {
334 hex_32(calc_status(context), send_buf);
335 } else if (reg == 17) {
336 hex_32(pc, send_buf);
337 } else {
338 send_buf[0] = 0;
339 }
340 send_buf[8] = 0;
341 gdb_send_command(send_buf);
342 break;
343 }
344 case 'P': {
345 char *after = NULL;
346 unsigned long reg = strtoul(command+1, &after, 16);
347 uint32_t value = strtoul(after+1, NULL, 16);
348
349 if (reg < 8) {
350 context->dregs[reg] = value;
351 } else if (reg < 16) {
352 context->aregs[reg-8] = value;
353 } else if (reg == 16) {
354 update_status(context, value);
355 } else {
356 //supporting updates to PC is going to be a pain
357 gdb_send_command("E01");
358 break;
359 }
360 gdb_send_command("OK");
361 break;
362 }
363 case 'q':
364 if (!memcmp("Supported", command+1, strlen("Supported"))) {
365 sprintf(send_buf, "PacketSize=%X", (int)bufsize);
366 gdb_send_command(send_buf);
367 } else if (!memcmp("Attached", command+1, strlen("Attached"))) {
368 //not really meaningful for us, but saying we spawned a new process
369 //is probably closest to the truth
370 gdb_send_command("0");
371 } else if (!memcmp("Offsets", command+1, strlen("Offsets"))) {
372 //no relocations, so offsets are all 0
373 gdb_send_command("Text=0;Data=0;Bss=0");
374 } else if (!memcmp("Symbol", command+1, strlen("Symbol"))) {
375 gdb_send_command("");
376 } else if (!memcmp("TStatus", command+1, strlen("TStatus"))) {
377 //TODO: actual tracepoint support
378 gdb_send_command("T0;tnotrun:0");
379 } else if (!memcmp("TfV", command+1, strlen("TfV")) || !memcmp("TfP", command+1, strlen("TfP"))) {
380 //TODO: actual tracepoint support
381 gdb_send_command("");
382 } else if (command[1] == 'C') {
383 //we only support a single thread currently, so send 1
384 gdb_send_command("QC1");
385 } else if (!strcmp("fThreadInfo", command + 1)) {
386 //we only support a single thread currently, so send 1
387 gdb_send_command("m1");
388 } else if (!strcmp("sThreadInfo", command + 1)) {
389 gdb_send_command("l");
390 } else {
391 goto not_impl;
392 }
393 break;
394 case 'v':
395 if (!memcmp("Cont?", command+1, strlen("Cont?"))) {
396 gdb_send_command("vCont;c;C;s;S");
397 } else if (!strcmp("MustReplyEmpty", command + 1)) {
398 gdb_send_command("");
399 } else if (!memcmp("Cont;", command+1, strlen("Cont;"))) {
400 switch (*(command + 1 + strlen("Cont;")))
401 {
402 case 'c':
403 case 'C':
404 //might be interesting to have continue with signal fire a
405 //trap exception or something, but for no we'll treat it as
406 //a normal continue
407 cont = 1;
408 expect_break_response = 1;
409 break;
410 case 's':
411 case 'S': {
412 m68kinst inst;
413 genesis_context *gen = context->system;
414 uint16_t * pc_ptr = get_native_pointer(pc, (void **)context->mem_pointers, &context->options->gen);
415 if (!pc_ptr) {
416 fatal_error("Entered gdb remote debugger stub at address %X\n", pc);
417 }
418 uint16_t * after_pc = m68k_decode(pc_ptr, &inst, pc & 0xFFFFFF);
419 uint32_t after = pc + (after_pc-pc_ptr)*2;
420
421 if (inst.op == M68K_RTS) {
422 after = (read_dma_value(context->aregs[7]/2) << 16) | read_dma_value(context->aregs[7]/2 + 1);
423 } else if (inst.op == M68K_RTE || inst.op == M68K_RTR) {
424 after = (read_dma_value((context->aregs[7]+2)/2) << 16) | read_dma_value((context->aregs[7]+2)/2 + 1);
425 } else if(m68k_is_branch(&inst)) {
426 if (inst.op == M68K_BCC && inst.extra.cond != COND_TRUE) {
427 branch_f = after;
428 branch_t = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
429 insert_breakpoint(context, branch_t, gdb_debug_enter);
430 } else if(inst.op == M68K_DBCC && inst.extra.cond != COND_FALSE) {
431 branch_t = after;
432 branch_f = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
433 insert_breakpoint(context, branch_f, gdb_debug_enter);
434 } else {
435 after = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
436 }
437 }
438 insert_breakpoint(context, after, gdb_debug_enter);
439
440 cont = 1;
441 expect_break_response = 1;
442 break;
443 }
444 default:
445 goto not_impl;
446 }
447 } else {
448 goto not_impl;
449 }
450 break;
451 case '?':
452 gdb_send_command("S05");
453 break;
454 default:
455 goto not_impl;
456
457 }
458 return;
459not_impl:
460 fatal_error("Command %s is not implemented, exiting...\n", command);
461}
462
463void gdb_debug_enter(m68k_context * context, uint32_t pc)
464{
465 dfprintf(stderr, "Entered debugger at address %X\n", pc);
466 if (expect_break_response) {
467 gdb_send_command("S05");
468 expect_break_response = 0;
469 }
470 if ((pc & 0xFFFFFF) == branch_t) {
471 bp_def ** f_bp = find_breakpoint(&breakpoints, branch_f);
472 if (!*f_bp) {
473 remove_breakpoint(context, branch_f);
474 }
475 branch_t = branch_f = 0;
476 } else if((pc & 0xFFFFFF) == branch_f) {
477 bp_def ** t_bp = find_breakpoint(&breakpoints, branch_t);
478 if (!*t_bp) {
479 remove_breakpoint(context, branch_t);
480 }
481 branch_t = branch_f = 0;
482 }
483 //Check if this is a user set breakpoint, or just a temporary one
484 bp_def ** this_bp = find_breakpoint(&breakpoints, pc & 0xFFFFFF);
485 if (!*this_bp) {
486 remove_breakpoint(context, pc & 0xFFFFFF);
487 }
488 resume_pc = pc;
489 cont = 0;
490 uint8_t partial = 0;
491 while(!cont)
492 {
493 if (!curbuf) {
494 int numread = GDB_READ(GDB_IN_FD, buf, bufsize);
495 if (numread < 0) {
496 fatal_error("Failed to read on GDB input file descriptor\n");
497 }
498 dfprintf(stderr, "read %d bytes\n", numread);
499 curbuf = buf;
500 end = buf + numread;
501 } else if (partial) {
502 if (curbuf != buf) {
503 memmove(curbuf, buf, end-curbuf);
504 end -= curbuf - buf;
505 }
506 int numread = GDB_READ(GDB_IN_FD, end, bufsize - (end-buf));
507 end += numread;
508 curbuf = buf;
509 }
510 for (; curbuf < end; curbuf++)
511 {
512 if (*curbuf == '$')
513 {
514 curbuf++;
515 char * start = curbuf;
516 while (curbuf < end && *curbuf != '#') {
517 curbuf++;
518 }
519 if (*curbuf == '#') {
520 //check to make sure we've received the checksum bytes
521 if (end-curbuf >= 2) {
522 //TODO: verify checksum
523 //Null terminate payload
524 *curbuf = 0;
525 //send acknowledgement
526 if (GDB_WRITE(GDB_OUT_FD, "+", 1) < 1) {
527 fatal_error("Error writing to stdout\n");
528 }
529 gdb_run_command(context, pc, start);
530 curbuf += 2;
531 }
532 } else {
533 curbuf--;
534 partial = 1;
535 break;
536 }
537 } else {
538 dfprintf(stderr, "Ignoring character %c\n", *curbuf);
539 }
540 }
541 if (curbuf == end) {
542 curbuf = NULL;
543 }
544 }
545}
546
547void gdb_remote_init(void)
548{
549 buf = malloc(INITIAL_BUFFER_SIZE);
550 curbuf = NULL;
551 bufsize = INITIAL_BUFFER_SIZE;
552 disable_stdout_messages();
553}