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