| # 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 |
| } |