blob: 9c1f4240af5046f41df54e94e44fdb61e39390eb [file]
# Copyright 2024-2025 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
}