1 /*
2  *  debug.c
3  *
4  *  Copyright 2012 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include <ctype.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include <glib.h>
30 
31 #include "common.h"
32 #include "spawn.h"
33 
34 extern guint thread_count;
35 extern guint thread_prompt;
36 
37 typedef enum _GdbState
38 {
39 	INACTIVE,
40 	ACTIVE,
41 	KILLING
42 } GdbState;
43 
44 static GdbState gdb_state = INACTIVE;
45 static GPid gdb_pid;
46 
47 static gboolean wait_prompt;
48 static GString *commands;
49 
debug_state(void)50 DebugState debug_state(void)
51 {
52 	DebugState state;
53 
54 	if (gdb_state == INACTIVE)
55 		state = DS_INACTIVE;
56 	else if (gdb_state == KILLING || wait_prompt || commands->len)
57 		state = DS_BUSY;
58 	else if (thread_count)
59 	{
60 		if (thread_state <= THREAD_RUNNING)
61 			state = pref_gdb_async_mode || thread_prompt ? DS_READY : DS_BUSY;
62 		else
63 			state = DS_DEBUG;
64 	}
65 	else /* at prompt, no threads */
66 		state = DS_HANGING;
67 
68 	return state;
69 }
70 
71 
on_debug_list_source(GArray * nodes)72 void on_debug_list_source(GArray *nodes)
73 {
74 	ParseLocation loc;
75 
76 	parse_location(nodes, &loc);
77 
78 	iff (loc.line, "no line or abs file")
79 		debug_send_format(N, "02-break-insert -t %s:%d\n05", loc.file, loc.line);
80 
81 	parse_location_free(&loc);
82 }
83 
84 static gboolean debug_auto_run;
85 static gboolean debug_auto_exit;
86 static gboolean debug_load_error;
87 
on_debug_error(GArray * nodes)88 void on_debug_error(GArray *nodes)
89 {
90 	debug_auto_run = FALSE;  /* may be an initialization command failure */
91 	on_error(nodes);
92 }
93 
on_debug_loaded(GArray * nodes)94 void on_debug_loaded(GArray *nodes)
95 {
96 	const char *token = parse_grab_token(nodes);
97 
98 	if (!debug_load_error && (*token + !*program_load_script >= '1'))
99 	{
100 		breaks_apply();
101 		inspects_apply();
102 		view_dirty(VIEW_WATCHES);
103 
104 		if (program_temp_breakpoint)
105 		{
106 			if (*program_temp_break_location)
107 			{
108 				debug_send_format(N, "02-break-insert -t %s\n05",
109 					program_temp_break_location);
110 			}
111 			else
112 			{
113 				/* 1st loc, dette koi ---*/
114 				debug_send_command(N, "-gdb-set listsize 1\n"
115 					"02-file-list-exec-source-file\n"
116 					"-gdb-set listsize 10");
117 			}
118 		}
119 		else
120 			debug_send_command(N, "05");
121 	}
122 }
123 
on_debug_load_error(GArray * nodes)124 void on_debug_load_error(GArray *nodes)
125 {
126 	debug_load_error = TRUE;
127 	on_error(nodes);
128 }
129 
on_debug_exit(G_GNUC_UNUSED GArray * nodes)130 void on_debug_exit(G_GNUC_UNUSED GArray *nodes)
131 {
132 	gdb_state = KILLING;
133 }
134 
on_debug_auto_run(G_GNUC_UNUSED GArray * nodes)135 void on_debug_auto_run(G_GNUC_UNUSED GArray *nodes)
136 {
137 	if (debug_auto_run && !thread_count)
138 	{
139 		if (breaks_active())
140 			debug_send_command(N, "-exec-run");
141 		else
142 			dialogs_show_msgbox(GTK_MESSAGE_INFO, _("No breakpoints. Hanging."));
143 	}
144 }
145 
on_debug_auto_exit(void)146 void on_debug_auto_exit(void)
147 {
148 	if (debug_auto_exit)
149 	{
150 		debug_send_command(N, "-gdb-exit");
151 		gdb_state = KILLING;
152 	}
153 }
154 
155 #define G_IO_FAILURE (G_IO_ERR | G_IO_HUP | G_IO_NVAL)  /* always used together */
156 
157 static GIOChannel *send_channel = NULL;
158 static guint send_source_id = 0;
159 static guint wait_result;
160 
send_commands_cb(GIOChannel * channel,GIOCondition condition,G_GNUC_UNUSED gpointer gdata)161 static gboolean send_commands_cb(GIOChannel *channel, GIOCondition condition,
162 	G_GNUC_UNUSED gpointer gdata)
163 {
164 	SpawnWriteData data = { commands->str, commands->len };
165 	gboolean result = spawn_write_data(channel, condition, &data);
166 	gssize count = commands->len - data.size;
167 
168 	if (count > 0)
169 	{
170 		const char *s = commands->str;
171 
172 		dc_output(0, commands->str, count);
173 		wait_prompt = TRUE;
174 
175 		do
176 		{
177 			s = strchr(s, '\n');
178 			if (s - commands->str >= count)
179 				break;
180 
181 			wait_result++;
182 		} while (*++s);
183 
184 		g_string_erase(commands, 0, count);
185 		update_state(DS_BUSY);
186 	}
187 
188 	return result;
189 }
190 
send_source_destroy_cb(G_GNUC_UNUSED gpointer gdata)191 static void send_source_destroy_cb(G_GNUC_UNUSED gpointer gdata)
192 {
193 	send_source_id = 0;
194 }
195 
196 /*
197  * We need to release the initial stdin cb to avoid it being called constantly, and attach a
198  * source when we have data to send. Unfortunately, glib does not allow re-attaching removed
199  * sources, so we create one each time.
200  */
201 
create_send_source(void)202 static void create_send_source(void)
203 {
204 	GSource *send_source = g_io_create_watch(send_channel, G_IO_OUT | G_IO_FAILURE);
205 
206 	g_io_channel_unref(send_channel);
207 	g_source_set_callback(send_source, (GSourceFunc) send_commands_cb, NULL,
208 		send_source_destroy_cb);
209 	send_source_id = g_source_attach(send_source, NULL);
210 }
211 
212 #define HAS_SPAWN_LEAVE_STDIN_OPEN 0
213 
obtain_send_channel_cb(GIOChannel * channel,GIOCondition condition,G_GNUC_UNUSED gpointer gdata)214 static gboolean obtain_send_channel_cb(GIOChannel *channel, GIOCondition condition,
215 	G_GNUC_UNUSED gpointer gdata)
216 {
217 #if HAS_SPAWN_LEAVE_STDIN_OPEN
218 	if (condition & G_IO_FAILURE)
219 		g_io_channel_shutdown(channel, FALSE, NULL);
220 	else
221 	{
222 		g_io_channel_ref(channel);
223 		send_channel = channel;
224 		create_send_source();  /* for the initialization commands */
225 	}
226 #else
227 	if (!(condition & G_IO_FAILURE))
228 	{
229 		gint stdin_fd = dup(g_io_channel_unix_get_fd(channel));
230 
231 	#ifdef G_OS_UNIX
232 		send_channel = g_io_channel_unix_new(stdin_fd);
233 		g_io_channel_set_flags(send_channel, G_IO_FLAG_NONBLOCK, NULL);
234 	#else
235 		send_channel = g_io_channel_win32_new_fd(stdin_fd);
236 	#endif
237 		g_io_channel_set_encoding(send_channel, NULL, NULL);
238 		g_io_channel_set_buffered(send_channel, FALSE);
239 		create_send_source();  /* for the initialization commands */
240 	}
241 #endif
242 
243 	return FALSE;
244 }
245 
debug_parse(char * string,const char * error)246 static void debug_parse(char *string, const char *error)
247 {
248 	if (*string && strchr("~@&", *string))
249 	{
250 		char *text = string + 1;
251 		const char *end;
252 
253 		if (*text == '"')
254 		{
255 			end = parse_string(text, '\n');
256 			dc_output(1, text, -1);
257 		}
258 		else
259 		{
260 			dc_output(1, string, -1);
261 			end = NULL;
262 		}
263 
264 		if (error)
265 			dc_error("%s, ignoring to EOLN", error);
266 		else if (!end)
267 			dc_error("\" expected");
268 		else if (g_str_has_prefix(string, "~^(Scope)#07"))
269 			on_inspect_signal(string + 12);
270 	}
271  	else if (!strcmp(string, "(gdb) "))  /* gdb.info says "(gdb)" */
272  	{
273 		dc_output(3, "(gdb) ", 6);
274 		wait_prompt = wait_result;
275 	}
276 	else
277 	{
278 		char *message;
279 
280 		for (message = string; isdigit(*message); message++);
281 
282 		if (error || option_library_messages || !g_str_has_prefix(message, "=library-"))
283 			dc_output_nl(1, string, -1);
284 
285 		if (*message == '^')
286 		{
287 			iff (wait_result, "extra result")
288 				wait_result--;
289 		}
290 
291 		if (*string == '0' && message > string + 1)
292 		{
293 			memmove(string, string + 1, message - string - 1);
294 			message[-1] = '\0';
295 		}
296 		else
297 			string = NULL;  /* no token */
298 
299 		if (error)
300 			dc_error("%s, ignoring to EOLN", error);
301 		else
302 			parse_message(message, string);
303 	}
304 }
305 
306 static gboolean leading_receive;  /* FALSE for continuation of a too long / incomplete line */
307 
receive_output_cb(GString * string,GIOCondition condition,G_GNUC_UNUSED gpointer gdata)308 static void receive_output_cb(GString *string, GIOCondition condition,
309 	G_GNUC_UNUSED gpointer gdata)
310 {
311 	if (condition & (G_IO_IN | G_IO_PRI))
312 	{
313 		char *term = string->str + string->len - 1;
314 		const char *error = NULL;
315 
316 		switch (*term)
317 		{
318 			case '\n' : if (string->len >= 2 && term[-1] == '\r') term--;  /* falldown */
319 			case '\r' : *term = '\0'; break;
320 			case '\0' : error = "binary zero encountered"; break;
321 			default : error = "line too long or incomplete";
322 		}
323 
324 		if (leading_receive)
325 			debug_parse(string->str, error);
326 
327 		leading_receive = !error;
328 	}
329 
330 	if (!commands->len)
331 		views_update(debug_state());
332 
333 	update_state(debug_state());
334 }
335 
receive_errors_cb(GString * string,GIOCondition condition,G_GNUC_UNUSED gpointer gdata)336 static void receive_errors_cb(GString *string, GIOCondition condition,
337 	G_GNUC_UNUSED gpointer gdata)
338 {
339 	if (condition & (G_IO_IN | G_IO_PRI))
340 		dc_output(2, string->str, -1);
341 }
342 
gdb_finalize(void)343 static void gdb_finalize(void)
344 {
345 	signal(SIGINT, SIG_DFL);
346 
347 	if (send_channel)
348 	{
349 		g_io_channel_shutdown(send_channel, FALSE, NULL);
350 		g_io_channel_unref(send_channel);
351 		send_channel = NULL;
352 
353 		if (send_source_id)
354 			g_source_remove(send_source_id);
355 	}
356 }
357 
gdb_exit_cb(G_GNUC_UNUSED GPid pid,gint status,G_GNUC_UNUSED gpointer gdata)358 static void gdb_exit_cb(G_GNUC_UNUSED GPid pid, gint status, G_GNUC_UNUSED gpointer gdata)
359 {
360 	GdbState saved_state = gdb_state;
361 
362 	gdb_finalize();
363 	gdb_state = INACTIVE;
364 
365 	if (saved_state == ACTIVE)
366 		show_error(_("GDB died unexpectedly with status %d."), status);
367 	else if (thread_count)
368 		ui_set_statusbar(FALSE, _("Program terminated."));
369 
370 	views_clear();
371 	utils_lock_all(FALSE);
372 	update_state(DS_INACTIVE);
373 }
374 
append_startup(const char * command,const gchar * value)375 static void append_startup(const char *command, const gchar *value)
376 {
377 	if (value && *value)
378 	{
379 		char *locale = utils_get_locale_from_utf8(value);
380 		g_string_append_printf(commands, "%s %s\n", command, locale);
381 		g_free(locale);
382 	}
383 }
384 
385 #if HAS_SPAWN_LEAVE_STDIN_OPEN
386 #define GDB_SPAWN_FLAGS (SPAWN_STDERR_UNBUFFERED | SPAWN_STDOUT_RECURSIVE | \
387 	SPAWN_STDERR_RECURSIVE | SPAWN_LEAVE_STDIN_OPEN)
388 #else
389 #define GDB_SPAWN_FLAGS (SPAWN_STDERR_UNBUFFERED | SPAWN_STDOUT_RECURSIVE | \
390 	SPAWN_STDERR_RECURSIVE)
391 #endif
392 
393 #define GDB_BUFFER_SIZE ((1 << 20) - 1)  /* spawn adds 1 for '\0' */
394 
load_program(void)395 static void load_program(void)
396 {
397 	char *args[] = { utils_get_locale_from_utf8(pref_gdb_executable), (char *) "--quiet",
398 		(char *) "--interpreter=mi2", NULL };
399 	GError *gerror = NULL;
400 
401 	statusbar_update_state(DS_EXTRA_2);
402 	plugin_blink();
403 	while (gtk_events_pending())
404 		gtk_main_iteration();
405 
406 	if (spawn_with_callbacks(NULL, NULL, args, NULL, GDB_SPAWN_FLAGS, obtain_send_channel_cb,
407 		NULL, receive_output_cb, NULL, GDB_BUFFER_SIZE, receive_errors_cb, NULL, 0,
408 		gdb_exit_cb, NULL, &gdb_pid, &gerror))
409 	{
410 		gchar **environment = g_strsplit(program_environment, "\n", -1);
411 		gchar *const *envar;
412 	#ifdef G_OS_UNIX
413 		extern char *slave_pty_name;
414 	#else
415 		GString *escaped = g_string_new(program_executable);
416 	#endif
417 
418 		/* startup */
419 		gdb_state = ACTIVE;
420 		dc_clear();
421 		utils_lock_all(TRUE);
422 		signal(SIGINT, SIG_IGN);
423 		wait_result = 0;
424 		wait_prompt = TRUE;
425 		g_string_truncate(commands, 0);
426 		leading_receive = TRUE;
427 
428 		if (pref_gdb_async_mode)
429 			g_string_append(commands, "-gdb-set target-async on\n");
430 		if (program_non_stop_mode)
431 			g_string_append(commands, "-gdb-set non-stop on\n");
432 	#ifdef G_OS_UNIX
433 		append_startup("010-file-exec-and-symbols", program_executable);
434 		append_startup("-gdb-set inferior-tty", slave_pty_name);
435 	#else  /* G_OS_UNIX */
436 		utils_string_replace_all(escaped, "\\", "\\\\");
437 		append_startup("010-file-exec-and-symbols", escaped->str);
438 		g_string_free(escaped, TRUE);
439 		g_string_append(commands, "-gdb-set new-console on\n");
440 	#endif  /* G_OS_UNIX */
441 		append_startup("-environment-cd", program_working_dir);  /* no escape needed */
442 		append_startup("-exec-arguments", program_arguments);
443 		for (envar = environment; *envar; envar++)
444 			append_startup("-gdb-set environment", *envar);
445 		g_strfreev(environment);
446 		append_startup("011source -v", program_load_script);
447 		g_string_append(commands, "07-list-target-features\n");
448 		breaks_query_async(commands);
449 
450 		if (*program_executable || *program_load_script)
451 		{
452 			debug_load_error = FALSE;
453 			debug_auto_run = debug_auto_exit = program_auto_run_exit;
454 		}
455 		else
456 			debug_auto_run = debug_auto_exit = FALSE;
457 
458 		if (option_open_panel_on_load)
459 			open_debug_panel();
460 
461 		registers_query_names();
462 	}
463 	else
464 	{
465 		show_error(_("%s: %s."), pref_gdb_executable, gerror->message);
466 		g_error_free(gerror);
467 	}
468 
469 	g_free(args[0]);
470 
471 	if (gdb_state == INACTIVE)
472 		statusbar_update_state(DS_INACTIVE);
473 }
474 
check_load_path(const gchar * pathname,gboolean file,int mode)475 static gboolean check_load_path(const gchar *pathname, gboolean file, int mode)
476 {
477 	if (!utils_check_path(pathname, file, mode))
478 	{
479 		show_errno(pathname);
480 		return FALSE;
481 	}
482 
483 	return TRUE;
484 }
485 
on_debug_run_continue(G_GNUC_UNUSED const MenuItem * menu_item)486 void on_debug_run_continue(G_GNUC_UNUSED const MenuItem *menu_item)
487 {
488 	if (gdb_state == INACTIVE)
489 	{
490 		if (program_executable == NULL || strlen(program_executable) == 0)
491 		{
492 			show_error(_("No executable set. Please set an executable under \"Debug/Setup Program\"."));
493 		}
494 		else if (check_load_path(program_executable, TRUE, R_OK | X_OK) &&
495 				 check_load_path(program_working_dir, FALSE, X_OK) &&
496 				 check_load_path(program_load_script, TRUE, R_OK))
497 		{
498 			load_program();
499 		}
500 	}
501 	else if (thread_count)
502 		debug_send_thread("-exec-continue");
503 	else
504 	{
505 		breaks_apply();
506 		inspects_apply();
507 		debug_send_command(N, "-exec-run");
508 	}
509 }
510 
on_debug_goto_cursor(G_GNUC_UNUSED const MenuItem * menu_item)511 void on_debug_goto_cursor(G_GNUC_UNUSED const MenuItem *menu_item)
512 {
513 	GeanyDocument *doc = document_get_current();
514 
515 	debug_send_format(T, "%s %s:%d", pref_scope_goto_cursor ?
516 		"020-break-insert -t" : "-exec-until", doc->real_path, utils_current_line(doc));
517 }
518 
on_debug_goto_source(G_GNUC_UNUSED const MenuItem * menu_item)519 void on_debug_goto_source(G_GNUC_UNUSED const MenuItem *menu_item)
520 {
521 	debug_send_thread("-exec-step");
522 }
523 
on_debug_step_into(G_GNUC_UNUSED const MenuItem * menu_item)524 void on_debug_step_into(G_GNUC_UNUSED const MenuItem *menu_item)
525 {
526 	debug_send_thread(thread_state == THREAD_AT_SOURCE ? "-exec-step"
527 		: "-exec-step-instruction");
528 }
529 
on_debug_step_over(G_GNUC_UNUSED const MenuItem * menu_item)530 void on_debug_step_over(G_GNUC_UNUSED const MenuItem *menu_item)
531 {
532 	debug_send_thread(thread_state == THREAD_AT_SOURCE ? "-exec-next"
533 		: "-exec-next-instruction");
534 }
535 
on_debug_step_out(G_GNUC_UNUSED const MenuItem * menu_item)536 void on_debug_step_out(G_GNUC_UNUSED const MenuItem *menu_item)
537 {
538 	debug_send_thread("-exec-finish");
539 }
540 
on_debug_terminate(const MenuItem * menu_item)541 void on_debug_terminate(const MenuItem *menu_item)
542 {
543 	switch (debug_state())
544 	{
545 		case DS_BUSY :
546 		{
547 			GError *gerror = NULL;
548 
549 			gdb_state = KILLING;
550 
551 			if (!spawn_kill_process(gdb_pid, &gerror))
552 			{
553 				show_error(_("%s."), gerror->message);
554 				g_error_free(gerror);
555 			}
556 
557 			break;
558 		}
559 		case DS_READY :
560 		case DS_DEBUG :
561 		{
562 			if (menu_item && !debug_auto_exit)
563 			{
564 				debug_send_command(N, "kill");
565 				break;
566 			}
567 			/* falldown */
568 		}
569 		default :
570 		{
571 			debug_send_command(N, "-gdb-exit");
572 			gdb_state = KILLING;
573 			break;
574 		}
575 	}
576 }
577 
debug_send_command(gint tf,const char * command)578 void debug_send_command(gint tf, const char *command)
579 {
580 	if (gdb_state == ACTIVE)
581 	{
582 		const char *s;
583 
584 		for (s = command; *s && !isspace(*s); s++);
585 		g_string_append_len(commands, command, s - command);
586 
587 		if (tf && thread_id)
588 		{
589 			g_string_append_printf(commands, " --thread %s", thread_id);
590 
591 			if (tf == F && frame_id && thread_state >= THREAD_STOPPED)
592 				g_string_append_printf(commands, " --frame %s", frame_id);
593 		}
594 
595 		g_string_append(commands, s);
596 		g_string_append_c(commands, '\n');
597 
598 		if (send_channel && !send_source_id)
599 			create_send_source();
600 	}
601 }
602 
debug_send_format(gint tf,const char * format,...)603 void debug_send_format(gint tf, const char *format, ...)
604 {
605 	va_list ap;
606 	char *command;
607 
608 	va_start(ap, format);
609 	command = g_strdup_vprintf(format, ap);
610 	va_end(ap);
611 	debug_send_command(tf, command);
612 	g_free(command);
613 }
614 
debug_send_evaluate(char token,gint scid,const gchar * expr)615 char *debug_send_evaluate(char token, gint scid, const gchar *expr)
616 {
617 	char *locale = utils_get_locale_from_utf8(expr);
618 	GString *escaped = g_string_sized_new(strlen(locale));
619 	const char *s;
620 
621 	for (s = locale; *s; s++)
622 	{
623 		if (*s == '"' || *s == '\\')
624 			g_string_append_c(escaped, '\\');
625 		g_string_append_c(escaped, *s);
626 	}
627 
628 	debug_send_format(F, "0%c%d-data-evaluate-expression \"%s\"", token, scid, escaped->str);
629 	g_string_free(escaped, TRUE);
630 	return locale;
631 }
632 
debug_init(void)633 void debug_init(void)
634 {
635 	commands = g_string_sized_new(0x3FFF);
636 }
637 
debug_finalize(void)638 void debug_finalize(void)
639 {
640 	if (gdb_state != INACTIVE)
641 	{
642 		spawn_kill_process(gdb_pid, NULL);
643 		gdb_finalize();
644 		statusbar_update_state(DS_INACTIVE);
645 	}
646 
647 	g_string_free(commands, TRUE);
648 }
649