mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
Add a new Python event registry, events.corefile_changed. This event is emitted each time the corefile within an inferior changes. The event object has a single 'inferior' attribute which is the gdb.Inferior object for which the core file changed. The user can then inspect Inferior.corefile to see details about the new core file, or this will be None if the core file was removed from the inferior. I've updated the existing test to cover this new event. The new test covers both the corefile_changed event, but also monitors the exited event. This ties into the work done in the previous commit where we use whether the inferior has exited or not as a guard for whether core_target::exit_core_file_inferior should be called. Unloading a core file should result in a single corefile_changed event and a single exited event. Reviewed-By: Eli Zaretskii <eliz@gnu.org>
412 lines
14 KiB
Text
412 lines
14 KiB
Text
# Copyright (C) 2025-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 the core file
|
|
# support in Python.
|
|
|
|
require isnative
|
|
require {!is_remote host}
|
|
|
|
load_lib gdb-python.exp
|
|
|
|
require allow_python_tests
|
|
|
|
standard_testfile
|
|
|
|
if {[build_executable "build executable" $testfile $srcfile] == -1} {
|
|
return
|
|
}
|
|
|
|
set remote_python_file \
|
|
[gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
|
|
|
|
set corefile [core_find $binfile]
|
|
if {$corefile == ""} {
|
|
unsupported "couldn't create or find corefile"
|
|
return
|
|
}
|
|
|
|
# Helper proc to run the 'core-file' command. Takes optional arguments:
|
|
#
|
|
# -corefile FILENAME : Load FILENAME as the new core file. If this
|
|
# argument is not given then the current core
|
|
# file will be unloaded.
|
|
#
|
|
# -inferior NUM : The inferior in which the corefile is being changed.
|
|
# This is used to match the corefile_changed events
|
|
# that will be emitted.
|
|
#
|
|
# -prefix STRING : A test prefix, to make test names unique.
|
|
#
|
|
# -replacement : There's already a core file loaded when this command
|
|
# is being run.
|
|
proc core_file_cmd { args } {
|
|
parse_some_args {
|
|
{corefile ""}
|
|
{inferior 1}
|
|
{prefix ""}
|
|
{replacement}
|
|
}
|
|
|
|
if { $prefix eq "" } {
|
|
if { $corefile eq "" } {
|
|
set prefix "unload corefile"
|
|
} else {
|
|
set prefix "load corefile"
|
|
}
|
|
}
|
|
|
|
with_test_prefix $prefix {
|
|
gdb_test "events corefile_changed check" \
|
|
"^No corefile_changed event has been seen\\." \
|
|
"no corefile event has been seen"
|
|
|
|
gdb_test "events exited check" \
|
|
"^No exited event has been seen\\." \
|
|
"no exited event has been seen"
|
|
|
|
if { $corefile eq "" } {
|
|
gdb_test "core-file" "^No core file now\\." "unload current core file"
|
|
|
|
gdb_test "events corefile_changed check" \
|
|
"Event 1/1, Inferior $inferior, Corefile None" \
|
|
"expected corefile event has been seen"
|
|
|
|
gdb_test "events exited check" \
|
|
"Event 1/1, Inferior $inferior, Exit Code None" \
|
|
"expected exited event has been seen"
|
|
} else {
|
|
gdb_test "core-file $corefile" ".*" "load core file"
|
|
|
|
if { $replacement } {
|
|
gdb_test "events corefile_changed check" \
|
|
[multi_line \
|
|
"Event 1/2, Inferior $inferior, Corefile None" \
|
|
"Event 2/2, Inferior $inferior, Corefile [string_to_regexp $corefile]"] \
|
|
"expected corefile event has been seen"
|
|
|
|
gdb_test "events exited check" \
|
|
"Event 1/1, Inferior $inferior, Exit Code None" \
|
|
"expected exited event has been seen"
|
|
} else {
|
|
gdb_test "events corefile_changed check" \
|
|
"Event 1/1, Inferior $inferior, Corefile [string_to_regexp $corefile]" \
|
|
"expected corefile event has been seen"
|
|
|
|
gdb_test "events exited check" \
|
|
"^No exited event has been seen\\." \
|
|
"no exited event was emitted"
|
|
}
|
|
}
|
|
}
|
|
|
|
gdb_test_no_output -nopass "events corefile_changed reset"
|
|
gdb_test_no_output -nopass "events exited reset"
|
|
}
|
|
|
|
# A helper proc runs clean_restart passing through ARGS, and then loads the
|
|
# test's Python script.
|
|
proc clean_restart_and_load_py_script { args } {
|
|
clean_restart {*}$args
|
|
|
|
# Load the Python script into GDB.
|
|
gdb_test "source $::remote_python_file" "^Success" \
|
|
"source python script"
|
|
}
|
|
|
|
# Create a copy of the corefile.
|
|
set other_corefile [standard_output_file ${testfile}-other.core]
|
|
remote_exec build "cp $corefile $other_corefile"
|
|
|
|
clean_restart_and_load_py_script
|
|
|
|
gdb_test_no_output "python inf = gdb.selected_inferior()" \
|
|
"capture current inferior"
|
|
|
|
gdb_test "python print(inf.corefile)" "^None" \
|
|
"Inferior.corefile is None before loading a core file"
|
|
|
|
core_file_cmd -corefile $corefile
|
|
|
|
set file_re [string_to_regexp $corefile]
|
|
gdb_test "python print(inf.corefile)" "^<gdb\\.Corefile inferior=1 filename='$file_re'>" \
|
|
"Inferior.corefile is a valid object after loading a core file"
|
|
|
|
gdb_test_no_output "python core1=inf.corefile" "capture gdb.Corefile object"
|
|
|
|
gdb_test "python print(core1.__dict__)" "^\\{\\}" \
|
|
"print Corefile.__dict__ when empty"
|
|
|
|
gdb_test_no_output "python core1._my_attribute = \"Hello\"" \
|
|
"write new attribute into Corefile object"
|
|
|
|
gdb_test "python print(core1._my_attribute)" "^Hello" \
|
|
"immediately read new attribute"
|
|
|
|
gdb_test "python print(core1.__dict__)" "^\\{'_my_attribute': 'Hello'\\}" \
|
|
"print Corefile.__dict__ after adding an attribute"
|
|
|
|
gdb_test "python print(core1.filename)" "^$file_re" \
|
|
"Corefile.filename attribute works as expected"
|
|
|
|
gdb_test "python print(core1.is_valid())" "^True" \
|
|
"Corefile.is_valid() is True while corefile is loaded"
|
|
|
|
core_file_cmd
|
|
|
|
gdb_test "python print(core1.is_valid())" "^False" \
|
|
"Corefile.is_valid() is False after corefile is unloaded"
|
|
|
|
gdb_test "python print(core1.__dict__)" "^\\{'_my_attribute': 'Hello'\\}" \
|
|
"print Corefile.__dict__ with attribute when invalid"
|
|
|
|
gdb_test "python print(core1)" "^<gdb\\.Corefile \\(invalid\\)>" \
|
|
"print an invalid gdb.Corefile object"
|
|
|
|
gdb_test "python print(core1.filename)" \
|
|
[multi_line \
|
|
"Python Exception <class 'RuntimeError'>: Corefile no longer exists\\." \
|
|
"Error occurred in Python: Corefile no longer exists\\."] \
|
|
"error when reading filename from invalid Corefile"
|
|
|
|
gdb_test "python print(inf.corefile)" "^None" \
|
|
"Inferior.corefile is None again after corefile unload"
|
|
|
|
gdb_test "python print(core1._my_attribute)" "^Hello" \
|
|
"read new attribute from invalid core file"
|
|
|
|
# Create a second inferior.
|
|
gdb_test "add-inferior"
|
|
gdb_test "inferior 2"
|
|
|
|
with_test_prefix "in second inferior" {
|
|
core_file_cmd -corefile $corefile -inferior 2
|
|
|
|
gdb_test "python print(inf.corefile)" "^None" \
|
|
"first inferior still has no core file"
|
|
|
|
gdb_test_no_output "python core2=gdb.selected_inferior().corefile" \
|
|
"capture gdb.Corefile object"
|
|
|
|
# The _my_attribute was added to CORE1, not CORE2. Check it
|
|
# doesn't somehow appear on CORE2.
|
|
gdb_test "python print(core2._my_attribute)" \
|
|
"AttributeError.*: 'gdb\\.Corefile' object has no attribute '_my_attribute'" \
|
|
"try to read attribute that doesn't exist"
|
|
|
|
gdb_test "python print(core2.filename)" "^$file_re" \
|
|
"Corefile.filename attribute works as expected"
|
|
|
|
gdb_test "inferior 1"
|
|
}
|
|
|
|
# Read the name of the core file from the second program space while
|
|
# the current program space is the first one.
|
|
gdb_test "python print(core2.filename)" "^$file_re" \
|
|
"Corefile.filename attribute works from different progspace"
|
|
|
|
# Load the other corefile into the first inferior.
|
|
core_file_cmd -corefile $other_corefile \
|
|
-prefix "load other corefile into inferior 1"
|
|
|
|
# Delete the second inferior. We need to switch to the second
|
|
# inferior and unload its corefile before we can do that. Then,
|
|
# switch back to the first inferior, delete the second, and try to
|
|
# read the filename of the core file from the (now deleted) second
|
|
# inferior. We should get an error about the gdb.Corefile being
|
|
# invalid.
|
|
with_test_prefix "remove second inferior" {
|
|
gdb_test "inferior 2"
|
|
|
|
gdb_test "python print(inf.corefile.filename)" \
|
|
"^[string_to_regexp $other_corefile]" \
|
|
"read inferior 1 corefile when in inferior 2"
|
|
|
|
gdb_test_no_output "python core1=inf.corefile" \
|
|
"capture inferior 1 gdb.Corefile while in inferior 2"
|
|
|
|
# This is a new CORE1 object, check that _my_attribute is gone.
|
|
gdb_test "python print(core1._my_attribute)" \
|
|
"AttributeError.*: 'gdb\\.Corefile' object has no attribute '_my_attribute'" \
|
|
"try to read attribute that doesn't exist"
|
|
|
|
core_file_cmd -inferior 2
|
|
|
|
gdb_test "python print(core2.filename)" \
|
|
[multi_line \
|
|
"Python Exception <class 'RuntimeError'>: Corefile no longer exists\\." \
|
|
"Error occurred in Python: Corefile no longer exists\\."] \
|
|
"error when reading filename from invalid Corefile"
|
|
|
|
gdb_test "inferior 1"
|
|
|
|
gdb_test "remove-inferiors 2"
|
|
|
|
gdb_test "python print(core2.is_valid())" "^False" \
|
|
"Corefile.is_valid() is False after corefile is unloaded, and Progspace is deleted"
|
|
|
|
gdb_test "python print(core2.filename)" \
|
|
[multi_line \
|
|
"Python Exception <class 'RuntimeError'>: Corefile no longer exists\\." \
|
|
"Error occurred in Python: Corefile no longer exists\\."] \
|
|
"error when reading filename of an invalid Corefile, from deleted program space"
|
|
|
|
gdb_test "python print(core1.is_valid())" "^True" \
|
|
"check inferior 1 core file is still valid"
|
|
}
|
|
|
|
# Test the Corefile.mapped_files() API. The Python script that is
|
|
# sourced here implements 'info proc mappings' in Python using the
|
|
# mapped_files API. The output from the built-in command, and the
|
|
# Python command should be identical.
|
|
with_test_prefix "test mapped files data" {
|
|
clean_restart_and_load_py_script
|
|
|
|
# Load the core file.
|
|
core_file_cmd -corefile $corefile
|
|
|
|
# Two files to write the output to.
|
|
set out_1 [standard_output_file ${gdb_test_file_name}-out-1.txt]
|
|
set out_2 [standard_output_file ${gdb_test_file_name}-out-2.txt]
|
|
|
|
# Run the built-in command, then the new Python command, capture
|
|
# the output.
|
|
gdb_test "pipe info proc mappings | tee $out_1" ".*" \
|
|
"capture built-in mappings output"
|
|
gdb_test "pipe info proc py-mappings | tee $out_2" ".*" \
|
|
"capture Python based mappings data"
|
|
|
|
# Check the output is identical.
|
|
gdb_test "shell diff -s $out_1 $out_2" \
|
|
"Files \[^\r\n\]+-out-1.txt and \[^\r\n\]+-out-2.txt are identical" \
|
|
"diff input and output one"
|
|
|
|
# Check build-ids within the core file mapping data. I'm only
|
|
# aware of GNU/Linux placing the first page of each mapped ELF
|
|
# into the generated core file so that the build-id can be found.
|
|
if {[istarget *-*-linux*]} {
|
|
set results [list]
|
|
gdb_test_multiple "show-build-ids" "" {
|
|
-re "^show-build-ids\r\n" {
|
|
exp_continue
|
|
}
|
|
-re "^Objfile Build-Id\\s+Core File Build-Id\\s+File Name\\s*\r\n" {
|
|
exp_continue
|
|
}
|
|
-re "^(\\S+)\\s+(\\S+)\\s+(\[^\r\n\]+)\r\n" {
|
|
set objfile_build_id $expect_out(1,string)
|
|
set core_file_build_id $expect_out(2,string)
|
|
set file_name $expect_out(3,string)
|
|
lappend results [list $objfile_build_id \
|
|
$core_file_build_id \
|
|
$file_name]
|
|
exp_continue
|
|
}
|
|
-re "^$gdb_prompt " {
|
|
pass $gdb_test_name
|
|
}
|
|
}
|
|
|
|
set bad_count 0
|
|
foreach entry $results {
|
|
set objfile_build_id [lindex $entry 0]
|
|
set core_file_build_id [lindex $entry 1]
|
|
set file_name [lindex $entry 2]
|
|
if { $objfile_build_id ne $core_file_build_id } {
|
|
if { $core_file_build_id ne "None" } {
|
|
verbose -log "Mismatched build-ids $objfile_build_id vs $core_file_build_id for $file_name"
|
|
incr bad_count
|
|
} elseif { [expect_build_id_in_core_file $file_name] } {
|
|
verbose -log "Failed to find build-id for $file_name"
|
|
incr bad_count
|
|
} else {
|
|
verbose -log "This build-id was likely not in the core file"
|
|
}
|
|
}
|
|
}
|
|
|
|
gdb_assert { $bad_count == 0 } \
|
|
"found expected build-ids in core file"
|
|
}
|
|
|
|
# Check the is_main_executable flag in the mapping data.
|
|
gdb_test "check-main-executable" "^PASS"
|
|
|
|
# Check that the mapped files "list" is actually an immutable
|
|
# tuple.
|
|
gdb_test_no_output "python core = gdb.selected_inferior().corefile"
|
|
gdb_test_no_output "python mapped_files = core.mapped_files()"
|
|
gdb_test "python print(type(mapped_files))" \
|
|
"^<class 'tuple'>"
|
|
gdb_test "python mapped_files\[0\] = None" \
|
|
"'tuple' object does not support item assignment"
|
|
gdb_test "python print(mapped_files\[0\] is None)" "^False"
|
|
|
|
# And same for the list of regions for a mapped file.
|
|
gdb_test_no_output "python regions = mapped_files\[0\].regions"
|
|
gdb_test "python print(type(regions))" \
|
|
"^<class 'tuple'>"
|
|
gdb_test "python regions\[0\] = None" \
|
|
"'tuple' object does not support item assignment"
|
|
}
|
|
|
|
# Load a core file. GDB should figure out which file is being debugged.
|
|
# Then use 'start' to run this executable, this will replace the core file
|
|
# target. At least on Linux, this replacement is done without calling
|
|
# target_detach. This test checks that the expected core file changed and
|
|
# inferior exited events are still seen.
|
|
with_test_prefix "start from corefile" {
|
|
if { [gdb_protocol_is_native] } {
|
|
clean_restart_and_load_py_script
|
|
|
|
# Load the core file.
|
|
core_file_cmd -corefile $corefile
|
|
|
|
# Check GDB figured out the executable.
|
|
gdb_test "info inferiors 1" \
|
|
[multi_line \
|
|
"\[^\r\n\]+[string_to_regexp $binfile]\\s*" \
|
|
"\[^\r\n\]+[string_to_regexp $corefile]\\s*"] \
|
|
"check executable was detected correctly"
|
|
|
|
gdb_test "start" \
|
|
"Temporary breakpoint $::decimal, main \\(\\).*" \
|
|
|
|
gdb_test "events corefile_changed check" \
|
|
"Event 1/1, Inferior 1, Corefile None" \
|
|
"expected corefile event has been seen"
|
|
|
|
gdb_test "events exited check" \
|
|
"Event 1/1, Inferior 1, Exit Code None" \
|
|
"expected exited event has been seen"
|
|
|
|
gdb_test_no_output -nopass "events corefile_changed reset"
|
|
gdb_test_no_output -nopass "events exited reset"
|
|
}
|
|
}
|
|
|
|
# Load a core file, then load a different core file to replace it.
|
|
# Check that the events that are emitted are as expected.
|
|
with_test_prefix "load one core file over another" {
|
|
clean_restart_and_load_py_script
|
|
|
|
# Load the core file.
|
|
core_file_cmd -corefile $corefile \
|
|
-prefix "load first corefile"
|
|
|
|
core_file_cmd -corefile $other_corefile \
|
|
-prefix "load second corefile" \
|
|
-replacement
|
|
}
|