]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
patch ../102887280.patch
authorDoug Evans <dje@google.com>
Thu, 10 Dec 2015 20:00:34 +0000 (12:00 -0800)
committerDoug Evans <dje@google.com>
Thu, 10 Dec 2015 20:00:34 +0000 (12:00 -0800)
README.google
gdb/NEWS
gdb/doc/gdb.texinfo
gdb/solib-svr4.c
gdb/testsuite/gdb.base/gcore-build-id-pie.c [new file with mode: 0644]
gdb/testsuite/gdb.base/gcore-build-id-pie.exp [new file with mode: 0644]

index 0f6af39642c61cbb1691a23add6e2e225b0c1ee0..cbbd796d2beebdb2d13d4df760d204fdd90ad1e4 100644 (file)
@@ -445,3 +445,26 @@ they are an ongoing maintenance burden.
 +      Submitted upstream, but not committed there yet.
 +      PR go/18926
 +      * gdb.go/methods.exp: Mark gdb_breakpoint calls as known failures.
+--- README.google      2015-09-08 13:41:18.000000000 -0700
++++ README.google      2015-09-11 16:22:06.000000000 -0700
++
++2015-09-11  Doug Evans  <dje@google.com>
++
++      * NEWS: Mention pie-displacement-verification.
++      * solib-svr4.c: #include complaints.h, gdbcmd.h.
++      (pie_displacement_verification): New static global.
++      (build_id): New struct.
++      (find_build_id_in_note_buffer, get_build_id): New functions.
++      (build_ids_match_p): New function.
++      (svr4_exec_displacement): Don't verify phdrs if
++      !pie_exec_displacement.  If build-ids match, return success.
++      Print complaint if there's a phdr mismatch.  Print warning on
++      failure.
++      (_initialize_svr4_solib): New parameter pie-displacement-verification.
++
++      doc/
++      * gdb.texinfo (Files): Document pie-displacement-verification.
++
++      testsuite/
++      * gdb.base/gcore-build-id-pie.c: New file.
++      * gdb.base/gcore-build-id-pie.exp: New file.
index 7d2b90bc4609ee4d54ad41b922410e799fba1bf3..cf11fffb4f5af6ee57982e41ea8d7f2a7c883105 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -1,6 +1,14 @@
                What has changed in GDB?
             (Organized release by release)
 
+*** Changes since GDB 7.10
+
+* New options
+set pie-displacement-verification
+show pie-displacement-verification
+Control whether to verify ELF PIE program headers.
+
 *** Changes in GDB 7.10
 
 * Support for process record-replay and reverse debugging on aarch64*-linux*
index b073152ffa63137c083a799801ddc420d8c60a65..32e20f1505191ee2f29eba928fc0bd4bec8c7eb7 100644 (file)
@@ -18186,6 +18186,39 @@ Set whether a source file may have multiple base names.
 Show whether a source file may have multiple base names.
 @end table
 
+@cindex problems with ELF PIE programs
+When the kernel runs PIE (Position Independent Executable) programs,
+they are loaded at essentially arbitrary addresses.
+When debugging these programs @value{GDBN} needs to obtain this
+load address in order to properly show program information
+like backtraces.  @value{GDBN} obtains this address from the
+@code{AT_ENTRY} entry in the target's @dfn{auxiliary vector}.
+@xref{OS Information, auxiliary vector}.
+If @value{GDBN} gets the wrong address, say because
+one is using a core file that doesn't match the program, then
+@value{GDBN}'s output will be wrong and confusing.
+Thus the load address is verified before it is used.
+This is done by comparing the ELF program headers of the program
+(say core file) with the ELF headers in the PIE executable.
+However, these tests are not bullet proof.  Plus, tool bugs
+may cause innocuous problems that cause the tests to fail, preventing the
+user from being able to use @value{GDBN}.
+@value{GDBN} provides an escape hatch to disable these tests for when they're
+getting in the way.
+
+@table @code
+@item set pie-displacement-verification
+@kindex set pie-displacement-verification
+Set whether to verify PIE program headers.
+When set to @code{on} @value{GDBN} performs a set of tests to verify
+the displacement obtained from the program is correct.
+This is on by default.
+
+@item show pie-displacement-verification
+@kindex show pie-displacement-verification
+Show whether to verify PIE program headers.
+@end table
+
 @node Separate Debug Files
 @section Debugging Information in Separate Files
 @cindex separate debugging information files
index 688f0adffb8357057d1055344d103d53e0af5c7b..99ad28595b8400cef359ba81d86da0af65641437 100644 (file)
 #include "regcache.h"
 #include "gdbthread.h"
 #include "observer.h"
-
+#include "complaints.h"
 #include "solist.h"
 #include "solib.h"
 #include "solib-svr4.h"
-
+#include "gdbcmd.h"
 #include "bfd-target.h"
 #include "elf-bfd.h"
 #include "exec.h"
@@ -46,6 +46,9 @@
 #include "gdb_bfd.h"
 #include "probe.h"
 
+/* If non-zero, verify the displacement computed for PIE executables.  */
+static int pie_displacement_verification = 1;
+
 static struct link_map_offsets *svr4_fetch_link_map_offsets (void);
 static int svr4_have_link_map_offsets (void);
 static void svr4_relocate_main_executable (void);
@@ -2527,6 +2530,201 @@ read_program_headers_from_bfd (bfd *abfd, int *phdrs_size)
   return buf;
 }
 
+/* Private copy of bfd_build_id in newer source trees.  */
+
+struct build_id
+{
+  unsigned int size;
+  unsigned char data[1];
+};
+
+/* Given a PT_NOTE segment in BUF,SIZE, return the recorded build id
+   if present, otherwise NULL.  */
+
+static struct build_id *
+find_build_id_in_note_buffer (const gdb_byte *buf, ULONGEST size,
+                             enum bfd_endian byte_order)
+{
+  /* Note: This is cribbed from bfd/elf.c:elf_parse_notes.  */
+  const Elf_External_Note *note_p;
+  const char *p, *end;
+
+  p = (const char *) buf;
+  end = p + size;
+  while (p < end)
+    {
+      const Elf_External_Note *xnp = (const Elf_External_Note *) p;
+      unsigned int type, namesz, descsz;
+      const char *namedata, *descdata;
+      struct build_id *result;
+
+      type = extract_unsigned_integer ((const gdb_byte *) xnp->type, 4,
+                                      byte_order);
+      namesz = extract_unsigned_integer ((const gdb_byte *) xnp->namesz, 4,
+                                        byte_order);
+      namedata = xnp->name;
+      if (namedata + namesz > end)
+       return NULL;
+      descsz = extract_unsigned_integer ((const gdb_byte *) xnp->descsz, 4,
+                                        byte_order);
+      descdata = namedata + align_up (namesz, 4);
+      if (descsz != 0
+         && (descdata >= end
+             || descdata + descsz > end))
+       return NULL;
+      if (namesz == sizeof "GNU"
+         && namedata[sizeof "GNU" - 1] == '\0'
+         && strcmp (namedata, "GNU") == 0
+         && type == NT_GNU_BUILD_ID)
+       {
+         if (descsz == 0)
+           return NULL;
+         result = xmalloc (sizeof (struct build_id) - 1 + descsz);
+         result->size = descsz;
+         memcpy (result->data, descdata, descsz);
+         return result;
+       }
+
+      p = descdata + align_up (descsz, 4);
+    }
+
+  return NULL;
+}
+
+/* Given a set of program headers, try to find the build id.
+   The result is a malloc'd struct build_id, caller must free;
+   or NULL if the build id could not be found.
+   ARCH_SIZE must be one of 32 or 64.
+   If ABFD is non-NULL then fetch data from the bfd, otherwise
+   fetch data from target memory.  */
+
+static struct build_id *
+get_build_id (bfd *abfd, const gdb_byte *phdrs, int num_hdrs,
+             CORE_ADDR displacement, int arch_size,
+             enum bfd_endian byte_order)
+{
+  int i;
+  int phdr_size;
+  struct build_id *result;
+
+  switch (arch_size)
+    {
+    case 32:
+      phdr_size = sizeof (Elf32_External_Phdr);
+      break;
+    case 64:
+      phdr_size = sizeof (Elf64_External_Phdr);
+      break;
+    default:
+      gdb_assert_not_reached ("bad arch_size");
+    }
+
+  for (i = 0; i < num_hdrs; ++i)
+    {
+      const gdb_byte *phdr = phdrs + (i * phdr_size);
+      gdb_byte *note_buf;
+      const gdb_byte *type_p;
+      const gdb_byte *vaddr_or_offset_p;
+      const gdb_byte *filesz_p;
+      unsigned int type;
+      CORE_ADDR vaddr_or_offset;
+      ULONGEST filesz;
+      struct cleanup *cleanups;
+
+      if (arch_size == 32)
+       {
+         const Elf32_External_Phdr *phdrp = (Elf32_External_Phdr *) phdr;
+
+         type_p = (const gdb_byte *) &phdrp->p_type;
+         if (abfd != NULL)
+           vaddr_or_offset_p = (const gdb_byte *) &phdrp->p_offset;
+         else
+           vaddr_or_offset_p = (const gdb_byte *) &phdrp->p_vaddr;
+         filesz_p = (const gdb_byte *) &phdrp->p_filesz;
+       }
+      else /* arch_size == 64 */
+       {
+         const Elf64_External_Phdr *phdrp = (Elf64_External_Phdr *) phdr;
+
+         type_p = (const gdb_byte *) &phdrp->p_type;
+         if (abfd != NULL)
+           vaddr_or_offset_p = (const gdb_byte *) &phdrp->p_offset;
+         else
+           vaddr_or_offset_p = (const gdb_byte *) &phdrp->p_vaddr;
+         filesz_p = (const gdb_byte *) &phdrp->p_filesz;
+       }
+
+      type = extract_unsigned_integer (type_p, 4, byte_order);
+      if (type != PT_NOTE)
+       continue;
+      vaddr_or_offset = extract_unsigned_integer (vaddr_or_offset_p,
+                                                 arch_size == 32 ? 4 : 8,
+                                                 byte_order);
+      filesz = extract_unsigned_integer (filesz_p, arch_size == 32 ? 4 : 8,
+                                        byte_order);
+      note_buf = xmalloc (filesz);
+      cleanups = make_cleanup (xfree, note_buf);
+      if (abfd != NULL)
+       {
+         if (bfd_seek (abfd, vaddr_or_offset, SEEK_SET) != 0
+             || bfd_bread (note_buf, filesz, abfd) != filesz)
+           {
+             do_cleanups (cleanups);
+             return NULL;
+           }
+       }
+      else
+       {
+         vaddr_or_offset += displacement;
+         if (target_read_memory (vaddr_or_offset, note_buf, filesz) != 0)
+           {
+             do_cleanups (cleanups);
+             return NULL;
+           }
+       }
+      result = find_build_id_in_note_buffer (note_buf, filesz, byte_order);
+      do_cleanups (cleanups);
+      if (result != NULL)
+       return result;
+    }
+
+  return NULL;
+}
+
+/* Return non-zero if the program headers in DISPLACEMENT1+BUF1 and OWNER2+BUF2
+   contain build ids and the build ids match.
+   DISPLACEMENT1 is the offset to use, for PIE binaries.
+   OWNER2 is the bfd that BUF2 comes from target memory.
+   BUF1 and BUF2 are copies of the program headers.
+   ARCH_SIZE must be one of 32 or 64.  */
+
+static int
+build_ids_match_p (CORE_ADDR displacement1, const gdb_byte *buf1,
+                  bfd *owner2, const gdb_byte *buf2,
+                  int num_hdrs, int arch_size, enum bfd_endian byte_order)
+{
+  struct build_id *build_id1, *build_id2;
+  int match;
+  struct cleanup *cleanups;
+
+  build_id1 = get_build_id (NULL, buf1, num_hdrs, displacement1,
+                           arch_size, byte_order);
+  cleanups = make_cleanup (xfree, build_id1);
+  build_id2 = get_build_id (owner2, buf2, num_hdrs, 0,
+                           arch_size, byte_order);
+  make_cleanup (xfree, build_id2);
+
+  match = 0;
+  if (build_id1 != NULL
+      && build_id2 != NULL
+      && build_id1->size == build_id2->size
+      && memcmp (build_id1->data, build_id2->data, build_id1->size) == 0)
+    match = 1;
+
+  do_cleanups (cleanups);
+  return match;
+}
+
 /* Return 1 and fill *DISPLACEMENTP with detected PIE offset of inferior
    exec_bfd.  Otherwise return 0.
 
@@ -2614,13 +2812,17 @@ svr4_exec_displacement (CORE_ADDR *displacementp)
      looking at a different file than the one used by the kernel - for
      instance, "gdb program" connected to "gdbserver :PORT ld.so program".  */
 
-  if (bfd_get_flavour (exec_bfd) == bfd_target_elf_flavour)
+  if (bfd_get_flavour (exec_bfd) == bfd_target_elf_flavour
+      && pie_displacement_verification)
     {
       /* Be optimistic and clear OK only if GDB was able to verify the headers
         really do not match.  */
       int phdrs_size, phdrs2_size, ok = 1;
+      /* Record which phdr is bad so we can tell the user.  */
+      int bad_phdr_num = -1;
       gdb_byte *buf, *buf2;
       int arch_size;
+      int build_ids_match = 0;
 
       buf = read_program_header (-1, &phdrs_size, &arch_size);
       buf2 = read_program_headers_from_bfd (exec_bfd, &phdrs2_size);
@@ -2653,6 +2855,11 @@ svr4_exec_displacement (CORE_ADDR *displacementp)
              CORE_ADDR displacement = 0;
              int i;
 
+             build_ids_match = build_ids_match_p (exec_displacement, buf,
+                                                  exec_bfd, buf2,
+                                                  ehdr2->e_phnum,
+                                                  arch_size, byte_order);
+
              /* DISPLACEMENT could be found more easily by the difference of
                 ehdr2->e_entry.  But we haven't read the ehdr yet, and we
                 already have enough information to compute that displacement
@@ -2772,6 +2979,7 @@ svr4_exec_displacement (CORE_ADDR *displacementp)
                    }
 
                  ok = 0;
+                 bad_phdr_num = i;
                  break;
                }
            }
@@ -2784,6 +2992,11 @@ svr4_exec_displacement (CORE_ADDR *displacementp)
              CORE_ADDR displacement = 0;
              int i;
 
+             build_ids_match = build_ids_match_p (exec_displacement, buf,
+                                                  exec_bfd, buf2,
+                                                  ehdr2->e_phnum,
+                                                  arch_size, byte_order);
+
              /* DISPLACEMENT could be found more easily by the difference of
                 ehdr2->e_entry.  But we haven't read the ehdr yet, and we
                 already have enough information to compute that displacement
@@ -2903,6 +3116,7 @@ svr4_exec_displacement (CORE_ADDR *displacementp)
                    }
 
                  ok = 0;
+                 bad_phdr_num = i;
                  break;
                }
            }
@@ -2913,8 +3127,25 @@ svr4_exec_displacement (CORE_ADDR *displacementp)
       xfree (buf);
       xfree (buf2);
 
-      if (!ok)
-       return 0;
+      if (bad_phdr_num >= 0)
+       {
+         complaint (&symfile_complaints,
+                    _("mismatch in ELF phdr #%d between executable and"
+                      " in-memory copy"),
+                    bad_phdr_num);
+       }
+
+      /* If either the phdrs match or the build ids match we're good.  */
+      if (!ok && !build_ids_match)
+       {
+         /* Print *something*.  It's not nice for a debugging session to
+            go awry because of a gdb decision, and not pass on to the user
+            what that decision was.  */
+         warning (_("Unable to calculate PIE (Position Independent"
+                    " Executable) displacement: ELF phdr mismatch and"
+                    " build-id mismatch."));
+         return 0;
+       }
     }
 
   if (info_verbose)
@@ -3273,4 +3504,18 @@ _initialize_svr4_solib (void)
   svr4_so_ops.keep_data_in_core = svr4_keep_data_in_core;
   svr4_so_ops.update_breakpoints = svr4_update_solib_event_breakpoints;
   svr4_so_ops.handle_event = svr4_handle_solib_event;
+
+  add_setshow_boolean_cmd ("pie-displacement-verification", class_obscure,
+                          &pie_displacement_verification, _("\
+Set whether to verify PIE program headers."), _("\
+Show whether to verify PIE program headers."), _("\
+When debugging PIE (Position Independent Executable) programs,\n\
+it is important, for example, to verify that the core file being used\n\
+matches the program.  If it does not then the information GDB prints\n\
+may be wrong.  However, these tests are not bullet proof.  Plus, tool bugs\n\
+may cause innocuous problems that cause the tests to fail, preventing the\n\
+user from being able to use GDB.  Set this parameter to \"off\" as an escape\n\
+hatch to disable these tests."),
+                          NULL, NULL,
+                          &setlist, &showlist);
 }
diff --git a/gdb/testsuite/gdb.base/gcore-build-id-pie.c b/gdb/testsuite/gdb.base/gcore-build-id-pie.c
new file mode 100644 (file)
index 0000000..7f53d53
--- /dev/null
@@ -0,0 +1,43 @@
+/* Copyright 2013-2015 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/>.  */
+
+__thread int tlsvar;
+
+void
+break_here (void)
+{
+  *(int *) 0 = 0;
+}
+
+void
+foo (void)
+{
+  break_here ();
+}
+
+void
+bar (void)
+{
+  foo ();
+}
+
+int
+main (void)
+{
+  bar ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/gcore-build-id-pie.exp b/gdb/testsuite/gdb.base/gcore-build-id-pie.exp
new file mode 100644 (file)
index 0000000..d59824f
--- /dev/null
@@ -0,0 +1,148 @@
+# Copyright 2013-2015 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 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/>.
+
+# PR 11786 strip can change the in-memory size of the TLS segment.
+# Generate a core file from the stripped version of the program,
+# and then try to debug the core with a hacked unstripped version.
+# The hack is to modify the program header in a way to simulate the
+# strip bug.  The actual bug causes a change to the stripped binary,
+# but we'd need to get that breakage into the core file, so instead
+# we modify the header of the unstripped binary.
+
+# This test only works on local GNU/Linux.
+if { ![isnative]
+     || [is_remote host]
+     || ![istarget *-linux*]
+     || ![istarget x86_64-*-* ]
+     || ![is_lp64_target]} {
+    continue
+}
+
+set readelf_program [gdb_find_readelf]
+
+standard_testfile
+
+if {[prepare_for_testing $testfile.exp $testfile $srcfile {debug additional_flags=-fpie "ldflags=-pie -Wl,-z,relro"}]} {
+    return -1
+}
+
+set stripped_binfile ${binfile}.stripped
+set gcorefile ${binfile}.gcore
+
+set strip_program [transform strip]
+remote_file host delete ${stripped_binfile}
+if [run_on_host "strip" "$strip_program" "-g -o ${stripped_binfile} $binfile"] {
+    return -1
+}
+
+# Workaround PR binutils/10802:
+# Preserve the 'x' bit also for PIEs (Position Independent Executables).
+set perm [file attributes ${binfile} -permissions]
+file attributes ${stripped_binfile} -permissions $perm
+
+clean_restart ${stripped_binfile}
+
+# The binary is stripped of debug info, but not minsyms.
+if ![runto break_here] {
+    fail "Can't run to break_here"
+    return -1
+}
+
+if {![gdb_gcore_cmd $gcorefile "save a corefile"]} {
+    return -1
+}
+
+proc get_phdr_offset { binfile test } {
+    global readelf_program
+    set command "exec $readelf_program -h $binfile"
+    verbose -log "command is $command"
+    set result [catch $command output]
+    verbose -log "result is $result"
+    verbose -log "output is $output"
+    if {$result != 0} {
+       fail $test
+       return
+    }
+    if ![regexp -line { *Start of program headers: +([0-9]+) } $output trash phdr_offset] {
+       fail "$test (no phdr offset found)"
+       return
+    }
+    verbose -log "phdr_offset is $phdr_offset"
+    pass $test
+    return $phdr_offset
+}
+
+proc get_tls_segment_number { binfile test } {
+    global readelf_program
+    set command "exec $readelf_program -Wl $binfile"
+    verbose -log "command is $command"
+    set result [catch $command output]
+    verbose -log "result is $result"
+    verbose -log "output is $output"
+    if {$result != 0} {
+       fail $test
+       return
+    }
+    if ![regexp {\nProgram Headers:\n *Type [^\n]+\n(.*?)\n\n} $output trash phdr] {
+       fail "$test (no Program Headers)"
+       return -1
+    }
+    set phdr [regsub {\n[^\n]+Requesting program interpreter: [^\n]+\n} $phdr "\n"]
+    verbose -log "phdr: $phdr"
+    set seg_num 0
+    set tls_seg_num -1
+    foreach {trash name} [regexp -line -all -inline {^ *([A-Z_]+) .*$} $phdr] {
+       verbose -log "line $seg_num: $name"
+       if { $name == "TLS" } {
+           set tls_seg_num $seg_num
+       }
+       set seg_num [expr $seg_num + 1]
+    }
+    if { $tls_seg_num < 0 } {
+       fail "$test (no TLS segment)"
+       return -1
+    }
+    verbose -log "tls segment number is $tls_seg_num"
+    pass $test
+    return $tls_seg_num
+}
+
+# Hack the unstripped binary so that the program header comparison that
+# gdb does will fail.
+# The TLS segment will have a non-zero memory size, we just have to zero it.
+# On amd64-linux, each ELF program header is 56 bytes and the offset of the
+# p_memsz field of Elf64_External_Phdr is 40.
+
+set phdr_offset [get_phdr_offset $binfile "get phdr offset"]
+set tls_segment_number [get_tls_segment_number $binfile "get tls seg number"]
+
+set f [open $binfile "r+"]
+set offset [expr $phdr_offset + $tls_segment_number * 56 + 40]
+verbose -log "Setting byte @$offset to zero."
+seek $f $offset start
+puts -nonewline $f "\0"
+close $f
+
+# Now restart gdb with the unstripped binary and load the corefile.
+
+clean_restart ${binfile}
+
+gdb_test "core ${gcorefile}" \
+    "Core was generated by .*" "re-load generated corefile"
+
+# Put $pc in gdb.log for debug purposes for comparison with stripped case.
+gdb_test "x/i \$pc" "break_here.*"
+
+gdb_test "frame" "#0 \[^\r\n\]* break_here .*" "unstripped + core ok"