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