1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
# 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/>.
import gdb
from gdb.unwinder import Unwinder
# These are set to test whether invalid register names cause an error.
add_saved_register_error = False
read_register_error = False
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 = None
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()
self._last_arch = None
# Update the register descriptor AMD64_RIP based on ARCH.
def _update_register_descriptors(self, arch):
if self._last_arch != arch:
TestUnwinder.AMD64_RIP = arch.registers().find("rip")
self._last_arch = arch
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.
"""
# Check that we can access the architecture of the pending
# frame, and that this is the same architecture as for the
# currently selected inferior.
inf_arch = gdb.selected_inferior().architecture()
frame_arch = pending_frame.architecture()
if inf_arch != frame_arch:
raise gdb.GdbError("architecture mismatch")
self._update_register_descriptors(frame_arch)
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
try:
pending_frame.read_register("nosuchregister")
except ValueError:
global read_register_error
read_register_error = True
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)
try:
unwind_info.add_saved_register("nosuchregister", previous_sp)
except ValueError:
global add_saved_register_error
add_saved_register_error = True
return unwind_info
except (gdb.error, RuntimeError):
return None
gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
print("Python script imported")
|