xref: /netbsd-src/external/gpl3/gdb.old/dist/gdb/testsuite/gdb.python/py-unwind.py (revision 4724848cf0da353df257f730694b7882798e5daf)
1# Copyright (C) 2015-2020 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
16import gdb
17from gdb.unwinder import Unwinder
18
19class FrameId(object):
20
21    def __init__(self, sp, pc):
22        self._sp = sp
23        self._pc = pc
24
25    @property
26    def sp(self):
27        return self._sp
28
29    @property
30    def pc(self):
31        return self._pc
32
33class TestUnwinder(Unwinder):
34    AMD64_RBP = 6
35    AMD64_RSP = 7
36    AMD64_RIP = None
37
38    def __init__(self):
39        Unwinder.__init__(self, "test unwinder")
40        self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
41        self.char_ptr_ptr_t = self.char_ptr_t.pointer()
42        self._last_arch = None
43
44    # Update the register descriptor AMD64_RIP based on ARCH.
45    def _update_register_descriptors (self, arch):
46        if (self._last_arch != arch):
47            TestUnwinder.AMD64_RIP = arch.registers ().find ("rip")
48            self._last_arch = arch
49
50    def _read_word(self, address):
51        return address.cast(self.char_ptr_ptr_t).dereference()
52
53    def __call__(self, pending_frame):
54        """Test unwinder written in Python.
55
56        This unwinder can unwind the frames that have been deliberately
57        corrupted in a specific way (functions in the accompanying
58        py-unwind.c file do that.)
59        This code is only on AMD64.
60        On AMD64 $RBP points to the innermost frame (unless the code
61        was compiled with -fomit-frame-pointer), which contains the
62        address of the previous frame at offset 0. The functions
63        deliberately corrupt their frames as follows:
64                     Before                 After
65                   Corruption:           Corruption:
66                +--------------+       +--------------+
67        RBP-8   |              |       | Previous RBP |
68                +--------------+       +--------------+
69        RBP     + Previous RBP |       |    RBP       |
70                +--------------+       +--------------+
71        RBP+8   | Return RIP   |       | Return  RIP  |
72                +--------------+       +--------------+
73        Old SP  |              |       |              |
74
75        This unwinder recognizes the corrupt frames by checking that
76        *RBP == RBP, and restores previous RBP from the word above it.
77        """
78
79        # Check that we can access the architecture of the pending
80        # frame, and that this is the same architecture as for the
81        # currently selected inferior.
82        inf_arch = gdb.selected_inferior ().architecture ()
83        frame_arch = pending_frame.architecture ()
84        if (inf_arch != frame_arch):
85            raise gdb.GdbError ("architecture mismatch")
86
87        self._update_register_descriptors (frame_arch)
88
89        try:
90            # NOTE: the registers in Unwinder API can be referenced
91            # either by name or by number. The code below uses both
92            # to achieve more coverage.
93            bp = pending_frame.read_register("rbp").cast(self.char_ptr_t)
94            if self._read_word(bp) != bp:
95                return None
96            # Found the frame that the test program has corrupted for us.
97            # The correct BP for the outer frame has been saved one word
98            # above, previous IP and SP are at the expected places.
99            previous_bp = self._read_word(bp - 8)
100            previous_ip = self._read_word(bp + 8)
101            previous_sp = bp + 16
102
103            frame_id = FrameId(
104                pending_frame.read_register(TestUnwinder.AMD64_RSP),
105                pending_frame.read_register(TestUnwinder.AMD64_RIP))
106            unwind_info = pending_frame.create_unwind_info(frame_id)
107            unwind_info.add_saved_register(TestUnwinder.AMD64_RBP,
108                                           previous_bp)
109            unwind_info.add_saved_register("rip", previous_ip)
110            unwind_info.add_saved_register("rsp", previous_sp)
111            return unwind_info
112        except (gdb.error, RuntimeError):
113            return None
114
115gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
116print("Python script imported")
117