best = prev;
best_symtab = iter_s;
- /* If during the binary search we land on a non-statement entry,
- scan backward through entries at the same address to see if
- there is an entry marked as is-statement. In theory this
- duplication should have been removed from the line table
- during construction, this is just a double check. If the line
- table has had the duplication removed then this should be
- pretty cheap. */
- if (!best->is_stmt)
+ /* If NOTCURRENT is false then the address we are looking for is
+ the address the inferior is currently stopped at. In this
+ case our preference is to report a stop at a line marked as
+ is_stmt. If BEST is not marked as a statement then scan
+ backwards through entries at this address looking for one that
+ is marked as a statement; if one is found then use that.
+
+ If NOTCURRENT is true then the address we're looking for is
+ not the inferior's current address, but is an address from a
+ previous stack frame (i.e. frames 1, 2, 3, ... etc). In this
+ case scanning backwards for an is_stmt line table entry is not
+ the desired behaviour. If an inline function terminated at
+ this address then the last is_stmt line will be within the
+ inline function, while the following non-statement line will
+ be for the outer function. When looking up the stack we
+ expect to see the outer function. */
+ if (!best->is_stmt && !notcurrent)
{
const linetable_entry *tmp = best;
while (tmp > first
--- /dev/null
+/* Copyright 2024 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/>. */
+
+/* Used to insert labels with which we can build a fake line table. */
+#define LL(N) asm ("line_label_" #N ": .globl line_label_" #N)
+
+/* The following non-compiled code exists for the generated line table to
+ point at. */
+
+#if 0
+
+volatile int global = 0;
+
+__attribute__((noinline, noclone)) void
+foo (int arg)
+{ /* foo prologue */
+ asm ("");
+ global += arg;
+}
+
+inline __attribute__((always_inline)) int
+bar (void)
+{
+ return 1; /* bar body */
+}
+
+int
+main (void)
+{ /* main prologue */
+ foo (bar ()); /* call line */
+ return 0;
+}
+
+#endif /* 0 */
+
+volatile int var;
+
+/* Generate some code to take up some space. */
+#define FILLER do { \
+ var = 99; \
+} while (0)
+
+void
+func (void)
+{
+ asm ("func_label: .globl func_label");
+ FILLER;
+ LL (1);
+ FILLER;
+ LL (2);
+ return;
+}
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+ FILLER;
+ LL (4);
+ FILLER;
+ LL (5);
+ func ();
+ FILLER;
+ LL (6);
+ FILLER;
+ return 0;
+}
--- /dev/null
+# Copyright 2024 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 {} {
+ compile_unit {
+ {producer "gcc" }
+ {language @DW_LANG_C}
+ {name $::srcfile}
+ {low_pc 0 addr}
+ {stmt_list ${lines_label} DW_FORM_sec_offset}
+ } {
+ foo_label: subprogram {
+ {external 1 flag}
+ {name foo}
+ {low_pc $func_start addr}
+ {high_pc "$func_start + $func_len" addr}
+ }
+ bar_label: subprogram {
+ {external 1 flag}
+ {name bar}
+ {inline 3 data1}
+ }
+ subprogram {
+ {external 1 flag}
+ {name main}
+ {low_pc $main_start addr}
+ {high_pc "$main_start + $main_len" addr}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$bar_label}
+ {low_pc line_label_4 addr}
+ {high_pc line_label_5 addr}
+ {call_file 1 data1}
+ {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
# Backtrace. Check frame #1 looks right. Bug gdb/25987 would report
# frame #1 as being the correct function, but would report the line for
# ptr::get_myclass(), which is not correct.
- setup_xfail *-*-* gdb/25987
gdb_test "bt" \
[multi_line \
"#0\\s+MyClass::call\[^\r\n\]+/$::srcfile:$::final_bp_line" \
# Check frame #1 looks right. Bug gdb/25987 would report frame #1 as
# being the correct function, but would report the line for a nearby
# inlined function.
- setup_xfail *-*-* gdb/25987
gdb_test "frame 1" \
[multi_line \
"#1\\s+\[^\r\n\]*main \\(\\) \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
+#include "attributes.h"
+
/* This is only ever run if it is compiled with a new-enough GCC, but
we don't want the compilation to fail if compiled by some other
compiler. */
return x * func1 (1);
}
+inline ATTR int
+return_one (void)
+{
+ /* The following empty asm() statement prevents older (< 11.x) versions
+ of gcc from completely optimising away this function. And for newer
+ versions of gcc (>= 11.x) this ensures that we have two line table
+ entries in main for the inline call to this function, with the second
+ of these lines being a non-statement, which is critical for this
+ test. These two behaviours have been checked for versions of gcc
+ between 8.4.0 and 14.2.0. */
+ asm ("");
+ return 1;
+}
+
+volatile int global = 0;
+
+__attribute__((noinline)) ATTRIBUTE_NOCLONE void
+not_inline_func (int count)
+{
+ global += count;
+ global += count; /* b/p in not_inline_func */
+ global += count;
+}
+
int main (void)
{
int val;
val = func2 ();
result = val;
+ not_inline_func (return_one ()); /* bt line in main */
+
return 0;
}
standard_testfile .c inline-markers.c
+set opts {debug additional_flags=-Winline}
+lappend_include_file opts $srcdir/lib/attributes.h
+
if {[prepare_for_testing "failed to prepare" $testfile \
- [list $srcfile $srcfile2] \
- {debug additional_flags=-Winline}]} {
+ [list $srcfile $srcfile2] $opts]} {
return -1
}
return
}
-set line1 [gdb_get_line_number "set breakpoint 1 here" ${srcfile2}]
-gdb_breakpoint $srcfile2:$line1
-
-gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 1"
-gdb_test "backtrace" "#0 bar.*#1 .*main.*" "backtrace from bar, 1"
-gdb_test "info frame" ".*called by frame.*" "bar not inlined"
-
-gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 2"
-gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*main.*" \
- "backtrace from bar, 2"
-gdb_test "up" "#1 .*func1.*" "up from bar, 2"
-gdb_test "info frame" ".*inlined into frame.*" "func1 inlined, 2"
-
-gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 3"
-gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*func2.*#3 .*main.*" \
- "backtrace from bar, 3"
-gdb_test "up" "#1 .*func1.*" "up from bar, 3"
-gdb_test "info frame" ".*inlined into frame.*" "func1 inlined, 3"
-gdb_test "up" "#2 .*func2.*" "up from func1, 3"
-gdb_test "info frame" ".*inlined into frame.*" "func2 inlined, 3"
-
-# A regression test for having a backtrace limit that forces unwinding
-# to stop after an inline frame. GDB needs to compute the frame_id of
-# the inline frame, which requires unwinding past all the inline
-# frames to the real stack frame, even if that means bypassing the
-# user visible backtrace limit. See PR backtrace/15558.
-#
-# Set a backtrace limit that forces an unwind stop after an inline
-# function.
-gdb_test_no_output "set backtrace limit 2"
-# Force flushing the frame cache.
-gdb_test "maint flush register-cache" "Register cache flushed."
-gdb_test "up" "#1 .*func1.*" "up from bar, 4"
-gdb_test "info frame" ".*in func1.*" "info frame still works"
-# Verify the user visible limit works as expected.
-gdb_test "up" "Initial frame selected; you cannot go up." "up hits limit"
-gdb_test "backtrace" "#0 bar.*#1 .*func1.*" "backtrace hits limit"
+# Run inline function backtrace tests, compile with binary with OPT_LEVEL
+# optimisation level. OPT_LEVEL should be a string like 'O0', 'O1', etc.
+# No leading '-' is needed on OPT_LEVEL, that is added in this proc.
+proc run_test { opt_level } {
+
+ set local_opts $::opts
+ lappend local_opts "additional_flags=-$opt_level"
+
+ if {[prepare_for_testing "failed to prepare" ${::testfile}-${opt_level} \
+ [list $::srcfile $::srcfile2] $local_opts]} {
+ return
+ }
+
+ runto_main
+
+ set line1 [gdb_get_line_number "set breakpoint 1 here" ${::srcfile2}]
+ gdb_breakpoint $::srcfile2:$line1
+
+ with_test_prefix "first stop at bar" {
+ gdb_continue_to_breakpoint "continue to bar" \
+ ".*set breakpoint 1 here.*"
+ gdb_test "backtrace" "#0 bar.*#1 .*main.*" "backtrace from bar"
+ gdb_test "info frame" ".*called by frame.*" "bar not inlined"
+ }
+
+ with_test_prefix "second stop at bar" {
+ gdb_continue_to_breakpoint "continue to bar" \
+ ".*set breakpoint 1 here.*"
+ gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*main.*" \
+ "backtrace from bar"
+ gdb_test "up" "#1 .*func1.*" "up from bar"
+ gdb_test "info frame" ".*inlined into frame.*" "func1 inlined"
+ }
+
+ with_test_prefix "third stop at bar" {
+ gdb_continue_to_breakpoint "continue to bar" \
+ ".*set breakpoint 1 here.*"
+ gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*func2.*#3 .*main.*" \
+ "backtrace from bar"
+ gdb_test "up" "#1 .*func1.*" "up from bar"
+ gdb_test "info frame" ".*inlined into frame.*" "func1 inlined"
+ gdb_test "up" "#2 .*func2.*" "up from func1"
+ gdb_test "info frame" ".*inlined into frame.*" "func2 inlined"
+ }
+
+ # A regression test for having a backtrace limit that forces unwinding
+ # to stop after an inline frame. GDB needs to compute the frame_id of
+ # the inline frame, which requires unwinding past all the inline
+ # frames to the real stack frame, even if that means bypassing the
+ # user visible backtrace limit. See PR backtrace/15558.
+ #
+ # Set a backtrace limit that forces an unwind stop after an inline
+ # function.
+ gdb_test_no_output "set backtrace limit 2"
+ # Force flushing the frame cache.
+ gdb_test "maint flush register-cache" "Register cache flushed."
+ gdb_test "up" "#1 .*func1.*" "up from bar"
+ gdb_test "info frame" ".*in func1.*" "info frame still works"
+ # Verify the user visible limit works as expected.
+ gdb_test "up" "Initial frame selected; you cannot go up." "up hits limit"
+ gdb_test "backtrace" "#0 bar.*#1 .*func1.*" "backtrace hits limit"
+
+ set line2 [gdb_get_line_number "b/p in not_inline_func" $::srcfile]
+ set line3 [gdb_get_line_number "bt line in main" $::srcfile]
+
+ gdb_breakpoint $::srcfile:$line2
+
+ gdb_continue_to_breakpoint "stop in not_inline_func" \
+ ".*b/p in not_inline_func.*"
+ gdb_test "bt" \
+ [multi_line \
+ "^#0\\s+not_inline_func \\(\[^)\]+\\) at \[^\r\n\]+$::srcfile:$line2" \
+ "#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$line3"] \
+ "bt from not_inline_func to main"
+ gdb_test "frame 1" \
+ [multi_line \
+ "^#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$line3" \
+ "$line3\\s+not_inline_func \\(return_one \\(\\)\\);\[^\r\n\]+"] \
+ "select frame for main from not_inline_func"
+}
+
+foreach_with_prefix opt_level { O0 Og O1 O2 } {
+ run_test $opt_level
+}