Add support for locating separate debug info files via the build-id method.
authorNick Clifton <nickc@redhat.com>
Thu, 12 Jan 2017 16:56:54 +0000 (16:56 +0000)
committerNick Clifton <nickc@redhat.com>
Thu, 12 Jan 2017 16:56:54 +0000 (16:56 +0000)
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
bfd/bfd-in2.h
bfd/dwarf2.c
bfd/opncls.c
binutils/ChangeLog
binutils/NEWS
binutils/testsuite/binutils-all/objdump.exp

index 252e57baf5b78476586df6e6014e83c2b8dc91fa..6705ae4f3a7c96cc11304eff0af3ffaae27b973b 100644 (file)
@@ -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
index 2930089afe4722cf2bba35c0f7a3f7c35d46b310..80d60cbb77219c67c630850bb41fdc9da25037c4 100644 (file)
@@ -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.  */
index 40edd91aefac62fed89c53ea2b52f709bb7298d0..369958765ad4733c506bf65f1f2413d017003cb7 100644 (file)
@@ -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.
index 10684d2682da7623f4b2f3426eaa2d2ba0cd85b0..b4d4dcf64643145e71e70dba29cd8208c945ddec 100644 (file)
@@ -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);
+}
index 38d033ef27de8ff3371f6b115491d93bab640a59..16e9c605484a3a990ffc98ce003c22fa43ccb798 100644 (file)
@@ -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
index 00675f7231eb068f3cf1712c7ad087643e66bc65..b4259019833cd6995117385390112cd03753809c 100644 (file)
@@ -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
index 67fc8aa7469ad13ea24b031fa1a4b9728aa8bbaf..c29965b7d42f9c4088016e14473c42ac14dc9579 100644 (file)
@@ -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.