]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blob - gdb/testsuite/gdb.python/py-unwind.py
Update copyright year range in header of all files managed by GDB
[thirdparty/binutils-gdb.git] / gdb / testsuite / gdb.python / py-unwind.py
1 # Copyright (C) 2015-2024 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
16 import gdb
17 from gdb.unwinder import Unwinder, FrameId
18
19
20 # These are set to test whether invalid register names cause an error.
21 add_saved_register_errors = {}
22 read_register_error = False
23
24
25 class TestUnwinder(Unwinder):
26 AMD64_RBP = 6
27 AMD64_RSP = 7
28 AMD64_RIP = None
29
30 def __init__(self):
31 Unwinder.__init__(self, "test unwinder")
32 self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
33 self.char_ptr_ptr_t = self.char_ptr_t.pointer()
34 self._last_arch = None
35
36 # Update the register descriptor AMD64_RIP based on ARCH.
37 def _update_register_descriptors(self, arch):
38 if self._last_arch != arch:
39 TestUnwinder.AMD64_RIP = arch.registers().find("rip")
40 self._last_arch = arch
41
42 def _read_word(self, address):
43 return address.cast(self.char_ptr_ptr_t).dereference()
44
45 def __call__(self, pending_frame):
46 """Test unwinder written in Python.
47
48 This unwinder can unwind the frames that have been deliberately
49 corrupted in a specific way (functions in the accompanying
50 py-unwind.c file do that.)
51 This code is only on AMD64.
52 On AMD64 $RBP points to the innermost frame (unless the code
53 was compiled with -fomit-frame-pointer), which contains the
54 address of the previous frame at offset 0. The functions
55 deliberately corrupt their frames as follows:
56 Before After
57 Corruption: Corruption:
58 +--------------+ +--------------+
59 RBP-8 | | | Previous RBP |
60 +--------------+ +--------------+
61 RBP + Previous RBP | | RBP |
62 +--------------+ +--------------+
63 RBP+8 | Return RIP | | Return RIP |
64 +--------------+ +--------------+
65 Old SP | | | |
66
67 This unwinder recognizes the corrupt frames by checking that
68 *RBP == RBP, and restores previous RBP from the word above it.
69 """
70
71 # Check that we can access the architecture of the pending
72 # frame, and that this is the same architecture as for the
73 # currently selected inferior.
74 inf_arch = gdb.selected_inferior().architecture()
75 frame_arch = pending_frame.architecture()
76 if inf_arch != frame_arch:
77 raise gdb.GdbError("architecture mismatch")
78
79 self._update_register_descriptors(frame_arch)
80
81 try:
82 # NOTE: the registers in Unwinder API can be referenced
83 # either by name or by number. The code below uses both
84 # to achieve more coverage.
85 bp = pending_frame.read_register("rbp").cast(self.char_ptr_t)
86 if self._read_word(bp) != bp:
87 return None
88 # Found the frame that the test program has corrupted for us.
89 # The correct BP for the outer frame has been saved one word
90 # above, previous IP and SP are at the expected places.
91 previous_bp = self._read_word(bp - 8)
92 previous_ip = self._read_word(bp + 8)
93 previous_sp = bp + 16
94
95 try:
96 pending_frame.read_register("nosuchregister")
97 except ValueError as ve:
98 global read_register_error
99 read_register_error = str(ve)
100
101 frame_id = FrameId(
102 pending_frame.read_register(register=TestUnwinder.AMD64_RSP),
103 pending_frame.read_register(TestUnwinder.AMD64_RIP),
104 )
105 unwind_info = pending_frame.create_unwind_info(frame_id)
106 unwind_info.add_saved_register(TestUnwinder.AMD64_RBP, previous_bp)
107 unwind_info.add_saved_register(value=previous_ip, register="rip")
108 unwind_info.add_saved_register(register="rsp", value=previous_sp)
109
110 global add_saved_register_errors
111 try:
112 unwind_info.add_saved_register("nosuchregister", previous_sp)
113 except ValueError as ve:
114 add_saved_register_errors["unknown_name"] = str(ve)
115
116 try:
117 unwind_info.add_saved_register(999, previous_sp)
118 except ValueError as ve:
119 add_saved_register_errors["unknown_number"] = str(ve)
120
121 try:
122 unwind_info.add_saved_register("rsp", 1234)
123 except TypeError as ve:
124 add_saved_register_errors["bad_value"] = str(ve)
125
126 return unwind_info
127 except (gdb.error, RuntimeError):
128 return None
129
130
131 global_test_unwinder = TestUnwinder()
132 gdb.unwinder.register_unwinder(None, global_test_unwinder, True)
133
134 # These are filled in by the simple_unwinder class.
135 captured_pending_frame = None
136 captured_pending_frame_repr = None
137 captured_unwind_info = None
138 captured_unwind_info_repr = None
139
140
141 class simple_unwinder(Unwinder):
142 def __init__(self, name, sp=0x123, pc=0x456):
143 super().__init__(name)
144 self._sp = sp
145 self._pc = pc
146
147 def __call__(self, pending_frame):
148 global captured_pending_frame
149 global captured_pending_frame_repr
150 global captured_unwind_info
151 global captured_unwind_info_repr
152
153 assert pending_frame.is_valid()
154
155 if captured_pending_frame is None:
156 captured_pending_frame = pending_frame
157 captured_pending_frame_repr = repr(pending_frame)
158 fid = FrameId(self._sp, self._pc)
159 uw = pending_frame.create_unwind_info(frame_id=fid)
160 uw.add_saved_register("rip", gdb.Value(0x123))
161 uw.add_saved_register("rbp", gdb.Value(0x456))
162 uw.add_saved_register("rsp", gdb.Value(0x789))
163 captured_unwind_info = uw
164 captured_unwind_info_repr = repr(uw)
165 return None
166
167
168 # Return a dictionary of information about FRAME.
169 def capture_frame_information(frame):
170 name = frame.name()
171 level = frame.level()
172 language = frame.language()
173 function = frame.function()
174 architecture = frame.architecture()
175 pc = frame.pc()
176 sal = frame.find_sal()
177 try:
178 block = frame.block()
179 assert isinstance(block, gdb.Block)
180 except RuntimeError as rte:
181 assert str(rte) == "Cannot locate block for frame."
182 block = "RuntimeError: " + str(rte)
183
184 return {
185 "name": name,
186 "level": level,
187 "language": language,
188 "function": function,
189 "architecture": architecture,
190 "pc": pc,
191 "sal": sal,
192 "block": block,
193 }
194
195
196 # List of information about each frame. The index into this list is
197 # the frame level. This is populated by
198 # capture_all_frame_information.
199 all_frame_information = []
200
201
202 # Fill in the global ALL_FRAME_INFORMATION list.
203 def capture_all_frame_information():
204 global all_frame_information
205
206 all_frame_information = []
207
208 gdb.newest_frame().select()
209 frame = gdb.selected_frame()
210 count = 0
211
212 while frame is not None:
213 frame.select()
214 info = capture_frame_information(frame)
215 level = info["level"]
216 info["matched"] = False
217
218 while len(all_frame_information) <= level:
219 all_frame_information.append(None)
220
221 assert all_frame_information[level] is None
222 all_frame_information[level] = info
223
224 if frame.name == "main" or count > 10:
225 break
226
227 count += 1
228 frame = frame.older()
229
230
231 # Assert that every entry in the global ALL_FRAME_INFORMATION list was
232 # matched by the validating_unwinder.
233 def check_all_frame_information_matched():
234 global all_frame_information
235 for entry in all_frame_information:
236 assert entry["matched"]
237
238
239 # An unwinder that doesn't match any frames. What it does do is
240 # lookup information from the PendingFrame object and compare it
241 # against information stored in the global ALL_FRAME_INFORMATION list.
242 class validating_unwinder(Unwinder):
243 def __init__(self):
244 super().__init__("validating_unwinder")
245
246 def __call__(self, pending_frame):
247 info = capture_frame_information(pending_frame)
248 level = info["level"]
249
250 global all_frame_information
251 old_info = all_frame_information[level]
252
253 assert old_info is not None
254 assert not old_info["matched"]
255
256 for key, value in info.items():
257 assert key in old_info, key + " not in old_info"
258 assert type(value) == type(old_info[key])
259 if isinstance(value, gdb.Block):
260 assert value.start == old_info[key].start
261 assert value.end == old_info[key].end
262 assert value.is_static == old_info[key].is_static
263 assert value.is_global == old_info[key].is_global
264 else:
265 assert str(value) == str(old_info[key])
266
267 old_info["matched"] = True
268 return None
269
270
271 print("Python script imported")