blob: e57fa1affc9070054d44671456f2a245fac1e49a [file]
# 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
}
}