

12. Hooking a breakpoint debugger into libjit

The 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 jit_insn_mark_breakpoint:

Function: int jit_insn_mark_breakpoint (jit_function_t func, jit_nint data1, jit_nint data2)

Mark the current position in func as corresponding to a breakpoint location. When a break occurs, the debugging routines are passed func, data1, and data2 as arguments. By convention, data1 is the type of breakpoint (source line, function entry, function exit, etc).

There are two ways for a front end to receive notification about breakpoints. The bulk of this chapter describes the 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 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:

  1. Event thread which calls jit_debugger_wait_event to receive notifications of breakpoints and other interesting events.
  2. User interface thread which calls functions like jit_debugger_run, jit_debugger_step, etc, to control the debug process.

These two threads should be set to "unbreakable" with a call to 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.

Events have the following members:


The type of event (see the next table for details).


The thread that the event occurred on.


The function that the breakpoint occurred within.


The data values at the breakpoint. These values are inserted into the function’s code with jit_insn_mark_breakpoint.


The identifier for the breakpoint.


The stack trace corresponding to the location where the breakpoint occurred. This value is automatically freed upon the next call to jit_debugger_wait_event. If you wish to preserve the value, then you must call jit_stack_trace_copy.

The following event types are currently supported:


A thread called jit_debugger_quit, indicating that it wanted the event thread to terminate.


A thread stopped at a hard breakpoint. That is, a breakpoint defined by a call to jit_debugger_add_breakpoint.


A thread stopped at a breakpoint that wasn’t explicitly defined by a call to jit_debugger_add_breakpoint. This typicaly results from a call to a "step" function like jit_debugger_step, where execution stopped at the next line but there isn’t an explicit breakpoint on that line.


A thread stopped because of a call to jit_debugger_break.


A thread called jit_debugger_attach_self. The data1 field of the event is set to the value of stop_immediately for the call.


A thread called jit_debugger_detach_self.

Function: int jit_insn_mark_breakpoint_variable (jit_function_t func, jit_value_t data1, jit_value_t data2)

This function is similar to jit_insn_mark_breakpoint except that values in data1 and data2 can be computed at runtime. You can use this function for example to get address of local variable.

Function: int jit_debugging_possible (void)

Determine if debugging is possible. i.e. that threading is available and compatible with the debugger’s requirements.

Function: jit_debugger_t jit_debugger_create (jit_context_t context)

Create a new debugger instance and attach it to a JIT context. If the context already has a debugger associated with it, then this function will return the previous debugger.

Function: void jit_debugger_destroy (jit_debugger_t dbg)

Destroy a debugger instance.

Function: jit_context_t jit_debugger_get_context (jit_debugger_t dbg)

Get the JIT context that is associated with a debugger instance.

Function: 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.

Function: 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".

Function: 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 native_thread pointer is assumed to point at a block of memory containing a native thread handle. This would be a pthread_t on Pthreads platforms or a HANDLE on Win32 platforms. If the native thread has not been seen previously, then a new thread identifier is allocated.

Function: 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.

Function: 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).

Function: void jit_debugger_attach_self (jit_debugger_t dbg, int stop_immediately)

Attach the current thread to a debugger. If stop_immediately is non-zero, then the current thread immediately suspends, waiting for the user to start it with jit_debugger_run. This function is typically called in a thread’s startup code just before any "real work" is performed.

Function: 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.

Function: 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 timeout is in milliseconds, or -1 for an infinite timeout period. Returns non-zero if an event has arrived, or zero on timeout.

Function: 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 info structure defines the conditions under which the breakpoint should fire. The fields of info are as follows:


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 JIT_DEBUGGER_FLAG_THREAD, JIT_DEBUGGER_FLAG_FUNCTION, JIT_DEBUGGER_FLAG_DATA1, and JIT_DEBUGGER_FLAG_DATA2.


The thread to match against, if JIT_DEBUGGER_FLAG_THREAD is set.


The function to match against, if JIT_DEBUGGER_FLAG_FUNCTION is set.


The data1 value to match against, if JIT_DEBUGGER_FLAG_DATA1 is set.


The data2 value to match against, if JIT_DEBUGGER_FLAG_DATA2 is set.

The following special values for data1 are recommended for marking breakpoint locations with jit_insn_mark_breakpoint:


Breakpoint location that corresponds to a source line. This is used to determine where to continue to upon a "step".


Breakpoint location that corresponds to the start of a function.


Breakpoint location that corresponds to the end of a function, just prior to a return statement. This is used to determine where to continue to upon a "finish".


Breakpoint location that corresponds to an exception throw.

Function: void jit_debugger_remove_breakpoint (jit_debugger_t dbg, jit_debugger_breakpoint_id_t id)

Remove a previously defined breakpoint from a debugger instance.

Function: void jit_debugger_remove_all_breakpoints (jit_debugger_t dbg)

Remove all breakpoints from a debugger instance.

Function: int jit_debugger_is_alive (jit_debugger_t dbg, jit_debugger_thread_id_t thread)

Determine if a particular thread is still alive.

Function: 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).

Function: 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.

Function: 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.

Function: 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.

Function: 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.

Function: 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.

Function: void jit_debugger_quit (jit_debugger_t dbg)

Sends a request to the thread that called jit_debugger_wait_event indicating that the debugger should quit.

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

void hook(jit_function_t func, jit_nint data1, jit_nint data2);

The func argument indicates the function that the breakpoint occurred within. The data1 and data2 arguments are those supplied to 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, 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.

