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