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