From 2425a30e406a0523020b7e70abb864a06a45bb97 Mon Sep 17 00:00:00 2001 From: Nick Clifton Date: Thu, 12 Jan 2017 16:56:54 +0000 Subject: [PATCH] Add support for locating separate debug info files via the build-id method. PR binutils/20876 bfd * 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. binutils * 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. --- bfd/ChangeLog | 23 ++ bfd/bfd-in2.h | 2 + bfd/dwarf2.c | 6 +- bfd/opncls.c | 373 ++++++++++++++++++-- binutils/ChangeLog | 8 + binutils/NEWS | 4 + binutils/testsuite/binutils-all/objdump.exp | 80 +++++ 7 files changed, 456 insertions(+), 40 deletions(-) diff --git a/bfd/ChangeLog b/bfd/ChangeLog index 252e57baf5b..6705ae4f3a7 100644 --- a/bfd/ChangeLog +++ b/bfd/ChangeLog @@ -1,3 +1,26 @@ +2017-01-12 Nick Clifton + + 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 PR ld/21038 diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h index 2930089afe4..80d60cbb772 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 40edd91aefa..369958765ad 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 10684d2682d..b4d4dcf6464 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 <> 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 <> 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 + <> 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 38d033ef27d..16e9c605484 100644 --- a/binutils/ChangeLog +++ b/binutils/ChangeLog @@ -1,3 +1,11 @@ +2017-01-12 Nick Clifton + + 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 PR 21034 diff --git a/binutils/NEWS b/binutils/NEWS index 00675f7231e..b4259019833 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 67fc8aa7469..c29965b7d42 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. -- 2.30.2