aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoug Evans <dje@google.com>2015-12-10 12:00:34 -0800
committerDoug Evans <dje@google.com>2015-12-10 12:00:34 -0800
commit1b5d99cd6925dfaff8dc7ac56a63bf52732d2782 (patch)
treea2e633e4429c23b8646d5c96264fa4eb2a9167a3
parentc5f275f1c37610a5b5d1f5ead58f716352202a82 (diff)
downloadfsf-binutils-gdb-1b5d99cd6925dfaff8dc7ac56a63bf52732d2782.zip
fsf-binutils-gdb-1b5d99cd6925dfaff8dc7ac56a63bf52732d2782.tar.gz
fsf-binutils-gdb-1b5d99cd6925dfaff8dc7ac56a63bf52732d2782.tar.bz2
patch ../102887280.patch
-rw-r--r--README.google23
-rw-r--r--gdb/NEWS8
-rw-r--r--gdb/doc/gdb.texinfo33
-rw-r--r--gdb/solib-svr4.c255
-rw-r--r--gdb/testsuite/gdb.base/gcore-build-id-pie.c43
-rw-r--r--gdb/testsuite/gdb.base/gcore-build-id-pie.exp148
6 files changed, 505 insertions, 5 deletions
diff --git a/README.google b/README.google
index 0f6af39..cbbd796 100644
--- a/README.google
+++ b/README.google
@@ -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.
diff --git a/gdb/NEWS b/gdb/NEWS
index 7d2b90b..cf11fff 100644
--- 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*
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index b073152..32e20f1 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -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
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index 688f0ad..99ad285 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -34,11 +34,11 @@
#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
index 0000000..7f53d53
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gcore-build-id-pie.c
@@ -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
index 0000000..d59824f
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gcore-build-id-pie.exp
@@ -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"