diff options
author | Sasha Smundak <asmundak@google.com> | 2015-04-01 11:49:12 -0700 |
---|---|---|
committer | Doug Evans <dje@google.com> | 2015-04-01 11:49:12 -0700 |
commit | d11916aa89c43071c08c1f9b4550a01f8eec78e3 (patch) | |
tree | 7befd0c4b5e47eba48fec1b2b03888dcffbadd7b /gdb/testsuite | |
parent | 79730a3b2683dba745663fa3b907f564bee8a0ef (diff) | |
download | gdb-d11916aa89c43071c08c1f9b4550a01f8eec78e3.zip gdb-d11916aa89c43071c08c1f9b4550a01f8eec78e3.tar.gz gdb-d11916aa89c43071c08c1f9b4550a01f8eec78e3.tar.bz2 |
Add support for writing unwinders in Python.
gdb/ChangeLog:
* Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
(SUBDIR_PYTHON_SRCS): Add py-unwind.c.
(py-unwind.o): New recipe.
* NEWS: mention Python frame unwinding.
* data-directory/Makefile.in (PYTHON_FILE_LIST): Add
gdb/unwinder.py and gdb/command/unwinder.py
* python/lib/gdb/__init__.py (packages): Add frame_unwinders
list.
(execute_unwinders): New function.
* python/lib/gdb/command/unwinders.py: New file.
* python/lib/gdb/unwinder.py: New file.
* python/py-objfile.c (objfile_object): Add frame_unwinders field.
(objfpy_dealloc): Decrement frame_unwinders reference count.
(objfpy_initialize): Create frame_unwinders list.
(objfpy_get_frame_unwinders): New function.
(objfpy_set_frame_unwinders): Ditto.
(objfile_getset): Add frame_unwinders attribute to Objfile.
* python/py-progspace.c (pspace_object): Add frame_unwinders field.
(pspy_dealloc): Decrement frame_unwinders reference count.
(pspy_initialize): Create frame_unwinders list.
(pspy_get_frame_unwinders): New function.
(pspy_set_frame_unwinders): Ditto.
(pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
* python/py-unwind.c: New file.
* python/python-internal.h (pspy_get_name_unwinders): New prototype.
(objpy_get_frame_unwinders): New prototype.
(gdbpy_initialize_unwind): New prototype.
* python/python.c (gdbpy_apply_type_printers): Call
gdbpy_initialize_unwind.
gdb/doc/ChangeLog:
* doc/python.texi (Writing a Frame Unwinder in Python): Add
section.
gdb/testsuite/ChangeLog:
* gdb.python/py-unwind-maint.c: New file.
* gdb.python/py-unwind-maint.exp: New test.
* gdb.python/py-unwind-maint.py: New file.
* gdb.python/py-unwind.c: New file.
* gdb.python/py-unwind.exp: New test.
* gdb.python/py-unwind.py: New test.
Diffstat (limited to 'gdb/testsuite')
-rw-r--r-- | gdb/testsuite/ChangeLog | 9 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-unwind-maint.c | 24 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-unwind-maint.exp | 64 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-unwind-maint.py | 59 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-unwind.c | 81 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-unwind.exp | 54 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-unwind.py | 99 |
7 files changed, 390 insertions, 0 deletions
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index dfce0c0..0b6bbca 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,12 @@ +2015-04-01 Sasha Smundak <asmundak@google.com> + + * gdb.python/py-unwind-maint.c: New file. + * gdb.python/py-unwind-maint.exp: New test. + * gdb.python/py-unwind-maint.py: New file. + * gdb.python/py-unwind.c: New file. + * gdb.python/py-unwind.exp: New test. + * gdb.python/py-unwind.py: New test. + 2015-04-01 Pedro Alves <palves@redhat.com> * gdb.threads/manythreads.exp (interrupt_and_wait): Pass $message diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.c b/gdb/testsuite/gdb.python/py-unwind-maint.c new file mode 100644 index 0000000..8c1d935 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind-maint.c @@ -0,0 +1,24 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2015 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/>. */ + +int +main (void) +{ + int i = 0; + + return i; /* next-line */ +} diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.exp b/gdb/testsuite/gdb.python/py-unwind-maint.exp new file mode 100644 index 0000000..3b0e021 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp @@ -0,0 +1,64 @@ +# Copyright (C) 2015 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 tests Python-based +# unwinding CLI. + +load_lib gdb-python.exp + +standard_testfile + +if {[prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } { + return -1 +} + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] + +if ![runto_main ] then { + fail "Can't run to main" + return -1 +} + +gdb_test "source ${pyfile}" "Python script imported" "import python scripts" + +gdb_test_sequence "info unwinder" "Show all unwinders" { + "Global:" + " global_unwinder" + "Progspace .*py-unwind-maint:" + "py_unwind_maint_ps_unwinder" +} + +gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"] + +gdb_test_sequence "continue" "Unwinders called" { + "py_unwind_maint_ps_unwinder called" + "global_unwinder called" +} + +gdb_test "disable unwinder global .*" "1 unwinder disabled" "Unwinder disabled" + +gdb_test_sequence "info unwinder" "Show with global unwinder disabled" { + "Global:" + " global_unwinder \\[disabled\\]" + "Progspace .*py-unwind-maint:" + " py_unwind_maint_ps_unwinder" +} + +gdb_test_sequence "where" "Global unwinder disabled" { + "py_unwind_maint_ps_unwinder called\r\n#0 main" +} diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.py b/gdb/testsuite/gdb.python/py-unwind-maint.py new file mode 100644 index 0000000..f8c6277 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind-maint.py @@ -0,0 +1,59 @@ +# Copyright (C) 2015 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 tests python unwinders. + +import re +import gdb.types +from gdb.unwinder import Unwinder, register_unwinder + +class TestGlobalUnwinder(Unwinder): + def __init__(self): + super(TestGlobalUnwinder, self).__init__("global_unwinder") + + def __call__(self, unwinder_info): + print "%s called" % self.name + return None + +class TestProgspaceUnwinder(Unwinder): + def __init__(self, name): + super(TestProgspaceUnwinder, self).__init__("%s_ps_unwinder" % name) + + def __call__(self, unwinder_info): + print "%s called" % self.name + return None + +class TestObjfileUnwinder(Unwinder): + def __init__(self, name): + super(TestObjfileUnwinder, self).__init__("%s_obj_unwinder" % name) + + def __call__(self, unwinder_info): + print "%s called" % self.name + return None + + + +gdb.unwinder.register_unwinder(None, TestGlobalUnwinder()) +saw_runtime_error = False +try: + gdb.unwinder.register_unwinder(None, TestGlobalUnwinder(), replace=False) +except RuntimeError: + saw_runtime_error = True +if not saw_runtime_error: + raise RuntimeError("Missing runtime error from register_unwinder.") +gdb.unwinder.register_unwinder(None, TestGlobalUnwinder(), replace=True) +gdb.unwinder.register_unwinder(gdb.current_progspace(), + TestProgspaceUnwinder("py_unwind_maint")) +print "Python script imported" diff --git a/gdb/testsuite/gdb.python/py-unwind.c b/gdb/testsuite/gdb.python/py-unwind.c new file mode 100644 index 0000000..cf41d78 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind.c @@ -0,0 +1,81 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2015 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 is the test program loaded into GDB by the py-unwind test. */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +static void * +swap_value (void **location, void *new_value) +{ + void *old_value = *location; + *location = new_value; + return old_value; +} + +static void +bad_layout(void **variable_ptr, void *fp) +{ + fprintf (stderr, "First variable should be allocated one word below " + "the frame. Got variable's address %p, frame at %p instead.\n", + variable_ptr, fp); + abort(); +} + +#define MY_FRAME (__builtin_frame_address (0)) + +static void +corrupt_frame_inner (void) +{ + /* Save outer frame address, then corrupt the unwind chain by + setting the outer frame address in it to self. This is + ABI-specific: the first word of the frame contains previous frame + address in amd64. */ + void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME); + + /* Verify the compiler allocates the first local variable one word + below frame. This is where the test unwinder expects to find the + correct outer frame address. */ + if (&previous_fp + 1 != (void **) MY_FRAME) + bad_layout (&previous_fp + 1, MY_FRAME); + + /* Now restore it so that we can return. The test sets the + breakpoint just before this happens, and GDB will not be able to + show the backtrace without JIT reader. */ + swap_value ((void **) MY_FRAME, previous_fp); /* break backtrace-broken */ +} + +static void +corrupt_frame_outer (void) +{ + /* See above for the explanation of the code here. This function + corrupts its frame, too, and then calls the inner one. */ + void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME); + if (&previous_fp + 1 != (void **) MY_FRAME) + bad_layout (&previous_fp, MY_FRAME); + corrupt_frame_inner (); + swap_value ((void **) MY_FRAME, previous_fp); +} + +int +main () +{ + corrupt_frame_outer (); + return 0; +} diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp new file mode 100644 index 0000000..53d6746 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind.exp @@ -0,0 +1,54 @@ +# Copyright (C) 2015 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 + +standard_testfile + +if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } { + return -1 +} + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +# This test runs on a specific platform. +if { ! [istarget x86_64-*]} { continue } + +# The following tests require execution. + +if ![runto_main] then { + fail "Can't run to main" + return 0 +} + +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] + +gdb_breakpoint [gdb_get_line_number "break backtrace-broken"] + +gdb_test "source ${pyfile}" "Python script imported" \ + "import python scripts" + +gdb_continue_to_breakpoint "break backtrace-broken" +gdb_test_sequence "where" "Backtrace restored by unwinder" { + "\\r\\n#0 .* corrupt_frame_inner \\(\\) at " + "\\r\\n#1 .* corrupt_frame_outer \\(\\) at " + "\\r\\n#2 .* main \\(.*\\) at" +} + + diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py new file mode 100644 index 0000000..6257fd7 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-unwind.py @@ -0,0 +1,99 @@ +# Copyright (C) 2015 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/>. + +import gdb +from gdb.unwinder import Unwinder + +class FrameId(object): + + def __init__(self, sp, pc): + self._sp = sp + self._pc = pc + + @property + def sp(self): + return self._sp + + @property + def pc(self): + return self._pc + + +class TestUnwinder(Unwinder): + AMD64_RBP = 6 + AMD64_RSP = 7 + AMD64_RIP = 16 + + def __init__(self): + Unwinder.__init__(self, "test unwinder") + self.char_ptr_t = gdb.lookup_type("unsigned char").pointer() + self.char_ptr_ptr_t = self.char_ptr_t.pointer() + + def _read_word(self, address): + return address.cast(self.char_ptr_ptr_t).dereference() + + def __call__(self, pending_frame): + """Test unwinder written in Python. + + This unwinder can unwind the frames that have been deliberately + corrupted in a specific way (functions in the accompanying + py-unwind.c file do that.) + This code is only on AMD64. + On AMD64 $RBP points to the innermost frame (unless the code + was compiled with -fomit-frame-pointer), which contains the + address of the previous frame at offset 0. The functions + deliberately corrupt their frames as follows: + Before After + Corruption: Corruption: + +--------------+ +--------------+ + RBP-8 | | | Previous RBP | + +--------------+ +--------------+ + RBP + Previous RBP | | RBP | + +--------------+ +--------------+ + RBP+8 | Return RIP | | Return RIP | + +--------------+ +--------------+ + Old SP | | | | + + This unwinder recognizes the corrupt frames by checking that + *RBP == RBP, and restores previous RBP from the word above it. + """ + try: + # NOTE: the registers in Unwinder API can be referenced + # either by name or by number. The code below uses both + # to achieve more coverage. + bp = pending_frame.read_register("rbp").cast(self.char_ptr_t) + if self._read_word(bp) != bp: + return None + # Found the frame that the test program has corrupted for us. + # The correct BP for the outer frame has been saved one word + # above, previous IP and SP are at the expected places. + previous_bp = self._read_word(bp - 8) + previous_ip = self._read_word(bp + 8) + previous_sp = bp + 16 + + frame_id = FrameId( + pending_frame.read_register(TestUnwinder.AMD64_RSP), + pending_frame.read_register(TestUnwinder.AMD64_RIP)) + unwind_info = pending_frame.create_unwind_info(frame_id) + unwind_info.add_saved_register(TestUnwinder.AMD64_RBP, + previous_bp) + unwind_info.add_saved_register("rip", previous_ip) + unwind_info.add_saved_register("rsp", previous_sp) + return unwind_info + except (gdb.error, RuntimeError): + return None + +gdb.unwinder.register_unwinder(None, TestUnwinder(), True) +print("Python script imported") |