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