Re: [unladen-swallow] Re: [RFA] Add interface for registering JITed code

Reid Kleckner
2009-07-31T18:18:04Z

[-- Attachment #1: Type: text/plain, Size: 2517 bytes --]

On Fri, Jul 31, 2009 at 8:01 AM, Thiago Jung
Bauermann<thiago.bauermann@gmail.com> wrote:
>
> Ok, I had one night's sleep over this, and just re-read the thread. :-)
>
> Em Quinta-feira 30 Julho 2009 15:01:17 Thiago Jung Bauermann escreveu:
>> Em Quinta-feira 30 Julho 2009 12:12:50 Tom Tromey escreveu:
>> > >>>>> "Reid" == Reid Kleckner <rnk@mit.edu> writes:
>> >
>> > Tom> Yeah.  This would work if you had two JITs in a process, say loaded
>> > Tom> dynamically, and the various __jit symbols had hidden visibility.
>> >
>> > Reid> I think if someone has that use case, then they can write a client
>> > Reid> side library that wraps the __jit symbols and make the two JITs
>> > call Reid> that.
>> >
>> > Yeah, I changed my mind, let's not worry about it.  If it ever comes up,
>> > we can deal with it then without breaking compatibility.
>>
>> If/when someone wants to add another scripting language to GDB, we will be
>> bitten by this issue. I can see that happening already. :-)
>
> I still think this will happen soon enough ...
>
>> You're saying that the current patch is extensible to deal with that?
>
> ... but I agree with Reid in that the inferior (or client) can deal with that
> case internally (also it seems fair, since it is the client who wanted to have
> two JITs in the first place. :-) ). It is nice that the GDB-facing interface is
> as simple as possible.
>
> Also the protocol is versioned. That is awesome. I don't have worries
> regarding this issue.

Great, I really didn't want to tackle that issue from the GDB side. :)

>> Also, changing subjects: it is important that this feature works on core
>> files, or at least is forward-compatible with such a feature. I think this
>> needs to be thought through before the patch goes in.
>
> I still think that the patch needs to be tested with a core file scenario
> before it is checked in. Since said scenario is similar to attaching to a
> running process, it will likely magically work, though.

Well, it didn't magically work because the jit_inferior_create_hook
was getting called twice for core files instead of once. What would
happen is that the binary would get loaded, the hook called, there
would be no debug info, the memory would get loaded, it would get
called again, and it would think that it already read the descriptor.
So I made it look for new code every time just in case, and now it
works.

Here's an updated patch. What else needs to be dealt with?

Reid

[-- Attachment #2: jit-patch.txt --]
[-- Type: text/plain, Size: 36129 bytes --]

2009-07-28 Reid Kleckner <reid@kleckner.net>

Add interface for JIT code generation.
* NEWS: Announce JIT interface.
* doc/gdb.texinfo: Add chapter on 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.
* objfiles.c (free_objfile): Fixed a memory leak.

Index: gdb/Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/Makefile.in,v
retrieving revision 1.1091
diff -u -p -u -r1.1091 Makefile.in
--- gdb/Makefile.in 3 Jul 2009 12:06:35 -0000 1.1091
+++ gdb/Makefile.in 31 Jul 2009 18:03:35 -0000
@@ -677,7 +677,8 @@ SFILES = ada-exp.y ada-lang.c ada-typepr
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

@@ -746,7 +747,7 @@ config/rs6000/nm-rs6000.h top.h bsd-kvm.
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.

@@ -828,7 +829,8 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
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

Index: gdb/NEWS
===================================================================
RCS file: /cvs/src/src/gdb/NEWS,v
retrieving revision 1.319
diff -u -p -u -r1.319 NEWS
--- gdb/NEWS 20 Jul 2009 18:51:41 -0000 1.319
+++ gdb/NEWS 31 Jul 2009 18:03:35 -0000
@@ -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
Index: gdb/breakpoint.c
===================================================================
RCS file: /cvs/src/src/gdb/breakpoint.c,v
retrieving revision 1.413
diff -u -p -u -r1.413 breakpoint.c
--- gdb/breakpoint.c 7 Jul 2009 22:21:09 -0000 1.413
+++ gdb/breakpoint.c 31 Jul 2009 18:03:35 -0000
@@ -59,6 +59,7 @@
#include "top.h"
#include "wrapper.h"
#include "valprint.h"
+#include "jit.h"

/* readline include files */
#include "readline/readline.h"
@@ -1575,6 +1576,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
@@ -2573,6 +2581,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;
@@ -3298,6 +3307,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
};
@@ -3313,6 +3325,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
@@ -3333,12 +3346,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 <

@@ -3356,28 +3370,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
@@ -3390,6 +3394,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;

@@ -3460,6 +3465,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:
@@ -3593,6 +3601,7 @@ print_one_breakpoint_location (struct br
{bp_longjmp_master, "longjmp master"},
{bp_catchpoint, "catchpoint"},
{bp_tracepoint, "tracepoint"},
+ {bp_jit_event, "jit events"},
};

static char bpenables[] = "nynny";
@@ -3721,6 +3730,7 @@ print_one_breakpoint_location (struct br
case bp_overlay_event:
case bp_longjmp_master:
case bp_tracepoint:
+ case bp_jit_event:
if (opts.addressprint)
{
annotate_field (4);
@@ -4362,6 +4372,7 @@ allocate_bp_location (struct breakpoint
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;
@@ -4644,6 +4655,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)
@@ -5279,6 +5301,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;
}
@@ -7585,6 +7608,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
@@ -7604,6 +7628,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)
@@ -7926,6 +7951,7 @@ breakpoint_re_set_one (void *bint)
case bp_step_resume:
case bp_longjmp:
case bp_longjmp_resume:
+ case bp_jit_event:
break;
}

@@ -7954,6 +7980,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");
Index: gdb/breakpoint.h
===================================================================
RCS file: /cvs/src/src/gdb/breakpoint.h,v
retrieving revision 1.95
diff -u -p -u -r1.95 breakpoint.h
--- gdb/breakpoint.h 2 Jul 2009 17:12:24 -0000 1.95
+++ gdb/breakpoint.h 31 Jul 2009 18:03:35 -0000
@@ -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. */
@@ -548,6 +551,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
};
@@ -841,6 +847,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);

Index: gdb/infrun.c
===================================================================
RCS file: /cvs/src/src/gdb/infrun.c,v
retrieving revision 1.402
diff -u -p -u -r1.402 infrun.c
--- gdb/infrun.c 20 Jul 2009 15:05:12 -0000 1.402
+++ gdb/infrun.c 31 Jul 2009 18:03:36 -0000
@@ -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_pat
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...) */
@@ -3529,6 +3532,22 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (
}
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. */

Index: gdb/jit.c
===================================================================
RCS file: gdb/jit.c
diff -N gdb/jit.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ gdb/jit.c 31 Jul 2009 18:03:36 -0000
@@ -0,0 +1,430 @@
+/* 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;
+
+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;
+ 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;
+ }
+
+ /* This call takes ownership of sai. */
+ objfile = symbol_file_add_from_bfd (nbfd, 0, sai, OBJF_SHARED);
+
+ /* 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);
+}
+
+/* Helper cleanup function to clear an integer flag. */
+
+void
+clear_int (void *int_addr)
+{
+ *((int *) int_addr) = 0;
+}
+
+/* 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;
+ struct objfile *temp;
+ CORE_ADDR *objf_entry_addr;
+
+ ALL_OBJFILES_SAFE (objf, temp)
+ {
+ 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;
+ static int already_entered;
+ struct cleanup *old_cleanups;
+
+ /* When we register code down there, GDB resets its breakpoints in case
+ symbols have changed. That in turn calls this handler, which makes us look
+ for new code again. There's nothing wrong with that because we won't find
+ any new code, but it makes an O(n^4) loop. Therefore we do nothing if
+ we're re-entered. */
+ if (already_entered)
+ return;
+ already_entered = 1;
+ old_cleanups = make_cleanup (clear_int, &already_entered);
+
+ /* 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);
+ }
+
+ /* Clear the already_entered flag. */
+ do_cleanups (old_cleanups);
+}
+
+/* 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 ();
+}
Index: gdb/jit.h
===================================================================
RCS file: gdb/jit.h
diff -N gdb/jit.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ gdb/jit.h 31 Jul 2009 18:03:36 -0000
@@ -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 */
Index: gdb/objfiles.c
===================================================================
RCS file: /cvs/src/src/gdb/objfiles.c,v
retrieving revision 1.87
diff -u -p -u -r1.87 objfiles.c
--- gdb/objfiles.c 22 Jul 2009 19:21:31 -0000 1.87
+++ gdb/objfiles.c 31 Jul 2009 18:03:36 -0000
@@ -25,6 +25,7 @@

#include "defs.h"
#include "bfd.h" /* Binary File Description */
+#include "libbfd.h"
#include "symtab.h"
#include "symfile.h"
#include "objfiles.h"
@@ -438,9 +439,19 @@ free_objfile (struct objfile *objfile)
if (objfile->obfd != NULL && !(objfile->flags & OBJF_KEEPBFD))
{
char *name = bfd_get_filename (objfile->obfd);
+ struct bfd_in_memory *bim = NULL;
+ /* Hack to work around the fact that BFD does not take ownership of the
+ memory for files allocated in memory. */
+ if (objfile->obfd->flags & BFD_IN_MEMORY)
+ bim = (struct bfd_in_memory *) objfile->obfd->iostream;
if (!bfd_close (objfile->obfd))
warning (_("cannot close \"%s\": %s"),
name, bfd_errmsg (bfd_get_error ()));
+ if (bim != NULL)
+ {
+ xfree (bim->buffer);
+ xfree (bim);
+ }
xfree (name);
}

Index: gdb/doc/gdb.texinfo
===================================================================
RCS file: /cvs/src/src/gdb/doc/gdb.texinfo,v
retrieving revision 1.608
diff -u -p -u -r1.608 gdb.texinfo
--- gdb/doc/gdb.texinfo 20 Jul 2009 18:51:41 -0000 1.608
+++ gdb/doc/gdb.texinfo 31 Jul 2009 18:03:38 -0000
@@ -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}

@@ -25822,6 +25823,136 @@ source which is being displayed. @var{a
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}