Add GDB class, which provides methods to run and capture GDB command
output. The GDB class is a wrapper around the pygdbmi module and
interacts with GDB via GDB's machine interface (MI).
Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
tests/functional/qemu_test/__init__.py | 1 +
tests/functional/qemu_test/gdb.py | 85 ++++++++++++++++++++++++++
2 files changed, 86 insertions(+)
create mode 100644 tests/functional/qemu_test/gdb.py
diff --git a/tests/functional/qemu_test/__init__.py b/tests/functional/qemu_test/__init__.py
index 6e666a059f..60d19891bf 100644
--- a/tests/functional/qemu_test/__init__.py
+++ b/tests/functional/qemu_test/__init__.py
@@ -18,3 +18,4 @@
skipIfMissingImports, skipIfOperatingSystem, skipLockedMemoryTest
from .archive import archive_extract
from .uncompress import uncompress
+from .gdb import GDB
diff --git a/tests/functional/qemu_test/gdb.py b/tests/functional/qemu_test/gdb.py
new file mode 100644
index 0000000000..15eb5eca18
--- /dev/null
+++ b/tests/functional/qemu_test/gdb.py
@@ -0,0 +1,85 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# A simple interface module built around pygdbmi for handling GDB commands.
+#
+# Copyright (c) 2025 Linaro Limited
+#
+# Author:
+# Gustavo Romero <gustavo.romero@linaro.org>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later. See the COPYING file in the top-level directory.
+
+import re
+from pygdbmi.gdbcontroller import GdbController
+
+
+class GDB:
+ """Provides methods to run and capture GDB command output."""
+
+
+ def __init__(self, gdb_path, echo=True, suffix='# ', prompt="$ "):
+ gdb_cmd = [gdb_path, "-q", "--interpreter=mi2"]
+ self.gdbmi = GdbController(gdb_cmd)
+ self.echo = echo
+ self.suffix = suffix
+ self.prompt = prompt
+ self.response = None
+ self.cmd_output = None
+
+
+ def get_payload(self, response, kind):
+ output = []
+ for o in response:
+ # Unpack payloads of the same type.
+ _type, _, payload, *_ = o.values()
+ if _type == kind:
+ output += [payload]
+
+ # Some output lines do not end with \n but begin with it,
+ # so remove the leading \n and merge them with the next line
+ # that ends with \n.
+ lines = [line.lstrip('\n') for line in output]
+ lines = "".join(lines)
+ lines = lines.splitlines(keepends=True)
+
+ return lines
+
+
+ def cli(self, cmd, timeout=4.0):
+ self.response = self.gdbmi.write(cmd, timeout_sec=timeout)
+ self.cmd_output = self.get_payload(self.response, "console")
+ if self.echo:
+ print(self.suffix + self.prompt + cmd)
+
+ if len(self.cmd_output) > 0:
+ cmd_output = self.suffix.join(self.cmd_output)
+ print(self.suffix + cmd_output, end="")
+
+ return self
+
+
+ def get_addr(self):
+ address_pattern = r"0x[0-9A-Fa-f]+"
+ cmd_output = "".join(self.cmd_output) # Concat output lines.
+
+ match = re.search(address_pattern, cmd_output)
+
+ return int(match[0], 16) if match else None
+
+
+ def get_log(self):
+ r = self.get_payload(self.response, kind="log")
+ r = "".join(r)
+
+ return r
+
+
+ def get_console(self):
+ r = "".join(self.cmd_output)
+
+ return r
+
+
+ def exit(self):
+ self.gdbmi.exit()
--
2.34.1