forked from Imagelibrary/binutils-gdb
Currently when creating a gdb.UnwindInfo object a user must call
gdb.PendingFrame.create_unwind_info and pass a frame-id object.
The frame-id object should have at least a 'sp' attribute, and
probably a 'pc' attribute too (it can also, in some cases have a
'special' attribute).
Currently all of these frame-id attributes need to be gdb.Value
objects, but the only reason for that requirement is that we have some
code in py-unwind.c that only handles gdb.Value objects.
If instead we switch to using get_addr_from_python in py-utils.c then
we will support both gdb.Value objects and also raw numbers, which
might make things simpler in some cases.
So, I started rewriting pyuw_object_attribute_to_pointer (in
py-unwind.c) to use get_addr_from_python. However, while looking at
the code I noticed a problem.
The pyuw_object_attribute_to_pointer function returns a boolean flag,
if everything goes OK we return true, but we return false in two
cases, (1) when the attribute is not present, which might be
acceptable, or might be an error, and (2) when we get an error trying
to extract the attribute value, in which case a Python error will have
been set.
Now in pending_framepy_create_unwind_info we have this code:
if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "sp", &sp))
{
PyErr_SetString (PyExc_ValueError,
_("frame_id should have 'sp' attribute."));
return NULL;
}
Notice how we always set an error. This will override any error that
is already set.
So, if you create a frame-id object that has an 'sp' attribute, but
the attribute is not a gdb.Value, then currently we fail to extract
the attribute value (it's not a gdb.Value) and set this error in
pyuw_object_attribute_to_pointer:
rc = pyuw_value_obj_to_pointer (pyo_value.get (), addr);
if (!rc)
PyErr_Format (
PyExc_ValueError,
_("The value of the '%s' attribute is not a pointer."),
attr_name);
Then we return to pending_framepy_create_unwind_info and immediately
override this error with the error about 'sp' being missing.
This all feels very confused.
Here's my proposed solution: pyuw_object_attribute_to_pointer will now
return a tri-state enum, with states OK, MISSING, or ERROR. The
meanings of these states are:
OK - Attribute exists and was extracted fine,
MISSING - Attribute doesn't exist, no Python error was set.
ERROR - Attribute does exist, but there was an error while
extracting it, a Python error was set.
We need to update pending_framepy_create_unwind_info, the only user of
pyuw_object_attribute_to_pointer, but now I think things are much
clearer. Errors from lower levels are not blindly overridden with the
generic meaningless error message, but we still get the "missing 'sp'
attribute" error when appropriate.
This change also includes the switch to get_addr_from_python which was
what started this whole journey.
For well behaving user code there should be no visible changes after
this commit.
For user code that hits an error, hopefully the new errors should be
more helpful in figuring out what's gone wrong.
Additionally, users can now use integers for the 'sp' and 'pc'
attributes in their frame-id objects if that is useful.
Reviewed-By: Tom Tromey <tom@tromey.com>
267 lines
9.9 KiB
Plaintext
267 lines
9.9 KiB
Plaintext
# 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\\."]
|