[review] Core file build-id support

Message ID gerrit.1572883380000.I15e9e8e58f10c68b5cae55e2eba58df1e8aef529@gnutoolchain-gerrit.osci.io
State Superseded
Headers show
Series
  • [review] Core file build-id support
Related show

Commit Message

Mihails Strasuns (Code Review) Nov. 4, 2019, 4:03 p.m.
Change URL: https://gnutoolchain-gerrit.osci.io/r/c/binutils-gdb/+/509
......................................................................

Core file build-id support

This patch uses new BFD support for detecting build-ids in core
files.

After this patch, it is possible to run gdb with only the
core file, and gdb will automatically load the executable and
debug info [example from tests]:

$ gdb -nx -q
(gdb) core-file corefile-buildid.core
[New LWP 29471]
Reading symbols from gdb.base/corefile-buildid/debugdir-exec/.build-id/36/fe5722c5a7ca3ac746a84e223c6a2a69193a24...
Core was generated by `outputs/gdb.base/coref'.
Program terminated with signal SIGABRT, Aborted.
(gdb)

This work is based on functionality available in Fedora originally
written by Jan Kratochvil.

Regression tested on buildbot.

gdb/ChangeLog:
2019-11-01  Keith Seitz  <keiths@redhat.com>

	* build-id.c (build_id_bfd_get): Permit bfd_core, too.
	(build_id_to_debug_bfd): Make static, rewriting to use
	build_id_to_bfd_suffix.
	(build_id_to_bfd_suffix): Copy of build_id_to_debug_bfd,
	adding `suffix' parameter. Append SUFFIX to file names
	when searching for matching files.
	(build_id_to_debug_bfd): Use build_id_to_bfd_suffix.
	(build_id_to_exec_bfd): Likewise.
	* build-id.h (build_id_to_debug_bfd): Clarify that function
	searches for BFD of debug info file.
	(build_id_to_exec_bfd): Declare.
	* corelow.c: Include build-id.h.
	(locate_exec_from_corefile_build_id): New function.
	(core_target_open): If no executable BFD is found,
	search for a core file BFD using build-id.

gdb/testsuite/ChangeLog:
2019-11-01  Keith Seitz  <keiths@redhat.com>

	* gdb.base/corefile-buildid-shlib-shr.c: New file.
	* gdb.base/corefile-buildid-shlib.c: New file.
	* gdb.base/corefile-buildid.c: New file.
	* gdb.base/corefile-buildid.exp: New file.

Change-Id: I15e9e8e58f10c68b5cae55e2eba58df1e8aef529
---
M gdb/build-id.c
M gdb/build-id.h
M gdb/corelow.c
A gdb/testsuite/gdb.base/corefile-buildid-shlib-shr.c
A gdb/testsuite/gdb.base/corefile-buildid-shlib.c
A gdb/testsuite/gdb.base/corefile-buildid.c
A gdb/testsuite/gdb.base/corefile-buildid.exp
7 files changed, 461 insertions(+), 8 deletions(-)




-- 
Gerrit-Project: binutils-gdb
Gerrit-Branch: master
Gerrit-Change-Id: I15e9e8e58f10c68b5cae55e2eba58df1e8aef529
Gerrit-Change-Number: 509
Gerrit-PatchSet: 1
Gerrit-Owner: Keith Seitz <keiths@redhat.com>
Gerrit-MessageType: newchange

Comments

Mihails Strasuns (Code Review) Dec. 5, 2019, 8:27 p.m. | #1
Tom Tromey has posted comments on this change.

Change URL: https://gnutoolchain-gerrit.osci.io/r/c/binutils-gdb/+/509
......................................................................


Patch Set 1: Code-Review+2

(1 comment)

I'm sorry about the delay on this review.

This patch looks good.  Impressive test case!  Thank you for doing this.

I had one nit in a comment, but no need for a re-review.

One thing I don't understand is how shared libraries work in this scenario.
How does gdb find the correct version of these from the core file?

| --- gdb/build-id.h
| +++ gdb/build-id.h
| @@ -39,10 +37,19 @@ /* Find and open a BFD given a build-id.  If no BFD can be found,
| -   the caller.  */
| +/* Find and open a BFD for a debuginfo file  given a build-id.  If no BFD
| +   can be found, return NULL.  The returned reference to the BFD must be
| +   released by the caller.  */
|  
|  extern gdb_bfd_ref_ptr build_id_to_debug_bfd (size_t build_id_len,
|  					      const bfd_byte *build_id);
|  
| +/* Find and open a BFD for an executable file given a build-id.  If no BFD
| +   can be found, return NULL.  The returned reference to the BFD must be
| +   released by the caller.  */

PS1, Line 46:

This text is just a leftover from before we had ref pointers.
Now I think it's implicit in the type and can just be left out.

| +
| +extern gdb_bfd_ref_ptr build_id_to_exec_bfd (size_t build_id_len,
| +					     const bfd_byte *build_id);
| +
|  /* Find the separate debug file for OBJFILE, by using the build-id
|     associated with OBJFILE's BFD.  If successful, returns the file name for the
|     separate debug file, otherwise, return an empty string.  */
|  
|  extern std::string find_separate_debug_file_by_buildid

-- 
Gerrit-Project: binutils-gdb
Gerrit-Branch: master
Gerrit-Change-Id: I15e9e8e58f10c68b5cae55e2eba58df1e8aef529
Gerrit-Change-Number: 509
Gerrit-PatchSet: 1
Gerrit-Owner: Keith Seitz <keiths@redhat.com>
Gerrit-Reviewer: Tom Tromey <tromey@sourceware.org>
Gerrit-Comment-Date: Thu, 05 Dec 2019 20:27:20 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: Yes
Gerrit-MessageType: comment
Keith Seitz Dec. 7, 2019, 8:18 p.m. | #2
On 12/5/19 12:27 PM, Tom Tromey (Code Review) wrote:
> Tom Tromey has posted comments on this change.

> 

> Change URL: https://gnutoolchain-gerrit.osci.io/r/c/binutils-gdb/+/509

> ......................................................................

> 

> 

> Patch Set 1: Code-Review+2

> 

> (1 comment)

> 

> I'm sorry about the delay on this review.


No big deal -- there's still plenty to do in this area!

> This patch looks good.  Impressive test case!  Thank you for doing this.

> 

> I had one nit in a comment, but no need for a re-review.

> 

> One thing I don't understand is how shared libraries work in this scenario.

> How does gdb find the correct version of these from the core file?


That is coming up in a follow-on patch. I don't fully understand it, either,
just yet. I've been attempting to work my way through this recently.

> | --- gdb/build-id.h

> | +++ gdb/build-id.h

> | @@ -39,10 +37,19 @@ /* Find and open a BFD given a build-id.  If no BFD can be found,

> | -   the caller.  */

> | +/* Find and open a BFD for a debuginfo file  given a build-id.  If no BFD

> | +   can be found, return NULL.  The returned reference to the BFD must be

> | +   released by the caller.  */

> |  

> |  extern gdb_bfd_ref_ptr build_id_to_debug_bfd (size_t build_id_len,

> |  					      const bfd_byte *build_id);

> |  

> | +/* Find and open a BFD for an executable file given a build-id.  If no BFD

> | +   can be found, return NULL.  The returned reference to the BFD must be

> | +   released by the caller.  */

> 

> PS1, Line 46:

> 

> This text is just a leftover from before we had ref pointers.

> Now I think it's implicit in the type and can just be left out.

> 


Indeed. Just reading that and seeing gdb_bfd_ref_ptr makes me cringe. Forest,
meet trees.

Thank you for the reivew, I've pushed this.

Keith

Patch

diff --git a/gdb/build-id.c b/gdb/build-id.c
index 048da2a..e8d77bb 100644
--- a/gdb/build-id.c
+++ b/gdb/build-id.c
@@ -32,7 +32,8 @@ 
 const struct bfd_build_id *
 build_id_bfd_get (bfd *abfd)
 {
-  if (!bfd_check_format (abfd, bfd_object))
+  if (!bfd_check_format (abfd, bfd_object)
+      && !bfd_check_format (abfd, bfd_core))
     return NULL;
 
   if (abfd->build_id != NULL)
@@ -117,10 +118,13 @@ 
   return debug_bfd;
 }
 
-/* See build-id.h.  */
+/* Common code for finding BFDs of a given build-id.  This function
+   works with both debuginfo files (SUFFIX == ".debug") and executable
+   files (SUFFIX == "").  */
 
-gdb_bfd_ref_ptr
-build_id_to_debug_bfd (size_t build_id_len, const bfd_byte *build_id)
+static gdb_bfd_ref_ptr
+build_id_to_bfd_suffix (size_t build_id_len, const bfd_byte *build_id,
+			const char *suffix)
 {
   /* Keep backward compatibility so that DEBUG_FILE_DIRECTORY being "" will
      cause "/.build-id/..." lookups.  */
@@ -149,7 +153,7 @@ 
       while (size-- > 0)
 	string_appendf (link, "%02x", (unsigned) *data++);
 
-      link += ".debug";
+      link += suffix;
 
       gdb_bfd_ref_ptr debug_bfd
 	= build_id_to_debug_bfd_1 (link, build_id_len, build_id);
@@ -177,6 +181,22 @@ 
 
 /* See build-id.h.  */
 
+gdb_bfd_ref_ptr
+build_id_to_debug_bfd (size_t build_id_len, const bfd_byte *build_id)
+{
+  return build_id_to_bfd_suffix (build_id_len, build_id, ".debug");
+}
+
+/* See build-id.h.  */
+
+gdb_bfd_ref_ptr
+build_id_to_exec_bfd (size_t build_id_len, const bfd_byte *build_id)
+{
+  return build_id_to_bfd_suffix (build_id_len, build_id, "");
+}
+
+/* See build-id.h.  */
+
 std::string
 find_separate_debug_file_by_buildid (struct objfile *objfile)
 {
diff --git a/gdb/build-id.h b/gdb/build-id.h
index 2835a76..46ec5ae 100644
--- a/gdb/build-id.h
+++ b/gdb/build-id.h
@@ -34,13 +34,20 @@ 
 			    size_t check_len, const bfd_byte *check);
 
 
-/* Find and open a BFD given a build-id.  If no BFD can be found,
-   return NULL.  The returned reference to the BFD must be released by
-   the caller.  */
+/* Find and open a BFD for a debuginfo file  given a build-id.  If no BFD
+   can be found, return NULL.  The returned reference to the BFD must be
+   released by the caller.  */
 
 extern gdb_bfd_ref_ptr build_id_to_debug_bfd (size_t build_id_len,
 					      const bfd_byte *build_id);
 
+/* Find and open a BFD for an executable file given a build-id.  If no BFD
+   can be found, return NULL.  The returned reference to the BFD must be
+   released by the caller.  */
+
+extern gdb_bfd_ref_ptr build_id_to_exec_bfd (size_t build_id_len,
+					     const bfd_byte *build_id);
+
 /* Find the separate debug file for OBJFILE, by using the build-id
    associated with OBJFILE's BFD.  If successful, returns the file name for the
    separate debug file, otherwise, return an empty string.  */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index b32fa95..7a66fe5 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -43,6 +43,7 @@ 
 #include "gdb_bfd.h"
 #include "completer.h"
 #include "gdbsupport/filestuff.h"
+#include "build-id.h"
 
 #ifndef O_LARGEFILE
 #define O_LARGEFILE 0
@@ -351,6 +352,27 @@ 
     core_target_open (filename, from_tty);
 }
 
+/* Locate (and load) an executable file (and symbols) given the core file
+   BFD ABFD.  */
+
+static void
+locate_exec_from_corefile_build_id (bfd *abfd, int from_tty)
+{
+  const bfd_build_id *build_id = build_id_bfd_get (abfd);
+  if (build_id == nullptr)
+    return;
+
+  gdb_bfd_ref_ptr execbfd
+    = build_id_to_exec_bfd (build_id->size, build_id->data);
+
+  if (execbfd != nullptr)
+    {
+      exec_file_attach (bfd_get_filename (execbfd.get ()), from_tty);
+      symbol_file_add_main (bfd_get_filename (execbfd.get ()),
+			    symfile_add_flag (from_tty ? SYMFILE_VERBOSE : 0));
+    }
+}
+
 /* See gdbcore.h.  */
 
 void
@@ -456,6 +478,9 @@ 
 	switch_to_thread (thread);
     }
 
+  if (exec_bfd == nullptr)
+    locate_exec_from_corefile_build_id (core_bfd, from_tty);
+
   post_create_inferior (target, from_tty);
 
   /* Now go through the target stack looking for threads since there
diff --git a/gdb/testsuite/gdb.base/corefile-buildid-shlib-shr.c b/gdb/testsuite/gdb.base/corefile-buildid-shlib-shr.c
new file mode 100644
index 0000000..55b9bfa
--- /dev/null
+++ b/gdb/testsuite/gdb.base/corefile-buildid-shlib-shr.c
@@ -0,0 +1,29 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2019 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/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+static int crashfunc_global = 1234;
+
+int
+crashfunc (void)
+{
+  printf ("in crashfunc\n");
+  abort ();
+  return crashfunc_global;
+}
diff --git a/gdb/testsuite/gdb.base/corefile-buildid-shlib.c b/gdb/testsuite/gdb.base/corefile-buildid-shlib.c
new file mode 100644
index 0000000..10c523d
--- /dev/null
+++ b/gdb/testsuite/gdb.base/corefile-buildid-shlib.c
@@ -0,0 +1,58 @@ 
+/* Copyright (C) 2007-2019 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/>.  */
+
+/* This shared library will dlopen another shared object.
+   This is based on gdb.base/solib-disc.c.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef __WIN32__
+#include <windows.h>
+#define dlopen(name, mode) LoadLibrary (name)
+#define dlsym(handle, func) GetProcAddress (handle, func)
+#define dlclose(handle) FreeLibrary (handle)
+#define dlerror() "an error occurred"
+#else
+#include <dlfcn.h>
+#endif
+
+const char *the_shlib = SHLIB_NAME;
+
+int
+shlib_function (void)
+{
+  void *handle;
+  int (*func) (void);
+  int result;
+
+  handle = dlopen (the_shlib, RTLD_LAZY);
+  if (!handle)
+    {
+      fprintf (stderr, "%s\n", dlerror ());
+      exit (1);
+    }
+
+  func = (int (*)(void)) dlsym (handle, "crashfunc");
+  if (func == NULL)
+    {
+      fprintf (stderr, "%s\n", dlerror ());
+      exit (1);
+    }
+
+  result = func ();
+  dlclose (handle);
+  return result;
+}
diff --git a/gdb/testsuite/gdb.base/corefile-buildid.c b/gdb/testsuite/gdb.base/corefile-buildid.c
new file mode 100644
index 0000000..ae0a1ca
--- /dev/null
+++ b/gdb/testsuite/gdb.base/corefile-buildid.c
@@ -0,0 +1,43 @@ 
+/* Copyright (C) 2019 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/>.  */
+
+#ifdef TEST_SHARED
+/* A function in the shared library linked with this program.  */
+extern int shlib_function (void);
+#else
+#include <stdlib.h>
+
+static int crashfunc_global = 4321;
+
+static int
+crashfunc (void)
+{
+  abort ();
+  return crashfunc_global;
+}
+
+int
+shlib_function (void)
+{
+  return crashfunc ();
+}
+#endif
+
+int
+main (void)
+{
+  int ret = shlib_function ();
+  return ret;
+}
diff --git a/gdb/testsuite/gdb.base/corefile-buildid.exp b/gdb/testsuite/gdb.base/corefile-buildid.exp
new file mode 100644
index 0000000..8a29359
--- /dev/null
+++ b/gdb/testsuite/gdb.base/corefile-buildid.exp
@@ -0,0 +1,271 @@ 
+# Copyright 2019 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/>.
+
+# Based on break.exp, written by Rob Savoye. (rob@cygnus.com)
+# Modified to test gdb's handling of separate debug info files.
+# Modified to test gdb's handling of a debug-id retrieval.
+
+# Build-id-related tests for core files.
+
+standard_testfile
+
+# Build a non-shared executable.
+
+proc build_corefile_buildid_exec {} {
+    global testfile srcfile binfile execdir
+
+    if {[build_executable $testfile.exp $testfile $srcfile debug] == -1} {
+	untested "failed to compile"
+	return false
+    }
+
+    # Move executable to non-default path.
+    set builddir [standard_output_file $execdir]
+    remote_exec build "rm -rf $builddir"
+    remote_exec build "mkdir $builddir"
+    remote_exec build "mv $binfile [file join $builddir [file tail $binfile]]"
+
+    return true
+}
+
+# Build a shared executable.
+
+proc build_corefile_buildid_shared {} {
+    global srcdir subdir testfile binfile srcfile sharedir
+
+    set builddir [standard_output_file $sharedir]
+
+    # Compile DSO.
+    set srcdso [file join $srcdir $subdir $testfile-shlib-shr.c]
+    set objdso [standard_output_file $testfile-shlib-shr.so]
+    if {[gdb_compile_shlib $srcdso $objdso {debug}] != ""} {
+	untested "failed to compile dso"
+	return false
+    }
+
+    # Compile shared library.
+    set srclib [file join $srcdir $subdir $testfile-shlib.c]
+    set libname lib$testfile.so
+    set objlib [standard_output_file $libname]
+    set dlopen_lib [shlib_target_file \
+			[file join $builddir [file tail $objdso]]]
+    set opts [list debug shlib_load \
+		  additional_flags=-DSHLIB_NAME=\"$dlopen_lib\"]
+    if {[gdb_compile_shlib $srclib $objlib $opts] != ""} {
+	untested "failed to compile shared library"
+	return false
+    }
+
+    # Compile main program.
+    set srcexec [file join $srcdir $subdir $srcfile]
+    set binfile [standard_output_file $testfile-shared]
+    set opts [list debug shlib=$objlib additional_flags=-DTEST_SHARED]
+    if {[gdb_compile $srcexec $binfile executable $opts] != ""} {
+	untested "failed to compile shared executable"
+	return false
+    }
+
+    # Move objects to non-default path.
+    remote_exec build "rm -rf $builddir"
+    remote_exec build "mkdir $builddir"
+    remote_exec build "mv $binfile $builddir"
+    remote_exec build "mv $objdso  $builddir"
+    remote_exec build "mv $objlib $builddir"
+
+    return true
+}
+
+# Append DEBUGDIR to the debug-file-directory path.
+
+proc append_debug_dir {debugdir} {
+    global gdb_prompt
+
+    set orig_debugdir {}
+    gdb_test_multiple "show debug-file-directory" \
+	"get debug-file-directory" {
+	    -re "The directory where separate debug symbols are searched for is \"(.*)\"\.\[\r\n\]+$gdb_prompt $" {
+		set orig_debugdir $expect_out(1,string)
+		pass "get debug-file-directory"
+	    }
+	}
+    gdb_test_no_output "set debug-file-directory $debugdir:$orig_debugdir" \
+	"append debug directory"
+}
+
+# A convenience procedure to check if "info files" mentions the exec file
+# FILE.
+
+proc check_exec_file {file} {
+    send_log "expecting exec file \"$file\"\n"
+    gdb_test "info files" "Local exec file:\[\r\n\t\ \]+`[string_to_regexp $file]'.*"
+}
+
+# Test whether gdb can find an exec file from a core file's build-id.
+# The executable (and separate debuginfo if SEPDEBUG is true) is
+# copied to the .build-id directory.
+#
+# SUFFIX is appended to the .builid-id parent directory name to
+# keep all tests separate.
+# SYMLINK specifies whether build-id files should be copied or symlinked.
+# SHARED is a boolean indicating whether we are testing the shared
+# library core dump test case.
+
+proc locate_exec_from_core_build_id {corefile buildid suffix \
+					 sepdebug symlink shared} {
+    global testfile binfile srcfile
+
+    clean_restart
+
+    # Set up the build-id directory and symlink the binary there.
+    if {$symlink} {
+	set d "symlinkdir"
+    } else {
+	set d "debugdir"
+    }
+    set debugdir [standard_output_file $d-$suffix]
+    remote_exec build "rm -rf $debugdir"
+    remote_exec build \
+	"mkdir -p [file join $debugdir [file dirname $buildid]]"
+
+    set files_list {}
+    if {$sepdebug} {
+	lappend files_list "$binfile.stripped" $buildid
+	lappend files_list "$binfile.debug" "$buildid.debug"
+    } else {
+	lappend files_list $binfile $buildid
+    }
+    if {$shared} {
+	global sharedir
+	set builddir [standard_output_file $sharedir]
+    } else {
+	global execdir
+	set builddir [standard_output_file $execdir]
+    }
+    foreach {target name} $files_list {
+	set t [file join $builddir [file tail $target]]
+	if {$symlink} {
+	    remote_exec build "ln -s $t [file join $debugdir $name]"
+	} else {
+	    remote_exec build "cp $t [file join $debugdir $name]"
+	}
+    }
+
+    # Append the debugdir to the separate debug directory search path.
+    append_debug_dir $debugdir
+
+    gdb_test "core-file $corefile" "Program terminated with .*" \
+	"load core file"
+    if {$symlink} {
+	if {$sepdebug} {
+	    set expected_file [file join $builddir \
+				   [file tail "$binfile.stripped"]]
+	} else {
+	    set expected_file [file join $builddir [file tail $binfile]]
+	}
+    } else {
+	set expected_file $buildid
+    }
+    check_exec_file [file join $debugdir $expected_file]
+}
+
+# Run a build-id tests on a core file.
+# Supported options: "-shared" and "-sepdebug" for running tests
+# of shared and/or stripped/.debug executables.
+
+proc do_corefile_buildid_tests {args} {
+    global binfile testfile srcfile execdir sharedir
+
+    # Parse options.
+    parse_args [list {sepdebug} {shared}]
+
+    # PROGRAM to run to generate core file.  This could be different
+    # than the program that was originally built, e.g., for a stripped
+    # executable.
+    if {$shared} {
+	set builddir [standard_output_file $sharedir]
+    } else {
+	set builddir [standard_output_file $execdir]
+    }
+    set program_to_run [file join $builddir [file tail $binfile]]
+
+    # A list of suffixes to use to describe the test and the .build-id
+    # directory for the test.  The suffix will be used, joined with spaces,
+    # to prefix all tests for the given run.  It will be used, joined with
+    # dashes, to create a unique build-id directory.
+    set suffix {}
+    if {$shared} {
+	lappend suffix "shared"
+    } else {
+	lappend suffix "exec"
+    }
+
+    if {$sepdebug} {
+	# Strip debuginfo into its own file.
+	if {[gdb_gnu_strip_debug [standard_output_file $program_to_run]] \
+		!= 0} {
+	    untested "could not strip executable  for [join $suffix \ ]"
+	    return
+	}
+
+	# Run the stripped program instead of the original.
+	set program_to_run [file join $builddir \
+				[file tail "$binfile.stripped"]]
+	lappend suffix "sepdebug"
+    }
+
+    # Find the core file.
+    set corefile [core_find $program_to_run]
+    if {$corefile == ""} {
+	untested "could not generate core file"
+	return
+    }
+    verbose -log "corefile is $corefile"
+
+    # Grab the build-id from the binary, removing ".debug" from the end.
+    set buildid [build_id_debug_filename_get $program_to_run]
+    if {$buildid == ""} {
+	untested "binary for [join $suffix \ ] has no build-id"
+    }
+    regsub {\.debug$} $buildid {} buildid
+    verbose -log "build-id is $buildid"
+
+    with_test_prefix "[join $suffix \ ]"  {
+	locate_exec_from_core_build_id $corefile $buildid \
+	    [join $suffix -] $sepdebug false $shared
+    }
+
+    with_test_prefix "symlink [join $suffix \ ]" {
+	locate_exec_from_core_build_id $corefile $buildid \
+	    [join $suffix -] $sepdebug true $shared
+    }
+}
+
+# Directories where executables will be moved before testing.
+set execdir "build-exec"
+set sharedir "build-shared"
+
+#
+# Do tests
+#
+
+build_corefile_buildid_exec
+do_corefile_buildid_tests
+do_corefile_buildid_tests -sepdebug
+
+if {![skip_shlib_tests]} {
+    build_corefile_buildid_shared
+    do_corefile_buildid_tests -shared
+    do_corefile_buildid_tests -shared -sepdebug
+}