| 1 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | # |
| 3 | # A simple interface module built around pygdbmi for handling GDB commands. |
| 4 | # |
| 5 | # Copyright (c) 2025 Linaro Limited |
| 6 | # |
| 7 | # Author: |
| 8 | # Gustavo Romero <gustavo.romero@linaro.org> |
| 9 | # |
| 10 | |
| 11 | import re |
| 12 | |
| 13 | |
| 14 | class GDB: |
| 15 | """Provides methods to run and capture GDB command output.""" |
| 16 | |
| 17 | |
| 18 | def __init__(self, gdb_path, echo=True, suffix='# ', prompt="$ "): |
| 19 | from pygdbmi.gdbcontroller import GdbController |
| 20 | from pygdbmi.constants import GdbTimeoutError |
| 21 | type(self).TimeoutError = GdbTimeoutError |
| 22 | |
| 23 | gdb_cmd = [gdb_path, "-q", "--interpreter=mi2"] |
| 24 | self.gdbmi = GdbController(gdb_cmd) |
| 25 | self.echo = echo |
| 26 | self.suffix = suffix |
| 27 | self.prompt = prompt |
| 28 | self.response = None |
| 29 | self.cmd_output = None |
| 30 | |
| 31 | |
| 32 | def get_payload(self, response, kind): |
| 33 | output = [] |
| 34 | for o in response: |
| 35 | # Unpack payloads of the same type. |
| 36 | _type, _, payload, *_ = o.values() |
| 37 | if _type == kind: |
| 38 | output += [payload] |
| 39 | |
| 40 | # Some output lines do not end with \n but begin with it, |
| 41 | # so remove the leading \n and merge them with the next line |
| 42 | # that ends with \n. |
| 43 | lines = [line.lstrip('\n') for line in output] |
| 44 | lines = "".join(lines) |
| 45 | lines = lines.splitlines(keepends=True) |
| 46 | |
| 47 | return lines |
| 48 | |
| 49 | |
| 50 | def cli(self, cmd, timeout=32.0): |
| 51 | self.response = self.gdbmi.write(cmd, timeout_sec=timeout) |
| 52 | self.cmd_output = self.get_payload(self.response, kind="console") |
| 53 | if self.echo: |
| 54 | print(self.suffix + self.prompt + cmd) |
| 55 | |
| 56 | if len(self.cmd_output) > 0: |
| 57 | cmd_output = self.suffix.join(self.cmd_output) |
| 58 | print(self.suffix + cmd_output, end="") |
| 59 | |
| 60 | return self |
| 61 | |
| 62 | |
| 63 | def get_addr(self): |
| 64 | address_pattern = r"0x[0-9A-Fa-f]+" |
| 65 | cmd_output = "".join(self.cmd_output) # Concat output lines. |
| 66 | |
| 67 | match = re.search(address_pattern, cmd_output) |
| 68 | |
| 69 | return int(match[0], 16) if match else None |
| 70 | |
| 71 | |
| 72 | def get_log(self): |
| 73 | r = self.get_payload(self.response, kind="log") |
| 74 | r = "".join(r) |
| 75 | |
| 76 | return r |
| 77 | |
| 78 | |
| 79 | def get_console(self): |
| 80 | r = "".join(self.cmd_output) |
| 81 | |
| 82 | return r |
| 83 | |
| 84 | |
| 85 | def exit(self): |
| 86 | self.gdbmi.exit() |