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:
Doug Evans 2009-08-20 18:02:48 +00:00
parent c469dcaa81
commit 4efc650796
10 changed files with 769 additions and 31 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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);

View File

@ -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

View File

@ -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}

View File

@ -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
View 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, &registering_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
View 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 */