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