| # Copyright 2024-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/>. |
| |
| # Create an inline function which uses DW_AT_ranges and which has a |
| # DW_AT_entry_pc. |
| # |
| # Within the function's ranges, create an empty sub-range, many |
| # versions of gcc (8.x to at least 14.x) do this, and point the |
| # DW_AT_entry_pc at this empty sub-range (at least 8.x to 9.x did |
| # this). |
| # |
| # Now place a breakpoint on the inline function and run to the |
| # breakpoint, check that GDB reports we are inside the inline |
| # function. |
| # |
| # At one point GDB would use the entry-pc value as the breakpoint |
| # location even though that address is not actually associated with |
| # the inline function. Now GDB will reject the entry-pc value and |
| # select a suitable default entry-pc value instead, one which is |
| # associated with the inline function. |
| |
| load_lib dwarf.exp |
| |
| require dwarf2_support |
| |
| 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 |
| |
| if { [prepare_for_testing "failed to prepare" ${testfile} \ |
| [list ${srcfile}]] } { |
| return |
| } |
| |
| if {![runto_main]} { |
| return |
| } |
| |
| # Some label addresses, needed to match against the output later. |
| foreach foo {foo_1 foo_2 foo_3 foo_4 foo_5 foo_6 foo_7} { |
| set $foo [get_hexadecimal_valueof "&$foo" "UNKNOWN" \ |
| "get address for $foo label"] |
| } |
| |
| set foo_3_end [get_hexadecimal_valueof "&foo_3 + 1" "UNKNOWN" \ |
| "get address for 'foo_3 + 1'"] |
| |
| # Some line numbers needed in the generated DWARF. |
| set foo_decl_line [gdb_get_line_number "foo decl line"] |
| set bar_call_line [gdb_get_line_number "bar call line"] |
| |
| if {[is_ilp32_target]} { |
| set ptr_type "data4" |
| } else { |
| set ptr_type "data8" |
| } |
| |
| # Setup the fake DWARF (see comment at top of this file for more |
| # details). Use DWARF_VERSION (either 4 or 5) to select which type of |
| # ranges are created. Compile the source and generated DWARF and run |
| # the test. |
| # |
| # The ENTRY_LABEL is the label to use as the entry-pc value. The |
| # useful choices are 'foo_3', this label is for an empty sub-range, |
| # 'foo_4', this label is within the blocks low/high addresses, but is |
| # not in any sub-range for the block at all, or 'foo_6', this label is |
| # the end address of a non-empty sub-range, and is also the end |
| # address for the whole block. |
| # |
| # The 'foo_4' case is not something that has been seen generated by |
| # any compiler, but it doesn't hurt to test. |
| # |
| # When WITH_LINE_TABLE is true a small snippet of line table will be |
| # generated which covers some parts of the inlined function. This |
| # makes most sense when being tested with the 'foo_6' label, as that |
| # label is all about handling the end of the inline function case. |
| # |
| # The PRODUCER is the string used to control the DW_AT_producer string |
| # in the CU. When PRODUCER is 'gcc' then a string is used that |
| # represents the gcc compiler. When PRODUCER is 'other' then a string |
| # that will not be interpreted as gcc is used. The gcc compiler will |
| # sometimes generate empty ranges for inline functions (from at least |
| # gcc 8.x through to the currently latest release 14.x), and so GDB |
| # has code in place to convert empty ranges to non-empty. This fix is |
| # not applied to other compilers at this time. |
| |
| proc run_test { producer entry_label dwarf_version with_line_table } { |
| set dw_testname "${::testfile}-${producer}-${dwarf_version}-${entry_label}" |
| |
| if { $with_line_table } { |
| set dw_testname ${dw_testname}-lt |
| } |
| |
| if { $producer eq "other" } { |
| set producer_str "ACME C 1.0.0" |
| } else { |
| set producer_str "GNU C 10.0.0" |
| } |
| |
| set asm_file [standard_output_file "${dw_testname}.S"] |
| Dwarf::assemble $asm_file { |
| upvar dwarf_version dwarf_version |
| upvar entry_label entry_label |
| upvar producer_str producer_str |
| |
| declare_labels lines_table inline_func ranges_label |
| |
| cu { version $dwarf_version } { |
| compile_unit { |
| DW_AT_producer $producer_str |
| DW_AT_language @DW_LANG_C |
| DW_AT_name $::srcfile |
| DW_AT_comp_dir /tmp |
| DW_AT_stmt_list $lines_table DW_FORM_sec_offset |
| DW_AT_low_pc 0 addr |
| } { |
| inline_func: subprogram { |
| DW_AT_name bar |
| DW_AT_inline @DW_INL_declared_inlined |
| } |
| subprogram { |
| DW_AT_name foo |
| DW_AT_decl_file 1 data1 |
| DW_AT_decl_line $::foo_decl_line data1 |
| DW_AT_decl_column 1 data1 |
| DW_AT_low_pc $::foo_start addr |
| DW_AT_high_pc $::foo_len $::ptr_type |
| DW_AT_external 1 flag |
| } { |
| inlined_subroutine { |
| DW_AT_abstract_origin %$inline_func |
| DW_AT_call_file 1 data1 |
| DW_AT_call_line $::bar_call_line data1 |
| DW_AT_entry_pc $entry_label addr |
| DW_AT_ranges ${ranges_label} DW_FORM_sec_offset |
| } |
| } |
| } |
| } |
| |
| lines {version 2} lines_table { |
| include_dir "$::srcdir/$::subdir" |
| file_name "$::srcfile" 1 |
| |
| upvar with_line_table with_line_table |
| |
| if {$with_line_table} { |
| program { |
| DW_LNE_set_address foo_label |
| line [expr {$::bar_call_line - 2}] |
| DW_LNS_copy |
| |
| DW_LNE_set_address foo_0 |
| line [expr {$::bar_call_line - 1}] |
| DW_LNS_copy |
| |
| DW_LNE_set_address foo_1 |
| line 1 |
| DW_LNS_copy |
| |
| DW_LNE_set_address foo_2 |
| line 2 |
| DW_LNS_copy |
| |
| DW_LNE_set_address foo_3 |
| line 3 |
| DW_LNS_copy |
| |
| DW_LNE_set_address foo_6 |
| line 10 |
| DW_LNS_copy |
| |
| DW_LNE_set_address foo_6 |
| line 10 |
| DW_LNS_negate_stmt |
| DW_LNS_copy |
| |
| DW_LNE_set_address foo_6 |
| line $::bar_call_line |
| DW_LNS_copy |
| |
| DW_LNE_set_address foo_7 |
| DW_LNS_negate_stmt |
| line [expr $::bar_call_line + 1] |
| DW_LNS_copy |
| |
| DW_LNE_set_address "$::foo_start + $::foo_len" |
| line [expr $::bar_call_line + 2] |
| DW_LNE_end_sequence |
| } |
| } |
| } |
| |
| if { $dwarf_version == 5 } { |
| rnglists {} { |
| table {} { |
| ranges_label: list_ { |
| start_end foo_3 foo_3 |
| start_end foo_1 foo_2 |
| start_end foo_5 foo_6 |
| } |
| } |
| } |
| } else { |
| ranges { } { |
| ranges_label: sequence { |
| range foo_3 foo_3 |
| range foo_1 foo_2 |
| range foo_5 foo_6 |
| } |
| } |
| } |
| } |
| |
| if {[prepare_for_testing "failed to prepare" "${dw_testname}" \ |
| [list $::srcfile $asm_file] {nodebug}]} { |
| return false |
| } |
| |
| if {![runto_main]} { |
| return false |
| } |
| |
| if { $producer eq "gcc" } { |
| set entry_pc $::foo_3 |
| set empty_range_re "\r\n $::foo_3\\.\\.$::foo_3_end" |
| set line_num 3 |
| } else { |
| set entry_pc $::foo_1 |
| set empty_range_re "" |
| set line_num 1 |
| } |
| |
| # Place a breakpoint on `bar` and run to the breakpoint. Use |
| # gdb_test as we want full pattern matching against the stop |
| # location. |
| # |
| # When we have a line table GDB will find a line for the |
| # breakpoint location, so the output will be different. |
| if { $with_line_table } { |
| set re \ |
| [multi_line \ |
| "Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:$line_num" \ |
| "$line_num\\s+\[^\r\n\]+"] |
| } else { |
| set re "Breakpoint $::decimal, $::hex in bar \\(\\)" |
| } |
| gdb_breakpoint bar |
| gdb_test "continue" $re |
| |
| # Inspect the block structure of `bar` at this location. We are |
| # expecting that the empty range (that contained the entry-pc) has |
| # been removed from the block, and that the entry-pc has its |
| # default value. |
| gdb_test "maint info blocks" \ |
| [multi_line \ |
| "\\\[\\(block \\*\\) $::hex\\\] $::foo_1\\.\\.$::foo_6" \ |
| " entry pc: $entry_pc" \ |
| " inline function: bar" \ |
| " symbol count: $::decimal" \ |
| " address ranges:$empty_range_re" \ |
| " $::foo_1\\.\\.$::foo_2" \ |
| " $::foo_5\\.\\.$::foo_6"] |
| } |
| |
| foreach_with_prefix producer { other gcc } { |
| foreach_with_prefix dwarf_version { 4 5 } { |
| # Test various labels without any line table present. |
| foreach_with_prefix entry_label { foo_3 foo_4 foo_2 foo_6 } { |
| run_test $producer $entry_label $dwarf_version false |
| } |
| |
| # Now test what happens if we use the end address of the block, |
| # but also supply a line table. Does GDB do anything different? |
| run_test $producer foo_6 $dwarf_version true |
| } |
| } |