aboutsummaryrefslogtreecommitdiff
path: root/gdb/linux-tdep.c
diff options
context:
space:
mode:
authorPedro Alves <palves@redhat.com>2014-10-10 15:57:14 +0100
committerPedro Alves <palves@redhat.com>2014-10-10 16:36:38 +0100
commitcdfa0b0ac16739350360b29b05223655d3b9b740 (patch)
treea46e564037ce1cf56385b658db9d010b105121b8 /gdb/linux-tdep.c
parent8b9a549d3a9176f92b4cac5b388eb473919f80e6 (diff)
downloadgdb-cdfa0b0ac16739350360b29b05223655d3b9b740.zip
gdb-cdfa0b0ac16739350360b29b05223655d3b9b740.tar.gz
gdb-cdfa0b0ac16739350360b29b05223655d3b9b740.tar.bz2
Cache the vsyscall/vDSO range per-inferior
We're now doing a vsyscall/vDSO address range lookup whenever we fetch shared libraries, either through an explicit "info shared", or when the target reports new libraries have been loaded, in order to filter out the vDSO from glibc's DSO list. Before we started doing that, GDB would only ever lookup the vsyscall's address range once in the process's lifetime. Looking up the vDSO address range requires an auxv lookup (which is already cached, so no problem), but also reading the process's mappings from /proc to find out the vDSO's mapping's size. That generates extra RSP traffic when remote debugging. Particularly annoying when the process's mappings grow linearly as more libraries are mapped in, and we went through the trouble of making incremental DSO list updates work against gdbserver (when the probes-based dynamic linker interface is available). The vsyscall/vDSO is mapped by the kernel when the process is initially mapped in, and doesn't change throughout the process's lifetime, so we can cache its address range. Caching at this level brings GDB back to one and only one vsyscall address range lookup per process. Tested on x86_64 Fedora 20. gdb/ 2014-10-10 Pedro Alves <palves@redhat.com> * linux-tdep.c: Include observer.h. (linux_inferior_data): New global. (struct linux_info): New structure. (invalidate_linux_cache_inf, linux_inferior_data_cleanup) (get_linux_inferior_data): New functions. (linux_vsyscall_range): Rename to ... (linux_vsyscall_range_raw): ... this. (linux_vsyscall_range): New function; handles caching. (_initialize_linux_tdep): Register linux_inferior_data. Install inferior_exit and inferior_appeared observers.
Diffstat (limited to 'gdb/linux-tdep.c')
-rw-r--r--gdb/linux-tdep.c102
1 files changed, 100 insertions, 2 deletions
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 2ffd992..ffc3e87 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -32,6 +32,7 @@
#include "cli/cli-utils.h"
#include "arch-utils.h"
#include "gdb_obstack.h"
+#include "observer.h"
#include <ctype.h>
@@ -119,6 +120,72 @@ get_linux_gdbarch_data (struct gdbarch *gdbarch)
return gdbarch_data (gdbarch, linux_gdbarch_data_handle);
}
+/* Per-inferior data key. */
+static const struct inferior_data *linux_inferior_data;
+
+/* Linux-specific cached data. This is used by GDB for caching
+ purposes for each inferior. This helps reduce the overhead of
+ transfering data from a remote target to the local host. */
+struct linux_info
+{
+ /* Cache of the inferior's vsyscall/vDSO mapping range. Only valid
+ if VSYSCALL_RANGE_P is positive. This is cached because getting
+ at this info requires an auxv lookup (which is itself cached),
+ and looking through the inferior's mappings (which change
+ throughout execution and therefore cannot be cached). */
+ struct mem_range vsyscall_range;
+
+ /* Zero if we haven't tried looking up the vsyscall's range before
+ yet. Positive if we tried looking it up, and found it. Negative
+ if we tried looking it up but failed. */
+ int vsyscall_range_p;
+};
+
+/* Frees whatever allocated space there is to be freed and sets INF's
+ linux cache data pointer to NULL. */
+
+static void
+invalidate_linux_cache_inf (struct inferior *inf)
+{
+ struct linux_info *info;
+
+ info = inferior_data (inf, linux_inferior_data);
+ if (info != NULL)
+ {
+ xfree (info);
+ set_inferior_data (inf, linux_inferior_data, NULL);
+ }
+}
+
+/* Handles the cleanup of the linux cache for inferior INF. ARG is
+ ignored. Callback for the inferior_appeared and inferior_exit
+ events. */
+
+static void
+linux_inferior_data_cleanup (struct inferior *inf, void *arg)
+{
+ invalidate_linux_cache_inf (inf);
+}
+
+/* Fetch the linux cache info for INF. This function always returns a
+ valid INFO pointer. */
+
+static struct linux_info *
+get_linux_inferior_data (void)
+{
+ struct linux_info *info;
+ struct inferior *inf = current_inferior ();
+
+ info = inferior_data (inf, linux_inferior_data);
+ if (info == NULL)
+ {
+ info = XCNEW (struct linux_info);
+ set_inferior_data (inf, linux_inferior_data, info);
+ }
+
+ return info;
+}
+
/* This function is suitable for architectures that don't
extend/override the standard siginfo structure. */
@@ -1813,10 +1880,11 @@ find_mapping_size (CORE_ADDR vaddr, unsigned long size,
return 0;
}
-/* Implementation of the "vsyscall_range" gdbarch hook. */
+/* Helper for linux_vsyscall_range that does the real work of finding
+ the vsyscall's address range. */
static int
-linux_vsyscall_range (struct gdbarch *gdbarch, struct mem_range *range)
+linux_vsyscall_range_raw (struct gdbarch *gdbarch, struct mem_range *range)
{
if (target_auxv_search (&current_target, AT_SYSINFO_EHDR, &range->start) <= 0)
return 0;
@@ -1830,6 +1898,29 @@ linux_vsyscall_range (struct gdbarch *gdbarch, struct mem_range *range)
return 1;
}
+/* Implementation of the "vsyscall_range" gdbarch hook. Handles
+ caching, and defers the real work to linux_vsyscall_range_raw. */
+
+static int
+linux_vsyscall_range (struct gdbarch *gdbarch, struct mem_range *range)
+{
+ struct linux_info *info = get_linux_inferior_data ();
+
+ if (info->vsyscall_range_p == 0)
+ {
+ if (linux_vsyscall_range_raw (gdbarch, &info->vsyscall_range))
+ info->vsyscall_range_p = 1;
+ else
+ info->vsyscall_range_p = -1;
+ }
+
+ if (info->vsyscall_range_p < 0)
+ return 0;
+
+ *range = info->vsyscall_range;
+ return 1;
+}
+
/* To be called from the various GDB_OSABI_LINUX handlers for the
various GNU/Linux architectures and machine types. */
@@ -1858,4 +1949,11 @@ _initialize_linux_tdep (void)
{
linux_gdbarch_data_handle =
gdbarch_data_register_post_init (init_linux_gdbarch_data);
+
+ /* Set a cache per-inferior. */
+ linux_inferior_data
+ = register_inferior_data_with_cleanup (NULL, linux_inferior_data_cleanup);
+ /* Observers used to invalidate the cache when needed. */
+ observer_attach_inferior_exit (invalidate_linux_cache_inf);
+ observer_attach_inferior_appeared (invalidate_linux_cache_inf);
}