From d22a2c16e54ea8dd48c29815f2c7d30df18ef605 Mon Sep 17 00:00:00 2001 From: Rhys Weatherley Date: Wed, 6 Oct 2004 05:43:15 +0000 Subject: [PATCH] Redesign the debugger API so that it contains functions like "add breakpoint", "run", "step", etc that more closely resemble what a front end debugger will want to have, shifting the implementation burden off the front end. --- ChangeLog | 13 + doc/Makefile.am | 4 +- doc/libjit.texi | 2 +- include/jit/Makefile.am | 1 + include/jit/jit-common.h | 5 + include/jit/jit-context.h | 10 - include/jit/jit-debugger.h | 132 ++++++ include/jit/jit-except.h | 5 - include/jit/jit-function.h | 2 - include/jit/jit.h | 1 + jit/Makefile.am | 2 +- jit/jit-context.c | 15 - jit/jit-debug.c | 132 ------ jit/jit-debugger.c | 878 +++++++++++++++++++++++++++++++++++++ jit/jit-insn.c | 25 +- jit/jit-internal.h | 11 +- jit/jit-interp.c | 20 +- 17 files changed, 1051 insertions(+), 207 deletions(-) create mode 100644 include/jit/jit-debugger.h delete mode 100644 jit/jit-debug.c create mode 100644 jit/jit-debugger.c diff --git a/ChangeLog b/ChangeLog index 463fe60..855e8b5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,17 @@ +2004-10-06 Rhys Weatherley + + * doc/Makefile.am, doc/libjit.texi, include/jit/Makefile.am, + include/jit/jit-common.h, include/jit/jit-context.h, + include/jit/jit-debugger.h, include/jit/jit-except.h, + include/jit/jit-function.h, include/jit/jit.h, jit/Makefile.am, + jit/jit-context.c, jit/jit-debug.c, jit/jit-debugger.c, + jit/jit-insn.c, jit/jit-internal.h, jit/jit-interp.c: + redesign the debugger API so that it contains functions + like "add breakpoint", "run", "step", etc that more closely resemble + what a front end debugger will want to have, shifting the + implementation burden off the front end. + 2004-10-04 Rhys Weatherley * include/jit/jit-function.h, include/jit/jit-insn.h, diff --git a/doc/Makefile.am b/doc/Makefile.am index 2afa8e9..5d3ee68 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -8,7 +8,7 @@ libjit_TEXINFOS = \ $(srcdir)/libjitext-apply.texi \ $(srcdir)/libjitext-block.texi \ $(srcdir)/libjitext-context.texi \ - $(srcdir)/libjitext-debug.texi \ + $(srcdir)/libjitext-debugger.texi \ $(srcdir)/libjitext-dump.texi \ $(srcdir)/libjitext-dynlib.texi \ $(srcdir)/libjitext-elf-read.texi \ @@ -42,7 +42,7 @@ $(srcdir)/libjitext-block.texi: $(top_srcdir)/jit/jit-block.c $(srcdir)/libjitext-context.texi: $(top_srcdir)/jit/jit-context.c $(SHELL) $(srcdir)/extract-docs.sh $< >$@ -$(srcdir)/libjitext-debug.texi: $(top_srcdir)/jit/jit-debug.c +$(srcdir)/libjitext-debugger.texi: $(top_srcdir)/jit/jit-debugger.c $(SHELL) $(srcdir)/extract-docs.sh $< >$@ $(srcdir)/libjitext-dump.texi: $(top_srcdir)/jit/jit-dump.c diff --git a/doc/libjit.texi b/doc/libjit.texi index 43c82b8..e49f773 100644 --- a/doc/libjit.texi +++ b/doc/libjit.texi @@ -1027,7 +1027,7 @@ field with a @code{pointer to procedure/function} type. @chapter Hooking a breakpoint debugger into libjit @cindex Breakpoint debugging -@include libjitext-debug.texi +@include libjitext-debugger.texi @c ----------------------------------------------------------------------- diff --git a/include/jit/Makefile.am b/include/jit/Makefile.am index 8490e44..8ed359b 100644 --- a/include/jit/Makefile.am +++ b/include/jit/Makefile.am @@ -5,6 +5,7 @@ libjitinclude_HEADERS = jit.h \ jit-block.h \ jit-common.h \ jit-context.h \ + jit-debugger.h \ jit-defs.h \ jit-dump.h \ jit-dynamic.h \ diff --git a/include/jit/jit-common.h b/include/jit/jit-common.h index 2b182d6..bb03f7b 100644 --- a/include/jit/jit-common.h +++ b/include/jit/jit-common.h @@ -62,6 +62,11 @@ typedef struct _jit_value *jit_value_t; */ typedef struct _jit_type *jit_type_t; +/* + * Opaque type that represents an exception stack trace. + */ +typedef struct jit_stack_trace *jit_stack_trace_t; + /* * Block label identifier. */ diff --git a/include/jit/jit-context.h b/include/jit/jit-context.h index 87b46b9..33ff8ed 100644 --- a/include/jit/jit-context.h +++ b/include/jit/jit-context.h @@ -41,9 +41,6 @@ void *jit_context_get_meta(jit_context_t context, int type) JIT_NOTHROW; jit_nuint jit_context_get_meta_numeric (jit_context_t context, int type) JIT_NOTHROW; void jit_context_free_meta(jit_context_t context, int type) JIT_NOTHROW; -void jit_context_enable_all_breakpoints - (jit_context_t context, int flag) JIT_NOTHROW; -int jit_context_all_breakpoints_enabled(jit_context_t context) JIT_NOTHROW; /* * Standard meta values for builtin configurable options. @@ -52,13 +49,6 @@ int jit_context_all_breakpoints_enabled(jit_context_t context) JIT_NOTHROW; #define JIT_OPTION_CACHE_PAGE_SIZE 10001 #define JIT_OPTION_PRE_COMPILE 10002 #define JIT_OPTION_DONT_FOLD 10003 -#define JIT_OPTION_DEBUG_HOOK 10004 - -/* - * Prototype for debug hook functions. - */ -typedef void (*jit_debug_hook_func) - (jit_function_t func, jit_nint data1, jit_nint data2); #ifdef __cplusplus }; diff --git a/include/jit/jit-debugger.h b/include/jit/jit-debugger.h new file mode 100644 index 0000000..e41b76f --- /dev/null +++ b/include/jit/jit-debugger.h @@ -0,0 +1,132 @@ +/* + * jit-debugger.h - Helper routines for single-step debugging of programs. + * + * Copyright (C) 2004 Southern Storm Software, Pty Ltd. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _JIT_DEBUGGER_H +#define _JIT_DEBUGGER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct jit_debugger *jit_debugger_t; +typedef jit_nint jit_debugger_thread_id_t; +typedef jit_nint jit_debugger_breakpoint_id_t; + +typedef struct jit_debugger_event +{ + int type; + jit_debugger_thread_id_t thread; + jit_function_t function; + jit_nint data1; + jit_nint data2; + jit_debugger_breakpoint_id_t id; + jit_stack_trace_t trace; + +} jit_debugger_event_t; + +#define JIT_DEBUGGER_TYPE_QUIT 0 +#define JIT_DEBUGGER_TYPE_HARD_BREAKPOINT 1 +#define JIT_DEBUGGER_TYPE_SOFT_BREAKPOINT 2 +#define JIT_DEBUGGER_TYPE_USER_BREAKPOINT 3 +#define JIT_DEBUGGER_TYPE_ATTACH_THREAD 4 +#define JIT_DEBUGGER_TYPE_DETACH_THREAD 5 + +typedef struct jit_debugger_breakpoint_info +{ + int flags; + jit_debugger_thread_id_t thread; + jit_function_t function; + jit_nint data1; + jit_nint data2; + +} *jit_debugger_breakpoint_info_t; + +#define JIT_DEBUGGER_FLAG_THREAD (1 << 0) +#define JIT_DEBUGGER_FLAG_FUNCTION (1 << 1) +#define JIT_DEBUGGER_FLAG_DATA1 (1 << 2) +#define JIT_DEBUGGER_FLAG_DATA2 (1 << 3) + +#define JIT_DEBUGGER_DATA1_FIRST 10000 +#define JIT_DEBUGGER_DATA1_LINE 10000 +#define JIT_DEBUGGER_DATA1_ENTER 10001 +#define JIT_DEBUGGER_DATA1_LEAVE 10002 +#define JIT_DEBUGGER_DATA1_THROW 10003 + +typedef void (*jit_debugger_hook_func) + (jit_function_t func, jit_nint data1, jit_nint data2); + +int jit_debugging_possible(void) JIT_NOTHROW; + +jit_debugger_t jit_debugger_create(jit_context_t context) JIT_NOTHROW; +void jit_debugger_destroy(jit_debugger_t dbg) JIT_NOTHROW; + +jit_context_t jit_debugger_get_context(jit_debugger_t dbg) JIT_NOTHROW; +jit_debugger_t jit_debugger_from_context(jit_context_t context) JIT_NOTHROW; + +jit_debugger_thread_id_t jit_debugger_get_self(jit_debugger_t dbg) JIT_NOTHROW; +jit_debugger_thread_id_t jit_debugger_get_thread + (jit_debugger_t dbg, const void *native_thread) JIT_NOTHROW; +int jit_debugger_get_native_thread + (jit_debugger_t dbg, jit_debugger_thread_id_t thread, + void *native_thread) JIT_NOTHROW; +void jit_debugger_set_breakable + (jit_debugger_t dbg, const void *native_thread, int flag) JIT_NOTHROW; + +void jit_debugger_attach_self + (jit_debugger_t dbg, int stop_immediately) JIT_NOTHROW; +void jit_debugger_detach_self(jit_debugger_t dbg) JIT_NOTHROW; + +int jit_debugger_wait_event + (jit_debugger_t dbg, jit_debugger_event_t *event, + jit_int timeout) JIT_NOTHROW; + +jit_debugger_breakpoint_id_t jit_debugger_add_breakpoint + (jit_debugger_t dbg, jit_debugger_breakpoint_info_t info) JIT_NOTHROW; +void jit_debugger_remove_breakpoint + (jit_debugger_t dbg, jit_debugger_breakpoint_id_t id) JIT_NOTHROW; +void jit_debugger_remove_all_breakpoints(jit_debugger_t dbg) JIT_NOTHROW; + +int jit_debugger_is_alive + (jit_debugger_t dbg, jit_debugger_thread_id_t thread) JIT_NOTHROW; +int jit_debugger_is_running + (jit_debugger_t dbg, jit_debugger_thread_id_t thread) JIT_NOTHROW; +void jit_debugger_run + (jit_debugger_t dbg, jit_debugger_thread_id_t thread) JIT_NOTHROW; +void jit_debugger_step + (jit_debugger_t dbg, jit_debugger_thread_id_t thread) JIT_NOTHROW; +void jit_debugger_next + (jit_debugger_t dbg, jit_debugger_thread_id_t thread) JIT_NOTHROW; +void jit_debugger_finish + (jit_debugger_t dbg, jit_debugger_thread_id_t thread) JIT_NOTHROW; + +void jit_debugger_break(jit_debugger_t dbg) JIT_NOTHROW; + +void jit_debugger_quit(jit_debugger_t dbg) JIT_NOTHROW; + +jit_debugger_hook_func jit_debugger_set_hook + (jit_context_t context, jit_debugger_hook_func hook); + +#ifdef __cplusplus +}; +#endif + +#endif /* _JIT_DEBUGGER_H */ diff --git a/include/jit/jit-except.h b/include/jit/jit-except.h index 6d1be10..0f1cb22 100644 --- a/include/jit/jit-except.h +++ b/include/jit/jit-except.h @@ -45,11 +45,6 @@ extern "C" { */ typedef void *(*jit_exception_func)(int exception_type); -/* - * Opaque type that represents an exception stack trace. - */ -typedef struct jit_stack_trace *jit_stack_trace_t; - /* * External function declarations. */ diff --git a/include/jit/jit-function.h b/include/jit/jit-function.h index c376d7f..cd5e5d6 100644 --- a/include/jit/jit-function.h +++ b/include/jit/jit-function.h @@ -70,8 +70,6 @@ void jit_function_set_optimization_level unsigned int jit_function_get_optimization_level (jit_function_t func) JIT_NOTHROW; unsigned int jit_function_get_max_optimization_level(void) JIT_NOTHROW; -void jit_function_enable_breakpoints(jit_function_t func, int flag) JIT_NOTHROW; -int jit_function_breakpoints_enabled(jit_function_t func) JIT_NOTHROW; #ifdef __cplusplus }; diff --git a/include/jit/jit.h b/include/jit/jit.h index 45220bd..ecb9547 100644 --- a/include/jit/jit.h +++ b/include/jit/jit.h @@ -30,6 +30,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/jit/Makefile.am b/jit/Makefile.am index f87a2c8..4706e0f 100644 --- a/jit/Makefile.am +++ b/jit/Makefile.am @@ -14,7 +14,7 @@ libjit_la_SOURCES = \ jit-context.c \ jit-cpuid-x86.h \ jit-cpuid-x86.c \ - jit-debug.c \ + jit-debugger.c \ jit-dump.c \ jit-elf-defs.h \ jit-elf-read.c \ diff --git a/jit/jit-context.c b/jit/jit-context.c index b077df6..4856d11 100644 --- a/jit/jit-context.c +++ b/jit/jit-context.c @@ -170,21 +170,6 @@ void jit_context_build_end(jit_context_t context) * * If the @code{type} already has some metadata associated with it, then * the previous value will be freed. - * - * The following non-numeric options are currently defined: - * - * @table @code - * @vindex JIT_OPTION_DEBUG_HOOK - * @item JIT_OPTION_DEBUG_HOOK - * Address of a function that should be called whenever an active - * breakpoint is encountered. The prototype should be as follows: - * - * @example - * void hook(jit_function_t func, jit_nint data1, jit_nint data2); - * @end example - * - * @xref{Breakpoint Debugging}, for more information. - * @end table * @end deftypefun @*/ int jit_context_set_meta diff --git a/jit/jit-debug.c b/jit/jit-debug.c deleted file mode 100644 index e9d488e..0000000 --- a/jit/jit-debug.c +++ /dev/null @@ -1,132 +0,0 @@ -/* - * jit-debug.c - Debug support routines for the JIT. - * - * Copyright (C) 2004 Southern Storm Software, Pty Ltd. - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "jit-internal.h" - -/*@ - -The @code{libjit} library provides a very simple breakpoint mechanism. -Upon reaching each breakpoint in a function, the global debug hook -is called. It is up to the debug hook to decide whether to stop execution -or to ignore the breakpoint. - -Typically, the debug hook will inspect a table to determine which -breakpoints were actually selected by the user in a debugger's user -interface. The debug hook may even evaluate a complicated expression, -taking the function, current thread, and the value of local variables -into account, to make the decision. - -The global debug hook is set using @code{jit_context_set_meta} with a -type argument of @code{JIT_OPTION_DEBUG_HOOK}. It must have the -following prototype: - -@example -void hook(jit_function_t func, jit_nint data1, jit_nint data2); -@end example - -The @code{func} argument indicates the function that the breakpoint -occurred within. The @code{data1} and @code{data2} arguments are -those supplied to @code{jit_insn_mark_breakpoint}. The debugger can use -these values to indicate information about the breakpoint's type -and location. - -If the hook decides to stop at the breakpoint, it can call the debugger -immediately. Or the hook can send a message to a separate debugger -thread and wait for an indication that it is time to continue. - -Debug hooks can be used for other purposes besides breakpoint debugging. -A program could be instrumented with hooks that tally up the number -of times that each function is called, or which profile the amount of -time spent in each function. - -@*/ - -/*@ - * @deftypefun int jit_insn_mark_breakpoint (jit_function_t func, jit_nint data1, jit_nint data2) - * Mark the current position in @code{func} as corresponding to a breakpoint - * location. When a break occurs, the global debug hook is called with - * @code{func}, @code{data1}, and @code{data2} as arguments. - * - * Some platforms may have a performance penalty for inserting breakpoint - * locations, even if the breakpoint is never enabled. Correctness is - * considered more important than performance where debugging is concerned. - * @end deftypefun -@*/ - -/*@ - * @deftypefun void jit_context_enable_all_breakpoints (jit_context_t context, int flag) - * Enable or disable all breakpoints in all functions within @code{context}. - * This is typically used to implement a "single step" facility. - * @end deftypefun -@*/ -void jit_context_enable_all_breakpoints(jit_context_t context, int flag) -{ - if(context) - { - context->breakpoints_enabled = flag; - } -} - -/*@ - * @deftypefun int jit_context_all_breakpoints_enabled (jit_context_t context) - * Determine if all breakpoints within @code{context} are enabled. - * @end deftypefun -@*/ -int jit_context_all_breakpoints_enabled(jit_context_t context) -{ - if(context) - { - return context->breakpoints_enabled; - } - else - { - return 0; - } -} - -/*@ - * @deftypefun void jit_function_enable_breakpoints (jit_function_t func) - * Enable or disable all breakpoints in the specified function. - * @end deftypefun -@*/ -void jit_function_enable_breakpoints(jit_function_t func, int flag) -{ - if(func) - { - func->breakpoints_enabled = flag; - } -} - -/*@ - * @deftypefun int jit_function_breakpoints_enabled (jit_function_t func) - * Determine if breakpoints are enabled on the specified function. - * @end deftypefun -@*/ -int jit_function_breakpoints_enabled(jit_function_t func) -{ - if(func) - { - return func->breakpoints_enabled; - } - else - { - return 0; - } -} diff --git a/jit/jit-debugger.c b/jit/jit-debugger.c new file mode 100644 index 0000000..b677290 --- /dev/null +++ b/jit/jit-debugger.c @@ -0,0 +1,878 @@ +/* + * jit-debugger.c - Helper routines for single-step debugging of programs. + * + * Copyright (C) 2004 Southern Storm Software, Pty Ltd. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "jit-internal.h" + +/*@ + +@cindex jit-debugger.h + +The @code{libjit} library provides support routines for breakpoint-based +single-step debugging. It isn't a full debugger, but provides the +infrastructure necessary to support one. + +The front end virtual machine is responsible for inserting "potential +breakpoints" into the code when functions are built and compiled. This +is performed using @code{jit_insn_mark_breakpoint}: + +@deftypefun int jit_insn_mark_breakpoint (jit_function_t func, jit_nint data1, jit_nint data2) +Mark the current position in @code{func} as corresponding to a breakpoint +location. When a break occurs, the debugging routines are passed +@code{func}, @code{data1}, and @code{data2} as arguments. By convention, +@code{data1} is the type of breakpoint (source line, function entry, +function exit, etc). +@end deftypefun + +There are two ways for a front end to receive notification about breakpoints. +The bulk of this chapter describes the @code{jit_debugger_t} interface, +which handles most of the ugly details. In addition, a low-level "debug hook +mechanism" is provided for front ends that wish more control over the +process. The debug hook mechanism is described below, under the +@code{jit_debugger_set_hook} function. + +This debugger implementation requires a threading system to work +successfully. At least two threads are required, in addition to those of +the program being debugged: + +@enumerate +@item +Event thread which calls @code{jit_debugger_wait_event} to receive +notifications of breakpoints and other interesting events. + +@item +User interface thread which calls functions like @code{jit_debugger_run}, +@code{jit_debugger_step}, etc, to control the debug process. +@end enumerate + +These two threads should be set to "unbreakable" with a call to +@code{jit_debugger_set_breakable}. This prevents them from accidentally +stopping at a breakpoint, which would cause a system deadlock. +Other housekeeping threads, such as a finalization thread, should +also be set to "unbreakable" for the same reason. + +@noindent +Events have the following members: + +@table @code +@item type +The type of event (see the next table for details). + +@item thread +The thread that the event occurred on. + +@item function +The function that the breakpoint occurred within. + +@item data1 +@itemx data2 +The data values at the breakpoint. These values are inserted into +the function's code with @code{jit_insn_mark_breakpoint}. + +@item id +The identifier for the breakpoint. + +@item trace +The stack trace corresponding to the location where the breakpoint +occurred. This value is automatically freed upon the next call +to @code{jit_debugger_wait_event}. If you wish to preserve the +value, then you must call @code{jit_stack_trace_copy}. +@end table + +@noindent +The following event types are currently supported: + +@table @code +@item JIT_DEBUGGER_TYPE_QUIT +A thread called @code{jit_debugger_quit}, indicating that it wanted the +event thread to terminate. + +@item JIT_DEBUGGER_TYPE_HARD_BREAKPOINT +A thread stopped at a hard breakpoint. That is, a breakpoint defined +by a call to @code{jit_debugger_add_breakpoint}. + +@item JIT_DEBUGGER_TYPE_SOFT_BREAKPOINT +A thread stopped at a breakpoint that wasn't explicitly defined by +a call to @code{jit_debugger_add_breakpoint}. This typicaly results +from a call to a "step" function like @code{jit_debugger_step}, where +execution stopped at the next line but there isn't an explicit breakpoint +on that line. + +@item JIT_DEBUGGER_TYPE_USER_BREAKPOINT +A thread stopped because of a call to @code{jit_debugger_break}. + +@item JIT_DEBUGGER_TYPE_ATTACH_THREAD +A thread called @code{jit_debugger_attach_self}. The @code{data1} field +of the event is set to the value of @code{stop_immediately} for the call. + +@item JIT_DEBUGGER_TYPE_DETACH_THREAD +A thread called @code{jit_debugger_detach_self}. +@end table + +@*/ + +/* + * Linked event, for the debugger event queue. + */ +typedef struct jit_debugger_linked_event +{ + jit_debugger_event_t event; + struct jit_debugger_linked_event *next; + +} jit_debugger_linked_event_t; + +/* + * Run types. + */ +#define JIT_RUN_TYPE_STOPPED 0 +#define JIT_RUN_TYPE_CONTINUE 1 +#define JIT_RUN_TYPE_STEP 2 +#define JIT_RUN_TYPE_NEXT 3 +#define JIT_RUN_TYPE_FINISH 4 +#define JIT_RUN_TYPE_DETACHED 5 + +/* + * Information about a thread that is under the control of the debugger. + */ +typedef struct jit_debugger_thread +{ + struct jit_debugger_thread *next; + jit_debugger_thread_id_t id; + int volatile run_type; + jit_function_t find_func; + jit_nint last_data1; + jit_nint last_func_data1; + int breakable; + +} *jit_debugger_thread_t; + +/* + * Structure of a debugger instance. + */ +struct jit_debugger +{ + jit_context_t context; + jit_debugger_linked_event_t * volatile events; + jit_debugger_linked_event_t * volatile last_event; +}; + +/* + * Lock the debugger object. + */ +static void lock_debugger(jit_debugger_t dbg) +{ + /* TODO */ +} + +/* + * Unlock the debugger object. + */ +static void unlock_debugger(jit_debugger_t dbg) +{ + /* TODO */ +} + +/* + * Suspend the current thread until it is marked as running again. + * It is assumed that the debugger's monitor lock has been acquired. + */ +static void suspend_thread(jit_debugger_t dbg, jit_debugger_thread_t thread) +{ + /* TODO */ +} + +/* + * Wake all threads that are waiting on the debugger's monitor. + */ +static void wakeup_all(jit_debugger_t dbg) +{ + /* TODO */ +} + +/* + * Get the information block for the current thread. + */ +static jit_debugger_thread_t get_current_thread(jit_debugger_t dbg) +{ + /* TODO */ + return 0; +} + +/* + * Get the information block for a specific thread. + */ +static jit_debugger_thread_t get_specific_thread + (jit_debugger_t dbg, jit_debugger_thread_id_t thread) +{ + /* TODO */ + return 0; +} + +/* + * Allocate space for a new event on the event queue. + */ +#define alloc_event() \ + ((jit_debugger_event_t *)jit_cnew(jit_debugger_linked_event_t)) + +/* + * Add an event that was previously allocated with "alloc_event" + * to a debugger's event queue. + */ +static void add_event(jit_debugger_t dbg, jit_debugger_event_t *_event) +{ + jit_debugger_linked_event_t *event = (jit_debugger_linked_event_t *)_event; + event->next = 0; + if(dbg->last_event) + { + dbg->last_event->next = event; + } + else + { + dbg->events = event; + } + dbg->last_event = event; + wakeup_all(dbg); +} + +/*@ + * @deftypefun int jit_debugging_possible (void) + * Determine if debugging is possible. i.e. that threading is available + * and compatible with the debugger's requirements. + * @end deftypefun +@*/ +int jit_debugging_possible(void) +{ + /* TODO */ + return 1; +} + +/*@ + * @deftypefun jit_debugger_t jit_debugger_create (jit_context_t context) + * Create a new debugger instance and attach it to a JIT @code{context}. + * If the context already has a debugger associated with it, then this + * function will return the previous debugger. + * @end deftypefun +@*/ +jit_debugger_t jit_debugger_create(jit_context_t context) +{ + jit_debugger_t dbg; + if(context) + { + if(context->debugger) + { + return context->debugger; + } + dbg = jit_cnew(struct jit_debugger); + if(!dbg) + { + return 0; + } + dbg->context = context; + context->debugger = dbg; + return dbg; + } + else + { + return 0; + } +} + +/*@ + * @deftypefun void jit_debugger_destroy (jit_debugger_t dbg) + * Destroy a debugger instance. + * @end deftypefun +@*/ +void jit_debugger_destroy(jit_debugger_t dbg) +{ + /* TODO */ +} + +/*@ + * @deftypefun jit_context_t jit_debugger_get_context (jit_debugger_t dbg) + * Get the JIT context that is associated with a debugger instance. + * @end deftypefun +@*/ +jit_context_t jit_debugger_get_context(jit_debugger_t dbg) +{ + if(dbg) + { + return dbg->context; + } + else + { + return 0; + } +} + +/*@ + * @deftypefun jit_debugger_t jit_debugger_from_context (jit_context_t context) + * Get the debugger that is currently associated with a JIT context, + * or NULL if there is no debugger associated with the context. + * @end deftypefun +@*/ +jit_debugger_t jit_debugger_from_context(jit_context_t context) +{ + if(context) + { + return context->debugger; + } + else + { + return 0; + } +} + +/*@ + * @deftypefun jit_debugger_thread_id_t jit_debugger_get_self (jit_debugger_t dbg) + * Get the thread identifier associated with the current thread. + * The return values are normally values like 1, 2, 3, etc, allowing + * the user interface to report messages like "thread 3 has stopped + * at a breakpoint". + * @end deftypefun +@*/ +jit_debugger_thread_id_t jit_debugger_get_self(jit_debugger_t dbg) +{ + /* TODO */ + return 0; +} + +/*@ + * @deftypefun jit_debugger_thread_id_t jit_debugger_get_thread (jit_debugger_t dbg, {const void *} native_thread) + * Get the thread identifier for a specific native thread. The + * @code{native_thread} pointer is assumed to point at a block + * of memory containing a native thread handle. This would be a + * @code{pthread_t} on Pthreads platforms or a @code{HANDLE} + * on Win32 platforms. If the native thread has not been seen + * previously, then a new thread identifier is allocated. + * @end deftypefun +@*/ +jit_debugger_thread_id_t jit_debugger_get_thread + (jit_debugger_t dbg, const void *native_thread) +{ + /* TODO */ + return 0; +} + +/*@ + * @deftypefun int jit_debugger_get_native_thread (jit_debugger_t dbg, jit_debugger_thread_id_t thread, {void *} native_thread) + * Get the native thread handle associated with a debugger thread identifier. + * Returns non-zero if OK, or zero if the debugger thread identifier is not + * yet associated with a native thread handle. + * @end deftypefun +@*/ +int jit_debugger_get_native_thread + (jit_debugger_t dbg, jit_debugger_thread_id_t thread, + void *native_thread) +{ + /* TODO */ + return 0; +} + +/*@ + * @deftypefun void jit_debugger_set_breakable (jit_debugger_t dbg, {const void *} native_thread, int flag) + * Set a flag that indicates if a native thread can stop at breakpoints. + * If set to 1 (the default), breakpoints will be active on the thread. + * If set to 0, breakpoints will be ignored on the thread. Typically + * this is used to mark threads associated with the debugger's user + * interface, or the virtual machine's finalization thread, so that they + * aren't accidentally suspended by the debugger (which might cause a + * deadlock). + * @end deftypefun +@*/ +void jit_debugger_set_breakable + (jit_debugger_t dbg, const void *native_thread, int flag) +{ + /* TODO */ +} + +/*@ + * @deftypefun void jit_debugger_attach_self (jit_debugger_t dbg, int stop_immediately) + * Attach the current thread to a debugger. If @code{stop_immediately} + * is non-zero, then the current thread immediately suspends, waiting for + * the user to start it with @code{jit_debugger_run}. This function is + * typically called in a thread's startup code just before any "real work" + * is performed. + * @end deftypefun +@*/ +void jit_debugger_attach_self(jit_debugger_t dbg, int stop_immediately) +{ + jit_debugger_event_t *event; + jit_debugger_thread_t th; + lock_debugger(dbg); + th = get_current_thread(dbg); + if(th) + { + event = alloc_event(); + if(event) + { + event->type = JIT_DEBUGGER_TYPE_ATTACH_THREAD; + event->thread = th->id; + event->data1 = (jit_nint)stop_immediately; + add_event(dbg, event); + th->find_func = 0; + th->last_data1 = 0; + th->last_func_data1 = 0; + if(stop_immediately) + { + th->run_type = JIT_RUN_TYPE_STOPPED; + suspend_thread(dbg, th); + } + else + { + th->run_type = JIT_RUN_TYPE_CONTINUE; + } + } + } + unlock_debugger(dbg); +} + +/*@ + * @deftypefun void jit_debugger_detach_self (jit_debugger_t dbg) + * Detach the current thread from the debugger. This is typically + * called just before the thread exits. + * @end deftypefun +@*/ +void jit_debugger_detach_self(jit_debugger_t dbg) +{ + jit_debugger_event_t *event; + jit_debugger_thread_t th; + lock_debugger(dbg); + th = get_current_thread(dbg); + if(th) + { + event = alloc_event(); + if(event) + { + event->type = JIT_DEBUGGER_TYPE_DETACH_THREAD; + event->thread = th->id; + add_event(dbg, event); + th->run_type = JIT_RUN_TYPE_DETACHED; + } + } + unlock_debugger(dbg); +} + +/*@ + * @deftypefun int jit_debugger_wait_event (jit_debugger_t dbg, {jit_debugger_event_t *} event, jit_nint timeout) + * Wait for the next debugger event to arrive. Debugger events typically + * indicate breakpoints that have occurred. The @code{timeout} is in + * milliseconds, or -1 for an infinite timeout period. Returns non-zero + * if an event has arrived, or zero on timeout. + * @end deftypefun +@*/ +int jit_debugger_wait_event + (jit_debugger_t dbg, jit_debugger_event_t *event, jit_int timeout) +{ + /* TODO */ + return 0; +} + +/*@ + * @deftypefun jit_debugger_breakpoint_id_t jit_debugger_add_breakpoint (jit_debugger_t dbg, jit_debugger_breakpoint_info_t info) + * Add a hard breakpoint to a debugger instance. The @code{info} structure + * defines the conditions under which the breakpoint should fire. + * The fields of @code{info} are as follows: + * + * @table @code + * @item flags + * Flags that indicate which of the following fields should be matched. + * If a flag is not present, then all possible values of the field will match. + * Valid flags are @code{JIT_DEBUGGER_FLAG_THREAD}, + * @code{JIT_DEBUGGER_FLAG_FUNCTION}, @code{JIT_DEBUGGER_FLAG_DATA1}, + * and @code{JIT_DEBUGGER_FLAG_DATA2}. + * + * @item thread + * The thread to match against, if @code{JIT_DEBUGGER_FLAG_THREAD} is set. + * + * @item function + * The function to match against, if @code{JIT_DEBUGGER_FLAG_FUNCTION} is set. + * + * @item data1 + * The @code{data1} value to match against, if @code{JIT_DEBUGGER_FLAG_DATA1} + * is set. + * + * @item data2 + * The @code{data2} value to match against, if @code{JIT_DEBUGGER_FLAG_DATA2} + * is set. + * @end table + * + * The following special values for @code{data1} are recommended for marking + * breakpoint locations with @code{jit_insn_mark_breakpoint}: + * + * @table @code + * @item JIT_DEBUGGER_DATA1_LINE + * Breakpoint location that corresponds to a source line. This is used + * to determine where to continue to upon a "step". + * + * @item JIT_DEBUGGER_DATA1_ENTER + * Breakpoint location that corresponds to the start of a function. + * + * @item JIT_DEBUGGER_DATA1_LEAVE + * Breakpoint location that corresponds to the end of a function, just + * prior to a @code{return} statement. This is used to determine where + * to continue to upon a "finish". + * + * @item JIT_DEBUGGER_DATA1_THROW + * Breakpoint location that corresponds to an exception throw. + * @end table + * @end deftypefun +@*/ +jit_debugger_breakpoint_id_t jit_debugger_add_breakpoint + (jit_debugger_t dbg, jit_debugger_breakpoint_info_t info) +{ + /* TODO */ + return 0; +} + +/*@ + * @deftypefun void jit_debugger_remove_breakpoint (jit_debugger_t dbg, jit_debugger_breakpoint_id_t id) + * Remove a previously defined breakpoint from a debugger instance. + * @end deftypefun +@*/ +void jit_debugger_remove_breakpoint + (jit_debugger_t dbg, jit_debugger_breakpoint_id_t id) +{ + /* TODO */ +} + +/*@ + * @deftypefun void jit_debugger_remove_all_breakpoints (jit_debugger_t dbg) + * Remove all breakpoints from a debugger instance. + * @end deftypefun +@*/ +void jit_debugger_remove_all_breakpoints(jit_debugger_t dbg) +{ + /* TODO */ +} + +/*@ + * @deftypefun int jit_debugger_is_alive (jit_debugger_t dbg, jit_debugger_thread_id_t thread) + * Determine if a particular thread is still alive. + * @end deftypefun +@*/ +int jit_debugger_is_alive(jit_debugger_t dbg, jit_debugger_thread_id_t thread) +{ + /* TODO */ + return 1; +} + +/*@ + * @deftypefun int jit_debugger_is_running (jit_debugger_t dbg, jit_debugger_thread_id_t thread) + * Determine if a particular thread is currently running (non-zero) or + * stopped (zero). + * @end deftypefun +@*/ +int jit_debugger_is_running(jit_debugger_t dbg, jit_debugger_thread_id_t thread) +{ + jit_debugger_thread_t th; + int flag = 0; + lock_debugger(dbg); + th = get_specific_thread(dbg, thread); + if(th) + { + flag = (th->run_type != JIT_RUN_TYPE_STOPPED); + } + unlock_debugger(dbg); + return flag; +} + +/*@ + * @deftypefun void jit_debugger_run (jit_debugger_t dbg, jit_debugger_thread_id_t thread) + * Start the specified thread running, or continue from the last breakpoint. + * + * This function, and the others that follow, sends a request to the specified + * thread and then returns to the caller immediately. + * @end deftypefun +@*/ +void jit_debugger_run(jit_debugger_t dbg, jit_debugger_thread_id_t thread) +{ + jit_debugger_thread_t th; + lock_debugger(dbg); + th = get_specific_thread(dbg, thread); + if(th && th->run_type == JIT_RUN_TYPE_STOPPED) + { + th->run_type = JIT_RUN_TYPE_CONTINUE; + wakeup_all(dbg); + } + unlock_debugger(dbg); +} + +/*@ + * @deftypefun void jit_debugger_step (jit_debugger_t dbg, jit_debugger_thread_id_t thread) + * Step over a single line of code. If the line performs a method call, + * then this will step into the call. The request will be ignored if + * the thread is currently running. + * @end deftypefun +@*/ +void jit_debugger_step(jit_debugger_t dbg, jit_debugger_thread_id_t thread) +{ + jit_debugger_thread_t th; + lock_debugger(dbg); + th = get_specific_thread(dbg, thread); + if(th && th->run_type == JIT_RUN_TYPE_STOPPED) + { + th->run_type = JIT_RUN_TYPE_STEP; + wakeup_all(dbg); + } + unlock_debugger(dbg); +} + +/*@ + * @deftypefun void jit_debugger_next (jit_debugger_t dbg, jit_debugger_thread_id_t thread) + * Step over a single line of code but do not step into method calls. + * The request will be ignored if the thread is currently running. + * @end deftypefun +@*/ +void jit_debugger_next(jit_debugger_t dbg, jit_debugger_thread_id_t thread) +{ + jit_debugger_thread_t th; + lock_debugger(dbg); + th = get_specific_thread(dbg, thread); + if(th && th->run_type == JIT_RUN_TYPE_STOPPED) + { + th->run_type = JIT_RUN_TYPE_NEXT; + wakeup_all(dbg); + } + unlock_debugger(dbg); +} + +/*@ + * @deftypefun void jit_debugger_finish (jit_debugger_t dbg, jit_debugger_thread_id_t thread) + * Keep running until the end of the current function. The request will + * be ignored if the thread is currently running. + * @end deftypefun +@*/ +void jit_debugger_finish(jit_debugger_t dbg, jit_debugger_thread_id_t thread) +{ + jit_debugger_thread_t th; + lock_debugger(dbg); + th = get_specific_thread(dbg, thread); + if(th && th->run_type == JIT_RUN_TYPE_STOPPED) + { + th->run_type = JIT_RUN_TYPE_FINISH; + wakeup_all(dbg); + } + unlock_debugger(dbg); +} + +/*@ + * @deftypefun void jit_debugger_break (jit_debugger_t dbg) + * Force an explicit user breakpoint at the current location within the + * current thread. Control returns to the caller when the debugger + * calls one of the above "run" or "step" functions in another thread. + * @end deftypefun +@*/ +void jit_debugger_break(jit_debugger_t dbg) +{ + jit_debugger_event_t *event; + jit_debugger_thread_t th; + lock_debugger(dbg); + th = get_current_thread(dbg); + if(th && th->breakable) + { + event = alloc_event(); + if(event) + { + th->run_type = JIT_RUN_TYPE_STOPPED; + th->find_func = 0; + th->last_data1 = 0; + th->last_func_data1 = 0; + event->type = JIT_DEBUGGER_TYPE_USER_BREAKPOINT; + event->thread = th->id; + event->trace = jit_exception_get_stack_trace(); + add_event(dbg, event); + suspend_thread(dbg, th); + } + } + unlock_debugger(dbg); +} + +/*@ + * @deftypefun void jit_debugger_quit (jit_debugger_t dbg) + * Sends a request to the thread that called @code{jit_debugger_wait_event} + * indicating that the debugger should quit. + * @end deftypefun +@*/ +void jit_debugger_quit(jit_debugger_t dbg) +{ + jit_debugger_event_t *event; + lock_debugger(dbg); + event = alloc_event(); + if(event) + { + event->type = JIT_DEBUGGER_TYPE_QUIT; + add_event(dbg, event); + } + unlock_debugger(dbg); +} + +/*@ + * @deftypefun jit_debugger_hook_func jit_debugger_set_hook (jit_context_t context, jit_debugger_hook_func hook) + * Set a debugger hook on a JIT context. Returns the previous hook. + * + * Debug hooks are a very low-level breakpoint mechanism. Upon reaching each + * breakpoint in a function, a user-supplied hook function is called. + * It is up to the hook function to decide whether to stop execution + * or to ignore the breakpoint. The hook function has the following + * prototype: + * + * @example + * void hook(jit_function_t func, jit_nint data1, jit_nint data2); + * @end example + * + * The @code{func} argument indicates the function that the breakpoint + * occurred within. The @code{data1} and @code{data2} arguments are + * those supplied to @code{jit_insn_mark_breakpoint}. The debugger can use + * these values to indicate information about the breakpoint's type + * and location. + * + * Hook functions can be used for other purposes besides breakpoint + * debugging. For example, a program could be instrumented with hooks + * that tally up the number of times that each function is called, + * or which profile the amount of time spent in each function. + * + * By convention, @code{data1} values less than 10000 are intended for + * use by user-defined hook functions. Values of 10000 and greater are + * reserved for the full-blown debugger system described earlier. + * @end deftypefun +@*/ +jit_debugger_hook_func jit_debugger_set_hook + (jit_context_t context, jit_debugger_hook_func hook) +{ + jit_debugger_hook_func prev; + if(context) + { + prev = context->debug_hook; + context->debug_hook = hook; + return prev; + } + else + { + return 0; + } +} + +void _jit_debugger_hook(jit_function_t func, jit_nint data1, jit_nint data2) +{ + jit_context_t context; + jit_debugger_t dbg; + jit_debugger_thread_t th; + jit_debugger_event_t *event; + int stop; + + /* Find the context and look for a user-supplied debug hook */ + context = func->context; + if(context->debug_hook) + { + (*(context->debug_hook))(func, data1, data2); + } + + /* Ignore breakpoints with data1 values less than 10000. These are + presumed to be handled by a user-supplied debug hook instead */ + if(data1 < JIT_DEBUGGER_DATA1_FIRST) + { + return; + } + + /* Determine if there is a debugger attached to the context */ + dbg = context->debugger; + if(!dbg) + { + return; + } + + /* Lock down the debugger while we do this */ + lock_debugger(dbg); + + /* Get the current thread's information block */ + th = get_current_thread(dbg); + if(!th || !(th->breakable)) + { + unlock_debugger(dbg); + return; + } + + /* Determine if there is a hard breakpoint at this location */ + /* TODO */ + + /* Determine if we are looking for a soft breakpoint */ + stop = 0; + switch(th->run_type) + { + case JIT_RUN_TYPE_STEP: + { + /* Stop at all breakpoints */ + stop = 1; + } + break; + + case JIT_RUN_TYPE_NEXT: + { + /* Stop only if we are in the same function as the last stopping + point, or if we might have already left the function */ + if(func == th->find_func || th->find_func == 0 || + th->last_func_data1 == JIT_DEBUGGER_DATA1_LEAVE || + th->last_data1 == JIT_DEBUGGER_DATA1_THROW) + { + stop = 1; + } + if(func == th->find_func) + { + th->last_func_data1 = data1; + } + } + break; + + case JIT_RUN_TYPE_FINISH: + { + /* Stop if we are at a leave point, or we saw an exception */ + if((func == th->find_func && data1 == JIT_DEBUGGER_DATA1_LEAVE) || + th->last_data1 == JIT_DEBUGGER_DATA1_THROW || + th->find_func == 0) + { + stop = 1; + } + } + break; + } + th->last_data1 = data1; + + /* Do we need to stop the thread at this breakpoint? */ + if(stop) + { + event = alloc_event(); + if(event) + { + th->run_type = JIT_RUN_TYPE_STOPPED; + th->find_func = func; + th->last_func_data1 = data1; + event->type = JIT_DEBUGGER_TYPE_SOFT_BREAKPOINT; + event->thread = th->id; + event->function = func; + event->data1 = data1; + event->data2 = data2; + event->trace = jit_exception_get_stack_trace(); + add_event(dbg, event); + suspend_thread(dbg, th); + } + } + + /* Unlock and exit */ + unlock_debugger(dbg); +} diff --git a/jit/jit-insn.c b/jit/jit-insn.c index b0b7a9c..968487a 100644 --- a/jit/jit-insn.c +++ b/jit/jit-insn.c @@ -7694,27 +7694,6 @@ int jit_insn_mark_offset(jit_function_t func, jit_int offset) (func, jit_type_int, offset)); } -#if !defined(JIT_BACKEND_INTERP) - -/* - * Perform first-level debug hook testing. - */ -void _jit_hook_test(jit_function_t func, jit_nint data1, jit_nint data2) -{ - if(func->breakpoints_enabled || func->context->breakpoints_enabled) - { - jit_debug_hook_func hook; - hook = (jit_debug_hook_func) - jit_context_get_meta(func->context, JIT_OPTION_DEBUG_HOOK); - if(hook) - { - (*hook)(func, data1, data2); - } - } -} - -#endif /* !JIT_BACKEND_INTERP */ - /* Documentation is in jit-debug.c */ int jit_insn_mark_breakpoint (jit_function_t func, jit_nint data1, jit_nint data2) @@ -7731,7 +7710,7 @@ int jit_insn_mark_breakpoint jit_value_create_nint_constant (func, jit_type_nint, data2)); #else - /* Insert a call to "_jit_hook_test" on native platforms */ + /* Insert a call to "_jit_debugger_hook" on native platforms */ jit_type_t params[3]; jit_type_t signature; jit_value_t values[3]; @@ -7762,7 +7741,7 @@ int jit_insn_mark_breakpoint jit_type_free(signature); return 0; } - jit_insn_call_native(func, "_jit_hook_test", (void *)_jit_hook_test, + jit_insn_call_native(func, "_jit_debugger_hook", (void *)_jit_debugger_hook, signature, values, 3, JIT_CALL_NOTHROW); jit_type_free(signature); return 1; diff --git a/jit/jit-internal.h b/jit/jit-internal.h index e1ec224..e2255aa 100644 --- a/jit/jit-internal.h +++ b/jit/jit-internal.h @@ -377,7 +377,6 @@ struct _jit_function int has_try : 1; int optimization_level : 8; int volatile is_compiled; - int volatile breakpoints_enabled; /* The entry point for the function's compiled code */ void * volatile entry_point; @@ -459,8 +458,9 @@ struct _jit_context jit_regsym_t *registered_symbols; int num_registered_symbols; - /* Flag that is set if all breakpoints in the system are enabled */ - int volatile breakpoints_enabled; + /* Debugger support */ + jit_debugger_hook_func debug_hook; + jit_debugger_t debugger; }; /* @@ -575,6 +575,11 @@ int _jit_load_opcode(int base_opcode, jit_type_t type, */ int _jit_store_opcode(int base_opcode, int small_base, jit_type_t type); +/* + * Function that is called upon each breakpoint location. + */ +void _jit_debugger_hook(jit_function_t func, jit_nint data1, jit_nint data2); + /* * Internal structure of a type descriptor. */ diff --git a/jit/jit-interp.c b/jit/jit-interp.c index 8cf2b08..b035d50 100644 --- a/jit/jit-interp.c +++ b/jit/jit-interp.c @@ -4462,20 +4462,14 @@ void _jit_run_function(jit_function_interp_t func, jit_item *args, VMCASE(JIT_OP_MARK_BREAKPOINT): { - /* Process a breakpoint within the current function */ - if(func->func->breakpoints_enabled || - func->func->context->breakpoints_enabled) - { - jit_debug_hook_func hook; - hook = (jit_debug_hook_func) - jit_context_get_meta(func->func->context, - JIT_OPTION_DEBUG_HOOK); - if(hook) - { - (*hook)(func->func, VM_NINT_ARG, VM_NINT_ARG2); - } - } + /* Process a marked breakpoint within the current function */ + tempptr = (void *)VM_NINT_ARG; + tempptr2 = (void *)VM_NINT_ARG2; VM_MODIFY_PC_AND_STACK(3, 0); + _jit_backtrace_push(&call_trace, pc); + _jit_debugger_hook + (func->func, (jit_nint)tempptr, (jit_nint)tempptr2); + _jit_backtrace_pop(); } VMBREAK; -- 2.47.3