Add interface for JIT code generation.
* NEWS: Announce JIT interface. * Makefile.in (SFILES): Add jit.c. (HFILES_NO_SRCDIR): Add jit.h. (COMMON_OBS): Add jit.o. * jit.c: New file. * jit.h: New file. * breakpoint.h (enum bptype): Add bp_jit_event to enum. * breakpoint.c: (update_breakpoints_after_exec): Delete jit breakpoints after exec. (bpstat_what): Update event table for bp_jit_event. (print_it_typical): Added case for bp_jit_event. (print_one_breakpoint_location): Added case for bp_jit_event. (allocate_bp_location): Added case for bp_jit_event. (mention): Added case for bp_jit_event. (delete_command): Added case for bp_jit_event. (breakpoint_re_set_one): Added case for bp_jit_event. (breakpoint_re_set): Added call to jit_inferior_created_hook. (create_jit_event_breakpoint): New. * infrun.c (handle_inferior_event): Add handler for jit event. (follow_exec): Add call to jit_inferior_created_hook. * doc/gdb.texinfo: Add chapter on JIT interface.
This commit is contained in:
parent
c469dcaa81
commit
4efc650796
@ -1,3 +1,27 @@
|
||||
2009-07-24 Reid Kleckner <reid@kleckner.net>
|
||||
|
||||
Add interface for JIT code generation.
|
||||
* NEWS: Announce JIT interface.
|
||||
* Makefile.in (SFILES): Add jit.c.
|
||||
(HFILES_NO_SRCDIR): Add jit.h.
|
||||
(COMMON_OBS): Add jit.o.
|
||||
* jit.c: New file.
|
||||
* jit.h: New file.
|
||||
* breakpoint.h (enum bptype): Add bp_jit_event to enum.
|
||||
* breakpoint.c:
|
||||
(update_breakpoints_after_exec): Delete jit breakpoints after exec.
|
||||
(bpstat_what): Update event table for bp_jit_event.
|
||||
(print_it_typical): Added case for bp_jit_event.
|
||||
(print_one_breakpoint_location): Added case for bp_jit_event.
|
||||
(allocate_bp_location): Added case for bp_jit_event.
|
||||
(mention): Added case for bp_jit_event.
|
||||
(delete_command): Added case for bp_jit_event.
|
||||
(breakpoint_re_set_one): Added case for bp_jit_event.
|
||||
(breakpoint_re_set): Added call to jit_inferior_created_hook.
|
||||
(create_jit_event_breakpoint): New.
|
||||
* infrun.c (handle_inferior_event): Add handler for jit event.
|
||||
(follow_exec): Add call to jit_inferior_created_hook.
|
||||
|
||||
2009-08-19 Ulrich Weigand <uweigand@de.ibm.com>
|
||||
|
||||
* value.c (enum internalvar_kind): Replace INTERNALVAR_SCALAR by
|
||||
|
@ -676,7 +676,8 @@ SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c ada-tasks.c \
|
||||
wrapper.c \
|
||||
xml-tdesc.c xml-support.c \
|
||||
inferior.c gdb_usleep.c \
|
||||
record.c
|
||||
record.c \
|
||||
jit.c
|
||||
|
||||
LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c
|
||||
|
||||
@ -745,7 +746,7 @@ config/rs6000/nm-rs6000.h top.h bsd-kvm.h gdb-stabs.h reggroups.h \
|
||||
annotate.h sim-regno.h dictionary.h dfp.h main.h frame-unwind.h \
|
||||
remote-fileio.h i386-linux-tdep.h vax-tdep.h objc-lang.h \
|
||||
sentinel-frame.h bcache.h symfile.h windows-tdep.h linux-tdep.h \
|
||||
gdb_usleep.h
|
||||
gdb_usleep.h jit.h
|
||||
|
||||
# Header files that already have srcdir in them, or which are in objdir.
|
||||
|
||||
@ -827,7 +828,8 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \
|
||||
solib.o solib-null.o \
|
||||
prologue-value.o memory-map.o xml-support.o \
|
||||
target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
|
||||
inferior.o osdata.o gdb_usleep.o record.o
|
||||
inferior.o osdata.o gdb_usleep.o record.o \
|
||||
jit.o
|
||||
|
||||
TSOBS = inflow.o
|
||||
|
||||
|
6
gdb/NEWS
6
gdb/NEWS
@ -3,6 +3,12 @@
|
||||
|
||||
*** Changes since GDB 6.8
|
||||
|
||||
* GDB now has an interface for JIT compilation. Applications that
|
||||
dynamically generate code can create symbol files in memory and register
|
||||
them with GDB. For users, the feature should work transparently, and
|
||||
for JIT developers, the interface is documented in the GDB manual in the
|
||||
"JIT Compilation Interface" chapter.
|
||||
|
||||
* Tracepoints may now be conditional. The syntax is as for
|
||||
breakpoints; either an "if" clause appended to the "trace" command,
|
||||
or the "condition" command is available. GDB sends the condition to
|
||||
|
@ -59,6 +59,7 @@
|
||||
#include "top.h"
|
||||
#include "wrapper.h"
|
||||
#include "valprint.h"
|
||||
#include "jit.h"
|
||||
|
||||
/* readline include files */
|
||||
#include "readline/readline.h"
|
||||
@ -1592,6 +1593,13 @@ update_breakpoints_after_exec (void)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* JIT breakpoints must be explicitly reset after an exec(). */
|
||||
if (b->type == bp_jit_event)
|
||||
{
|
||||
delete_breakpoint (b);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Thread event breakpoints must be set anew after an exec(),
|
||||
as must overlay event and longjmp master breakpoints. */
|
||||
if (b->type == bp_thread_event || b->type == bp_overlay_event
|
||||
@ -2583,6 +2591,7 @@ print_it_typical (bpstat bs)
|
||||
case bp_watchpoint_scope:
|
||||
case bp_call_dummy:
|
||||
case bp_tracepoint:
|
||||
case bp_jit_event:
|
||||
default:
|
||||
result = PRINT_UNKNOWN;
|
||||
break;
|
||||
@ -3308,6 +3317,9 @@ bpstat_what (bpstat bs)
|
||||
/* We hit the shared library event breakpoint. */
|
||||
shlib_event,
|
||||
|
||||
/* We hit the jit event breakpoint. */
|
||||
jit_event,
|
||||
|
||||
/* This is just used to count how many enums there are. */
|
||||
class_last
|
||||
};
|
||||
@ -3323,6 +3335,7 @@ bpstat_what (bpstat bs)
|
||||
#define clr BPSTAT_WHAT_CLEAR_LONGJMP_RESUME
|
||||
#define sr BPSTAT_WHAT_STEP_RESUME
|
||||
#define shl BPSTAT_WHAT_CHECK_SHLIBS
|
||||
#define jit BPSTAT_WHAT_CHECK_JIT
|
||||
|
||||
/* "Can't happen." Might want to print an error message.
|
||||
abort() is not out of the question, but chances are GDB is just
|
||||
@ -3343,12 +3356,13 @@ bpstat_what (bpstat bs)
|
||||
back and decide something of a lower priority is better. The
|
||||
ordering is:
|
||||
|
||||
kc < clr sgl shl slr sn sr ss
|
||||
sgl < shl slr sn sr ss
|
||||
slr < err shl sn sr ss
|
||||
clr < err shl sn sr ss
|
||||
ss < shl sn sr
|
||||
sn < shl sr
|
||||
kc < jit clr sgl shl slr sn sr ss
|
||||
sgl < jit shl slr sn sr ss
|
||||
slr < jit err shl sn sr ss
|
||||
clr < jit err shl sn sr ss
|
||||
ss < jit shl sn sr
|
||||
sn < jit shl sr
|
||||
jit < shl sr
|
||||
shl < sr
|
||||
sr <
|
||||
|
||||
@ -3366,28 +3380,18 @@ bpstat_what (bpstat bs)
|
||||
table[(int) class_last][(int) BPSTAT_WHAT_LAST] =
|
||||
{
|
||||
/* old action */
|
||||
/* kc ss sn sgl slr clr sr shl
|
||||
*/
|
||||
/*no_effect */
|
||||
{kc, ss, sn, sgl, slr, clr, sr, shl},
|
||||
/*wp_silent */
|
||||
{ss, ss, sn, ss, ss, ss, sr, shl},
|
||||
/*wp_noisy */
|
||||
{sn, sn, sn, sn, sn, sn, sr, shl},
|
||||
/*bp_nostop */
|
||||
{sgl, ss, sn, sgl, slr, slr, sr, shl},
|
||||
/*bp_silent */
|
||||
{ss, ss, sn, ss, ss, ss, sr, shl},
|
||||
/*bp_noisy */
|
||||
{sn, sn, sn, sn, sn, sn, sr, shl},
|
||||
/*long_jump */
|
||||
{slr, ss, sn, slr, slr, err, sr, shl},
|
||||
/*long_resume */
|
||||
{clr, ss, sn, err, err, err, sr, shl},
|
||||
/*step_resume */
|
||||
{sr, sr, sr, sr, sr, sr, sr, sr},
|
||||
/*shlib */
|
||||
{shl, shl, shl, shl, shl, shl, sr, shl}
|
||||
/* kc ss sn sgl slr clr sr shl jit */
|
||||
/* no_effect */ {kc, ss, sn, sgl, slr, clr, sr, shl, jit},
|
||||
/* wp_silent */ {ss, ss, sn, ss, ss, ss, sr, shl, jit},
|
||||
/* wp_noisy */ {sn, sn, sn, sn, sn, sn, sr, shl, jit},
|
||||
/* bp_nostop */ {sgl, ss, sn, sgl, slr, slr, sr, shl, jit},
|
||||
/* bp_silent */ {ss, ss, sn, ss, ss, ss, sr, shl, jit},
|
||||
/* bp_noisy */ {sn, sn, sn, sn, sn, sn, sr, shl, jit},
|
||||
/* long_jump */ {slr, ss, sn, slr, slr, err, sr, shl, jit},
|
||||
/* long_resume */ {clr, ss, sn, err, err, err, sr, shl, jit},
|
||||
/* step_resume */ {sr, sr, sr, sr, sr, sr, sr, sr, sr },
|
||||
/* shlib */ {shl, shl, shl, shl, shl, shl, sr, shl, shl},
|
||||
/* jit_event */ {jit, jit, jit, jit, jit, jit, sr, jit, jit}
|
||||
};
|
||||
|
||||
#undef kc
|
||||
@ -3400,6 +3404,7 @@ bpstat_what (bpstat bs)
|
||||
#undef sr
|
||||
#undef ts
|
||||
#undef shl
|
||||
#undef jit
|
||||
enum bpstat_what_main_action current_action = BPSTAT_WHAT_KEEP_CHECKING;
|
||||
struct bpstat_what retval;
|
||||
|
||||
@ -3470,6 +3475,9 @@ bpstat_what (bpstat bs)
|
||||
case bp_shlib_event:
|
||||
bs_class = shlib_event;
|
||||
break;
|
||||
case bp_jit_event:
|
||||
bs_class = jit_event;
|
||||
break;
|
||||
case bp_thread_event:
|
||||
case bp_overlay_event:
|
||||
case bp_longjmp_master:
|
||||
@ -3603,6 +3611,7 @@ print_one_breakpoint_location (struct breakpoint *b,
|
||||
{bp_longjmp_master, "longjmp master"},
|
||||
{bp_catchpoint, "catchpoint"},
|
||||
{bp_tracepoint, "tracepoint"},
|
||||
{bp_jit_event, "jit events"},
|
||||
};
|
||||
|
||||
static char bpenables[] = "nynny";
|
||||
@ -3731,6 +3740,7 @@ print_one_breakpoint_location (struct breakpoint *b,
|
||||
case bp_overlay_event:
|
||||
case bp_longjmp_master:
|
||||
case bp_tracepoint:
|
||||
case bp_jit_event:
|
||||
if (opts.addressprint)
|
||||
{
|
||||
annotate_field (4);
|
||||
@ -4375,6 +4385,7 @@ allocate_bp_location (struct breakpoint *bpt)
|
||||
case bp_shlib_event:
|
||||
case bp_thread_event:
|
||||
case bp_overlay_event:
|
||||
case bp_jit_event:
|
||||
case bp_longjmp_master:
|
||||
loc->loc_type = bp_loc_software_breakpoint;
|
||||
break;
|
||||
@ -4657,6 +4668,17 @@ struct lang_and_radix
|
||||
int radix;
|
||||
};
|
||||
|
||||
/* Create a breakpoint for JIT code registration and unregistration. */
|
||||
|
||||
struct breakpoint *
|
||||
create_jit_event_breakpoint (struct gdbarch *gdbarch, CORE_ADDR address)
|
||||
{
|
||||
struct breakpoint *b;
|
||||
|
||||
b = create_internal_breakpoint (gdbarch, address, bp_jit_event);
|
||||
update_global_location_list_nothrow (1);
|
||||
return b;
|
||||
}
|
||||
|
||||
void
|
||||
remove_solib_event_breakpoints (void)
|
||||
@ -5338,6 +5360,7 @@ mention (struct breakpoint *b)
|
||||
case bp_shlib_event:
|
||||
case bp_thread_event:
|
||||
case bp_overlay_event:
|
||||
case bp_jit_event:
|
||||
case bp_longjmp_master:
|
||||
break;
|
||||
}
|
||||
@ -7654,6 +7677,7 @@ delete_command (char *arg, int from_tty)
|
||||
{
|
||||
if (b->type != bp_call_dummy
|
||||
&& b->type != bp_shlib_event
|
||||
&& b->type != bp_jit_event
|
||||
&& b->type != bp_thread_event
|
||||
&& b->type != bp_overlay_event
|
||||
&& b->type != bp_longjmp_master
|
||||
@ -7673,6 +7697,7 @@ delete_command (char *arg, int from_tty)
|
||||
if (b->type != bp_call_dummy
|
||||
&& b->type != bp_shlib_event
|
||||
&& b->type != bp_thread_event
|
||||
&& b->type != bp_jit_event
|
||||
&& b->type != bp_overlay_event
|
||||
&& b->type != bp_longjmp_master
|
||||
&& b->number >= 0)
|
||||
@ -7999,6 +8024,7 @@ breakpoint_re_set_one (void *bint)
|
||||
case bp_step_resume:
|
||||
case bp_longjmp:
|
||||
case bp_longjmp_resume:
|
||||
case bp_jit_event:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -8027,6 +8053,8 @@ breakpoint_re_set (void)
|
||||
set_language (save_language);
|
||||
input_radix = save_input_radix;
|
||||
|
||||
jit_inferior_created_hook ();
|
||||
|
||||
create_overlay_event_breakpoint ("_ovly_debug_event");
|
||||
create_longjmp_master_breakpoint ("longjmp");
|
||||
create_longjmp_master_breakpoint ("_longjmp");
|
||||
|
@ -120,6 +120,9 @@ enum bptype
|
||||
bp_catchpoint,
|
||||
|
||||
bp_tracepoint,
|
||||
|
||||
/* Event for JIT compiled code generation or deletion. */
|
||||
bp_jit_event,
|
||||
};
|
||||
|
||||
/* States of enablement of breakpoint. */
|
||||
@ -554,6 +557,9 @@ enum bpstat_what_main_action
|
||||
keep checking. */
|
||||
BPSTAT_WHAT_CHECK_SHLIBS,
|
||||
|
||||
/* Check for new JITed code. */
|
||||
BPSTAT_WHAT_CHECK_JIT,
|
||||
|
||||
/* This is just used to keep track of how many enums there are. */
|
||||
BPSTAT_WHAT_LAST
|
||||
};
|
||||
@ -865,6 +871,9 @@ extern void mark_breakpoints_out (void);
|
||||
|
||||
extern void make_breakpoint_permanent (struct breakpoint *);
|
||||
|
||||
extern struct breakpoint *create_jit_event_breakpoint (struct gdbarch *,
|
||||
CORE_ADDR);
|
||||
|
||||
extern struct breakpoint *create_solib_event_breakpoint (struct gdbarch *,
|
||||
CORE_ADDR);
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
2009-08-20 Reid Kleckner <reid@kleckner.net>
|
||||
|
||||
* gdb.texinfo: Add chapter on JIT interface.
|
||||
|
||||
2009-08-07 Nick Roberts <nickrob@snap.net.nz>
|
||||
|
||||
* gdb.texinfo (Server Prefix): Explain that server prefix suppresses
|
||||
|
@ -159,6 +159,7 @@ software in general. We will miss him.
|
||||
* Emacs:: Using @value{GDBN} under @sc{gnu} Emacs
|
||||
* GDB/MI:: @value{GDBN}'s Machine Interface.
|
||||
* Annotations:: @value{GDBN}'s annotation interface.
|
||||
* JIT Interface:: Using the JIT debugging interface.
|
||||
|
||||
* GDB Bugs:: Reporting bugs in @value{GDBN}
|
||||
|
||||
@ -25921,6 +25922,136 @@ source which is being displayed. @var{addr} is in the form @samp{0x}
|
||||
followed by one or more lowercase hex digits (note that this does not
|
||||
depend on the language).
|
||||
|
||||
@node JIT Interface
|
||||
@chapter JIT Compilation Interface
|
||||
@cindex just-in-time compilation
|
||||
@cindex JIT compilation interface
|
||||
|
||||
This chapter documents @value{GDBN}'s @dfn{just-in-time} (JIT) compilation
|
||||
interface. A JIT compiler is a program or library that generates native
|
||||
executable code at runtime and executes it, usually in order to achieve good
|
||||
performance while maintaining platform independence.
|
||||
|
||||
Programs that use JIT compilation are normally difficult to debug because
|
||||
portions of their code are generated at runtime, instead of being loaded from
|
||||
object files, which is where @value{GDBN} normally finds the program's symbols
|
||||
and debug information. In order to debug programs that use JIT compilation,
|
||||
@value{GDBN} has an interface that allows the program to register in-memory
|
||||
symbol files with @value{GDBN} at runtime.
|
||||
|
||||
If you are using @value{GDBN} to debug a program that uses this interface, then
|
||||
it should work transparently so long as you have not stripped the binary. If
|
||||
you are developing a JIT compiler, then the interface is documented in the rest
|
||||
of this chapter. At this time, the only known client of this interface is the
|
||||
LLVM JIT.
|
||||
|
||||
Broadly speaking, the JIT interface mirrors the dynamic loader interface. The
|
||||
JIT compiler communicates with @value{GDBN} by writing data into a global
|
||||
variable and calling a fuction at a well-known symbol. When @value{GDBN}
|
||||
attaches, it reads a linked list of symbol files from the global variable to
|
||||
find existing code, and puts a breakpoint in the function so that it can find
|
||||
out about additional code.
|
||||
|
||||
@menu
|
||||
* Declarations:: Relevant C struct declarations
|
||||
* Registering Code:: Steps to register code
|
||||
* Unregistering Code:: Steps to unregister code
|
||||
@end menu
|
||||
|
||||
@node Declarations
|
||||
@section JIT Declarations
|
||||
|
||||
These are the relevant struct declarations that a C program should include to
|
||||
implement the interface:
|
||||
|
||||
@smallexample
|
||||
typedef enum
|
||||
@{
|
||||
JIT_NOACTION = 0,
|
||||
JIT_REGISTER_FN,
|
||||
JIT_UNREGISTER_FN
|
||||
@} jit_actions_t;
|
||||
|
||||
struct jit_code_entry
|
||||
@{
|
||||
struct jit_code_entry *next_entry;
|
||||
struct jit_code_entry *prev_entry;
|
||||
const char *symfile_addr;
|
||||
uint64_t symfile_size;
|
||||
@};
|
||||
|
||||
struct jit_descriptor
|
||||
@{
|
||||
uint32_t version;
|
||||
/* This type should be jit_actions_t, but we use uint32_t
|
||||
to be explicit about the bitwidth. */
|
||||
uint32_t action_flag;
|
||||
struct jit_code_entry *relevant_entry;
|
||||
struct jit_code_entry *first_entry;
|
||||
@};
|
||||
|
||||
/* GDB puts a breakpoint in this function. */
|
||||
void __attribute__((noinline)) __jit_debug_register_code() @{ @};
|
||||
|
||||
/* Make sure to specify the version statically, because the
|
||||
debugger may check the version before we can set it. */
|
||||
struct jit_descriptor __jit_debug_descriptor = @{ 1, 0, 0, 0 @};
|
||||
@end smallexample
|
||||
|
||||
If the JIT is multi-threaded, then it is important that the JIT synchronize any
|
||||
modifications to this global data properly, which can easily be done by putting
|
||||
a global mutex around modifications to these structures.
|
||||
|
||||
@node Registering Code
|
||||
@section Registering Code
|
||||
|
||||
To register code with @value{GDBN}, the JIT should follow this protocol:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
Generate an object file in memory with symbols and other desired debug
|
||||
information. The file must include the virtual addresses of the sections.
|
||||
|
||||
@item
|
||||
Create a code entry for the file, which gives the start and size of the symbol
|
||||
file.
|
||||
|
||||
@item
|
||||
Add it to the linked list in the JIT descriptor.
|
||||
|
||||
@item
|
||||
Point the relevant_entry field of the descriptor at the entry.
|
||||
|
||||
@item
|
||||
Set @code{action_flag} to @code{JIT_REGISTER} and call
|
||||
@code{__jit_debug_register_code}.
|
||||
@end itemize
|
||||
|
||||
When @value{GDBN} is attached and the breakpoint fires, @value{GDBN} uses the
|
||||
@code{relevant_entry} pointer so it doesn't have to walk the list looking for
|
||||
new code. However, the linked list must still be maintained in order to allow
|
||||
@value{GDBN} to attach to a running process and still find the symbol files.
|
||||
|
||||
@node Unregistering Code
|
||||
@section Unregistering Code
|
||||
|
||||
If code is freed, then the JIT should use the following protocol:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
Remove the code entry corresponding to the code from the linked list.
|
||||
|
||||
@item
|
||||
Point the @code{relevant_entry} field of the descriptor at the code entry.
|
||||
|
||||
@item
|
||||
Set @code{action_flag} to @code{JIT_UNREGISTER} and call
|
||||
@code{__jit_debug_register_code}.
|
||||
@end itemize
|
||||
|
||||
If the JIT frees or recompiles code without unregistering it, then @value{GDBN}
|
||||
and the JIT will leak the memory used for the associated symbol files.
|
||||
|
||||
@node GDB Bugs
|
||||
@chapter Reporting Bugs in @value{GDBN}
|
||||
@cindex bugs in @value{GDBN}
|
||||
|
19
gdb/infrun.c
19
gdb/infrun.c
@ -50,6 +50,7 @@
|
||||
#include "event-top.h"
|
||||
#include "record.h"
|
||||
#include "inline-frame.h"
|
||||
#include "jit.h"
|
||||
|
||||
/* Prototypes for local functions */
|
||||
|
||||
@ -544,6 +545,8 @@ follow_exec (ptid_t pid, char *execd_pathname)
|
||||
solib_create_inferior_hook ();
|
||||
#endif
|
||||
|
||||
jit_inferior_created_hook ();
|
||||
|
||||
/* Reinsert all breakpoints. (Those which were symbolic have
|
||||
been reset to the proper address in the new a.out, thanks
|
||||
to symbol_file_command...) */
|
||||
@ -3540,6 +3543,22 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (!gdbarch_get_longjmp_target)\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case BPSTAT_WHAT_CHECK_JIT:
|
||||
if (debug_infrun)
|
||||
fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_CHECK_JIT\n");
|
||||
|
||||
/* Switch terminal for any messages produced by breakpoint_re_set. */
|
||||
target_terminal_ours_for_output ();
|
||||
|
||||
jit_event_handler ();
|
||||
|
||||
target_terminal_inferior ();
|
||||
|
||||
/* We want to step over this breakpoint, then keep going. */
|
||||
ecs->event_thread->stepping_over_breakpoint = 1;
|
||||
|
||||
break;
|
||||
|
||||
case BPSTAT_WHAT_LAST:
|
||||
/* Not a real code, but listed here to shut up gcc -Wall. */
|
||||
|
||||
|
438
gdb/jit.c
Normal file
438
gdb/jit.c
Normal file
@ -0,0 +1,438 @@
|
||||
/* Handle JIT code generation in the inferior for GDB, the GNU Debugger.
|
||||
|
||||
Copyright (C) 2009
|
||||
Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GDB.
|
||||
|
||||
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 3 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, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
#include "jit.h"
|
||||
#include "breakpoint.h"
|
||||
#include "gdbcore.h"
|
||||
#include "observer.h"
|
||||
#include "objfiles.h"
|
||||
#include "symfile.h"
|
||||
#include "symtab.h"
|
||||
#include "target.h"
|
||||
#include "gdb_stat.h"
|
||||
|
||||
static const struct objfile_data *jit_objfile_data;
|
||||
|
||||
static const char *const jit_break_name = "__jit_debug_register_code";
|
||||
|
||||
static const char *const jit_descriptor_name = "__jit_debug_descriptor";
|
||||
|
||||
/* This is the address of the JIT descriptor in the inferior. */
|
||||
|
||||
static CORE_ADDR jit_descriptor_addr = 0;
|
||||
|
||||
/* This is a boolean indicating whether we're currently registering code. This
|
||||
is used to avoid re-entering the registration code. We want to check for
|
||||
new JITed every time a new object file is loaded, but we want to avoid
|
||||
checking for new code while we're registering object files for JITed code.
|
||||
Therefore, we flip this variable to 1 before registering new object files,
|
||||
and set it to 0 before returning. */
|
||||
|
||||
static int registering_code = 0;
|
||||
|
||||
/* Helper cleanup function to clear an integer flag like the one above. */
|
||||
|
||||
static void
|
||||
clear_int (void *int_addr)
|
||||
{
|
||||
*((int *) int_addr) = 0;
|
||||
}
|
||||
|
||||
struct target_buffer
|
||||
{
|
||||
CORE_ADDR base;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
/* Openning the file is a no-op. */
|
||||
|
||||
static void *
|
||||
mem_bfd_iovec_open (struct bfd *abfd, void *open_closure)
|
||||
{
|
||||
return open_closure;
|
||||
}
|
||||
|
||||
/* Closing the file is just freeing the base/size pair on our side. */
|
||||
|
||||
static int
|
||||
mem_bfd_iovec_close (struct bfd *abfd, void *stream)
|
||||
{
|
||||
xfree (stream);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* For reading the file, we just need to pass through to target_read_memory and
|
||||
fix up the arguments and return values. */
|
||||
|
||||
static file_ptr
|
||||
mem_bfd_iovec_pread (struct bfd *abfd, void *stream, void *buf,
|
||||
file_ptr nbytes, file_ptr offset)
|
||||
{
|
||||
int err;
|
||||
struct target_buffer *buffer = (struct target_buffer *) stream;
|
||||
|
||||
/* If this read will read all of the file, limit it to just the rest. */
|
||||
if (offset + nbytes > buffer->size)
|
||||
nbytes = buffer->size - offset;
|
||||
|
||||
/* If there are no more bytes left, we've reached EOF. */
|
||||
if (nbytes == 0)
|
||||
return 0;
|
||||
|
||||
err = target_read_memory (buffer->base + offset, (gdb_byte *) buf, nbytes);
|
||||
if (err)
|
||||
return -1;
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
/* For statting the file, we only support the st_size attribute. */
|
||||
|
||||
static int
|
||||
mem_bfd_iovec_stat (struct bfd *abfd, void *stream, struct stat *sb)
|
||||
{
|
||||
struct target_buffer *buffer = (struct target_buffer*) stream;
|
||||
|
||||
sb->st_size = buffer->size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Open a BFD from the target's memory. */
|
||||
|
||||
static struct bfd *
|
||||
bfd_open_from_target_memory (CORE_ADDR addr, size_t size, char *target)
|
||||
{
|
||||
const char *filename = xstrdup ("<in-memory>");
|
||||
struct target_buffer *buffer = xmalloc (sizeof (struct target_buffer));
|
||||
|
||||
buffer->base = addr;
|
||||
buffer->size = size;
|
||||
return bfd_openr_iovec (filename, target,
|
||||
mem_bfd_iovec_open,
|
||||
buffer,
|
||||
mem_bfd_iovec_pread,
|
||||
mem_bfd_iovec_close,
|
||||
mem_bfd_iovec_stat);
|
||||
}
|
||||
|
||||
/* Helper function for reading the global JIT descriptor from remote memory. */
|
||||
|
||||
static void
|
||||
jit_read_descriptor (struct jit_descriptor *descriptor)
|
||||
{
|
||||
int err;
|
||||
struct type *ptr_type;
|
||||
int ptr_size;
|
||||
int desc_size;
|
||||
gdb_byte *desc_buf;
|
||||
enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch);
|
||||
|
||||
/* Figure out how big the descriptor is on the remote and how to read it. */
|
||||
ptr_type = builtin_type (target_gdbarch)->builtin_data_ptr;
|
||||
ptr_size = TYPE_LENGTH (ptr_type);
|
||||
desc_size = 8 + 2 * ptr_size; /* Two 32-bit ints and two pointers. */
|
||||
desc_buf = alloca (desc_size);
|
||||
|
||||
/* Read the descriptor. */
|
||||
err = target_read_memory (jit_descriptor_addr, desc_buf, desc_size);
|
||||
if (err)
|
||||
error (_("Unable to read JIT descriptor from remote memory!"));
|
||||
|
||||
/* Fix the endianness to match the host. */
|
||||
descriptor->version = extract_unsigned_integer (&desc_buf[0], 4, byte_order);
|
||||
descriptor->action_flag =
|
||||
extract_unsigned_integer (&desc_buf[4], 4, byte_order);
|
||||
descriptor->relevant_entry = extract_typed_address (&desc_buf[8], ptr_type);
|
||||
descriptor->first_entry =
|
||||
extract_typed_address (&desc_buf[8 + ptr_size], ptr_type);
|
||||
}
|
||||
|
||||
/* Helper function for reading a JITed code entry from remote memory. */
|
||||
|
||||
static void
|
||||
jit_read_code_entry (CORE_ADDR code_addr, struct jit_code_entry *code_entry)
|
||||
{
|
||||
int err;
|
||||
struct type *ptr_type;
|
||||
int ptr_size;
|
||||
int entry_size;
|
||||
gdb_byte *entry_buf;
|
||||
enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch);
|
||||
|
||||
/* Figure out how big the entry is on the remote and how to read it. */
|
||||
ptr_type = builtin_type (target_gdbarch)->builtin_data_ptr;
|
||||
ptr_size = TYPE_LENGTH (ptr_type);
|
||||
entry_size = 3 * ptr_size + 8; /* Three pointers and one 64-bit int. */
|
||||
entry_buf = alloca (entry_size);
|
||||
|
||||
/* Read the entry. */
|
||||
err = target_read_memory (code_addr, entry_buf, entry_size);
|
||||
if (err)
|
||||
error (_("Unable to read JIT code entry from remote memory!"));
|
||||
|
||||
/* Fix the endianness to match the host. */
|
||||
ptr_type = builtin_type (target_gdbarch)->builtin_data_ptr;
|
||||
code_entry->next_entry = extract_typed_address (&entry_buf[0], ptr_type);
|
||||
code_entry->prev_entry =
|
||||
extract_typed_address (&entry_buf[ptr_size], ptr_type);
|
||||
code_entry->symfile_addr =
|
||||
extract_typed_address (&entry_buf[2 * ptr_size], ptr_type);
|
||||
code_entry->symfile_size =
|
||||
extract_unsigned_integer (&entry_buf[3 * ptr_size], 8, byte_order);
|
||||
}
|
||||
|
||||
/* This function registers code associated with a JIT code entry. It uses the
|
||||
pointer and size pair in the entry to read the symbol file from the remote
|
||||
and then calls symbol_file_add_from_local_memory to add it as though it were
|
||||
a symbol file added by the user. */
|
||||
|
||||
static void
|
||||
jit_register_code (CORE_ADDR entry_addr, struct jit_code_entry *code_entry)
|
||||
{
|
||||
bfd *nbfd;
|
||||
struct section_addr_info *sai;
|
||||
struct bfd_section *sec;
|
||||
struct objfile *objfile;
|
||||
struct cleanup *old_cleanups, *my_cleanups;
|
||||
int i;
|
||||
const struct bfd_arch_info *b;
|
||||
CORE_ADDR *entry_addr_ptr;
|
||||
|
||||
nbfd = bfd_open_from_target_memory (code_entry->symfile_addr,
|
||||
code_entry->symfile_size, gnutarget);
|
||||
old_cleanups = make_cleanup_bfd_close (nbfd);
|
||||
|
||||
/* Check the format. NOTE: This initializes important data that GDB uses!
|
||||
We would segfault later without this line. */
|
||||
if (!bfd_check_format (nbfd, bfd_object))
|
||||
{
|
||||
printf_unfiltered (_("\
|
||||
JITed symbol file is not an object file, ignoring it.\n"));
|
||||
do_cleanups (old_cleanups);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check bfd arch. */
|
||||
b = gdbarch_bfd_arch_info (target_gdbarch);
|
||||
if (b->compatible (b, bfd_get_arch_info (nbfd)) != b)
|
||||
warning (_("JITed object file architecture %s is not compatible "
|
||||
"with target architecture %s."), bfd_get_arch_info
|
||||
(nbfd)->printable_name, b->printable_name);
|
||||
|
||||
/* Read the section address information out of the symbol file. Since the
|
||||
file is generated by the JIT at runtime, it should all of the absolute
|
||||
addresses that we care about. */
|
||||
sai = alloc_section_addr_info (bfd_count_sections (nbfd));
|
||||
make_cleanup_free_section_addr_info (sai);
|
||||
i = 0;
|
||||
for (sec = nbfd->sections; sec != NULL; sec = sec->next)
|
||||
if ((bfd_get_section_flags (nbfd, sec) & (SEC_ALLOC|SEC_LOAD)) != 0)
|
||||
{
|
||||
/* We assume that these virtual addresses are absolute, and do not
|
||||
treat them as offsets. */
|
||||
sai->other[i].addr = bfd_get_section_vma (nbfd, sec);
|
||||
sai->other[i].name = (char *) bfd_get_section_name (nbfd, sec);
|
||||
sai->other[i].sectindex = sec->index;
|
||||
++i;
|
||||
}
|
||||
|
||||
/* Raise this flag while we register code so we won't trigger any
|
||||
re-registration. */
|
||||
registering_code = 1;
|
||||
my_cleanups = make_cleanup (clear_int, ®istering_code);
|
||||
|
||||
/* This call takes ownership of sai. */
|
||||
objfile = symbol_file_add_from_bfd (nbfd, 0, sai, OBJF_SHARED);
|
||||
|
||||
/* Clear the registering_code flag. */
|
||||
do_cleanups (my_cleanups);
|
||||
|
||||
/* Remember a mapping from entry_addr to objfile. */
|
||||
entry_addr_ptr = xmalloc (sizeof (CORE_ADDR));
|
||||
*entry_addr_ptr = entry_addr;
|
||||
set_objfile_data (objfile, jit_objfile_data, entry_addr_ptr);
|
||||
|
||||
discard_cleanups (old_cleanups);
|
||||
}
|
||||
|
||||
/* This function unregisters JITed code and frees the corresponding objfile. */
|
||||
|
||||
static void
|
||||
jit_unregister_code (struct objfile *objfile)
|
||||
{
|
||||
free_objfile (objfile);
|
||||
}
|
||||
|
||||
/* Look up the objfile with this code entry address. */
|
||||
|
||||
static struct objfile *
|
||||
jit_find_objf_with_entry_addr (CORE_ADDR entry_addr)
|
||||
{
|
||||
struct objfile *objf;
|
||||
CORE_ADDR *objf_entry_addr;
|
||||
|
||||
ALL_OBJFILES (objf)
|
||||
{
|
||||
objf_entry_addr = (CORE_ADDR *) objfile_data (objf, jit_objfile_data);
|
||||
if (objf_entry_addr != NULL && *objf_entry_addr == entry_addr)
|
||||
return objf;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
jit_inferior_created_hook (void)
|
||||
{
|
||||
struct minimal_symbol *reg_symbol;
|
||||
struct minimal_symbol *desc_symbol;
|
||||
CORE_ADDR reg_addr;
|
||||
struct jit_descriptor descriptor;
|
||||
struct jit_code_entry cur_entry;
|
||||
CORE_ADDR cur_entry_addr;
|
||||
struct cleanup *old_cleanups;
|
||||
|
||||
/* When we register code, GDB resets its breakpoints in case symbols have
|
||||
changed. That in turn calls this handler, which makes us look for new
|
||||
code again. To avoid being re-entered, we check this flag. */
|
||||
if (registering_code)
|
||||
return;
|
||||
|
||||
/* Lookup the registration symbol. If it is missing, then we assume we are
|
||||
not attached to a JIT. */
|
||||
reg_symbol = lookup_minimal_symbol (jit_break_name, NULL, NULL);
|
||||
if (reg_symbol == NULL)
|
||||
return;
|
||||
reg_addr = SYMBOL_VALUE_ADDRESS (reg_symbol);
|
||||
if (reg_addr == 0)
|
||||
return;
|
||||
|
||||
/* Lookup the descriptor symbol and cache the addr. If it is missing, we
|
||||
assume we are not attached to a JIT and return early. */
|
||||
desc_symbol = lookup_minimal_symbol (jit_descriptor_name, NULL, NULL);
|
||||
if (desc_symbol == NULL)
|
||||
return;
|
||||
jit_descriptor_addr = SYMBOL_VALUE_ADDRESS (desc_symbol);
|
||||
if (jit_descriptor_addr == 0)
|
||||
return;
|
||||
|
||||
/* Read the descriptor so we can check the version number and load any already
|
||||
JITed functions. */
|
||||
jit_read_descriptor (&descriptor);
|
||||
|
||||
/* Check that the version number agrees with that we support. */
|
||||
if (descriptor.version != 1)
|
||||
error (_("Unsupported JIT protocol version in descriptor!"));
|
||||
|
||||
/* Put a breakpoint in the registration symbol. */
|
||||
create_jit_event_breakpoint (target_gdbarch, reg_addr);
|
||||
|
||||
/* If we've attached to a running program, we need to check the descriptor to
|
||||
register any functions that were already generated. */
|
||||
for (cur_entry_addr = descriptor.first_entry;
|
||||
cur_entry_addr != 0;
|
||||
cur_entry_addr = cur_entry.next_entry)
|
||||
{
|
||||
jit_read_code_entry (cur_entry_addr, &cur_entry);
|
||||
|
||||
/* This hook may be called many times during setup, so make sure we don't
|
||||
add the same symbol file twice. */
|
||||
if (jit_find_objf_with_entry_addr (cur_entry_addr) != NULL)
|
||||
continue;
|
||||
|
||||
jit_register_code (cur_entry_addr, &cur_entry);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wrapper to match the observer function pointer prototype. */
|
||||
|
||||
static void
|
||||
jit_inferior_created_hook1 (struct target_ops *objfile, int from_tty)
|
||||
{
|
||||
jit_inferior_created_hook ();
|
||||
}
|
||||
|
||||
/* This function cleans up any code entries left over when the inferior exits.
|
||||
We get left over code when the inferior exits without unregistering its code,
|
||||
for example when it crashes. */
|
||||
|
||||
static void
|
||||
jit_inferior_exit_hook (int pid)
|
||||
{
|
||||
struct objfile *objf;
|
||||
struct objfile *temp;
|
||||
|
||||
/* We need to reset the descriptor addr so that next time we load up the
|
||||
inferior we look for it again. */
|
||||
jit_descriptor_addr = 0;
|
||||
|
||||
ALL_OBJFILES_SAFE (objf, temp)
|
||||
if (objfile_data (objf, jit_objfile_data) != NULL)
|
||||
jit_unregister_code (objf);
|
||||
}
|
||||
|
||||
void
|
||||
jit_event_handler (void)
|
||||
{
|
||||
struct jit_descriptor descriptor;
|
||||
struct jit_code_entry code_entry;
|
||||
CORE_ADDR entry_addr;
|
||||
struct objfile *objf;
|
||||
|
||||
/* Read the descriptor from remote memory. */
|
||||
jit_read_descriptor (&descriptor);
|
||||
entry_addr = descriptor.relevant_entry;
|
||||
|
||||
/* Do the corresponding action. */
|
||||
switch (descriptor.action_flag)
|
||||
{
|
||||
case JIT_NOACTION:
|
||||
break;
|
||||
case JIT_REGISTER:
|
||||
jit_read_code_entry (entry_addr, &code_entry);
|
||||
jit_register_code (entry_addr, &code_entry);
|
||||
break;
|
||||
case JIT_UNREGISTER:
|
||||
objf = jit_find_objf_with_entry_addr (entry_addr);
|
||||
if (objf == NULL)
|
||||
printf_unfiltered ("Unable to find JITed code entry at address: %p\n",
|
||||
(void *) entry_addr);
|
||||
else
|
||||
jit_unregister_code (objf);
|
||||
|
||||
break;
|
||||
default:
|
||||
error (_("Unknown action_flag value in JIT descriptor!"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Provide a prototype to silence -Wmissing-prototypes. */
|
||||
|
||||
extern void _initialize_jit (void);
|
||||
|
||||
void
|
||||
_initialize_jit (void)
|
||||
{
|
||||
observer_attach_inferior_created (jit_inferior_created_hook1);
|
||||
observer_attach_inferior_exit (jit_inferior_exit_hook);
|
||||
jit_objfile_data = register_objfile_data ();
|
||||
}
|
77
gdb/jit.h
Normal file
77
gdb/jit.h
Normal file
@ -0,0 +1,77 @@
|
||||
/* JIT declarations for GDB, the GNU Debugger.
|
||||
|
||||
Copyright (C) 2009
|
||||
Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GDB.
|
||||
|
||||
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 3 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, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef JIT_H
|
||||
#define JIT_H
|
||||
|
||||
/* When the JIT breakpoint fires, the inferior wants us to take one of these
|
||||
actions. These values are used by the inferior, so the values of these enums
|
||||
cannot be changed. */
|
||||
|
||||
typedef enum
|
||||
{
|
||||
JIT_NOACTION = 0,
|
||||
JIT_REGISTER,
|
||||
JIT_UNREGISTER
|
||||
} jit_actions_t;
|
||||
|
||||
/* This struct describes a single symbol file in a linked list of symbol files
|
||||
describing generated code. As the inferior generates code, it adds these
|
||||
entries to the list, and when we attach to the inferior, we read them all.
|
||||
For the first element prev_entry should be NULL, and for the last element
|
||||
next_entry should be NULL. */
|
||||
|
||||
struct jit_code_entry
|
||||
{
|
||||
CORE_ADDR next_entry;
|
||||
CORE_ADDR prev_entry;
|
||||
CORE_ADDR symfile_addr;
|
||||
uint64_t symfile_size;
|
||||
};
|
||||
|
||||
/* This is the global descriptor that the inferior uses to communicate
|
||||
information to the debugger. To alert the debugger to take an action, the
|
||||
inferior sets the action_flag to the appropriate enum value, updates
|
||||
relevant_entry to point to the relevant code entry, and calls the function at
|
||||
the well-known symbol with our breakpoint. We then read this descriptor from
|
||||
another global well-known symbol. */
|
||||
|
||||
struct jit_descriptor
|
||||
{
|
||||
uint32_t version;
|
||||
/* This should be jit_actions_t, but we want to be specific about the
|
||||
bit-width. */
|
||||
uint32_t action_flag;
|
||||
CORE_ADDR relevant_entry;
|
||||
CORE_ADDR first_entry;
|
||||
};
|
||||
|
||||
/* Looks for the descriptor and registration symbols and breakpoints the
|
||||
registration function. If it finds both, it registers all the already JITed
|
||||
code. If it has already found the symbols, then it doesn't try again. */
|
||||
|
||||
extern void jit_inferior_created_hook (void);
|
||||
|
||||
/* This function is called by handle_inferior_event when it decides that the JIT
|
||||
event breakpoint has fired. */
|
||||
|
||||
extern void jit_event_handler (void);
|
||||
|
||||
#endif /* JIT_H */
|
Loading…
x
Reference in New Issue
Block a user