diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c index 995abf1405d..5a5e27f30a6 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 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 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 00000000000..96014af7ecd --- /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 . */ + +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 00000000000..5e7d8cc1725 --- /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 . + +# 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 +}