[geany/geany-plugins] 5ddb7c: debugger: Add a real GDB/MI parser

Colomban Wendling git-noreply at xxxxx
Sun Nov 15 17:28:15 UTC 2015


Branch:      refs/heads/master
Author:      Colomban Wendling <ban at herbesfolles.org>
Committer:   Colomban Wendling <ban at herbesfolles.org>
Date:        Sat, 25 Oct 2014 12:39:40 UTC
Commit:      5ddb7c7211068f5603826d98790b47cdbcdc8a8a
             https://github.com/geany/geany-plugins/commit/5ddb7c7211068f5603826d98790b47cdbcdc8a8a

Log Message:
-----------
debugger: Add a real GDB/MI parser


Modified Paths:
--------------
    debugger/src/Makefile.am
    debugger/src/gdb_mi.c
    debugger/src/gdb_mi.h

Modified: debugger/src/Makefile.am
2 lines changed, 2 insertions(+), 0 deletions(-)
===================================================================
@@ -39,6 +39,8 @@ debugger_la_SOURCES = \
 	envtree.h     \
 	gui.h     \
 	gui.c     \
+	gdb_mi.h  \
+	gdb_mi.c  \
 	keys.c     \
 	keys.h     \
 	atree.c     \


Modified: debugger/src/gdb_mi.c
512 lines changed, 512 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,512 @@
+/*
+ *      gdb_mi.c
+ *      
+ *      Copyright 2014 Colomban Wendling <colomban at geany.org>
+ *      
+ *      This program is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published by
+ *      the Free Software Foundation; either version 2 of the License, or
+ *      (at your option) any later version.
+ *      
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ *      
+ *      You should have received a copy of the GNU General Public License
+ *      along with this program; if not, write to the Free Software
+ *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *      MA 02110-1301, USA.
+ */
+
+/* 
+ * Parses GDB/MI records
+ * http://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_214.html
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <glib.h>
+
+#include "gdb_mi.h"
+
+#define DEBUG
+
+#define isodigit(c) ((c) >= '0' && (c) <= '7')
+
+
+#if defined(DEBUG) || defined(TEST)
+static void gdb_mi_result_dump(const struct gdb_mi_result *r, gboolean next, gint indent);
+#endif
+static struct gdb_mi_value *parse_value(const gchar **p);
+
+
+void gdb_mi_value_free(struct gdb_mi_value *val)
+{
+	if (! val)
+		return;
+	switch (val->type)
+	{
+		case GDB_MI_VAL_STRING:
+			g_free(val->string);
+			g_warn_if_fail(val->list == NULL);
+			break;
+
+		case GDB_MI_VAL_LIST:
+			gdb_mi_result_free(val->list, TRUE);
+			g_warn_if_fail(val->string == NULL);
+			break;
+	}
+	g_free(val);
+}
+
+void gdb_mi_result_free(struct gdb_mi_result *res, gboolean next)
+{
+	if (! res)
+		return;
+	g_free(res->var);
+	gdb_mi_value_free(res->val);
+	if (next)
+		gdb_mi_result_free(res->next, next);
+	g_free(res);
+}
+
+void gdb_mi_record_free(struct gdb_mi_record *record)
+{
+	if (! record)
+		return;
+	g_free(record->token);
+	g_free(record->klass);
+	gdb_mi_result_free(record->first, TRUE);
+	g_free(record);
+}
+
+#if defined(DEBUG) || defined(TEST)
+
+static void gdb_mi_value_dump(const struct gdb_mi_value *v, gint indent)
+{
+	fprintf(stderr, "%*stype = %d\n", indent * 2, "", v->type);
+	switch (v->type)
+	{
+		case GDB_MI_VAL_STRING:
+			fprintf(stderr, "%*sstring = %s\n", indent * 2, "", v->string);
+			break;
+		case GDB_MI_VAL_LIST:
+			fprintf(stderr, "%*slist =>\n", indent * 2, "");
+			if (v->list)
+				gdb_mi_result_dump(v->list, TRUE, indent + 1);
+			break;
+	}
+}
+
+static void gdb_mi_result_dump(const struct gdb_mi_result *r, gboolean next, gint indent)
+{
+	fprintf(stderr, "%*svar = %s\n", indent * 2, "", r->var);
+	fprintf(stderr, "%*sval =>\n", indent * 2, "");
+	gdb_mi_value_dump(r->val, indent + 1);
+	if (next && r->next)
+		gdb_mi_result_dump(r->next, next, indent);
+}
+
+static void gdb_mi_record_dump(const struct gdb_mi_record *record)
+{
+	fprintf(stderr, "record =>\n");
+	fprintf(stderr, "  type = '%c' (%d)\n", record->type ? record->type : '0', record->type);
+	fprintf(stderr, "  token = %s\n", record->token);
+	fprintf(stderr, "  class = %s\n", record->klass);
+	fprintf(stderr, "  results =>\n");
+	if (record->first)
+		gdb_mi_result_dump(record->first, TRUE, 2);
+}
+
+#endif
+
+/* parses: cstring
+ * 
+ * cstring is defined as:
+ * 
+ * c-string ==>
+ *     """ seven-bit-iso-c-string-content """ 
+ * 
+ * FIXME: what exactly does "seven-bit-iso-c-string-content" mean?
+ *        reading between the lines suggests it's US-ASCII with values >= 0x80
+ *        encoded as \NNN (most likely octal), but that's not really clear */
+static gchar *parse_cstring(const gchar **p)
+{
+	GString *str = g_string_new(NULL);
+
+	if (**p == '"')
+	{
+		(*p)++;
+		while (**p != '"')
+		{
+			int c = **p;
+			/* TODO: check expansions here */
+			if (c == '\\')
+			{
+				(*p)++;
+				c = **p;
+				switch (tolower(c))
+				{
+					case '\\':
+					case '"': break;
+					case 'a': c = '\a'; break;
+					case 'b': c = '\b'; break;
+					case 'n': c = '\n'; break;
+					case 'r': c = '\r'; break;
+					case 't': c = '\t'; break;
+					case 'v': c = '\v'; break;
+					default:
+						/* two-digit hex escape */
+						if (tolower(c) == 'x' && isxdigit((*p)[1]) && isxdigit((*p)[2]))
+						{
+							c  = (tolower(*++(*p)) - '0') * 16;
+							c += (tolower(*++(*p)) - '0');
+						}
+						/* three-digit octal escape */
+						else if (c >= '0' && c <= '3' && isodigit((*p)[1]) && isodigit((*p)[2]))
+						{
+							c  = (*  (*p) - '0') * 8 * 8;
+							c += (*++(*p) - '0') * 8;
+							c += (*++(*p) - '0');
+						}
+						else
+						{
+							g_warning("Unkown escape \"\\%c\"", **p);
+							(*p)--; /* put the \ back */
+							c = **p;
+						}
+						break;
+				}
+			}
+			if (**p == '\0')
+				break;
+			g_string_append_c(str, (gchar) c);
+			(*p)++;
+		}
+		if (**p == '"')
+			(*p)++;
+	}
+	return g_string_free(str, FALSE);
+}
+
+/* parses: string
+ * FIXME: what really is a string?  here it uses [a-zA-Z_][a-zA-Z0-9_-.]* but
+ *        the docs aren't clear on this */
+static gchar *parse_string(const gchar **p)
+{
+	GString *str = g_string_new(NULL);
+
+	if (isalpha(**p) || **p == '_')
+	{
+		g_string_append_c(str, **p);
+		for ((*p)++; isalnum(**p) || strchr("-_.", **p); (*p)++)
+			g_string_append_c(str, **p);
+	}
+	return g_string_free(str, FALSE);
+}
+
+/* parses: string "=" value */
+static gboolean parse_result(struct gdb_mi_result *result, const gchar **p)
+{
+	result->var = parse_string(p);
+	while (isspace(**p)) (*p)++;
+	if (**p == '=')
+	{
+		(*p)++;
+		while (isspace(**p)) (*p)++;
+		result->val = parse_value(p);
+	}
+	return result->var && result->val;
+}
+
+/* parses: cstring | list | tuple
+ * Actually, this is more permissive and allows mixed tuples/lists */
+static struct gdb_mi_value *parse_value(const gchar **p)
+{
+	struct gdb_mi_value *val = g_malloc0(sizeof *val);
+	if (**p == '"')
+	{
+		val->type = GDB_MI_VAL_STRING;
+		val->string = parse_cstring(p);
+	}
+	else if (**p == '{' || **p == '[')
+	{
+		struct gdb_mi_result *prev = NULL;
+		val->type = GDB_MI_VAL_LIST;
+		gchar end = **p == '{' ? '}' : ']';
+		(*p)++;
+		while (**p && **p != end)
+		{
+			struct gdb_mi_result *item = g_malloc0(sizeof *item);
+			while (isspace(**p)) (*p)++;
+			if ((item->val = parse_value(p)) ||
+				parse_result(item, p))
+			{
+				if (prev)
+					prev->next = item;
+				else
+					val->list = item;
+				prev = item;
+			}
+			else
+			{
+				gdb_mi_result_free(item, TRUE);
+				break;
+			}
+			while (isspace(**p)) (*p)++;
+			if (**p != ',') break;
+			(*p)++;
+		}
+		if (**p == end)
+			(*p)++;
+	}
+	else
+	{
+		gdb_mi_value_free(val);
+		val = NULL;
+	}
+	return val;
+}
+
+/* parses: async-record | stream-record | result-record
+ * note: post-value data is ignored.
+ * 
+ * FIXME: that's NOT exactly what the GDB docs call an output, and that's not
+ *        exactly what a line could be.  The GDB docs state that a line can
+ *        contain more than one stream-record, as it's not terminated by a
+ *        newline, and as it defines:
+ * 
+ *        output ==> 
+ *            ( out-of-band-record )* [ result-record ] "(gdb)" nl
+ *        out-of-band-record ==>
+ *            async-record | stream-record
+ *        stream-record ==>
+ *            console-stream-output | target-stream-output | log-stream-output
+ *        console-stream-output ==>
+ *            "~" c-string
+ *        target-stream-output ==>
+ *            "@" c-string
+ *        log-stream-output ==>
+ *            "&" c-string
+ * 
+ *        so as none of the stream-outputs are terminated by a newline, and the
+ *        parser here only extracts the first record it will fail with combined
+ *        records in one line.
+ */
+struct gdb_mi_record *gdb_mi_record_parse(const gchar *line)
+{
+	struct gdb_mi_record *record = g_malloc0(sizeof *record);
+	char nl;
+
+#ifdef DEBUG
+	fprintf(stderr, "line: %s\n", line);
+#endif
+
+	if (sscanf(line, "(gdb) %c", &nl) == 1 && (nl == '\r' || nl == '\n'))
+		record->type = GDB_MI_TYPE_PROMPT;
+	else
+	{
+		/* extract token */
+		const gchar *token_end = line;
+		for (token_end = line; isdigit(*token_end); token_end++)
+			;
+		if (token_end > line)
+		{
+			record->token = g_strndup(line, (gsize)(token_end - line));
+			line = token_end;
+			while (isspace(*line))
+				line++;
+		}
+
+		/* extract record */
+		record->type = *line;
+		++line;
+		while (isspace(*line)) line++;
+		switch (record->type)
+		{
+			case '~':
+			case '@':
+			case '&':
+				/* FIXME: although the syntax description in the docs are clear,
+				 * the "GDB/MI Stream Records" section does not agree with it,
+				 * widening the input to:
+				 * 
+				 * > [string-output] is either raw text (with an implicit new
+				 * > line) or a quoted C string (which does not contain an
+				 * > implicit newline).
+				 * 
+				 * This adds "raw text" to "c-string", so? */
+				record->klass = parse_cstring(&line);
+				break;
+			case '^':
+			case '*':
+			case '+':
+			case '=':
+			{
+				struct gdb_mi_result *prev = NULL;
+				record->klass = parse_string(&line);
+				while (*line)
+				{
+					while (isspace(*line)) line++;
+					if (*line != ',')
+						break;
+					else
+					{
+						struct gdb_mi_result *res = g_malloc0(sizeof *res);
+						line++;
+						while (isspace(*line)) line++;
+						if (!parse_result(res, &line))
+						{
+							g_warning("failed to parse result");
+							gdb_mi_result_free(res, TRUE);
+							break;
+						}
+						if (prev)
+							prev->next = res;
+						else
+							record->first = res;
+						prev = res;
+					}
+				}
+				break;
+			}
+			default:
+				/* FIXME: what to do with invalid prefix? */
+				record->type = GDB_MI_TYPE_PROMPT;
+		}
+	}
+
+#ifdef DEBUG
+	if (! (gdb_mi_record_matches(record, '^', "done", NULL) &&
+		   gdb_mi_result_var(record->first, "files", GDB_MI_VAL_LIST)))
+	gdb_mi_record_dump(record);
+#endif
+
+	return record;
+}
+
+/* Extracts a variable value from a result
+ * @res may be NULL */
+static const struct gdb_mi_value *gdb_mi_result_var_value(const struct gdb_mi_result *result, const gchar *name)
+{
+	g_return_val_if_fail(name != NULL, NULL);
+
+	for (; result; result = result->next)
+	{
+		if (result->var && strcmp(result->var, name) == 0)
+			return result->val;
+	}
+	return NULL;
+}
+
+/* Extracts a variable value from a record
+ * @param res a first result, or NULL
+ * @param name the variable name
+ * @param type the expected type of the value
+ * @returns the value of @p name variable (type depending on @p type), or NULL
+ */
+const void *gdb_mi_result_var(const struct gdb_mi_result *result, const gchar *name, enum gdb_mi_value_type type)
+{
+	const struct gdb_mi_value *val = gdb_mi_result_var_value(result, name);
+	if (! val || val->type != type)
+		return NULL;
+	else if (val->type == GDB_MI_VAL_STRING)
+		return val->string;
+	else if (val->type == GDB_MI_VAL_LIST)
+		return val->list;
+	return NULL;
+}
+
+/* FIXME: */
+const void *gdb_mi_result_var_path(const struct gdb_mi_result *result, const gchar *path, enum gdb_mi_value_type type)
+{
+	gchar **chunks = g_strsplit(path, "/", -1);
+	gchar **p;
+	void *value = NULL;
+
+	for (p = chunks; *p; p++)
+	{
+		const struct gdb_mi_value *val = gdb_mi_result_var_value(result, *p);
+		if (! val)
+			break;
+		if (! p[1])
+		{
+			if (val->type == type)
+			{
+				if (val->type == GDB_MI_VAL_STRING)
+					value = val->string;
+				else if (val->type == GDB_MI_VAL_LIST)
+					value = val->list;
+			}
+		}
+		else if (val->type == GDB_MI_VAL_LIST)
+			result = val->list;
+		else
+			break;
+	}
+	g_strfreev(chunks);
+	return value;
+}
+
+/* checks whether a record matches, possibly including some string values
+ * @param record a record
+ * @param type the expected type of the record
+ * @param klass the expected class of the record
+ * @param ... a NULL-terminated name/return location pairs for string results
+ * @returns TRUE if record matched, FALSE otherwise
+ * 
+ * Usage example
+ * @{
+ *     const gchar *id;
+ *     if (gdb_mi_record_matches(record, '=', 'thread-created', "id", &id, NULL))
+ *         // here record matched and `id` is present and a string
+ * @}
+ */
+gboolean gdb_mi_record_matches(const struct gdb_mi_record *record, enum gdb_mi_record_type type, const gchar *klass, ...)
+{
+	va_list ap;
+	const gchar *name;
+	gboolean success = TRUE;
+
+	g_return_val_if_fail(record != NULL, FALSE);
+
+	if (record->type != type || strcmp(record->klass, klass) != 0)
+		return FALSE;
+
+	va_start(ap, klass);
+	while ((name = va_arg(ap, const gchar *)) != NULL && success)
+	{
+		const gchar **out = va_arg(ap, const gchar **);
+
+		g_return_val_if_fail(out != NULL, FALSE);
+
+		*out = gdb_mi_result_var(record->first, name, GDB_MI_VAL_STRING);
+		success = *out != NULL;
+	}
+	va_end(ap);
+	return success;
+}
+
+
+#ifdef TEST
+
+int main(void)
+{
+	char buf[256] = {0};
+
+	while (fgets(buf, sizeof buf, stdin))
+	{
+		struct gdb_mi_record *record = gdb_mi_record_parse(buf);
+
+		gdb_mi_record_dump(record);
+		gdb_mi_record_free(record);
+	}
+	return 0;
+}
+
+#endif


Modified: debugger/src/gdb_mi.h
86 lines changed, 86 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,86 @@
+/*
+ *      gdb_mi.h
+ *      
+ *      Copyright 2014 Colomban Wendling <colomban at geany.org>
+ *      
+ *      This program is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published by
+ *      the Free Software Foundation; either version 2 of the License, or
+ *      (at your option) any later version.
+ *      
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ *      
+ *      You should have received a copy of the GNU General Public License
+ *      along with this program; if not, write to the Free Software
+ *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *      MA 02110-1301, USA.
+ */
+
+#ifndef GDB_MI_H
+#define GDB_MI_H
+
+#include <glib.h>
+
+enum gdb_mi_value_type
+{
+	GDB_MI_VAL_STRING,
+	GDB_MI_VAL_LIST
+};
+
+struct gdb_mi_result;
+struct gdb_mi_value
+{
+	enum gdb_mi_value_type type;
+	gchar *string;
+	struct gdb_mi_result *list;
+};
+
+struct gdb_mi_result
+{
+	gchar *var;
+	struct gdb_mi_value *val;
+	struct gdb_mi_result *next;
+};
+
+enum gdb_mi_record_type
+{
+	GDB_MI_TYPE_PROMPT = 0,
+	GDB_MI_TYPE_RESULT = '^',
+	GDB_MI_TYPE_EXEC_ASYNC = '*',
+	GDB_MI_TYPE_STATUS_ASYNC = '+',
+	GDB_MI_TYPE_NOTIFY_ASYNC = '=',
+	GDB_MI_TYPE_CONSOLE_STREAM = '~',
+	GDB_MI_TYPE_TARGET_STREAM = '@',
+	GDB_MI_TYPE_LOG_STREAM = '&'
+};
+
+struct gdb_mi_record
+{
+	enum gdb_mi_record_type type;
+	gchar *token;
+	gchar *klass; /*< contains the async record class or the stream output */
+	struct gdb_mi_result *first; /*< pointer to the first result (if any) */
+};
+
+
+void gdb_mi_value_free(struct gdb_mi_value *val);
+void gdb_mi_result_free(struct gdb_mi_result *res, gboolean next);
+void gdb_mi_record_free(struct gdb_mi_record *record);
+struct gdb_mi_record *gdb_mi_record_parse(const gchar *line);
+const void *gdb_mi_result_var(const struct gdb_mi_result *result, const gchar *name, enum gdb_mi_value_type type);
+gboolean gdb_mi_record_matches(const struct gdb_mi_record *record, enum gdb_mi_record_type type, const gchar *klass, ...) G_GNUC_NULL_TERMINATED;
+
+#define gdb_mi_result_foreach(node_, result_) \
+	for ((node_) = (result_); (node_); (node_) = (node_)->next)
+
+#define gdb_mi_result_foreach_matched(node_, result_, name_, type_) \
+	gdb_mi_result_foreach ((node_), (result_)) \
+		if (((name_) != NULL && (! (node_)->var || strcmp((node_)->var, (name_)) != 0)) || \
+			((type_) >= 0 && (node_)->val->type != (type_))) \
+			continue; \
+		else
+
+#endif /* guard */



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Plugins-Commits mailing list