| # 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/>. |
| |
| # 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 last 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} { |
| set $foo [get_hexadecimal_valueof "&$foo" "UNKNOWN" \ |
| "get address for $foo label"] |
| } |
| |
| # 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. |
| |
| proc run_test { entry_label dwarf_version with_line_table } { |
| set dw_testname "${::testfile}-${dwarf_version}-${entry_label}" |
| |
| if { $with_line_table } { |
| set dw_testname ${dw_testname}-lt |
| } |
| |
| set asm_file [standard_output_file "${dw_testname}.S"] |
| Dwarf::assemble $asm_file { |
| upvar dwarf_version dwarf_version |
| upvar entry_label entry_label |
| |
| declare_labels lines_table inline_func ranges_label |
| |
| cu { version $dwarf_version } { |
| 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} |
| } { |
| inline_func: subprogram { |
| {name bar} |
| {inline @DW_INL_declared_inlined} |
| } |
| subprogram { |
| {name foo} |
| {decl_file 1 data1} |
| {decl_line $::foo_decl_line data1} |
| {decl_column 1 data1} |
| {low_pc $::foo_start addr} |
| {high_pc $::foo_len $::ptr_type} |
| {external 1 flag} |
| } { |
| inlined_subroutine { |
| {abstract_origin %$inline_func} |
| {call_file 1 data1} |
| {call_line $::bar_call_line data1} |
| {entry_pc $entry_label addr} |
| {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_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_start + $::foo_len" |
| 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 |
| } |
| |
| # 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:1" \ |
| "1\\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: $::foo_1" \ |
| " inline function: bar" \ |
| " symbol count: $::decimal" \ |
| " address ranges:" \ |
| " $::foo_1\\.\\.$::foo_2" \ |
| " $::foo_5\\.\\.$::foo_6"] |
| } |
| |
| 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 $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 foo_6 $dwarf_version true |
| } |