diff --git a/gdb/arch-utils.h b/gdb/arch-utils.h index cae036315e2..d27cc1dbc06 100644 --- a/gdb/arch-utils.h +++ b/gdb/arch-utils.h @@ -22,6 +22,7 @@ #include "gdbarch.h" #include "gdbsupport/environ.h" +#include "filenames.h" class frame_info_ptr; struct minimal_symbol; @@ -87,15 +88,23 @@ struct core_file_exec_context 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 exec_name, + gdb::unique_xmalloc_ptr exec_filename, std::vector> argv, std::vector> 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 @@ -112,6 +121,13 @@ struct core_file_exec_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. */ @@ -127,6 +143,13 @@ private: if no executable name is found. */ gdb::unique_xmalloc_ptr 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 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> m_arguments; diff --git a/gdb/corelow.c b/gdb/corelow.c index 7100399ae4e..9b9bee05bac 100644 --- a/gdb/corelow.c +++ b/gdb/corelow.c @@ -871,34 +871,138 @@ rename_vmcore_idle_reg_sections (bfd *abfd, inferior *inf) 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; - gdb_bfd_ref_ptr execbfd - = find_objfile_by_build_id (current_program_space, build_id, - filename.c_str ()); + 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 ()); + } + + execbfd + = find_objfile_by_build_id (current_program_space, build_id, + filename.c_str ()); + } if (execbfd != nullptr) { @@ -967,13 +1071,6 @@ core_target_open (const char *arg, int from_tty) 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 (); @@ -1028,9 +1125,31 @@ core_target_open (const char *arg, int from_tty) 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); @@ -1048,11 +1167,6 @@ core_target_open (const char *arg, int 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; diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index 6d90e89e3c4..d3ab02d03e0 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -2090,7 +2090,29 @@ linux_corefile_parse_exec_context_1 (struct gdbarch *gdbarch, bfd *cbfd) 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 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 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)); } diff --git a/gdb/testsuite/gdb.base/corefile-find-exec.c b/gdb/testsuite/gdb.base/corefile-find-exec.c new file mode 100644 index 00000000000..ed4df606a2d --- /dev/null +++ b/gdb/testsuite/gdb.base/corefile-find-exec.c @@ -0,0 +1,25 @@ +/* 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 . */ + +#include + +int +main (int argc, char **argv) +{ + abort (); + return 0; +} diff --git a/gdb/testsuite/gdb.base/corefile-find-exec.exp b/gdb/testsuite/gdb.base/corefile-find-exec.exp new file mode 100644 index 00000000000..40324c1f01c --- /dev/null +++ b/gdb/testsuite/gdb.base/corefile-find-exec.exp @@ -0,0 +1,242 @@ +# 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 . + +# 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/ + # + # 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/ + # + # workdir/ + # + # + # 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 + } + } +}