mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
Move the call to clear_symtab_users from within finish_new_objfile to
instead reside within symbol_file_add_with_addrs. This resolves an
issue where clear_symtab_users can be called multiple times during the
loading of the main executable, which causes important information to
be discarded.
To understand the problem we must understand two things. First, how
does clear_symtab_users discard critical information? This is the
easy part of the problem, clear_symtab_users notifies the
all_objfiles_removed observer, in auto-load.c there is a listener for
this observer, clear_section_scripts, which discards information about
any auto-loaded scripts. If we call clear_symtab_users after
auto-loading a script within a program space, then information about
that script having been loaded will be discarded.
The second thing we must consider is the order in which functions are
called when loading a main executable with separate debug
information. I'm only listing some of the most important functions in
the process here, the ones relevant to the issue being fixed. In the
following text I'll use "main objfile" to refer to the actual
executable, and "debug info objfile" to refer to the split debug info
corresponding to the "main objfile".
1. Call symbol_file_add_with_addrs for the main objfile.
2. Call syms_from_objfile passing in the main objfile.
3. Call objfile::find_and_add_separate_symbol_file on the main
objfile.
4. Call symbol_file_add_separate for the debug info objfile.
5. Call symbol_file_add_with_addrs for the debug info objfile.
6. Call syms_from_objfile for the debug info objfile. The debug
symbols are contained in this objfile so they are read and added
to GDB.
7. Call finish_new_objfile for the debug info objfile, followed by
triggering the new objfile observer for the debug info objfile.
8. We are now done in symbol_file_add_with_addrs for the debug info
objfile. We now unwind the stack back to (1).
9. Back in symbol_file_add_with_addrs for the main objfile, the
debug symbols have now been added (from the separate debug info
objfile), so we can now call finish_new_objfile for the main
objfile, followed by triggering the new objfile observer for the
main objfile.
10. Success! The main objfile, and the associated debug info objfile
have now been added to GDB.
Notice that we end up with a recursive call back into
symbol_file_add_with_addrs, which results in two calls to
finish_new_objfile, the debug info objfile is processed first, and the
main objfile is processed second.
The main objfile will have SYMFILE_MAINLINE in its symfile_add_flags,
and this will end up being passed to symbol_file_add_separate.
This means that in finish_new_objfile we currently call
clear_symtab_users after finishing both the debug info objfile, and
after the main objfile, in that order.
Auto-loaded scripts are loaded by load_auto_scripts_for_objfile (in
auto-load.c) which is called by the new_objfile observer. If the
debug info objfile contains any scripts within the .debug_gdb_scripts
section, then these will be loaded (7) in the above list, after the
debug info objfile has been added, but before the main objfile is
added.
Now, this shouldn't be a problem, except that currently, when
finishing the main objfile, we call clear_symtab_users, which triggers
the all_objfiles_removed observer, which calls
clear_section_scripts (in auto-load.c), which discards all records of
auto-loaded scripts.
This issue is exposed by the test extension added in this commit. An
executable is compiled with debug information, the executable includes
a .debug_gdb_scripts section. We then use objcopy to split the debug
information into a separate objfile. This takes the
.debug_gdb_scripts section with it. We then ask GDB to load the
executable, which triggers loading of the separate debug information
file. Loading the separate debug information file loads the
.debug_gdb_scripts section, then GDB finishes loading the main
executable and discards the record of loading the .debug_gdb_scripts.
This is exposed by using 'info auto-load'.
The solution I propose is to move the call to clear_symtab_users
earlier within symbol_file_add_with_addrs, and to guard the call so
that it is only called for the main objfile, and not for the debug
info objfile. The new location for the clear_symtab_users call is
before the syms_from_objfile call. This means that it will be called
for the main objfile before GDB starts loading the debug info objfile.
There are two additional, related changes in this commit. The new
location for the clear_symtab_users call is before
program_space::symfile_object_file is removed by unlinking the
referenced objfile, so we don't want the breakpoint_re_set call in
clear_symtab_users to resolve breakpoints against the old objfiles.
To avoid this, at the new call to clear_symtab_users, we force the
SYMFILE_DEFER_BP_RESET. Then we restructure finish_new_objfile so
that breakpoint_re_set can always be called if appropriate. This
replaces the old breakpoint_re_set call which was reached via the old
call to clear_symtab_users.
Additionally, in finish_new_objfile, we only set
program_space::symfile_object_file for the main objfile. Previously
we would set this field first to the debug info objfile, and then
reset this field to the main objfile. This set then overwrite
sequence was harmless, but also pointless. I think by restricting
this code so we only set it to the main objfile better reflects the
state we want GDB to be in.
Approved-By: Kevin Buettner <kevinb@redhat.com>
230 lines
7.4 KiB
Text
230 lines
7.4 KiB
Text
# Copyright (C) 2010-2026 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 file is part of the GDB testsuite. It tests automagic loading of
|
|
# scripts specified in the .debug_gdb_scripts section.
|
|
|
|
require is_elf_target
|
|
require allow_python_tests
|
|
|
|
standard_testfile
|
|
|
|
# Make this available to gdb before the program starts, it is
|
|
# automagically loaded by gdb.
|
|
set remote_python_file [gdb_remote_download host \
|
|
${srcdir}/${subdir}/${testfile}.py]
|
|
|
|
set quoted_name "\"$remote_python_file\""
|
|
|
|
# Helper proc to 'set auto-load safe-path ...'. Build the new path value by
|
|
# joining ARGS together with an appropriate path separator character.
|
|
proc set_auto_load_safe_path { args } {
|
|
if {[ishost *-*-mingw*]} {
|
|
set remote_pathsep ";"
|
|
} else {
|
|
set remote_pathsep ":"
|
|
}
|
|
|
|
set safe_path [join $args $remote_pathsep]
|
|
|
|
gdb_test_no_output "set auto-load safe-path $safe_path" \
|
|
"set auto-load safe-path"
|
|
}
|
|
|
|
foreach_with_prefix variant {plain compressed} {
|
|
set the_testfile $testfile-$variant
|
|
set the_binfile [standard_output_file $the_testfile]
|
|
|
|
set flags [list debug \
|
|
additional_flags=-I${srcdir}/../../include \
|
|
additional_flags=-DSCRIPT_FILE=$quoted_name]
|
|
if {$variant == "compressed"} {
|
|
lappend flags additional_flags=-Wa,--compress-debug-sections=zlib-gabi
|
|
lappend flags additional_flags=-Wl,--compress-debug-sections=zlib-gabi
|
|
}
|
|
|
|
if {[build_executable "build executable" $the_testfile $srcfile $flags] == -1} {
|
|
return
|
|
}
|
|
|
|
if {$variant == "compressed"} {
|
|
set objdump_program [gdb_find_objdump]
|
|
set debug_sect [exec $objdump_program -j .debug_gdb_scripts -s "$the_binfile"]
|
|
set debug_sect_decompressed [exec $objdump_program -j .debug_gdb_scripts \
|
|
-s "$the_binfile" --decompress]
|
|
|
|
if {$debug_sect == $debug_sect_decompressed} {
|
|
fail ".debug_gdb_scripts in $the_binfile is not compressed"
|
|
}
|
|
}
|
|
|
|
clean_restart
|
|
|
|
# Try first with a restrictive safe-path.
|
|
|
|
gdb_test_no_output "set auto-load safe-path /restricted" \
|
|
"set restricted auto-load safe-path"
|
|
gdb_load ${the_binfile}
|
|
|
|
# Verify gdb did not load the scripts.
|
|
gdb_test_multiple "info auto-load python-scripts" "verify scripts not loaded" {
|
|
-re -wrap "Yes.*${testfile}.py.*Yes.*inlined-script.*" {
|
|
fail $gdb_test_name
|
|
}
|
|
-re -wrap "No.*${testfile}.py.*No.*inlined-script.*" {
|
|
pass $gdb_test_name
|
|
}
|
|
}
|
|
|
|
# Try again with a working safe-path.
|
|
|
|
clean_restart
|
|
|
|
# Get the name of the binfile on the host; on a remote host this means
|
|
# stripping off any directory prefix.
|
|
if {[is_remote host]} {
|
|
set remote_binfile [file tail ${the_binfile}]
|
|
} else {
|
|
set remote_binfile ${the_binfile}
|
|
}
|
|
|
|
set_auto_load_safe_path $remote_python_file $remote_binfile
|
|
gdb_load ${the_binfile}
|
|
# Verify gdb loaded each script and they appear once in the list.
|
|
gdb_test_multiple "info auto-load python-scripts" "verify scripts loaded" {
|
|
-re -wrap "Yes.*${testfile}.py.*Yes.*inlined-script.*" {
|
|
pass $gdb_test_name
|
|
}
|
|
-re -wrap "${testfile}.py.*${testfile}.py.*" {
|
|
fail $gdb_test_name
|
|
}
|
|
-re -wrap "inlined-script.*inlined-script.*" {
|
|
fail $gdb_test_name
|
|
}
|
|
}
|
|
|
|
# Again, with a regexp this time.
|
|
gdb_test "info auto-load python-scripts ${testfile}" "Yes.*${testfile}.py.*"
|
|
|
|
# Again, with a regexp that matches no scripts.
|
|
gdb_test "info auto-load python-scripts no-script-matches-this" \
|
|
"No auto-load scripts matching no-script-matches-this."
|
|
|
|
if {![runto_main]} {
|
|
return
|
|
}
|
|
|
|
gdb_test "b [gdb_get_line_number {break to inspect}]" \
|
|
".*Breakpoint.*"
|
|
gdb_test "continue" ".*Breakpoint.*"
|
|
|
|
gdb_test "print ss" " = a=<1> b=<2>"
|
|
|
|
gdb_test "test-cmd 1 2 3" "test-cmd output, arg = 1 2 3"
|
|
|
|
|
|
# eu-strip creates NOBITS .debug_gdb_scripts sections in the debug
|
|
# files, and leaves a useful copy of .debug_gdb_scripts in the
|
|
# executable. Make sure the NOBITS copy in the separate debug file is
|
|
# ignored silently.
|
|
with_test_prefix "sepdebug from eu-strip" {
|
|
clean_restart
|
|
|
|
set eu_strip_binfile [standard_output_file ${the_testfile}-eu-strip]
|
|
file copy -force $the_binfile $eu_strip_binfile
|
|
|
|
set result [catch {exec eu-strip -g -f ${eu_strip_binfile}.debug ${eu_strip_binfile}} output]
|
|
verbose "result is $result"
|
|
verbose "output is $output"
|
|
if {$result != 0 || $output != ""} {
|
|
unsupported "cannot produce separate debug info files"
|
|
return
|
|
}
|
|
|
|
set_auto_load_safe_path $remote_python_file $eu_strip_binfile
|
|
gdb_test_multiple "file $eu_strip_binfile" "load sepdebug" {
|
|
-re -wrap "\r\nwarning: Invalid entry in \\.debug_gdb_scripts section.*" {
|
|
fail $gdb_test_name
|
|
}
|
|
-re -wrap "" {
|
|
pass $gdb_test_name
|
|
}
|
|
}
|
|
|
|
# Verify gdb loaded each script and they appear once in the list.
|
|
gdb_test_multiple "info auto-load python-scripts" "verify scripts loaded" {
|
|
-re -wrap "Yes.*${testfile}.py.*Yes.*inlined-script.*" {
|
|
pass $gdb_test_name
|
|
}
|
|
-re -wrap "${testfile}.py.*${testfile}.py.*" {
|
|
fail $gdb_test_name
|
|
}
|
|
-re -wrap "inlined-script.*inlined-script.*" {
|
|
fail $gdb_test_name
|
|
}
|
|
}
|
|
|
|
# Again, with a regexp this time.
|
|
gdb_test "info auto-load python-scripts ${testfile}" "Yes.*${testfile}.py.*"
|
|
|
|
# Again, with a regexp that matches no scripts.
|
|
gdb_test "info auto-load python-scripts no-script-matches-this" \
|
|
"No auto-load scripts matching no-script-matches-this."
|
|
}
|
|
|
|
# Unlike eu-strip, objcopy moves the .debug_gdb_scripts into the .debug
|
|
# file, removing it from the executable. GDB should still load the
|
|
# script though, just when it loads the symbol file, not the main
|
|
# executable.
|
|
with_test_prefix "sepdebug from objcopy" {
|
|
clean_restart
|
|
|
|
set objcopy_testfile ${the_testfile}-objcopy
|
|
set objcopy_binfile [standard_output_file $objcopy_testfile]
|
|
file copy -force $the_binfile $objcopy_binfile
|
|
|
|
if { [gdb_gnu_strip_debug $objcopy_binfile] != 0 } {
|
|
fail "strip $objcopy_testfile debuginfo"
|
|
return
|
|
}
|
|
|
|
# Add the directory containing OBJCOPY_BINFILE because the scripts
|
|
# section will be auto-loaded from the separate .debug file (not the
|
|
# main executable), which resides alongside it.
|
|
set_auto_load_safe_path $remote_python_file \
|
|
[file dirname ${objcopy_binfile}]
|
|
gdb_load $objcopy_binfile
|
|
|
|
# Verify gdb loaded each script and they appear once in the list.
|
|
gdb_test_multiple "info auto-load python-scripts" "verify scripts loaded" {
|
|
-re -wrap "Yes.*${testfile}.py.*Yes.*inlined-script.*" {
|
|
pass $gdb_test_name
|
|
}
|
|
-re -wrap "${testfile}.py.*${testfile}.py.*" {
|
|
fail $gdb_test_name
|
|
}
|
|
-re -wrap "inlined-script.*inlined-script.*" {
|
|
fail $gdb_test_name
|
|
}
|
|
}
|
|
|
|
# Again, with a regexp this time.
|
|
gdb_test "info auto-load python-scripts ${testfile}" "Yes.*${testfile}.py.*"
|
|
|
|
# Again, with a regexp that matches no scripts.
|
|
gdb_test "info auto-load python-scripts no-script-matches-this" \
|
|
"No auto-load scripts matching no-script-matches-this."
|
|
}
|
|
}
|