mirror of https://github.com/ademakov/libjit
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
920 lines
25 KiB
920 lines
25 KiB
/*
|
|
* 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;
|
|
jit_thread_id_t native_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_monitor_t queue_lock;
|
|
jit_monitor_t run_lock;
|
|
jit_context_t context;
|
|
jit_debugger_linked_event_t * volatile events;
|
|
jit_debugger_linked_event_t * volatile last_event;
|
|
};
|
|
|
|
/*
|
|
* Lock the debugger object.
|
|
*/
|
|
#define lock_debugger(dbg) jit_monitor_lock(&((dbg)->run_lock))
|
|
|
|
/*
|
|
* Unlock the debugger object.
|
|
*/
|
|
#define unlock_debugger(dbg) jit_monitor_unlock(&((dbg)->run_lock))
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
while(thread->run_type == JIT_RUN_TYPE_STOPPED)
|
|
{
|
|
jit_monitor_wait(&(dbg->run_lock), -1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Wake all threads that are waiting on the debugger's monitor.
|
|
*/
|
|
#define wakeup_all(dbg) jit_monitor_signal_all(&((dbg)->run_lock))
|
|
|
|
/*
|
|
* 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;
|
|
jit_monitor_lock(&(dbg->queue_lock));
|
|
if(dbg->last_event)
|
|
{
|
|
dbg->last_event->next = event;
|
|
}
|
|
else
|
|
{
|
|
dbg->events = event;
|
|
}
|
|
dbg->last_event = event;
|
|
jit_monitor_signal(&(dbg->queue_lock));
|
|
jit_monitor_unlock(&(dbg->queue_lock));
|
|
}
|
|
|
|
/*@
|
|
* @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)
|
|
{
|
|
return JIT_THREADS_SUPPORTED;
|
|
}
|
|
|
|
/*@
|
|
* @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;
|
|
jit_monitor_create(&(dbg->queue_lock));
|
|
jit_monitor_create(&(dbg->run_lock));
|
|
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)
|
|
{
|
|
jit_thread_id_t id = jit_thread_self();
|
|
jit_debugger_thread_id_t thread;
|
|
thread = jit_debugger_get_thread(dbg, &id);
|
|
jit_thread_release_self(id);
|
|
return thread;
|
|
}
|
|
|
|
/*@
|
|
* @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)
|
|
{
|
|
jit_debugger_thread_t th;
|
|
lock_debugger(dbg);
|
|
th = get_specific_thread(dbg, thread);
|
|
if(th)
|
|
{
|
|
jit_memcpy(native_thread, &(th->native_id), sizeof(th->native_id));
|
|
unlock_debugger(dbg);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
unlock_debugger(dbg);
|
|
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)
|
|
{
|
|
jit_debugger_thread_t th;
|
|
jit_debugger_thread_id_t id;
|
|
id = jit_debugger_get_thread(dbg, native_thread);
|
|
lock_debugger(dbg);
|
|
th = get_specific_thread(dbg, id);
|
|
if(th)
|
|
{
|
|
th->breakable = flag;
|
|
}
|
|
unlock_debugger(dbg);
|
|
}
|
|
|
|
/*@
|
|
* @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)
|
|
{
|
|
jit_debugger_linked_event_t *levent;
|
|
jit_monitor_lock(&(dbg->queue_lock));
|
|
if((levent = dbg->events) == 0)
|
|
{
|
|
if(!jit_monitor_wait(&(dbg->queue_lock), timeout))
|
|
{
|
|
jit_monitor_unlock(&(dbg->queue_lock));
|
|
return 0;
|
|
}
|
|
levent = dbg->events;
|
|
}
|
|
*event = levent->event;
|
|
dbg->events = levent->next;
|
|
if(!(levent->next))
|
|
{
|
|
dbg->last_event = 0;
|
|
}
|
|
jit_free(levent);
|
|
jit_monitor_unlock(&(dbg->queue_lock));
|
|
return 1;
|
|
}
|
|
|
|
/*@
|
|
* @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);
|
|
}
|
|
|