gdb: implement missing debug handler hook for Python
[binutils-gdb.git] / gdb / testsuite / gdb.python / py-missing-debug.exp
1 # Copyright (C) 2023 Free Software Foundation, Inc.
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 3 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16 load_lib gdb-python.exp
17
18 require allow_python_tests
19
20 standard_testfile
21
22 if {[build_executable "failed to prepare" ${testfile} ${srcfile}]} {
23 return -1
24 }
25
26 # Remove debug information from BINFILE and place it into
27 # BINFILE.debug.
28 if {[gdb_gnu_strip_debug $binfile]} {
29 unsupported "cannot produce separate debug info files"
30 return -1
31 }
32
33 set remote_python_file \
34 [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
35
36 set debug_filename ${binfile}.debug
37 set hidden_filename ${binfile}.hidden
38
39 # Start GDB.
40 clean_restart
41
42 # Some initial sanity checks; initially, we can find the debug information
43 # (this will use the .gnu_debuglink), then after we move the debug
44 # information, reload the executable, now the debug can't be found.
45 with_test_prefix "initial checks" {
46 # Load BINFILE, we should find the separate debug information.
47 gdb_file_cmd $binfile
48 gdb_assert {$gdb_file_cmd_debug_info == "debug"} \
49 "debug info is found"
50
51 # Rename the debug information file, re-load BINFILE, GDB should fail
52 # to find the debug information
53 remote_exec build "mv $debug_filename $hidden_filename"
54 gdb_file_cmd $binfile
55 gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
56 "debug info no longer found"
57 }
58
59 # Load the Python script into GDB.
60 gdb_test "source $remote_python_file" "^Success" \
61 "source python script"
62
63 # Setup the separate debug info directory. This isn't actually needed until
64 # some of the later tests, but might as well get this done now.
65 set debug_directory [standard_output_file "debug-dir"]
66 remote_exec build "mkdir -p $debug_directory"
67 gdb_test_no_output "set debug-file-directory $debug_directory" \
68 "set debug-file-directory"
69
70 # Initially the missing debug handler we install is in a mode where it
71 # returns None, indicating that it can't help locate the debug information.
72 # Check this works as expected.
73 with_test_prefix "handler returning None" {
74 gdb_test_no_output \
75 "python gdb.missing_debug.register_handler(None, handler_obj)" \
76 "register the initial handler"
77
78 gdb_file_cmd $binfile
79 gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
80 "debug info not found"
81
82 # Check the handler was only called once.
83 gdb_test "python print(handler_obj.call_count)" "^1" \
84 "check handler was only called once"
85 }
86
87 # Now configure the handler to move the debug file back to the
88 # .gnu_debuglink location and then return True, this will cause GDB to
89 # recheck, at which point it should find the debug info.
90 with_test_prefix "handler in gnu_debuglink mode" {
91 gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
92 \"$hidden_filename\", \
93 \"$debug_filename\")" \
94 "confirgure handler"
95 gdb_file_cmd $binfile
96 gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
97
98 # Check the handler was only called once.
99 gdb_test "python print(handler_obj.call_count)" "^1" \
100 "check handler was only called once"
101 }
102
103 # Setup a directory structure based on the build-id of BINFILE, but don't
104 # move the debug information into place just yet.
105 #
106 # Instead, configure the handler to move the debug info into the build-id
107 # directory.
108 #
109 # Reload BINFILE, at which point the handler will move the debug info into
110 # the build-id directory and return True, GDB will then recheck for the
111 # debug information, and should find it.
112 with_test_prefix "handler in build-id mode" {
113 # Move the debug file out of the way once more.
114 remote_exec build "mv $debug_filename $hidden_filename"
115
116 # Create the build-id based directory in which the debug information
117 # will be placed.
118 set build_id_filename \
119 $debug_directory/[build_id_debug_filename_get $binfile]
120 remote_exec build "mkdir -p [file dirname $build_id_filename]"
121
122 # Configure the handler to move the debug info into the build-id dir.
123 gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
124 \"$hidden_filename\", \
125 \"$build_id_filename\")" \
126 "confirgure handler"
127
128 # Reload the binary and check the debug information is found.
129 gdb_file_cmd $binfile
130 gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
131
132 # Check the handler was only called once.
133 gdb_test "python print(handler_obj.call_count)" "^1" \
134 "check handler was only called once"
135 }
136
137 # Move the debug information back to a hidden location and configure the
138 # handler to return the filename of the hidden debug info location. GDB
139 # should immediately use this file as the debug information.
140 with_test_prefix "handler returning a string" {
141 remote_exec build "mv $build_id_filename $hidden_filename"
142
143 # Configure the handler return a filename string.
144 gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_STRING, \
145 \"$hidden_filename\")" \
146 "confirgure handler"
147
148 # Reload the binary and check the debug information is found.
149 gdb_file_cmd $binfile
150 gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
151
152 # Check the handler was only called once.
153 gdb_test "python print(handler_obj.call_count)" "^1" \
154 "check handler was only called once"
155 }
156
157 # Register another global handler, this one raises an exception. Reload the
158 # debug information, the bad handler should be invoked first, which raises
159 # an excetption, at which point GDB should skip further Python handlers.
160 with_test_prefix "handler raises an exception" {
161 gdb_test_no_output \
162 "python gdb.missing_debug.register_handler(None, rhandler)"
163
164 foreach_with_prefix exception_type {gdb.GdbError TypeError} {
165 gdb_test_no_output \
166 "python rhandler.exception_type = $exception_type"
167
168 gdb_file_cmd $binfile
169 gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
170 "debug info not found"
171
172 set re [string_to_regexp \
173 "Python Exception <class '$exception_type'>: message"]
174 gdb_assert {[regexp $re $gdb_file_cmd_msg]} \
175 "check for exception in file command output"
176
177 # Our original handler is still registered, but should not have been
178 # called again (as the exception occurs first).
179 gdb_test "python print(handler_obj.call_count)" "^1" \
180 "check good handler hasn't been called again"
181 }
182 }
183
184 gdb_test "info missing-debug-handlers" \
185 [multi_line \
186 "Global:" \
187 " exception_handler" \
188 " handler"] \
189 "check both handlers are visible"
190
191 # Re-start GDB.
192 clean_restart
193
194 # Load the Python script into GDB.
195 gdb_test "source $remote_python_file" "^Success" \
196 "source python script for bad handler name checks"
197
198 # Attempt to register a missing-debug-handler with NAME. The expectation is
199 # that this should fail as NAME contains some invalid characters.
200 proc check_bad_name {name} {
201 set name_re [string_to_regexp $name]
202 set re \
203 [multi_line \
204 "ValueError: invalid character '.' in handler name: $name_re" \
205 "Error while executing Python code\\."]
206
207 gdb_test "python register(\"$name\")" $re \
208 "check that '$name' is not accepted"
209 }
210
211 # We don't attempt to be exhaustive here, just check a few random examples
212 # of invalid names.
213 check_bad_name "!! Bad Name"
214 check_bad_name "Bad Name"
215 check_bad_name "(Bad Name)"
216 check_bad_name "Bad \[Name\]"
217 check_bad_name "Bad,Name"
218 check_bad_name "Bad;Name"
219
220 # Check that there are no handlers registered.
221 gdb_test_no_output "info missing-debug-handlers" \
222 "check no handlers are registered"
223
224 # Check we can use the enable/disable commands where there are no handlers
225 # registered.
226 gdb_test "enable missing-debug-handler foo" \
227 "^0 missing debug handlers enabled"
228 gdb_test "disable missing-debug-handler foo" \
229 "^0 missing debug handlers disabled"
230
231 # Grab the current program space object, used for registering handler later.
232 gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
233
234 # Now register some handlers.
235 foreach hspec {{\"Foo\" None}
236 {\"-bar\" None}
237 {\"baz-\" pspace}
238 {\"abc-def\" pspace}} {
239 lassign $hspec name locus
240 gdb_test "python register($name, $locus)"
241 }
242
243 with_test_prefix "all handlers enabled" {
244 gdb_test "info missing-debug-handlers" \
245 [multi_line \
246 "Current Progspace:" \
247 " abc-def" \
248 " baz-" \
249 "Global:" \
250 " -bar" \
251 " Foo"]
252
253 gdb_file_cmd $binfile
254 gdb_test "python print(handler_call_log)" \
255 [string_to_regexp {['abc-def', 'baz-', '-bar', 'Foo']}]
256 gdb_test_no_output "python handler_call_log = \[\]" \
257 "reset call log"
258 }
259
260 with_test_prefix "disable 'baz-'" {
261 gdb_test "disable missing-debug-handler progspace baz-" \
262 "^1 missing debug handler disabled"
263
264 gdb_test "info missing-debug-handlers" \
265 [multi_line \
266 "Progspace \[^\r\n\]+:" \
267 " abc-def" \
268 " baz- \\\[disabled\\\]" \
269 "Global:" \
270 " -bar" \
271 " Foo"]
272
273 gdb_file_cmd $binfile
274 gdb_test "python print(handler_call_log)" \
275 [string_to_regexp {['abc-def', '-bar', 'Foo']}]
276 gdb_test_no_output "python handler_call_log = \[\]" \
277 "reset call log"
278 }
279
280 with_test_prefix "disable 'Foo'" {
281 gdb_test "disable missing-debug-handler .* Foo" \
282 "^1 missing debug handler disabled"
283
284 gdb_test "info missing-debug-handlers" \
285 [multi_line \
286 "Progspace \[^\r\n\]+:" \
287 " abc-def" \
288 " baz- \\\[disabled\\\]" \
289 "Global:" \
290 " -bar" \
291 " Foo \\\[disabled\\\]"]
292
293 gdb_file_cmd $binfile
294 gdb_test "python print(handler_call_log)" \
295 [string_to_regexp {['abc-def', '-bar']}]
296 gdb_test_no_output "python handler_call_log = \[\]" \
297 "reset call log"
298 }
299
300 with_test_prefix "disable everything" {
301 gdb_test "disable missing-debug-handler .* .*" \
302 "^2 missing debug handlers disabled"
303
304 gdb_test "info missing-debug-handlers" \
305 [multi_line \
306 "Progspace \[^\r\n\]+:" \
307 " abc-def \\\[disabled\\\]" \
308 " baz- \\\[disabled\\\]" \
309 "Global:" \
310 " -bar \\\[disabled\\\]" \
311 " Foo \\\[disabled\\\]"]
312
313 gdb_file_cmd $binfile
314 gdb_test "python print(handler_call_log)" \
315 [string_to_regexp {[]}]
316 gdb_test_no_output "python handler_call_log = \[\]" \
317 "reset call log"
318 }
319
320 with_test_prefix "enable 'abc-def'" {
321 set re [string_to_regexp $binfile]
322
323 gdb_test "enable missing-debug-handler \"$re\" abc-def" \
324 "^1 missing debug handler enabled"
325
326 gdb_test "info missing-debug-handlers" \
327 [multi_line \
328 "Progspace \[^\r\n\]+:" \
329 " abc-def" \
330 " baz- \\\[disabled\\\]" \
331 "Global:" \
332 " -bar \\\[disabled\\\]" \
333 " Foo \\\[disabled\\\]"]
334
335 gdb_file_cmd $binfile
336 gdb_test "python print(handler_call_log)" \
337 [string_to_regexp {['abc-def']}]
338 gdb_test_no_output "python handler_call_log = \[\]" \
339 "reset call log"
340 }
341
342 with_test_prefix "enable global handlers" {
343 set re [string_to_regexp $binfile]
344
345 gdb_test "enable missing-debug-handler global" \
346 "^2 missing debug handlers enabled"
347
348 gdb_test "info missing-debug-handlers" \
349 [multi_line \
350 "Progspace \[^\r\n\]+:" \
351 " abc-def" \
352 " baz- \\\[disabled\\\]" \
353 "Global:" \
354 " -bar" \
355 " Foo"]
356
357 gdb_file_cmd $binfile
358 gdb_test "python print(handler_call_log)" \
359 [string_to_regexp {['abc-def', '-bar', 'Foo']}]
360 gdb_test_no_output "python handler_call_log = \[\]" \
361 "reset call log"
362 }
363
364 # Add handler_obj to the global handler list, and configure it to
365 # return False. We should call all of the program space specific
366 # handlers (which return None), and then call handler_obj from the
367 # global list, which returns False, at which point we shouldn't call
368 # anyone else.
369 with_test_prefix "return False handler in progspace list" {
370 gdb_test "enable missing-debug-handler progspace" \
371 "^1 missing debug handler enabled"
372
373 gdb_test_no_output \
374 "python gdb.missing_debug.register_handler(None, handler_obj)" \
375 "register the initial handler"
376
377 gdb_test "info missing-debug-handlers" \
378 [multi_line \
379 "Progspace \[^\r\n\]+:" \
380 " abc-def" \
381 " baz-" \
382 "Global:" \
383 " handler" \
384 " -bar" \
385 " Foo"]
386
387 gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_FALSE)" \
388 "confirgure handler"
389
390 gdb_file_cmd $binfile
391 gdb_test "python print(handler_call_log)" \
392 [string_to_regexp {['abc-def', 'baz-', 'handler']}]
393 gdb_test_no_output "python handler_call_log = \[\]" \
394 "reset call log"
395 }
396
397 # Now add handler_obj to the current program space's handler list. We
398 # use the same handler object here, that's fine. We should only see a
399 # call to the first handler object in the call log.
400 with_test_prefix "return False handler in global list" {
401 gdb_test_no_output \
402 "python gdb.missing_debug.register_handler(pspace, handler_obj)" \
403 "register the initial handler"
404
405 gdb_test "info missing-debug-handlers" \
406 [multi_line \
407 "Progspace \[^\r\n\]+:" \
408 " handler" \
409 " abc-def" \
410 " baz-" \
411 "Global:" \
412 " handler" \
413 " -bar" \
414 " Foo"]
415
416 gdb_file_cmd $binfile
417 gdb_test "python print(handler_call_log)" \
418 [string_to_regexp {['handler']}]
419 gdb_test_no_output "python handler_call_log = \[\]" \
420 "reset call log"
421 }
422
423 with_test_prefix "check handler replacement" {
424 # First, check we can have the same name appear in both program
425 # space and global lists without giving an error.
426 gdb_test_no_output "python register(\"Foo\", pspace)"
427
428 gdb_test "info missing-debug-handlers" \
429 [multi_line \
430 "Progspace \[^\r\n\]+:" \
431 " Foo" \
432 " handler" \
433 " abc-def" \
434 " baz-" \
435 "Global:" \
436 " handler" \
437 " -bar" \
438 " Foo"]
439
440 # Now check that we get an error if we try to add a handler with
441 # the same name.
442 gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
443 [multi_line \
444 "RuntimeError: Handler Foo already exists\\." \
445 "Error while executing Python code\\."]
446
447 gdb_test "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=pspace)" \
448 [multi_line \
449 "RuntimeError: Handler Foo already exists\\." \
450 "Error while executing Python code\\."]
451
452 # And now try again, but this time with 'replace=True', we
453 # shouldn't get an error in this case.
454 gdb_test_no_output \
455 "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)"
456
457 gdb_test_no_output \
458 "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=None, replace=True)"
459
460 # Now disable a handler and check we still need to use 'replace=True'.
461 gdb_test "disable missing-debug-handler progspace Foo" \
462 "^1 missing debug handler disabled"
463
464 gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
465 [multi_line \
466 "RuntimeError: Handler Foo already exists\\." \
467 "Error while executing Python code\\."] \
468 "still get an error when handler is disabled"
469
470 gdb_test_no_output \
471 "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \
472 "can replace a disabled handler"
473 }