diff options
author | Doug Evans <dje@google.com> | 2015-12-10 12:00:34 -0800 |
---|---|---|
committer | Doug Evans <dje@google.com> | 2015-12-10 12:00:34 -0800 |
commit | 1b5d99cd6925dfaff8dc7ac56a63bf52732d2782 (patch) | |
tree | a2e633e4429c23b8646d5c96264fa4eb2a9167a3 | |
parent | c5f275f1c37610a5b5d1f5ead58f716352202a82 (diff) | |
download | fsf-binutils-gdb-1b5d99cd6925dfaff8dc7ac56a63bf52732d2782.zip fsf-binutils-gdb-1b5d99cd6925dfaff8dc7ac56a63bf52732d2782.tar.gz fsf-binutils-gdb-1b5d99cd6925dfaff8dc7ac56a63bf52732d2782.tar.bz2 |
patch ../102887280.patch
-rw-r--r-- | README.google | 23 | ||||
-rw-r--r-- | gdb/NEWS | 8 | ||||
-rw-r--r-- | gdb/doc/gdb.texinfo | 33 | ||||
-rw-r--r-- | gdb/solib-svr4.c | 255 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/gcore-build-id-pie.c | 43 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/gcore-build-id-pie.exp | 148 |
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. @@ -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" |