1 # Copyright (C) 2015-2024 Free Software Foundation, Inc.
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.
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.
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/>.
17 from gdb
.unwinder
import Unwinder
, FrameId
20 # These are set to test whether invalid register names cause an error.
21 add_saved_register_errors
= {}
22 read_register_error
= False
25 class TestUnwinder(Unwinder
):
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
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
42 def _read_word(self
, address
):
43 return address
.cast(self
.char_ptr_ptr_t
).dereference()
45 def __call__(self
, pending_frame
):
46 """Test unwinder written in Python.
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:
57 Corruption: Corruption:
58 +--------------+ +--------------+
59 RBP-8 | | | Previous RBP |
60 +--------------+ +--------------+
61 RBP + Previous RBP | | RBP |
62 +--------------+ +--------------+
63 RBP+8 | Return RIP | | Return RIP |
64 +--------------+ +--------------+
67 This unwinder recognizes the corrupt frames by checking that
68 *RBP == RBP, and restores previous RBP from the word above it.
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")
79 self
._update
_register
_descriptors
(frame_arch
)
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
:
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)
96 pending_frame
.read_register("nosuchregister")
97 except ValueError as ve
:
98 global read_register_error
99 read_register_error
= str(ve
)
102 pending_frame
.read_register(register
=TestUnwinder
.AMD64_RSP
),
103 pending_frame
.read_register(TestUnwinder
.AMD64_RIP
),
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
)
110 global add_saved_register_errors
112 unwind_info
.add_saved_register("nosuchregister", previous_sp
)
113 except ValueError as ve
:
114 add_saved_register_errors
["unknown_name"] = str(ve
)
117 unwind_info
.add_saved_register(999, previous_sp
)
118 except ValueError as ve
:
119 add_saved_register_errors
["unknown_number"] = str(ve
)
122 unwind_info
.add_saved_register("rsp", 1234)
123 except TypeError as ve
:
124 add_saved_register_errors
["bad_value"] = str(ve
)
127 except (gdb
.error
, RuntimeError):
131 global_test_unwinder
= TestUnwinder()
132 gdb
.unwinder
.register_unwinder(None, global_test_unwinder
, True)
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
141 class simple_unwinder(Unwinder
):
142 def __init__(self
, name
, sp
=0x123, pc
=0x456):
143 super().__init
__(name
)
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
153 assert pending_frame
.is_valid()
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
)
168 # Return a dictionary of information about FRAME.
169 def capture_frame_information(frame
):
171 level
= frame
.level()
172 language
= frame
.language()
173 function
= frame
.function()
174 architecture
= frame
.architecture()
176 sal
= frame
.find_sal()
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
)
187 "language": language
,
188 "function": function
,
189 "architecture": architecture
,
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
= []
202 # Fill in the global ALL_FRAME_INFORMATION list.
203 def capture_all_frame_information():
204 global all_frame_information
206 all_frame_information
= []
208 gdb
.newest_frame().select()
209 frame
= gdb
.selected_frame()
212 while frame
is not None:
214 info
= capture_frame_information(frame
)
215 level
= info
["level"]
216 info
["matched"] = False
218 while len(all_frame_information
) <= level
:
219 all_frame_information
.append(None)
221 assert all_frame_information
[level
] is None
222 all_frame_information
[level
] = info
224 if frame
.name
== "main" or count
> 10:
228 frame
= frame
.older()
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"]
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
):
244 super().__init
__("validating_unwinder")
246 def __call__(self
, pending_frame
):
247 info
= capture_frame_information(pending_frame
)
248 level
= info
["level"]
250 global all_frame_information
251 old_info
= all_frame_information
[level
]
253 assert old_info
is not None
254 assert not old_info
["matched"]
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
265 assert str(value
) == str(old_info
[key
])
267 old_info
["matched"] = True
271 print("Python script imported")