| # Copyright 2024-2025 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 test creates a function 'foo' that contains an inline function |
| # 'bar'. Both 'foo' and 'bar' are split into two regions. The layout |
| # in memory looks something like this: |
| # |
| # <part-a> <gap> <part-b> |
| # |
| # bar: |------------| |---| |
| # foo: |------------------| |--------| |
| # |
| # We see things like this in "real" code where the parts after the gap |
| # (part-b) are cold paths within the function that have been moved |
| # aside. |
| # |
| # However, in this test program we use 'goto' to jump directly from |
| # the first part of the function (part-a) to the second part (part-b). |
| # |
| # At the time we hit the goto we are inside 'bar'. After the goto we |
| # expect to still be in bar. At one point GDB would get this wrong |
| # and after the jump we would revert back to 'foo'. |
| # |
| # This bug will only trigger if both 'foo' and 'bar' are split, and |
| # both 'foo' and 'bar' must start at the same address for part-b. |
| |
| load_lib dwarf.exp |
| |
| require dwarf2_support |
| |
| # The source program use 'goto *ADDR' which is a GCC extension. |
| require is_c_compiler_gcc |
| |
| standard_testfile |
| |
| # This compiles the source file and starts and stops GDB, so run it |
| # before calling prepare_for_testing otherwise GDB will have exited. |
| get_func_info foo |
| |
| # Make some DWARF for the test. |
| set asm_file [standard_output_file "$::testfile-dw.S"] |
| Dwarf::assemble $asm_file { |
| global srcfile |
| |
| # Create local varibles BAR_SRC_* containing the line number for |
| # the four souce lines of 'foo' and 'bar'. These will be |
| # referenced in the generated DWARF. |
| for { set i 1 } { $i <= 4 } { incr i } { |
| set bar_src_$i [gdb_get_line_number "bar line $i"] |
| set foo_src_$i [gdb_get_line_number "foo line $i"] |
| } |
| |
| # More line numbers needed for the generated DWARF. |
| set foo_decl_line [gdb_get_line_number "foo decl line"] |
| set bar_decl_line [gdb_get_line_number "bar decl line"] |
| |
| # Labels used to link parts of the DWARF together. |
| declare_labels lines_table bar_label ranges_label_bar ranges_label_foo |
| |
| cu { version 4 } { |
| compile_unit { |
| {producer "gcc"} |
| {language @DW_LANG_C} |
| {name ${srcfile}} |
| {comp_dir /tmp} |
| {stmt_list $lines_table DW_FORM_sec_offset} |
| {low_pc 0 addr} |
| } { |
| bar_label: subprogram { |
| {external 1 flag} |
| {name bar} |
| {decl_file 1 data1} |
| {decl_line $bar_decl_line data1} |
| {decl_column 1 data1} |
| {inline 3 data1} |
| } |
| subprogram { |
| {name foo} |
| {decl_file 1 data1} |
| {decl_line $foo_decl_line data1} |
| {decl_column 1 data1} |
| {ranges ${ranges_label_foo} DW_FORM_sec_offset} |
| {external 1 flag} |
| } { |
| inlined_subroutine { |
| {abstract_origin %$bar_label} |
| {call_file 1 data1} |
| {call_line $foo_src_3 data1} |
| {ranges ${ranges_label_bar} DW_FORM_sec_offset} |
| } |
| } |
| } |
| } |
| |
| lines {version 2} lines_table { |
| include_dir "$::srcdir/$::subdir" |
| file_name "$srcfile" 1 |
| program { |
| DW_LNE_set_address "foo_label" |
| line $foo_src_1 |
| DW_LNS_copy |
| DW_LNE_set_address "foo_label_1" |
| line $foo_src_2 |
| DW_LNS_copy |
| DW_LNE_set_address "foo_label_2" |
| line $foo_src_3 |
| DW_LNS_copy |
| DW_LNE_set_address "foo_label_2" |
| line $bar_src_1 |
| DW_LNS_copy |
| DW_LNE_set_address "foo_label_3" |
| line $bar_src_2 |
| DW_LNS_copy |
| |
| DW_LNE_set_address "foo_label_4" |
| DW_LNE_end_sequence |
| |
| DW_LNE_set_address "foo_label_6" |
| line $bar_src_3 |
| DW_LNS_copy |
| DW_LNE_set_address "foo_label_7" |
| line $bar_src_4 |
| DW_LNS_copy |
| |
| DW_LNS_negate_stmt |
| DW_LNE_set_address "foo_label_7" |
| line $foo_src_3 |
| DW_LNS_copy |
| |
| DW_LNS_negate_stmt |
| DW_LNE_set_address "foo_label_8" |
| line $foo_src_4 |
| DW_LNS_copy |
| |
| DW_LNE_set_address $::foo_end |
| DW_LNE_end_sequence |
| } |
| } |
| |
| ranges { } { |
| ranges_label_bar: sequence { |
| range foo_label_2 foo_label_4 |
| range foo_label_6 foo_label_8 |
| } |
| ranges_label_foo: sequence { |
| range foo_label_1 foo_label_4 |
| range foo_label_6 foo_label_9 |
| } |
| } |
| } |
| |
| if {[prepare_for_testing "failed to prepare" "${::testfile}" \ |
| [list $srcfile $asm_file] {nodebug}]} { |
| return -1 |
| } |
| |
| if ![runto_main] { |
| return -1 |
| } |
| |
| gdb_breakpoint bar |
| gdb_continue_to_breakpoint "continue to bar line 1" \ |
| ".*bar line 1\[^\r\n\]+" |
| |
| gdb_test "step" ".*bar line 2\[^\r\n\]+" \ |
| "step to bar line 2" |
| |
| # This is the interesting one. This step will take us over the goto |
| # and into the second range of both 'foo' and 'bar'. As we started |
| # the step in 'bar' GDB should reselect 'bar' after the step. |
| # |
| # If this goes wrong the GDB will claim we are at foo_line_3, which is |
| # the DW_AT_call_line for 'bar'. |
| gdb_test "step" ".*bar line 3\[^\r\n\]+" \ |
| "step to bar line 3" |
| |
| # These following steps should be straight forward, but lets just |
| # check we can step out of 'bar' and back to 'foo', there shouldn't be |
| # anything tricky going on here though. |
| gdb_test "step" ".*bar line 4\[^\r\n\]+" \ |
| "step to bar line 4" |
| |
| gdb_test "step" ".*foo line 4\[^\r\n\]+" \ |
| "step out of bar to foo line 4" |