1 /*
2 * gdb_mi.c
3 *
4 * Copyright 2014 Colomban Wendling <colomban@geany.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 /*
23 * Parses GDB/MI records
24 * https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI-Output-Syntax.html
25 */
26
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <glib.h>
33
34 #include "gdb_mi.h"
35
36
37 #define ascii_isodigit(c) (((guchar) (c)) >= '0' && ((guchar) (c)) <= '7')
38
39
40 static struct gdb_mi_value *parse_value(const gchar **p);
41
42
gdb_mi_value_free(struct gdb_mi_value * val)43 void gdb_mi_value_free(struct gdb_mi_value *val)
44 {
45 if (! val)
46 return;
47 switch (val->type)
48 {
49 case GDB_MI_VAL_STRING:
50 g_free(val->v.string);
51 break;
52
53 case GDB_MI_VAL_LIST:
54 gdb_mi_result_free(val->v.list, TRUE);
55 break;
56 }
57 g_free(val);
58 }
59
gdb_mi_result_free(struct gdb_mi_result * res,gboolean next)60 void gdb_mi_result_free(struct gdb_mi_result *res, gboolean next)
61 {
62 if (! res)
63 return;
64 g_free(res->var);
65 gdb_mi_value_free(res->val);
66 if (next)
67 gdb_mi_result_free(res->next, next);
68 g_free(res);
69 }
70
gdb_mi_record_free(struct gdb_mi_record * record)71 void gdb_mi_record_free(struct gdb_mi_record *record)
72 {
73 if (! record)
74 return;
75 g_free(record->token);
76 g_free(record->klass);
77 gdb_mi_result_free(record->first, TRUE);
78 g_free(record);
79 }
80
81 /* parses: cstring
82 *
83 * cstring is defined as:
84 *
85 * c-string ==>
86 * """ seven-bit-iso-c-string-content """
87 *
88 * FIXME: what exactly does "seven-bit-iso-c-string-content" mean?
89 * reading between the lines suggests it's US-ASCII with values >= 0x80
90 * encoded as \NNN (most likely octal), but that's not really clear --
91 * although it parses everything I encountered
92 * FIXME: this does NOT convert to UTF-8. should it? */
parse_cstring(const gchar ** p)93 static gchar *parse_cstring(const gchar **p)
94 {
95 GString *str = g_string_new(NULL);
96
97 if (**p == '"')
98 {
99 const gchar *base;
100
101 (*p)++;
102 base = *p;
103 while (**p != '"')
104 {
105 gchar c = **p;
106 /* TODO: check expansions here */
107 if (c == '\\')
108 {
109 g_string_append_len(str, base, (*p) - base);
110 (*p)++;
111 c = **p;
112 switch (g_ascii_tolower(c))
113 {
114 case '\\':
115 case '"': break;
116 case 'a': c = '\a'; break;
117 case 'b': c = '\b'; break;
118 case 'f': c = '\f'; break;
119 case 'n': c = '\n'; break;
120 case 'r': c = '\r'; break;
121 case 't': c = '\t'; break;
122 case 'v': c = '\v'; break;
123 default:
124 /* hex escape, 1-2 digits (\xN or \xNN)
125 *
126 * FIXME: is this useful? Is this right?
127 * the original dbm_gdb.c:unescape_hex_values() used to
128 * read escapes of the form \xNNN and treat them as wide
129 * characters numbers, but this looks weird for a C-like
130 * escape.
131 * Also, note that this doesn't seem to be referenced anywhere
132 * in GDB/MI syntax. Only reference in GDB manual is about
133 * keybindings, which use the syntax implemented here */
134 if (g_ascii_tolower(**p) == 'x' && g_ascii_isxdigit((*p)[1]))
135 {
136 c = (gchar) g_ascii_xdigit_value(*++(*p));
137 if (g_ascii_isxdigit((*p)[1]))
138 c = (gchar) ((c * 16) + g_ascii_xdigit_value(*++(*p)));
139 }
140 /* octal escape, 1-3 digits (\N, \NN or \NNN) */
141 else if (ascii_isodigit(**p))
142 {
143 int i, v;
144 v = g_ascii_digit_value(**p);
145 for (i = 0; ascii_isodigit((*p)[1]) && i < 2; i++)
146 v = (v * 8) + g_ascii_digit_value(*++(*p));
147 if (v <= 0xff)
148 c = (gchar) v;
149 else
150 {
151 *p = *p - 3; /* put the whole sequence back */
152 c = **p;
153 g_warning("Octal escape sequence out of range: %.4s", *p);
154 }
155 }
156 else
157 {
158 g_warning("Unkown escape \"\\%c\"", **p);
159 (*p)--; /* put the \ back */
160 c = **p;
161 }
162 break;
163 }
164 g_string_append_c(str, c);
165 base = (*p) + 1;
166 }
167 else if (**p == '\0')
168 break;
169 (*p)++;
170 }
171 g_string_append_len(str, base, (*p) - base);
172 if (**p == '"')
173 (*p)++;
174 }
175 return g_string_free(str, FALSE);
176 }
177
178 /* parses: string
179 * FIXME: what really is a string? here it uses [a-zA-Z_-.][a-zA-Z0-9_-.]* but
180 * the docs aren't clear on this */
parse_string(const gchar ** p)181 static gchar *parse_string(const gchar **p)
182 {
183 const gchar *base = *p;
184
185 if (g_ascii_isalpha(**p) || strchr("-_.", **p))
186 {
187 for ((*p)++; g_ascii_isalnum(**p) || strchr("-_.", **p); (*p)++)
188 ;
189 }
190
191 return g_strndup (base, *p - base);
192 }
193
194 /* parses: string "=" value */
parse_result(struct gdb_mi_result * result,const gchar ** p)195 static gboolean parse_result(struct gdb_mi_result *result, const gchar **p)
196 {
197 result->var = parse_string(p);
198 while (g_ascii_isspace(**p)) (*p)++;
199 if (**p == '=')
200 {
201 (*p)++;
202 while (g_ascii_isspace(**p)) (*p)++;
203 result->val = parse_value(p);
204 }
205 return result->var && result->val;
206 }
207
208 /* parses: cstring | list | tuple
209 * Actually, this is more permissive and allows mixed tuples/lists */
parse_value(const gchar ** p)210 static struct gdb_mi_value *parse_value(const gchar **p)
211 {
212 struct gdb_mi_value *val = NULL;
213 if (**p == '"')
214 {
215 val = g_malloc0(sizeof *val);
216 val->type = GDB_MI_VAL_STRING;
217 val->v.string = parse_cstring(p);
218 }
219 else if (**p == '{' || **p == '[')
220 {
221 struct gdb_mi_result *prev = NULL;
222 val = g_malloc0(sizeof *val);
223 val->type = GDB_MI_VAL_LIST;
224 gchar end = **p == '{' ? '}' : ']';
225 (*p)++;
226 while (**p && **p != end)
227 {
228 struct gdb_mi_result *item = g_malloc0(sizeof *item);
229 while (g_ascii_isspace(**p)) (*p)++;
230 if ((item->val = parse_value(p)) ||
231 parse_result(item, p))
232 {
233 if (prev)
234 prev->next = item;
235 else
236 val->v.list = item;
237 prev = item;
238 }
239 else
240 {
241 gdb_mi_result_free(item, TRUE);
242 break;
243 }
244 while (g_ascii_isspace(**p)) (*p)++;
245 if (**p != ',') break;
246 (*p)++;
247 }
248 if (**p == end)
249 (*p)++;
250 }
251 return val;
252 }
253
is_prompt(const gchar * p)254 static gboolean is_prompt(const gchar *p)
255 {
256 if (strncmp("(gdb)", p, 5) == 0)
257 {
258 p += 5;
259 while (g_ascii_isspace(*p)) p++;
260 }
261 return *p == 0;
262 }
263
264 /* parses: async-record | stream-record | result-record
265 * note: post-value data is ignored.
266 *
267 * FIXME: that's NOT exactly what the GDB docs call an output, and that's not
268 * exactly what a line could be. The GDB docs state that a line can
269 * contain more than one stream-record, as it's not terminated by a
270 * newline, and as it defines:
271 *
272 * output ==>
273 * ( out-of-band-record )* [ result-record ] "(gdb)" nl
274 * out-of-band-record ==>
275 * async-record | stream-record
276 * stream-record ==>
277 * console-stream-output | target-stream-output | log-stream-output
278 * console-stream-output ==>
279 * "~" c-string
280 * target-stream-output ==>
281 * "@" c-string
282 * log-stream-output ==>
283 * "&" c-string
284 *
285 * so as none of the stream-outputs are terminated by a newline, and the
286 * parser here only extracts the first record it will fail with combined
287 * records in one line.
288 */
gdb_mi_record_parse(const gchar * line)289 struct gdb_mi_record *gdb_mi_record_parse(const gchar *line)
290 {
291 struct gdb_mi_record *record = g_malloc0(sizeof *record);
292
293 /* FIXME: prompt detection should not really be useful, especially not as a
294 * special case, as the prompt should always follow an (optional) record */
295 if (is_prompt(line))
296 record->type = GDB_MI_TYPE_PROMPT;
297 else
298 {
299 /* extract token */
300 const gchar *token_end = line;
301 for (token_end = line; g_ascii_isdigit(*token_end); token_end++)
302 ;
303 if (token_end > line)
304 {
305 record->token = g_strndup(line, (gsize)(token_end - line));
306 line = token_end;
307 while (g_ascii_isspace(*line)) line++;
308 }
309
310 /* extract record */
311 record->type = *line;
312 if (*line) ++line;
313 while (g_ascii_isspace(*line)) line++;
314 switch (record->type)
315 {
316 case '~':
317 case '@':
318 case '&':
319 /* FIXME: although the syntax description in the docs are clear,
320 * the "GDB/MI Stream Records" section does not agree with it,
321 * widening the input to:
322 *
323 * > [string-output] is either raw text (with an implicit new
324 * > line) or a quoted C string (which does not contain an
325 * > implicit newline).
326 *
327 * This adds "raw text" to "c-string"... so? */
328 record->klass = parse_cstring(&line);
329 break;
330 case '^':
331 case '*':
332 case '+':
333 case '=':
334 {
335 struct gdb_mi_result *prev = NULL;
336 record->klass = parse_string(&line);
337 while (*line)
338 {
339 while (g_ascii_isspace(*line)) line++;
340 if (*line != ',')
341 break;
342 else
343 {
344 struct gdb_mi_result *res = g_malloc0(sizeof *res);
345 line++;
346 while (g_ascii_isspace(*line)) line++;
347 if (!parse_result(res, &line))
348 {
349 g_warning("failed to parse result");
350 gdb_mi_result_free(res, TRUE);
351 break;
352 }
353 if (prev)
354 prev->next = res;
355 else
356 record->first = res;
357 prev = res;
358 }
359 }
360 break;
361 }
362 default:
363 /* FIXME: what to do with invalid prefix? */
364 record->type = GDB_MI_TYPE_PROMPT;
365 }
366 }
367
368 return record;
369 }
370
371 /* Extracts a variable value from a result
372 * @res may be NULL */
gdb_mi_result_var_value(const struct gdb_mi_result * result,const gchar * name)373 static const struct gdb_mi_value *gdb_mi_result_var_value(const struct gdb_mi_result *result, const gchar *name)
374 {
375 g_return_val_if_fail(name != NULL, NULL);
376
377 for (; result; result = result->next)
378 {
379 if (result->var && strcmp(result->var, name) == 0)
380 return result->val;
381 }
382 return NULL;
383 }
384
385 /* Extracts a variable value from a record
386 * @param res a first result, or NULL
387 * @param name the variable name
388 * @param type the expected type of the value
389 * @returns the value of @p name variable (type depending on @p type), or NULL
390 */
gdb_mi_result_var(const struct gdb_mi_result * result,const gchar * name,enum gdb_mi_value_type type)391 const void *gdb_mi_result_var(const struct gdb_mi_result *result, const gchar *name, enum gdb_mi_value_type type)
392 {
393 const struct gdb_mi_value *val = gdb_mi_result_var_value(result, name);
394 if (! val || val->type != type)
395 return NULL;
396 else if (val->type == GDB_MI_VAL_STRING)
397 return val->v.string;
398 else if (val->type == GDB_MI_VAL_LIST)
399 return val->v.list;
400 return NULL;
401 }
402
403 /* checks whether a record matches, possibly including some string values
404 * @param record a record
405 * @param type the expected type of the record
406 * @param klass the expected class of the record
407 * @param ... a NULL-terminated name/return location pairs for string results
408 * @returns TRUE if record matched, FALSE otherwise
409 *
410 * Usage example
411 * @{
412 * const gchar *id;
413 * if (gdb_mi_record_matches(record, '=', 'thread-created', "id", &id, NULL))
414 * // here record matched and `id` is present and a string
415 * @}
416 */
gdb_mi_record_matches(const struct gdb_mi_record * record,enum gdb_mi_record_type type,const gchar * klass,...)417 gboolean gdb_mi_record_matches(const struct gdb_mi_record *record, enum gdb_mi_record_type type, const gchar *klass, ...)
418 {
419 va_list ap;
420 const gchar *name;
421 gboolean success = TRUE;
422
423 g_return_val_if_fail(record != NULL, FALSE);
424
425 if (record->type != type || strcmp(record->klass, klass) != 0)
426 return FALSE;
427
428 va_start(ap, klass);
429 while ((name = va_arg(ap, const gchar *)) != NULL && success)
430 {
431 const gchar **out = va_arg(ap, const gchar **);
432
433 g_return_val_if_fail(out != NULL, FALSE);
434
435 *out = gdb_mi_result_var(record->first, name, GDB_MI_VAL_STRING);
436 success = *out != NULL;
437 }
438 va_end(ap);
439 return success;
440 }
441
442
443 #ifdef TEST
444
445 static void gdb_mi_result_dump(const struct gdb_mi_result *r, gboolean next, gint indent);
446
gdb_mi_value_dump(const struct gdb_mi_value * v,gint indent)447 static void gdb_mi_value_dump(const struct gdb_mi_value *v, gint indent)
448 {
449 fprintf(stderr, "%*stype = %d\n", indent * 2, "", v->type);
450 switch (v->type)
451 {
452 case GDB_MI_VAL_STRING:
453 fprintf(stderr, "%*sstring = %s\n", indent * 2, "", v->v.string);
454 break;
455 case GDB_MI_VAL_LIST:
456 fprintf(stderr, "%*slist =>\n", indent * 2, "");
457 if (v->v.list)
458 gdb_mi_result_dump(v->v.list, TRUE, indent + 1);
459 break;
460 }
461 }
462
gdb_mi_result_dump(const struct gdb_mi_result * r,gboolean next,gint indent)463 static void gdb_mi_result_dump(const struct gdb_mi_result *r, gboolean next, gint indent)
464 {
465 fprintf(stderr, "%*svar = %s\n", indent * 2, "", r->var);
466 fprintf(stderr, "%*sval =>\n", indent * 2, "");
467 gdb_mi_value_dump(r->val, indent + 1);
468 if (next && r->next)
469 gdb_mi_result_dump(r->next, next, indent);
470 }
471
gdb_mi_record_dump(const struct gdb_mi_record * record)472 static void gdb_mi_record_dump(const struct gdb_mi_record *record)
473 {
474 fprintf(stderr, "record =>\n");
475 fprintf(stderr, " type = '%c' (%d)\n", record->type ? record->type : '0', record->type);
476 fprintf(stderr, " token = %s\n", record->token);
477 fprintf(stderr, " class = %s\n", record->klass);
478 fprintf(stderr, " results =>\n");
479 if (record->first)
480 gdb_mi_result_dump(record->first, TRUE, 2);
481 }
482
read_line(FILE * fp)483 static gchar *read_line(FILE *fp)
484 {
485 char buf[1024] = {0};
486 GString *line = g_string_new(NULL);
487
488 while (fgets(buf, sizeof buf, fp))
489 {
490 g_string_append(line, buf);
491 if (line->len < 1 || line->str[line->len - 1] == '\n')
492 break;
493 }
494
495 return g_string_free(line, line->len < 1);
496 }
497
main(int argc,char ** argv)498 int main(int argc, char **argv)
499 {
500 gchar *line;
501
502 while ((line = read_line(stdin)) != NULL)
503 {
504 struct gdb_mi_record *record = gdb_mi_record_parse(line);
505
506 gdb_mi_record_dump(record);
507 gdb_mi_record_free(record);
508
509 g_free(line);
510 }
511
512 return 0;
513 }
514
515 #endif
516