[rfc / remote protocol] Remote shared library events

Daniel Jacobowitz
2007-05-09T20:16:48Z

System V platforms (like GNU/Linux, Solaris, BSD, et cetera) have a
mostly userspace shared library loader. That means the loader already
has to keep track of what libraries are mapped, and GDB uses the same
data structures that the loader does to find the list. There's also
typically an application function like _dl_debug_state which is called
at every event; GDB can set a breakpoint there to be notified of
events.

Not all platforms work this way. The exceptions I'm looking at
recently are DLL-based platforms: Windows and SymbianOS. Both use an
OS-level loader instead. You have to query the OS to get the list
of libraries, and the OS provides event notification directly instead
of via a magic breakpoint. We can't poke and prod at the OS directly
during remote debugging, so to implement DLL support for these
platforms I extended the remote protocol.

The additions are three new stop replies for the "T" packet to report
events (load, unload, and dll for "something more complicated than
just a load or unload") and two new packets (qfDllInfo and qsDllInfo)
to query the current state. Detailed documentation is below and the
patch is attached.

This patch introduces a new solib_ops vector in solib-target.c. This
vector is target driven; it maintains a list based on the query packet
and event notices, instead of looking through memory.

I have tested this code on SymbianOS, Cygwin, and MinGW32. Pedro
Alves has tested a slightly earlier version on Windows CE (and wrote
the gdbserver bits I used to test it on Windows - thanks!).

What do you think, Kevin especially? I'm not entirely thrilled with
the way this interacts with other solib_ops vectors, but I'm not
unhappy with it either.

Eli, do the manual changes look OK?

All comments welcome!

+@item
+If @var{n} is @samp{load}, then the packet indicates a DLL load event,
+and @var{r} describes the newly loaded library. The library format is
+the same used in @samp{qfDllInfo} replies (@pxref{qfDllInfo}), e.g.@:
+@samp{load:Name=@var{hexname},TextSeg=@var{textaddr}}. The entry may
+end in @samp{,nop} if this library was already mapped, e.g.@: by an
+earlier call to @code{LoadLibrary}. @var{aa} should be @samp{05}, the
+trap signal.
+
+@item
+If @var{n} is @samp{unload}, then the packet indicates a DLL unload
+event, and @var{r} describes the unloaded library. @var{r} is a list
+of comma-separated @samp{Key=Value} pairs, similar to a
+@samp{qfDllInfo} reply. The name, the segment offsets, or both may be
+used to specify which DLL to unload, e.g.@:
+@samp{unload:Name=@var{hexname}} or
+@samp{unload:Name=@var{hexname},TextSeg=@var{textaddr}}. The entry
+may end in @samp{,nop} if this library is still mapped, e.g.@: by
+another open handle. @var{aa} should be @samp{05}, the trap signal.
+
+@item
+If @var{n} is @samp{dll}, then the packet indicates that the loaded
+DLLs have changed. @value{GDBN} should use @samp{qfDllInfo} to fetch
+a new list of loaded libraries. @var{r} is ignored. @var{aa} should
+be @samp{05}, the trap signal.

+@item qfDllInfo
+@itemx qsDllInfo
+@anchor{qfDllInfo}
+@cindex list loaded DLLs, remote request
+@cindex @samp{qfDllInfo} packet
+@cindex @samp{qsDllInfo} packet
+Obtain a list of all loaded DLLs (dynamic link libraries) from the
+target.
+
+Since there may be too many DLLs to fit into one reply packet, this
+query works iteratively: it may require more than one query/reply
+sequence to obtain the entire list. The first query of the sequence
+will be the @samp{qfDllInfo} query; subsequent queries in the
+sequence will be the @samp{qsDllInfo} query.
+
+In response to each query, the target will reply with a list of one or
+more libraries. @value{GDBN} will respond to each reply with a
+request for more libraries (using the @samp{qs} form of the query),
+until the target responds with @samp{l} (lower-case el, for
+@dfn{last}).
+
+If any stopped threads had pending DLL events not yet reported to GDB
+in a @samp{T} stop reply (@pxref{Stop Reply Packets}), then either the
+effects of those events should not be included in the result of the
+@samp{qfDllInfo} or the queued events should be discarded before they
+are reported to @value{GDBN}.
+
+Reply:
+@table @samp
+@item m Name=@var{hexname},TextSeg=@var{textaddr}@r{[},DataSeg=@var{dataaddr}@r{]}
+A single loaded library. @var{hexname} is the name of the library,
+as a hexadecimal sequence of ASCII characters. @var{textaddr} is the
+load address for the text segment of the library. @var{dataaddr} is
+the load address for the data segment of the library, if necessary.
+If only @var{textaddr} is provided, the data segment will be relocated
+by the same amount as the text segment.
+
+Other @samp{Key=Value} pairs may be added later to describe target
+specific data.
+
+@item l
+Marks the end of the list of DLLs. No further @samp{qsDllInfo} packets
+will be sent.
+
+@item m @var{library}; @var{library}@dots{} @r{[}; l@r{[}
+Multiple items may be combined into a single reply packet, if they
+fit. Each @var{library} is a comma-separated list as describe above.
+If the packet ends with @samp{;l} then no further @samp{qsDllInfo}
+packet will be sent.
+@end table

--
Daniel Jacobowitz
CodeSourcery

2007-05-09 Daniel Jacobowitz <dan@codesourcery.com>

* Makefile.in (ALLDEPFILES): Add solib-target.o.
(solib-target.o): New rule.
* infrun.c (handle_inferior_event): Update TARGET_WAITKIND_LOADED
support.
* remote.c (PACKET_qfDllInfo): New constant.
(parse_load_response, remote_get_shared_libraries): New.
(remote_protocol_features): Add qfDllInfo.
(remote_wait, remote_async_wait): Recognize DLL stop replies.
(init_remote_ops): Register remote_get_shared_libraries.
(init_remote_async_ops): Likewise. Fix typo.
(_initialize_remote): Register "set remote dll-info-packet".
* solib-som.c (som_current_sos): Set addr_low and addr_high.
* solib-target.c: New file.
* solib.c (solib_map_sections): Use addr_low and addr_high instead
of textsection.
(info_sharedlibrary_command): Likewise.
(solib_add_library, solib_remove_library): New.
* solib.h (solib_add_library, solib_remove_library): New prototypes.
* solist.h (struct so_list): Replace textsection with addr_low and
addr_high.
(struct target_so_ops): Add add_one_solib and remove_one_solib.
* target.c (update_current_target): Add comment about
to_get_shared_libraries.
(target_get_shared_libraries): New.
* target.h (struct target_ops): Add to_get_shared_libraries.
(target_get_shared_libraries): New prototype.
* NEWS: Describe new qfDllInfo and shared library event support.

* gdb.texinfo (Remote Configuration): Document dll-info-packet.
(Stop Reply Packets): Document load, unload, and dll events.
(General Query Packets): Document qfDllInfo and qsDllInfo.
Mention qfDllInfo response to qSupporteed.

---
gdb/Makefile.in | 3
gdb/NEWS | 13 ++
gdb/doc/gdb.texinfo | 95 +++++++++++++++
gdb/infrun.c | 52 +++++++-
gdb/remote.c | 286 +++++++++++++++++++++++++++++++++++++++++++++-
gdb/solib-som.c | 3
gdb/solib-target.c | 323 ++++++++++++++++++++++++++++++++++++++++++++++++++++
gdb/solib.c | 39 +++++-
gdb/solib.h | 9 +
gdb/solist.h | 12 +
gdb/target.c | 24 +++
gdb/target.h | 7 +
12 files changed, 846 insertions(+), 20 deletions(-)

Index: gdb/Makefile.in
===================================================================
--- gdb/Makefile.in.orig 2007-05-09 13:27:00.000000000 -0400
+++ gdb/Makefile.in 2007-05-09 15:45:10.000000000 -0400
@@ -1471,6 +1471,7 @@ ALLDEPFILES = \
mips64obsd-nat.c mips64obsd-tdep.c \
nbsd-nat.c nbsd-tdep.c obsd-tdep.c \
solib-osf.c \
+ solib-target.c \
somread.c solib-som.c \
posix-hdep.c \
ppc-sysv-tdep.c ppc-linux-nat.c ppc-linux-tdep.c \
@@ -2596,6 +2597,8 @@ solib-svr4.o: solib-svr4.c $(defs_h) $(e
$(gdbcore_h) $(target_h) $(inferior_h) $(gdb_assert_h) \
$(solist_h) $(solib_h) $(solib_svr4_h) $(bfd_target_h) $(elf_bfd_h) \
$(exec_h)
+solib-target.o: solib-target.c $(defs_h) $(objfiles_h) $(solist_h) \
+ $(symtab_h) $(symfile_h) $(target_h) $(gdb_string_h)
sol-thread.o: sol-thread.c $(defs_h) $(gdbthread_h) $(target_h) \
$(inferior_h) $(gdb_stat_h) $(gdbcmd_h) $(gdbcore_h) $(regcache_h) \
$(solib_h) $(symfile_h) $(gdb_string_h) $(gregset_h)
Index: gdb/infrun.c
===================================================================
--- gdb/infrun.c.orig 2007-05-09 13:27:00.000000000 -0400
+++ gdb/infrun.c 2007-05-09 13:27:15.000000000 -0400
@@ -1311,13 +1311,16 @@ handle_inferior_event (struct execution_
/* Ignore gracefully during startup of the inferior, as it
might be the shell which has just loaded some objects,
otherwise add the symbols for the newly loaded objects. */
-#ifdef SOLIB_ADD
if (stop_soon == NO_STOP_QUIETLY)
{
+ int breakpoints_were_inserted;
+
/* Remove breakpoints, SOLIB_ADD might adjust
breakpoint addresses via breakpoint_re_set. */
+ breakpoints_were_inserted = breakpoints_inserted;
if (breakpoints_inserted)
remove_breakpoints ();
+ breakpoints_inserted = 0;

/* Check for any newly added shared libraries if we're
supposed to be adding them automatically. Switch
@@ -1339,17 +1342,52 @@ handle_inferior_event (struct execution_
exec/process stratum, instead relying on the target stack
to propagate relevant changes (stop, section table
changed, ...) up to other layers. */
+#ifdef SOLIB_ADD
SOLIB_ADD (NULL, 0, &current_target, auto_solib_add);
+#else
+ solib_add (NULL, 0, &current_target, auto_solib_add);
+#endif
target_terminal_inferior ();

+ /* Try to reenable shared library breakpoints, additional
+ code segments in shared libraries might be mapped in now. */
+ re_enable_breakpoints_in_shlibs ();
+
+ /* If requested, stop when the dynamic linker notifies
+ gdb of events. This allows the user to get control
+ and place breakpoints in initializer routines for
+ dynamically loaded objects (among other things). */
+ if (stop_on_solib_events)
+ {
+ stop_stepping (ecs);
+ return;
+ }
+
+ /* NOTE drow/2006-03-14: This might be a good place to check
+ for "catch load". */
+
/* Reinsert breakpoints and continue. */
- if (breakpoints_inserted)
- insert_breakpoints ();
+ if (breakpoints_were_inserted)
+ {
+ insert_breakpoints ();
+ breakpoints_inserted = 1;
+ }
}
-#endif
- resume (0, TARGET_SIGNAL_0);
- prepare_to_wait (ecs);
- return;
+
+ /* NOTE drow/2006-03-28: For the reason described before the
+ previous if statement, GDB used to automatically resume
+ here. But that's only true if a shell is running; if
+ we've just attached to a process, then that's a whole
+ different case - it might have been stopped at a load
+ event. */
+ if (inferior_ignoring_startup_exec_events || stop_soon == NO_STOP_QUIETLY)
+ {
+ resume (0, TARGET_SIGNAL_0);
+ prepare_to_wait (ecs);
+ return;
+ }
+ else
+ break;

case TARGET_WAITKIND_SPURIOUS:
if (debug_infrun)
Index: gdb/remote.c
===================================================================
--- gdb/remote.c.orig 2007-05-09 13:27:04.000000000 -0400
+++ gdb/remote.c 2007-05-09 15:46:17.000000000 -0400
@@ -899,6 +899,7 @@ enum {
PACKET_Z2,
PACKET_Z3,
PACKET_Z4,
+ PACKET_qfDllInfo,
PACKET_qXfer_auxv,
PACKET_qXfer_features,
PACKET_qXfer_memory_map,
@@ -2150,6 +2151,148 @@ get_offsets (void)
objfile_relocate (symfile_objfile, offs);
}

+/* Parse a load or unload message for a DLL. Loads are required to
+ have all fields, unloads must have at least one. P is not
+ necessarily NUL terminated, but it is always either NUL or semicolon
+ terminated (i.e. *P_END is either NUL or a semicolon) and the containing
+ string is NUL terminated. */
+
+int
+parse_load_response (const char *p, const char *p_end, int is_load)
+{
+ const char *name_start;
+ char *name;
+ CORE_ADDR segments[2];
+ int i, num_segments = 0;
+
+ name = NULL;
+
+ if (p < p_end && strncmp (p, "Name=", 5) == 0)
+ {
+ p += 5;
+
+ name_start = p;
+ while (p < p_end && *p != ',')
+ p++;
+
+ if ((p - name_start) % 2 != 0)
+ return -1;
+
+ name = alloca ((p - name_start) / 2 + 1);
+ i = hex2bin (name_start, name, (p - name_start) / 2);
+ name[i] = '\0';
+
+ if (p < p_end)
+ p++;
+ }
+ else if (is_load)
+ return -1;
+
+ if (p < p_end && strncmp (p, "TextSeg=", 8) == 0)
+ {
+ p += 8;
+
+ segments[0] = 0;
+ while (p < p_end && *p != ',')
+ segments[0] = (segments[0] << 4) + fromhex (*p++);
+ num_segments++;
+
+ if (p < p_end)
+ p++;
+ }
+ else if (is_load)
+ return -1;
+
+ if (p < p_end && strncmp (p, "DataSeg=", 8) == 0)
+ {
+ p += 8;
+
+ if (num_segments == 0)
+ return -1;
+
+ segments[1] = 0;
+ while (p < p_end && *p != ',')
+ segments[1] = (segments[1] << 4) + fromhex (*p++);
+ num_segments++;
+
+ if (p < p_end)
+ p++;
+ }
+
+ if (is_load)
+ solib_add_library (name, num_segments, segments);
+ else
+ {
+ if (num_segments == 0 && name == NULL)
+ return -1;
+
+ solib_remove_library (name, num_segments, segments);
+ }
+
+ return 0;
+}
+
+/* Query the remote side for loaded solibs. */
+
+static void
+remote_get_shared_libraries (struct target_ops *ops)
+{
+ struct remote_state *rs = get_remote_state ();
+
+ /* If qfDllInfo is not available, nothing to do. */
+ if (remote_protocol_packets[PACKET_qfDllInfo].support == PACKET_DISABLE)
+ return;
+
+ putpkt ("qfDllInfo");
+ getpkt (&rs->buf, &rs->buf_size, 0);
+
+ if (packet_ok (rs->buf, &remote_protocol_packets[PACKET_qfDllInfo]) == PACKET_ERROR)
+ {
+ warning (_("Remote failure reply: %s"), rs->buf);
+ return;
+ }
+ else if (remote_protocol_packets[PACKET_qfDllInfo].support == PACKET_DISABLE)
+ /* It wasn't disabled before, but it is now. */
+ return;
+
+ while (rs->buf[0] == 'm')
+ {
+ char *p = rs->buf + 1;
+
+ while (1)
+ {
+ char *p_end = p;
+
+ /* If the packet ends with ";l", we are finished. */
+ if (p[0] == 'l' && p[1] == '\0')
+ return;
+
+ while (*p_end && *p_end != ';')
+ p_end++;
+
+ if (parse_load_response (p, p_end, 1) != 0)
+ {
+ warning (_("Malformed response to DLL query, %s"), rs->buf);
+ return;
+ }
+
+ if (*p_end == ';')
+ p = p_end + 1;
+ else
+ break;
+ }
+
+ putpkt ("qsDllInfo");
+ getpkt (&rs->buf, &rs->buf_size, 0);
+ }
+
+ if (strcmp (rs->buf, "l") != 0)
+ warning (_("Malformed response to DLL query, %s"), rs->buf);
+
+ /* We don't need to call solib_add here, because we're being called from
+ ->current_sos (). */
+}
+
/* Stub for catch_exception. */

static void
@@ -2366,6 +2509,8 @@ remote_packet_size (const struct protoco

static struct protocol_feature remote_protocol_features[] = {
{ "PacketSize", PACKET_DISABLE, remote_packet_size, -1 },
+ { "qfDllInfo", PACKET_DISABLE, remote_supported_packet,
+ PACKET_qfDllInfo },
{ "qXfer:auxv:read", PACKET_DISABLE, remote_supported_packet,
PACKET_qXfer_auxv },
{ "qXfer:features:read", PACKET_DISABLE, remote_supported_packet,
@@ -3167,6 +3312,7 @@ remote_wait (ptid_t ptid, struct target_
struct remote_arch_state *rsa = get_remote_arch_state ();
ULONGEST thread_num = -1;
ULONGEST addr;
+ int solibs_changed = 0;

status->kind = TARGET_WAITKIND_EXITED;
status->value.integer = 0;
@@ -3222,7 +3368,8 @@ remote_wait (ptid_t ptid, struct target_
/* If this packet is an awatch packet, don't parse the
'a' as a register number. */

- if (strncmp (p, "awatch", strlen("awatch")) != 0)
+ if (strncmp (p, "awatch", strlen("awatch")) != 0
+ && strncmp (p, "dll", strlen("dll")) != 0)
{
/* Read the ``P'' register number. */
pnum = strtol (p, &p_temp, 16);
@@ -3252,6 +3399,60 @@ Packet: '%s'\n"),
p = unpack_varlen_hex (++p1, &addr);
remote_watch_data_address = (CORE_ADDR)addr;
}
+ else if (strncmp (p, "load", p1 - p) == 0)
+ {
+ p1++;
+ p_temp = p1;
+ while (*p_temp && *p_temp != ';')
+ p_temp++;
+
+ if (p_temp - p1 > 4
+ && strncmp (p_temp - 4, ",nop", 4) == 0)
+ {
+ /* For now, ignore no-op unload events. Later,
+ maybe report them? */
+ }
+ else
+ {
+ parse_load_response (p1, p_temp, 1);
+
+ solibs_changed = 1;
+ }
+
+ p = p_temp;
+ }
+ else if (strncmp (p, "unload", p1 - p) == 0)
+ {
+ p1++;
+ p_temp = p1;
+ while (*p_temp && *p_temp != ';')
+ p_temp++;
+
+ if (p_temp - p1 > 4
+ && strncmp (p_temp - 4, ",nop", 4) == 0)
+ {
+ /* For now, ignore no-op unload events. Later,
+ maybe report them? */
+ }
+ else
+ {
+ parse_load_response (p1, p_temp, 0);
+
+ solibs_changed = 1;
+ }
+
+ p = p_temp;
+ }
+ else if (strncmp (p, "dll", p1 - p) == 0)
+ {
+ p1++;
+ p_temp = p1;
+ while (*p_temp && *p_temp != ';')
+ p_temp++;
+
+ solibs_changed = -1;
+ p = p_temp;
+ }
else
{
/* Silently skip unknown optional info. */
@@ -3343,6 +3544,15 @@ Packet: '%s'\n"),
}
}
got_status:
+ if (solibs_changed == -1)
+ {
+ no_shared_libraries (NULL, 0);
+ remote_get_shared_libraries (NULL);
+ }
+
+ if (solibs_changed != 0)
+ status->kind = TARGET_WAITKIND_LOADED;
+
if (thread_num != -1)
{
return pid_to_ptid (thread_num);
@@ -3358,6 +3568,7 @@ remote_async_wait (ptid_t ptid, struct t
struct remote_arch_state *rsa = get_remote_arch_state ();
ULONGEST thread_num = -1;
ULONGEST addr;
+ int solibs_changed = 0;

status->kind = TARGET_WAITKIND_EXITED;
status->value.integer = 0;
@@ -3419,7 +3630,8 @@ remote_async_wait (ptid_t ptid, struct t
/* If this packet is an awatch packet, don't parse the 'a'
as a register number. */

- if (!strncmp (p, "awatch", strlen ("awatch")) != 0)
+ if (strncmp (p, "awatch", strlen("awatch")) != 0
+ && strncmp (p, "dll", strlen("dll")) != 0)
{
/* Read the register number. */
pnum = strtol (p, &p_temp, 16);
@@ -3449,6 +3661,60 @@ Packet: '%s'\n"),
p = unpack_varlen_hex (++p1, &addr);
remote_watch_data_address = (CORE_ADDR)addr;
}
+ else if (strncmp (p, "load", p1 - p) == 0)
+ {
+ p1++;
+ p_temp = p1;
+ while (*p_temp && *p_temp != ';')
+ p_temp++;
+
+ if (p_temp - p1 > 4
+ && strncmp (p_temp - 4, ",nop", 4) == 0)
+ {
+ /* For now, ignore no-op unload events. Later,
+ maybe report them? */
+ }
+ else
+ {
+ parse_load_response (p1, p_temp, 1);
+
+ solibs_changed = 1;
+ }
+
+ p = p_temp;
+ }
+ else if (strncmp (p, "unload", p1 - p) == 0)
+ {
+ p1++;
+ p_temp = p1;
+ while (*p_temp && *p_temp != ';')
+ p_temp++;
+
+ if (p_temp - p1 > 4
+ && strncmp (p_temp - 4, ",nop", 4) == 0)
+ {
+ /* For now, ignore no-op unload events. Later,
+ maybe report them? */
+ }
+ else
+ {
+ parse_load_response (p1, p_temp, 0);
+
+ solibs_changed = 1;
+ }
+
+ p = p_temp;
+ }
+ else if (strncmp (p, "dll", p1 - p) == 0)
+ {
+ p1++;
+ p_temp = p1;
+ while (*p_temp && *p_temp != ';')
+ p_temp++;
+
+ solibs_changed = -1;
+ p = p_temp;
+ }
else
{
/* Silently skip unknown optional info. */
@@ -3542,6 +3808,15 @@ Packet: '%s'\n"),
}
}
got_status:
+ if (solibs_changed == -1)
+ {
+ no_shared_libraries (NULL, 0);
+ remote_get_shared_libraries (NULL);
+ }
+
+ if (solibs_changed != 0)
+ status->kind = TARGET_WAITKIND_LOADED;
+
if (thread_num != -1)
{
return pid_to_ptid (thread_num);
@@ -6224,6 +6499,7 @@ Specify the serial device it is connecte
remote_ops.to_flash_erase = remote_flash_erase;
remote_ops.to_flash_done = remote_flash_done;
remote_ops.to_read_description = remote_read_description;
+ remote_ops.to_get_shared_libraries = remote_get_shared_libraries;
}

/* Set up the extended remote vector by making a copy of the standard
@@ -6356,7 +6632,8 @@ Specify the serial device it is connecte
remote_async_ops.to_memory_map = remote_memory_map;
remote_async_ops.to_flash_erase = remote_flash_erase;
remote_async_ops.to_flash_done = remote_flash_done;
- remote_ops.to_read_description = remote_read_description;
+ remote_async_ops.to_read_description = remote_read_description;
+ remote_async_ops.to_get_shared_libraries = remote_get_shared_libraries;
}

/* Set up the async extended remote vector by making a copy of the standard
@@ -6625,6 +6902,9 @@ Show the maximum size of the address (in
add_packet_config_cmd (&remote_protocol_packets[PACKET_qSupported],
"qSupported", "supported-packets", 0);

+ add_packet_config_cmd (&remote_protocol_packets[PACKET_qfDllInfo],
+ "qfDllInfo", "dll-info", 0);
+
/* Keep the old ``set remote Z-packet ...'' working. Each individual
Z sub-packet has its own set and show commands, but users may
have sets to this variable in their .gdbinit files (or in their
Index: gdb/solib-som.c
===================================================================
--- gdb/solib-som.c.orig 2007-05-09 13:27:00.000000000 -0400
+++ gdb/solib-som.c 2007-05-09 13:27:15.000000000 -0400
@@ -623,6 +623,9 @@ som_current_sos (void)
paddr_nz (new->lm_info->tsd_start_addr));
#endif

+ new->addr_low = lmi->text_addr;
+ new->addr_high = lmi->text_end;
+
/* Link the new object onto the list. */
new->next = NULL;
*link_ptr = new;
Index: gdb/solib-target.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb/solib-target.c 2007-05-09 13:27:15.000000000 -0400
@@ -0,0 +1,323 @@
+/* Definitions for targets which report shared library events.
+
+ Copyright (C) 2006
+ 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 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA. */
+
+#include "defs.h"
+#include "objfiles.h"
+#include "solist.h"
+#include "symtab.h"
+#include "symfile.h"
+#include "target.h"
+
+#include "gdb_string.h"
+
+struct lm_info
+{
+ struct section_offsets *offsets;
+ int num_bases;
+ CORE_ADDR *segment_bases;
+};
+
+static struct so_list *solib_start;
+static int solibs_fetched;
+
+static struct so_list *
+solib_target_current_sos (void)
+{
+ struct so_list *sop;
+ struct so_list *start = NULL;
+ struct so_list *last = NULL;
+
+ /* If we have not asked the target for the list of shared libraries
+ yet, do it now. */
+ if (!solibs_fetched)
+ {
+ solibs_fetched = 1;
+ target_get_shared_libraries ();
+ }
+
+ for (sop = solib_start; sop; sop = sop->next)
+ {
+ struct so_list *new;
+
+ /* Duplicate the recorded solib. */
+ new = XZALLOC (struct so_list);
+ strcpy (new->so_name, sop->so_name);
+ strcpy (new->so_original_name, sop->so_original_name);
+ new->lm_info = XZALLOC (struct lm_info);
+ if (sop->lm_info->num_bases > 0)
+ {
+ new->lm_info->num_bases = sop->lm_info->num_bases;
+ new->lm_info->segment_bases
+ = xmalloc (sizeof (CORE_ADDR) * new->lm_info->num_bases);
+ memcpy (new->lm_info->segment_bases, sop->lm_info->segment_bases,
+ sizeof (CORE_ADDR) * new->lm_info->num_bases);
+ }
+
+ /* Add it to the list. */
+ if (!start)
+ last = start = new;
+ else
+ {
+ last->next = new;
+ last = new;
+ }
+ }
+
+ return start;
+}
+
+static void
+solib_target_special_symbol_handling (void)
+{
+ /* Nothing needed. */
+}
+
+static void
+solib_target_solib_create_inferior_hook (void)
+{
+ /* Nothing needed. */
+}
+
+static void
+solib_target_clear_solib (void)
+{
+ struct so_list *sop, *next;
+
+ for (sop = solib_start; sop; sop = next)
+ {
+ next = sop->next;
+
+ free_so (sop);
+ }
+
+ solib_start = NULL;
+ solibs_fetched = 0;
+}
+
+static void
+solib_target_free_so (struct so_list *so)
+{
+ xfree (so->lm_info->offsets);
+ xfree (so->lm_info->segment_bases);
+ xfree (so->lm_info);
+}
+
+static void
+solib_target_relocate_section_addresses (struct so_list *so,
+ struct section_table *sec)
+{
+ int flags = bfd_get_section_flags (sec->bfd, sec->the_bfd_section);
+ CORE_ADDR offset;
+
+ if (so->lm_info->offsets == NULL)
+ {
+ struct symfile_segment_data *data;
+ int num_sections = bfd_count_sections (so->abfd);
+
+ so->lm_info->offsets = xzalloc (SIZEOF_N_SECTION_OFFSETS (num_sections));
+
+ data = get_symfile_segment_data (so->abfd);
+ if (data == NULL)
+ warning (_("Could not relocate shared library \"%s\": no segments"),
+ so->so_name);
+ else
+ {
+ ULONGEST orig_delta;
+ int i;
+
+ if (!symfile_map_offsets_to_segments (so->abfd, data,
+ so->lm_info->offsets,
+ so->lm_info->num_bases,
+ so->lm_info->segment_bases))
+ warning (_("Could not relocate shared library \"%s\": bad offsets"),
+ so->so_name);
+
+ /* Find the range of addresses to report for this library in
+ "info sharedlibrary". Report any consecutive segments
+ which were relocated as a single unit. */
+ gdb_assert (so->lm_info->num_bases > 0);
+ orig_delta = so->lm_info->segment_bases[0] - data->segment_bases[0];
+
+ for (i = 1; i < data->num_segments; i++)
+ {
+ ULONGEST delta;
+
+ /* If we have run out of offsets, assume all remaining segments
+ have the same offset. */
+ if (i >= so->lm_info->num_bases)
+ continue;
+
+ /* If this segment does not have the same offset, do not include
+ it in the library's range. */
+ delta = so->lm_info->segment_bases[i] - data->segment_bases[i];
+ if (delta != orig_delta)
+ break;
+ }
+
+ so->addr_low = so->lm_info->segment_bases[0];
+ so->addr_high = (data->segment_bases[i - 1]
+ + data->segment_sizes[i - 1]);
+
+ free_symfile_segment_data (data);
+ }
+ }
+
+ offset = so->lm_info->offsets->offsets[sec->the_bfd_section->index];
+ sec->addr += offset;
+ sec->endaddr += offset;
+}
+
+static int
+solib_target_open_symbol_file_object (void *from_ttyp)
+{
+ /* We can't locate the main symbol file based on the target's
+ knowledge; the user has to specify it. */
+ return 0;
+}
+
+static int
+solib_target_in_dynsym_resolve_code (CORE_ADDR pc)
+{
+ /* DLL targets generally have only import stubs (which GDB treats as
+ "PLT entries"), and no runtime binding code. */
+ return in_plt_section (pc, NULL);
+}
+
+static void
+solib_target_add_one_solib (char *soname, int num_bases,
+ CORE_ADDR *segment_bases)
+{
+ struct so_list *new_solib, *so;
+
+ /* We should already have queried the target for shared libraries
+ before this point. If we haven't, we may have just connected;
+ we'll be querying shortly. */
+ if (!solibs_fetched)
+ return;
+
+ /* Check for duplicates already on the list. This can happen, for
+ instance, if we are stopped at a DLL load event when we first
+ connect to a remote target: the DLL will already be in the
+ queried list, but also be reported by the initial wait. */
+ for (so = solib_start; so; so = so->next)
+ if (strcmp (so->so_name, soname) == 0
+ && so->lm_info->num_bases == num_bases
+ && memcmp (so->lm_info->segment_bases, segment_bases,
+ sizeof (CORE_ADDR) * num_bases) == 0)
+ return;
+
+ new_solib = XZALLOC (struct so_list);
+ strncpy (new_solib->so_name, soname, SO_NAME_MAX_PATH_SIZE - 1);
+ new_solib->so_name[SO_NAME_MAX_PATH_SIZE - 1] = '\0';
+ strncpy (new_solib->so_original_name, soname, SO_NAME_MAX_PATH_SIZE - 1);
+ new_solib->so_original_name[SO_NAME_MAX_PATH_SIZE - 1] = '\0';
+
+ new_solib->lm_info = XZALLOC (struct lm_info);
+ new_solib->lm_info->num_bases = num_bases;
+ new_solib->lm_info->segment_bases
+ = xmalloc (sizeof (CORE_ADDR) * num_bases);
+ memcpy (new_solib->lm_info->segment_bases, segment_bases,
+ sizeof (CORE_ADDR) * num_bases);
+
+ if (solib_start == NULL)
+ solib_start = new_solib;
+ else
+ {
+ so = solib_start;
+ while (so->next)
+ so = so->next;
+ so->next = new_solib;
+ }
+
+ /* We do not trigger symbol reading here; the target will do it,
+ after all load events have been processed. */
+}
+
+static void
+solib_target_remove_one_solib (char *soname, int num_bases,
+ CORE_ADDR *segment_bases)
+{
+ struct so_list **slot, *removed;
+
+ /* We should already have queried the target for shared libraries
+ before this point. If we haven't, we may have just connected;
+ we'll be querying shortly. */
+ if (!solibs_fetched)
+ return;
+
+ for (slot = &solib_start; *slot != NULL; slot = &(*slot)->next)
+ {
+ int i;
+
+ if (soname != NULL && strcmp (soname, (*slot)->so_name) != 0)
+ continue;
+
+ if (num_bases != (*slot)->lm_info->num_bases)
+ continue;
+
+ for (i = 0; i < num_bases; i++)
+ if (segment_bases[i] != (*slot)->lm_info->segment_bases[i])
+ break;
+ if (i < num_bases)
+ continue;
+
+ /* Got a match. */
+ break;
+ }
+
+ if (*slot == NULL)
+ return;
+
+ removed = *slot;
+ *slot = removed->next;
+
+ free_so (removed);
+
+ /* We do not trigger symbol unloading here; the target will do it,
+ after all unload events have been processed. */
+}
+
+static struct target_so_ops solib_target_so_ops;
+
+extern initialize_file_ftype _initialize_solib_target; /* -Wmissing-prototypes */
+
+void
+_initialize_solib_target (void)
+{
+ solib_target_so_ops.relocate_section_addresses
+ = solib_target_relocate_section_addresses;
+ solib_target_so_ops.free_so = solib_target_free_so;
+ solib_target_so_ops.clear_solib = solib_target_clear_solib;
+ solib_target_so_ops.solib_create_inferior_hook
+ = solib_target_solib_create_inferior_hook;
+ solib_target_so_ops.special_symbol_handling
+ = solib_target_special_symbol_handling;
+ solib_target_so_ops.current_sos = solib_target_current_sos;
+ solib_target_so_ops.open_symbol_file_object
+ = solib_target_open_symbol_file_object;
+ solib_target_so_ops.in_dynsym_resolve_code
+ = solib_target_in_dynsym_resolve_code;
+ solib_target_so_ops.add_one_solib = solib_target_add_one_solib;
+ solib_target_so_ops.remove_one_solib = solib_target_remove_one_solib;
+
+ current_target_so_ops = &solib_target_so_ops;
+}
Index: gdb/solib.c
===================================================================
--- gdb/solib.c.orig 2007-05-09 13:27:00.000000000 -0400
+++ gdb/solib.c 2007-05-09 13:27:15.000000000 -0400
@@ -306,9 +306,14 @@ solib_map_sections (void *arg)
object's file by the base address to which the object was actually
mapped. */
ops->relocate_section_addresses (so, p);
- if (strcmp (p->the_bfd_section->name, ".text") == 0)
+
+ /* If the target didn't provide information about the address range
+ of the DLL, assume we want the location of the .text section. */
+ if (so->addr_low == 0 && so->addr_high == 0
+ && strcmp (p->the_bfd_section->name, ".text") == 0)
{
- so->textsection = p;
+ so->addr_low = p->addr;
+ so->addr_high = p->endaddr;
}
}

@@ -732,15 +737,15 @@ info_sharedlibrary_command (char *ignore
}

printf_unfiltered ("%-*s", addr_width,
- so->textsection != NULL
+ so->addr_high != 0
? hex_string_custom (
- (LONGEST) so->textsection->addr,
+ (LONGEST) so->addr_low,
addr_width - 4)
: "");
printf_unfiltered ("%-*s", addr_width,
- so->textsection != NULL
+ so->addr_high != 0
? hex_string_custom (
- (LONGEST) so->textsection->endaddr,
+ (LONGEST) so->addr_high,
addr_width - 4)
: "");
printf_unfiltered ("%-12s", so->symbols_loaded ? "Yes" : "No");
@@ -888,6 +893,28 @@ in_solib_dynsym_resolve_code (CORE_ADDR
return ops->in_dynsym_resolve_code (pc);
}

+/* Add a reported shared library to the list. */
+
+void
+solib_add_library (char *soname, int num_bases, CORE_ADDR *segment_bases)
+{
+ struct target_so_ops *ops = solib_ops (current_gdbarch);
+
+ if (ops->add_one_solib != NULL)
+ ops->add_one_solib (soname, num_bases, segment_bases);
+}
+
+/* Remove a reported shared library from the list. */
+
+void
+solib_remove_library (char *soname, int num_bases, CORE_ADDR *segment_bases)
+{
+ struct target_so_ops *ops = solib_ops (current_gdbarch);
+
+ if (ops->remove_one_solib != NULL)
+ ops->remove_one_solib (soname, num_bases, segment_bases);
+}
+
/*

LOCAL FUNCTION
Index: gdb/solist.h
===================================================================
--- gdb/solist.h.orig 2007-05-09 13:27:00.000000000 -0400
+++ gdb/solist.h 2007-05-09 14:06:57.000000000 -0400
@@ -64,7 +64,11 @@ struct so_list
struct objfile *objfile; /* objfile for loaded lib */
struct section_table *sections;
struct section_table *sections_end;
- struct section_table *textsection;
+
+ /* Record the range of addresses belonging to this shared library.
+ There may not be just one (e.g. if two segments are relocated
+ differently); but this is only used for "info sharedlibrary". */
+ CORE_ADDR addr_low, addr_high;
};

struct target_so_ops
@@ -103,7 +107,11 @@ struct target_so_ops
Convenience function for remote debuggers finding host libs. */
int (*find_and_open_solib) (char *soname,
unsigned o_flags, char **temp_pathname);
-
+
+ void (*add_one_solib) (char *soname, int num_bases,
+ CORE_ADDR *segment_bases);
+ void (*remove_one_solib) (char *soname, int num_bases,
+ CORE_ADDR *segment_bases);
};

/* Free the memory associated with a (so_list *). */
Index: gdb/target.c
===================================================================
--- gdb/target.c.orig 2007-05-09 13:27:00.000000000 -0400
+++ gdb/target.c 2007-05-09 13:27:15.000000000 -0400
@@ -467,10 +467,11 @@ update_current_target (void)
INHERIT (to_make_corefile_notes, t);
INHERIT (to_get_thread_local_address, t);
/* Do not inherit to_read_description. */
- INHERIT (to_magic, t);
/* Do not inherit to_memory_map. */
/* Do not inherit to_flash_erase. */
/* Do not inherit to_flash_done. */
+ /* Do not inherit to_get_shared_libraries. */
+ INHERIT (to_magic, t);
}
#undef INHERIT

@@ -1719,6 +1720,27 @@ target_read_description (struct target_o
}

/* Look through the list of possible targets for a target that can
+ fetch shared libraries. */
+
+void
+target_get_shared_libraries (void)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ {
+ if (t->to_get_shared_libraries != NULL)
+ {
+ t->to_get_shared_libraries (t);
+ break;
+ }
+ }
+
+ if (targetdebug)
+ fprintf_unfiltered (gdb_stdlog, "target_get_shared_libraries ()\n");
+}
+
+/* Look through the list of possible targets for a target that can
execute a run or attach command without any other data. This is
used to locate the default process stratum.

Index: gdb/target.h
===================================================================
--- gdb/target.h.orig 2007-05-09 13:27:00.000000000 -0400
+++ gdb/target.h 2007-05-09 13:27:15.000000000 -0400
@@ -501,6 +501,9 @@ struct target_ops
was available. */
const struct target_desc *(*to_read_description) (struct target_ops *ops);

+ /* Refresh the list of shared libraries. */
+ void (*to_get_shared_libraries) (struct target_ops *ops);
+
int to_magic;
/* Need sub-structure for target machine related rather than comm related?
*/
@@ -1153,6 +1156,10 @@ extern int target_stopped_data_address_p

extern const struct target_desc *target_read_description (struct target_ops *);

+/* Refresh the list of shared libraries from the target. */
+
+extern void target_get_shared_libraries (void);
+
/* Routines for maintenance of the target structures...

add_target: Add a target to the list of all possible targets.
Index: gdb/NEWS
===================================================================
--- gdb/NEWS.orig 2007-05-09 13:54:51.000000000 -0400
+++ gdb/NEWS 2007-05-09 13:55:03.000000000 -0400
@@ -35,6 +35,12 @@ has been rewritten to use the standard G
layout. It also supports a TextSeg= and DataSeg= response when only
segment base addresses (rather than offsets) are available.

+* The GDB remote protocol "T" stop reply packet now supports "dll",
+"load", and "unload" pairs. Combined with the qfDllInfo and qsDllInfo
+packets, these responses allow GDB to debug shared libraries on
+targets where the operating system manages the list of loaded
+libraries (e.g. Windows and SymbianOS).
+
* New commands

set mem inaccessible-by-default
@@ -93,6 +99,13 @@ qXfer:features:read:
Read an XML target description from the target, which describes its
features.

+qfDllInfo
+qsDllInfo
+ Report the loaded shared libraries. Combined with new T packet
+ responses, these packets allow GDB to debug shared libraries on
+ targets where the operating system manages the list of loaded
+ libraries (e.g. Windows and SymbianOS).
+
* Removed targets

Support for these obsolete configurations has been removed.
Index: gdb/doc/gdb.texinfo
===================================================================
--- gdb/doc/gdb.texinfo.orig 2007-05-09 13:54:54.000000000 -0400
+++ gdb/doc/gdb.texinfo 2007-05-09 15:36:15.000000000 -0400
@@ -12897,6 +12897,10 @@ The available settings are:
@tab @code{Z4}
@tab @code{awatch}

+@item @code{dll-info-packet}
+@tab @code{qfDllInfo}
+@tab @code{info sharedlibrary}
+
@item @code{get-thread-local-storage-address-packet}
@tab @code{qGetTLSAddr}
@tab Displaying @code{__thread} variables
@@ -23221,13 +23225,42 @@ If @var{n} is a hexadecimal number, it i
corresponding @var{r} gives that register's value. @var{r} is a
series of bytes in target byte order, with each byte given by a
two-digit hex number.
+
@item
If @var{n} is @samp{thread}, then @var{r} is the thread process ID, in
hex.
+
@item
If @var{n} is @samp{watch}, @samp{rwatch}, or @samp{awatch}, then the
packet indicates a watchpoint hit, and @var{r} is the data address, in
-hex.
+hex. @var{aa} should be @samp{05}, the trap signal.
+
+@item
+If @var{n} is @samp{load}, then the packet indicates a DLL load event,
+and @var{r} describes the newly loaded library. The library format is
+the same used in @samp{qfDllInfo} replies (@pxref{qfDllInfo}), e.g.@:
+@samp{load:Name=@var{hexname},TextSeg=@var{textaddr}}. The entry may
+end in @samp{,nop} if this library was already mapped, e.g.@: by an
+earlier call to @code{LoadLibrary}. @var{aa} should be @samp{05}, the
+trap signal.
+
+@item
+If @var{n} is @samp{unload}, then the packet indicates a DLL unload
+event, and @var{r} describes the unloaded library. @var{r} is a list
+of comma-separated @samp{Key=Value} pairs, similar to a
+@samp{qfDllInfo} reply. The name, the segment offsets, or both may be
+used to specify which DLL to unload, e.g.@:
+@samp{unload:Name=@var{hexname}} or
+@samp{unload:Name=@var{hexname},TextSeg=@var{textaddr}}. The entry
+may end in @samp{,nop} if this library is still mapped, e.g.@: by
+another open handle. @var{aa} should be @samp{05}, the trap signal.
+
+@item
+If @var{n} is @samp{dll}, then the packet indicates that the loaded
+DLLs have changed. @value{GDBN} should use @samp{qfDllInfo} to fetch
+a new list of loaded libraries. @var{r} is ignored. @var{aa} should
+be @samp{05}, the trap signal.
+
@item
Otherwise, @value{GDBN} should ignore this @samp{@var{n}:@var{r}} pair
and go on to the next; this allows us to extend the protocol in the
@@ -23341,6 +23374,57 @@ An error (such as memory fault)
The specified memory region's checksum is @var{crc32}.
@end table

+@item qfDllInfo
+@itemx qsDllInfo
+@anchor{qfDllInfo}
+@cindex list loaded DLLs, remote request
+@cindex @samp{qfDllInfo} packet
+@cindex @samp{qsDllInfo} packet
+Obtain a list of all loaded DLLs (dynamic link libraries) from the
+target.
+
+Since there may be too many DLLs to fit into one reply packet, this
+query works iteratively: it may require more than one query/reply
+sequence to obtain the entire list. The first query of the sequence
+will be the @samp{qfDllInfo} query; subsequent queries in the
+sequence will be the @samp{qsDllInfo} query.
+
+In response to each query, the target will reply with a list of one or
+more libraries. @value{GDBN} will respond to each reply with a
+request for more libraries (using the @samp{qs} form of the query),
+until the target responds with @samp{l} (lower-case el, for
+@dfn{last}).
+
+If any stopped threads had pending DLL events not yet reported to GDB
+in a @samp{T} stop reply (@pxref{Stop Reply Packets}), then either the
+effects of those events should not be included in the result of the
+@samp{qfDllInfo} or the queued events should be discarded before they
+are reported to @value{GDBN}.
+
+Reply:
+@table @samp
+@item m Name=@var{hexname},TextSeg=@var{textaddr}@r{[},DataSeg=@var{dataaddr}@r{]}
+A single loaded library. @var{hexname} is the name of the library,
+as a hexadecimal sequence of ASCII characters. @var{textaddr} is the
+load address for the text segment of the library. @var{dataaddr} is
+the load address for the data segment of the library, if necessary.
+If only @var{textaddr} is provided, the data segment will be relocated
+by the same amount as the text segment.
+
+Other @samp{Key=Value} pairs may be added later to describe target
+specific data.
+
+@item l
+Marks the end of the list of DLLs. No further @samp{qsDllInfo} packets
+will be sent.
+
+@item m @var{library}; @var{library}@dots{} @r{[}; l@r{[}
+Multiple items may be combined into a single reply packet, if they
+fit. Each @var{library} is a comma-separated list as describe above.
+If the packet ends with @samp{;l} then no further @samp{qsDllInfo}
+packet will be sent.
+@end table
+
@item qfThreadInfo
@itemx qsThreadInfo
@cindex list active threads, remote request
@@ -23631,6 +23715,11 @@ These are the currently defined stub fea
@tab @samp{-}
@tab No

+@item @samp{qfDllInfo}
+@tab No
+@tab @samp{-}
+@tab Yes
+
@item @samp{qXfer:auxv:read}
@tab No
@tab @samp{-}
@@ -23667,6 +23756,10 @@ stores packets in a NUL-terminated forma
byte in its buffer for the NUL. If this stub feature is not supported,
@value{GDBN} guesses based on the size of the @samp{g} packet response.

+@item qfDllInfo
+The remote stub understands the @samp{qfDllInfo} and @samp{qsDllInfo}
+packets (@pxref{qfDllInfo}).
+
@item qXfer:auxv:read
The remote stub understands the @samp{qXfer:auxv:read} packet
(@pxref{qXfer auxiliary vector read}).
Index: gdb/solib.h
===================================================================
--- gdb/solib.h.orig 2007-01-09 13:11:17.000000000 -0500
+++ gdb/solib.h 2007-05-09 14:06:57.000000000 -0400
@@ -53,6 +53,15 @@ extern char *solib_address (CORE_ADDR);

extern int in_solib_dynsym_resolve_code (CORE_ADDR);

+/* Add a reported shared library to the list. */
+
+void solib_add_library (char *soname, int num_bases, CORE_ADDR *segment_bases);
+
+/* Remove a reported shared library from the list. */
+
+void solib_remove_library (char *soname, int num_bases,
+ CORE_ADDR *segment_bases);
+
/* Discard symbols that were auto-loaded from shared libraries. */

extern void no_shared_libraries (char *ignored, int from_tty);