This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

cope with varying prelink base addresses


If you take a core file created on a box whose libraries have been
prelinked with random base addresses and try to use it for debugging
on another box that has the very same binaries, but prelinked with a
different base address, gdb will get all confused.

That's because the load_addr of the prelinked binaries will be
whatever difference there was between the base address selected by
prelink and the base address used at run time by the dynamic loader.
If everything is prelinked successfully, the load addresses will all
be zero.

So when gdb reads the binary prelinked with a different base address,
and applies to it the load address read from the core file, it won't
get the correct address if the prelinked binary on the debugging host
got a different address.

This is arguably a change in the binary, so debugging is not expected
to work anyway, but it definitely is surprising, especially when
prelink is enabled by default.  This would render core files useless
across different hosts.

This patch gets gdb to try to recognize the situation in which a
binary got prelinked but is otherwise unchanged, and figure out the
difference between the base addresses at core-generating host and at
the debugging host.

The only piece of information available for this that I could find was
the address of the dynamic table in the dynamic loader data
structures.  The heuristics I used was to check whether the dynamic
table address changed but remained at the same position within a
page.  If so, I assume the difference is caused by prelinking, and
then I adjust the load_addr that gdb is going to use for that binary.
Otherwise, it will face the same problems you're expected to face when
debugging a core file using a different binary.

I'd appreciate if whoever reviews this would pay particular attention
to the gdb testcase; it's the first time I write one, and I could use
some guidance to make sure I'm not making undeserved assumptions.
It's bad enough that I have to use compile- and run-time flags that
will likely only work with GCC and GNU ld, and that the test requires
the prelink program to be in the PATH; I hope I'm coping well with
cases in which the flags are not accepted or prelink is not in place.

Comments?  Ok to install?  Tested on amd64-linux-gnu.

for gdb/ChangeLog
from  Alexandre Oliva  <aoliva@redhat.com>

	* solib-svr4.h (struct link_map_offsets): Add l_ld_offset and
	l_ld_size fields.
	* solib-svr4.c (struct lm_info): Add l_addr field.
	(LM_ADDR_FROM_LINK_MAP): Renamed from LM_ADDR.
	(HAS_LM_DYNAMIC_FROM_LINK_MAP): New.
	(LM_DYNAMIC_FROM_LINK_MAP): New.
	(LM_ADDR_CHECK): New.  Use it instead of LM_ADDR.
	(svr4_current_sos): Initialize l_addr.  Adjust.
	(svr4_relocate_section_addresses): Adjust.
	(svr4_ilp32_fetch_link_map_offsets): Define new members.
	(svr4_lp64_fetch_link_map_offsets): Likewise.
	* solib-legacy.c (legacy_svr4_fetch_link_map_offsets): Likewise.
	* mipsnbsd-tdep.c (mipsnbsd_ilp32_fetch_link_map_offsets): Likewise.
	(mipsnbsd_lp64_fetch_link_map_offsets): Likewise.

for gdb/testsuite/ChangeLog
from  Alexandre Oliva  <aoliva@redhat.com>

	* gdb.base/prelink.exp: New test.
	* gdb.base/prelink.c, gdb.base/prelink-lib.c: New sources.

Index: gdb/mipsnbsd-tdep.c
===================================================================
--- gdb/mipsnbsd-tdep.c.orig	2006-02-08 00:09:27.000000000 -0200
+++ gdb/mipsnbsd-tdep.c	2006-02-08 00:38:54.000000000 -0200
@@ -339,6 +339,8 @@ mipsnbsd_ilp32_fetch_link_map_offsets (v
       lmo.l_addr_size = 4;
       lmo.l_name_offset = 8;
       lmo.l_name_size = 4;
+      lmo.l_ld_offset = 12;
+      lmo.l_ld_size = 4;
       lmo.l_next_offset = 16;
       lmo.l_next_size = 4;
       lmo.l_prev_offset = 20;
@@ -369,6 +371,8 @@ mipsnbsd_lp64_fetch_link_map_offsets (vo
       lmo.l_addr_size = 8;
       lmo.l_name_offset = 16; 
       lmo.l_name_size = 8;
+      lmo.l_ld_offset = 24;
+      lmo.l_ld_size = 8;
       lmo.l_next_offset = 32;
       lmo.l_next_size = 8;
       lmo.l_prev_offset = 40;
Index: gdb/solib-legacy.c
===================================================================
--- gdb/solib-legacy.c.orig	2006-02-08 00:09:27.000000000 -0200
+++ gdb/solib-legacy.c	2006-02-08 00:38:54.000000000 -0200
@@ -69,6 +69,9 @@ legacy_svr4_fetch_link_map_offsets (void
       lmo.l_next_offset = offsetof (struct link_map, l_next);
       lmo.l_next_size = fieldsize (struct link_map, l_next);
 
+      lmo.l_ld_offset = offsetof (struct link_map, l_ld);
+      lmo.l_ld_size = fieldsize (struct link_map, l_ld);
+
       lmo.l_prev_offset = offsetof (struct link_map, l_prev);
       lmo.l_prev_size = fieldsize (struct link_map, l_prev);
 
@@ -84,6 +87,10 @@ legacy_svr4_fetch_link_map_offsets (void
       lmo.l_next_offset = offsetof (struct link_map, lm_next);
       lmo.l_next_size = fieldsize (struct link_map, lm_next);
 
+      /* FIXME: Is this the right field name, or is it available at all?  */
+      lmo.l_ld_offset = offsetof (struct link_map, lm_ld);
+      lmo.l_ld_size = fieldsize (struct link_map, lm_ld);
+
       lmo.l_name_offset = offsetof (struct link_map, lm_name);
       lmo.l_name_size = fieldsize (struct link_map, lm_name);
 #else /* !defined(HAVE_STRUCT_LINK_MAP_WITH_LM_MEMBERS) */
@@ -98,6 +105,10 @@ legacy_svr4_fetch_link_map_offsets (void
 
       lmo.l_name_offset = offsetof (struct so_map, som_path);
       lmo.l_name_size = fieldsize (struct so_map, som_path);
+
+      /* FIXME: Is the address of the dynamic table available?  */
+      lmo.l_ld_offset = 0;
+      lmo.l_ld_size = 0;
 #endif /* HAVE_STRUCT_SO_MAP_WITH_SOM_MEMBERS */
 #endif /* HAVE_STRUCT_LINK_MAP_WITH_LM_MEMBERS */
 #endif /* HAVE_STRUCT_LINK_MAP_WITH_L_MEMBERS */
Index: gdb/solib-svr4.c
===================================================================
--- gdb/solib-svr4.c.orig	2006-02-08 00:09:27.000000000 -0200
+++ gdb/solib-svr4.c	2006-02-08 02:58:15.000000000 -0200
@@ -42,6 +42,7 @@
 #include "solib-svr4.h"
 
 #include "bfd-target.h"
+#include "elf-bfd.h"
 #include "exec.h"
 
 static struct link_map_offsets *svr4_fetch_link_map_offsets (void);
@@ -59,6 +60,13 @@ struct lm_info
        rather than void *, so that we may use byte offsets to find the
        various fields without the need for a cast.  */
     gdb_byte *lm;
+
+    /* Amount by which addresses in the binary should be relocated to
+       match the inferior.  This could most often be taken directly
+       from lm, but when prelinking is involved and the prelink base
+       address changes, we may need a different offset, we want to
+       warn about the difference and compute it only once.  */
+    CORE_ADDR l_addr;
   };
 
 /* On SVR4 systems, a list of symbols in the dynamic linker where
@@ -127,14 +135,101 @@ static char *main_name_list[] =
 /* link map access functions */
 
 static CORE_ADDR
-LM_ADDR (struct so_list *so)
+LM_ADDR_FROM_LINK_MAP (struct so_list *so)
 {
   struct link_map_offsets *lmo = svr4_fetch_link_map_offsets ();
 
-  return (CORE_ADDR) extract_signed_integer (so->lm_info->lm + lmo->l_addr_offset, 
+  return (CORE_ADDR) extract_signed_integer (so->lm_info->lm
+					     + lmo->l_addr_offset,
 					     lmo->l_addr_size);
 }
 
+static int
+HAS_LM_DYNAMIC_FROM_LINK_MAP ()
+{
+  struct link_map_offsets *lmo = svr4_fetch_link_map_offsets ();
+
+  return (lmo->l_ld_size != 0);
+}
+
+static CORE_ADDR
+LM_DYNAMIC_FROM_LINK_MAP (struct so_list *so)
+{
+  struct link_map_offsets *lmo = svr4_fetch_link_map_offsets ();
+
+  gdb_assert (lmo->l_ld_size != 0);
+
+  return (CORE_ADDR) extract_signed_integer (so->lm_info->lm
+					     + lmo->l_ld_offset,
+					     lmo->l_ld_size);
+}
+
+static CORE_ADDR
+LM_ADDR_CHECK (struct so_list *so, bfd *abfd)
+{
+  if (so->lm_info->l_addr == (CORE_ADDR)-1)
+    {
+      struct bfd_section *dyninfo_sect;
+      CORE_ADDR l_addr, l_dynaddr, dynaddr, align = 0x1000;
+
+      l_addr = LM_ADDR_FROM_LINK_MAP (so);
+
+      if (! abfd || ! HAS_LM_DYNAMIC_FROM_LINK_MAP ())
+	goto set_addr;
+
+      l_dynaddr = LM_DYNAMIC_FROM_LINK_MAP (so);
+
+      dyninfo_sect = bfd_get_section_by_name (abfd, ".dynamic");
+      if (dyninfo_sect == NULL)
+	goto set_addr;
+
+      dynaddr = bfd_section_vma (abfd, dyninfo_sect);
+
+      if (dynaddr + l_addr != l_dynaddr)
+	{
+	  warning (_(".dynamic section for \"%s\" "
+		     "is not at the expected address"), so->so_name);
+
+	  if (bfd_get_flavour (abfd) == bfd_target_elf_flavour)
+	    {
+	      Elf_Internal_Ehdr *ehdr = elf_tdata (abfd)->elf_header;
+	      Elf_Internal_Phdr *phdr = elf_tdata (abfd)->phdr;
+	      int i;
+
+	      align = 1;
+
+	      for (i = 0; i < ehdr->e_phnum; i++)
+		if (phdr[i].p_type == PT_LOAD && phdr[i].p_align > align)
+		  align = phdr[i].p_align;
+	    }
+
+	  /* Turn it into a mask.  */
+	  align--;
+
+	  /* If the changes match the alignment requirements, we
+	     assume we're using a core file that was generated by the
+	     same binary, just prelinked with a different base offset.
+	     If it doesn't match, we may have a different binary, the
+	     same binary with the dynamic table loaded at an unrelated
+	     location, or anything, really.  To avoid regressions,
+	     don't adjust the base offset in the latter case, although
+	     odds are that, if things really changed, debugging won't
+	     quite work.  */
+	  if ((l_addr & align) == 0 && ((dynaddr - l_dynaddr) & align) == 0)
+	    {
+	      l_addr = l_dynaddr - dynaddr;
+	      warning (_("difference appears to be caused by prelink, "
+			 "adjusting expectations"));
+	    }
+	}
+
+    set_addr:
+      so->lm_info->l_addr = l_addr;
+    }
+
+  return so->lm_info->l_addr;
+}
+
 static CORE_ADDR
 LM_NEXT (struct so_list *so)
 {
@@ -649,6 +744,8 @@ svr4_current_sos (void)
 	    free_so (new);
 	  else
 	    {
+	      new->lm_info->l_addr = (CORE_ADDR)-1;
+
 	      new->next = 0;
 	      *link_ptr = new;
 	      link_ptr = &new->next;
@@ -912,7 +1009,7 @@ enable_break (void)
 	  if (strcmp (buf, so->so_original_name) == 0)
 	    {
 	      load_addr_found = 1;
-	      load_addr = LM_ADDR (so);
+	      load_addr = LM_ADDR_CHECK (so, tmp_bfd);
 	      break;
 	    }
 	  so = so->next;
@@ -1272,8 +1369,10 @@ static void
 svr4_relocate_section_addresses (struct so_list *so,
                                  struct section_table *sec)
 {
-  sec->addr    = svr4_truncate_ptr (sec->addr    + LM_ADDR (so));
-  sec->endaddr = svr4_truncate_ptr (sec->endaddr + LM_ADDR (so));
+  sec->addr    = svr4_truncate_ptr (sec->addr    + LM_ADDR_CHECK (so,
+								  sec->bfd));
+  sec->endaddr = svr4_truncate_ptr (sec->endaddr + LM_ADDR_CHECK (so,
+								  sec->bfd));
 }
 
 
@@ -1362,6 +1461,8 @@ svr4_ilp32_fetch_link_map_offsets (void)
       lmo.l_addr_size = 4;
       lmo.l_name_offset = 4;
       lmo.l_name_size = 4;
+      lmo.l_ld_offset = 8;
+      lmo.l_ld_size = 4;
       lmo.l_next_offset = 12;
       lmo.l_next_size = 4;
       lmo.l_prev_offset = 16;
@@ -1395,6 +1496,8 @@ svr4_lp64_fetch_link_map_offsets (void)
       lmo.l_addr_size = 8;
       lmo.l_name_offset = 8;
       lmo.l_name_size = 8;
+      lmo.l_ld_offset = 16;
+      lmo.l_ld_size = 8;
       lmo.l_next_offset = 24;
       lmo.l_next_size = 8;
       lmo.l_prev_offset = 32;
Index: gdb/solib-svr4.h
===================================================================
--- gdb/solib-svr4.h.orig	2006-02-08 00:09:27.000000000 -0200
+++ gdb/solib-svr4.h	2006-02-08 00:38:54.000000000 -0200
@@ -50,6 +50,12 @@ struct link_map_offsets
     /* Size of l_addr field in struct link_map.  */
     int l_addr_size;
 
+    /* Offset to l_ld field in struct link_map.  */
+    int l_ld_offset;
+
+    /* Size of l_ld field in struct link_map.  */
+    int l_ld_size;
+
     /* Offset to l_next field in struct link_map.  */
     int l_next_offset;
 
Index: gdb/Makefile.in
===================================================================
--- gdb/Makefile.in.orig	2006-02-06 03:44:14.000000000 -0200
+++ gdb/Makefile.in	2006-02-08 01:19:32.000000000 -0200
@@ -2570,7 +2570,8 @@ solib-sunos.o: solib-sunos.c $(defs_h) $
 solib-svr4.o: solib-svr4.c $(defs_h) $(elf_external_h) $(elf_common_h) \
 	$(elf_mips_h) $(symtab_h) $(bfd_h) $(symfile_h) $(objfiles_h) \
 	$(gdbcore_h) $(target_h) $(inferior_h) $(gdb_assert_h) \
-	$(solist_h) $(solib_h) $(solib_svr4_h) $(bfd_target_h) $(exec_h)
+	$(solist_h) $(solib_h) $(solib_svr4_h) $(bfd_target_h) $(elf_bfd_h) \
+	$(exec_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/testsuite/gdb.base/prelink-lib.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb/testsuite/gdb.base/prelink-lib.c	2006-02-08 02:42:10.000000000 -0200
@@ -0,0 +1,15 @@
+int
+g (void (*p)(void))
+{
+  p ();
+}
+
+void
+f(void (*p)(void)) {
+  g (p);
+}
+
+void (*h (void)) (void (*p)(void))
+{
+  return f;
+}
Index: gdb/testsuite/gdb.base/prelink.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb/testsuite/gdb.base/prelink.c	2006-02-08 02:42:13.000000000 -0200
@@ -0,0 +1,11 @@
+#include <stdio.h>
+
+extern void (*h (void)) (void (*)(void));
+
+int
+main (void)
+{
+  void (*f) (void (*)(void)) = h ();
+  printf ("%p\n", f);
+  f (0);
+}
Index: gdb/testsuite/gdb.base/prelink.exp
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb/testsuite/gdb.base/prelink.exp	2006-02-08 03:00:21.000000000 -0200
@@ -0,0 +1,128 @@
+# Copyright 2006 Free Software Foundation, Inc.
+
+# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# Please email any bugs, comments, and/or additions to this file to:
+# bug-gdb@prep.ai.mit.edu
+
+# This file was written by Alexandre Oliva <aoliva@redhat.com>
+
+if $tracelevel then {
+	strace $tracelevel
+	}
+
+set prms_id 0
+set bug_id 0
+
+# are we on a target board
+if ![isnative] then {
+    return
+}
+
+set testfile "prelink"
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/${testfile}
+
+set libsrcfile ${testfile}-lib.c
+set libfile ${objdir}/${subdir}/${testfile}.so
+if { [gdb_compile "${srcdir}/${subdir}/${libsrcfile}" "${libfile}" executable [list debug "additional_flags=-fpic -shared -nodefaultlibs"]] != ""} {
+    # If creating the shared library fails, maybe we don't have the right tools
+    return -1
+}
+
+if {[catch "system \"prelink -NR ${libfile}\""] != 0} {
+    # Maybe we don't have prelink.
+    return -1
+}
+
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/${testfile}
+if { [gdb_compile "${srcdir}/${subdir}/${srcfile} ${libfile}" "${binfile}" executable [list debug "additional_flags=-Wl,-rpath,${objdir}/${subdir}"]] != ""} {
+    return -1;
+}
+
+if [get_compiler_info ${binfile}] {
+    return -1
+}
+
+if {$gcc_compiled == 0} {
+    return -1
+}
+
+set found 0
+set coredir "${objdir}/${subdir}/coredir.[getpid]"
+file mkdir $coredir
+catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile}; true) >/dev/null 2>&1\""
+
+foreach i "${coredir}/core ${coredir}/core.coremaker.c ${binfile}.core" {
+    if [remote_file build exists $i] {
+	remote_exec build "mv $i ${objdir}/${subdir}/prelink.core"
+	set found 1
+    }
+}
+# Check for "core.PID".
+if { $found == 0 } {
+    set names [glob -nocomplain -directory $coredir core.*]
+    if {[llength $names] == 1} {
+        set corefile [file join $coredir [lindex $names 0]]
+        remote_exec build "mv $corefile ${objdir}/${subdir}/prelink.core"
+        set found 1
+    }
+}
+
+catch "system \"prelink -u ${libfile}\""
+catch "system \"prelink -NR ${libfile}\""
+
+# Try to clean up after ourselves.
+remote_file build delete [file join $coredir coremmap.data]
+remote_exec build "rmdir $coredir"
+
+if { $found == 0  } {
+    warning "can't generate a core file - prelink tests suppressed - check ulimit -c"
+    return 0
+}
+
+# Start with a fresh gdb
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+set oldtimeout $timeout
+set timeout [expr "$timeout + 60"]
+verbose "Timeout is now $timeout seconds" 2
+send_gdb "core-file $objdir/$subdir/prelink.core\n"
+gdb_expect {
+    -re "warning: \.dynamic section.*not at the expected address" {
+	pass "changed base address"
+    }
+    -re ".*$gdb_prompt $"	{ fail "changed base address" }
+    timeout 		{ fail "(timeout) changed base address" }
+}
+gdb_expect {
+    -re "warning: difference.*caused by prelink, adjusting" {
+	pass "prelink adjustment"
+    }
+    -re ".*$gdb_prompt $"	{ fail "prelink adjustment" }
+    timeout 		{ fail "(timeout) prelink adjustment" }
+}
+set timeout $oldtimeout
+verbose "Timeout is now $timeout seconds" 2
+
+gdb_exit
+
+return 0
+
-- 
Alexandre Oliva         http://www.lsd.ic.unicamp.br/~oliva/
Secretary for FSF Latin America        http://www.fsfla.org/
Red Hat Compiler Engineer   aoliva@{redhat.com, gcc.gnu.org}
Free Software Evangelist  oliva@{lsd.ic.unicamp.br, gnu.org}

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]