Mercurial > repos > blastem
comparison gdb_remote.c @ 515:1495179d6737
Initial GDB remote debugging support. Lacks some features, but breakpoints and basic inspection of registers and memory work.
author | Mike Pavone <pavone@retrodev.com> |
---|---|
date | Sat, 08 Feb 2014 23:37:09 -0800 |
parents | b7b7a1cab44a |
children | 62860044337d |
comparison
equal
deleted
inserted
replaced
514:f66c78cbdcaa | 515:1495179d6737 |
---|---|
1 /* | 1 /* |
2 Copyright 2013 Michael Pavone | 2 Copyright 2013 Michael Pavone |
3 This file is part of BlastEm. | 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. | 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 */ | 5 */ |
6 #include "blastem.h" | 6 #include "gdb_remote.h" |
7 #include <unistd.h> | 7 #include <unistd.h> |
8 #include <fcntl.h> | 8 #include <fcntl.h> |
9 #include <stddef.h> | 9 #include <stddef.h> |
10 #include <stdlib.h> | 10 #include <stdlib.h> |
11 #include <stdio.h> | 11 #include <stdio.h> |
12 | 12 #include <string.h> |
13 #define INITIAL_BUFFER_SIZE 4096 | 13 |
14 #define INITIAL_BUFFER_SIZE (16*1024) | |
14 | 15 |
15 char * buf = NULL; | 16 char * buf = NULL; |
16 char * curbuf = NULL; | 17 char * curbuf = NULL; |
17 char * end = NULL; | 18 char * end = NULL; |
18 size_t bufsize; | 19 size_t bufsize; |
19 int cont = 0; | 20 int cont = 0; |
20 int expect_break_response=0; | 21 int expect_break_response=0; |
21 uint32_t resume_pc; | 22 uint32_t resume_pc; |
22 | 23 |
23 void gdb_debug_enter(genesis_context * gen, uint32_t pc) | 24 |
24 { | 25 void hex_32(uint32_t num, char * out) |
25 fcntl(STDIN_FILENO, FD_SETFL, 0); | 26 { |
27 for (int32_t shift = 28; shift >= 0; shift -= 4) | |
28 { | |
29 uint8_t nibble = num >> shift & 0xF; | |
30 *(out++) = nibble > 9 ? nibble - 0xA + 'A' : nibble + '0'; | |
31 } | |
32 } | |
33 | |
34 void hex_16(uint16_t num, char * out) | |
35 { | |
36 for (int16_t shift = 14; shift >= 0; shift -= 4) | |
37 { | |
38 uint8_t nibble = num >> shift & 0xF; | |
39 *(out++) = nibble > 9 ? nibble - 0xA + 'A' : nibble + '0'; | |
40 } | |
41 } | |
42 | |
43 void hex_8(uint8_t num, char * out) | |
44 { | |
45 uint8_t nibble = num >> 4; | |
46 *(out++) = nibble > 9 ? nibble - 0xA + 'A' : nibble + '0'; | |
47 nibble = num & 0xF; | |
48 *out = nibble > 9 ? nibble - 0xA + 'A' : nibble + '0'; | |
49 } | |
50 | |
51 void gdb_calc_checksum(char * command, char *out) | |
52 { | |
53 uint8_t checksum = 0; | |
54 while (*command) | |
55 { | |
56 checksum += *(command++); | |
57 } | |
58 hex_8(checksum, out); | |
59 } | |
60 | |
61 void write_or_die(int fd, const void *buf, size_t count) | |
62 { | |
63 if (write(fd, buf, count) < count) { | |
64 fputs("Error writing to stdout\n", stderr); | |
65 exit(1); | |
66 } | |
67 } | |
68 | |
69 void gdb_send_command(char * command) | |
70 { | |
71 char end[3]; | |
72 write_or_die(STDOUT_FILENO, "$", 1); | |
73 write_or_die(STDOUT_FILENO, command, strlen(command)); | |
74 end[0] = '#'; | |
75 gdb_calc_checksum(command, end+1); | |
76 write_or_die(STDOUT_FILENO, end, 3); | |
77 fprintf(stderr, "Sent $%s#%c%c\n", command, end[1], end[2]); | |
78 } | |
79 | |
80 uint32_t calc_status(m68k_context * context) | |
81 { | |
82 uint32_t status = context->status << 3; | |
83 for (int i = 0; i < 5; i++) | |
84 { | |
85 status <<= 1; | |
86 status |= context->flags[i]; | |
87 } | |
88 return status; | |
89 } | |
90 | |
91 uint8_t read_byte(m68k_context * context, uint32_t address) | |
92 { | |
93 uint16_t * word; | |
94 //TODO: Use generated read/write functions so that memory map is properly respected | |
95 if (address < 0x400000) { | |
96 word = context->mem_pointers[0] + address/2; | |
97 } else if (address >= 0xE00000) { | |
98 word = context->mem_pointers[1] + (address & 0xFFFF)/2; | |
99 } else { | |
100 return 0; | |
101 } | |
102 if (address & 1) { | |
103 return *word; | |
104 } | |
105 return *word >> 8; | |
106 } | |
107 | |
108 void gdb_run_command(m68k_context * context, uint32_t pc, char * command) | |
109 { | |
110 char send_buf[512]; | |
111 fprintf(stderr, "Received command %s\n", command); | |
112 switch(*command) | |
113 { | |
114 | |
115 case 'c': | |
116 if (*(command+1) != 0) { | |
117 //TODO: implement resuming at an arbitrary address | |
118 goto not_impl; | |
119 } | |
120 cont = 1; | |
121 expect_break_response = 1; | |
122 break; | |
123 case 'H': | |
124 if (command[1] == 'g' || command[1] == 'c') {; | |
125 //no thread suport, just acknowledge | |
126 gdb_send_command("OK"); | |
127 } else { | |
128 goto not_impl; | |
129 } | |
130 break; | |
131 case 'Z': { | |
132 uint8_t type = command[1]; | |
133 if (type < '2') { | |
134 uint32_t address = strtoul(command+3, NULL, 16); | |
135 insert_breakpoint(context, address, (uint8_t *)gdb_debug_enter); | |
136 gdb_send_command("OK"); | |
137 } else { | |
138 //watchpoints are not currently supported | |
139 gdb_send_command(""); | |
140 } | |
141 break; | |
142 } | |
143 case 'z': { | |
144 uint8_t type = command[1]; | |
145 if (type < '2') { | |
146 uint32_t address = strtoul(command+3, NULL, 16); | |
147 remove_breakpoint(context, address); | |
148 gdb_send_command("OK"); | |
149 } else { | |
150 //watchpoints are not currently supported | |
151 gdb_send_command(""); | |
152 } | |
153 break; | |
154 } | |
155 case 'g': { | |
156 char * cur = send_buf; | |
157 for (int i = 0; i < 8; i++) | |
158 { | |
159 hex_32(context->dregs[i], cur); | |
160 cur += 8; | |
161 } | |
162 for (int i = 0; i < 8; i++) | |
163 { | |
164 hex_32(context->aregs[i], cur); | |
165 cur += 8; | |
166 } | |
167 hex_32(calc_status(context), cur); | |
168 cur += 8; | |
169 hex_32(pc, cur); | |
170 cur += 8; | |
171 *cur = 0; | |
172 gdb_send_command(send_buf); | |
173 break; | |
174 } | |
175 case 'm': { | |
176 char * rest; | |
177 uint32_t address = strtoul(command+1, &rest, 16); | |
178 uint32_t size = strtoul(rest+1, NULL, 16); | |
179 if (size > sizeof(send_buf-1)/2) { | |
180 size = sizeof(send_buf-1)/2; | |
181 } | |
182 char *cur = send_buf; | |
183 while (size) | |
184 { | |
185 hex_8(read_byte(context, address), cur); | |
186 cur += 2; | |
187 address++; | |
188 size--; | |
189 } | |
190 *cur = 0; | |
191 gdb_send_command(send_buf); | |
192 break; | |
193 } | |
194 case 'p': { | |
195 unsigned long reg = strtoul(command+1, NULL, 16); | |
196 | |
197 if (reg < 8) { | |
198 hex_32(context->dregs[reg], send_buf); | |
199 } else if (reg < 16) { | |
200 hex_32(context->aregs[reg-8], send_buf); | |
201 } else if (reg == 16) { | |
202 hex_32(calc_status(context), send_buf); | |
203 } else if (reg == 17) { | |
204 hex_32(pc, send_buf); | |
205 } else { | |
206 send_buf[0] = 0; | |
207 } | |
208 send_buf[8] = 0; | |
209 gdb_send_command(send_buf); | |
210 break; | |
211 } | |
212 case 'q': | |
213 if (!memcmp("Supported", command+1, strlen("Supported"))) { | |
214 sprintf(send_buf, "PacketSize=%X", (int)bufsize); | |
215 gdb_send_command(send_buf); | |
216 } else if (!memcmp("Attached", command+1, strlen("Supported"))) { | |
217 //not really meaningful for us, but saying we spawned a new process | |
218 //is probably closest to the truth | |
219 gdb_send_command("0"); | |
220 } else if (!memcmp("Offsets", command+1, strlen("Offsets"))) { | |
221 //no relocations, so offsets are all 0 | |
222 gdb_send_command("Text=0;Data=0;Bss=0"); | |
223 } else if (!memcmp("Symbol", command+1, strlen("Symbol"))) { | |
224 gdb_send_command(""); | |
225 } else if (!memcmp("TStatus", command+1, strlen("TStatus"))) { | |
226 //TODO: actual tracepoint support | |
227 gdb_send_command("T0;tnotrun:0"); | |
228 } else if (!memcmp("TfV", command+1, strlen("TfV")) || !memcmp("TfP", command+1, strlen("TfP"))) { | |
229 //TODO: actual tracepoint support | |
230 gdb_send_command(""); | |
231 } else if (command[1] == 'C') { | |
232 //we only support a single thread currently, so send 1 | |
233 gdb_send_command("1"); | |
234 } else { | |
235 goto not_impl; | |
236 } | |
237 break; | |
238 case 'v': | |
239 if (!memcmp("Cont?", command+1, strlen("Cont?"))) { | |
240 gdb_send_command("vCont;c;C;s;S"); | |
241 } else if (!memcmp("Cont;", command+1, strlen("Cont;"))) { | |
242 switch (*(command + 1 + strlen("Cont;"))) | |
243 { | |
244 case 'c': | |
245 case 'C': | |
246 //might be interesting to have continue with signal fire a | |
247 //trap exception or something, but for no we'll treat it as | |
248 //a normal continue | |
249 cont = 1; | |
250 expect_break_response = 1; | |
251 break; | |
252 default: | |
253 goto not_impl; | |
254 } | |
255 } else { | |
256 goto not_impl; | |
257 } | |
258 break; | |
259 case '?': | |
260 gdb_send_command("S05"); | |
261 break; | |
262 default: | |
263 goto not_impl; | |
264 | |
265 } | |
266 return; | |
267 not_impl: | |
268 fprintf(stderr, "Command %s is not implemented, exiting...\n", command); | |
269 exit(1); | |
270 } | |
271 | |
272 m68k_context * gdb_debug_enter(m68k_context * context, uint32_t pc) | |
273 { | |
274 fprintf(stderr, "Entered debugger at address %X\n", pc); | |
275 if (expect_break_response) { | |
276 gdb_send_command("S05"); | |
277 expect_break_response = 0; | |
278 } | |
26 resume_pc = pc; | 279 resume_pc = pc; |
27 cont = 0; | 280 cont = 0; |
28 uint8_t partial = 0; | 281 uint8_t partial = 0; |
29 while(!cont) | 282 while(!cont) |
30 { | 283 { |
33 curbuf = buf; | 286 curbuf = buf; |
34 end = buf + numread; | 287 end = buf + numread; |
35 } else if (partial) { | 288 } else if (partial) { |
36 if (curbuf != buf) { | 289 if (curbuf != buf) { |
37 memmove(curbuf, buf, end-curbuf); | 290 memmove(curbuf, buf, end-curbuf); |
38 end -= cufbuf - buf; | 291 end -= curbuf - buf; |
39 } | 292 } |
40 int numread = read(STDIN_FILENO, end, bufsize - (end-buf)); | 293 int numread = read(STDIN_FILENO, end, bufsize - (end-buf)); |
41 end += numread; | 294 end += numread; |
42 curbuf = buf; | 295 curbuf = buf; |
43 } | 296 } |
53 if (*curbuf == '#') { | 306 if (*curbuf == '#') { |
54 //check to make sure we've received the checksum bytes | 307 //check to make sure we've received the checksum bytes |
55 if (end-curbuf >= 2) { | 308 if (end-curbuf >= 2) { |
56 //TODO: verify checksum | 309 //TODO: verify checksum |
57 //Null terminate payload | 310 //Null terminate payload |
58 *curbuf = 0 | 311 *curbuf = 0; |
59 //send acknowledgement | 312 //send acknowledgement |
60 write(FILENO_STDOUT, "+", 1); | 313 if (write(STDOUT_FILENO, "+", 1) < 1) { |
61 gdb_run_command(genesis_context * gen, start); | 314 fputs("Error writing to stdout\n", stderr); |
315 exit(1); | |
316 } | |
317 gdb_run_command(context, pc, start); | |
62 curbuf += 2; | 318 curbuf += 2; |
63 } | 319 } |
64 } else { | 320 } else { |
65 curbuf--; | 321 curbuf--; |
66 partial = 1; | 322 partial = 1; |
67 break; | 323 break; |
68 } | 324 } |
325 } else { | |
326 fprintf(stderr, "Ignoring character %c\n", *curbuf); | |
69 } | 327 } |
70 } | 328 } |
71 } | 329 if (curbuf == end) { |
72 fcntl(STDIN_FILENO, FD_SETFL, O_NONBLOCK); | 330 curbuf = NULL; |
73 } | 331 } |
74 | 332 } |
75 void gdb_run_command(genesis_context * gen, char * command) | 333 return context; |
76 { | 334 } |
77 switch(*command) | 335 |
78 { | 336 void gdb_remote_init(void) |
79 case 'c': | 337 { |
80 if (*(command+1) != 0) { | |
81 resume_pc = | |
82 } | |
83 cont = 1; | |
84 expect_break_response = 1; | |
85 break; | |
86 case 's': | |
87 | |
88 } | |
89 } | |
90 | |
91 void gdb_run_commands(genesis_context * gen) | |
92 { | |
93 int enter_debugger = 0; | |
94 char * cur = buf; | |
95 while(cur < curbuf); | |
96 { | |
97 if(*cur == '$') { | |
98 cur++ | |
99 char * start = cur; | |
100 while (cur < curbuf && *cur != '#') { | |
101 cur++; | |
102 } | |
103 if (*cur == '#') { | |
104 //check to make sure we've received the checksum bytes | |
105 if (curbuf-cur >= 2) { | |
106 //TODO: verify checksum | |
107 //Null terminate payload | |
108 //send acknowledgement | |
109 write(FILENO_STDOUT, "+", 1); | |
110 gdb_run_command(genesis_context * gen, start); | |
111 cur += 2; | |
112 } else { | |
113 cur = start - 1; | |
114 break; | |
115 } | |
116 } else { | |
117 cur = start - 1; | |
118 break; | |
119 } | |
120 } else { | |
121 if (*cur == 0x03) { | |
122 enter_debugger = 1; | |
123 } | |
124 cur++; | |
125 } | |
126 } | |
127 | |
128 //FIXME | |
129 if (consumed == curbuf-buf) { | |
130 curbuf = buf; | |
131 } else if (consumed > 0) { | |
132 memmove(buf, buf + consumed, curbuf - buf - consumed); | |
133 curbuf -= consumed; | |
134 } | |
135 } | |
136 | |
137 int gdb_command_poll(genesis_context * gen) | |
138 { | |
139 for(;;) | |
140 { | |
141 if (curbuf == buf + bufsize) { | |
142 //buffer is full, expand it | |
143 bufsize *= 2; | |
144 buf = realloc(buf, bufsize); | |
145 if (!buf) { | |
146 fprintf(stderr, "Failed to grow GDB command buffer to %d bytes\n", (int)bufsize); | |
147 exit(1); | |
148 } | |
149 curbuf = buf + bufsize/2; | |
150 } | |
151 int numread = read(STDIN_FILENO, buf, bufsize); | |
152 if (numread < 0) { | |
153 if (errno == EAGAIN || errno == EWOULDBLOCK) { | |
154 return 0; | |
155 } else { | |
156 fprintf(stderr, "Error %d while reading GDB commands from stdin", errno); | |
157 exit(1); | |
158 } | |
159 } else if (numread == 0) { | |
160 exit(0); | |
161 } | |
162 for (curbuf = buf, end = buf+numread; curbuf < end; curbuf++) | |
163 { | |
164 if (*curbuf = 0x03) | |
165 { | |
166 curbuf++; | |
167 return 1; | |
168 } | |
169 } | |
170 } | |
171 return 0; | |
172 } | |
173 | |
174 void gdb_remote_init() | |
175 { | |
176 fcntl(STDIN_FILENO, FD_SETFL, O_NONBLOCK); | |
177 buf = malloc(INITIAL_BUFFER_SIZE); | 338 buf = malloc(INITIAL_BUFFER_SIZE); |
178 curbuf = buf; | 339 curbuf = NULL; |
179 bufzie = INITIAL_BUFFER_SIZE; | 340 bufsize = INITIAL_BUFFER_SIZE; |
180 } | 341 } |