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