aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/dwarf2/read.c68
-rw-r--r--gdb/testsuite/gdb.dwarf2/dw2-bad-abstract-origin.c83
-rw-r--r--gdb/testsuite/gdb.dwarf2/dw2-bad-abstract-origin.exp274
3 files changed, 403 insertions, 22 deletions
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 995abf1..5a5e27f 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -11450,6 +11450,21 @@ dwarf2_record_block_ranges (struct die_info *die, struct block *block,
struct attribute *attr;
struct attribute *attr_high;
+ /* Like dwarf_get_pc_bounds_ranges_or_highlow_pc, we read either the
+ low/high pc attributes, OR the ranges attribute, but not both. If we
+ parse both here then we open up the possibility that, due to invalid
+ DWARF, a block's start() and end() might not contain all of the ranges.
+
+ We have seen this in the wild with older (pre v9) versions of GCC. In
+ this case a GCC bug meant that a DIE was linked via DW_AT_abstract_origin
+ to the wrong DIE. Instead of pointing at the abstract DIE, GCC was
+ linking one instance DIE to an earlier instance DIE. The first instance
+ DIE had low/high pc attributes, while the second instance DIE had a
+ ranges attribute. When processing the incorrectly linked instance GDB
+ would see a DIE with both a low/high pc and some ranges data. However,
+ the ranges data was all outside the low/high range, which would trigger
+ asserts when setting the entry-pc. */
+
attr_high = dwarf2_attr (die, DW_AT_high_pc, cu);
if (attr_high)
{
@@ -11467,32 +11482,41 @@ dwarf2_record_block_ranges (struct die_info *die, struct block *block,
CORE_ADDR high = per_objfile->relocate (unrel_high);
cu->get_builder ()->record_block_range (block, low, high - 1);
}
- }
- attr = dwarf2_attr (die, DW_AT_ranges, cu);
- if (attr != nullptr && attr->form_is_unsigned ())
+ attr = dwarf2_attr (die, DW_AT_ranges, cu);
+ if (attr != nullptr && attr->form_is_unsigned ())
+ complaint (_("in %s, DIE %s, DW_AT_ranges ignored due to DW_AT_low_pc"),
+ objfile_name (per_objfile->objfile),
+ sect_offset_str (die->sect_off));
+ }
+ else
{
- /* Offset in the .debug_ranges or .debug_rnglist section (depending
- on DWARF version). */
- ULONGEST ranges_offset = attr->as_unsigned ();
-
- /* See dwarf2_cu::gnu_ranges_base's doc for why we might want to add
- this value. */
- if (die->tag != DW_TAG_compile_unit)
- ranges_offset += cu->gnu_ranges_base;
-
- std::vector<blockrange> blockvec;
- dwarf2_ranges_process (ranges_offset, cu, die->tag,
- [&] (unrelocated_addr start, unrelocated_addr end)
+ attr = dwarf2_attr (die, DW_AT_ranges, cu);
+ if (attr != nullptr && attr->form_is_unsigned ())
{
- CORE_ADDR abs_start = per_objfile->relocate (start);
- CORE_ADDR abs_end = per_objfile->relocate (end);
- cu->get_builder ()->record_block_range (block, abs_start,
- abs_end - 1);
- blockvec.emplace_back (abs_start, abs_end);
- });
+ /* Offset in the .debug_ranges or .debug_rnglist section (depending
+ on DWARF version). */
+ ULONGEST ranges_offset = attr->as_unsigned ();
- block->set_ranges (make_blockranges (objfile, blockvec));
+ /* See dwarf2_cu::gnu_ranges_base's doc for why we might want to add
+ this value. */
+ if (die->tag != DW_TAG_compile_unit)
+ ranges_offset += cu->gnu_ranges_base;
+
+ std::vector<blockrange> blockvec;
+ dwarf2_ranges_process (ranges_offset, cu, die->tag,
+ [&] (unrelocated_addr start,
+ unrelocated_addr end)
+ {
+ CORE_ADDR abs_start = per_objfile->relocate (start);
+ CORE_ADDR abs_end = per_objfile->relocate (end);
+ cu->get_builder ()->record_block_range (block, abs_start,
+ abs_end - 1);
+ blockvec.emplace_back (abs_start, abs_end);
+ });
+
+ block->set_ranges (make_blockranges (objfile, blockvec));
+ }
}
dwarf2_record_block_entry_pc (die, block, cu);
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-bad-abstract-origin.c b/gdb/testsuite/gdb.dwarf2/dw2-bad-abstract-origin.c
new file mode 100644
index 0000000..96014af
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-bad-abstract-origin.c
@@ -0,0 +1,83 @@
+/* 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
+func_a (void) /* func_a decl line */
+{
+ /* This label is used to find the start of 'func_a' when generating the
+ debug information. Place nothing before it. */
+ asm ("func_a_label: .globl func_a_label");
+ ++global_var;
+
+ asm ("func_a_0: .globl func_a_0");
+ ++global_var;
+
+ asm ("func_a_1: .globl func_a_1");
+ ++global_var;
+
+ asm ("func_a_2: .globl func_a_2");
+ ++global_var;
+
+ asm ("func_a_3: .globl func_a_3");
+ ++global_var;
+
+ asm ("func_a_4: .globl func_a_4");
+ ++global_var;
+
+ asm ("func_a_5: .globl func_a_5");
+ ++global_var;
+
+ asm ("func_a_6: .globl func_a_6");
+ ++global_var;
+}
+
+void
+func_b (void) /* func_b decl line */
+{
+ /* This label is used to find the start of 'func_b' when generating the
+ debug information. Place nothing before it. */
+ asm ("func_b_label: .globl func_b_label");
+ ++global_var;
+
+ asm ("func_b_0: .globl func_b_0");
+ ++global_var; /* inline func_a call line */
+
+ asm ("func_b_1: .globl func_b_1");
+ ++global_var;
+
+ asm ("func_b_2: .globl func_b_2");
+ ++global_var;
+
+ asm ("func_b_3: .globl func_b_3");
+ ++global_var;
+
+ asm ("func_b_4: .globl func_b_4");
+ ++global_var;
+
+ asm ("func_b_5: .globl func_b_5");
+ ++global_var;
+}
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+ func_b ();
+ func_a ();
+}
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-bad-abstract-origin.exp b/gdb/testsuite/gdb.dwarf2/dw2-bad-abstract-origin.exp
new file mode 100644
index 0000000..5e7d8cc
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-bad-abstract-origin.exp
@@ -0,0 +1,274 @@
+# 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/>.
+
+# There was a bug in GCC, which appears to be fixed in version 9 and
+# later, where GCC would, in some case, create an invalid
+# DW_AT_abstract_origin value.
+#
+# The bug was that there existed a function which could be inlined,
+# and so the DWARF contained a DW_TAG_subprogram describing the
+# abstract instance of the function.
+#
+# For whatever reason, the compiler generated a non-inline instance of
+# the function, and so we had a DW_TAG_subprogram with a
+# DW_AT_abstract_origin that referenced the abstract instance.
+#
+# Additionally there was an inlined instance of the function, and so
+# we had a DW_TAG_inlined_subroutine with a DW_AT_abstract_origin that
+# referenced the abstract instance.
+#
+# Within the function there was a DW_TAG_lexical_block, which also
+# appeared in the abstract instance, and both concrete instances. The
+# lexical block also has DW_AT_abstract_origin that should link back
+# to the lexical block within the abstract instance.
+#
+# The bug was that the DW_AT_abstract_origin for the lexical block
+# within the inlined instance instead referenced the lexical block
+# within the non-inline instance, not within the abstract instance.
+#
+# The problem this caused is that the non-inline instance defined the
+# extents of the lexical block using DW_AT_ranges, while the inline
+# instance defined the extend using DW_AT_low_pc and DW_AT_high_pc.
+#
+# When GDB tried to parse the block ranges for the lexical block for
+# the inline function GDB would then find both the DW_AT_ranges and
+# the DW_AT_low_pc/DW_AT_high_pc values. This alone is unexpected.
+#
+# What is worse though, is that the DW_AT_ranges were not within the
+# low-pc/high-pc bounds, and this really confused GDB.
+#
+# The solution is that, when GDB finds blocks with both ranges AND
+# low-pc/high-pc information, GDB should only accept the
+# low-pc/high-pc information.
+#
+# Of course, there's no guarantee which of the information is correct,
+# but if GDB tries to hold both piece of information, then we end up
+# in a non-consistent state, and this triggers assertions.
+
+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 func_a
+get_func_info func_b
+
+# Some line numbers needed in the generated DWARF.
+set func_a_decl_line [gdb_get_line_number "func_a decl line"]
+set func_b_decl_line [gdb_get_line_number "func_b decl line"]
+set call_line [gdb_get_line_number "inline func_a call line"]
+
+# See the problem description at the head of this file.
+#
+# Create the test program, use DWARF_VERSION to decide which format of
+# ranges table to generate.
+#
+# Then run the test program and check that GDB doesn't crash, and
+# check that the block structure is as we expect.
+proc run_test { dwarf_version } {
+ set dw_testname "${::testfile}-${dwarf_version}"
+
+ 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 foo_func foo_block block_ranges bad_block \
+ value_label int_label
+
+ cu { version $dwarf_version } {
+ compile_unit {
+ {producer "GNU C 14.1.0"}
+ {language @DW_LANG_C}
+ {name $::srcfile}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ {low_pc 0 addr}
+ } {
+ int_label: base_type {
+ {name "int"}
+ {byte_size 4 sdata}
+ {encoding @DW_ATE_signed}
+ }
+ foo_func: subprogram {
+ {name foo}
+ {inline @DW_INL_declared_inlined}
+ {decl_file 1 data1}
+ {decl_line $::func_a_decl_line data1}
+ } {
+ foo_block: lexical_block {
+ } {
+ value_label: DW_TAG_variable {
+ {name value}
+ {type :$int_label}
+ }
+ }
+ }
+ subprogram {
+ {abstract_origin %$foo_func}
+ {low_pc func_a_0 addr}
+ {high_pc func_a_6 addr}
+ {external 1 flag}
+ } {
+ bad_block: lexical_block {
+ {abstract_origin %$foo_block}
+ {ranges $block_ranges DW_FORM_sec_offset}
+ } {
+ DW_TAG_variable {
+ {abstract_origin %$value_label}
+ {DW_AT_location {
+ DW_OP_const1u 23
+ DW_OP_stack_value
+ } SPECIAL_expr}
+ }
+ }
+ }
+ subprogram {
+ {name baz}
+ {low_pc func_b_0 addr}
+ {high_pc func_b_5 addr}
+ {external 1 flag}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$foo_func}
+ {call_file 1 data1}
+ {call_line $::call_line data1}
+ {low_pc func_b_1 addr}
+ {high_pc func_b_4 addr}
+ } {
+ lexical_block {
+ {abstract_origin %$bad_block}
+ {low_pc func_b_2 addr}
+ {high_pc func_b_3 addr}
+ } {
+ DW_TAG_variable {
+ {abstract_origin %$value_label}
+ {DW_AT_location {
+ DW_OP_const1u 99
+ DW_OP_stack_value
+ } SPECIAL_expr}
+ }
+ }
+ }
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$::srcfile" 1
+ }
+
+ if { $dwarf_version == 5 } {
+ rnglists {} {
+ table {} {
+ block_ranges: list_ {
+ start_end func_a_1 func_a_2
+ start_end func_a_4 func_a_5
+ }
+ }
+ }
+ } else {
+ ranges { } {
+ block_ranges: sequence {
+ range func_a_1 func_a_2
+ range func_a_4 func_a_5
+ }
+ }
+ }
+ }
+
+ if {[prepare_for_testing "failed to prepare" "${dw_testname}" \
+ [list $::srcfile $asm_file] {nodebug}]} {
+ return false
+ }
+
+ if {![runto_main]} {
+ return false
+ }
+
+ # Breakpoint on the inline function `foo'.
+ gdb_breakpoint foo
+
+ # Breakpoint within the lexical block inside of `foo'.
+ gdb_breakpoint func_a_1
+ gdb_breakpoint func_b_2
+
+ gdb_continue_to_breakpoint "continue to first foo breakpoint"
+ gdb_continue_to_breakpoint "continue to func_b_2 breakpoint"
+
+ gdb_test "print value" " = 99" "print value at func_b_2"
+
+ # Some addresses we need to look for in the 'maint info blocks'
+ # output.
+ set func_b_0 [get_hexadecimal_valueof "&func_b_0" "*UNKNOWN*"]
+ set func_b_1 [get_hexadecimal_valueof "&func_b_1" "*UNKNOWN*"]
+ set func_b_2 [get_hexadecimal_valueof "&func_b_2" "*UNKNOWN*"]
+ set func_b_3 [get_hexadecimal_valueof "&func_b_3" "*UNKNOWN*"]
+ set func_b_4 [get_hexadecimal_valueof "&func_b_4" "*UNKNOWN*"]
+ set func_b_5 [get_hexadecimal_valueof "&func_b_5" "*UNKNOWN*"]
+
+ gdb_test "maint info blocks" \
+ [multi_line \
+ "\\\[\\(block \\*\\) $::hex\\\] $func_b_0\\.\\.$func_b_5" \
+ " entry pc: $func_b_0" \
+ " function: baz" \
+ " is contiguous" \
+ "\\\[\\(block \\*\\) $::hex\\\] $func_b_1\\.\\.$func_b_4" \
+ " entry pc: $func_b_1" \
+ " inline function: foo" \
+ " symbol count: $::decimal" \
+ " is contiguous" \
+ "\\\[\\(block \\*\\) $::hex\\\] $func_b_2\\.\\.$func_b_3" \
+ " entry pc: $func_b_2" \
+ " symbol count: $::decimal" \
+ " is contiguous"] \
+ "check block structure at func_b_2"
+
+ gdb_continue_to_breakpoint "continue to second foo breakpoint"
+ gdb_continue_to_breakpoint "continue to func_a_1 breakpoint"
+
+ gdb_test "print value" " = 23" "print value at func_a_1"
+
+ # Some addresses we need to look for in the 'maint info blocks'
+ # output.
+ set func_a_0 [get_hexadecimal_valueof "&func_a_0" "*UNKNOWN*"]
+ set func_a_1 [get_hexadecimal_valueof "&func_a_1" "*UNKNOWN*"]
+ set func_a_2 [get_hexadecimal_valueof "&func_a_2" "*UNKNOWN*"]
+ set func_a_4 [get_hexadecimal_valueof "&func_a_4" "*UNKNOWN*"]
+ set func_a_5 [get_hexadecimal_valueof "&func_a_5" "*UNKNOWN*"]
+ set func_a_6 [get_hexadecimal_valueof "&func_a_6" "*UNKNOWN*"]
+
+ gdb_test "maint info blocks" \
+ [multi_line \
+ "\\\[\\(block \\*\\) $::hex\\\] $func_a_0\\.\\.$func_a_6" \
+ " entry pc: $func_a_0" \
+ " function: foo" \
+ " is contiguous" \
+ "\\\[\\(block \\*\\) $::hex\\\] $func_a_1\\.\\.$func_a_5" \
+ " entry pc: $func_a_1" \
+ " symbol count: $::decimal" \
+ " address ranges:" \
+ " $func_a_1\\.\\.$func_a_2" \
+ " $func_a_4\\.\\.$func_a_5"] \
+ "check block structure at func_a_1"
+}
+
+foreach_with_prefix dwarf_version { 4 5 } {
+ run_test $dwarf_version
+}