1# Copyright (C) 2023-2024 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
16load_lib gdb-python.exp
17
18require allow_python_tests
19
20standard_testfile
21
22if {[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.
28if {[gdb_gnu_strip_debug $binfile]} {
29    unsupported "cannot produce separate debug info files"
30    return -1
31}
32
33set remote_python_file \
34    [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
35
36set debug_filename ${binfile}.debug
37set hidden_filename ${binfile}.hidden
38
39# Start GDB.
40clean_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.
45with_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.
60gdb_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.
65set debug_directory [standard_output_file "debug-dir"]
66remote_exec build "mkdir -p $debug_directory"
67gdb_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.
73with_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.
90with_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.
112with_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.
140with_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.
160with_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
184gdb_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.
192clean_restart
193
194# Load the Python script into GDB.
195gdb_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.
200proc 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 occurred in Python.*"]
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.
213check_bad_name "!! Bad Name"
214check_bad_name "Bad Name"
215check_bad_name "(Bad Name)"
216check_bad_name "Bad \[Name\]"
217check_bad_name "Bad,Name"
218check_bad_name "Bad;Name"
219
220# Check that there are no handlers registered.
221gdb_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.
226gdb_test "enable missing-debug-handler foo" \
227    "^0 missing debug handlers enabled"
228gdb_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.
232gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
233
234# Now register some handlers.
235foreach 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
243with_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
260with_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
280with_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
300with_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
320with_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          "enable missing-debug-handler"
326
327    gdb_test "info missing-debug-handlers" \
328          [multi_line \
329               "Progspace \[^\r\n\]+:" \
330               "  abc-def" \
331               "  baz- \\\[disabled\\\]" \
332               "Global:" \
333               "  -bar \\\[disabled\\\]" \
334               "  Foo \\\[disabled\\\]"]
335
336    gdb_file_cmd $binfile
337    gdb_test "python print(handler_call_log)" \
338          [string_to_regexp {['abc-def']}]
339    gdb_test_no_output "python handler_call_log = \[\]" \
340          "reset call log"
341}
342
343with_test_prefix "enable global handlers" {
344    set re [string_to_regexp $binfile]
345
346    gdb_test "enable missing-debug-handler global" \
347          "^2 missing debug handlers enabled"
348
349    gdb_test "info missing-debug-handlers" \
350          [multi_line \
351               "Progspace \[^\r\n\]+:" \
352               "  abc-def" \
353               "  baz- \\\[disabled\\\]" \
354               "Global:" \
355               "  -bar" \
356               "  Foo"]
357
358    gdb_file_cmd $binfile
359    gdb_test "python print(handler_call_log)" \
360          [string_to_regexp {['abc-def', '-bar', 'Foo']}]
361    gdb_test_no_output "python handler_call_log = \[\]" \
362          "reset call log"
363}
364
365# Add handler_obj to the global handler list, and configure it to
366# return False.  We should call all of the program space specific
367# handlers (which return None), and then call handler_obj from the
368# global list, which returns False, at which point we shouldn't call
369# anyone else.
370with_test_prefix "return False handler in progspace list" {
371    gdb_test "enable missing-debug-handler progspace" \
372          "^1 missing debug handler enabled"
373
374    gdb_test_no_output \
375          "python gdb.missing_debug.register_handler(None, handler_obj)" \
376          "register the initial handler"
377
378    gdb_test "info missing-debug-handlers" \
379          [multi_line \
380               "Progspace \[^\r\n\]+:" \
381               "  abc-def" \
382               "  baz-" \
383               "Global:" \
384               "  handler" \
385               "  -bar" \
386               "  Foo"]
387
388    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_FALSE)" \
389          "confirgure handler"
390
391    gdb_file_cmd $binfile
392    gdb_test "python print(handler_call_log)" \
393          [string_to_regexp {['abc-def', 'baz-', 'handler']}]
394    gdb_test_no_output "python handler_call_log = \[\]" \
395          "reset call log"
396}
397
398# Now add handler_obj to the current program space's handler list.  We
399# use the same handler object here, that's fine.  We should only see a
400# call to the first handler object in the call log.
401with_test_prefix "return False handler in global list" {
402    gdb_test_no_output \
403          "python gdb.missing_debug.register_handler(pspace, handler_obj)" \
404          "register the initial handler"
405
406    gdb_test "info missing-debug-handlers" \
407          [multi_line \
408               "Progspace \[^\r\n\]+:" \
409               "  handler" \
410               "  abc-def" \
411               "  baz-" \
412               "Global:" \
413               "  handler" \
414               "  -bar" \
415               "  Foo"]
416
417    gdb_file_cmd $binfile
418    gdb_test "python print(handler_call_log)" \
419          [string_to_regexp {['handler']}]
420    gdb_test_no_output "python handler_call_log = \[\]" \
421          "reset call log"
422}
423
424with_test_prefix "check handler replacement" {
425    # First, check we can have the same name appear in both program
426    # space and global lists without giving an error.
427    gdb_test_no_output "python register(\"Foo\", pspace)"
428
429    gdb_test "info missing-debug-handlers" \
430          [multi_line \
431               "Progspace \[^\r\n\]+:" \
432               "  Foo" \
433               "  handler" \
434               "  abc-def" \
435               "  baz-" \
436               "Global:" \
437               "  handler" \
438               "  -bar" \
439               "  Foo"]
440
441    # Now check that we get an error if we try to add a handler with
442    # the same name.
443    gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
444          [multi_line \
445               "RuntimeError.*: Handler Foo already exists\\." \
446               "Error occurred in Python.*"]
447
448    gdb_test "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=pspace)" \
449          [multi_line \
450               "RuntimeError.*: Handler Foo already exists\\." \
451               "Error occurred in Python.*"]
452
453    # And now try again, but this time with 'replace=True', we
454    # shouldn't get an error in this case.
455    gdb_test_no_output \
456          "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)"
457
458    gdb_test_no_output \
459          "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=None, replace=True)"
460
461    # Now disable a handler and check we still need to use 'replace=True'.
462    gdb_test "disable missing-debug-handler progspace Foo" \
463          "^1 missing debug handler disabled"
464
465    gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
466          [multi_line \
467               "RuntimeError.*: Handler Foo already exists\\." \
468               "Error occurred in Python.*"] \
469          "still get an error when handler is disabled"
470
471    gdb_test_no_output \
472          "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \
473          "can replace a disabled handler"
474}
475