[geany/geany-plugins] 835bea: Merge pull request #202 from b4n/debugger/safer-gdb-mi-parsing

Frank Lanitz git-noreply at xxxxx
Sun Nov 15 17:28:09 UTC 2015


Branch:      refs/heads/master
Author:      Frank Lanitz <frank at frank.uvena.de>
Committer:   Frank Lanitz <frank at frank.uvena.de>
Date:        Sun, 15 Nov 2015 17:28:09 UTC
Commit:      835bea07dc537efe2306c36e3654e67850b9b655
             https://github.com/geany/geany-plugins/commit/835bea07dc537efe2306c36e3654e67850b9b655

Log Message:
-----------
Merge pull request #202 from b4n/debugger/safer-gdb-mi-parsing

debugger: Safer GDB/MI parsing and other fixes


Modified Paths:
--------------
    debugger/src/Makefile.am
    debugger/src/breakpoint.c
    debugger/src/breakpoints.c
    debugger/src/dbm_gdb.c
    debugger/src/debug.c
    debugger/src/debug_module.c
    debugger/src/debug_module.h
    debugger/src/dpaned.c
    debugger/src/gdb_mi.c
    debugger/src/gdb_mi.h
    debugger/src/markers.c
    debugger/src/plugin.c
    debugger/src/tests/gdb_mi_test.expected
    debugger/src/tests/gdb_mi_test.input
    debugger/src/tests/gdb_mi_test.sh
    debugger/src/tpage.c

Modified: debugger/src/Makefile.am
12 lines changed, 12 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     \
@@ -72,4 +74,14 @@ debugger_la_SOURCES = \
 debugger_la_LIBADD = $(COMMONLIBS) $(VTE_LIBS) -lutil
 debugger_la_CFLAGS = $(AM_CFLAGS) $(VTE_CFLAGS) -DDBGPLUG_DATA_DIR=\"$(plugindatadir)\" -DPLUGIN_NAME=\"$(plugin)\"
 
+check_PROGRAMS = gdb_mi_test
+dist_check_SCRIPTS = tests/gdb_mi_test.sh
+dist_check_DATA = tests/gdb_mi_test.input tests/gdb_mi_test.expected
+TESTS = $(dist_check_SCRIPTS)
+
+gdb_mi_test_SOURCES = gdb_mi.c gdb_mi.h
+gdb_mi_test_CFLAGS = $(AM_CFLAGS) -DTEST
+gdb_mi_test_LDFLAGS =
+gdb_mi_test_LDADD = $(COMMONLIBS)
+
 include $(top_srcdir)/build/cppcheck.mk


Modified: debugger/src/breakpoint.c
4 lines changed, 2 insertions(+), 2 deletions(-)
===================================================================
@@ -50,10 +50,10 @@ breakpoint* break_new(void)
 breakpoint* break_new_full(const char* file, int line, const char* condition, int enabled, int hitscount)
 {
 	breakpoint* bp = break_new();
-	strcpy(bp->file, file);
+	strncpy(bp->file, file, G_N_ELEMENTS(bp->file) - 1);
 	bp->line = line;
 	if (condition)
-		strcpy(bp->condition, condition);
+		strncpy(bp->condition, condition, G_N_ELEMENTS(bp->condition) - 1);
 	bp->enabled = enabled;
 	bp->hitscount = hitscount;
 


Modified: debugger/src/breakpoints.c
4 lines changed, 2 insertions(+), 2 deletions(-)
===================================================================
@@ -227,7 +227,7 @@ static void breaks_set_condition_debug(breakpoint* bp)
 	{
 		/* revert to old condition (taken from tree) */
 		gchar* oldcondition = bptree_get_condition(bp);
-		strcpy(bp->condition, oldcondition);
+		strncpy(bp->condition, oldcondition, G_N_ELEMENTS(bp->condition) - 1);
 		g_free(oldcondition);
 		/* show error message */
 		dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", debug_error_message());
@@ -596,7 +596,7 @@ void breaks_set_condition(const char* file, int line, const char* condition)
 		return;
 	
 	/* change condition */
-	strcpy(bp->condition, condition);
+	strncpy(bp->condition, condition, G_N_ELEMENTS(bp->condition) - 1);
 	
 	/* handle setting condition instantly if debugger is idle or stopped
 	and request debug module interruption overwise */


Modified: debugger/src/dbm_gdb.c
950 lines changed, 370 insertions(+), 580 deletions(-)
===================================================================
@@ -38,6 +38,7 @@ extern GeanyData		*geany_data;
 
 #include "breakpoint.h"
 #include "debug_module.h"
+#include "gdb_mi.h"
 
 /* module features */
 #define MODULE_FEATURES MF_ASYNC_BREAKS
@@ -155,9 +156,8 @@ static void shutdown_channel(GIOChannel ** ch)
 {
 	if (*ch)
 	{
-		GError *err = NULL;
 		gint fd = g_io_channel_unix_get_fd(*ch);
-		g_io_channel_shutdown(*ch, TRUE, &err);
+		g_io_channel_shutdown(*ch, TRUE, NULL);
 		g_io_channel_unref(*ch);
 		*ch = NULL;
 		if (fd >= 0)
@@ -193,6 +193,7 @@ static void on_gdb_exit(GPid pid, gint status, gpointer data)
 	files = NULL;
 	
 	g_source_remove(gdb_src_id);
+	gdb_src_id = 0;
 	
 	dbg_cbs->set_exited(0);
 }
@@ -226,19 +227,20 @@ static void gdb_input_write_line(const gchar *line)
 	GIOStatus st;
 	GError *err = NULL;
 	gsize count;
-	
+	const char *p;
 	char command[1000];
-	sprintf(command, "%s\n", line);
+	g_snprintf(command, sizeof command, "%s\n", line);
 	
-	while (strlen(command))
+	for (p = command; *p; p += count)
 	{
-		st = g_io_channel_write_chars(gdb_ch_in, command, strlen(command), &count, &err);
-		strcpy(command, command + count);
+		st = g_io_channel_write_chars(gdb_ch_in, p, strlen(p), &count, &err);
 		if (err || (st == G_IO_STATUS_ERROR) || (st == G_IO_STATUS_EOF))
 		{
 #ifdef DEBUG_OUTPUT
 			dbg_cbs->send_message(err->message, "red");
 #endif
+			if (err)
+				g_clear_error(&err);
 			break;
 		}
 	}
@@ -249,6 +251,8 @@ static void gdb_input_write_line(const gchar *line)
 #ifdef DEBUG_OUTPUT
 		dbg_cbs->send_message(err->message, "red");
 #endif
+		if (err)
+			g_clear_error(&err);
 	}
 }
 
@@ -257,9 +261,11 @@ static void gdb_input_write_line(const gchar *line)
  */
 static void free_queue_item(queue_item *item)
 {
-	g_string_free(item->message, TRUE);
+	if (item->message)
+		g_string_free(item->message, TRUE);
 	g_string_free(item->command, TRUE);
-	g_string_free(item->error_message, TRUE);
+	if (item->error_message)
+		g_string_free(item->error_message, TRUE);
 	g_free(item);
 }
 
@@ -307,36 +313,31 @@ static gboolean on_read_async_output(GIOChannel * src, GIOCondition cond, gpoint
 {
 	gchar *line;
 	gsize length;
+	struct gdb_mi_record *record;
 	
 	if (G_IO_STATUS_NORMAL != g_io_channel_read_line(src, &line, NULL, &length, NULL))
 		return TRUE;		
 
-	*(line + length) = '\0';
+	record = gdb_mi_record_parse(line);
 
-	if ('^' == line[0])
+	if (record && record->type == '^')
 	{
 		/* got some result */
 
 		GList *lines;
 		GList *commands = (GList*)data;
-		gchar *coma;
 
-		g_source_remove(gdb_id_out);
+		if (gdb_id_out)
+		{
+			g_source_remove(gdb_id_out);
+			gdb_id_out = 0;
+		}
 
 		lines = read_until_prompt();
 		g_list_foreach(lines, (GFunc)g_free, NULL);
 		g_list_free (lines);
 
-		coma = strchr(line, ',');
-		if (coma)
-		{
-			*coma = '\0';
-			coma++;
-		}
-		else
-			coma = line + strlen(line);
-		
-		if (!strcmp(line, "^done"))
+		if (!strcmp(record->klass, "done"))
 		{
 			/* command completed succesfully - run next command if exists */
 			if (commands->next)
@@ -363,7 +364,11 @@ static gboolean on_read_async_output(GIOChannel * src, GIOCondition cond, gpoint
 				free_commands_queue(commands);
 
 				/* removing read callback */
-				g_source_remove(gdb_id_out);
+				if (gdb_id_out)
+				{
+					g_source_remove(gdb_id_out);
+					gdb_id_out = 0;
+				}
 
 				/* update source files list */
 				update_files();
@@ -379,13 +384,12 @@ static gboolean on_read_async_output(GIOChannel * src, GIOCondition cond, gpoint
 			{
 				if (item->format_error_message)
 				{
-					gchar* gdb_msg = g_strcompress(strstr(coma, "msg=\"") + strlen("msg=\""));
+					const gchar* gdb_msg = gdb_mi_result_var(record->first, "msg", GDB_MI_VAL_STRING);
 
 					GString *msg = g_string_new("");
 					g_string_printf(msg, item->error_message->str, gdb_msg);
 					dbg_cbs->report_error(msg->str);
 
-					g_free(gdb_msg);
 					g_string_free(msg, FALSE);
 				}
 				else
@@ -401,6 +405,7 @@ static gboolean on_read_async_output(GIOChannel * src, GIOCondition cond, gpoint
 		}
 	}
 
+	gdb_mi_record_free(record);
 	g_free(line);
 
 	return TRUE;
@@ -415,18 +420,18 @@ static gboolean on_read_from_gdb(GIOChannel * src, GIOCondition cond, gpointer d
 {
 	gchar *line;
 	gsize length;
-	gboolean prompt;
+	const gchar *id;
+	struct gdb_mi_record *record;
 	
 	if (G_IO_STATUS_NORMAL != g_io_channel_read_line(src, &line, NULL, &length, NULL))
 		return TRUE;		
 
-	prompt = !strcmp(line, GDB_PROMPT);
-	
-	*(line + length) = '\0';
+	record = gdb_mi_record_parse(line);
 
-	if (!prompt)
+	if (! record || record->type != GDB_MI_TYPE_PROMPT)
 	{
-		if ('~' == line[0])
+		line[length] = '\0';
+		if ((record && '~' == record->type) || '~' == line[0])
 		{
 			colorize_message(line);
 		}
@@ -437,161 +442,138 @@ static gboolean on_read_from_gdb(GIOChannel * src, GIOCondition cond, gpointer d
 			g_free(compressed);
 		}
 	}
-		
-	if (!target_pid && g_str_has_prefix(line, "=thread-group-created"))
+
+	if (! record)
 	{
-		*(strrchr(line, '\"')) = '\0';
-		target_pid = atoi(line + strlen("=thread-group-created,id=\""));
+		g_free(line);
+		return TRUE;
 	}
-	else if (!target_pid && g_str_has_prefix(line, "=thread-group-started"))
+
+	if (!target_pid &&
+		/* FIXME: =thread-group-created doesn't seem to exist, at least not in latest GDB docs */
+		(gdb_mi_record_matches(record, '=', "thread-group-created", "id", &id, NULL) ||
+		 gdb_mi_record_matches(record, '=', "thread-group-started", "pid", &id, NULL)))
 	{
-		*(strrchr(line, '\"')) = '\0';
-		target_pid = atoi(strrchr(line, '\"') + 1);
+		target_pid = atoi(id);
 	}
-	else if (g_str_has_prefix(line, "=thread-created"))
+	else if (gdb_mi_record_matches(record, '=', "thread-created", "id", &id, NULL))
 	{
-		int thread_id;
-
-		*(strrchr(line, ',') - 1) = '\0';
-		thread_id = atoi(line + strlen("=thread-created,id=\""));
-		dbg_cbs->add_thread(thread_id);
+		dbg_cbs->add_thread(atoi(id));
 	}
-	else if (g_str_has_prefix(line, "=thread-exited"))
+	else if (gdb_mi_record_matches(record, '=', "thread-exited", "id", &id, NULL))
 	{
-		int thread_id;
-
-		*(strrchr(line, ',') - 1) = '\0';
-		thread_id = atoi(line + strlen("=thread-exited,id=\""));
-		dbg_cbs->remove_thread(thread_id);
+		dbg_cbs->remove_thread(atoi(id));
 	}
-	else if (g_str_has_prefix(line, "=library-loaded") || g_str_has_prefix(line, "=library-unloaded"))
+	else if (gdb_mi_record_matches(record, '=', "library-loaded", NULL) ||
+			 gdb_mi_record_matches(record, '=', "library-unloaded", NULL))
 	{
 		file_refresh_needed = TRUE;
 	}
-	else if (*line == '*')
+	else if (gdb_mi_record_matches(record, '*', "running", NULL))
+		dbg_cbs->set_run();
+	else if (gdb_mi_record_matches(record, '*', "stopped", NULL))
 	{
-		/* asyncronous record found */
-		char *record = NULL;
-		if ( (record = strchr(line, ',')) )
+		const gchar *reason;
+
+		/* removing read callback (will pulling all output left manually) */
+		if (gdb_id_out)
 		{
-			*record = '\0';
-			record++;
+			g_source_remove(gdb_id_out);
+			gdb_id_out = 0;
+		}
+
+		/* looking for a reason to stop */
+		if ((reason = gdb_mi_result_var(record->first, "reason", GDB_MI_VAL_STRING)) != NULL)
+		{
+			if (!strcmp(reason, "breakpoint-hit"))
+				stop_reason = SR_BREAKPOINT_HIT;
+			else if (!strcmp(reason, "end-stepping-range"))
+				stop_reason = SR_END_STEPPING_RANGE;
+			else if (!strcmp(reason, "signal-received"))
+				stop_reason = SR_SIGNAL_RECIEVED;
+			else if (!strcmp(reason, "exited-normally"))
+				stop_reason = SR_EXITED_NORMALLY;
+			else if (!strcmp(reason, "exited-signalled"))
+				stop_reason = SR_EXITED_SIGNALLED;
+			else if (!strcmp(reason, "exited"))
+				stop_reason = SR_EXITED_WITH_CODE;
+			/* FIXME: handle "location-reached" */
 		}
 		else
-			record = line + strlen(line);
+		{
+			/* somehow, sometimes there can be no stop reason */
+			stop_reason = SR_EXITED_NORMALLY;
+		}
 		
-		if (!strcmp(line, "*running"))
-			dbg_cbs->set_run();
-		else if (!strcmp(line, "*stopped"))
+		if (SR_BREAKPOINT_HIT == stop_reason || SR_END_STEPPING_RANGE == stop_reason || SR_SIGNAL_RECIEVED == stop_reason)
 		{
-			char *reason;
+			const gchar *thread_id = gdb_mi_result_var(record->first, "thread-id", GDB_MI_VAL_STRING);
 
-			/* removing read callback (will pulling all output left manually) */
-			g_source_remove(gdb_id_out);
+			active_frame = 0;
 
-			/* looking for a reason to stop */
-			reason = strstr(record, "reason=\"");
-			if (reason)
+			if (SR_BREAKPOINT_HIT == stop_reason || SR_END_STEPPING_RANGE == stop_reason)
 			{
-				char *next;
-
-				reason += strlen("reason=\"");
-				next = strstr(reason, "\"") + 1;
-				*(next - 1) = '\0';
-				if (!strcmp(reason, "breakpoint-hit"))
-					stop_reason = SR_BREAKPOINT_HIT;
-				else if (!strcmp(reason, "end-stepping-range"))
-					stop_reason = SR_END_STEPPING_RANGE;
-				else if (!strcmp(reason, "signal-received"))
-					stop_reason = SR_SIGNAL_RECIEVED;
-				else if (!strcmp(reason, "exited-normally"))
-					stop_reason = SR_EXITED_NORMALLY;
-				else if (!strcmp(reason, "exited-signalled"))
-					stop_reason = SR_EXITED_SIGNALLED;
-				else if (!strcmp(reason, "exited"))
-					stop_reason = SR_EXITED_WITH_CODE;
-			}
-			else
-			{
-				/* somehow, sometimes there can be no stop reason */
-				stop_reason = SR_EXITED_NORMALLY;
-			}
-			
-			if (SR_BREAKPOINT_HIT == stop_reason || SR_END_STEPPING_RANGE == stop_reason || SR_SIGNAL_RECIEVED == stop_reason)
-			{
-				gchar *thread_id = strstr(reason + strlen(reason) + 1,"thread-id=\"") + strlen("thread-id=\"");
-				*(strchr(thread_id, '\"')) = '\0'; 
-				
-				active_frame = 0;
+				/* update autos */
+				update_autos();
 
-				if (SR_BREAKPOINT_HIT == stop_reason || SR_END_STEPPING_RANGE == stop_reason)
-				{
-					/* update autos */
-					update_autos();
-			
-					/* update watches */
-					update_watches();
-			
-					/* update files */
-					if (file_refresh_needed)
-					{
-						update_files();
-						file_refresh_needed = FALSE;
-					}
-
-					dbg_cbs->set_stopped(atoi(thread_id));
-				}
-				else
+				/* update watches */
+				update_watches();
+
+				/* update files */
+				if (file_refresh_needed)
 				{
-					if (!requested_interrupt)
-						dbg_cbs->report_error(_("Program received a signal"));
-					else
-						requested_interrupt = FALSE;
-						
-					dbg_cbs->set_stopped(atoi(thread_id));
+					update_files();
+					file_refresh_needed = FALSE;
 				}
 			}
-			else if (stop_reason == SR_EXITED_NORMALLY || stop_reason == SR_EXITED_SIGNALLED || stop_reason == SR_EXITED_WITH_CODE)
+			else
 			{
-				if (stop_reason == SR_EXITED_WITH_CODE)
-				{
-					gchar *code;
-					gchar *message;
+				if (!requested_interrupt)
+					dbg_cbs->report_error(_("Program received a signal"));
+				else
+					requested_interrupt = FALSE;
+			}
 
-					code = strstr(reason + strlen(reason) + 1,"exit-code=\"") + strlen("exit-code=\"");
-					*(strchr(code, '\"')) = '\0';
-					message = g_strdup_printf(_("Program exited with code \"%i\""), (int)(char)strtol(code, NULL, 8));
-					dbg_cbs->report_error(message);
+			dbg_cbs->set_stopped(thread_id ? atoi(thread_id) : 0);
+		}
+		else if (stop_reason == SR_EXITED_NORMALLY || stop_reason == SR_EXITED_SIGNALLED || stop_reason == SR_EXITED_WITH_CODE)
+		{
+			if (stop_reason == SR_EXITED_WITH_CODE)
+			{
+				const gchar *exit_code = gdb_mi_result_var(record->first, "exit-code", GDB_MI_VAL_STRING);
+				long int code = exit_code ? strtol(exit_code, NULL, 8) : 0;
+				gchar *message;
 
-					g_free(message);
-				}
+				message = g_strdup_printf(_("Program exited with code \"%i\""), (int)(char)code);
+				dbg_cbs->report_error(message);
 
-				stop();
+				g_free(message);
 			}
+
+			stop();
 		}
 	}
-	else if (g_str_has_prefix (line, "^error"))
+	else if (gdb_mi_record_matches(record, '^', "error", NULL))
 	{
 		GList *lines, *iter;
-		char *msg;
+		const gchar *msg = gdb_mi_result_var(record->first, "msg", GDB_MI_VAL_STRING);
 
 		/* removing read callback (will pulling all output left manually) */
-		g_source_remove(gdb_id_out);
+		if (gdb_id_out)
+		{
+			g_source_remove(gdb_id_out);
+			gdb_id_out = 0;
+		}
 
 		/* set debugger stopped if is running */
 		if (DBS_STOPPED != debug_get_state())
 		{
-			gchar *thread_id = strstr(line + strlen(line) + 1,"thread-id=\"");
-			*(strchr(thread_id, '\"')) = '\0'; 
+			/* FIXME: does GDB/MI ever return a thread-id with an error?? */
+			const gchar *thread_id = gdb_mi_result_var(record->first, "thread-id", GDB_MI_VAL_STRING);
 
-			dbg_cbs->set_stopped(atoi(thread_id));
+			dbg_cbs->set_stopped(thread_id ? atoi(thread_id) : 0);
 		}
 
-		/* get message */
-		msg = strstr(line, "msg=\"") + strlen("msg=\"");
-		*strrchr(msg, '\"') = '\0';
-		msg = g_strcompress(msg);
-		
 		/* reading until prompt */
 		lines = read_until_prompt();
 		for (iter = lines; iter; iter = iter->next)
@@ -605,11 +587,10 @@ static gboolean on_read_from_gdb(GIOChannel * src, GIOCondition cond, gpointer d
 
 		/* send error message */
 		dbg_cbs->report_error(msg);
-
-		g_free(msg);
 	}
 
 	g_free(line);
+	gdb_mi_record_free(record);
 
 	return TRUE;
 }
@@ -637,7 +618,7 @@ static void exec_async_command(const gchar* command)
  * i.e. reading output right
  * after execution
  */ 
-static result_class exec_sync_command(const gchar* command, gboolean wait4prompt, gchar** command_record)
+static result_class exec_sync_command(const gchar* command, gboolean wait4prompt, struct gdb_mi_record ** command_record)
 {
 	GList *lines, *iter;
 	result_class rc;
@@ -652,6 +633,9 @@ static result_class exec_sync_command(const gchar* command, gboolean wait4prompt
 	if (!wait4prompt)
 		return RC_DONE;
 	
+	if (command_record)
+		*command_record = NULL;
+
 	lines = read_until_prompt();
 
 #ifdef DEBUG_OUTPUT
@@ -666,42 +650,34 @@ static result_class exec_sync_command(const gchar* command, gboolean wait4prompt
 	for (iter = lines; iter; iter = iter->next)
 	{
 		gchar *line = (gchar*)iter->data;
+		struct gdb_mi_record *record = gdb_mi_record_parse(line);
 
-		if ('^' == line[0])
+		if (record && '^' == record->type)
 		{
-			gchar* coma = strchr(line, ',');
-			if (coma)
-			{
-				*coma = '\0';
-				coma++;
-			}
-			else
-				coma = line + strlen(line);
-			
-			if (command_record)
-			{
-				*command_record = (gchar*)g_malloc(strlen(coma) + 1);
-				strcpy(*command_record, coma);
-			}
-			
-			if (!strcmp(line, "^done"))
+			if (gdb_mi_record_matches(record, '^', "done", NULL))
 				rc = RC_DONE;
-			else if (!strcmp(line, "^error"))
+			else if (gdb_mi_record_matches(record, '^', "error", NULL))
 			{
 				/* save error message */
-				gchar* msg = g_strcompress(strstr(coma, "msg=\"") + strlen("msg=\""));
-				strcpy(err_message, msg);
-				g_free(msg);
+				const gchar *msg = gdb_mi_result_var(record->first, "msg", GDB_MI_VAL_STRING);
+				strncpy(err_message, msg ? msg : "", G_N_ELEMENTS(err_message) - 1);
 				
 				rc = RC_ERROR;
 			}
-			else if (!strcmp(line, "^exit"))
+			else if (gdb_mi_record_matches(record, '^', "exit", NULL))
 				rc = RC_EXIT;
+
+			if (command_record)
+			{
+				*command_record = record;
+				record = NULL;
+			}
 		}
-		else if ('&' != line[0])
+		else if (! record || '&' != record->type)
 		{
 			colorize_message (line);
 		}
+		gdb_mi_record_free(record);
 	}
 	
 	g_list_foreach(lines, (GFunc)g_free, NULL);
@@ -715,7 +691,6 @@ static result_class exec_sync_command(const gchar* command, gboolean wait4prompt
  */
 static gboolean run(const gchar* file, const gchar* commandline, GList* env, GList *witer, GList *biter, const gchar* terminal_device, dbg_callbacks* callbacks)
 {
-	GError *err = NULL;
 	const gchar *exclude[] = { "LANG", NULL };
 	gchar **gdb_env = utils_copy_environment(exclude, "LANG", "C", NULL);
 	gchar *working_directory = g_path_get_dirname(file);
@@ -730,7 +705,7 @@ static gboolean run(const gchar* file, const gchar* commandline, GList* env, GLi
 	/* spawn GDB */
 	if (!g_spawn_async_with_pipes(working_directory, (gchar**)gdb_args, gdb_env,
 				     GDB_SPAWN_FLAGS, NULL,
-				     NULL, &gdb_pid, &gdb_in, &gdb_out, NULL, &err))
+				     NULL, &gdb_pid, &gdb_in, &gdb_out, NULL, NULL))
 	{
 		dbg_cbs->report_error(_("Failed to spawn gdb process"));
 		g_free(working_directory);
@@ -944,7 +919,7 @@ static void step_out(void)
 static void execute_until(const gchar *file, int line)
 {
 	gchar command[1000];
-	sprintf(command, "-exec-until %s:%i", file, line);
+	g_snprintf(command, sizeof command, "-exec-until %s:%i", file, line);
 	exec_async_command(command);
 }
 
@@ -953,43 +928,41 @@ static void execute_until(const gchar *file, int line)
  */
 static int get_break_number(char* file, int line)
 {
-	gchar *record, *bstart;
+	struct gdb_mi_record *record;
+	const struct gdb_mi_result *table, *body, *bkpt;
 
 	exec_sync_command("-break-list", TRUE, &record);
-	bstart = record;
+	if (! record)
+		return -1;
 
-	while ( (bstart = strstr(bstart, "bkpt=")) )
+	table = gdb_mi_result_var(record->first, "BreakpointTable", GDB_MI_VAL_LIST);
+	body = gdb_mi_result_var(table, "body", GDB_MI_VAL_LIST);
+	gdb_mi_result_foreach_matched (bkpt, body, "bkpt", GDB_MI_VAL_LIST)
 	{
-		gchar *fname, *file_quoted;
-		int num, bline;
-		gboolean break_found;
+		const gchar *number = gdb_mi_result_var(bkpt->val->list, "number", GDB_MI_VAL_STRING);
+		const gchar *location = gdb_mi_result_var(bkpt->val->list, "original-location", GDB_MI_VAL_STRING);
+		const gchar *colon;
+		gboolean break_found = FALSE;
 
-		bstart += strlen("bkpt={number=\"");
-		*strchr(bstart, '\"') = '\0';
-		num = atoi(bstart);
-		
-		bstart += strlen(bstart) + 1;
-		bstart = strstr(bstart, "original-location=\"") + strlen("original-location=\"");
-		*strchr(bstart, ':') = '\0';
-		fname = bstart;
-		
-		bstart += strlen(bstart) + 1;
-		*strchr(bstart, '\"') = '\0';
-		bline = atoi(bstart);
+		if (! number || ! location)
+			continue;
 		
-		file_quoted = g_strdup_printf("\\\"%s\\\"", file);
-		break_found = !strcmp(fname, file_quoted) && bline == line;
-		g_free(file_quoted);
-
+		colon = strrchr(location, ':');
+		if (colon && atoi(colon + 1) == line)
+		{
+			gchar *fname = g_strndup(location, colon - location);
+			/* FIXME: the check used to be made against \"file\" (e.g. file surrounded
+			 * by backslash-quote), but that's not at least how GDB 7.7 does it */
+			break_found = strcmp(fname, file) == 0;
+			g_free(fname);
+		}
 		if (break_found)
 		{
-			return num;
+			return atoi(number);
 		}
-		
-		bstart += strlen(bstart) + 1;
-	} 
+	}
 	
-	free(record);
+	gdb_mi_record_free(record);
 	
 	return -1;
 }
@@ -1003,45 +976,46 @@ static gboolean set_break(breakpoint* bp, break_set_activity bsa)
 	if (BSA_NEW_BREAK == bsa)
 	{
 		/* new breakpoint */
-
-		char *pos;
-		int number;
-		gchar *record = NULL;
+		struct gdb_mi_record *record;
+		const struct gdb_mi_result *bkpt;
+		const gchar *number;
+		int num = 0;
 
 		/* 1. insert breakpoint */
-		sprintf (command, "-break-insert \"\\\"%s\\\":%i\"", bp->file, bp->line);
-		if (RC_DONE != exec_sync_command(command, TRUE, &record))
+		g_snprintf(command, sizeof command, "-break-insert \"\\\"%s\\\":%i\"", bp->file, bp->line);
+		if (RC_DONE != exec_sync_command(command, TRUE, &record) || !record)
 		{
-			g_free(record);
-			sprintf (command, "-break-insert -f \"\\\"%s\\\":%i\"", bp->file, bp->line);
-			if (RC_DONE != exec_sync_command(command, TRUE, &record))
+			gdb_mi_record_free(record);
+			record = NULL;
+			g_snprintf(command, sizeof command, "-break-insert -f \"\\\"%s\\\":%i\"", bp->file, bp->line);
+			if (RC_DONE != exec_sync_command(command, TRUE, &record) || !record)
 			{
-				g_free(record);
+				gdb_mi_record_free(record);
 				return FALSE;
 			}
 		}
 		/* lookup break-number */
-		pos = strstr(record, "number=\"") + strlen("number=\"");
-		*strchr(pos, '\"') = '\0';
-		number = atoi(pos);
-		g_free(record);
+		bkpt = gdb_mi_result_var(record->first, "bkpt", GDB_MI_VAL_LIST);
+		if ((number = gdb_mi_result_var(bkpt, "number", GDB_MI_VAL_STRING)))
+			num = atoi(number);
+		gdb_mi_record_free(record);
 		/* 2. set hits count if differs from 0 */
 		if (bp->hitscount)
 		{
-			sprintf (command, "-break-after %i %i", number, bp->hitscount);
+			g_snprintf(command, sizeof command, "-break-after %i %i", num, bp->hitscount);
 			exec_sync_command(command, TRUE, NULL);
 		}
 		/* 3. set condition if exists */
 		if (strlen(bp->condition))
 		{
-			sprintf (command, "-break-condition %i %s", number, bp->condition);
+			g_snprintf(command, sizeof command, "-break-condition %i %s", num, bp->condition);
 			if (RC_DONE != exec_sync_command(command, TRUE, NULL))
 				return FALSE;
 		}
 		/* 4. disable if disabled */
 		if (!bp->enabled)
 		{
-			sprintf (command, "-break-disable %i", number);
+			g_snprintf(command, sizeof command, "-break-disable %i", num);
 			exec_sync_command(command, TRUE, NULL);
 		}
 		
@@ -1055,11 +1029,11 @@ static gboolean set_break(breakpoint* bp, break_set_activity bsa)
 			return FALSE;
 
 		if (BSA_UPDATE_ENABLE == bsa)
-			sprintf (command, bp->enabled ? "-break-enable %i" : "-break-disable %i", bnumber);
+			g_snprintf(command, sizeof command, bp->enabled ? "-break-enable %i" : "-break-disable %i", bnumber);
 		else if (BSA_UPDATE_HITS_COUNT == bsa)
-			sprintf (command, "-break-after %i %i", bnumber, bp->hitscount);
+			g_snprintf(command, sizeof command, "-break-after %i %i", bnumber, bp->hitscount);
 		else if (BSA_UPDATE_CONDITION == bsa)
-			sprintf (command, "-break-condition %i %s", bnumber, bp->condition);
+			g_snprintf(command, sizeof command, "-break-condition %i %s", bnumber, bp->condition);
 
 		return RC_DONE == exec_sync_command(command, TRUE, NULL);
 	}
@@ -1079,7 +1053,7 @@ static gboolean remove_break(breakpoint* bp)
 		result_class rc;
 		gchar command[100];
 
-		sprintf(command, "-break-delete %i", number);
+		g_snprintf(command, sizeof command, "-break-delete %i", number);
 		rc = exec_sync_command(command, TRUE, NULL);
 		
 		return RC_DONE == rc;
@@ -1115,63 +1089,34 @@ static void set_active_frame(int frame_number)
  */
 static GList* get_stack(void)
 {
-	gchar* record = NULL;
+	struct gdb_mi_record *record = NULL;
+	const struct gdb_mi_result *stack_node, *frame_node;
 	GList *stack = NULL;
-	gchar **frames, **next;
-	result_class rc;
 
-	rc = exec_sync_command("-stack-list-frames", TRUE, &record);
-	if (RC_DONE != rc)
+	if (RC_DONE != exec_sync_command("-stack-list-frames", TRUE, &record) || ! record)
+	{
+		gdb_mi_record_free(record);
 		return NULL;
+	}
 
-	frames = g_strsplit(record, "frame=", 0);
-	next = frames + 1;
-	while (*next)
+	stack_node = gdb_mi_result_var(record->first, "stack", GDB_MI_VAL_LIST);
+	gdb_mi_result_foreach_matched (frame_node, stack_node, "frame", GDB_MI_VAL_LIST)
 	{
+		const gchar *addr = gdb_mi_result_var(frame_node->val->list, "addr", GDB_MI_VAL_STRING);
+		const gchar *func = gdb_mi_result_var(frame_node->val->list, "func", GDB_MI_VAL_STRING);
+		const gchar *line = gdb_mi_result_var(frame_node->val->list, "line", GDB_MI_VAL_STRING);
+		const gchar *file, *fullname;
 		frame *f = frame_new();
-		int line;
-		gchar *pos, *fullname, *file, *from;
-		
-		/* adresss */
-		pos = strstr(*next, "addr=\"") + strlen("addr=\"");
-		*strchr(pos, '\"') = '\0';
-		f->address = g_strdup(pos);
-		pos += strlen(pos) + 1;
-
-		/* function */
-		pos = strstr(pos, "func=\"") + strlen("func=\"");
-		*strchr(pos, '\"') = '\0';
-		f->function = g_strdup(pos);
-		pos += strlen(pos) + 1;
+
+		f->address = g_strdup(addr);
+		f->function = g_strdup(func);
 
 		/* file: fullname | file | from */
-		fullname = strstr(pos, "fullname=\"");
-		file = strstr(pos, "file=\"");
-		from = strstr(pos, "from=\"");
-		
-		if (fullname)
-		{
-			fullname += strlen("fullname=\"");
-			pos = fullname;
-			*strchr(pos, '\"') = '\0';
-			f->file = g_strdup(pos);
-			pos += strlen(pos) + 1;
-		}
-		else if (file)
+		if ((fullname = file = gdb_mi_result_var(frame_node->val->list, "fullname", GDB_MI_VAL_STRING)) ||
+			(file = gdb_mi_result_var(frame_node->val->list, "file", GDB_MI_VAL_STRING)) ||
+			(file = gdb_mi_result_var(frame_node->val->list, "from", GDB_MI_VAL_STRING)))
 		{
-			file += strlen("file=\"");
-			pos = file;
-			*strchr(pos, '\"') = '\0';
-			f->file = g_strdup(pos);
-			pos += strlen(pos) + 1;
-		}
-		else if (from)
-		{
-			from += strlen("from=\"");
-			pos = from;
-			*strchr(pos, '\"') = '\0';
-			f->file = g_strdup(pos);
-			pos += strlen(pos) + 1;
+			f->file = g_strdup(file);
 		}
 		else
 		{
@@ -1182,168 +1127,16 @@ static GList* get_stack(void)
 		f->have_source = fullname ? TRUE : FALSE;
 
 		/* line */
-		line = 0;
-		pos = strstr(pos, "line=\"");
-		if (pos)
-		{
-			pos += strlen("line=\"");
-			*strchr(pos, '\"') = '\0';
-			line = atoi(pos);
-			pos += strlen(pos) + 1;
-		}
-		f->line = line;
+		f->line = line ? atoi(line) : 0;
 
 		stack = g_list_append(stack, f);
-
-		next++;
 	}
-	g_strfreev(frames);	
-	
-	free(record);
+	gdb_mi_record_free(record);
 	
 	return stack;
 }
 
 /*
- * unescapes hex values (\0xXXX) to readable chars
- * converting it from wide character value to char
- */
-static gchar* unescape_hex_values(gchar *src)
-{
-	GString *dest = g_string_new("");
-	
-	gchar *slash;
-	while ( (slash = strstr(src, "\\x")) )
-	{
-		char hex[4] = { 0, 0, 0, '\0' };
-		wchar_t wc;
-
-		/* append what has been missed
-		unescaping it in advance */
-		if (slash - src)
-		{
-			gchar *missed = g_strndup(src, slash - src);
-			gchar *unescaped = g_strcompress(missed);
-			g_string_append(dest, unescaped);
-			g_free(missed);
-			g_free(unescaped);
-		}
-
-		strncpy(hex, slash + 2, 3);
-		wc = (wchar_t)strtol(hex, NULL, 16);
-
-		if (iswalpha(wc))
-		{
-			gchar mb[5];
-			int len = wctomb(mb, wc);
-			mb[len] = '\0';
-			g_string_append(dest, mb);
-		}
-		else
-			g_string_append_len(dest, slash, 5);
-		
-		src = slash + 5;
-	}
-	
-	if (strlen(src))
-	{
-		gchar *unescaped = g_strcompress(src);
-		g_string_append(dest, unescaped);
-		g_free(unescaped);
-	}
-
-	return g_string_free(dest, FALSE);
-}
-
-/*
- * checks if pc pointer points to the 
- * valid printable charater
- */
-static gboolean isvalidcharacter(gchar *pc, gboolean utf8)
-{
-	if (utf8)
-		return -1 != g_utf8_get_char_validated(pc, -1);
-	else
-		return isprint(*pc);
-}
-
-/*
- * unescapes string, handles octal characters representations
- */
-static gchar* unescape_octal_values(gchar *text)
-{
-	GString *value = g_string_new("");
-	
-	gboolean utf8 = g_str_has_suffix(getenv("LANG"), "UTF-8");
-
-	gchar *tmp = g_strdup(text);
-	gchar *unescaped = g_strcompress(tmp);
-
-	gchar *pos = unescaped;
-	while (*pos)
-	{
-		if (isvalidcharacter(pos, utf8))
-		{
-			if (utf8)
-			{
-				/* valid utf8 character, copy to output
-				 and move to the next character */
-				gchar *next = g_utf8_next_char(pos);
-				g_string_append_len(value, pos, next - pos);
-				pos = next;
-			}
-			else
-			{
-				g_string_append_len(value, pos++, 1);
-			}
-		}
-		else
-		{
-			/* not a valid character, convert it to its octal representation
-			 and append to the result string */
-			gchar *invalid = g_strndup(pos, 1);
-			gchar *escaped = g_strescape(invalid, NULL);
-
-			g_string_append(value, escaped);
-
-			g_free(escaped);
-			g_free(invalid);
-
-			pos += 1;
-		}
-	}
-
-	g_free(tmp);
-
-	return g_string_free (value, FALSE);
-}
-
-/*
- * unescapes value string, handles hexidecimal and octal characters representations
- */
-static gchar *unescape(gchar *text)
-{
-	gchar *retval = NULL;
-
-	/* create string copy */
-	gchar *value = g_strdup(text);
-
-	/* make first unescaping */
-	gchar *tmp = g_strcompress(value);
-
-	/* make first unescaping */
-	if (strstr(tmp, "\\x"))
-		retval = unescape_hex_values(tmp);
-	else
-		retval = unescape_octal_values(tmp);
-
-	g_free(tmp);
-	g_free(value);
-
-	return retval;
-}
-
-/*
  * updates variables from vars list 
  */
 static void get_variables (GList *vars)
@@ -1355,56 +1148,51 @@ static void get_variables (GList *vars)
 		variable *var = (variable*)vars->data;
 
 		gchar *varname = var->internal->str;
-		gchar *record = NULL;
-		gchar *pos;
-		gchar *expression;
-		int numchild;
-		gchar *value;
+		struct gdb_mi_record *record = NULL;
+		const gchar *expression = NULL;
+		const gchar *numchild = NULL;
+		const gchar *value = NULL;
+		const gchar *type = NULL;
 
 		/* path expression */
-		sprintf(command, "-var-info-path-expression \"%s\"", varname);
+		g_snprintf(command, sizeof command, "-var-info-path-expression \"%s\"", varname);
 		exec_sync_command(command, TRUE, &record);
-		pos = strstr(record, "path_expr=\"") + strlen("path_expr=\"");
-		*(strrchr(pos, '\"')) = '\0';
-		expression = unescape(pos);
-		g_string_assign(var->expression, expression);
-		g_free(expression);
-		g_free(record);
+		if (record)
+			expression = gdb_mi_result_var(record->first, "path_expr", GDB_MI_VAL_STRING);
+		g_string_assign(var->expression, expression ? expression : "");
+		gdb_mi_record_free(record);
 		
 		/* children number */
-		sprintf(command, "-var-info-num-children \"%s\"", varname);
+		g_snprintf(command, sizeof command, "-var-info-num-children \"%s\"", varname);
 		exec_sync_command(command, TRUE, &record);
-		pos = strstr(record, "numchild=\"") + strlen("numchild=\"");
-		*(strchr(pos, '\"')) = '\0';
-		numchild = atoi(pos);
-		var->has_children = numchild > 0;
-		g_free(record);
+		if (record)
+			numchild = gdb_mi_result_var(record->first, "numchild", GDB_MI_VAL_STRING);
+		var->has_children = numchild && atoi(numchild) > 0;
+		gdb_mi_record_free(record);
 
 		/* value */
-		sprintf(command, "-data-evaluate-expression \"%s\"", var->expression->str);
+		g_snprintf(command, sizeof command, "-data-evaluate-expression \"%s\"", var->expression->str);
 		exec_sync_command(command, TRUE, &record);
-		pos = strstr(record, "value=\"");
-		if (!pos)
+		if (record)
+			value = gdb_mi_result_var(record->first, "value", GDB_MI_VAL_STRING);
+		if (!value)
 		{
-			g_free(record);
-			sprintf(command, "-var-evaluate-expression \"%s\"", varname);
+			gdb_mi_record_free(record);
+			g_snprintf(command, sizeof command, "-var-evaluate-expression \"%s\"", varname);
 			exec_sync_command(command, TRUE, &record);
-			pos = strstr(record, "value=\"");
+			if (record)
+				value = gdb_mi_result_var(record->first, "value", GDB_MI_VAL_STRING);
 		}
-		pos +=  + strlen("value=\"");
-		*(strrchr(pos, '\"')) = '\0';
-		value = unescape(pos);
-		g_string_assign(var->value, value);
-		g_free(value);
-		g_free(record);
+		g_string_assign(var->value, value ? value : "");
+		gdb_mi_record_free(record);
 
 		/* type */
-		sprintf(command, "-var-info-type \"%s\"", varname);
+		g_snprintf(command, sizeof command, "-var-info-type \"%s\"", varname);
 		exec_sync_command(command, TRUE, &record);
-		pos = strstr(record, "type=\"") + strlen("type=\"");
-		*(strchr(pos, '\"')) = '\0';
-		g_string_assign(var->type, pos);
-		g_free(record);
+		if (record)
+			type = gdb_mi_result_var(record->first, "type", GDB_MI_VAL_STRING);
+		g_string_assign(var->type, type ? type : "");
+		gdb_mi_record_free(record);
 
 		vars = vars->next;
 	}
@@ -1415,9 +1203,9 @@ static void get_variables (GList *vars)
  */
 static void update_files(void)
 {
-	GHashTable *ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
-	gchar *record = NULL;
-	gchar *pos;
+	GHashTable *ht;
+	struct gdb_mi_record *record = NULL;
+	const struct gdb_mi_result *files_node;
 
 	if (files)
 	{
@@ -1428,22 +1216,25 @@ static void update_files(void)
 	}
 
 	exec_sync_command("-file-list-exec-source-files", TRUE, &record);
-	pos = record;
-	while ( (pos = strstr(pos, "fullname=\"")) )
+	if (! record)
+		return;
+
+	ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+	files_node = gdb_mi_result_var(record->first, "files", GDB_MI_VAL_LIST);
+	gdb_mi_result_foreach_matched (files_node, files_node, NULL, GDB_MI_VAL_LIST)
 	{
-		pos += strlen("fullname=\"");
-		*(strchr(pos, '\"')) = '\0';
-		if (!g_hash_table_lookup(ht, pos))
+		const gchar *fullname = gdb_mi_result_var(files_node->val->list, "fullname", GDB_MI_VAL_STRING);
+
+		if (fullname && !g_hash_table_lookup(ht, fullname))
 		{
-			g_hash_table_insert(ht, (gpointer)pos, (gpointer)1);
-			files = g_list_append(files, g_strdup(pos));
+			g_hash_table_insert(ht, (gpointer)fullname, (gpointer)1);
+			files = g_list_append(files, g_strdup(fullname));
 		}
-			
-		pos += strlen(pos) + 1;
 	}
 
 	g_hash_table_destroy(ht);
-	g_free(record);
+	gdb_mi_record_free(record);
 }
 
 /*
@@ -1462,7 +1253,7 @@ static void update_watches(void)
 		
 		if (var->internal->len)
 		{
-			sprintf(command, "-var-delete %s", var->internal->str);
+			g_snprintf(command, sizeof command, "-var-delete %s", var->internal->str);
 			exec_sync_command(command, TRUE, NULL);
 		}
 		
@@ -1475,32 +1266,31 @@ static void update_watches(void)
 	for (iter = watches; iter; iter = iter->next)
 	{
 		variable *var = (variable*)iter->data;
-		gchar *record = NULL;
-		gchar *pos;
+		struct gdb_mi_record *record = NULL;
+		const gchar *name;
 		gchar *escaped;
 
 		/* try to create variable */
 		escaped = g_strescape(var->name->str, NULL);
-		sprintf(command, "-var-create - * \"%s\"", escaped);
+		g_snprintf(command, sizeof command, "-var-create - * \"%s\"", escaped);
 		g_free(escaped);
 
-		if (RC_DONE != exec_sync_command(command, TRUE, &record))
+		if (RC_DONE != exec_sync_command(command, TRUE, &record) || !record)
 		{
 			/* do not include to updating list, move to next watch */
 			var->evaluated = FALSE;
 			g_string_assign(var->internal, "");
-			g_free(record);
+			gdb_mi_record_free(record);
 			
 			continue;
 		}
 		
 		/* find and assign internal name */
-		pos = strstr(record, "name=\"") + strlen("name=\"");
-		*strchr(pos, '\"') = '\0'; 
-		g_string_assign(var->internal, pos);
-		g_free(record);			
+		name = gdb_mi_result_var(record->first, "name", GDB_MI_VAL_STRING);
+		g_string_assign(var->internal, name ? name : "");
+		gdb_mi_record_free(record);
 		
-		var->evaluated = TRUE;
+		var->evaluated = name != NULL;
 
 		/* add to updating list */
 		updating = g_list_append(updating, var);
@@ -1519,16 +1309,14 @@ static void update_watches(void)
 static void update_autos(void)
 {
 	gchar command[1000];
-	GList *unevaluated = NULL, *iter;
-	const char *gdb_commands[2];
-	int i;
+	GList *unevaluated = NULL, *vars = NULL, *iter;
 
 	/* remove all previous GDB variables for autos */
 	for (iter = autos; iter; iter = iter->next)
 	{
 		variable *var = (variable*)iter->data;
 		
-		sprintf(command, "-var-delete %s", var->internal->str);
+		g_snprintf(command, sizeof command, "-var-delete %s", var->internal->str);
 		exec_sync_command(command, TRUE, NULL);
 	}
 
@@ -1538,55 +1326,67 @@ static void update_autos(void)
 	
 	/* add current autos to the list */
 	
-	gdb_commands[0] = g_strdup_printf("-stack-list-arguments 0 %i %i", active_frame, active_frame);
-	gdb_commands[1] = "-stack-list-locals 0";
-	for (i = 0; i < sizeof (gdb_commands) / sizeof(*gdb_commands); i++)
-	{
-		gchar *record = NULL, *pos;
+	struct gdb_mi_record *record = NULL;
 
-		result_class rc = exec_sync_command(gdb_commands[i], TRUE, &record);
-		if (RC_DONE != rc)
-			break;
+	g_snprintf(command, sizeof command, "-stack-list-arguments 0 %i %i", active_frame, active_frame);
+	if (RC_DONE == exec_sync_command(command, TRUE, &record) && record)
+	{
+		const struct gdb_mi_result *stack_args = gdb_mi_result_var(record->first, "stack-args", GDB_MI_VAL_LIST);
 
-		pos = record;
-		while ((pos = strstr(pos, "name=\"")))
+		gdb_mi_result_foreach_matched (stack_args, stack_args, "frame", GDB_MI_VAL_LIST)
 		{
-			variable *var;
-			gchar *create_record = NULL, *escaped;
+			const struct gdb_mi_result *args = gdb_mi_result_var(stack_args->val->list, "args", GDB_MI_VAL_LIST);
 
-			pos += strlen("name=\"");
-			*(strchr(pos, '\"')) = '\0';
+			gdb_mi_result_foreach_matched (args, args, "name", GDB_MI_VAL_STRING)
+			{
+				variable *var = variable_new(args->val->string, VT_ARGUMENT);
+				vars = g_list_append(vars, var);
+			}
+		}
+	}
+	gdb_mi_record_free(record);
 
-			var = variable_new(pos, i ? VT_LOCAL : VT_ARGUMENT);
+	if (RC_DONE == exec_sync_command("-stack-list-locals 0", TRUE, &record) && record)
+	{
+		const struct gdb_mi_result *locals = gdb_mi_result_var(record->first, "locals", GDB_MI_VAL_LIST);
 
-			/* create new gdb variable */
-			escaped = g_strescape(pos, NULL);
-			sprintf(command, "-var-create - * \"%s\"", escaped);
-			g_free(escaped);
+		gdb_mi_result_foreach_matched (locals, locals, "name", GDB_MI_VAL_STRING)
+		{
+			variable *var = variable_new(locals->val->string, VT_LOCAL);
+			vars = g_list_append(vars, var);
+		}
+	}
+	gdb_mi_record_free(record);
 
-			/* form new variable */
-			if (RC_DONE == exec_sync_command(command, TRUE, &create_record))
-			{
-				gchar *intname = strstr(create_record, "name=\"") + strlen ("name=\"");
-				*strchr(intname, '\"') = '\0';
-				var->evaluated = TRUE;
-				g_string_assign(var->internal, intname);
-				autos = g_list_append(autos, var);
+	for (iter = vars; iter; iter = iter->next)
+	{
+		variable *var = iter->data;
+		struct gdb_mi_record *create_record = NULL;
+		gchar *escaped;
+		const gchar *intname;
 
-				g_free(create_record);
-			}
-			else
-			{
-				var->evaluated = FALSE;
-				g_string_assign(var->internal, "");
-				unevaluated = g_list_append(unevaluated, var);
-			}
-			
-			pos += strlen(pos) + 1;
+		/* create new gdb variable */
+		escaped = g_strescape(var->name->str, NULL);
+		g_snprintf(command, sizeof command, "-var-create - * \"%s\"", escaped);
+		g_free(escaped);
+
+		/* form new variable */
+		if (RC_DONE == exec_sync_command(command, TRUE, &create_record) && create_record &&
+			(intname = gdb_mi_result_var(create_record->first, "name", GDB_MI_VAL_STRING)))
+		{
+			var->evaluated = TRUE;
+			g_string_assign(var->internal, intname);
+			autos = g_list_append(autos, var);
 		}
-		g_free(record);
+		else
+		{
+			var->evaluated = FALSE;
+			g_string_assign(var->internal, "");
+			unevaluated = g_list_append(unevaluated, var);
+		}
+		gdb_mi_record_free(create_record);
 	}
-	g_free((void*)gdb_commands[0]);
+	g_list_free(vars);
 	
 	/* get values for the autos (without incorrect variables) */
 	get_variables(autos);
@@ -1628,56 +1428,47 @@ static GList* get_children (gchar* path)
 	
 	gchar command[1000];
 	result_class rc;
-	gchar *record = NULL;
-	gchar *pos = NULL;
-	int numchild;
+	struct gdb_mi_record *record = NULL;
+	const gchar *numchild;
+	int n;
 
 	/* children number */
-	sprintf(command, "-var-info-num-children \"%s\"", path);
+	g_snprintf(command, sizeof command, "-var-info-num-children \"%s\"", path);
 	rc = exec_sync_command(command, TRUE, &record);
-	if (RC_DONE != rc)
+	if (RC_DONE != rc || ! record)
+	{
+		gdb_mi_record_free(record);
 		return NULL;
-	pos = strstr(record, "numchild=\"") + strlen("numchild=\"");
-	*(strchr(pos, '\"')) = '\0';
-	numchild = atoi(pos);
-	g_free(record);
-	if (!numchild)
+	}
+	numchild = gdb_mi_result_var(record->first, "numchild", GDB_MI_VAL_STRING);
+	n = numchild ? atoi(numchild) : 0;
+	gdb_mi_record_free(record);
+	if (!n)
 		return NULL;
 	
 	/* recursive get children and put into list */
-	sprintf(command, "-var-list-children \"%s\"", path);
+	g_snprintf(command, sizeof command, "-var-list-children \"%s\"", path);
 	rc = exec_sync_command(command, TRUE, &record);
-	if (RC_DONE == rc)
+	if (RC_DONE == rc && record)
 	{
-		pos = record;
-		while ( (pos = strstr(pos, "child={")) )
+		const struct gdb_mi_result *child_node = gdb_mi_result_var(record->first, "children", GDB_MI_VAL_LIST);
+
+		gdb_mi_result_foreach_matched (child_node, child_node, "child", GDB_MI_VAL_LIST)
 		{
-			gchar *name, *internal;
+			const gchar *internal = gdb_mi_result_var(child_node->val->list, "name", GDB_MI_VAL_STRING);
+			const gchar *name = gdb_mi_result_var(child_node->val->list, "exp", GDB_MI_VAL_STRING);
 			variable *var;
-			
-			/* name */
-			pos = strstr(pos, "name=\"") + strlen("name=\"");
-			*(strstr(pos, "\",exp=\"")) = '\0';
-			internal = pos;
-			pos += strlen(pos) + 1;
-
-			/* exp */
-			pos = strstr(pos, "exp=\"") + strlen("exp=\"");
-			*(strstr(pos, "\",numchild=\"")) = '\0';
-			
-			name = g_strcompress(pos);
-			
+
+			if (! name || ! internal)
+				continue;
+
 			var = variable_new2(name, internal, VT_CHILD);
 			var->evaluated = TRUE;
-			
-			pos += strlen(pos) + 1;
 
 			children = g_list_append(children, var);
-		
-			g_free(name);
 		}
 	}
-	g_free(record);
+	gdb_mi_record_free(record);
 	
 	get_variables(children);
 
@@ -1690,7 +1481,9 @@ static GList* get_children (gchar* path)
 static variable* add_watch(gchar* expression)
 {
 	gchar command[1000];
-	gchar *record = NULL, *escaped, *pos;
+	gchar *escaped;
+	struct gdb_mi_record *record = NULL;
+	const gchar *name;
 	GList *vars = NULL;
 	variable *var = variable_new(expression, VT_WATCH);
 
@@ -1698,24 +1491,23 @@ static variable* add_watch(gchar* expression)
 
 	/* try to create a variable */
 	escaped = g_strescape(expression, NULL);
-	sprintf(command, "-var-create - * \"%s\"", escaped);
+	g_snprintf(command, sizeof command, "-var-create - * \"%s\"", escaped);
 	g_free(escaped);
 
-	if (RC_DONE != exec_sync_command(command, TRUE, &record))
+	if (RC_DONE != exec_sync_command(command, TRUE, &record) || !record)
 	{
-		g_free(record);
+		gdb_mi_record_free(record);
 		return var;
 	}
 	
-	pos = strstr(record, "name=\"") + strlen("name=\"");
-	*strchr(pos, '\"') = '\0'; 
-	g_string_assign(var->internal, pos);
-	var->evaluated = TRUE;
+	name = gdb_mi_result_var(record->first, "name", GDB_MI_VAL_STRING);
+	g_string_assign(var->internal, name ? name : "");
+	var->evaluated = name != NULL;
 
 	vars = g_list_append(NULL, var);
 	get_variables(vars);
 
-	g_free(record);
+	gdb_mi_record_free(record);
 	g_list_free(vars);
 
 	return var;	
@@ -1733,7 +1525,7 @@ static void remove_watch(gchar* internal)
 		if (!strcmp(var->internal->str, internal))
 		{
 			gchar command[1000];
-			sprintf(command, "-var-delete %s", internal);
+			g_snprintf(command, sizeof command, "-var-delete %s", internal);
 			exec_sync_command(command, TRUE, NULL);
 			variable_free(var);
 			watches = g_list_delete_link(watches, iter);
@@ -1747,23 +1539,21 @@ static void remove_watch(gchar* internal)
  */
 static gchar *evaluate_expression(gchar *expression)
 {
-	gchar *record = NULL, *pos;
+	struct gdb_mi_record *record = NULL;
+	gchar *value;
 	char command[1000];
-	result_class rc;
 
-	sprintf (command, "-data-evaluate-expression \"%s\"", expression);
-	rc = exec_sync_command(command, TRUE, &record);
-	
-	if (RC_DONE != rc)
+	g_snprintf(command, sizeof command, "-data-evaluate-expression \"%s\"", expression);
+	if (RC_DONE != exec_sync_command(command, TRUE, &record) || ! record)
 	{
-		g_free(record);
+		gdb_mi_record_free(record);
 		return NULL;
 	}
 
-	pos = strstr(record, "value=\"") + strlen("value=\"");
-	*(strrchr(pos, '\"')) = '\0';
+	value = g_strdup(gdb_mi_result_var(record->first, "value", GDB_MI_VAL_STRING));
+	gdb_mi_record_free(record);
 
-	return unescape(pos);
+	return value;
 }
 
 /*
@@ -1773,7 +1563,7 @@ static gboolean request_interrupt(void)
 {
 #ifdef DEBUG_OUTPUT
 	char msg[1000];
-	sprintf(msg, "interrupting pid=%i", target_pid);
+	g_snprintf(msg, sizeof msg, "interrupting pid=%i", target_pid);
 	dbg_cbs->send_message(msg, "red");
 #endif
 	


Modified: debugger/src/debug.c
53 lines changed, 30 insertions(+), 23 deletions(-)
===================================================================
@@ -672,7 +672,8 @@ static void on_debugger_stopped (int thread_id)
 	}
 
 	/* clear calltips cache */
-	g_hash_table_remove_all(calltips);
+	if (calltips)
+		g_hash_table_remove_all(calltips);
 
 	/* if a stop was requested for asyncronous exiting -
 	 * stop debug module and exit */
@@ -841,8 +842,11 @@ static void on_debugger_exited (int code)
 	read_only_pages = NULL;
 
 	/* clear and destroy calltips cache */
-	g_hash_table_destroy(calltips);
-	calltips = NULL;
+	if (calltips)
+	{
+		g_hash_table_destroy(calltips);
+		calltips = NULL;
+	}
 
 	/* enable widgets */
 	enable_sensitive_widgets(TRUE);
@@ -937,7 +941,8 @@ static void on_select_frame(int frame_number)
 	active_module->set_active_frame(frame_number);
 	
 	/* clear calltips cache */
-	g_hash_table_remove_all(calltips);
+	if (calltips)
+		g_hash_table_remove_all(calltips);
 	
 	/* autos */
 	autos = active_module->get_autos();
@@ -1336,33 +1341,35 @@ gchar* debug_get_calltip_for_expression(gchar* expression)
 		if (var)
 		{
 			calltip_str = get_calltip_line(var, TRUE);
-			if (var->has_children)
+			if (calltip_str)
 			{
-				int lines_left = MAX_CALLTIP_HEIGHT - 1;
-				GList* children = active_module->get_children(var->internal->str); 
-				GList* child = children;
-				while(child && lines_left)
+				if (var->has_children)
 				{
-					variable *varchild = (variable*)child->data;
-					GString *child_string = get_calltip_line(varchild, FALSE);
-					g_string_append_printf(calltip_str, "\n%s", child_string->str);
-					g_string_free(child_string, TRUE);
+					int lines_left = MAX_CALLTIP_HEIGHT - 1;
+					GList* children = active_module->get_children(var->internal->str);
+					GList* child = children;
+					while(child && lines_left)
+					{
+						variable *varchild = (variable*)child->data;
+						GString *child_string = get_calltip_line(varchild, FALSE);
+						g_string_append_printf(calltip_str, "\n%s", child_string->str);
+						g_string_free(child_string, TRUE);
 
-					child = child->next;
-					lines_left--;
-				}
-				if (!lines_left && child)
-				{
-					g_string_append(calltip_str, "\n\t\t........");
+						child = child->next;
+						lines_left--;
+					}
+					if (!lines_left && child)
+					{
+						g_string_append(calltip_str, "\n\t\t........");
+					}
+					g_list_foreach(children, (GFunc)variable_free, NULL);
+					g_list_free(children);
 				}
-				g_list_foreach(children, (GFunc)variable_free, NULL);
-				g_list_free(children);
+				calltip = g_string_free(calltip_str, FALSE);
 			}
 
 			active_module->remove_watch(var->internal->str);
 
-			calltip = g_string_free(calltip_str, FALSE);
-
 			if (!calltips)
 			{
 				calltips = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free);


Modified: debugger/src/debug_module.c
4 lines changed, 2 insertions(+), 2 deletions(-)
===================================================================
@@ -32,7 +32,7 @@
 #include "debug_module.h"
 
 /* creates new variable */
-variable *variable_new(gchar *name, variable_type vt)
+variable *variable_new(const gchar *name, variable_type vt)
 {
 	variable *var = g_malloc(sizeof(variable));
 	var->name = g_string_new(name);
@@ -47,7 +47,7 @@ variable *variable_new(gchar *name, variable_type vt)
 }
 
 /* creates new variable with internal name */
-variable *variable_new2(gchar *name, gchar *internal, variable_type vt)
+variable *variable_new2(const gchar *name, const gchar *internal, variable_type vt)
 {
 	variable *var = variable_new(name, vt);
 	g_string_assign(var->internal, internal);


Modified: debugger/src/debug_module.h
4 lines changed, 2 insertions(+), 2 deletions(-)
===================================================================
@@ -166,8 +166,8 @@ typedef struct _dbg_module {
 	MODULE_FEATURES }
 
 void		variable_free(variable *var);
-variable*	variable_new(gchar *name, variable_type vt);
-variable*	variable_new2(gchar *name, gchar *internal, variable_type vt);
+variable*	variable_new(const gchar *name, variable_type vt);
+variable*	variable_new2(const gchar *name, const gchar *internal, variable_type vt);
 void		variable_reset(variable *var);
 
 frame*	frame_new(void);


Modified: debugger/src/dpaned.c
7 lines changed, 5 insertions(+), 2 deletions(-)
===================================================================
@@ -154,10 +154,10 @@ static void on_page_reordered(GtkNotebook *notebook, GtkWidget *child, guint pag
 	gboolean is_tabbed = config_get_tabbed();
 	int *tabs = NULL;
 	gsize length;
-	int prev_index;
+	gsize prev_index, min, max;
 	GtkWidget *page;
 	tab_id id;
-	int i, min, max;
+	int i;
 	int config_part_tabs;
 	int config_part_selected_index;
 	int *array;
@@ -351,6 +351,7 @@ void dpaned_init(void)
 			gtk_notebook_set_tab_detachable(GTK_NOTEBOOK(debug_notebook_left), tab, TRUE);
 			gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(debug_notebook_left), tab, TRUE);
 		}
+		g_free(tab_ids);
 
 		gtk_widget_show_all(hpaned);
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(debug_notebook_left), config_get_selected_tab_index());
@@ -406,6 +407,7 @@ void dpaned_set_tabbed(gboolean tabbed)
 				gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(debug_notebook_left), tab, TRUE);
 			}
 		}
+		g_free(tab_ids);
 
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(debug_notebook_left), config_get_selected_tab_index());
 
@@ -431,6 +433,7 @@ void dpaned_set_tabbed(gboolean tabbed)
 			gtk_notebook_set_tab_detachable(GTK_NOTEBOOK(debug_notebook_right), tab, TRUE);
 				gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(debug_notebook_right), tab, TRUE);
 		}
+		g_free(tab_ids);
 
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(debug_notebook_left), config_get_left_selected_tab_index());
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(debug_notebook_right), config_get_right_selected_tab_index());


Modified: debugger/src/gdb_mi.c
496 lines changed, 496 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,496 @@
+/*
+ *      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
+ * https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI-Output-Syntax.html
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "gdb_mi.h"
+
+
+#define ascii_isodigit(c) (((guchar) (c)) >= '0' && ((guchar) (c)) <= '7')
+
+
+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);
+}
+
+/* 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 --
+ *        although it parses everything I encountered
+ * FIXME: this does NOT convert to UTF-8.  should it? */
+static gchar *parse_cstring(const gchar **p)
+{
+	GString *str = g_string_new(NULL);
+
+	if (**p == '"')
+	{
+		(*p)++;
+		while (**p != '"')
+		{
+			gchar c = **p;
+			/* TODO: check expansions here */
+			if (c == '\\')
+			{
+				(*p)++;
+				c = **p;
+				switch (g_ascii_tolower(c))
+				{
+					case '\\':
+					case '"': break;
+					case 'a': c = '\a'; break;
+					case 'b': c = '\b'; break;
+					case 'f': c = '\f'; break;
+					case 'n': c = '\n'; break;
+					case 'r': c = '\r'; break;
+					case 't': c = '\t'; break;
+					case 'v': c = '\v'; break;
+					default:
+						/* hex escape, 1-2 digits (\xN or \xNN)
+						 * 
+						 * FIXME: is this useful?  Is this right?
+						 * the original dbm_gdb.c:unescape_hex_values() used to
+						 * read escapes of the form \xNNN and treat them as wide
+						 * characters numbers, but  this looks weird for a C-like
+						 * escape.
+						 * Also, note that this doesn't seem to be referenced anywhere
+						 * in GDB/MI syntax.  Only reference in GDB manual is about
+						 * keybindings, which use the syntax implemented here */
+						if (g_ascii_tolower(**p) == 'x' && g_ascii_isxdigit((*p)[1]))
+						{
+							c = (gchar) g_ascii_xdigit_value(*++(*p));
+							if (g_ascii_isxdigit((*p)[1]))
+								c = (gchar) ((c * 16) + g_ascii_xdigit_value(*++(*p)));
+						}
+						/* octal escape, 1-3 digits (\N, \NN or \NNN) */
+						else if (ascii_isodigit(**p))
+						{
+							int i, v;
+							v = g_ascii_digit_value(**p);
+							for (i = 0; ascii_isodigit((*p)[1]) && i < 2; i++)
+								v = (v * 8) + g_ascii_digit_value(*++(*p));
+							if (v <= 0xff)
+								c = (gchar) v;
+							else
+							{
+								*p = *p - 3; /* put the whole sequence back */
+								c = **p;
+								g_warning("Octal escape sequence out of range: %.4s", *p);
+							}
+						}
+						else
+						{
+							g_warning("Unkown escape \"\\%c\"", **p);
+							(*p)--; /* put the \ back */
+							c = **p;
+						}
+						break;
+				}
+			}
+			if (**p == '\0')
+				break;
+			g_string_append_c(str, 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 (g_ascii_isalpha(**p) || strchr("-_.", **p))
+	{
+		g_string_append_c(str, **p);
+		for ((*p)++; g_ascii_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 (g_ascii_isspace(**p)) (*p)++;
+	if (**p == '=')
+	{
+		(*p)++;
+		while (g_ascii_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 (g_ascii_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 (g_ascii_isspace(**p)) (*p)++;
+			if (**p != ',') break;
+			(*p)++;
+		}
+		if (**p == end)
+			(*p)++;
+	}
+	else
+	{
+		gdb_mi_value_free(val);
+		val = NULL;
+	}
+	return val;
+}
+
+static gboolean is_prompt(const gchar *p)
+{
+	if (strncmp("(gdb)", p, 5) == 0)
+	{
+		p += 5;
+		while (g_ascii_isspace(*p)) p++;
+	}
+	return *p == 0;
+}
+
+/* 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);
+
+	/* FIXME: prompt detection should not really be useful, especially not as a
+	 * special case, as the prompt should always follow an (optional) record */
+	if (is_prompt(line))
+		record->type = GDB_MI_TYPE_PROMPT;
+	else
+	{
+		/* extract token */
+		const gchar *token_end = line;
+		for (token_end = line; g_ascii_isdigit(*token_end); token_end++)
+			;
+		if (token_end > line)
+		{
+			record->token = g_strndup(line, (gsize)(token_end - line));
+			line = token_end;
+			while (g_ascii_isspace(*line)) line++;
+		}
+
+		/* extract record */
+		record->type = *line;
+		if (*line) ++line;
+		while (g_ascii_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 (g_ascii_isspace(*line)) line++;
+					if (*line != ',')
+						break;
+					else
+					{
+						struct gdb_mi_result *res = g_malloc0(sizeof *res);
+						line++;
+						while (g_ascii_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;
+		}
+	}
+
+	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;
+}
+
+/* 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
+
+static void gdb_mi_result_dump(const struct gdb_mi_result *r, gboolean next, gint indent);
+
+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);
+}
+
+int main(void)
+{
+	char buf[1024] = {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_) ? (name_) : "") != 0)) || \
+			((type_) >= 0 && (node_)->val->type != (type_))) \
+			continue; \
+		else
+
+#endif /* guard */


Modified: debugger/src/markers.c
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -98,7 +98,7 @@ void markers_set_for_document(ScintillaObject *sci)
 void markers_init(void)
 {
 	/* set markers in all currently opened documents */
-	int i;
+	guint i;
 	foreach_document(i)
 		markers_set_for_document(document_index(i)->editor->sci);
 }


Modified: debugger/src/plugin.c
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -89,7 +89,7 @@ static void on_paned_mode_changed(GtkToggleButton *button, gpointer user_data)
 void plugin_init(GeanyData *data)
 {
 	GtkWidget* vbox;
-	int i;
+	guint i;
 
 	plugin_module_make_resident(geany_plugin);
 


Modified: debugger/src/tests/gdb_mi_test.expected
338 lines changed, 338 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,338 @@
+# (gdb)
+record =>
+  type = '0' (0)
+  token = (null)
+  class = (null)
+  results =>
+# (gdb) 
+record =>
+  type = '0' (0)
+  token = (null)
+  class = (null)
+  results =>
+# (gdb)   
+record =>
+  type = '0' (0)
+  token = (null)
+  class = (null)
+  results =>
+# ^error,msg="failed"
+record =>
+  type = '^' (94)
+  token = (null)
+  class = error
+  results =>
+    var = msg
+    val =>
+      type = 0
+      string = failed
+# *stopped,reason="signal-received",signal-name="SIGINT",signal-meaning="Interrupt",frame={addr="0x00000000deadbeef",func="somefunc",args=[],file="somefile.c",fullname="/some/path/somefile.c",line="123"},thread-id="1",stopped-threads="all",core="1"
+record =>
+  type = '*' (42)
+  token = (null)
+  class = stopped
+  results =>
+    var = reason
+    val =>
+      type = 0
+      string = signal-received
+    var = signal-name
+    val =>
+      type = 0
+      string = SIGINT
+    var = signal-meaning
+    val =>
+      type = 0
+      string = Interrupt
+    var = frame
+    val =>
+      type = 1
+      list =>
+        var = addr
+        val =>
+          type = 0
+          string = 0x00000000deadbeef
+        var = func
+        val =>
+          type = 0
+          string = somefunc
+        var = args
+        val =>
+          type = 1
+          list =>
+        var = file
+        val =>
+          type = 0
+          string = somefile.c
+        var = fullname
+        val =>
+          type = 0
+          string = /some/path/somefile.c
+        var = line
+        val =>
+          type = 0
+          string = 123
+    var = thread-id
+    val =>
+      type = 0
+      string = 1
+    var = stopped-threads
+    val =>
+      type = 0
+      string = all
+    var = core
+    val =>
+      type = 0
+      string = 1
+# *stopped,reason="breakpoint-hit",disp="keep",bkptno="1",frame={addr="0x0000000000400c55",func="gdb_mi_record_dump",args=[{name="record",value="0x603010"}],file="gdb_mi.c",fullname="/geany-plugins/debugger/src/gdb_mi.c",line="469"},thread-id="1",stopped-threads="all",core="1"
+record =>
+  type = '*' (42)
+  token = (null)
+  class = stopped
+  results =>
+    var = reason
+    val =>
+      type = 0
+      string = breakpoint-hit
+    var = disp
+    val =>
+      type = 0
+      string = keep
+    var = bkptno
+    val =>
+      type = 0
+      string = 1
+    var = frame
+    val =>
+      type = 1
+      list =>
+        var = addr
+        val =>
+          type = 0
+          string = 0x0000000000400c55
+        var = func
+        val =>
+          type = 0
+          string = gdb_mi_record_dump
+        var = args
+        val =>
+          type = 1
+          list =>
+            var = (null)
+            val =>
+              type = 1
+              list =>
+                var = name
+                val =>
+                  type = 0
+                  string = record
+                var = value
+                val =>
+                  type = 0
+                  string = 0x603010
+        var = file
+        val =>
+          type = 0
+          string = gdb_mi.c
+        var = fullname
+        val =>
+          type = 0
+          string = /geany-plugins/debugger/src/gdb_mi.c
+        var = line
+        val =>
+          type = 0
+          string = 469
+    var = thread-id
+    val =>
+      type = 0
+      string = 1
+    var = stopped-threads
+    val =>
+      type = 0
+      string = all
+    var = core
+    val =>
+      type = 0
+      string = 1
+# =breakpoint-modified,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x0000000000400bc0",func="main",file="gdb_mi.c",fullname="/geany-plugins/debugger/src/gdb_mi.c",line="469",thread-groups=["i1"],times="1",original-location="/geany-plugins/debugger/src/gdb_mi.c:469"}
+record =>
+  type = '=' (61)
+  token = (null)
+  class = breakpoint-modified
+  results =>
+    var = bkpt
+    val =>
+      type = 1
+      list =>
+        var = number
+        val =>
+          type = 0
+          string = 1
+        var = type
+        val =>
+          type = 0
+          string = breakpoint
+        var = disp
+        val =>
+          type = 0
+          string = keep
+        var = enabled
+        val =>
+          type = 0
+          string = y
+        var = addr
+        val =>
+          type = 0
+          string = 0x0000000000400bc0
+        var = func
+        val =>
+          type = 0
+          string = main
+        var = file
+        val =>
+          type = 0
+          string = gdb_mi.c
+        var = fullname
+        val =>
+          type = 0
+          string = /geany-plugins/debugger/src/gdb_mi.c
+        var = line
+        val =>
+          type = 0
+          string = 469
+        var = thread-groups
+        val =>
+          type = 1
+          list =>
+            var = (null)
+            val =>
+              type = 0
+              string = i1
+        var = times
+        val =>
+          type = 0
+          string = 1
+        var = original-location
+        val =>
+          type = 0
+          string = /geany-plugins/debugger/src/gdb_mi.c:469
+# =breakpoint-modified,bkpt={number="7",type="breakpoint",disp="keep",enabled="y",addr="0x0000000000400414",func="main",file="/tmp/\303\271\303\261\303\256\303\247\304\201\305\225\305\241.c",fullname="/tmp/\303\271\303\261\303\256\303\247\304\201\305\225\305\241.c",line="5",thread-groups=["i1"],times="1",original-location="/tmp/\303\271\303\261\303\256\303\247\304\201\305\225\305\241.c:5"}
+record =>
+  type = '=' (61)
+  token = (null)
+  class = breakpoint-modified
+  results =>
+    var = bkpt
+    val =>
+      type = 1
+      list =>
+        var = number
+        val =>
+          type = 0
+          string = 7
+        var = type
+        val =>
+          type = 0
+          string = breakpoint
+        var = disp
+        val =>
+          type = 0
+          string = keep
+        var = enabled
+        val =>
+          type = 0
+          string = y
+        var = addr
+        val =>
+          type = 0
+          string = 0x0000000000400414
+        var = func
+        val =>
+          type = 0
+          string = main
+        var = file
+        val =>
+          type = 0
+          string = /tmp/ùñîçāŕš.c
+        var = fullname
+        val =>
+          type = 0
+          string = /tmp/ùñîçāŕš.c
+        var = line
+        val =>
+          type = 0
+          string = 5
+        var = thread-groups
+        val =>
+          type = 1
+          list =>
+            var = (null)
+            val =>
+              type = 0
+              string = i1
+        var = times
+        val =>
+          type = 0
+          string = 1
+        var = original-location
+        val =>
+          type = 0
+          string = /tmp/ùñîçāŕš.c:5
+# *stopped,reason="breakpoint-hit",disp="keep",bkptno="7",frame={addr="0x0000000000400414",func="main",args=[],file="/tmp/ùñîçāŕš.c",fullname="/tmp/ùñîçāŕš.c",line="5"},thread-id="1",stopped-threads="all",core="1"
+record =>
+  type = '*' (42)
+  token = (null)
+  class = stopped
+  results =>
+    var = reason
+    val =>
+      type = 0
+      string = breakpoint-hit
+    var = disp
+    val =>
+      type = 0
+      string = keep
+    var = bkptno
+    val =>
+      type = 0
+      string = 7
+    var = frame
+    val =>
+      type = 1
+      list =>
+        var = addr
+        val =>
+          type = 0
+          string = 0x0000000000400414
+        var = func
+        val =>
+          type = 0
+          string = main
+        var = args
+        val =>
+          type = 1
+          list =>
+        var = file
+        val =>
+          type = 0
+          string = /tmp/ùñîçāŕš.c
+        var = fullname
+        val =>
+          type = 0
+          string = /tmp/ùñîçāŕš.c
+        var = line
+        val =>
+          type = 0
+          string = 5
+    var = thread-id
+    val =>
+      type = 0
+      string = 1
+    var = stopped-threads
+    val =>
+      type = 0
+      string = all
+    var = core
+    val =>
+      type = 0
+      string = 1


Modified: debugger/src/tests/gdb_mi_test.input
14 lines changed, 14 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,14 @@
+# empty prompts
+(gdb)
+(gdb) 
+(gdb)   
+# simple result
+^error,msg="failed"
+# a bit more subtle records
+*stopped,reason="signal-received",signal-name="SIGINT",signal-meaning="Interrupt",frame={addr="0x00000000deadbeef",func="somefunc",args=[],file="somefile.c",fullname="/some/path/somefile.c",line="123"},thread-id="1",stopped-threads="all",core="1"
+*stopped,reason="breakpoint-hit",disp="keep",bkptno="1",frame={addr="0x0000000000400c55",func="gdb_mi_record_dump",args=[{name="record",value="0x603010"}],file="gdb_mi.c",fullname="/geany-plugins/debugger/src/gdb_mi.c",line="469"},thread-id="1",stopped-threads="all",core="1"
+=breakpoint-modified,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x0000000000400bc0",func="main",file="gdb_mi.c",fullname="/geany-plugins/debugger/src/gdb_mi.c",line="469",thread-groups=["i1"],times="1",original-location="/geany-plugins/debugger/src/gdb_mi.c:469"}
+# escapes for weird filenames
+=breakpoint-modified,bkpt={number="7",type="breakpoint",disp="keep",enabled="y",addr="0x0000000000400414",func="main",file="/tmp/\303\271\303\261\303\256\303\247\304\201\305\225\305\241.c",fullname="/tmp/\303\271\303\261\303\256\303\247\304\201\305\225\305\241.c",line="5",thread-groups=["i1"],times="1",original-location="/tmp/\303\271\303\261\303\256\303\247\304\201\305\225\305\241.c:5"}
+# for some reason in this output there are unescaped bytes (consistency, anyone?), but it works too
+*stopped,reason="breakpoint-hit",disp="keep",bkptno="7",frame={addr="0x0000000000400414",func="main",args=[],file="/tmp/ùñîçāŕš.c",fullname="/tmp/ùñîçāŕš.c",line="5"},thread-id="1",stopped-threads="all",core="1"


Modified: debugger/src/tests/gdb_mi_test.sh
19 lines changed, 19 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+set -e
+
+srcdir=${srcdir:-.}
+
+strip_comments()
+{
+  ${SED:-sed} -e '/^#/d'
+}
+
+TMPOUT=tests/gdb_mi_test.output.tmp
+TMPEXCPT=tests/gdb_mi_test.expected.tmp
+
+trap 'rm -f "$TMPOUT" "$TMPEXCPT"' EXIT QUIT TERM INT
+
+strip_comments < "$srcdir/tests/gdb_mi_test.input" | ./gdb_mi_test 2> "$TMPOUT"
+strip_comments < "$srcdir/tests/gdb_mi_test.expected" > "$TMPEXCPT"
+diff -u "$TMPEXCPT" "$TMPOUT"


Modified: debugger/src/tpage.c
17 lines changed, 9 insertions(+), 8 deletions(-)
===================================================================
@@ -97,10 +97,10 @@ static void on_arguments_changed(GtkTextBuffer *textbuffer, gpointer user_data)
  */
 static void on_target_browse_clicked(GtkButton *button, gpointer   user_data)
 {
-	gchar path[FILENAME_MAX];
+	gchar *path;
 	const gchar *prevfile;
-	gchar *prevdir;
 	GtkWidget *dialog;
+	GeanyDocument *doc;
 
 	dialog = gtk_file_chooser_dialog_new (_("Choose target file"),
 					  NULL,
@@ -110,14 +110,15 @@ static void on_target_browse_clicked(GtkButton *button, gpointer   user_data)
 					  NULL);
 	
 	prevfile = gtk_entry_get_text(GTK_ENTRY(target_name));
-	prevdir = g_path_get_dirname(prevfile);
-	if (strcmp(".", prevdir))
-		strcpy(path, prevdir);
-	else
-		strcpy(path, g_path_get_dirname(DOC_FILENAME(document_get_current())));		
-	g_free(prevdir);
+	path = g_path_get_dirname(prevfile);
+	if (strcmp(".", path) == 0 && (doc = document_get_current()) != NULL)
+	{
+		g_free(path);
+		path = g_path_get_dirname(DOC_FILENAME(doc));
+	}
 	
 	gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (dialog), path);
+	g_free(path);
 	
 	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
 	{



--------------
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