]>
Commit | Line | Data |
---|---|---|
15e15b2d AB |
1 | # Copyright (C) 2021-2022 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 | import gdb.disassembler | |
18 | import struct | |
19 | import sys | |
20 | ||
21 | from gdb.disassembler import Disassembler, DisassemblerResult | |
22 | ||
23 | # A global, holds the program-counter address at which we should | |
24 | # perform the extra disassembly that this script provides. | |
25 | current_pc = None | |
26 | ||
27 | ||
28 | # Remove all currently registered disassemblers. | |
29 | def remove_all_python_disassemblers(): | |
30 | for a in gdb.architecture_names(): | |
31 | gdb.disassembler.register_disassembler(None, a) | |
32 | gdb.disassembler.register_disassembler(None, None) | |
33 | ||
34 | ||
35 | class TestDisassembler(Disassembler): | |
36 | """A base class for disassemblers within this script to inherit from. | |
37 | Implements the __call__ method and ensures we only do any | |
38 | disassembly wrapping for the global CURRENT_PC.""" | |
39 | ||
40 | def __init__(self): | |
41 | global current_pc | |
42 | ||
43 | super().__init__("TestDisassembler") | |
44 | self.__info = None | |
45 | if current_pc == None: | |
46 | raise gdb.GdbError("no current_pc set") | |
47 | ||
48 | def __call__(self, info): | |
49 | global current_pc | |
50 | ||
51 | if info.address != current_pc: | |
52 | return None | |
53 | self.__info = info | |
54 | return self.disassemble(info) | |
55 | ||
56 | def get_info(self): | |
57 | return self.__info | |
58 | ||
59 | def disassemble(self, info): | |
60 | raise NotImplementedError("override the disassemble method") | |
61 | ||
62 | ||
63 | class GlobalPreInfoDisassembler(TestDisassembler): | |
64 | """Check the attributes of DisassembleInfo before disassembly has occurred.""" | |
65 | ||
66 | def disassemble(self, info): | |
67 | ad = info.address | |
68 | ar = info.architecture | |
69 | ||
70 | if ad != current_pc: | |
71 | raise gdb.GdbError("invalid address") | |
72 | ||
73 | if not isinstance(ar, gdb.Architecture): | |
74 | raise gdb.GdbError("invalid architecture type") | |
75 | ||
76 | result = gdb.disassembler.builtin_disassemble(info) | |
77 | ||
78 | text = result.string + "\t## ad = 0x%x, ar = %s" % (ad, ar.name()) | |
79 | return DisassemblerResult(result.length, text) | |
80 | ||
81 | ||
82 | class GlobalPostInfoDisassembler(TestDisassembler): | |
83 | """Check the attributes of DisassembleInfo after disassembly has occurred.""" | |
84 | ||
85 | def disassemble(self, info): | |
86 | result = gdb.disassembler.builtin_disassemble(info) | |
87 | ||
88 | ad = info.address | |
89 | ar = info.architecture | |
90 | ||
91 | if ad != current_pc: | |
92 | raise gdb.GdbError("invalid address") | |
93 | ||
94 | if not isinstance(ar, gdb.Architecture): | |
95 | raise gdb.GdbError("invalid architecture type") | |
96 | ||
97 | text = result.string + "\t## ad = 0x%x, ar = %s" % (ad, ar.name()) | |
98 | return DisassemblerResult(result.length, text) | |
99 | ||
100 | ||
101 | class GlobalReadDisassembler(TestDisassembler): | |
102 | """Check the DisassembleInfo.read_memory method. Calls the builtin | |
103 | disassembler, then reads all of the bytes of this instruction, and | |
104 | adds them as a comment to the disassembler output.""" | |
105 | ||
106 | def disassemble(self, info): | |
107 | result = gdb.disassembler.builtin_disassemble(info) | |
108 | len = result.length | |
109 | str = "" | |
110 | for o in range(len): | |
111 | if str != "": | |
112 | str += " " | |
113 | v = bytes(info.read_memory(1, o))[0] | |
114 | if sys.version_info[0] < 3: | |
115 | v = struct.unpack("<B", v) | |
116 | str += "0x%02x" % v | |
117 | text = result.string + "\t## bytes = %s" % str | |
118 | return DisassemblerResult(result.length, text) | |
119 | ||
120 | ||
121 | class GlobalAddrDisassembler(TestDisassembler): | |
122 | """Check the gdb.format_address method.""" | |
123 | ||
124 | def disassemble(self, info): | |
125 | result = gdb.disassembler.builtin_disassemble(info) | |
126 | arch = info.architecture | |
127 | addr = info.address | |
128 | program_space = info.progspace | |
129 | str = gdb.format_address(addr, program_space, arch) | |
130 | text = result.string + "\t## addr = %s" % str | |
131 | return DisassemblerResult(result.length, text) | |
132 | ||
133 | ||
134 | class GdbErrorEarlyDisassembler(TestDisassembler): | |
135 | """Raise a GdbError instead of performing any disassembly.""" | |
136 | ||
137 | def disassemble(self, info): | |
138 | raise gdb.GdbError("GdbError instead of a result") | |
139 | ||
140 | ||
141 | class RuntimeErrorEarlyDisassembler(TestDisassembler): | |
142 | """Raise a RuntimeError instead of performing any disassembly.""" | |
143 | ||
144 | def disassemble(self, info): | |
145 | raise RuntimeError("RuntimeError instead of a result") | |
146 | ||
147 | ||
148 | class GdbErrorLateDisassembler(TestDisassembler): | |
149 | """Raise a GdbError after calling the builtin disassembler.""" | |
150 | ||
151 | def disassemble(self, info): | |
152 | result = gdb.disassembler.builtin_disassemble(info) | |
153 | raise gdb.GdbError("GdbError after builtin disassembler") | |
154 | ||
155 | ||
156 | class RuntimeErrorLateDisassembler(TestDisassembler): | |
157 | """Raise a RuntimeError after calling the builtin disassembler.""" | |
158 | ||
159 | def disassemble(self, info): | |
160 | result = gdb.disassembler.builtin_disassemble(info) | |
161 | raise RuntimeError("RuntimeError after builtin disassembler") | |
162 | ||
163 | ||
164 | class MemoryErrorEarlyDisassembler(TestDisassembler): | |
165 | """Throw a memory error, ignore the error and disassemble.""" | |
166 | ||
167 | def disassemble(self, info): | |
168 | tag = "## FAIL" | |
169 | try: | |
170 | info.read_memory(1, -info.address + 2) | |
171 | except gdb.MemoryError: | |
172 | tag = "## AFTER ERROR" | |
173 | result = gdb.disassembler.builtin_disassemble(info) | |
174 | text = result.string + "\t" + tag | |
175 | return DisassemblerResult(result.length, text) | |
176 | ||
177 | ||
178 | class MemoryErrorLateDisassembler(TestDisassembler): | |
179 | """Throw a memory error after calling the builtin disassembler, but | |
180 | before we return a result.""" | |
181 | ||
182 | def disassemble(self, info): | |
183 | result = gdb.disassembler.builtin_disassemble(info) | |
184 | # The following read will throw an error. | |
185 | info.read_memory(1, -info.address + 2) | |
186 | return DisassemblerResult(1, "BAD") | |
187 | ||
188 | ||
189 | class RethrowMemoryErrorDisassembler(TestDisassembler): | |
190 | """Catch and rethrow a memory error.""" | |
191 | ||
192 | def disassemble(self, info): | |
193 | try: | |
194 | info.read_memory(1, -info.address + 2) | |
195 | except gdb.MemoryError as e: | |
196 | raise gdb.MemoryError("cannot read code at address 0x2") | |
197 | return DisassemblerResult(1, "BAD") | |
198 | ||
199 | ||
200 | class ResultOfWrongType(TestDisassembler): | |
201 | """Return something that is not a DisassemblerResult from disassemble method""" | |
202 | ||
203 | class Blah: | |
204 | def __init__(self, length, string): | |
205 | self.length = length | |
206 | self.string = string | |
207 | ||
208 | def disassemble(self, info): | |
209 | return self.Blah(1, "ABC") | |
210 | ||
211 | ||
212 | class ResultWrapper(gdb.disassembler.DisassemblerResult): | |
213 | def __init__(self, length, string, length_x=None, string_x=None): | |
214 | super().__init__(length, string) | |
215 | if length_x is None: | |
216 | self.__length = length | |
217 | else: | |
218 | self.__length = length_x | |
219 | if string_x is None: | |
220 | self.__string = string | |
221 | else: | |
222 | self.__string = string_x | |
223 | ||
224 | @property | |
225 | def length(self): | |
226 | return self.__length | |
227 | ||
228 | @property | |
229 | def string(self): | |
230 | return self.__string | |
231 | ||
232 | ||
233 | class ResultWithInvalidLength(TestDisassembler): | |
234 | """Return a result object with an invalid length.""" | |
235 | ||
236 | def disassemble(self, info): | |
237 | result = gdb.disassembler.builtin_disassemble(info) | |
238 | return ResultWrapper(result.length, result.string, 0) | |
239 | ||
240 | ||
241 | class ResultWithInvalidString(TestDisassembler): | |
242 | """Return a result object with an empty string.""" | |
243 | ||
244 | def disassemble(self, info): | |
245 | result = gdb.disassembler.builtin_disassemble(info) | |
246 | return ResultWrapper(result.length, result.string, None, "") | |
247 | ||
248 | ||
249 | class TaggingDisassembler(TestDisassembler): | |
250 | """A simple disassembler that just tags the output.""" | |
251 | ||
252 | def __init__(self, tag): | |
253 | super().__init__() | |
254 | self._tag = tag | |
255 | ||
256 | def disassemble(self, info): | |
257 | result = gdb.disassembler.builtin_disassemble(info) | |
258 | text = result.string + "\t## tag = %s" % self._tag | |
259 | return DisassemblerResult(result.length, text) | |
260 | ||
261 | ||
262 | class GlobalCachingDisassembler(TestDisassembler): | |
263 | """A disassembler that caches the DisassembleInfo that is passed in, | |
264 | as well as a copy of the original DisassembleInfo. | |
265 | ||
266 | Once the call into the disassembler is complete then the | |
267 | DisassembleInfo objects become invalid, and any calls into them | |
268 | should trigger an exception.""" | |
269 | ||
270 | # This is where we cache the DisassembleInfo objects. | |
271 | cached_insn_disas = [] | |
272 | ||
273 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
274 | def __init__(self, info): | |
275 | super().__init__(info) | |
276 | ||
277 | def disassemble(self, info): | |
278 | """Disassemble the instruction, add a CACHED comment to the output, | |
279 | and cache the DisassembleInfo so that it is not garbage collected.""" | |
280 | GlobalCachingDisassembler.cached_insn_disas.append(info) | |
281 | GlobalCachingDisassembler.cached_insn_disas.append(self.MyInfo(info)) | |
282 | result = gdb.disassembler.builtin_disassemble(info) | |
283 | text = result.string + "\t## CACHED" | |
284 | return DisassemblerResult(result.length, text) | |
285 | ||
286 | @staticmethod | |
287 | def check(): | |
288 | """Check that all of the methods on the cached DisassembleInfo trigger an | |
289 | exception.""" | |
290 | for info in GlobalCachingDisassembler.cached_insn_disas: | |
291 | assert isinstance(info, gdb.disassembler.DisassembleInfo) | |
292 | assert not info.is_valid() | |
293 | try: | |
294 | val = info.address | |
295 | raise gdb.GdbError("DisassembleInfo.address is still valid") | |
296 | except RuntimeError as e: | |
297 | assert str(e) == "DisassembleInfo is no longer valid." | |
298 | except: | |
299 | raise gdb.GdbError( | |
300 | "DisassembleInfo.address raised an unexpected exception" | |
301 | ) | |
302 | ||
303 | try: | |
304 | val = info.architecture | |
305 | raise gdb.GdbError("DisassembleInfo.architecture is still valid") | |
306 | except RuntimeError as e: | |
307 | assert str(e) == "DisassembleInfo is no longer valid." | |
308 | except: | |
309 | raise gdb.GdbError( | |
310 | "DisassembleInfo.architecture raised an unexpected exception" | |
311 | ) | |
312 | ||
313 | try: | |
314 | val = info.read_memory(1, 0) | |
315 | raise gdb.GdbError("DisassembleInfo.read is still valid") | |
316 | except RuntimeError as e: | |
317 | assert str(e) == "DisassembleInfo is no longer valid." | |
318 | except: | |
319 | raise gdb.GdbError( | |
320 | "DisassembleInfo.read raised an unexpected exception" | |
321 | ) | |
322 | ||
323 | print("PASS") | |
324 | ||
325 | ||
326 | class GlobalNullDisassembler(TestDisassembler): | |
327 | """A disassembler that does not change the output at all.""" | |
328 | ||
329 | def disassemble(self, info): | |
330 | pass | |
331 | ||
332 | ||
333 | class ReadMemoryMemoryErrorDisassembler(TestDisassembler): | |
334 | """Raise a MemoryError exception from the DisassembleInfo.read_memory | |
335 | method.""" | |
336 | ||
337 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
338 | def __init__(self, info): | |
339 | super().__init__(info) | |
340 | ||
341 | def read_memory(self, length, offset): | |
342 | # Throw a memory error with a specific address. We don't | |
343 | # expect this address to show up in the output though. | |
344 | raise gdb.MemoryError(0x1234) | |
345 | ||
346 | def disassemble(self, info): | |
347 | info = self.MyInfo(info) | |
348 | return gdb.disassembler.builtin_disassemble(info) | |
349 | ||
350 | ||
351 | class ReadMemoryGdbErrorDisassembler(TestDisassembler): | |
352 | """Raise a GdbError exception from the DisassembleInfo.read_memory | |
353 | method.""" | |
354 | ||
355 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
356 | def __init__(self, info): | |
357 | super().__init__(info) | |
358 | ||
359 | def read_memory(self, length, offset): | |
360 | raise gdb.GdbError("read_memory raised GdbError") | |
361 | ||
362 | def disassemble(self, info): | |
363 | info = self.MyInfo(info) | |
364 | return gdb.disassembler.builtin_disassemble(info) | |
365 | ||
366 | ||
367 | class ReadMemoryRuntimeErrorDisassembler(TestDisassembler): | |
368 | """Raise a RuntimeError exception from the DisassembleInfo.read_memory | |
369 | method.""" | |
370 | ||
371 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
372 | def __init__(self, info): | |
373 | super().__init__(info) | |
374 | ||
375 | def read_memory(self, length, offset): | |
376 | raise RuntimeError("read_memory raised RuntimeError") | |
377 | ||
378 | def disassemble(self, info): | |
379 | info = self.MyInfo(info) | |
380 | return gdb.disassembler.builtin_disassemble(info) | |
381 | ||
382 | ||
383 | class ReadMemoryCaughtMemoryErrorDisassembler(TestDisassembler): | |
384 | """Raise a MemoryError exception from the DisassembleInfo.read_memory | |
385 | method, catch this in the outer disassembler.""" | |
386 | ||
387 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
388 | def __init__(self, info): | |
389 | super().__init__(info) | |
390 | ||
391 | def read_memory(self, length, offset): | |
392 | raise gdb.MemoryError(0x1234) | |
393 | ||
394 | def disassemble(self, info): | |
395 | info = self.MyInfo(info) | |
396 | try: | |
397 | return gdb.disassembler.builtin_disassemble(info) | |
398 | except gdb.MemoryError: | |
399 | return None | |
400 | ||
401 | ||
402 | class ReadMemoryCaughtGdbErrorDisassembler(TestDisassembler): | |
403 | """Raise a GdbError exception from the DisassembleInfo.read_memory | |
404 | method, catch this in the outer disassembler.""" | |
405 | ||
406 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
407 | def __init__(self, info): | |
408 | super().__init__(info) | |
409 | ||
410 | def read_memory(self, length, offset): | |
411 | raise gdb.GdbError("exception message") | |
412 | ||
413 | def disassemble(self, info): | |
414 | info = self.MyInfo(info) | |
415 | try: | |
416 | return gdb.disassembler.builtin_disassemble(info) | |
417 | except gdb.GdbError as e: | |
418 | if e.args[0] == "exception message": | |
419 | return None | |
420 | raise e | |
421 | ||
422 | ||
423 | class ReadMemoryCaughtRuntimeErrorDisassembler(TestDisassembler): | |
424 | """Raise a RuntimeError exception from the DisassembleInfo.read_memory | |
425 | method, catch this in the outer disassembler.""" | |
426 | ||
427 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
428 | def __init__(self, info): | |
429 | super().__init__(info) | |
430 | ||
431 | def read_memory(self, length, offset): | |
432 | raise RuntimeError("exception message") | |
433 | ||
434 | def disassemble(self, info): | |
435 | info = self.MyInfo(info) | |
436 | try: | |
437 | return gdb.disassembler.builtin_disassemble(info) | |
438 | except RuntimeError as e: | |
439 | if e.args[0] == "exception message": | |
440 | return None | |
441 | raise e | |
442 | ||
443 | ||
444 | class MemorySourceNotABufferDisassembler(TestDisassembler): | |
445 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
446 | def __init__(self, info): | |
447 | super().__init__(info) | |
448 | ||
449 | def read_memory(self, length, offset): | |
450 | return 1234 | |
451 | ||
452 | def disassemble(self, info): | |
453 | info = self.MyInfo(info) | |
454 | return gdb.disassembler.builtin_disassemble(info) | |
455 | ||
456 | ||
457 | class MemorySourceBufferTooLongDisassembler(TestDisassembler): | |
458 | """The read memory returns too many bytes.""" | |
459 | ||
460 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
461 | def __init__(self, info): | |
462 | super().__init__(info) | |
463 | ||
464 | def read_memory(self, length, offset): | |
465 | buffer = super().read_memory(length, offset) | |
466 | # Create a new memory view made by duplicating BUFFER. This | |
467 | # will trigger an error as GDB expects a buffer of exactly | |
468 | # LENGTH to be returned, while this will return a buffer of | |
469 | # 2*LENGTH. | |
470 | return memoryview( | |
471 | bytes([int.from_bytes(x, "little") for x in (list(buffer[0:]) * 2)]) | |
472 | ) | |
473 | ||
474 | def disassemble(self, info): | |
475 | info = self.MyInfo(info) | |
476 | return gdb.disassembler.builtin_disassemble(info) | |
477 | ||
478 | ||
479 | class BuiltinDisassembler(Disassembler): | |
480 | """Just calls the builtin disassembler.""" | |
481 | ||
482 | def __init__(self): | |
483 | super().__init__("BuiltinDisassembler") | |
484 | ||
485 | def __call__(self, info): | |
486 | return gdb.disassembler.builtin_disassemble(info) | |
487 | ||
488 | ||
489 | class AnalyzingDisassembler(Disassembler): | |
490 | class MyInfo(gdb.disassembler.DisassembleInfo): | |
491 | """Wrapper around builtin DisassembleInfo type that overrides the | |
492 | read_memory method.""" | |
493 | ||
494 | def __init__(self, info, start, end, nop_bytes): | |
495 | """INFO is the DisassembleInfo we are wrapping. START and END are | |
496 | addresses, and NOP_BYTES should be a memoryview object. | |
497 | ||
498 | The length (END - START) should be the same as the length | |
499 | of NOP_BYTES. | |
500 | ||
501 | Any memory read requests outside the START->END range are | |
502 | serviced normally, but any attempt to read within the | |
503 | START->END range will return content from NOP_BYTES.""" | |
504 | super().__init__(info) | |
505 | self._start = start | |
506 | self._end = end | |
507 | self._nop_bytes = nop_bytes | |
508 | ||
509 | def _read_replacement(self, length, offset): | |
510 | """Return a slice of the buffer representing the replacement nop | |
511 | instructions.""" | |
512 | ||
513 | assert self._nop_bytes is not None | |
514 | rb = self._nop_bytes | |
515 | ||
516 | # If this request is outside of a nop instruction then we don't know | |
517 | # what to do, so just raise a memory error. | |
518 | if offset >= len(rb) or (offset + length) > len(rb): | |
519 | raise gdb.MemoryError("invalid length and offset combination") | |
520 | ||
521 | # Return only the slice of the nop instruction as requested. | |
522 | s = offset | |
523 | e = offset + length | |
524 | return rb[s:e] | |
525 | ||
526 | def read_memory(self, length, offset=0): | |
527 | """Callback used by the builtin disassembler to read the contents of | |
528 | memory.""" | |
529 | ||
530 | # If this request is within the region we are replacing with 'nop' | |
531 | # instructions, then call the helper function to perform that | |
532 | # replacement. | |
533 | if self._start is not None: | |
534 | assert self._end is not None | |
535 | if self.address >= self._start and self.address < self._end: | |
536 | return self._read_replacement(length, offset) | |
537 | ||
538 | # Otherwise, we just forward this request to the default read memory | |
539 | # implementation. | |
540 | return super().read_memory(length, offset) | |
541 | ||
542 | def __init__(self): | |
543 | """Constructor.""" | |
544 | super().__init__("AnalyzingDisassembler") | |
545 | ||
546 | # Details about the instructions found during the first disassembler | |
547 | # pass. | |
548 | self._pass_1_length = [] | |
549 | self._pass_1_insn = [] | |
550 | self._pass_1_address = [] | |
551 | ||
552 | # The start and end address for the instruction we will replace with | |
553 | # one or more 'nop' instructions during pass two. | |
554 | self._start = None | |
555 | self._end = None | |
556 | ||
557 | # The index in the _pass_1_* lists for where the nop instruction can | |
558 | # be found, also, the buffer of bytes that make up a nop instruction. | |
559 | self._nop_index = None | |
560 | self._nop_bytes = None | |
561 | ||
562 | # A flag that indicates if we are in the first or second pass of | |
563 | # this disassembler test. | |
564 | self._first_pass = True | |
565 | ||
566 | # The disassembled instructions collected during the second pass. | |
567 | self._pass_2_insn = [] | |
568 | ||
569 | # A copy of _pass_1_insn that has been modified to include the extra | |
570 | # 'nop' instructions we plan to insert during the second pass. This | |
571 | # is then checked against _pass_2_insn after the second disassembler | |
572 | # pass has completed. | |
573 | self._check = [] | |
574 | ||
575 | def __call__(self, info): | |
576 | """Called to perform the disassembly.""" | |
577 | ||
578 | # Override the info object, this provides access to our | |
579 | # read_memory function. | |
580 | info = self.MyInfo(info, self._start, self._end, self._nop_bytes) | |
581 | result = gdb.disassembler.builtin_disassemble(info) | |
582 | ||
583 | # Record some informaiton about the first 'nop' instruction we find. | |
584 | if self._nop_index is None and result.string == "nop": | |
585 | self._nop_index = len(self._pass_1_length) | |
586 | # The offset in the following read_memory call defaults to 0. | |
15e15b2d AB |
587 | self._nop_bytes = info.read_memory(result.length) |
588 | ||
589 | # Record information about each instruction that is disassembled. | |
590 | # This test is performed in two passes, and we need different | |
591 | # information in each pass. | |
592 | if self._first_pass: | |
593 | self._pass_1_length.append(result.length) | |
594 | self._pass_1_insn.append(result.string) | |
595 | self._pass_1_address.append(info.address) | |
596 | else: | |
597 | self._pass_2_insn.append(result.string) | |
598 | ||
599 | return result | |
600 | ||
601 | def find_replacement_candidate(self): | |
602 | """Call this after the first disassembly pass. This identifies a suitable | |
603 | instruction to replace with 'nop' instruction(s).""" | |
604 | ||
605 | if self._nop_index is None: | |
606 | raise gdb.GdbError("no nop was found") | |
607 | ||
608 | nop_idx = self._nop_index | |
609 | nop_length = self._pass_1_length[nop_idx] | |
610 | ||
611 | # First we look for an instruction that is larger than a nop | |
612 | # instruction, but whose length is an exact multiple of the nop | |
613 | # instruction's length. | |
614 | replace_idx = None | |
615 | for idx in range(len(self._pass_1_length)): | |
616 | if ( | |
617 | idx > 0 | |
618 | and idx != nop_idx | |
619 | and self._pass_1_insn[idx] != "nop" | |
620 | and self._pass_1_length[idx] > self._pass_1_length[nop_idx] | |
621 | and self._pass_1_length[idx] % self._pass_1_length[nop_idx] == 0 | |
622 | ): | |
623 | replace_idx = idx | |
624 | break | |
625 | ||
626 | # If we still don't have a replacement candidate, then search again, | |
627 | # this time looking for an instruciton that is the same length as a | |
628 | # nop instruction. | |
629 | if replace_idx is None: | |
630 | for idx in range(len(self._pass_1_length)): | |
631 | if ( | |
632 | idx > 0 | |
633 | and idx != nop_idx | |
634 | and self._pass_1_insn[idx] != "nop" | |
635 | and self._pass_1_length[idx] == self._pass_1_length[nop_idx] | |
636 | ): | |
637 | replace_idx = idx | |
638 | break | |
639 | ||
640 | # Weird, the nop instruction must be larger than every other | |
641 | # instruction, or all instructions are 'nop'? | |
642 | if replace_idx is None: | |
643 | raise gdb.GdbError("can't find an instruction to replace") | |
644 | ||
645 | # Record the instruction range that will be replaced with 'nop' | |
646 | # instructions, and mark that we are now on the second pass. | |
647 | self._start = self._pass_1_address[replace_idx] | |
648 | self._end = self._pass_1_address[replace_idx] + self._pass_1_length[replace_idx] | |
649 | self._first_pass = False | |
650 | print("Replace from 0x%x to 0x%x with NOP" % (self._start, self._end)) | |
651 | ||
652 | # Finally, build the expected result. Create the _check list, which | |
653 | # is a copy of _pass_1_insn, but replace the instruction we | |
654 | # identified above with a series of 'nop' instructions. | |
655 | self._check = list(self._pass_1_insn) | |
656 | nop_count = int(self._pass_1_length[replace_idx] / self._pass_1_length[nop_idx]) | |
657 | nops = ["nop"] * nop_count | |
658 | self._check[replace_idx : (replace_idx + 1)] = nops | |
659 | ||
660 | def check(self): | |
661 | """Call this after the second disassembler pass to validate the output.""" | |
662 | if self._check != self._pass_2_insn: | |
15e15b2d AB |
663 | raise gdb.GdbError("mismatch") |
664 | print("PASS") | |
665 | ||
666 | ||
667 | def add_global_disassembler(dis_class): | |
668 | """Create an instance of DIS_CLASS and register it as a global disassembler.""" | |
669 | dis = dis_class() | |
670 | gdb.disassembler.register_disassembler(dis, None) | |
671 | return dis | |
672 | ||
673 | ||
674 | class InvalidDisassembleInfo(gdb.disassembler.DisassembleInfo): | |
675 | """An attempt to create a DisassembleInfo sub-class without calling | |
676 | the parent class init method. | |
677 | ||
678 | Attempts to use instances of this class should throw an error | |
679 | saying that the DisassembleInfo is not valid, despite this class | |
680 | having all of the required attributes. | |
681 | ||
682 | The reason why this class will never be valid is that an internal | |
683 | field (within the C++ code) can't be initialized without calling | |
684 | the parent class init method.""" | |
685 | ||
686 | def __init__(self): | |
687 | assert current_pc is not None | |
688 | ||
689 | def is_valid(self): | |
690 | return True | |
691 | ||
692 | @property | |
693 | def address(self): | |
694 | global current_pc | |
695 | return current_pc | |
696 | ||
697 | @property | |
698 | def architecture(self): | |
699 | return gdb.selected_inferior().architecture() | |
700 | ||
701 | @property | |
702 | def progspace(self): | |
703 | return gdb.selected_inferior().progspace | |
704 | ||
705 | ||
706 | # Start with all disassemblers removed. | |
707 | remove_all_python_disassemblers() | |
708 | ||
709 | print("Python script imported") |