--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2024 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/>. */
+
+volatile int global_var = 0;
+
+void
+foo (void) /* foo decl line */
+{
+ /* This label is used to find the start of 'foo' when generating the
+ debug information. Place nothing before it. */
+ asm ("foo_label: .globl foo_label");
+ ++global_var;
+
+ asm ("foo_0: .globl foo_0");
+ ++global_var; /* bar call line */
+
+ asm ("foo_1: .globl foo_1");
+ ++global_var;
+
+ asm ("foo_2: .globl foo_2");
+ ++global_var;
+
+ asm ("foo_3: .globl foo_3");
+ ++global_var;
+
+ asm ("foo_4: .globl foo_4");
+ ++global_var;
+
+ asm ("foo_5: .globl foo_5");
+ ++global_var;
+
+ asm ("foo_6: .globl foo_6");
+ ++global_var;
+
+ asm ("foo_7: .globl foo_7");
+}
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+ foo ();
+}
--- /dev/null
+# Copyright 2024 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
+}