# 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 .
# This test creates a function 'foo' that contains an inline function
# 'bar'. Both 'foo' and 'bar' are split into two regions. The layout
# in memory looks something like this:
#
#
#
# bar: |------------| |---|
# foo: |------------------| |--------|
#
# We see things like this in "real" code where the parts after the gap
# (part-b) are cold paths within the function that have been moved
# aside.
#
# However, in this test program we use 'goto' to jump directly from
# the first part of the function (part-a) to the second part (part-b).
#
# At the time we hit the goto we are inside 'bar'. After the goto we
# expect to still be in bar. At one point GDB would get this wrong
# and after the jump we would revert back to 'foo'.
#
# This bug will only trigger if both 'foo' and 'bar' are split, and
# both 'foo' and 'bar' must start at the same address for part-b.
load_lib dwarf.exp
require dwarf2_support
# The source program use 'goto *ADDR' which is a GCC extension.
require is_c_compiler_gcc
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 foo
# Make some DWARF for the test.
set asm_file [standard_output_file "$::testfile-dw.S"]
Dwarf::assemble $asm_file {
global srcfile
# Create local varibles BAR_SRC_* containing the line number for
# the four souce lines of 'foo' and 'bar'. These will be
# referenced in the generated DWARF.
for { set i 1 } { $i <= 4 } { incr i } {
set bar_src_$i [gdb_get_line_number "bar line $i"]
set foo_src_$i [gdb_get_line_number "foo line $i"]
}
# More line numbers needed for the generated DWARF.
set foo_decl_line [gdb_get_line_number "foo decl line"]
set bar_decl_line [gdb_get_line_number "bar decl line"]
# Labels used to link parts of the DWARF together.
declare_labels lines_table bar_label ranges_label_bar ranges_label_foo
cu { version 4 } {
compile_unit {
{producer "gcc"}
{language @DW_LANG_C}
{name ${srcfile}}
{comp_dir /tmp}
{stmt_list $lines_table DW_FORM_sec_offset}
{low_pc 0 addr}
} {
bar_label: subprogram {
{external 1 flag}
{name bar}
{decl_file 1 data1}
{decl_line $bar_decl_line data1}
{decl_column 1 data1}
{inline 3 data1}
}
subprogram {
{name foo}
{decl_file 1 data1}
{decl_line $foo_decl_line data1}
{decl_column 1 data1}
{ranges ${ranges_label_foo} DW_FORM_sec_offset}
{external 1 flag}
} {
inlined_subroutine {
{abstract_origin %$bar_label}
{call_file 1 data1}
{call_line $foo_src_3 data1}
{ranges ${ranges_label_bar} DW_FORM_sec_offset}
}
}
}
}
lines {version 2} lines_table {
include_dir "$::srcdir/$::subdir"
file_name "$srcfile" 1
program {
DW_LNE_set_address "foo_label"
line $foo_src_1
DW_LNS_copy
DW_LNE_set_address "foo_label_1"
line $foo_src_2
DW_LNS_copy
DW_LNE_set_address "foo_label_2"
line $foo_src_3
DW_LNS_copy
DW_LNE_set_address "foo_label_2"
line $bar_src_1
DW_LNS_copy
DW_LNE_set_address "foo_label_3"
line $bar_src_2
DW_LNS_copy
DW_LNE_set_address "foo_label_4"
DW_LNE_end_sequence
DW_LNE_set_address "foo_label_6"
line $bar_src_3
DW_LNS_copy
DW_LNE_set_address "foo_label_7"
line $bar_src_4
DW_LNS_copy
DW_LNS_negate_stmt
DW_LNE_set_address "foo_label_7"
line $foo_src_3
DW_LNS_copy
DW_LNS_negate_stmt
DW_LNE_set_address "foo_label_8"
line $foo_src_4
DW_LNS_copy
DW_LNE_set_address $::foo_end
DW_LNE_end_sequence
}
}
ranges { } {
ranges_label_bar: sequence {
range foo_label_2 foo_label_4
range foo_label_6 foo_label_8
}
ranges_label_foo: sequence {
range foo_label_1 foo_label_4
range foo_label_6 foo_label_9
}
}
}
if {[prepare_for_testing "failed to prepare" "${::testfile}" \
[list $srcfile $asm_file] {nodebug}]} {
return -1
}
if ![runto_main] {
return -1
}
gdb_breakpoint bar
gdb_continue_to_breakpoint "continue to bar line 1" \
".*bar line 1\[^\r\n\]+"
gdb_test "step" ".*bar line 2\[^\r\n\]+" \
"step to bar line 2"
# This is the interesting one. This step will take us over the goto
# and into the second range of both 'foo' and 'bar'. As we started
# the step in 'bar' GDB should reselect 'bar' after the step.
#
# If this goes wrong the GDB will claim we are at foo_line_3, which is
# the DW_AT_call_line for 'bar'.
gdb_test "step" ".*bar line 3\[^\r\n\]+" \
"step to bar line 3"
# These following steps should be straight forward, but lets just
# check we can step out of 'bar' and back to 'foo', there shouldn't be
# anything tricky going on here though.
gdb_test "step" ".*bar line 4\[^\r\n\]+" \
"step to bar line 4"
gdb_test "step" ".*foo line 4\[^\r\n\]+" \
"step out of bar to foo line 4"