aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/gdb.python/py-unwind.exp
blob: fddf4f15393303ab3743a2fa7ee690cb4cccc7c6 (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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# Copyright (C) 2015-2023 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 file is part of the GDB testsuite.  It verifies that frame
# unwinders can be implemented in Python.

load_lib gdb-python.exp

require allow_python_tests

standard_testfile

# Stack protection can make the stack look a bit different, breaking the
# assumptions this test has about its layout.

set flags "additional_flags=-fno-stack-protector"

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

# This test runs on a specific platform.
require is_x86_64_m64_target

# The following tests require execution.

if {![runto_main]} {
    return 0
}

# Check for the corrupt backtrace.
proc check_for_broken_backtrace {testname} {
    gdb_test_sequence "where" $testname {
	"\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
	"\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
	"Backtrace stopped: frame did not save the PC"
    }
}

# Check for the correct backtrace.
proc check_for_fixed_backtrace {testname} {
    gdb_test_sequence "where" $testname {
	"\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
	"\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
	"\\r\\n#2 .* main \\(.*\\) at"
    }
}

# Check the 'info unwinder' output.
proc check_info_unwinder {testname enabled} {
    if {$enabled} {
	set suffix ""
    } else {
	set suffix " \\\[disabled\\\]"
    }

    gdb_test_sequence "info unwinder" $testname \
	[list \
	     "Global:" \
	     "  test unwinder${suffix}"]
}

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

gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]

gdb_continue_to_breakpoint "break backtrace-broken"

check_for_broken_backtrace "Backtrace is initially broken"

gdb_test "source ${pyfile}" "Python script imported" \
    "import python scripts"

check_info_unwinder "info unwinder after loading script" on

check_for_fixed_backtrace "check backtrace after loading unwinder"

# Check that the Python unwinder frames can be flushed / released.
gdb_test "maint flush register-cache" "Register cache flushed\\." "flush frames"

check_for_fixed_backtrace "check backtrace after flush"

# Try to disable the unwinder but instead set the enabled field to a
# non boolean value.  This should fail.  Check the 'info unwinder'
# output to be sure.
gdb_test "python global_test_unwinder.enabled = \"off\"" \
    [multi_line \
	 "TypeError: incorrect type for enabled attribute: <class 'str'>" \
	 "Error while executing Python code\\."]
check_info_unwinder "info unwinder after failed disable" on

# While we're doing silly stuff, lets try to change the name of this
# unwider.  Doing this is bad as the new name might clash with an
# already registered name, which violates the promises made during
# 'register_unwinder'.
gdb_test "python global_test_unwinder.name = \"foo\"" \
    [multi_line \
	 "AttributeError: can't set attribute" \
	 "Error while executing Python code\\."]
check_info_unwinder "info unwinder after failed name change" on

# Now actually disable the unwinder by manually adjusting the
# 'enabled' attribute.  Check that the stack is once again broken, and
# that the unwinder shows as disabled in the 'info unwinder' output.
gdb_test_no_output "python global_test_unwinder.enabled = False"
check_for_broken_backtrace "stack is broken after disabling"
check_info_unwinder "info unwinder after manually disabling" off

# Now enable the unwinder using the 'enable unwinder' command.
gdb_test "enable unwinder global \"test unwinder\"" \
    "1 unwinder enabled"
check_for_fixed_backtrace "check backtrace after enabling with command"
check_info_unwinder "info unwinder after command enabled" on

# And now disable using the command and check the stack is once again
# broken, and that the 'info unwinder' output updates correctly.
gdb_test "disable unwinder global \"test unwinder\"" \
    "1 unwinder disabled"
check_for_broken_backtrace "stack is broken after command disabling"
check_info_unwinder "info unwinder after command disabling" off

# Check that invalid register names cause errors.
gdb_test "python print(add_saved_register_error)" "True" \
    "add_saved_register error"
gdb_test "python print(read_register_error)" "True" \
    "read_register error"

# Try to create an unwinder object with a non-string name.
gdb_test "python obj = simple_unwinder(True)" \
    [multi_line \
	 "TypeError: incorrect type for name: <class 'bool'>" \
	 "Error while executing Python code\\."]

# Now register the simple_unwinder with a valid name, and use the
# unwinder to capture a PendingFrame object.
gdb_test_no_output "python obj = simple_unwinder(\"simple\")"
gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj)"
check_for_broken_backtrace "backtrace to capture a PendingFrame object"

# Check the captured PendingFrame is not valid.
gdb_test "python print(captured_pending_frame.is_valid())" "False"

# Check the __repr__ of an invalid PendingFrame.
gdb_test "python print(repr(captured_pending_frame))" \
    "<gdb.PendingFrame \\(invalid\\)>"

# Check the __repr__ of an UnwindInfo for an invalid PendingFrame.
gdb_test "python print(captured_unwind_info)"
gdb_test "python print(repr(captured_unwind_info))" \
    "<gdb.UnwindInfo for an invalid frame>"

# Check the repr of a PendingFrame that was copied (as a string) at a
# time the PendingFrame was valid.
gdb_test "python print(captured_pending_frame_repr)" \
    "<gdb.PendingFrame level=0, sp=$hex, pc=$hex>"

# Check the repr of an UnwindInfo that was copied (as a string) at a
# time the UnwindInfo was valid.
gdb_test "python print(captured_unwind_info_repr)" \
    "<gdb.UnwindInfo frame #0, saved_regs=\\(rip, rbp, rsp\\)>"

# Call methods on the captured gdb.PendingFrame and check we see the
# expected error.
gdb_test_no_output "python pf = captured_pending_frame"
foreach cmd {"pf.read_register(\"pc\")" \
		 "pf.create_unwind_info(None)" \
		 "pf.architecture()" \
		 "pf.level()" \
		 "pf.name()" \
		 "pf.pc()" \
		 "pf.language()" \
		 "pf.find_sal()" \
		 "pf.block()" \
		 "pf.function()" } {
    gdb_test "python $cmd" \
	[multi_line \
	     "ValueError: gdb\\.PendingFrame is invalid\\." \
	     "Error while executing Python code\\."]
}

# Turn on the useful unwinder so we have the full backtrace again, and
# disable the simple unwinder -- because we can!
gdb_test "enable unwinder global \"test unwinder\"" \
    "1 unwinder enabled" \
    "re-enable 'test unwinder' so we can check PendingFrame methods"
gdb_test "disable unwinder global \"simple\"" \
    "1 unwinder disabled"
check_for_fixed_backtrace \
    "check backtrace before testing PendingFrame methods"

# Turn the 'simple' unwinder back on.
gdb_test "enable unwinder global \"simple\"" \
    "1 unwinder enabled"

# Replace the "simple" unwinder with a new version that doesn't set
# the 'sp' attribute.  Also the 'pc' attribute is invalid, but we'll
# hit the missing 'sp' error first.
with_test_prefix "frame-id 'sp' is None" {
    gdb_test_no_output "python obj = simple_unwinder(\"simple\", None, \"xyz\")"
    gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj, replace=True)"
    gdb_test_no_output "python captured_pending_frame = None"
    gdb_test "backtrace" \
	"Python Exception <class 'ValueError'>: frame_id should have 'sp' attribute\\.\r\n.*"
}

# Replace the "simple" unwinder with a new version that sets the 'sp'
# attribute to an invalid value.  Also the 'pc' attribute is invalid, but we'll
# hit the invalid 'sp' error first.
with_test_prefix "frame-id 'sp' is invalid" {
    gdb_test_no_output "python obj = simple_unwinder(\"simple\", \"jkl\", \"xyz\")"
    gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj, replace=True)"
    gdb_test_no_output "python captured_pending_frame = None"
    gdb_test "backtrace" \
	"Python Exception <class 'ValueError'>: invalid literal for int\\(\\) with base 10: 'jkl'\r\n.*"
}

# Replace the "simple" unwinder with a new version that sets the 'sp'
# to a valid value, but set the 'pc' attribute to an invalid value.
with_test_prefix "frame-id 'pc' is invalid" {
    gdb_test_no_output "python obj = simple_unwinder(\"simple\", 0x123, \"xyz\")"
    gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj, replace=True)"
    gdb_test_no_output "python captured_pending_frame = None"
    gdb_test "backtrace" \
	"Python Exception <class 'ValueError'>: invalid literal for int\\(\\) with base 10: 'xyz'\r\n.*"
}

# Gather information about every frame.
gdb_test_no_output "python capture_all_frame_information()"
gdb_test_no_output "python gdb.newest_frame().select()"
gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
gdb_test_no_output "python obj = validating_unwinder()"
gdb_test_no_output "python gdb.unwinder.register_unwinder(pspace, obj)"

check_for_fixed_backtrace \
    "check backtrace to validate all information"

gdb_test_no_output "python check_all_frame_information_matched()"

# Check we can't sub-class from gdb.UnwindInfo.
gdb_test_multiline "Sub-class gdb.UnwindInfo " \
    "python" "" \
    "class my_unwind_info(gdb.UnwindInfo):" "" \
    "  def __init__(self):" "" \
    "    pass" "" \
    "end" \
    [multi_line \
	 "TypeError: type 'gdb\\.UnwindInfo' is not an acceptable base type" \
	 "Error while executing Python code\\."]

# Check we can't directly instantiate a gdb.UnwindInfo.
gdb_test "python uw = gdb.UnwindInfo()" \
    [multi_line \
     "TypeError: cannot create 'gdb\\.UnwindInfo' instances" \
     "Error while executing Python code\\."]