blob: 3a34bf060d94c64fad2275a885adf7e619ec5f3d [file]
# Copyright 2024-2026 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/>.
# Setup a line table where:
#
# | | | | Func | Func | Func |
# | Addr | Line | Stmt | main | foo | bar |
# |------|------|------|------|------|------|
# | 1 | 28 | Y | | X | |
# | 2 | 30 | Y | | X | |
# | 3 | 31 | N | | X | |
# | 4 | 41 | Y | X | | |
# | 5 | 42 | Y | X | | |
# | 5 | 36 | Y | X | | X |
# | 5 | 42 | N | X | | |
# | 6 | 43 | Y | X | | |
# | 7 | END | Y | X | | |
# |------|------|------|------|------|------|
#
#
# The function 'bar' is inline within 'main' while 'foo' is not
# inline. Function 'foo' is called from 'main' immediately after the
# inlined call to bar. The C code can be found within a '#if 0' block
# inside the test's .c file. The line table is similar to that
# generated by compiling the source code at optimisation level -Og.
#
# Place a breakpoint in 'foo', run to the breakpoint, and then examine
# frame #1, that is, the frame for 'main'. At one point, bugs in GDB
# meant that the user would be shown the inline line from 'bar' rather
# than the line from 'main'. In the example above the user expects to
# see line 42 from 'main', but instead would be shown line '36'.
#
# The cause of the bug is this: to find the line for frame #1 GDB
# first finds an address in frame #1 by unwinding frame #0. This
# provides the return address in frame #1. GDB subtracts 1 from this
# address and looks for a line matching this address. In this case
# that would be line 42.
#
# However, buggy GDB would then scan backward through the line table
# looking for a line table entry that is marked as is-stmt. In this
# case, the first matching entry is that for line 36, and so that is
# what is reported. This backward scan makes sense for frame #0, but
# not for outer frames.
#
# This has now been fixed to prevent the backward scan for frames
# other than frame #0.
load_lib dwarf.exp
# This test can only be run on targets which support DWARF-2 and use
# gas.
require dwarf2_support
standard_testfile .c .S
# Lines in the source code that we need to reference.
set call_line [gdb_get_line_number "call line" $srcfile]
set foo_prologue [gdb_get_line_number "foo prologue" $srcfile]
set main_prologue [gdb_get_line_number "main prologue" $srcfile]
set bar_body [gdb_get_line_number "bar body" $srcfile]
# We need the return address in 'main' after the call to 'func' so
# that we can build the line table. Compile the .c file with debug,
# and figure out the address. This works so long as the only
# difference in build flags between this compile and the later compile
# is that this is debug on, and the later compile is debug off.
if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
return
}
if {![runto func]} {
return
}
set func_call_line [gdb_get_line_number "func ();"]
gdb_test "up" \
[multi_line \
"#1\\s*$hex in main \\(\\) at \[^\r\n\]+" \
"$func_call_line\\s+ func \\(\\);"] \
"move up from func to main"
set return_addr_in_main [get_hexadecimal_valueof "\$pc" "*UNKNOWN*" \
"get pc after return from func"]
# Prepare and run the test. Placed into a proc in case we ever want
# to parameterise this test in the future.
proc do_test { } {
set build_options {nodebug}
set asm_file [standard_output_file $::srcfile2]
Dwarf::assemble $asm_file {
upvar build_options build_options
declare_labels lines_label foo_label bar_label
get_func_info main $build_options
get_func_info func $build_options
cu {} {
DW_TAG_compile_unit {
DW_AT_producer "gcc"
DW_AT_language @DW_LANG_C
DW_AT_name $::srcfile
DW_AT_low_pc 0 addr
DW_AT_stmt_list ${lines_label} DW_FORM_sec_offset
} {
foo_label: subprogram {
DW_AT_external 1 flag
DW_AT_name foo
DW_AT_low_pc $func_start addr
DW_AT_high_pc "$func_start + $func_len" addr
}
bar_label: subprogram {
DW_AT_external 1 flag
DW_AT_name bar
DW_AT_inline 3 data1
}
subprogram {
DW_AT_external 1 flag
DW_AT_name main
DW_AT_low_pc $main_start addr
DW_AT_high_pc "$main_start + $main_len" addr
} {
inlined_subroutine {
DW_AT_abstract_origin %$bar_label
DW_AT_low_pc line_label_4 addr
DW_AT_high_pc line_label_5 addr
DW_AT_call_file 1 data1
DW_AT_call_line $::call_line data1
}
}
}
}
lines {version 2 default_is_stmt 1} lines_label {
include_dir "${::srcdir}/${::subdir}"
file_name "$::srcfile" 1
program {
DW_LNE_set_address func
line $::foo_prologue
DW_LNS_copy
DW_LNE_set_address line_label_1
DW_LNS_advance_line 2
DW_LNS_copy
DW_LNE_set_address line_label_2
DW_LNS_advance_line 1
DW_LNS_negate_stmt
DW_LNS_copy
DW_LNE_set_address main
DW_LNS_advance_line [expr $::main_prologue - $::foo_prologue - 3]
DW_LNS_negate_stmt
DW_LNS_copy
DW_LNE_set_address line_label_4
DW_LNS_advance_line 1
DW_LNS_copy
DW_LNE_set_address line_label_4
line $::bar_body
DW_LNS_copy
DW_LNE_set_address line_label_4
line $::call_line
DW_LNS_negate_stmt
DW_LNS_copy
# Skip line_label_5, this is used as the end of `bar`
# the inline function.
DW_LNE_set_address $::return_addr_in_main
DW_LNS_advance_line 1
DW_LNS_negate_stmt
DW_LNS_copy
DW_LNE_set_address "$main_start + $main_len"
DW_LNE_end_sequence
}
}
}
if { [prepare_for_testing "failed to prepare" $::testfile \
[list $::srcfile $asm_file] $build_options] } {
return
}
if {![runto foo]} {
return
}
# For this backtrace we don't really care which line number in foo
# is reported. We might get different line numbers depending on
# how the architectures skip prologue function works. This test
# is all about how frame #1 is reported.
set foo_body_1 [expr {$::foo_prologue + 1}]
set foo_body_2 [expr {$::foo_prologue + 2}]
gdb_test "bt" \
[multi_line \
"^#0\\s+foo \\(\\) at \[^\r\n\]+$::srcfile:(?:$::foo_prologue|$foo_body_1|$foo_body_2)" \
"#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$::call_line"] \
"backtrace show correct line number in main"
gdb_test "frame 1" \
[multi_line \
"^#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$::call_line" \
"$::call_line\\s+foo \\(bar \\(\\)\\);\[^\r\n\]+"] \
"correct lines are shown for frame 1"
}
# Run the test.
do_test