#include "gdbarch.h"
#include "gdbsupport/environ.h"
+#include "filenames.h"
class frame_info_ptr;
struct minimal_symbol;
never be nullptr. Only call this constructor if all the arguments
have been collected successfully, i.e. if the EXEC_NAME could be
found but not ARGV then use the no-argument constructor to create an
- empty context object. */
+ empty context object.
+
+ The EXEC_FILENAME must be the absolute filename of the executable
+ that generated this core file, or nullptr if the absolute filename
+ is not known. */
core_file_exec_context (gdb::unique_xmalloc_ptr<char> exec_name,
+ gdb::unique_xmalloc_ptr<char> exec_filename,
std::vector<gdb::unique_xmalloc_ptr<char>> argv,
std::vector<gdb::unique_xmalloc_ptr<char>> envp)
: m_exec_name (std::move (exec_name)),
+ m_exec_filename (std::move (exec_filename)),
m_arguments (std::move (argv)),
m_environment (std::move (envp))
{
gdb_assert (m_exec_name != nullptr);
+ gdb_assert (exec_filename == nullptr
+ || IS_ABSOLUTE_PATH (exec_filename.get ()));
}
/* Create a default context object. In its default state a context
const char *execfn () const
{ return m_exec_name.get (); }
+ /* Return the absolute path to the executable if known. This might
+ return nullptr even when execfn() returns a non-nullptr value.
+ Additionally, the file referenced here might have a different name
+ than the file returned by execfn if execfn is a symbolic link. */
+ const char *exec_filename () const
+ { return m_exec_filename.get (); }
+
/* Return the vector of inferior arguments as extracted from the core
file. This does not include argv[0] (the executable name) for that
see the execfn() function. */
if no executable name is found. */
gdb::unique_xmalloc_ptr<char> m_exec_name;
+ /* Full filename to the executable that was actually executed. The name
+ within EXEC_FILENAME might not match what the user typed, e.g. if the
+ user typed ./symlinked_name which is a symlink to /tmp/real_name then
+ this is going to contain '/tmp/realname' while EXEC_NAME above will
+ contain './symlinkedname'. */
+ gdb::unique_xmalloc_ptr<char> m_exec_filename;
+
/* List of arguments. Doesn't include argv[0] which is the executable
name, for this look at m_exec_name field. */
std::vector<gdb::unique_xmalloc_ptr<char>> m_arguments;
replacement_lwpid_str.c_str ());
}
+/* Use CTX to try and find (and open) the executable file for the core file
+ CBFD. BUILD_ID is the build-id for CBFD which was already extracted by
+ our caller.
+
+ Will return the opened executable or nullptr if the executable couldn't
+ be found. */
+
+static gdb_bfd_ref_ptr
+locate_exec_from_corefile_exec_context (bfd *cbfd,
+ const bfd_build_id *build_id,
+ const core_file_exec_context &ctx)
+{
+ /* CTX must be valid, and a valid context has an execfn() string. */
+ gdb_assert (ctx.valid ());
+ gdb_assert (ctx.execfn () != nullptr);
+
+ /* EXEC_NAME will be the command used to start the inferior. This might
+ not be an absolute path (but could be). */
+ const char *exec_name = ctx.execfn ();
+
+ /* Function to open FILENAME and check if its build-id matches BUILD_ID
+ from this enclosing scope. Returns the open BFD for filename if the
+ FILENAME has a matching build-id, otherwise, returns nullptr. */
+ const auto open_and_check_build_id
+ = [&build_id] (const char *filename) -> gdb_bfd_ref_ptr
+ {
+ /* Try to open a file. If this succeeds then we still need to perform
+ a build-id check. */
+ gdb_bfd_ref_ptr execbfd = gdb_bfd_open (filename, gnutarget);
+
+ /* We managed to open a file, but if it's build-id doesn't match
+ BUILD_ID then we just cannot trust it's the right file. */
+ if (execbfd != nullptr)
+ {
+ const bfd_build_id *other_build_id = build_id_bfd_get (execbfd.get ());
+
+ if (other_build_id == nullptr
+ || !build_id_equal (other_build_id, build_id))
+ execbfd = nullptr;
+ }
+
+ return execbfd;
+ };
+
+ gdb_bfd_ref_ptr execbfd;
+
+ /* If EXEC_NAME is absolute then try to open it now. Otherwise, see if
+ EXEC_NAME is a relative path from the location of the core file. This
+ is just a guess, the executable might not be here, but we still rely
+ on a build-id match in order to accept any executable we find; we
+ don't accept something just because it happens to be in the right
+ location. */
+ if (IS_ABSOLUTE_PATH (exec_name))
+ execbfd = open_and_check_build_id (exec_name);
+ else
+ {
+ std::string p = (ldirname (bfd_get_filename (cbfd))
+ + '/'
+ + exec_name);
+ execbfd = open_and_check_build_id (p.c_str ());
+ }
+
+ /* If we haven't found the executable yet, then try checking to see if
+ the executable is in the same directory as the core file. Again,
+ there's no reason why this should be the case, but it's worth a try,
+ and the build-id check should ensure we don't use an invalid file if
+ we happen to find one. */
+ if (execbfd == nullptr)
+ {
+ const char *base_name = lbasename (exec_name);
+ std::string p = (ldirname (bfd_get_filename (cbfd))
+ + '/'
+ + base_name);
+ execbfd = open_and_check_build_id (p.c_str ());
+ }
+
+ /* If the above didn't provide EXECBFD then try the exec_filename from
+ the context. This will be an absolute filename which the gdbarch code
+ figured out from the core file. In some cases the gdbarch code might
+ not be able to figure out a suitable absolute filename though. */
+ if (execbfd == nullptr && ctx.exec_filename () != nullptr)
+ {
+ gdb_assert (IS_ABSOLUTE_PATH (ctx.exec_filename ()));
+
+ /* Try to open a file. If this succeeds then we still need to
+ perform a build-id check. */
+ execbfd = open_and_check_build_id (ctx.exec_filename ());
+ }
+
+ return execbfd;
+}
+
/* Locate (and load) an executable file (and symbols) given the core file
BFD ABFD. */
static void
-locate_exec_from_corefile_build_id (bfd *abfd, core_target *target,
+locate_exec_from_corefile_build_id (bfd *abfd,
+ core_target *target,
+ const core_file_exec_context &ctx,
int from_tty)
{
const bfd_build_id *build_id = build_id_bfd_get (abfd);
if (build_id == nullptr)
return;
- /* The filename used for the find_objfile_by_build_id call. */
- std::string filename;
+ gdb_bfd_ref_ptr execbfd;
- if (!target->expected_exec_filename ().empty ())
- filename = target->expected_exec_filename ();
- else
+ if (ctx.valid ())
+ execbfd = locate_exec_from_corefile_exec_context (abfd, build_id, ctx);
+
+ if (execbfd == nullptr)
{
- /* We didn't find an executable name from the mapped file
- information, so as a stand-in build a string based on the
- build-id. */
- std::string build_id_hex_str = bin2hex (build_id->data, build_id->size);
- filename = string_printf ("with build-id %s", build_id_hex_str.c_str ());
- }
+ /* The filename used for the find_objfile_by_build_id call. */
+ std::string filename;
+
+ if (!target->expected_exec_filename ().empty ())
+ filename = target->expected_exec_filename ();
+ else
+ {
+ /* We didn't find an executable name from the mapped file
+ information, so as a stand-in build a string based on the
+ build-id. */
+ std::string build_id_hex_str
+ = bin2hex (build_id->data, build_id->size);
+ filename
+ = string_printf ("with build-id %s", build_id_hex_str.c_str ());
+ }
- gdb_bfd_ref_ptr execbfd
- = find_objfile_by_build_id (current_program_space, build_id,
- filename.c_str ());
+ execbfd
+ = find_objfile_by_build_id (current_program_space, build_id,
+ filename.c_str ());
+ }
if (execbfd != nullptr)
{
validate_files ();
- /* If we have no exec file, try to set the architecture from the
- core file. We don't do this unconditionally since an exec file
- typically contains more information that helps us determine the
- architecture than a core file. */
- if (!current_program_space->exec_bfd ())
- set_gdbarch_from_file (current_program_space->core_bfd ());
-
current_inferior ()->push_target (std::move (target_holder));
switch_to_no_thread ();
switch_to_thread (thread);
}
+ /* In order to parse the exec context from the core file the current
+ inferior needs to have a suitable gdbarch set. If an exec file is
+ loaded then the gdbarch will have been set based on the exec file, but
+ if not, ensure we have a suitable gdbarch in place now. */
+ if (current_program_space->exec_bfd () == nullptr)
+ current_inferior ()->set_arch (target->core_gdbarch ());
+
+ /* See if the gdbarch can find the executable name and argument list from
+ the core file. */
+ core_file_exec_context ctx
+ = gdbarch_core_parse_exec_context (target->core_gdbarch (),
+ current_program_space->core_bfd ());
+
+ /* If we don't have an executable loaded then see if we can locate one
+ based on the core file. */
if (current_program_space->exec_bfd () == nullptr)
locate_exec_from_corefile_build_id (current_program_space->core_bfd (),
- target, from_tty);
+ target, ctx, from_tty);
+
+ /* If we have no exec file, try to set the architecture from the
+ core file. We don't do this unconditionally since an exec file
+ typically contains more information that helps us determine the
+ architecture than a core file. */
+ if (current_program_space->exec_bfd () == nullptr)
+ set_gdbarch_from_file (current_program_space->core_bfd ());
post_create_inferior (from_tty);
exception_print (gdb_stderr, except);
}
- /* See if the gdbarch can find the executable name and argument list from
- the core file. */
- core_file_exec_context ctx
- = gdbarch_core_parse_exec_context (target->core_gdbarch (),
- current_program_space->core_bfd ());
if (ctx.valid ())
{
std::string args;
if (execfn == nullptr)
return {};
+ /* When the core-file was loaded GDB processed the file backed mappings
+ (from the NT_FILE note). One of these should have been for the
+ executable. The AT_EXECFN string might not be an absolute path, but
+ the path in NT_FILE will be absolute, though if AT_EXECFN is a
+ symlink, then the NT_FILE entry will point to the actual file, not the
+ symlink.
+
+ Use the AT_ENTRY address to look for the NT_FILE entry which contains
+ that address, this should be the executable. */
+ gdb::unique_xmalloc_ptr<char> exec_filename;
+ CORE_ADDR exec_entry_addr;
+ if (target_auxv_search (contents, current_inferior ()->top_target (),
+ gdbarch, AT_ENTRY, &exec_entry_addr) == 1)
+ {
+ std::optional<core_target_mapped_file_info> info
+ = core_target_find_mapped_file (nullptr, exec_entry_addr);
+ if (info.has_value () && !info->filename ().empty ()
+ && IS_ABSOLUTE_PATH (info->filename ().c_str ()))
+ exec_filename = make_unique_xstrdup (info->filename ().c_str ());
+ }
+
return core_file_exec_context (std::move (execfn),
+ std::move (exec_filename),
std::move (arguments),
std::move (environment));
}
--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2024 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 <stdlib.h>
+
+int
+main (int argc, char **argv)
+{
+ abort ();
+ return 0;
+}
--- /dev/null
+# Copyright 2024 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/>.
+
+# Check GDB's ability to auto-load the executable based on the file
+# names extracted from the core file.
+#
+# Currently, only Linux supports reading full executable and arguments
+# from a core file.
+require {istarget *-linux*}
+
+standard_testfile
+
+if {[build_executable $testfile.exp $testfile $srcfile {debug build-id}] == -1} {
+ untested "failed to compile"
+ return -1
+}
+
+# Load the COREFILE and confirm that GDB auto-loads the executable.
+# The symbols should be read from SYMBOL_FILE and the core file should
+# be reported as generated by GEN_FROM_FILE.
+proc test_load { corefile symbol_file gen_from_file } {
+ clean_restart
+ set saw_generated_line false
+ set saw_reading_symbols false
+
+ gdb_test_multiple "core-file $corefile" "load core file" {
+
+ -re "^Reading symbols from [string_to_regexp $symbol_file]\\.\\.\\.\r\n" {
+ set saw_reading_symbols true
+ exp_continue
+ }
+
+ -re "^Core was generated by `[string_to_regexp $gen_from_file]'\\.\r\n" {
+ set saw_generated_line true
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { $saw_generated_line && $saw_reading_symbols} \
+ $gdb_test_name
+ }
+
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+ }
+}
+
+with_test_prefix "absolute path" {
+ # Generate a core file, this uses an absolute path to the
+ # executable.
+ with_test_prefix "to file" {
+ set corefile [core_find $binfile]
+ if {$corefile == ""} {
+ untested "unable to create corefile"
+ return 0
+ }
+ set corefile_1 "$binfile.1.core"
+ remote_exec build "mv $corefile $corefile_1"
+
+ test_load $corefile_1 $binfile $binfile
+ }
+
+ # And create a symlink, and repeat the test using an absolute path
+ # to the symlink.
+ with_test_prefix "to symlink" {
+ set symlink_name "symlink_1"
+ set symlink [standard_output_file $symlink_name]
+
+ with_cwd [standard_output_file ""] {
+ remote_exec build "ln -s ${testfile} $symlink_name"
+ }
+
+ set corefile [core_find $symlink]
+ if {$corefile == ""} {
+ untested "unable to create corefile"
+ return 0
+ }
+ set corefile_2 "$binfile.2.core"
+ remote_exec build "mv $corefile $corefile_2"
+
+ test_load $corefile_2 $symlink $symlink
+ }
+
+ # Like the previous test, except this time, delete the symlink
+ # after generating the core file. GDB should be smart enough to
+ # figure out that we can use the underlying TESTFILE binary.
+ with_test_prefix "to deleted symlink" {
+ set symlink_name "symlink_2"
+ set symlink [standard_output_file $symlink_name]
+
+ with_cwd [standard_output_file ""] {
+ remote_exec build "ln -s ${testfile} $symlink_name"
+ }
+
+ set corefile [core_find $symlink]
+ if {$corefile == ""} {
+ untested "unable to create corefile"
+ return 0
+ }
+ set corefile_3 "$binfile.3.core"
+ remote_exec build "mv $corefile $corefile_3"
+
+ remote_exec build "rm -f $symlink"
+
+ test_load $corefile_3 $binfile $symlink
+ }
+
+ # Generate the core file with an absolute path to the executable,
+ # but move the core file and executable into a single directory
+ # together so GDB can't use the absolute path to find the
+ # executable.
+ #
+ # GDB should still find the executable though, but looking in the
+ # same directory as the core file.
+ with_test_prefix "in side directory" {
+ set binfile_2 [standard_output_file ${testfile}_2]
+ remote_exec build "cp $binfile $binfile_2"
+
+ set corefile [core_find $binfile_2]
+ if {$corefile == ""} {
+ untested "unable to create corefile"
+ return 0
+ }
+ set corefile_4 "$binfile.4.core"
+ remote_exec build "mv $corefile $corefile_4"
+
+ set side_dir [standard_output_file side_dir]
+ remote_exec build "mkdir -p $side_dir"
+ remote_exec build "mv $binfile_2 $side_dir"
+ remote_exec build "mv $corefile_4 $side_dir"
+
+ set relocated_corefile_4 [file join $side_dir [file tail $corefile_4]]
+ set relocated_binfile_2 [file join $side_dir [file tail $binfile_2]]
+ test_load $relocated_corefile_4 $relocated_binfile_2 $binfile_2
+ }
+}
+
+with_test_prefix "relative path" {
+ # Generate a core file using relative a path. We ned to work
+ # around the core_find proc a little here. The core_find proc
+ # creates a sub-directory using standard_output_file and runs the
+ # test binary from inside that directory.
+ #
+ # Usually core_find is passed an absolute path, so thre's no
+ # problem, but we want to pass a relative path.
+ #
+ # So setup a directory structure like this:
+ #
+ # corefile-find-exec/
+ # reldir/
+ # <copy of $binfile here>
+ # workdir/
+ #
+ # Place a copy of BINFILE in 'reldir/' and switch to workdir, use
+ # core_find which will create a sibling directory of workdir, and
+ # run the relative path from there. We then move the generated
+ # core file back into 'workdir/', this leaves a tree like:
+ #
+ # corefile-find-exec/
+ # reldir/
+ # <copy of $binfile here>
+ # workdir/
+ # <core file here>
+ #
+ # Now we can ask GDB to open the core file, if all goes well GDB
+ # should make use of the relative path encoded in the core file to
+ # locate the executable in 'reldir/'.
+ #
+ # We also setup a symlink in 'reldir' that points to the
+ # executable and repeat the test, but this time executing the
+ # symlink.
+ set reldir_name "reldir"
+ set reldir [standard_output_file $reldir_name]
+ remote_exec build "mkdir -p $reldir"
+
+ set alt_testfile "alt_${testfile}"
+ set binfile_3 "$reldir/${alt_testfile}"
+ remote_exec build "cp $binfile $binfile_3"
+
+ set symlink_2 "symlink_2"
+ with_cwd $reldir {
+ remote_exec build "ln -s ${alt_testfile} ${symlink_2}"
+ }
+
+ set work_dir [standard_output_file "workdir"]
+ remote_exec build "mkdir -p $work_dir"
+
+ set rel_path_to_file "../${reldir_name}/${alt_testfile}"
+ set rel_path_to_symlink_2 "../${reldir_name}/${symlink_2}"
+
+ with_cwd $work_dir {
+ with_test_prefix "to file" {
+ set corefile [core_find $rel_path_to_file]
+ if {$corefile == ""} {
+ untested "unable to create corefile"
+ return 0
+ }
+ set corefile_5 "${work_dir}/${testfile}.5.core"
+ remote_exec build "mv $corefile $corefile_5"
+
+ test_load $corefile_5 \
+ [file join $work_dir $rel_path_to_file] \
+ $rel_path_to_file
+ }
+
+ with_test_prefix "to symlink" {
+ set corefile [core_find $rel_path_to_symlink_2]
+ if {$corefile == ""} {
+ untested "unable to create corefile"
+ return 0
+ }
+ set corefile_6 "${work_dir}/${testfile}.6.core"
+ remote_exec build "mv $corefile $corefile_6"
+
+ test_load $corefile_6 \
+ [file join $work_dir $rel_path_to_symlink_2] \
+ $rel_path_to_symlink_2
+ }
+
+ # Move the core file. Now the relative path doesn't work so
+ # we instead rely on GDB to use information about the mapped
+ # files to help locate the executable.
+ with_test_prefix "with moved corefile" {
+ set corefile_7 [standard_output_file "${testfile}.7.core"]
+ remote_exec build "cp $corefile_6 $corefile_7"
+ test_load $corefile_7 $binfile_3 $rel_path_to_symlink_2
+ }
+ }
+}