# 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 }