]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: update shared libraries when inferior is created, even if no bfd exists
authorSébastien Darche <sdarche@efficios.com>
Tue, 30 Sep 2025 18:54:30 +0000 (14:54 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Fri, 9 Jan 2026 19:54:59 +0000 (14:54 -0500)
I noticed an unexpected behaviour difference when loading a core file
in GDB depending on whether the main executable can be accessed or not.
If GDB knows about the main executable, then symbols for unrelated
libraries (such as libc) are loaded. If GDB cannot find the main
executable, then they are not.

Here is a reproducer using the artifacts from
gdb.python/py-missing-objfile.exp.  This test is ideal to reproduce the
problem, because it hides from GDB the executable and library used to
generate the core file.

In the "good" case, where we give an executable to GDB (in addition to
the core), we get a complete backtrace (assuming GDB can find debug symbols
for glibc):

    $ ./gdb -nx -q --data-directory=data-directory testsuite/outputs/gdb.python/py-missing-objfile/hidden/py-missing-objfile  -c testsuite/outputs/gdb.python/py-missing-objfile/py-missing-objfile.core -ex bt -batch
    ...
    #0  __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
    #1  0x00007fcbd0c98a13 in __pthread_kill_internal (threadid=<optimized out>, signo=6) at pthread_kill.c:89
    #2  0x00007fcbd0c3e410 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
    #3  0x00007fcbd0c2557a in __GI_abort () at abort.c:77
    #4  0x000055561659d152 in dump_core () at /home/sdarche/binutils-gdb/gdb/testsuite/gdb.python/py-missing-objfile.c:34
    #5  0x000055561659d182 in main () at /home/sdarche/binutils-gdb/gdb/testsuite/gdb.python/py-missing-objfile.c:46

Or, if debug symbols for glibc aren't available, GDB at least tells us
from which .so each frame comes from:

    #0  0x00007fcbd0c9894c in ?? () from /usr/lib/libc.so.6
    #1  0x00007fcbd0c3e410 in raise () from /usr/lib/libc.so.6
    #2  0x00007fcbd0c2557a in abort () from /usr/lib/libc.so.6
    #3  0x000055561659d152 in dump_core () at /home/sdarche/binutils-gdb/gdb/testsuite/gdb.python/py-missing-objfile.c:34
    #4  0x000055561659d182 in main () at /home/sdarche/binutils-gdb/gdb/testsuite/gdb.python/py-missing-objfile.c:46

If we omit passing the main executable to GDB (and GDB has no way to
find it, because the test moved it on purpose), we get:

    $ ./gdb -nx -q --data-directory=data-directory -c testsuite/outputs/gdb.python/py-missing-objfile/py-missing-objfile.core -ex bt -batch
    #0  0x00007fcbd0c9894c in ?? ()
    #1  0x0000000000000000 in ?? ()

Upon investigating, the "normal" path to load associated sos is through
the post_create_inferior function in infcmd.c, which calls
solib_create_inferior_hook.  The solib_ops in turn loads the symbols by
calling solib_add.

When loading a core file, the list of loaded shared libraries can be
found (and as we can see with `info sharedlibraries`, this is done
properly).  However, the current control flow handling in
post_create_inferior does not ask the solib to load the symbols through
solib_create_inferior_hook if a main exec_bfd is not present.

The proposed change eliminates the if statement in the
post_create_inferior function.

This change may have side-effects on some solib_ops which may not check
if the current inferior has a valid exec_bfd().

This commit also modifies the gdb.python/py-missing-objfile.exp test,
providing a test case in which the main exec file is missing, but not
the shared library.  This is a good way to confirm this fix works, as
the symbols from the shared library should be found even though the main
exec file is missing.  The test cases also ask GDB to clean_restart to
ensure there is no leftover bfd or symbols.

Furthermore, the total number of calls to the missing objfiles handler
is 4 in the "all objfiles missing" test case instead of the
previous 3 :
- 2 for the mapped files (exec and so), in
  core_target_build_file_mappings
- 1 for the core file exec, in locate_exec_from_corefile_build_id
- 1 for the missing so, in solib_map_sections

The changes in check_loaded_debug handle the case where no symbol exist
at all, and also when a symbol table exists (but the symbol cannot be
found)

Note: Some comments in some solib_ops (frv, dbst) seem to indicate that
solib_add should be called before solib_create_inferior_hook by
post_create_inferior, but this does not seem to be the case anymore.
Those comments might need to be updated.

Change-Id: I517ff85813c941215b19fa8540c77f755a0aca28
Reviewed-By: Tankut Baris Aktemur <tankut.baris.aktemur@intel.com>
Tested-By: Guinevere Larsen <guinevere@redhat.com>
Approved-By: Simon Marchi <simon.marchi@efficios.com>
gdb/infcmd.c
gdb/testsuite/gdb.python/py-missing-objfile.exp

index 862cef6c1bb74ea82f74e6f9ebeecbd01327f654..0b9ca48b70f02fb236ad2f10da58e0c8142df7a7 100644 (file)
@@ -402,36 +402,35 @@ post_create_inferior (int from_tty, bool set_pspace_solib_ops)
       (gdbarch_make_solib_ops (current_inferior ()->arch (),
                               current_program_space));
 
-  if (current_program_space->exec_bfd ())
-    {
-      const unsigned solib_add_generation
-       = current_program_space->solib_add_generation;
+  {
+    const unsigned solib_add_generation
+      = current_program_space->solib_add_generation;
 
-      scoped_restore restore_in_initial_library_scan
-       = make_scoped_restore (&current_inferior ()->in_initial_library_scan,
-                              true);
+    scoped_restore restore_in_initial_library_scan
+      = make_scoped_restore (&current_inferior ()->in_initial_library_scan,
+                            true);
 
-      /* Create the hooks to handle shared library load and unload
-        events.  */
-      solib_create_inferior_hook (from_tty);
+    /* Create the hooks to handle shared library load and unload
+       events.  */
+    solib_create_inferior_hook (from_tty);
 
-      if (current_program_space->solib_add_generation == solib_add_generation)
-       {
-         /* The platform-specific hook should load initial shared libraries,
-            but didn't.  FROM_TTY will be incorrectly 0 but such solib
-            targets should be fixed anyway.  Call it only after the solib
-            target has been initialized by solib_create_inferior_hook.  */
-
-         if (info_verbose)
-           warning (_("platform-specific solib_create_inferior_hook did "
-                      "not load initial shared libraries."));
-
-         /* If the solist is global across processes, there's no need to
-            refetch it here.  */
-         if (!gdbarch_has_global_solist (current_inferior ()->arch ()))
-           solib_add (nullptr, 0, auto_solib_add);
-       }
-    }
+    if (current_program_space->solib_add_generation == solib_add_generation)
+      {
+       /* The platform-specific hook should load initial shared libraries,
+          but didn't.  FROM_TTY will be incorrectly 0 but such solib
+          targets should be fixed anyway.  Call it only after the solib
+          target has been initialized by solib_create_inferior_hook.  */
+
+       if (info_verbose)
+         warning (_("platform-specific solib_create_inferior_hook did "
+                    "not load initial shared libraries."));
+
+       /* If the solist is global across processes, there's no need to
+          refetch it here.  */
+       if (!gdbarch_has_global_solist (current_inferior ()->arch ()))
+         solib_add (nullptr, 0, auto_solib_add);
+      }
+  }
 
   /* If the user sets watchpoints before execution having started,
      then she gets software watchpoints, because GDB can't know which
index bb747d69a1b9162810bd9c6e415cf8e66d77b2ae..e5e086130b004a77f1d0774c8e3c684e48a14e08 100644 (file)
@@ -96,18 +96,34 @@ proc check_loaded_debug { exec_loaded lib_loaded } {
 
     if { $exec_loaded } {
        gdb_test "whatis global_exec_var" "^type = volatile struct exec_type"
-
-       if { $lib_loaded } {
-           gdb_test "whatis global_lib_var" "^type = volatile struct lib_type"
-       } else {
-           gdb_test "whatis global_lib_var" \
-               "^No symbol \"global_lib_var\" in current context\\."
+    } else {
+       # If the debug info for libc, etc. are available, there might
+       # be a symbol table.
+       gdb_test_multiple "whatis global_exec_var" "" {
+           -re -wrap "No symbol \"global_exec_var\" in current context\\." {
+               pass $gdb_test_name
+           }
+
+           -re -wrap "No symbol table is loaded\\.  Use the \"file\" command\\." {
+               pass $gdb_test_name
+           }
        }
+    }
+
+    if { $lib_loaded } {
+       gdb_test "whatis global_lib_var" "^type = volatile struct lib_type"
     } else {
-       gdb_test "whatis global_exec_var" \
-           "^No symbol table is loaded\\.  Use the \"file\" command\\."
-       gdb_test "whatis global_lib_var" \
-           "^No symbol table is loaded\\.  Use the \"file\" command\\."
+       # If the debug info for libc, etc. are available, there might
+       # be a symbol table.
+       gdb_test_multiple "whatis global_lib_var" "" {
+           -re -wrap "No symbol \"global_lib_var\" in current context\\." {
+               pass $gdb_test_name
+           }
+
+           -re -wrap "No symbol table is loaded\\.  Use the \"file\" command\\." {
+               pass $gdb_test_name
+           }
+       }
     }
 }
 
@@ -173,6 +189,8 @@ with_test_prefix "no objfiles, no debug-file-directory" {
 # Setup some debug-file-directories.
 set debugdir_no_lib \
     [setup_debugdir "debugdir.no-lib" [list "$hidden_binfile"]]
+set debugdir_no_main \
+    [setup_debugdir "debugdir.no-main" [list "$hidden_libfile"]]
 set debugdir_empty \
     [setup_debugdir "debugdir.empty" {}]
 set debugdir_all \
@@ -196,6 +214,7 @@ require {expr {$expect_build_id_in_core_file_libfile}}
 with_test_prefix "all objfiles available" {
     # Another sanity check that GDB can find the files via the
     # debug-file-directory.
+    clean_restart
     set_debug_file_dir $debugdir_all
     load_core_file
     check_loaded_debug true true
@@ -204,11 +223,21 @@ with_test_prefix "all objfiles available" {
 with_test_prefix "lib objfile missing" {
     # Another sanity check that GDB can find the files via the
     # debug-file-directory.
+    clean_restart
     set_debug_file_dir $debugdir_no_lib
     load_core_file
     check_loaded_debug true false
 }
 
+with_test_prefix "main objfile missing" {
+    # Another sanity check that GDB can find the files via the
+    # debug-file-directory.
+    clean_restart
+    set_debug_file_dir $debugdir_no_main
+    load_core_file
+    check_loaded_debug false true
+}
+
 with_test_prefix "all objfiles missing, handler returns None" {
     clean_restart_load_python
     gdb_test_no_output \
@@ -218,11 +247,11 @@ with_test_prefix "all objfiles missing, handler returns None" {
 
     check_loaded_debug false false
 
-    # The handler should be called three times, once for the
-    # mapped-file, once for the core-file's exec, and once for the
+    # The handler should be called four times, twice for the
+    # mapped-files, once for the core-file's exec, and once for the
     # shared library.
-    gdb_test "python print(handler_obj.call_count)" "^3" \
-       "check handler was called three times"
+    gdb_test "python print(handler_obj.call_count)" "^4" \
+       "check handler was called four times"
 }
 
 with_test_prefix "lib objfile missing, handler returns None" {