}
}
+/* Return true if an empty range associated with an entry of type TAG in
+ CU should be "fixed", that is, converted to a single byte, non-empty
+ range. */
+
+static bool
+dwarf_fixup_empty_range (struct dwarf2_cu *cu, dwarf_tag tag)
+{
+ return (tag == DW_TAG_inlined_subroutine
+ && producer_is_gcc (cu->producer, nullptr, nullptr));
+}
+
/* Call CALLBACK from DW_AT_ranges attribute value OFFSET
reading .debug_rnglists.
Callback's type should be:
/* Empty range entries have no effect. */
if (range_beginning == range_end)
- continue;
+ {
+ if (dwarf_fixup_empty_range (cu, tag))
+ range_end = (unrelocated_addr) ((CORE_ADDR) range_end + 1);
+ else
+ continue;
+ }
/* Only DW_RLE_offset_pair needs the base address added. */
if (rlet == DW_RLE_offset_pair)
/* Empty range entries have no effect. */
if (range_beginning == range_end)
- continue;
+ {
+ if (dwarf_fixup_empty_range (cu, tag))
+ range_end = (unrelocated_addr) ((CORE_ADDR) range_end + 1);
+ else
+ continue;
+ }
range_beginning = (unrelocated_addr) ((CORE_ADDR) range_beginning
+ (CORE_ADDR) *base);
if (ret == PC_BOUNDS_NOT_PRESENT || ret == PC_BOUNDS_INVALID)
return ret;
- /* partial_die_info::read has also the strict LOW < HIGH requirement. */
+ /* These LOW and HIGH values will be used to create a block. A block's
+ high address is the first address after the block's address range, so
+ if 'high <= low' then the block has no code associated with it. */
if (high <= low)
- return PC_BOUNDS_INVALID;
+ {
+ /* In some cases though, when the blocks LOW / HIGH were defined with
+ the DW_AT_low_pc and DW_AT_high_pc, we see some compilers create
+ an empty block when we can provide a better debug experience by
+ having a non-empty block. We do this by "fixing" the block to be
+ a single byte in length. See dwarf_fixup_empty_range for when
+ this fixup is performed. */
+ if (high == low
+ && ret == PC_BOUNDS_HIGH_LOW
+ && dwarf_fixup_empty_range (cu, die->tag))
+ high = (unrelocated_addr) (((ULONGEST) low) + 1);
+ else
+ return PC_BOUNDS_INVALID;
+ }
/* When using the GNU linker, .gnu.linkonce. sections are used to
eliminate duplicate copies of functions and vtables and such.
CORE_ADDR low = per_objfile->relocate (unrel_low);
CORE_ADDR high = per_objfile->relocate (unrel_high);
- cu->get_builder ()->record_block_range (block, low, high - 1);
+
+ /* Blocks where 'high < low' should be rejected earlier in the
+ process, e.g. see dwarf2_get_pc_bounds. */
+ gdb_assert (high >= low);
+
+ /* The value of HIGH is the first address past the end, but
+ GDB stores ranges with the high value as last inclusive
+ address, so in most cases we need to decrement HIGH here.
+
+ Blocks where 'high == low' represent an empty block (i.e. a
+ block with no associated code).
+
+ When 'high == low' and dwarf_fixup_empty_range returns true we
+ "fix" the empty range into a single byte range, which we can
+ do by leaving HIGH untouched. Otherwise we decrement HIGH,
+ which might result in 'high < low'. */
+ if (high > low || !dwarf_fixup_empty_range (cu, die->tag))
+ high -= 1;
+
+ /* If the above decrement resulted in 'high < low' then this
+ represents an empty range. There's little point storing this
+ in GDB's internal structures, it's just more to search
+ through, and it will never match any address. */
+ if (high >= low)
+ cu->get_builder ()->record_block_range (block, low, high);
}
}
}
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
- set line1 {\t\{}
- set line2 {\t if \(t != NULL}
- gdb_test_multiple "step" "step into get_alias_set" {
- -re -wrap $line1 {
- gdb_test "next" $line2 $gdb_test_name
- }
- -re -wrap $line2 {
- pass $gdb_test_name
- }
- }
+ gdb_test "step" \
+ [multi_line \
+ "get_alias_set \\(t=\[^\r\n\]+\\) at \[^\r\n\]+:$::decimal" \
+ "$::decimal\\s+if \\(t != NULL\\s*"] \
+ "step into get_alias_set"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 1"
+ gdb_test "next" ".*TREE_TYPE.*" "next step 1"
+ gdb_test "bt" "#0\\s+get_alias_set\[^\r\n\]*${srcfile}:.*" \
+ "not in inline 2"
# It's possible that this first failure (when not using a header
# file) is GCC's fault, though the remaining failures would best
# having location view support, so for now it is tagged as such.
set have_kfail [expr [test_compiler_info gcc*] && !$use_header]
- set ok 1
- gdb_test_multiple "next" "next step 1" {
- -re -wrap "if \\(t->x != i\\)" {
- set ok 0
- send_gdb "next\n"
- exp_continue
- }
- -re -wrap ".*TREE_TYPE.* != 1" {
- if { $ok } {
- pass $gdb_test_name
- } else {
- if { $have_kfail } {
- setup_kfail "*-*-*" symtab/25507
- }
- fail $gdb_test_name
- }
- }
- }
- gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
- "not in inline 2"
-
set ok 1
gdb_test_multiple "next" "next step 2" {
-re -wrap "return x;" {
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
"not in inline 5"
- if {!$use_header} {
- # With the debug from GCC 10.x (and earlier) GDB is currently
- # unable to successfully complete the following tests when we
- # are not using a header file.
- kfail symtab/25507 "stepping tests"
- return
- }
-
clean_restart ${executable}
if ![runto_main] {
}
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main pass 2"
- gdb_test "step" ".*" "step into get_alias_set pass 2"
+ gdb_test "step" \
+ [multi_line \
+ "get_alias_set \\(t=\[^\r\n\]+\\) at \[^\r\n\]+:$::decimal" \
+ "$::decimal\\s+if \\(t != NULL\\s*"] \
+ "step into get_alias_set, pass 2"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"in get_alias_set pass 2"
gdb_test "step" ".*TREE_TYPE.*" "step 1"
gdb_test "step" ".*if \\(t->x != i\\).*" "step 2"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 1 pass 2"
- gdb_test "step" ".*TREE_TYPE.*" "step 3"
+ gdb_test "step" ".*return x.*" "step 3"
+ gdb_test "step" ".*TREE_TYPE.*" "step 4"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 2 pass 2"
- gdb_test "step" ".*if \\(t->x != i\\).*" "step 4"
+ gdb_test "step" ".*if \\(t->x != i\\).*" "step 5"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 2 pass 2"
- gdb_test "step" ".*TREE_TYPE.*" "step 5"
+ gdb_test "step" ".*return x.*" "step 6"
+ gdb_test "step" ".*TREE_TYPE.*" "step 7"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 3 pass 2"
- gdb_test "step" ".*if \\(t->x != i\\).*" "step 6"
+ gdb_test "step" ".*if \\(t->x != i\\).*" "step 8"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 3 pass 2"
- gdb_test "step" "return 0.*" "step 7"
+ gdb_test "step" "return x.*" "step 9"
+ gdb_test "step" "return 0.*" "step 10"
gdb_test "bt" \
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
"not in inline 4 pass 2"
+
+ clean_restart ${executable}
+
+ if ![runto_main] {
+ return
+ }
+
+ gdb_test "bt" "#0\\s+main.*" "in main pass 3"
+ gdb_test "step" \
+ [multi_line \
+ "get_alias_set \\(t=\[^\r\n\]+\\) at \[^\r\n\]+:$::decimal" \
+ "$::decimal\\s+if \\(t != NULL\\s*"] \
+ "step into get_alias_set, pass 3"
+ gdb_test "bt" "#0\\s+get_alias_set\[^\r\n\]*${srcfile}:.*" \
+ "in get_alias_set pass 3"
+ gdb_test "step" ".*TREE_TYPE.*" "step 1 pass 3"
+ gdb_test "bt" "#0\\s+get_alias_set\[^\r\n\]*${srcfile}:.*" \
+ "not in inline 1 pass 3"
+ gdb_test "step" ".*if \\(t->x != i\\).*" "step 2 pass 3"
+ gdb_test "bt" "#0\\s+\[^\r\n\]*tree_check\[^\r\n\]*${hdrfile}:.*" \
+ "in inline 1 pass 3"
+ gdb_test_multiple "p t->x = 2" "change value pass 3" {
+ -re ".*value has been optimized out.*$::gdb_prompt $" {
+ gdb_test "p xx.x = 2" ".* = 2.*" $gdb_test_name
+ }
+ -re ".* = 2.*$::gdb_prompt $" {
+ pass $gdb_test_name
+ }
+ }
+ gdb_test "step" ".*abort.*" "step 3, pass 3"
+ gdb_test "bt" "#0\\s+\[^\r\n\]*tree_check\[^\r\n\]*${hdrfile}:.*" \
+ "abort from inline 1 pass 3"
}
}
--- /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;
+
+int
+main (void)
+{ /* main decl line */
+ asm ("main_label: .globl main_label");
+ ++global_var;
+
+ asm ("main_0: .globl main_0");
+ ++global_var;
+
+ asm ("main_1: .globl main_1");
+ ++global_var; /* foo call line */
+
+ asm ("main_2: .globl main_2");
+ ++global_var;
+
+ asm ("main_3: .globl main_3");
+ ++global_var;
+
+ return 0;
+}
--- /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/>.
+
+# Define an inline function `foo` within the function `main`. The
+# function `foo` uses DW_AT_low_pc and DW_AT_high_pc to define its
+# range, except that DW_AT_high_pc is the constant 0.
+#
+# This should indicate that there is no code associated with `foo`,
+# however, with gcc versions at least between 8.x and 14.x (latest at
+# the time of writing this comment), it is observed that when these
+# empty inline functions are created, if GDB stops at the address
+# given in DW_AT_low_pc, then locals associated with the inline
+# function can usually be read.
+#
+# At the very least, stopping at the location of the inline function
+# means that the user can place a breakpoint on the inline function
+# and have GDB stop in a suitable location, that alone is helpful.
+#
+# This test defines an inline function, places a breakpoint, and then
+# runs and expects GDB to stop, and report the stop as being inside
+# the inline function.
+#
+# We then check that the next outer frame is `main` as expected, and
+# that the block for `foo` has been extended to a single byte, which
+# is how GDB gives the previously empty block some range.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+standard_testfile .c .S
+
+# Lines we reference in the generated DWARF.
+set main_decl_line [gdb_get_line_number "main decl line"]
+set foo_call_line [gdb_get_line_number "foo call line"]
+
+get_func_info main
+
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+ upvar entry_label entry_label
+
+ declare_labels lines_table inline_func
+
+ cu { } {
+ compile_unit {
+ {producer "GNU C 14.1.0"}
+ {language @DW_LANG_C}
+ {name $::srcfile}
+ {comp_dir /tmp}
+ {low_pc 0 addr}
+ {DW_AT_stmt_list $lines_table DW_FORM_sec_offset}
+ } {
+ inline_func: subprogram {
+ {name foo}
+ {inline @DW_INL_declared_inlined}
+ }
+ subprogram {
+ {name main}
+ {decl_file 1 data1}
+ {decl_line $::main_decl_line data1}
+ {decl_column 1 data1}
+ {low_pc $::main_start addr}
+ {high_pc $::main_len data4}
+ {external 1 flag}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$inline_func}
+ {call_file 1 data1}
+ {call_line $::foo_call_line data1}
+ {low_pc main_1 addr}
+ {high_pc 0 data4}
+ }
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$::srcfile" 1
+ }
+}
+
+if {[prepare_for_testing "failed to prepare" $testfile \
+ [list $srcfile $asm_file] {nodebug}]} {
+ return
+}
+
+if {![runto_main]} {
+ return
+}
+
+gdb_breakpoint foo
+gdb_test "continue" \
+ "Breakpoint $decimal, $hex in foo \\(\\)" \
+ "continue to b/p in foo"
+
+set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \
+ "get address of foo start"]
+set foo_end [get_hexadecimal_valueof "&main_1 + 1" "*UNKNOWN*" \
+ "get address of foo end"]
+
+gdb_test "maintenance info blocks" \
+ [multi_line \
+ "\\\[\\(block \\*\\) $hex\\\] $foo_start\\.\\.$foo_end" \
+ " entry pc: $foo_start" \
+ " inline function: foo" \
+ " symbol count: $decimal" \
+ " is contiguous"] \
+ "block for foo has some content"
+
+gdb_test "frame 1" \
+ [multi_line \
+ "#1 main \\(\\) at \[^\r\n\]+/$srcfile:$foo_call_line" \
+ "$foo_call_line\\s+\[^\r\n\]+/\\* foo call line \\*/"] \
+ "frame 1 is for main"
--- /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;
+
+int
+main (void)
+{ /* main decl line */
+ asm ("main_label: .globl main_label");
+ ++global_var;
+
+ asm ("main_0: .globl main_0");
+ ++global_var;
+
+ asm ("main_1: .globl main_1");
+ ++global_var; /* foo call line */
+
+ asm ("main_2: .globl main_2");
+ ++global_var;
+
+ asm ("main_3: .globl main_3");
+ ++global_var;
+
+ asm ("main_4: .globl main_4");
+ ++global_var;
+
+ asm ("main_5: .globl main_5");
+ ++global_var;
+
+ asm ("main_6: .globl main_6");
+ ++global_var;
+
+ asm ("main_7: .globl main_7");
+ ++global_var;
+
+ asm ("main_8: .globl main_9");
+ ++global_var;
+
+ return 0;
+}
--- /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/>.
+
+# Define an inline function `foo` within the function `main`. The
+# function `foo` uses DW_AT_ranges to define its ranges. One of the
+# sub-ranges for foo will be empty.
+#
+# An empty sub-rnage should indicate that there is no code associated
+# with `foo` at that address, however, with gcc versions at least
+# between 8.x and 14.x (latest at the time of writing this comment),
+# it is observed that when these empty sub-ranges are created for an
+# inline function, if GDB treats the sub-range as non-empty, and stops
+# at that location, then this generally gives a better debug
+# experience. It is often still possible to read local variables at
+# that address.
+#
+# This function defines an inline function, places a breakpoint on its
+# entry-pc, and then runs and expects GDB to stop, and report the stop
+# as being inside the inline function.
+#
+# We then check that the next outer frame is `main` as expected, and
+# that the block for `foo` has the expected sub-ranges.
+#
+# We compile a variety of different configurations, broadly there are
+# two variables, the location of the empty sub-range, and whether the
+# entry-pc points at the empty sub-range or not.
+#
+# The the empty sub-range location, the empty sub-range can be the
+# sub-range at the lowest address, highest address, or can be
+# somewhere between a blocks low and high addresses.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+standard_testfile .c .S
+
+# Lines we reference in the generated DWARF.
+set main_decl_line [gdb_get_line_number "main decl line"]
+set foo_call_line [gdb_get_line_number "foo call line"]
+
+get_func_info main
+
+# Compile the source file and load the executable into GDB so we can
+# extract some addresses needed for creating the DWARF.
+if { [prepare_for_testing "failed to prepare" ${testfile} \
+ [list ${srcfile}]] } {
+ return -1
+}
+
+if {![runto_main]} {
+ return -1
+}
+
+# Some addresses that we need when generating the DWARF.
+for { set i 0 } { $i < 9 } { incr i } {
+ set main_$i [get_hexadecimal_valueof "&main_$i" "UNKNOWN" \
+ "get address for main_$i"]
+}
+
+# Create the DWARF assembler file into ASM_FILE. Using DWARF_VERSION
+# to define which style of ranges to create. FUNC_RANGES is a list of
+# 6 entries, each of which is an address, used to create the ranges
+# for the inline function DIE. The ENTRY_PC is also an address and is
+# used for the DW_AT_entry_pc of the inlined function.
+proc write_asm_file { asm_file dwarf_version func_ranges entry_pc } {
+ Dwarf::assemble $asm_file {
+ upvar entry_label entry_label
+ upvar dwarf_version dwarf_version
+ upvar func_ranges func_ranges
+ upvar entry_pc entry_pc
+
+ declare_labels lines_table inline_func ranges_label
+
+ cu { version $dwarf_version } {
+ compile_unit {
+ {producer "GNU C 14.1.0"}
+ {language @DW_LANG_C}
+ {name $::srcfile}
+ {comp_dir /tmp}
+ {low_pc 0 addr}
+ {DW_AT_stmt_list $lines_table DW_FORM_sec_offset}
+ } {
+ inline_func: subprogram {
+ {name foo}
+ {inline @DW_INL_declared_inlined}
+ }
+ subprogram {
+ {name main}
+ {decl_file 1 data1}
+ {decl_line $::main_decl_line data1}
+ {decl_column 1 data1}
+ {low_pc $::main_start addr}
+ {high_pc $::main_len data4}
+ {external 1 flag}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$inline_func}
+ {call_file 1 data1}
+ {call_line $::foo_call_line data1}
+ {entry_pc $entry_pc addr}
+ {ranges $ranges_label DW_FORM_sec_offset}
+ }
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$::srcfile" 1
+ }
+
+ if { $dwarf_version == 5 } {
+ rnglists {} {
+ table {} {
+ ranges_label: list_ {
+ start_end [lindex $func_ranges 0] [lindex $func_ranges 1]
+ start_end [lindex $func_ranges 2] [lindex $func_ranges 3]
+ start_end [lindex $func_ranges 4] [lindex $func_ranges 5]
+ }
+ }
+ }
+ } else {
+ ranges { } {
+ ranges_label: sequence {
+ range [lindex $func_ranges 0] [lindex $func_ranges 1]
+ range [lindex $func_ranges 2] [lindex $func_ranges 3]
+ range [lindex $func_ranges 4] [lindex $func_ranges 5]
+ }
+ }
+ }
+ }
+}
+
+# Gobal used to give each generated binary a unique name.
+set test_id 0
+
+proc run_test { dwarf_version empty_loc entry_pc_type } {
+ incr ::test_id
+
+ set this_testfile $::testfile-$::test_id
+
+ set asm_file [standard_output_file $this_testfile.S]
+
+ if { $empty_loc eq "start" } {
+ set ranges [list \
+ $::main_1 $::main_1 \
+ $::main_3 $::main_4 \
+ $::main_6 $::main_7]
+ set entry_pc_choices [list $::main_1 $::main_3]
+ } elseif { $empty_loc eq "middle" } {
+ set ranges [list \
+ $::main_1 $::main_2 \
+ $::main_4 $::main_4 \
+ $::main_6 $::main_7]
+ set entry_pc_choices [list $::main_4 $::main_1]
+ } elseif { $empty_loc eq "end" } {
+ set ranges [list \
+ $::main_1 $::main_2 \
+ $::main_4 $::main_5 \
+ $::main_7 $::main_7]
+ set entry_pc_choices [list $::main_7 $::main_1]
+ } else {
+ error "unknown location for empty range '$empty_loc'"
+ }
+
+ if { $entry_pc_type eq "empty" } {
+ set entry_pc [lindex $entry_pc_choices 0]
+ } elseif { $entry_pc_type eq "non_empty" } {
+ set entry_pc [lindex $entry_pc_choices 1]
+ } else {
+ error "unknown entry-pc type '$entry_pc_type'"
+ }
+
+ write_asm_file $asm_file $dwarf_version $ranges $entry_pc
+
+ if {[prepare_for_testing "failed to prepare" $this_testfile \
+ [list $::srcfile $asm_file] {nodebug}]} {
+ return
+ }
+
+ if {![runto_main]} {
+ return
+ }
+
+ # Continue until we stop in 'foo'.
+ gdb_breakpoint foo
+ gdb_test "continue" \
+ "Breakpoint $::decimal, $::hex in foo \\(\\)" \
+ "continue to b/p in foo"
+
+ # Check we stopped at the entry-pc.
+ set pc [get_hexadecimal_valueof "\$pc" "*UNKNOWN*" \
+ "get \$pc at breakpoint"]
+ gdb_assert { $pc == $entry_pc } "stopped at entry-pc"
+
+ # The block's expected overall low/high addresses.
+ set block_start [lindex $ranges 0]
+ set block_end [lindex $ranges 5]
+
+ # Setup variables r{0,1,2}s, r{0,1,2}e, to represent ranges start
+ # and end addresses. These are extracted from the RANGES
+ # variable. However, RANGES includes the empty ranges, so spot
+ # the empty ranges and update the end address as GDB does.
+ #
+ # Also, if the empty range is at the end of the block, then the
+ # block's overall end address also needs adjusting.
+ for { set i 0 } { $i < 3 } { incr i } {
+ set start [lindex $ranges [expr $i * 2]]
+ set end [lindex $ranges [expr $i * 2 + 1]]
+ if { $start == $end } {
+ set end [format "0x%x" [expr $end + 1]]
+ }
+ if { $block_end == $start } {
+ set block_end $end
+ }
+ set r${i}s $start
+ set r${i}e $end
+ }
+
+ # Check the block 'foo' has the expected ranges.
+ gdb_test "maintenance info blocks" \
+ [multi_line \
+ "\\\[\\(block \\*\\) $::hex\\\] $block_start\\.\\.$block_end" \
+ " entry pc: $entry_pc" \
+ " inline function: foo" \
+ " symbol count: $::decimal" \
+ " address ranges:" \
+ " $r0s\\.\\.$r0e" \
+ " $r1s\\.\\.$r1e" \
+ " $r2s\\.\\.$r2e"] \
+ "block for foo has some content"
+
+ # Check the outer frame is 'main' as expected.
+ gdb_test "frame 1" \
+ [multi_line \
+ "#1 main \\(\\) at \[^\r\n\]+/$::srcfile:$::foo_call_line" \
+ "$::foo_call_line\\s+\[^\r\n\]+/\\* foo call line \\*/"] \
+ "frame 1 is for main"
+}
+
+foreach_with_prefix dwarf_version { 4 5 } {
+ foreach_with_prefix empty_loc { start middle end } {
+ foreach_with_prefix entry_pc_type { empty non_empty } {
+ run_test $dwarf_version $empty_loc $entry_pc_type
+ }
+ }
+}
"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"]
# 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}"
+#
+# 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 {
- {producer "gcc"}
+ {producer $producer_str}
{language @DW_LANG_C}
{name $::srcfile}
{comp_dir /tmp}
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
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.
if { $with_line_table } {
set re \
[multi_line \
- "Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:1" \
- "1\\s+\[^\r\n\]+"]
+ "Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:$line_num" \
+ "$line_num\\s+\[^\r\n\]+"]
} else {
set re "Breakpoint $::decimal, $::hex in bar \\(\\)"
}
gdb_test "maint info blocks" \
[multi_line \
"\\\[\\(block \\*\\) $::hex\\\] $::foo_1\\.\\.$::foo_6" \
- " entry pc: $::foo_1" \
+ " entry pc: $entry_pc" \
" inline function: bar" \
" symbol count: $::decimal" \
- " address ranges:" \
+ " address ranges:$empty_range_re" \
" $::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
- }
+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 foo_6 $dwarf_version true
+ # 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
+ }
}
--- /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/>. */
+
+#include "attributes.h"
+
+/* A global to do some work on. This being volatile is important. Without
+ this the compiler might optimise the whole program away. */
+volatile int global = 0;
+
+__attribute__((noinline)) ATTRIBUTE_NOCLONE void
+breakpt ()
+{
+ /* Some filler work. */
+ global++;
+}
+
+struct MyClass;
+
+struct ptr
+{
+ /* The following line is a single line to aid matching in the test
+ script. Sometimes the DWARF will point GDB at the '{' and sometimes
+ at the body of the function. We don't really care for this test, so
+ placing everything on one line removes this variability. */
+ MyClass* get_myclass () { return t; }
+
+ MyClass* t;
+};
+
+struct MyClass
+{
+ void call();
+};
+
+void
+MyClass::call ()
+{
+ breakpt (); /* Final breakpoint. */
+}
+
+static void
+intermediate (ptr p)
+{
+ p.get_myclass ()->call ();
+}
+
+int
+main ()
+{
+ intermediate (ptr {new MyClass});
+}
--- /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/>.
+
+standard_testfile .cc
+
+require {expr ![test_compiler_info gcc* c++] \
+ || [supports_statement_frontiers] }
+
+set options {c++ debug optimize=-Og}
+lappend_include_file options $srcdir/lib/attributes.h
+if {[supports_statement_frontiers]} {
+ lappend options additional_flags=-gstatement-frontiers
+}
+
+# Some line numbers we need for the test.
+set get_myclass_line [gdb_get_line_number "MyClass* get_myclass ()"]
+set call_get_line [gdb_get_line_number "p.get_myclass ()"]
+set final_bp_line [gdb_get_line_number "Final breakpoint"]
+
+# Build the test executable adding "-OPT_LEVEL" to the compilation
+# flags. The break on the small function which is likely to have been
+# inlined, check we stop where we expect, and that the backtrace looks
+# correct.
+#
+# Then return from the inline function and call to another function,
+# check the backtrace from this second function also looks good,
+# specifically, we're checking that the backtrace doesn't incorrectly
+# place frame #1 on the line for the inline function.
+proc run_test { opt_level } {
+
+ set opts $::options
+ lappend opts "additional_flags=-${opt_level}"
+
+ if { [prepare_for_testing "failed to prepare" "$::testfile-$opt_level" \
+ $::srcfile $opts] } {
+ return
+ }
+
+ if { ![runto_main] } {
+ return
+ }
+
+ gdb_test "bt" "#0\\s+main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal" \
+ "backtrace in main"
+
+ # Break at the empty inline function ptr::get_myclass.
+ gdb_breakpoint get_myclass
+ gdb_continue_to_breakpoint "continue to get_myclass" \
+ [multi_line \
+ ".*/$::srcfile:$::get_myclass_line" \
+ "$::get_myclass_line\\s+MyClass\\* get_myclass \\(\\) \[^\r\n\]+"]
+
+ # Backtrace.
+ gdb_test "bt" \
+ [multi_line \
+ "#0\\s+ptr::get_myclass\[^\r\n\]+/$::srcfile:$::get_myclass_line" \
+ "#1\\s+intermediate\[^\r\n\]+/$::srcfile:$::call_get_line" \
+ "#2\\s+\[^\r\n\]+main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal"] \
+ "at get_myclass"
+
+ # Print a class member variable, this should be in scope, but is often
+ # reported as optimised out.
+ gdb_test "p t" \
+ "(?:\\\$1 = \\(MyClass \\*\\) $::hex|value has been optimized out)" \
+ "print ptr::t"
+
+ gdb_breakpoint $::srcfile:$::final_bp_line
+ gdb_continue_to_breakpoint "continue to final breakpoint"
+
+ # Backtrace. Check frame #1 looks right. Bug gdb/25987 would report
+ # frame #1 as being the correct function, but would report the line for
+ # ptr::get_myclass(), which is not correct.
+ setup_xfail *-*-* gdb/25987
+ gdb_test "bt" \
+ [multi_line \
+ "#0\\s+MyClass::call\[^\r\n\]+/$::srcfile:$::final_bp_line" \
+ "#1\\s+\[^\r\n\]+ intermediate\[^\r\n\]+/$::srcfile:$::call_get_line" \
+ "#2\\s+\[^\r\n\]+ main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal"] \
+ "at call"
+}
+
+foreach_with_prefix opt_level { Og O0 O1 O2 } {
+ run_test ${opt_level}
+}
--- /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/>. */
+
+#include "attributes.h"
+
+static int
+test0 (void)
+{
+ asm (""); /* First line of test0. */
+ return 1; /* Second line of test0. */
+}
+
+int __attribute__((noinline)) ATTRIBUTE_NOCLONE
+test1 (int x)
+{
+ asm ("");
+ return x + 1; /* Second line of test1. */
+}
+
+int
+main (void)
+{
+ test1 (test0 ()); /* First line of main. */
+ test1 (test0 ()); /* Second line of main. */
+ return 0; /* Third line of main. */
+}
--- /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/>.
+
+standard_testfile
+
+require {expr ![test_compiler_info gcc* c++] \
+ || [supports_statement_frontiers] }
+
+set options {debug nowarnings optimize=-O2}
+lappend_include_file options $srcdir/lib/attributes.h
+if {[supports_statement_frontiers]} {
+ lappend options additional_flags=-gstatement-frontiers
+}
+
+# Some line numbers we need.
+set lineno_main_1 [gdb_get_line_number "First line of main"]
+set lineno_main_2 [gdb_get_line_number "Second line of main"]
+set lineno_main_3 [gdb_get_line_number "Third line of main"]
+set lineno_test0_1 [gdb_get_line_number "First line of test0"]
+set lineno_test0_2 [gdb_get_line_number "Second line of test0"]
+set lineno_test1_2 [gdb_get_line_number "Second line of test1"]
+
+# ...
+proc run_test { opt_level } {
+
+ set opts $::options
+ lappend opts "additional_flags=-${opt_level}"
+
+ if { [prepare_for_testing "failed to prepare" "$::testfile-$opt_level" \
+ $::srcfile $opts] } {
+ return
+ }
+
+ if { ![runto_main] } {
+ return
+ }
+
+ gdb_test "frame 0" \
+ [multi_line \
+ "#0\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
+ "$::lineno_main_1\\s+\[^\r\n\]+"] \
+ "frame 0 while in main"
+
+ gdb_test_multiple "step" "step into test0" {
+ -re -wrap ".*test0.*$::srcfile:$::lineno_test0_1.*" {
+ gdb_test "step" ".*line $::lineno_test0_2.*" $gdb_test_name
+ }
+ -re -wrap ".*test0.*$::srcfile:$::lineno_test0_2.*" {
+ pass $gdb_test_name
+ }
+ }
+
+ gdb_test "frame 1" \
+ [multi_line \
+ "#1\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
+ "$::lineno_main_1\\s+\[^\r\n\]+"] \
+ "inspect frame 1, main"
+
+ # Step into test1() function.
+ gdb_test "step" \
+ [multi_line \
+ "test1 \\(\[^)\]+\\) at \[^\r\n\]+/$::srcfile:$::lineno_test1_2" \
+ "$::lineno_test1_2\\s+\[^\r\n\]+"] \
+ "step into test1"
+
+ # Check frame #1 looks right. Bug gdb/25987 would report frame #1 as
+ # being the correct function, but would report the line for a nearby
+ # inlined function.
+ setup_xfail *-*-* gdb/25987
+ gdb_test "frame 1" \
+ [multi_line \
+ "#1\\s+\[^\r\n\]*main \\(\\) \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
+ "$::lineno_main_1\\s+\[^\r\n\]+"] \
+ "inspect frame 1 again, still main"
+
+ # Step from the last line of test1 back into main.
+ gdb_test "step" \
+ [multi_line \
+ "main \\(\\) at \[^\r\n\]+/$::srcfile:$::lineno_main_2" \
+ "$::lineno_main_2\\s+\[^\r\n\]+"] \
+ "step back to main"
+
+ # Use next to step to the last line of main. This skips over the inline
+ # call to test0, and the non-inline call to test1.
+ gdb_test "next" \
+ "$::lineno_main_3\\s+return 0;\\s+\[^\r\n\]+" \
+ "step over test0+1"
+
+ # Sanity check that we are in main like we expect.
+ gdb_test "frame 0" \
+ [multi_line \
+ "#0\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_3" \
+ "$::lineno_main_3\\s+return 0;\\s+\[^\r\n\]+"] \
+ "confirm expected frame in main"
+}
+
+foreach_with_prefix opt_level { Og O0 O1 O2 } {
+ run_test ${opt_level}
+}