aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
blob: df04ea0fe24cab56cadd03828dd01eb70b45ccf3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# Copyright (C) 2021 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/>.

# This test checks for an edge case when unwinding inline frames which
# occur towards the older end of the stack when the stack ends with a
# cycle.  Consider this well formed stack:
#
#   main -> normal_frame -> inline_frame
#
# Now consider that, for whatever reason, the stack unwinding of
# "normal_frame" becomes corrupted, such that the stack appears to be
# this:
#
#   .-> normal_frame -> inline_frame
#   |      |
#   '------'
#
# When confronted with such a situation we would expect GDB to detect
# the stack frame cycle and terminate the backtrace at the first
# instance of "normal_frame" with a message:
#
#   Backtrace stopped: previous frame identical to this frame (corrupt stack?)
#
# However, at one point there was a bug in GDB's inline frame
# mechanism such that the fact that "inline_frame" was inlined into
# "normal_frame" would cause GDB to trigger an assertion.
#
# This text makes use of a Python unwinder which can fake the cyclic
# stack cycle, further the test sets up multiple levels of normal and
# inline frames.  At the point of testing the stack looks like this:
#
#   main -> normal_func -> inline_func -> normal_func -> inline_func -> normal_func -> inline_func
#
# Where "normal_func" is a normal frame, and "inline_func" is an inline frame.
#
# The python unwinder is then used to force a stack cycle at each
# "normal_func" frame in turn, we then check that GDB can successfully unwind
# the stack.

standard_testfile

if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}]} {
    return -1
}

# Skip this test if Python scripting is not enabled.
if { [skip_python_tests] } { continue }

if ![runto_main] then {
    return 0
}

set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]

# Run to the breakpoint where we will carry out the test.
gdb_breakpoint [gdb_get_line_number "Break here"]
gdb_continue_to_breakpoint "stop at test breakpoint"

# Load the script containing the unwinder, this must be done at the
# testing point as the script will examine the stack as it is loaded.
gdb_test_no_output "source ${pyfile}"\
    "import python scripts"

# Check the unbroken stack.
gdb_test_sequence "bt" "backtrace when the unwind is left unbroken" {
    "\\r\\n#0 \[^\r\n\]* inline_func \\(\\) at "
    "\\r\\n#1 \[^\r\n\]* normal_func \\(\\) at "
    "\\r\\n#2 \[^\r\n\]* inline_func \\(\\) at "
    "\\r\\n#3 \[^\r\n\]* normal_func \\(\\) at "
    "\\r\\n#4 \[^\r\n\]* inline_func \\(\\) at "
    "\\r\\n#5 \[^\r\n\]* normal_func \\(\\) at "
    "\\r\\n#6 \[^\r\n\]* main \\(\\) at "
}

with_test_prefix "cycle at level 5" {
    # Arrange to introduce a stack cycle at frame 5.
    gdb_test_no_output "python stop_at_level=5"
    gdb_test "maint flush register-cache" \
	"Register cache flushed\\."
    gdb_test_lines "bt" "backtrace when the unwind is broken at frame 5" \
	[multi_line \
	     "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
	     "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
	     "#2 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
	     "#3 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
	     "#4 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
	     "#5 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
	     "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
}

with_test_prefix "cycle at level 3" {
    # Arrange to introduce a stack cycle at frame 3.
    gdb_test_no_output "python stop_at_level=3"
    gdb_test "maint flush register-cache" \
	"Register cache flushed\\."
    gdb_test_lines "bt" "backtrace when the unwind is broken at frame 3" \
	[multi_line \
	     "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
	     "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
	     "#2 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
	     "#3 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
	     "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
}

with_test_prefix "cycle at level 1" {
    # Arrange to introduce a stack cycle at frame 1.
    gdb_test_no_output "python stop_at_level=1"
    gdb_test "maint flush register-cache" \
	"Register cache flushed\\."
    gdb_test_lines "bt" "backtrace when the unwind is broken at frame 1" \
	[multi_line \
	     "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
	     "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
	     "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
}

# Flush the register cache (which also flushes the frame cache) so we
# get a full backtrace again, then switch on frame debugging and try
# to back trace.  At one point this triggered an assertion.
gdb_test "maint flush register-cache" \
    "Register cache flushed\\." ""
gdb_test_no_output "set debug frame 1"
gdb_test_multiple "bt" "backtrace with debugging on" {
    -re "^$gdb_prompt $" {
	pass $gdb_test_name
    }
    -re "\[^\r\n\]+\r\n" {
	exp_continue
    }
}
gdb_test "p 1 + 2 + 3" " = 6" \
    "ensure GDB is still alive"