aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bfd/ChangeLog23
-rw-r--r--bfd/bfd-in2.h2
-rw-r--r--bfd/dwarf2.c6
-rw-r--r--bfd/opncls.c373
-rw-r--r--binutils/ChangeLog8
-rw-r--r--binutils/NEWS4
-rw-r--r--binutils/testsuite/binutils-all/objdump.exp80
7 files changed, 456 insertions, 40 deletions
diff --git a/bfd/ChangeLog b/bfd/ChangeLog
index 252e57b..6705ae4 100644
--- a/bfd/ChangeLog
+++ b/bfd/ChangeLog
@@ -1,3 +1,26 @@
+2017-01-12 Nick Clifton <nickc@redhat.com>
+
+ PR binutils/20876
+ * opncls.c (find_separate_debug_file): Add include_dirs
+ parameter. Only include the directory part of the bfd's filename
+ in search paths if include_dirs is true. Add a couple of extra
+ locations for looking for debug files.
+ ( bfd_follow_gnu_debuglink): Update invocation of
+ find_separate_debug_file.
+ (bfd_follow_gnu_debugaltlink): Likewise.
+ (get_build_id): New function: Finds the build-id of the given bfd.
+ (get_build_id_name): New function: Computes the name of the
+ separate debug info file for a bfd, based upon its build-id.
+ (check_build_id_file): New function: Checks to see if a separate
+ debug info file exists at the given location, and that its
+ build-id matches that of the original bfd.
+ (bfd_follow_build_id_debuglink): New function: Finds a separate
+ debug info file for a given bfd by using the build-id method.
+ * dwarf2.c (_bfd_dwarf2_slurp_debug_info): Try using the build-id
+ method of locating a separate debug info file before using the
+ debuglink method.
+ * bfd-in2.h: Regenerate.
+
2017-01-11 H.J. Lu <hongjiu.lu@intel.com>
PR ld/21038
diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index 2930089..80d60cb 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -1121,6 +1121,8 @@ struct bfd_section *bfd_create_gnu_debuglink_section
bfd_boolean bfd_fill_in_gnu_debuglink_section
(bfd *abfd, struct bfd_section *sect, const char *filename);
+char *bfd_follow_build_id_debuglink (bfd *abfd, const char *dir);
+
/* Extracted from libbfd.c. */
/* Byte swapping macros for user section data. */
diff --git a/bfd/dwarf2.c b/bfd/dwarf2.c
index 40edd91..3699587 100644
--- a/bfd/dwarf2.c
+++ b/bfd/dwarf2.c
@@ -3921,7 +3921,11 @@ _bfd_dwarf2_slurp_debug_info (bfd *abfd, bfd *debug_bfd,
msec = find_debug_info (debug_bfd, debug_sections, NULL);
if (msec == NULL && abfd == debug_bfd)
{
- char * debug_filename = bfd_follow_gnu_debuglink (abfd, DEBUGDIR);
+ char * debug_filename;
+
+ debug_filename = bfd_follow_build_id_debuglink (abfd, DEBUGDIR);
+ if (debug_filename == NULL)
+ debug_filename = bfd_follow_gnu_debuglink (abfd, DEBUGDIR);
if (debug_filename == NULL)
/* No dwarf2 info, and no gnu_debuglink to follow.
diff --git a/bfd/opncls.c b/bfd/opncls.c
index 10684d2..b4d4dcf 100644
--- a/bfd/opncls.c
+++ b/bfd/opncls.c
@@ -25,6 +25,7 @@
#include "objalloc.h"
#include "libbfd.h"
#include "libiberty.h"
+#include "elf-bfd.h"
#ifndef S_IXUSR
#define S_IXUSR 0100 /* Execute by owner. */
@@ -1305,7 +1306,7 @@ INTERNAL_FUNCTION
SYNOPSIS
bfd_boolean separate_alt_debug_file_exists
- (char *name, unsigned long crc32);
+ (char *name, unsigned long buildid);
DESCRIPTION
Checks to see if @var{name} is a file and if its BuildID
@@ -1336,17 +1337,21 @@ INTERNAL_FUNCTION
find_separate_debug_file
SYNOPSIS
- char *find_separate_debug_file (bfd *abfd);
+ char *find_separate_debug_file
+ (bfd *abfd, const char *dir, bfd_boolean include_dirs,
+ get_func_type get, check_func_type check);
DESCRIPTION
- Searches @var{abfd} for a section called @var{section_name} which
- is expected to contain a reference to a file containing separate
- debugging information. The function scans various locations in
- the filesystem, including the file tree rooted at
- @var{debug_file_directory}, and returns the first matching
- filename that it finds. If @var{check_crc} is TRUE then the
- contents of the file must also match the CRC value contained in
- @var{section_name}. Returns NULL if no valid file could be found.
+ Searches for a debug information file corresponding to @var{abfd}.
+ The name of the separate debug info file is returned by the @var{get}
+ function. This function scans various fixed locations in the
+ filesystem, including the file tree rooted at @var{dir}. If the
+ @var{include_dirs} parameter is true then the directory components of
+ @var{abfd}'s filename will be included in the searched locations.
+
+ Returns the filename of the first file to be found which receives a
+ TRUE result from the @var{check} function. Returns NULL if no valid
+ file could be found.
*/
typedef char * (* get_func_type) (bfd *, unsigned long *);
@@ -1355,6 +1360,7 @@ typedef bfd_boolean (* check_func_type) (const char *, const unsigned long);
static char *
find_separate_debug_file (bfd * abfd,
const char * debug_file_directory,
+ bfd_boolean include_dirs,
get_func_type get_func,
check_func_type check_func)
{
@@ -1389,18 +1395,27 @@ find_separate_debug_file (bfd * abfd,
return NULL;
}
- for (dirlen = strlen (abfd->filename); dirlen > 0; dirlen--)
- if (IS_DIR_SEPARATOR (abfd->filename[dirlen - 1]))
- break;
+ if (include_dirs)
+ {
+ for (dirlen = strlen (abfd->filename); dirlen > 0; dirlen--)
+ if (IS_DIR_SEPARATOR (abfd->filename[dirlen - 1]))
+ break;
- dir = (char *) bfd_malloc (dirlen + 1);
- if (dir == NULL)
+ dir = (char *) bfd_malloc (dirlen + 1);
+ if (dir == NULL)
+ {
+ free (base);
+ return NULL;
+ }
+ memcpy (dir, abfd->filename, dirlen);
+ dir[dirlen] = '\0';
+ }
+ else
{
- free (base);
- return NULL;
+ dir = (char *) bfd_malloc (1);
+ * dir = 0;
+ dirlen = 0;
}
- memcpy (dir, abfd->filename, dirlen);
- dir[dirlen] = '\0';
/* Compute the canonical name of the bfd object with all symbolic links
resolved, for use in the global debugfile directory. */
@@ -1410,38 +1425,78 @@ find_separate_debug_file (bfd * abfd,
break;
canon_dir[canon_dirlen] = '\0';
+#ifndef EXTRA_DEBUG_ROOT1
+#define EXTRA_DEBUG_ROOT1 "/usr/lib/debug"
+#endif
+#ifndef EXTRA_DEBUG_ROOT2
+#define EXTRA_DEBUG_ROOT2 "/usr/lib/debug/usr"
+#endif
+
debugfile = (char *)
bfd_malloc (strlen (debug_file_directory) + 1
+ (canon_dirlen > dirlen ? canon_dirlen : dirlen)
+ strlen (".debug/")
+#ifdef EXTRA_DEBUG_ROOT1
+ + strlen (EXTRA_DEBUG_ROOT1)
+#endif
+#ifdef EXTRA_DEBUG_ROOT2
+ + strlen (EXTRA_DEBUG_ROOT2)
+#endif
+ strlen (base)
+ 1);
if (debugfile == NULL)
goto found; /* Actually this returns NULL. */
- /* First try in the same directory as the original file: */
- strcpy (debugfile, dir);
- strcat (debugfile, base);
+ /* First try in the same directory as the original file.
+ FIXME: Strictly speaking if we are using the build-id method,
+ (ie include_dirs == FALSE) then we should only check absolute
+ paths, not relative ones like this one (and the next one).
+ The check is left in however as this allows the binutils
+ testsuite to exercise this feature without having to install
+ a file into the root filesystem. (See binutils/testsuite/
+ binutils-all/objdump.exp for the test). */
+ sprintf (debugfile, "%s%s", dir, base);
if (check_func (debugfile, crc32))
goto found;
/* Then try in a subdirectory called .debug. */
- strcpy (debugfile, dir);
- strcat (debugfile, ".debug/");
- strcat (debugfile, base);
+ sprintf (debugfile, "%s.debug/%s", dir, base);
+ if (check_func (debugfile, crc32))
+ goto found;
+#ifdef EXTRA_DEBUG_ROOT1
+ /* Try the first extra debug file root. */
+ sprintf (debugfile, "%s%s%s", EXTRA_DEBUG_ROOT1,
+ include_dirs ? canon_dir : "/", base);
if (check_func (debugfile, crc32))
goto found;
+#endif
+#ifdef EXTRA_DEBUG_ROOT2
+ /* Try the second extra debug file root. */
+ sprintf (debugfile, "%s%s%s", EXTRA_DEBUG_ROOT2,
+ include_dirs ? canon_dir : "/", base);
+ if (check_func (debugfile, crc32))
+ goto found;
+#endif
+
/* Then try in the global debugfile directory. */
strcpy (debugfile, debug_file_directory);
dirlen = strlen (debug_file_directory) - 1;
- if (dirlen > 0
- && debug_file_directory[dirlen] != '/'
- && canon_dir[0] != '/')
- strcat (debugfile, "/");
- strcat (debugfile, canon_dir);
+ if (include_dirs)
+ {
+ if (dirlen > 0
+ && debug_file_directory[dirlen] != '/'
+ && canon_dir[0] != '/')
+ strcat (debugfile, "/");
+ strcat (debugfile, canon_dir);
+ }
+ else
+ {
+ if (dirlen > 0 && debug_file_directory[dirlen] != '/')
+ strcat (debugfile, "/");
+ }
strcat (debugfile, base);
if (check_func (debugfile, crc32))
@@ -1475,9 +1530,8 @@ DESCRIPTION
locations, including the directory tree rooted at @var{dir}, and if
found returns the full filename.
- If @var{dir} is NULL, it will search a default path configured into
- libbfd at build time. [XXX this feature is not currently
- implemented].
+ If @var{dir} is NULL, the search will take place starting at
+ the current directory.
RETURNS
<<NULL>> on any errors or failure to locate the .debug file,
@@ -1488,7 +1542,7 @@ RETURNS
char *
bfd_follow_gnu_debuglink (bfd *abfd, const char *dir)
{
- return find_separate_debug_file (abfd, dir,
+ return find_separate_debug_file (abfd, dir, TRUE,
bfd_get_debug_link_info,
separate_debug_file_exists);
}
@@ -1522,14 +1576,13 @@ DESCRIPTION
Takes a BFD and searches it for a .gnu_debugaltlink section. If this
section is found, it examines the section for the name of a file
- containing auxiliary debugging information. It then searches the
+ containing auxiliary debugging information. It then searches the
filesystem for this file in a set of standard locations, including
the directory tree rooted at @var{dir}, and if found returns the
full filename.
- If @var{dir} is NULL, it will search a default path configured into
- libbfd at build time. [FIXME: This feature is not currently
- implemented].
+ If @var{dir} is NULL, the search will take place starting at
+ the current directory.
RETURNS
<<NULL>> on any errors or failure to locate the debug file,
@@ -1540,7 +1593,7 @@ RETURNS
char *
bfd_follow_gnu_debugaltlink (bfd *abfd, const char *dir)
{
- return find_separate_debug_file (abfd, dir,
+ return find_separate_debug_file (abfd, dir, TRUE,
get_alt_debug_link_info_shim,
separate_alt_debug_file_exists);
}
@@ -1695,3 +1748,245 @@ bfd_fill_in_gnu_debuglink_section (bfd *abfd,
return TRUE;
}
+
+/*
+INTERNAL_FUNCTION
+ get_build_id
+
+SYNOPSIS
+ struct bfd_build_id * get_build_id
+ (bfd *abfd);
+
+DESCRIPTION
+ Finds the build-id associated with @var{abfd}. If the build-id is
+ extracted from the note section then a build-id structure is built
+ for it, using memory allocated to @var{abfd}, and this is then
+ attached to the @var{abfd}.
+
+ Returns a pointer to the build-id structure if a build-id could be
+ found. If no build-id is found NULL is returned and error code is
+ set.
+*/
+
+static struct bfd_build_id *
+get_build_id (bfd *abfd)
+{
+ struct bfd_build_id *build_id;
+ Elf_Internal_Note inote;
+ Elf_External_Note *enote;
+ bfd_byte *contents;
+ asection *sect;
+
+ BFD_ASSERT (abfd);
+
+ if (abfd->build_id && abfd->build_id->size > 0)
+ /* Save some time by using the already computed build-id. */
+ return (struct bfd_build_id *) abfd->build_id;
+
+ sect = bfd_get_section_by_name (abfd, ".note.gnu.build-id");
+ if (sect == NULL)
+ {
+ bfd_set_error (bfd_error_no_debug_section);
+ return NULL;
+ }
+
+ /* FIXME: Should we support smaller build-id notes ? */
+ if (bfd_get_section_size (sect) < 0x24)
+ {
+ bfd_set_error (bfd_error_invalid_operation);
+ return NULL;
+ }
+
+ if (!bfd_malloc_and_get_section (abfd, sect, & contents))
+ {
+ if (contents != NULL)
+ free (contents);
+ return NULL;
+ }
+
+ enote = (Elf_External_Note *) contents;
+ inote.type = H_GET_32 (abfd, enote->type);
+ inote.namesz = H_GET_32 (abfd, enote->namesz);
+ inote.namedata = enote->name;
+ inote.descsz = H_GET_32 (abfd, enote->descsz);
+ inote.descdata = inote.namedata + BFD_ALIGN (inote.namesz, 4);
+ /* FIXME: Should we check for extra notes in this section ? */
+
+ if (inote.descsz == 0
+ || inote.type != NT_GNU_BUILD_ID
+ || inote.namesz != 4 /* sizeof "GNU" */
+ || strcmp (inote.namedata, "GNU") != 0)
+ {
+ free (contents);
+ bfd_set_error (bfd_error_invalid_operation);
+ return NULL;
+ }
+
+ build_id = bfd_alloc (abfd, sizeof (struct bfd_build_id) + inote.descsz);
+ if (build_id == NULL)
+ {
+ free (contents);
+ return NULL;
+ }
+
+ build_id->size = inote.descsz;
+ memcpy (build_id->data, inote.descdata, inote.descsz);
+ abfd->build_id = build_id;
+ free (contents);
+
+ return build_id;
+}
+
+/*
+INTERNAL_FUNCTION
+ get_build_id_name
+
+SYNOPSIS
+ char * get_build_id_name
+ (bfd *abfd, unsigned long *build_id_out)
+
+DESCRIPTION
+ Searches @var{abfd} for a build-id, and then constructs a pathname
+ from it. The path is computed as .build-id/NN/NN+NN.debug where
+ NNNN+NN is the build-id value as a hexadecimal string.
+
+ Returns the constructed filename or NULL upon error.
+ It is the caller's responsibility to free the memory used to hold the
+ filename.
+ If a filename is returned then the @var{build_id_out} parameter is
+ set to a pointer to the build_id structure.
+*/
+
+static char *
+get_build_id_name (bfd *abfd, unsigned long *build_id_out)
+{
+ struct bfd_build_id *build_id;
+ char *name;
+ char *n;
+ bfd_size_type s;
+ bfd_byte *d;
+
+ if (abfd == NULL || abfd->filename == NULL || build_id_out == NULL)
+ {
+ bfd_set_error (bfd_error_invalid_operation);
+ return NULL;
+ }
+
+ build_id = get_build_id (abfd);
+ if (build_id == NULL)
+ return NULL;
+
+ /* Compute the debug pathname corresponding to the build-id. */
+ name = bfd_malloc (strlen (".build-id/") + build_id->size * 2 + 2 + strlen (".debug"));
+ if (name == NULL)
+ {
+ bfd_set_error (bfd_error_no_memory);
+ return NULL;
+ }
+ n = name;
+ d = build_id->data;
+ s = build_id->size;
+
+ n += sprintf (n, ".build-id/");
+ n += sprintf (n, "%02x", (unsigned) *d++); s--;
+ n += sprintf (n, "/");
+ while (s--)
+ n += sprintf (n, "%02x", (unsigned) *d++);
+ n += sprintf (n, ".debug");
+
+ * build_id_out = (unsigned long) build_id;
+ return name;
+}
+
+/*
+INTERNAL_FUNCTION
+ check_build_id_file
+
+SYNOPSIS
+ bfd_boolean check_build_id_file
+ (char *name, unsigned long buildid);
+
+DESCRIPTION
+ Checks to see if @var{name} is a readable file and if its build-id
+ matches @var{buildid}.
+
+ Returns TRUE if the file exists, is readable, and contains a build-id
+ which matches @var{build-id}.
+*/
+
+static bfd_boolean
+check_build_id_file (const char *name,
+ const unsigned long buildid)
+{
+ struct bfd_build_id *orig_build_id;
+ struct bfd_build_id *build_id;
+ bfd * file;
+ bfd_boolean result;
+
+ BFD_ASSERT (name);
+ BFD_ASSERT (buildid);
+
+ file = bfd_openr (name, NULL);
+ if (file == NULL)
+ return FALSE;
+
+ /* If the file is an archive, process all of its elements. */
+ if (! bfd_check_format (file, bfd_object))
+ {
+ bfd_close (file);
+ return FALSE;
+ }
+
+ build_id = get_build_id (file);
+ if (build_id == NULL)
+ {
+ bfd_close (file);
+ return FALSE;
+ }
+
+ orig_build_id = (struct bfd_build_id *) buildid;
+
+ result = build_id->size == orig_build_id->size
+ && memcmp (build_id->data, orig_build_id->data, build_id->size) == 0;
+
+ (void) bfd_close (file);
+
+ return result;
+}
+
+/*
+FUNCTION
+ bfd_follow_build_id_debuglink
+
+SYNOPSIS
+ char *bfd_follow_build_id_debuglink (bfd *abfd, const char *dir);
+
+DESCRIPTION
+
+ Takes @var{abfd} and searches it for a .note.gnu.build-id section.
+ If this section is found, it extracts the value of the NT_GNU_BUILD_ID
+ note, which should be a hexadecimal value @var{NNNN+NN} (for
+ 32+ hex digits). It then searches the filesystem for a file named
+ @var{.build-id/NN/NN+NN.debug} in a set of standard locations,
+ including the directory tree rooted at @var{dir}. The filename
+ of the first matching file to be found is returned. A matching
+ file should contain a .note.gnu.build-id section with the same
+ @var{NNNN+NN} note as @var{abfd}, although this check is currently
+ not implemented.
+
+ If @var{dir} is NULL, the search will take place starting at
+ the current directory.
+
+RETURNS
+ <<NULL>> on any errors or failure to locate the debug file,
+ otherwise a pointer to a heap-allocated string containing the
+ filename. The caller is responsible for freeing this string.
+*/
+
+char *
+bfd_follow_build_id_debuglink (bfd *abfd, const char *dir)
+{
+ return find_separate_debug_file (abfd, dir, FALSE,
+ get_build_id_name,
+ check_build_id_file);
+}
diff --git a/binutils/ChangeLog b/binutils/ChangeLog
index 38d033e..16e9c60 100644
--- a/binutils/ChangeLog
+++ b/binutils/ChangeLog
@@ -1,3 +1,11 @@
+2017-01-12 Nick Clifton <nickc@redhat.com>
+
+ PR binutils/20876
+ * NEWS: Mention the new feature.
+ * testsuite/binutils-all/objdump.exp (test_build_id_debuglink):
+ New proc to test the location of separate debug info files using
+ the build-id method.
+
2017-01-10 Nick Clifton <nickc@redhat.com>
PR 21034
diff --git a/binutils/NEWS b/binutils/NEWS
index 00675f7..b425901 100644
--- a/binutils/NEWS
+++ b/binutils/NEWS
@@ -1,5 +1,9 @@
-*- text -*-
+* Add support for locating separate debug info files using the build-id
+ method, where the separate file has a name based upon the build-id of
+ the original file.
+
Changes in 2.28:
* This version of binutils fixes a problem with PowerPC VLE 16A and 16D
diff --git a/binutils/testsuite/binutils-all/objdump.exp b/binutils/testsuite/binutils-all/objdump.exp
index 67fc8aa..c29965b 100644
--- a/binutils/testsuite/binutils-all/objdump.exp
+++ b/binutils/testsuite/binutils-all/objdump.exp
@@ -273,6 +273,86 @@ if { ![is_elf_format] } then {
}
}
+proc test_build_id_debuglink {} {
+ global srcdir
+ global subdir
+ global env
+ global CC_FOR_TARGET
+ global STRIP
+ global OBJCOPY
+ global OBJDUMP
+ global CFLAGS_FOR_TARGET
+
+ set test "build-id-debuglink"
+ if {![info exists CC_FOR_TARGET]} {
+ set CC_FOR_TARGET $env(CC)
+ }
+ if { $CC_FOR_TARGET == "" } {
+ unsupported $test
+ return
+ }
+
+ # Use a fixed build-id.
+ set CFLAGS_FOR_TARGET "-g -Wl,--build-id=0x12345678abcdef01"
+
+ if { [target_compile $srcdir/$subdir/testprog.c tmpdir/testprog exectuable debug] != "" } {
+ fail "$test (build)"
+ return
+ }
+
+ # FIXME: Do we need to restore CFLAGS_FOR_TARGET to its old value ?
+
+ if { [binutils_run $STRIP "--strip-debug --remove-section=.comment tmpdir/testprog -o tmpdir/testprog.strip"] != "" } {
+ fail "$test (strip debug info)"
+ return
+ }
+
+ if { [binutils_run $OBJCOPY "--only-keep-debug tmpdir/testprog tmpdir/testprog.debug"] != "" } {
+ fail "$test (create separate debug info file)"
+ return
+ }
+
+ set got [remote_exec host "mkdir -p .build-id/12" ]
+ if { [lindex $got 0] != 0 || ![string match "" [lindex $got 1]] } then {
+ fail "$test (make debug directory)"
+ return
+ }
+
+ set got [remote_exec host "cp tmpdir/testprog.debug .build-id/12/345678abcdef01.debug"]
+ if { [lindex $got 0] != 0 || ![string match "" [lindex $got 1]] } then {
+ fail "$test (copy debug info into debug directory)"
+ return
+ }
+
+ set got [remote_exec host "$OBJDUMP -Sl tmpdir/testprog.strip" "" "/dev/null" "tmpdir/testprog.strip.dump"]
+ if { [lindex $got 0] != 0 || ![string match "" [lindex $got 1]] } then {
+ fail "$test (post strip dump)"
+ return
+ }
+
+ set src2 tmpdir/testprog.strip.dump
+ verbose " grep -e testprog.c ${src2}"
+ set status [remote_exec build grep "-e testprog.c ${src2}"]
+ set exec_output [lindex $status 1]
+ set exec_output [prune_warnings $exec_output]
+ if [string match "" $exec_output] then {
+ send_log "$exec_output\n"
+ verbose "$exec_output" 1
+ fail "$test (grepping for source file name in disassembly output)"
+ } else {
+ pass "$test"
+ # Cleanup...
+ set got [remote_exec host "rm .build-id/12/345678abcdef01.debug"]
+ set got [remote_exec host "rmdir -p .build-id/12" ]
+ set got [remote_exec host "rm tmpdir/testprog.strip.dump"]
+ set got [remote_exec host "rm tmpdir/testprog.debug"]
+ set got [remote_exec host "rm tmpdir/testprog.strip"]
+ }
+}
+
+if {[isnative] && [is_elf_format]} then {
+ test_build_id_debuglink
+}
# Options which are not tested: -a -d -D -R -T -x -l --stabs
# I don't see any generic way to test any of these other than -a.