qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/4] tests/functional: Adapt reverse_debugging to run w/o Avocado
@ 2025-09-22  5:43 Gustavo Romero
  2025-09-22  5:43 ` [PATCH v3 1/4] python: Install pygdbmi in venv Gustavo Romero
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Gustavo Romero @ 2025-09-22  5:43 UTC (permalink / raw)
  To: qemu-devel, alex.bennee, thuth, berrange
  Cc: qemu-arm, gustavo.romero, manos.pitsidianakis, peter.maydell

tests/functional: Adapt reverse_debugging to run w/o Avocado

The goal of this series is to remove Avocado as a dependency for running
the reverse_debugging functional test.

After several rounds of discussions about v1 and v2, and experiments
done by Daniel and Thomas (thanks for all the experiments and comments
so far), I've taken a new approach and moved away from using a runner
for GDB. The changes, I believe, are much simpler now.

This new series uses GDB's machine interface (MI) via the pygdbmi module
(thanks Manos and Peter for the inputs). pygdbmi provides a controller
to start GDB and communicate with it through MI, so there is no longer a
risk of version clashes between libpython in GDB and Python modules in
the pyvenv, as it could, in theory, happen when GDB executes the test
script via -x option.

Also, as Daniel pointed out, the overall test output is pretty bad and
currently does not allow one to easily follow the sequence of GDB
commands used in the test. I took this opportunity to improve the output
and it now prints the sequence in a format that can be copied and pasted
directly into GDB. For instance, the test output for STEPS = 4 is:

TAP version 13
# $ set debug remote 1
# $ target remote localhost:62464
# Remote debugging using localhost:62464
# 0x0000000040000000 in ?? ()
# $ set debug remote 0
# $ print $pc
# $1 = (void (*)()) 0x40000000
# $ stepi
# 0x0000000040000004 in ?? ()
# $ print $pc
# $2 = (void (*)()) 0x40000004
# $ stepi
# 0x0000000040000008 in ?? ()
# $ print $pc
# $3 = (void (*)()) 0x40000008
# $ stepi
# 0x000000004000000c in ?? ()
# $ print $pc
# $4 = (void (*)()) 0x4000000c
# $ stepi
# 0x0000000040000010 in ?? ()
# $ reverse-stepi
# 0x000000004000000c in ?? ()
# $ print $pc
# $5 = (void (*)()) 0x4000000c
# $ reverse-stepi
# 0x0000000040000008 in ?? ()
# $ print $pc
# $6 = (void (*)()) 0x40000008
# $ reverse-stepi
# 0x0000000040000004 in ?? ()
# $ print $pc
# $7 = (void (*)()) 0x40000004
# $ reverse-stepi
# 0x0000000040000000 in ?? ()
# $ print $pc
# $8 = (void (*)()) 0x40000000
# $ print $pc
# $9 = (void (*)()) 0x40000000
# $ stepi
# 0x0000000040000004 in ?? ()
# $ print $pc
# $10 = (void (*)()) 0x40000004
# $ stepi
# 0x0000000040000008 in ?? ()
# $ print $pc
# $11 = (void (*)()) 0x40000008
# $ stepi
# 0x000000004000000c in ?? ()
# $ print $pc
# $12 = (void (*)()) 0x4000000c
# $ stepi
# 0x0000000040000010 in ?? ()
# $ break *0x40000000
# Breakpoint 1 at 0x40000000
# $ break *0x40000004
# Breakpoint 2 at 0x40000004
# $ break *0x40000008
# Breakpoint 3 at 0x40000008
# $ break *0x4000000c
# Breakpoint 4 at 0x4000000c
# $ continue
# Continuing.
# Program received signal SIGINT, Interrupt.
# 0xffff45a2feaa2050 in ?? ()
# $ print $pc
# $13 = (void (*)()) 0xffff45a2feaa2050
# **** Hit replay-break at icount=3691561, pc=0xffff45a2feaa2050 ****
# $ reverse-continue
# Continuing.
# Breakpoint 4, 0x000000004000000c in ?? ()
# $ print $pc
# $14 = (void (*)()) 0x4000000c
# **** Hit breakpoint at the first PC in reverse order (0x4000000c) ****
ok 1 test_reverse_debug.ReverseDebugging_AArch64.test_aarch64_virt
1..1

As can be observed above, the TAP protocol is respected, and Meson
correctly displays GDB's test output in testlog-thorough.txt.

Because the pygdbmi "shim" is so thin, I had to write a trivial GDB
class around it to easily capture and print the payloads returned by its
write() method. The GDB class allows clean, single-line commands to be
used in the tests through method chaining, making them easier to follow,
for example:

pc = gdb.cli("print $pc").get_add()

The test is kept “skipped” for aarch64, ppc64, and x86_64, so it is
necessary to set QEMU_TEST_FLAKY_TESTS=1 in the test environment to
effectively run the test on these archs.

On aarch64, the test is flaky, but there is a fix that I’ve tested while
writing this series [0] that resolves it. On ppc64 and x86_64, the test
always fails: on ppc64, GDB gets a bogus PC, and on x86_64, the last
part of the test (reverse-continue) does not hit the last executed PC
(as it should) but instead jumps to the beginning of the code (first PC
in forward order).

Thus, to effectively run the reverse_debugging test on aarch64:

$ export QEMU_TEST_FLAKY_TESTS=1
$ make check-functional

or even, to run only the reverse_debug test:
$ ./pyvenv/bin/meson test --verbose --no-rebuild -t 1 --setup thorough --suite func-thorough func-aarch64-reverse_debug


Cheers,
Gustavo

v1:
https://patchew.org/QEMU/20250819143916.4138035-1-gustavo.romero@linaro.org/

v2:
https://patchew.org/QEMU/20250904154640.52687-1-gustavo.romero@linaro.org/

v3:
- Use pygdbmi instead of run-test.py or any other GDB runner
- No changes in meson.build, except to set QEMU_TEST_GDB in the test env. 
- Improved test output to show all GDB commands used in the test


Gustavo Romero (4):
  python: Install pygdbmi in venv
  tests/functional: Provide GDB to the functional tests
  tests/functional: Adapt reverse_debugging to run w/o Avocado
  tests/functional: Adapt arches to reverse_debugging w/o Avocado

 configure                                     |   2 +
 meson_options.txt                             |   2 +
 .../wheels/pygdbmi-0.11.0.0-py3-none-any.whl  | Bin 0 -> 21258 bytes
 pythondeps.toml                               |   1 +
 scripts/meson-buildoptions.sh                 |   2 +
 .../functional/aarch64/test_reverse_debug.py  |  13 +-
 tests/functional/meson.build                  |   6 +
 tests/functional/ppc64/test_reverse_debug.py  |  15 +-
 tests/functional/reverse_debugging.py         | 308 +++++++++++-------
 tests/functional/x86_64/test_reverse_debug.py |  19 +-
 10 files changed, 217 insertions(+), 151 deletions(-)
 create mode 100644 python/wheels/pygdbmi-0.11.0.0-py3-none-any.whl

-- 
2.34.1



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v3 1/4] python: Install pygdbmi in venv
  2025-09-22  5:43 [PATCH v3 0/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
@ 2025-09-22  5:43 ` Gustavo Romero
  2025-09-22 11:10   ` Thomas Huth
  2025-09-22  5:43 ` [PATCH v3 2/4] tests/functional: Provide GDB to the functional tests Gustavo Romero
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: Gustavo Romero @ 2025-09-22  5:43 UTC (permalink / raw)
  To: qemu-devel, alex.bennee, thuth, berrange
  Cc: qemu-arm, gustavo.romero, manos.pitsidianakis, peter.maydell

Install pygdbmi in Meson's venv. pygdbmi is required by functional tests
that interact with GDB. pygdbmi size is only 21 kB.

The wheel file has been obtained with:

pyvenv/bin/pip3 download --only-binary :all: --dest . --no-cache pygdbmi

Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
 python/wheels/pygdbmi-0.11.0.0-py3-none-any.whl | Bin 0 -> 21258 bytes
 pythondeps.toml                                 |   1 +
 2 files changed, 1 insertion(+)
 create mode 100644 python/wheels/pygdbmi-0.11.0.0-py3-none-any.whl

diff --git a/python/wheels/pygdbmi-0.11.0.0-py3-none-any.whl b/python/wheels/pygdbmi-0.11.0.0-py3-none-any.whl
new file mode 100644
index 0000000000000000000000000000000000000000..3009f180cb802312ad694494a60660b53ffdeebd
GIT binary patch
literal 21258
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

literal 0
HcmV?d00001

diff --git a/pythondeps.toml b/pythondeps.toml
index 16fb2a989c..89f2f76545 100644
--- a/pythondeps.toml
+++ b/pythondeps.toml
@@ -21,6 +21,7 @@
 # The install key should match the version in python/wheels/
 meson = { accepted = ">=1.5.0", installed = "1.9.0", canary = "meson" }
 pycotap = { accepted = ">=1.1.0", installed = "1.3.1" }
+pygdbmi = { accepted = ">=0.11.0.0", installed = "0.11.0.0" }
 
 [meson-rust]
 # The install key should match the version in python/wheels/
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 2/4] tests/functional: Provide GDB to the functional tests
  2025-09-22  5:43 [PATCH v3 0/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
  2025-09-22  5:43 ` [PATCH v3 1/4] python: Install pygdbmi in venv Gustavo Romero
@ 2025-09-22  5:43 ` Gustavo Romero
  2025-09-22  5:43 ` [PATCH v3 3/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
  2025-09-22  5:43 ` [PATCH v3 4/4] tests/functional: Adapt arches to reverse_debugging " Gustavo Romero
  3 siblings, 0 replies; 10+ messages in thread
From: Gustavo Romero @ 2025-09-22  5:43 UTC (permalink / raw)
  To: qemu-devel, alex.bennee, thuth, berrange
  Cc: qemu-arm, gustavo.romero, manos.pitsidianakis, peter.maydell

The probe of gdb is done in 'configure' and the full path is passed to
meson.build via the -Dgdb=option.

Meson then can pass the location of GDB to the test via an environment
variable: QEMU_TEST_GDB.

Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
Signed-off-by: Thomas Huth <thuth@redhat.com>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
---
 configure                     | 2 ++
 meson_options.txt             | 2 ++
 scripts/meson-buildoptions.sh | 2 ++
 tests/functional/meson.build  | 6 ++++++
 4 files changed, 12 insertions(+)

diff --git a/configure b/configure
index 0f7eb95586..7226ddd589 100755
--- a/configure
+++ b/configure
@@ -1984,6 +1984,8 @@ if test "$skip_meson" = no; then
   test -n "${LIB_FUZZING_ENGINE+xxx}" && meson_option_add "-Dfuzzing_engine=$LIB_FUZZING_ENGINE"
   test "$plugins" = yes && meson_option_add "-Dplugins=true"
   test "$tcg" != enabled && meson_option_add "-Dtcg=$tcg"
+  test -n "$gdb_bin" && meson_option_add "-Dgdb=$gdb_bin"
+
   run_meson() {
     NINJA=$ninja $meson setup "$@" "$PWD" "$source_path"
   }
diff --git a/meson_options.txt b/meson_options.txt
index fff1521e58..5bb41bcbc4 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -36,6 +36,8 @@ option('trace_file', type: 'string', value: 'trace',
 option('coroutine_backend', type: 'combo',
        choices: ['ucontext', 'sigaltstack', 'windows', 'wasm', 'auto'],
        value: 'auto', description: 'coroutine backend to use')
+option('gdb', type: 'string', value: '',
+       description: 'Path to GDB')
 
 # Everything else can be set via --enable/--disable-* option
 # on the configure script command line.  After adding an option
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index 0ebe6bc52a..f4bd21220e 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -58,6 +58,7 @@ meson_options_help() {
   printf "%s\n" '  --enable-ubsan           enable undefined behaviour sanitizer'
   printf "%s\n" '  --firmwarepath=VALUES    search PATH for firmware files [share/qemu-'
   printf "%s\n" '                           firmware]'
+  printf "%s\n" '  --gdb=VALUE              Path to GDB'
   printf "%s\n" '  --iasl=VALUE             Path to ACPI disassembler'
   printf "%s\n" '  --includedir=VALUE       Header file directory [include]'
   printf "%s\n" '  --interp-prefix=VALUE    where to find shared libraries etc., use %M for'
@@ -323,6 +324,7 @@ _meson_option_parse() {
     --disable-fuzzing) printf "%s" -Dfuzzing=false ;;
     --enable-gcrypt) printf "%s" -Dgcrypt=enabled ;;
     --disable-gcrypt) printf "%s" -Dgcrypt=disabled ;;
+    --gdb=*) quote_sh "-Dgdb=$2" ;;
     --enable-gettext) printf "%s" -Dgettext=enabled ;;
     --disable-gettext) printf "%s" -Dgettext=disabled ;;
     --enable-gio) printf "%s" -Dgio=enabled ;;
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index 2a0c5aa141..725630d308 100644
--- a/tests/functional/meson.build
+++ b/tests/functional/meson.build
@@ -77,6 +77,12 @@ foreach speed : ['quick', 'thorough']
     test_env.set('PYTHONPATH', meson.project_source_root() / 'python:' +
                                meson.current_source_dir())
 
+    # Define the GDB environment variable if gdb is available.
+    gdb = get_option('gdb')
+    if gdb != ''
+      test_env.set('QEMU_TEST_GDB', gdb)
+    endif
+
     foreach test : target_tests
       testname = '@0@-@1@'.format(target_base, test)
       if fs.exists('generic' / 'test_' + test + '.py')
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 3/4] tests/functional: Adapt reverse_debugging to run w/o Avocado
  2025-09-22  5:43 [PATCH v3 0/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
  2025-09-22  5:43 ` [PATCH v3 1/4] python: Install pygdbmi in venv Gustavo Romero
  2025-09-22  5:43 ` [PATCH v3 2/4] tests/functional: Provide GDB to the functional tests Gustavo Romero
@ 2025-09-22  5:43 ` Gustavo Romero
  2025-09-22  9:30   ` Alex Bennée
  2025-09-22  9:30   ` Daniel P. Berrangé
  2025-09-22  5:43 ` [PATCH v3 4/4] tests/functional: Adapt arches to reverse_debugging " Gustavo Romero
  3 siblings, 2 replies; 10+ messages in thread
From: Gustavo Romero @ 2025-09-22  5:43 UTC (permalink / raw)
  To: qemu-devel, alex.bennee, thuth, berrange
  Cc: qemu-arm, gustavo.romero, manos.pitsidianakis, peter.maydell

This commit removes Avocado as a dependency for running the
reverse_debugging test.

The main benefit, beyond eliminating an extra dependency, is that there
is no longer any need to handle GDB packets manually. This removes the
need for ad-hoc functions dealing with endianness and arch-specific
register numbers, making the test easier to read. The timeout variable
is also removed, since Meson now manages timeouts automatically.

reverse_debugging now uses the pygdbmi module to interact with GDB, if
it's available in the test environment, otherwise the test is skipped.
GDB is detect via the QEMU_TEST_GDB env. variable.

This commit also significantly improves the output for the test and
now prints all the GDB commands used in sequence. It also adds
some clarifications to existing comments, for example, clarifying that
once the replay-break is reached, a SIGINT is captured in GDB.

reverse_debugging is kept "skipped" for aarch64, ppc64, and x86_64, so
won't run unless QEMU_TEST_FLAKY_TESTS=1 is set in the test environment,
before running 'make check-functional' or 'meson test [...]'.

Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
 tests/functional/reverse_debugging.py | 308 ++++++++++++++++----------
 1 file changed, 188 insertions(+), 120 deletions(-)

diff --git a/tests/functional/reverse_debugging.py b/tests/functional/reverse_debugging.py
index f9a1d395f1..38161beab8 100644
--- a/tests/functional/reverse_debugging.py
+++ b/tests/functional/reverse_debugging.py
@@ -1,21 +1,94 @@
-# Reverse debugging test
-#
 # SPDX-License-Identifier: GPL-2.0-or-later
 #
+# Reverse debugging test
+#
 # Copyright (c) 2020 ISP RAS
+# Copyright (c) 2025 Linaro Limited
 #
 # Author:
 #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
+#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
 #
 # 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 os
+
 import logging
+import os
+import re
+import subprocess
+from pygdbmi.gdbcontroller import GdbController
+from pygdbmi.constants import GdbTimeoutError
+
 
 from qemu_test import LinuxKernelTest, get_qemu_img
 from qemu_test.ports import Ports
 
 
+class GDB:
+    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
+
+
+    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):
+        pattern = r"0x[0-9A-Fa-f]+"
+        cmd_output = "".join(self.cmd_output)
+        match = re.search(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()
+
+
 class ReverseDebugging(LinuxKernelTest):
     """
     Test GDB reverse debugging commands: reverse step and reverse continue.
@@ -28,21 +101,17 @@ class ReverseDebugging(LinuxKernelTest):
     that the execution is stopped at the last of them.
     """
 
-    timeout = 10
     STEPS = 10
-    endian_is_le = True
 
     def run_vm(self, record, shift, args, replay_path, image_path, port):
-        from avocado.utils import datadrainer
-
         logger = logging.getLogger('replay')
         vm = self.get_vm(name='record' if record else 'replay')
         vm.set_console()
         if record:
-            logger.info('recording the execution...')
+            logger.info('Recording the execution...')
             mode = 'record'
         else:
-            logger.info('replaying the execution...')
+            logger.info('Replaying the execution...')
             mode = 'replay'
             vm.add_args('-gdb', 'tcp::%d' % port, '-S')
         vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' %
@@ -52,145 +121,144 @@ def run_vm(self, record, shift, args, replay_path, image_path, port):
         if args:
             vm.add_args(*args)
         vm.launch()
-        console_drainer = datadrainer.LineLogger(vm.console_socket.fileno(),
-                                    logger=self.log.getChild('console'),
-                                    stop_check=(lambda : not vm.is_running()))
-        console_drainer.start()
-        return vm
 
-    @staticmethod
-    def get_reg_le(g, reg):
-        res = g.cmd(b'p%x' % reg)
-        num = 0
-        for i in range(len(res))[-2::-2]:
-            num = 0x100 * num + int(res[i:i + 2], 16)
-        return num
-
-    @staticmethod
-    def get_reg_be(g, reg):
-        res = g.cmd(b'p%x' % reg)
-        return int(res, 16)
-
-    def get_reg(self, g, reg):
-        # value may be encoded in BE or LE order
-        if self.endian_is_le:
-            return self.get_reg_le(g, reg)
-        else:
-            return self.get_reg_be(g, reg)
-
-    def get_pc(self, g):
-        return self.get_reg(g, self.REG_PC)
-
-    def check_pc(self, g, addr):
-        pc = self.get_pc(g)
-        if pc != addr:
-            self.fail('Invalid PC (read %x instead of %x)' % (pc, addr))
-
-    @staticmethod
-    def gdb_step(g):
-        g.cmd(b's', b'T05thread:01;')
-
-    @staticmethod
-    def gdb_bstep(g):
-        g.cmd(b'bs', b'T05thread:01;')
+        return vm
 
     @staticmethod
     def vm_get_icount(vm):
         return vm.qmp('query-replay')['return']['icount']
 
     def reverse_debugging(self, shift=7, args=None):
-        from avocado.utils import gdb
-        from avocado.utils import process
-
         logger = logging.getLogger('replay')
 
-        # create qcow2 for snapshots
-        logger.info('creating qcow2 image for VM snapshots')
+        # Create qcow2 for snapshots
+        logger.info('Creating qcow2 image for VM snapshots')
         image_path = os.path.join(self.workdir, 'disk.qcow2')
         qemu_img = get_qemu_img(self)
         if qemu_img is None:
             self.skipTest('Could not find "qemu-img", which is required to '
                           'create the temporary qcow2 image')
         cmd = '%s create -f qcow2 %s 128M' % (qemu_img, image_path)
-        process.run(cmd)
+        r = subprocess.run(cmd, capture_output=True, shell=True, text=True)
+        logger.info(r.args)
+        logger.info(r.stdout)
 
         replay_path = os.path.join(self.workdir, 'replay.bin')
 
-        # record the log
+        # Record the log.
         vm = self.run_vm(True, shift, args, replay_path, image_path, -1)
         while self.vm_get_icount(vm) <= self.STEPS:
             pass
         last_icount = self.vm_get_icount(vm)
         vm.shutdown()
 
-        logger.info("recorded log with %s+ steps" % last_icount)
+        logger.info("Recorded log with %s+ steps" % last_icount)
+
+        # Replay and run debug commands.
+        gdb_cmd = os.getenv('QEMU_TEST_GDB')
+        if not gdb_cmd:
+            test.skipTest(f"Test skipped because there is no GDB available!")
 
-        # replay and run debug commands
         with Ports() as ports:
             port = ports.find_free_port()
             vm = self.run_vm(False, shift, args, replay_path, image_path, port)
-        logger.info('connecting to gdbstub')
-        g = gdb.GDBRemote('127.0.0.1', port, False, False)
-        g.connect()
-        r = g.cmd(b'qSupported')
-        if b'qXfer:features:read+' in r:
-            g.cmd(b'qXfer:features:read:target.xml:0,ffb')
-        if b'ReverseStep+' not in r:
-            self.fail('Reverse step is not supported by QEMU')
-        if b'ReverseContinue+' not in r:
-            self.fail('Reverse continue is not supported by QEMU')
-
-        logger.info('stepping forward')
-        steps = []
-        # record first instruction addresses
-        for _ in range(self.STEPS):
-            pc = self.get_pc(g)
-            logger.info('saving position %x' % pc)
-            steps.append(pc)
-            self.gdb_step(g)
-
-        # visit the recorded instruction in reverse order
-        logger.info('stepping backward')
-        for addr in steps[::-1]:
-            self.gdb_bstep(g)
-            self.check_pc(g, addr)
-            logger.info('found position %x' % addr)
-
-        # visit the recorded instruction in forward order
-        logger.info('stepping forward')
-        for addr in steps:
-            self.check_pc(g, addr)
-            self.gdb_step(g)
-            logger.info('found position %x' % addr)
-
-        # set breakpoints for the instructions just stepped over
-        logger.info('setting breakpoints')
-        for addr in steps:
-            # hardware breakpoint at addr with len=1
-            g.cmd(b'Z1,%x,1' % addr, b'OK')
-
-        # this may hit a breakpoint if first instructions are executed
-        # again
-        logger.info('continuing execution')
-        vm.qmp('replay-break', icount=last_icount - 1)
-        # continue - will return after pausing
-        # This could stop at the end and get a T02 return, or by
-        # re-executing one of the breakpoints and get a T05 return.
-        g.cmd(b'c')
-        if self.vm_get_icount(vm) == last_icount - 1:
-            logger.info('reached the end (icount %s)' % (last_icount - 1))
-        else:
-            logger.info('hit a breakpoint again at %x (icount %s)' %
-                        (self.get_pc(g), self.vm_get_icount(vm)))
 
-        logger.info('running reverse continue to reach %x' % steps[-1])
-        # reverse continue - will return after stopping at the breakpoint
-        g.cmd(b'bc', b'T05thread:01;')
+        try:
+            gdb = GDB(gdb_cmd)
 
-        # assume that none of the first instructions is executed again
-        # breaking the order of the breakpoints
-        self.check_pc(g, steps[-1])
-        logger.info('successfully reached %x' % steps[-1])
+            logger.info('Connecting to gdbstub...')
 
-        logger.info('exiting gdb and qemu')
-        vm.shutdown()
+            gdb.cli("set debug remote 1")
+
+            c = gdb.cli(f"target remote localhost:{port}").get_console()
+            if not f"Remote debugging using localhost:{port}" in c:
+                self.fail("Could not connect to gdbstub!")
+
+            # Remote debug messages are in 'log' payloads.
+            r = gdb.get_log()
+            if 'ReverseStep+' not in r:
+                self.fail('Reverse step is not supported by QEMU')
+            if 'ReverseContinue+' not in r:
+                self.fail('Reverse continue is not supported by QEMU')
+
+            gdb.cli("set debug remote 0")
+
+            logger.info('Stepping forward')
+            steps = []
+            # Record first instruction addresses.
+            for _ in range(self.STEPS):
+                pc = gdb.cli("print $pc").get_addr()
+                logger.info('Saving position %x' % pc)
+                steps.append(pc)
+
+                gdb.cli("stepi")
+
+            # Visit the recorded instructions in reverse order.
+            logger.info('Stepping backward')
+            for saved_pc in steps[::-1]:
+                logger.info('Found position %x' % saved_pc)
+                gdb.cli("reverse-stepi")
+                pc = gdb.cli("print $pc").get_addr()
+                if pc != saved_pc:
+                    logger.info('Invalid PC (read %x instead of %x)' % (pc, saved_pc))
+                    self.fail('Reverse stepping failed!')
+
+            # Visit the recorded instructions in forward order.
+            logger.info('Stepping forward')
+            for saved_pc in steps:
+                logger.info('Found position %x' % saved_pc)
+                pc = gdb.cli("print $pc").get_addr()
+                if pc != saved_pc:
+                    logger.info('Invalid PC (read %x instead of %x)' % (pc, saved_pc))
+                    self.fail('Forward stepping failed!')
+
+                gdb.cli("stepi")
+
+            # Set breakpoints for the instructions just stepped over.
+            logger.info('Setting breakpoints')
+            for saved_pc in steps:
+                gdb.cli(f"break *{hex(saved_pc)}")
+
+            # This may hit a breakpoint if first instructions are executed again.
+            logger.info('Continuing execution')
+            vm.qmp('replay-break', icount=last_icount - 1)
+            # continue - will return after pausing.
+            # This can stop at the end of the replay-break and gdb gets a SIGINT,
+            # or by re-executing one of the breakpoints and gdb stops at a
+            # breakpoint.
+            gdb.cli("continue")
+
+            pc = gdb.cli("print $pc").get_addr()
+            current_icount = self.vm_get_icount(vm)
+            if current_icount == last_icount - 1:
+                print(f"# **** Hit replay-break at icount={current_icount}, pc={hex(pc)} ****")
+                logger.info('Reached the end (icount %s)' % (current_icount))
+            else:
+                print(f"# **** Hit breakpoint at icount={current_icount}, pc={hex(pc)} ****")
+                logger.info('Hit a breakpoint again at %x (icount %s)' %
+                            (pc, current_icount))
+
+            logger.info('Running reverse continue to reach %x' % steps[-1])
+            # reverse-continue - will return after stopping at the breakpoint.
+            gdb.cli("reverse-continue")
+
+            # Assume that none of the first instructions are executed again
+            # breaking the order of the breakpoints.
+            # steps[-1] is the first saved $pc in reverse order.
+            pc = gdb.cli("print $pc").get_addr()
+            first_pc_in_rev_order = steps[-1]
+            if pc == first_pc_in_rev_order:
+                print(f"# **** Hit breakpoint at the first PC in reverse order ({hex(pc)}) ****")
+                logger.info('Successfully reached breakpoint at %x' % first_pc_in_rev_order)
+            else:
+                logger.info('Failed to reach breakpoint at %x' % first_pc_in_rev_order)
+                self.fail("'reverse-continue' did not hit the first PC in reverse order!")
+
+            logger.info('Exiting GDB and QEMU...')
+            gdb.exit()
+            vm.shutdown()
+
+            logger.info('Test passed.')
+
+        except GdbTimeoutError:
+            self.fail("Connection to gdbstub timeouted...")
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 4/4] tests/functional: Adapt arches to reverse_debugging w/o Avocado
  2025-09-22  5:43 [PATCH v3 0/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
                   ` (2 preceding siblings ...)
  2025-09-22  5:43 ` [PATCH v3 3/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
@ 2025-09-22  5:43 ` Gustavo Romero
  2025-09-22  9:33   ` Daniel P. Berrangé
  2025-09-22  9:34   ` Daniel P. Berrangé
  3 siblings, 2 replies; 10+ messages in thread
From: Gustavo Romero @ 2025-09-22  5:43 UTC (permalink / raw)
  To: qemu-devel, alex.bennee, thuth, berrange
  Cc: qemu-arm, gustavo.romero, manos.pitsidianakis, peter.maydell

reverse_debugging no longer depends on Avocado, so remove the import
checks for Avocado, the per-arch endianness tweaks, and the per-arch
register settings. All of these are now handled in the ReverseDebugging
class.

Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
---
 .../functional/aarch64/test_reverse_debug.py  | 13 +++++--------
 tests/functional/ppc64/test_reverse_debug.py  | 15 +++++----------
 tests/functional/x86_64/test_reverse_debug.py | 19 ++++++-------------
 3 files changed, 16 insertions(+), 31 deletions(-)

diff --git a/tests/functional/aarch64/test_reverse_debug.py b/tests/functional/aarch64/test_reverse_debug.py
index 8bc91ccfde..7f816025a9 100755
--- a/tests/functional/aarch64/test_reverse_debug.py
+++ b/tests/functional/aarch64/test_reverse_debug.py
@@ -1,26 +1,23 @@
-#!/usr/bin/env python3
-#
 # SPDX-License-Identifier: GPL-2.0-or-later
 #
-# Reverse debugging test
+# Reverse debugging test for aarch64
 #
 # Copyright (c) 2020 ISP RAS
+# Copyright (c) 2025 Linaro Limited
 #
 # Author:
 #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
+#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
 #
 # This work is licensed under the terms of the GNU GPL, version 2 or
 # later.  See the COPYING file in the top-level directory.
 
-from qemu_test import Asset, skipIfMissingImports, skipFlakyTest
+from qemu_test import Asset, skipFlakyTest
 from reverse_debugging import ReverseDebugging
 
 
-@skipIfMissingImports('avocado.utils')
 class ReverseDebugging_AArch64(ReverseDebugging):
 
-    REG_PC = 32
-
     ASSET_KERNEL = Asset(
         ('https://archives.fedoraproject.org/pub/archive/fedora/linux/'
          'releases/29/Everything/aarch64/os/images/pxeboot/vmlinuz'),
@@ -35,4 +32,4 @@ def test_aarch64_virt(self):
 
 
 if __name__ == '__main__':
-    ReverseDebugging.main()
+    ReverseDebugging_AArch64.main()
diff --git a/tests/functional/ppc64/test_reverse_debug.py b/tests/functional/ppc64/test_reverse_debug.py
index 5931adef5a..2b7b18e9a8 100755
--- a/tests/functional/ppc64/test_reverse_debug.py
+++ b/tests/functional/ppc64/test_reverse_debug.py
@@ -1,41 +1,36 @@
-#!/usr/bin/env python3
-#
 # SPDX-License-Identifier: GPL-2.0-or-later
 #
-# Reverse debugging test
+# Reverse debugging test for ppc64
 #
 # Copyright (c) 2020 ISP RAS
+# Copyright (c) 2025 Linaro Limited
 #
 # Author:
 #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
+#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
 #
 # This work is licensed under the terms of the GNU GPL, version 2 or
 # later.  See the COPYING file in the top-level directory.
 
-from qemu_test import skipIfMissingImports, skipFlakyTest
+from qemu_test import skipFlakyTest
 from reverse_debugging import ReverseDebugging
 
 
-@skipIfMissingImports('avocado.utils')
 class ReverseDebugging_ppc64(ReverseDebugging):
 
-    REG_PC = 0x40
-
     @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/1992")
     def test_ppc64_pseries(self):
         self.set_machine('pseries')
         # SLOF branches back to its entry point, which causes this test
         # to take the 'hit a breakpoint again' path. That's not a problem,
         # just slightly different than the other machines.
-        self.endian_is_le = False
         self.reverse_debugging()
 
     @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/1992")
     def test_ppc64_powernv(self):
         self.set_machine('powernv')
-        self.endian_is_le = False
         self.reverse_debugging()
 
 
 if __name__ == '__main__':
-    ReverseDebugging.main()
+    ReverseDebugging_ppc64.main()
diff --git a/tests/functional/x86_64/test_reverse_debug.py b/tests/functional/x86_64/test_reverse_debug.py
index d713e91e14..408e5d1f48 100755
--- a/tests/functional/x86_64/test_reverse_debug.py
+++ b/tests/functional/x86_64/test_reverse_debug.py
@@ -1,36 +1,29 @@
-#!/usr/bin/env python3
-#
 # SPDX-License-Identifier: GPL-2.0-or-later
 #
-# Reverse debugging test
+# Reverse debugging test for x86_64
 #
 # Copyright (c) 2020 ISP RAS
+# Copyright (c) 2025 Linaro Limited
 #
 # Author:
 #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
+#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
 #
 # This work is licensed under the terms of the GNU GPL, version 2 or
 # later.  See the COPYING file in the top-level directory.
 
-from qemu_test import skipIfMissingImports, skipFlakyTest
+from qemu_test import skipFlakyTest
 from reverse_debugging import ReverseDebugging
 
 
-@skipIfMissingImports('avocado.utils')
 class ReverseDebugging_X86_64(ReverseDebugging):
 
-    REG_PC = 0x10
-    REG_CS = 0x12
-    def get_pc(self, g):
-        return self.get_reg_le(g, self.REG_PC) \
-            + self.get_reg_le(g, self.REG_CS) * 0x10
-
     @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/2922")
     def test_x86_64_pc(self):
         self.set_machine('pc')
-        # start with BIOS only
+        # Start with BIOS only
         self.reverse_debugging()
 
 
 if __name__ == '__main__':
-    ReverseDebugging.main()
+    ReverseDebugging_X86_64.main()
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 3/4] tests/functional: Adapt reverse_debugging to run w/o Avocado
  2025-09-22  5:43 ` [PATCH v3 3/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
@ 2025-09-22  9:30   ` Alex Bennée
  2025-09-22  9:30   ` Daniel P. Berrangé
  1 sibling, 0 replies; 10+ messages in thread
From: Alex Bennée @ 2025-09-22  9:30 UTC (permalink / raw)
  To: Gustavo Romero
  Cc: qemu-devel, thuth, berrange, qemu-arm, manos.pitsidianakis,
	peter.maydell

Gustavo Romero <gustavo.romero@linaro.org> writes:

> This commit removes Avocado as a dependency for running the
> reverse_debugging test.
>
> The main benefit, beyond eliminating an extra dependency, is that there
> is no longer any need to handle GDB packets manually. This removes the
> need for ad-hoc functions dealing with endianness and arch-specific
> register numbers, making the test easier to read. The timeout variable
> is also removed, since Meson now manages timeouts automatically.
>
> reverse_debugging now uses the pygdbmi module to interact with GDB, if
> it's available in the test environment, otherwise the test is skipped.
> GDB is detect via the QEMU_TEST_GDB env. variable.
>
> This commit also significantly improves the output for the test and
> now prints all the GDB commands used in sequence. It also adds
> some clarifications to existing comments, for example, clarifying that
> once the replay-break is reached, a SIGINT is captured in GDB.
>
> reverse_debugging is kept "skipped" for aarch64, ppc64, and x86_64, so
> won't run unless QEMU_TEST_FLAKY_TESTS=1 is set in the test environment,
> before running 'make check-functional' or 'meson test [...]'.
>
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
>  tests/functional/reverse_debugging.py | 308 ++++++++++++++++----------
>  1 file changed, 188 insertions(+), 120 deletions(-)
>
> diff --git a/tests/functional/reverse_debugging.py b/tests/functional/reverse_debugging.py
> index f9a1d395f1..38161beab8 100644
> --- a/tests/functional/reverse_debugging.py
> +++ b/tests/functional/reverse_debugging.py
> @@ -1,21 +1,94 @@
> -# Reverse debugging test
> -#
>  # SPDX-License-Identifier: GPL-2.0-or-later
>  #
> +# Reverse debugging test
> +#
>  # Copyright (c) 2020 ISP RAS
> +# Copyright (c) 2025 Linaro Limited
>  #
>  # Author:
>  #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
> +#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
>  #
>  # 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 os
> +
>  import logging
> +import os
> +import re
> +import subprocess
> +from pygdbmi.gdbcontroller import GdbController
> +from pygdbmi.constants import GdbTimeoutError
> +
>  
>  from qemu_test import LinuxKernelTest, get_qemu_img
>  from qemu_test.ports import Ports
>  
>  
> +class GDB:
> +    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
> +
> +
> +    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):
> +        pattern = r"0x[0-9A-Fa-f]+"
> +        cmd_output = "".join(self.cmd_output)
> +        match = re.search(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()
> +
> +

Could this re-factor into a class have been a separate commit?

>  class ReverseDebugging(LinuxKernelTest):
>      """
>      Test GDB reverse debugging commands: reverse step and reverse continue.
> @@ -28,21 +101,17 @@ class ReverseDebugging(LinuxKernelTest):
>      that the execution is stopped at the last of them.
>      """
>  
> -    timeout = 10
>      STEPS = 10
> -    endian_is_le = True
>  
>      def run_vm(self, record, shift, args, replay_path, image_path, port):
> -        from avocado.utils import datadrainer
> -
>          logger = logging.getLogger('replay')
>          vm = self.get_vm(name='record' if record else 'replay')
>          vm.set_console()
>          if record:
> -            logger.info('recording the execution...')
> +            logger.info('Recording the execution...')

Mixing capitalisation fixes with logical change makes reviewing a pain.

>              mode = 'record'
>          else:
> -            logger.info('replaying the execution...')
> +            logger.info('Replaying the execution...')
>              mode = 'replay'
>              vm.add_args('-gdb', 'tcp::%d' % port, '-S')
>          vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' %
> @@ -52,145 +121,144 @@ def run_vm(self, record, shift, args, replay_path, image_path, port):
>          if args:
>              vm.add_args(*args)
>          vm.launch()
> -        console_drainer = datadrainer.LineLogger(vm.console_socket.fileno(),
> -                                    logger=self.log.getChild('console'),
> -                                    stop_check=(lambda : not vm.is_running()))
> -        console_drainer.start()
> -        return vm

I suspect dropping the console drainer could be a separate commit like
in Daniels series.

>  
> -    @staticmethod
> -    def get_reg_le(g, reg):
> -        res = g.cmd(b'p%x' % reg)
> -        num = 0
> -        for i in range(len(res))[-2::-2]:
> -            num = 0x100 * num + int(res[i:i + 2], 16)
> -        return num
> -
> -    @staticmethod
> -    def get_reg_be(g, reg):
> -        res = g.cmd(b'p%x' % reg)
> -        return int(res, 16)
> -
> -    def get_reg(self, g, reg):
> -        # value may be encoded in BE or LE order
> -        if self.endian_is_le:
> -            return self.get_reg_le(g, reg)
> -        else:
> -            return self.get_reg_be(g, reg)
> -
> -    def get_pc(self, g):
> -        return self.get_reg(g, self.REG_PC)
> -
> -    def check_pc(self, g, addr):
> -        pc = self.get_pc(g)
> -        if pc != addr:
> -            self.fail('Invalid PC (read %x instead of %x)' % (pc, addr))
> -
> -    @staticmethod
> -    def gdb_step(g):
> -        g.cmd(b's', b'T05thread:01;')
> -
> -    @staticmethod
> -    def gdb_bstep(g):
> -        g.cmd(b'bs', b'T05thread:01;')
> +        return vm
>  
>      @staticmethod
>      def vm_get_icount(vm):
>          return vm.qmp('query-replay')['return']['icount']
>  
>      def reverse_debugging(self, shift=7, args=None):
> -        from avocado.utils import gdb
> -        from avocado.utils import process
> -
>          logger = logging.getLogger('replay')
>  
> -        # create qcow2 for snapshots
> -        logger.info('creating qcow2 image for VM snapshots')
> +        # Create qcow2 for snapshots
> +        logger.info('Creating qcow2 image for VM snapshots')
>          image_path = os.path.join(self.workdir, 'disk.qcow2')
>          qemu_img = get_qemu_img(self)
>          if qemu_img is None:
>              self.skipTest('Could not find "qemu-img", which is required to '
>                            'create the temporary qcow2 image')
>          cmd = '%s create -f qcow2 %s 128M' % (qemu_img, image_path)
> -        process.run(cmd)
> +        r = subprocess.run(cmd, capture_output=True, shell=True, text=True)
> +        logger.info(r.args)
> +        logger.info(r.stdout)
>  
>          replay_path = os.path.join(self.workdir, 'replay.bin')
>  
> -        # record the log
> +        # Record the log.
>          vm = self.run_vm(True, shift, args, replay_path, image_path, -1)
>          while self.vm_get_icount(vm) <= self.STEPS:
>              pass
>          last_icount = self.vm_get_icount(vm)
>          vm.shutdown()
>  
> -        logger.info("recorded log with %s+ steps" % last_icount)
> +        logger.info("Recorded log with %s+ steps" % last_icount)
> +
> +        # Replay and run debug commands.
> +        gdb_cmd = os.getenv('QEMU_TEST_GDB')
> +        if not gdb_cmd:
> +            test.skipTest(f"Test skipped because there is no GDB
> available!")

This fails:

  test:         qemu:func-thorough+func-aarch64-thorough+thorough / func-aarch64-reverse_debug
  start time:   09:24:25
  duration:     0.88s
  result:       exit status 1
  command:      ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1 MALLOC_PERTURB_=220 QEMU_TEST_QEMU_BINARY=/home/alex/lsrc/qemu.git/builds/all/qemu-system-aarch64 
  LD_LIBRARY_PATH=/home/alex/lsrc/qemu.git/builds/all/contrib/plugins:/home/alex/lsrc/qemu.git/builds/all/tests/tcg/plugins UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print
  _summary=1:print_stacktrace=1 PYTHONPATH=/home/alex/lsrc/qemu.git/python:/home/alex/lsrc/qemu.git/tests/functional RUST_BACKTRACE=1 QEMU_BUILD_ROOT=/home/alex/lsrc/qemu.git/b
  uilds/all MSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 QEMU_TEST_QEMU_IMG=/home/alex/lsrc/qemu.git/builds/all/qemu-img MESON_TEST_ITERATIO
  N=1 /home/alex/lsrc/qemu.git/builds/all/pyvenv/bin/python3 /home/alex/lsrc/qemu.git/tests/functional/aarch64/test_reverse_debug.py
  ----------------------------------- stdout -----------------------------------
  TAP version 13
  not ok 1 test_reverse_debug.ReverseDebugging_AArch64.test_aarch64_virt
  1..1
  ----------------------------------- stderr -----------------------------------
  Traceback (most recent call last):
    File "/home/alex/lsrc/qemu.git/tests/functional/aarch64/test_reverse_debug.py", line 31, in test_aarch64_virt
      self.reverse_debugging(args=('-kernel', kernel_path))
      ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/home/alex/lsrc/qemu.git/tests/functional/reverse_debugging.py", line 160, in reverse_debugging
      test.skipTest(f"Test skipped because there is no GDB available!")
      ^^^^
  NameError: name 'test' is not defined

  More information on test_reverse_debug.ReverseDebugging_AArch64.test_aarch64_virt could be found here:
   /home/alex/lsrc/qemu.git/builds/all/tests/functional/aarch64/test_reverse_debug.ReverseDebugging_AArch64.test_aarch64_virt/base.log
   /home/alex/lsrc/qemu.git/builds/all/tests/functional/aarch64/test_reverse_debug.ReverseDebugging_AArch64.test_aarch64_virt/console.log

  (test program exited with status code 1)

Not sure why though as:

    cat config-host.mak
  # Automatically generated by configure - do not modify

  all:
  SRC_PATH=/home/alex/lsrc/qemu.git
  TARGET_DIRS=aarch64-linux-user aarch64_be-linux-user alpha-linux-user arm-linux-user armeb-linux-user hexagon-linux-user hppa-linux-user i386-linux-user loongarch64-linux-user m68k-linux-user microblaze-linux-user microblazeel-linux-user mips-linux-user mips64-linux-user mips64el-linux-user mipsel-linux-user mipsn32-linux-user mipsn32el-linux-user or1k-linux-user ppc-linux-user ppc64-linux-user ppc64le-linux-user riscv32-linux-user riscv64-linux-user s390x-linux-user sh4-linux-user sh4eb-linux-user sparc-linux-user sparc32plus-linux-user sparc64-linux-user x86_64-linux-user xtensa-linux-user xtensaeb-linux-user aarch64-softmmu alpha-softmmu arm-softmmu avr-softmmu hppa-softmmu i386-softmmu loongarch64-softmmu m68k-softmmu microblaze-softmmu microblazeel-softmmu mips-softmmu mips64-softmmu mips64el-softmmu mipsel-softmmu or1k-softmmu ppc-softmmu ppc64-softmmu riscv32-softmmu riscv64-softmmu rx-softmmu s390x-softmmu sh4-softmmu sh4eb-softmmu sparc-softmmu sparc64-softmmu tricore-softmmu x86_64-softmmu xtensa-softmmu xtensaeb-softmmu
  GDB=/usr/bin/gdb-multiarch

>  
> -        # replay and run debug commands
>          with Ports() as ports:
>              port = ports.find_free_port()
>              vm = self.run_vm(False, shift, args, replay_path, image_path, port)
> -        logger.info('connecting to gdbstub')
> -        g = gdb.GDBRemote('127.0.0.1', port, False, False)
> -        g.connect()
> -        r = g.cmd(b'qSupported')
> -        if b'qXfer:features:read+' in r:
> -            g.cmd(b'qXfer:features:read:target.xml:0,ffb')
> -        if b'ReverseStep+' not in r:
> -            self.fail('Reverse step is not supported by QEMU')
> -        if b'ReverseContinue+' not in r:
> -            self.fail('Reverse continue is not supported by QEMU')
> -
> -        logger.info('stepping forward')
> -        steps = []
> -        # record first instruction addresses
> -        for _ in range(self.STEPS):
> -            pc = self.get_pc(g)
> -            logger.info('saving position %x' % pc)
> -            steps.append(pc)
> -            self.gdb_step(g)
> -
> -        # visit the recorded instruction in reverse order
> -        logger.info('stepping backward')
> -        for addr in steps[::-1]:
> -            self.gdb_bstep(g)
> -            self.check_pc(g, addr)
> -            logger.info('found position %x' % addr)
> -
> -        # visit the recorded instruction in forward order
> -        logger.info('stepping forward')
> -        for addr in steps:
> -            self.check_pc(g, addr)
> -            self.gdb_step(g)
> -            logger.info('found position %x' % addr)
> -
> -        # set breakpoints for the instructions just stepped over
> -        logger.info('setting breakpoints')
> -        for addr in steps:
> -            # hardware breakpoint at addr with len=1
> -            g.cmd(b'Z1,%x,1' % addr, b'OK')
> -
> -        # this may hit a breakpoint if first instructions are executed
> -        # again
> -        logger.info('continuing execution')
> -        vm.qmp('replay-break', icount=last_icount - 1)
> -        # continue - will return after pausing
> -        # This could stop at the end and get a T02 return, or by
> -        # re-executing one of the breakpoints and get a T05 return.
> -        g.cmd(b'c')
> -        if self.vm_get_icount(vm) == last_icount - 1:
> -            logger.info('reached the end (icount %s)' % (last_icount - 1))
> -        else:
> -            logger.info('hit a breakpoint again at %x (icount %s)' %
> -                        (self.get_pc(g), self.vm_get_icount(vm)))
>  
> -        logger.info('running reverse continue to reach %x' % steps[-1])
> -        # reverse continue - will return after stopping at the breakpoint
> -        g.cmd(b'bc', b'T05thread:01;')
> +        try:
> +            gdb = GDB(gdb_cmd)
>  
> -        # assume that none of the first instructions is executed again
> -        # breaking the order of the breakpoints
> -        self.check_pc(g, steps[-1])
> -        logger.info('successfully reached %x' % steps[-1])
> +            logger.info('Connecting to gdbstub...')
>  
> -        logger.info('exiting gdb and qemu')
> -        vm.shutdown()
> +            gdb.cli("set debug remote 1")
> +
> +            c = gdb.cli(f"target remote localhost:{port}").get_console()
> +            if not f"Remote debugging using localhost:{port}" in c:
> +                self.fail("Could not connect to gdbstub!")
> +
> +            # Remote debug messages are in 'log' payloads.
> +            r = gdb.get_log()
> +            if 'ReverseStep+' not in r:
> +                self.fail('Reverse step is not supported by QEMU')
> +            if 'ReverseContinue+' not in r:
> +                self.fail('Reverse continue is not supported by QEMU')
> +
> +            gdb.cli("set debug remote 0")
> +
> +            logger.info('Stepping forward')
> +            steps = []
> +            # Record first instruction addresses.
> +            for _ in range(self.STEPS):
> +                pc = gdb.cli("print $pc").get_addr()
> +                logger.info('Saving position %x' % pc)
> +                steps.append(pc)
> +
> +                gdb.cli("stepi")
> +
> +            # Visit the recorded instructions in reverse order.
> +            logger.info('Stepping backward')
> +            for saved_pc in steps[::-1]:
> +                logger.info('Found position %x' % saved_pc)
> +                gdb.cli("reverse-stepi")
> +                pc = gdb.cli("print $pc").get_addr()
> +                if pc != saved_pc:
> +                    logger.info('Invalid PC (read %x instead of %x)' % (pc, saved_pc))
> +                    self.fail('Reverse stepping failed!')
> +
> +            # Visit the recorded instructions in forward order.
> +            logger.info('Stepping forward')
> +            for saved_pc in steps:
> +                logger.info('Found position %x' % saved_pc)
> +                pc = gdb.cli("print $pc").get_addr()
> +                if pc != saved_pc:
> +                    logger.info('Invalid PC (read %x instead of %x)' % (pc, saved_pc))
> +                    self.fail('Forward stepping failed!')
> +
> +                gdb.cli("stepi")
> +
> +            # Set breakpoints for the instructions just stepped over.
> +            logger.info('Setting breakpoints')
> +            for saved_pc in steps:
> +                gdb.cli(f"break *{hex(saved_pc)}")
> +
> +            # This may hit a breakpoint if first instructions are executed again.
> +            logger.info('Continuing execution')
> +            vm.qmp('replay-break', icount=last_icount - 1)
> +            # continue - will return after pausing.
> +            # This can stop at the end of the replay-break and gdb gets a SIGINT,
> +            # or by re-executing one of the breakpoints and gdb stops at a
> +            # breakpoint.
> +            gdb.cli("continue")
> +
> +            pc = gdb.cli("print $pc").get_addr()
> +            current_icount = self.vm_get_icount(vm)
> +            if current_icount == last_icount - 1:
> +                print(f"# **** Hit replay-break at icount={current_icount}, pc={hex(pc)} ****")
> +                logger.info('Reached the end (icount %s)' % (current_icount))
> +            else:
> +                print(f"# **** Hit breakpoint at icount={current_icount}, pc={hex(pc)} ****")
> +                logger.info('Hit a breakpoint again at %x (icount %s)' %
> +                            (pc, current_icount))
> +
> +            logger.info('Running reverse continue to reach %x' % steps[-1])
> +            # reverse-continue - will return after stopping at the breakpoint.
> +            gdb.cli("reverse-continue")
> +
> +            # Assume that none of the first instructions are executed again
> +            # breaking the order of the breakpoints.
> +            # steps[-1] is the first saved $pc in reverse order.
> +            pc = gdb.cli("print $pc").get_addr()
> +            first_pc_in_rev_order = steps[-1]
> +            if pc == first_pc_in_rev_order:
> +                print(f"# **** Hit breakpoint at the first PC in reverse order ({hex(pc)}) ****")
> +                logger.info('Successfully reached breakpoint at %x' % first_pc_in_rev_order)
> +            else:
> +                logger.info('Failed to reach breakpoint at %x' % first_pc_in_rev_order)
> +                self.fail("'reverse-continue' did not hit the first PC in reverse order!")
> +
> +            logger.info('Exiting GDB and QEMU...')
> +            gdb.exit()
> +            vm.shutdown()
> +
> +            logger.info('Test passed.')
> +
> +        except GdbTimeoutError:
> +            self.fail("Connection to gdbstub timeouted...")

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 3/4] tests/functional: Adapt reverse_debugging to run w/o Avocado
  2025-09-22  5:43 ` [PATCH v3 3/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
  2025-09-22  9:30   ` Alex Bennée
@ 2025-09-22  9:30   ` Daniel P. Berrangé
  1 sibling, 0 replies; 10+ messages in thread
From: Daniel P. Berrangé @ 2025-09-22  9:30 UTC (permalink / raw)
  To: Gustavo Romero
  Cc: qemu-devel, alex.bennee, thuth, qemu-arm, manos.pitsidianakis,
	peter.maydell

On Mon, Sep 22, 2025 at 05:43:50AM +0000, Gustavo Romero wrote:
> This commit removes Avocado as a dependency for running the
> reverse_debugging test.
> 
> The main benefit, beyond eliminating an extra dependency, is that there
> is no longer any need to handle GDB packets manually. This removes the
> need for ad-hoc functions dealing with endianness and arch-specific
> register numbers, making the test easier to read. The timeout variable
> is also removed, since Meson now manages timeouts automatically.
> 
> reverse_debugging now uses the pygdbmi module to interact with GDB, if
> it's available in the test environment, otherwise the test is skipped.
> GDB is detect via the QEMU_TEST_GDB env. variable.
> 
> This commit also significantly improves the output for the test and
> now prints all the GDB commands used in sequence. It also adds
> some clarifications to existing comments, for example, clarifying that
> once the replay-break is reached, a SIGINT is captured in GDB.
> 
> reverse_debugging is kept "skipped" for aarch64, ppc64, and x86_64, so
> won't run unless QEMU_TEST_FLAKY_TESTS=1 is set in the test environment,
> before running 'make check-functional' or 'meson test [...]'.
> 
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
>  tests/functional/reverse_debugging.py | 308 ++++++++++++++++----------
>  1 file changed, 188 insertions(+), 120 deletions(-)
> 
> diff --git a/tests/functional/reverse_debugging.py b/tests/functional/reverse_debugging.py
> index f9a1d395f1..38161beab8 100644
> --- a/tests/functional/reverse_debugging.py
> +++ b/tests/functional/reverse_debugging.py
> @@ -1,21 +1,94 @@
> -# Reverse debugging test
> -#
>  # SPDX-License-Identifier: GPL-2.0-or-later
>  #
> +# Reverse debugging test
> +#
>  # Copyright (c) 2020 ISP RAS
> +# Copyright (c) 2025 Linaro Limited
>  #
>  # Author:
>  #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
> +#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
>  #
>  # 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 os
> +
>  import logging
> +import os
> +import re
> +import subprocess
> +from pygdbmi.gdbcontroller import GdbController
> +from pygdbmi.constants import GdbTimeoutError
> +
>  
>  from qemu_test import LinuxKernelTest, get_qemu_img
>  from qemu_test.ports import Ports
>  
>  
> +class GDB:
> +    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
> +
> +
> +    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):
> +        pattern = r"0x[0-9A-Fa-f]+"
> +        cmd_output = "".join(self.cmd_output)
> +        match = re.search(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()
> +

Can you put this in tests/functional/qemu_test/gdb.py as it is
generic logic not tied to this specific test.

>  class ReverseDebugging(LinuxKernelTest):
>      """
>      Test GDB reverse debugging commands: reverse step and reverse continue.
> @@ -28,21 +101,17 @@ class ReverseDebugging(LinuxKernelTest):
>      that the execution is stopped at the last of them.
>      """
>  
> -    timeout = 10
>      STEPS = 10
> -    endian_is_le = True
>  
>      def run_vm(self, record, shift, args, replay_path, image_path, port):
> -        from avocado.utils import datadrainer
> -
>          logger = logging.getLogger('replay')
>          vm = self.get_vm(name='record' if record else 'replay')
>          vm.set_console()
>          if record:
> -            logger.info('recording the execution...')
> +            logger.info('Recording the execution...')
>              mode = 'record'
>          else:
> -            logger.info('replaying the execution...')
> +            logger.info('Replaying the execution...')

This change isn't really needed imho.

>              mode = 'replay'
>              vm.add_args('-gdb', 'tcp::%d' % port, '-S')
>          vm.add_args('-icount', 'shift=%s,rr=%s,rrfile=%s,rrsnapshot=init' %
> @@ -52,145 +121,144 @@ def run_vm(self, record, shift, args, replay_path, image_path, port):
>          if args:
>              vm.add_args(*args)
>          vm.launch()
> -        console_drainer = datadrainer.LineLogger(vm.console_socket.fileno(),
> -                                    logger=self.log.getChild('console'),
> -                                    stop_check=(lambda : not vm.is_running()))
> -        console_drainer.start()

This is unrelated to gdbmi conversino, so can you drop the data drainer
stuff on its own, eg this patch

  https://lists.nongnu.org/archive/html/qemu-devel/2025-09/msg02501.html

> -        return vm
>  
> -    @staticmethod
> -    def get_reg_le(g, reg):
> -        res = g.cmd(b'p%x' % reg)
> -        num = 0
> -        for i in range(len(res))[-2::-2]:
> -            num = 0x100 * num + int(res[i:i + 2], 16)
> -        return num
> -
> -    @staticmethod
> -    def get_reg_be(g, reg):
> -        res = g.cmd(b'p%x' % reg)
> -        return int(res, 16)
> -
> -    def get_reg(self, g, reg):
> -        # value may be encoded in BE or LE order
> -        if self.endian_is_le:
> -            return self.get_reg_le(g, reg)
> -        else:
> -            return self.get_reg_be(g, reg)
> -
> -    def get_pc(self, g):
> -        return self.get_reg(g, self.REG_PC)
> -
> -    def check_pc(self, g, addr):
> -        pc = self.get_pc(g)
> -        if pc != addr:
> -            self.fail('Invalid PC (read %x instead of %x)' % (pc, addr))
> -
> -    @staticmethod
> -    def gdb_step(g):
> -        g.cmd(b's', b'T05thread:01;')
> -
> -    @staticmethod
> -    def gdb_bstep(g):
> -        g.cmd(b'bs', b'T05thread:01;')
> +        return vm
>  
>      @staticmethod
>      def vm_get_icount(vm):
>          return vm.qmp('query-replay')['return']['icount']
>  
>      def reverse_debugging(self, shift=7, args=None):
> -        from avocado.utils import gdb
> -        from avocado.utils import process
> -
>          logger = logging.getLogger('replay')
>  
> -        # create qcow2 for snapshots
> -        logger.info('creating qcow2 image for VM snapshots')
> +        # Create qcow2 for snapshots
> +        logger.info('Creating qcow2 image for VM snapshots')

Please avoid mixing in extra changes like this with other
functional refactoring, as it makes the diff larger and
harder to review. There's many more examples of changes
like this but I wouln't point them all out.  If you still
think this is beneficial, do it as a separate commit from
the functional changes.

>          image_path = os.path.join(self.workdir, 'disk.qcow2')
>          qemu_img = get_qemu_img(self)
>          if qemu_img is None:
>              self.skipTest('Could not find "qemu-img", which is required to '
>                            'create the temporary qcow2 image')
>          cmd = '%s create -f qcow2 %s 128M' % (qemu_img, image_path)
> -        process.run(cmd)
> +        r = subprocess.run(cmd, capture_output=True, shell=True, text=True)
> +        logger.info(r.args)
> +        logger.info(r.stdout)

Can you remove the 'process.run' in a separate commit, and use
check_output, rather than 'run', and avoid the use of shell.
ie this patch:

 https://lists.nongnu.org/archive/html/qemu-devel/2025-09/msg02500.html

>  
>          replay_path = os.path.join(self.workdir, 'replay.bin')
>  
> -        # record the log
> +        # Record the log.
>          vm = self.run_vm(True, shift, args, replay_path, image_path, -1)
>          while self.vm_get_icount(vm) <= self.STEPS:
>              pass
>          last_icount = self.vm_get_icount(vm)
>          vm.shutdown()
>  
> -        logger.info("recorded log with %s+ steps" % last_icount)
> +        logger.info("Recorded log with %s+ steps" % last_icount)
> +
> +        # Replay and run debug commands.
> +        gdb_cmd = os.getenv('QEMU_TEST_GDB')
> +        if not gdb_cmd:
> +            test.skipTest(f"Test skipped because there is no GDB available!")


This message doesn't tell the user what envrionment variable they
are missing.

Can you introduce a 'skipIfMissingEnv(envname)' decorator to
tests/functional/qemu_test/decorators.py, and then use it to
decorate the tests

>  
> -        # replay and run debug commands
>          with Ports() as ports:
>              port = ports.find_free_port()
>              vm = self.run_vm(False, shift, args, replay_path, image_path, port)


I'd suggest that at this point to start a new method

       try:
         logger.info('connecting to gdbstub')
         self.reverse_debugging_run(vm)
         logger.info('Test passed.')
       except GdbTimeoutError:
         self.fail("Connection to gdbstub timeouted...")

So we have a separation between the test bootstrap and the test
execution, with the added benefit that this patch won't bulk
re-indent everything below this point - the bulk re-indent makes
it hard to review this

> -        logger.info('connecting to gdbstub')
> -        g = gdb.GDBRemote('127.0.0.1', port, False, False)
> -        g.connect()
> -        r = g.cmd(b'qSupported')
> -        if b'qXfer:features:read+' in r:
> -            g.cmd(b'qXfer:features:read:target.xml:0,ffb')
> -        if b'ReverseStep+' not in r:
> -            self.fail('Reverse step is not supported by QEMU')
> -        if b'ReverseContinue+' not in r:
> -            self.fail('Reverse continue is not supported by QEMU')
> -
> -        logger.info('stepping forward')
> -        steps = []
> -        # record first instruction addresses
> -        for _ in range(self.STEPS):
> -            pc = self.get_pc(g)
> -            logger.info('saving position %x' % pc)
> -            steps.append(pc)
> -            self.gdb_step(g)
> -
> -        # visit the recorded instruction in reverse order
> -        logger.info('stepping backward')
> -        for addr in steps[::-1]:
> -            self.gdb_bstep(g)
> -            self.check_pc(g, addr)
> -            logger.info('found position %x' % addr)
> -
> -        # visit the recorded instruction in forward order
> -        logger.info('stepping forward')
> -        for addr in steps:
> -            self.check_pc(g, addr)
> -            self.gdb_step(g)
> -            logger.info('found position %x' % addr)
> -
> -        # set breakpoints for the instructions just stepped over
> -        logger.info('setting breakpoints')
> -        for addr in steps:
> -            # hardware breakpoint at addr with len=1
> -            g.cmd(b'Z1,%x,1' % addr, b'OK')
> -
> -        # this may hit a breakpoint if first instructions are executed
> -        # again
> -        logger.info('continuing execution')
> -        vm.qmp('replay-break', icount=last_icount - 1)
> -        # continue - will return after pausing
> -        # This could stop at the end and get a T02 return, or by
> -        # re-executing one of the breakpoints and get a T05 return.
> -        g.cmd(b'c')
> -        if self.vm_get_icount(vm) == last_icount - 1:
> -            logger.info('reached the end (icount %s)' % (last_icount - 1))
> -        else:
> -            logger.info('hit a breakpoint again at %x (icount %s)' %
> -                        (self.get_pc(g), self.vm_get_icount(vm)))
>  
> -        logger.info('running reverse continue to reach %x' % steps[-1])
> -        # reverse continue - will return after stopping at the breakpoint
> -        g.cmd(b'bc', b'T05thread:01;')
> +        try:
> +            gdb = GDB(gdb_cmd)
>  
> -        # assume that none of the first instructions is executed again
> -        # breaking the order of the breakpoints
> -        self.check_pc(g, steps[-1])
> -        logger.info('successfully reached %x' % steps[-1])
> +            logger.info('Connecting to gdbstub...')
>  
> -        logger.info('exiting gdb and qemu')
> -        vm.shutdown()
> +            gdb.cli("set debug remote 1")
> +
> +            c = gdb.cli(f"target remote localhost:{port}").get_console()
> +            if not f"Remote debugging using localhost:{port}" in c:
> +                self.fail("Could not connect to gdbstub!")
> +
> +            # Remote debug messages are in 'log' payloads.
> +            r = gdb.get_log()
> +            if 'ReverseStep+' not in r:
> +                self.fail('Reverse step is not supported by QEMU')
> +            if 'ReverseContinue+' not in r:
> +                self.fail('Reverse continue is not supported by QEMU')
> +
> +            gdb.cli("set debug remote 0")
> +
> +            logger.info('Stepping forward')
> +            steps = []
> +            # Record first instruction addresses.
> +            for _ in range(self.STEPS):
> +                pc = gdb.cli("print $pc").get_addr()
> +                logger.info('Saving position %x' % pc)
> +                steps.append(pc)
> +
> +                gdb.cli("stepi")
> +
> +            # Visit the recorded instructions in reverse order.
> +            logger.info('Stepping backward')
> +            for saved_pc in steps[::-1]:
> +                logger.info('Found position %x' % saved_pc)
> +                gdb.cli("reverse-stepi")
> +                pc = gdb.cli("print $pc").get_addr()
> +                if pc != saved_pc:
> +                    logger.info('Invalid PC (read %x instead of %x)' % (pc, saved_pc))
> +                    self.fail('Reverse stepping failed!')
> +
> +            # Visit the recorded instructions in forward order.
> +            logger.info('Stepping forward')
> +            for saved_pc in steps:
> +                logger.info('Found position %x' % saved_pc)
> +                pc = gdb.cli("print $pc").get_addr()
> +                if pc != saved_pc:
> +                    logger.info('Invalid PC (read %x instead of %x)' % (pc, saved_pc))
> +                    self.fail('Forward stepping failed!')
> +
> +                gdb.cli("stepi")
> +
> +            # Set breakpoints for the instructions just stepped over.
> +            logger.info('Setting breakpoints')
> +            for saved_pc in steps:
> +                gdb.cli(f"break *{hex(saved_pc)}")
> +
> +            # This may hit a breakpoint if first instructions are executed again.
> +            logger.info('Continuing execution')
> +            vm.qmp('replay-break', icount=last_icount - 1)
> +            # continue - will return after pausing.
> +            # This can stop at the end of the replay-break and gdb gets a SIGINT,
> +            # or by re-executing one of the breakpoints and gdb stops at a
> +            # breakpoint.
> +            gdb.cli("continue")
> +
> +            pc = gdb.cli("print $pc").get_addr()
> +            current_icount = self.vm_get_icount(vm)
> +            if current_icount == last_icount - 1:
> +                print(f"# **** Hit replay-break at icount={current_icount}, pc={hex(pc)} ****")
> +                logger.info('Reached the end (icount %s)' % (current_icount))
> +            else:
> +                print(f"# **** Hit breakpoint at icount={current_icount}, pc={hex(pc)} ****")
> +                logger.info('Hit a breakpoint again at %x (icount %s)' %
> +                            (pc, current_icount))
> +
> +            logger.info('Running reverse continue to reach %x' % steps[-1])
> +            # reverse-continue - will return after stopping at the breakpoint.
> +            gdb.cli("reverse-continue")
> +
> +            # Assume that none of the first instructions are executed again
> +            # breaking the order of the breakpoints.
> +            # steps[-1] is the first saved $pc in reverse order.
> +            pc = gdb.cli("print $pc").get_addr()
> +            first_pc_in_rev_order = steps[-1]
> +            if pc == first_pc_in_rev_order:
> +                print(f"# **** Hit breakpoint at the first PC in reverse order ({hex(pc)}) ****")
> +                logger.info('Successfully reached breakpoint at %x' % first_pc_in_rev_order)
> +            else:
> +                logger.info('Failed to reach breakpoint at %x' % first_pc_in_rev_order)
> +                self.fail("'reverse-continue' did not hit the first PC in reverse order!")
> +
> +            logger.info('Exiting GDB and QEMU...')
> +            gdb.exit()
> +            vm.shutdown()
> +
> +            logger.info('Test passed.')
> +
> +        except GdbTimeoutError:
> +            self.fail("Connection to gdbstub timeouted...")
> -- 
> 2.34.1
> 

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 4/4] tests/functional: Adapt arches to reverse_debugging w/o Avocado
  2025-09-22  5:43 ` [PATCH v3 4/4] tests/functional: Adapt arches to reverse_debugging " Gustavo Romero
@ 2025-09-22  9:33   ` Daniel P. Berrangé
  2025-09-22  9:34   ` Daniel P. Berrangé
  1 sibling, 0 replies; 10+ messages in thread
From: Daniel P. Berrangé @ 2025-09-22  9:33 UTC (permalink / raw)
  To: Gustavo Romero
  Cc: qemu-devel, alex.bennee, thuth, qemu-arm, manos.pitsidianakis,
	peter.maydell

On Mon, Sep 22, 2025 at 05:43:51AM +0000, Gustavo Romero wrote:
> reverse_debugging no longer depends on Avocado, so remove the import
> checks for Avocado, the per-arch endianness tweaks, and the per-arch
> register settings. All of these are now handled in the ReverseDebugging
> class.
> 
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
>  .../functional/aarch64/test_reverse_debug.py  | 13 +++++--------
>  tests/functional/ppc64/test_reverse_debug.py  | 15 +++++----------
>  tests/functional/x86_64/test_reverse_debug.py | 19 ++++++-------------
>  3 files changed, 16 insertions(+), 31 deletions(-)
> 
> diff --git a/tests/functional/aarch64/test_reverse_debug.py b/tests/functional/aarch64/test_reverse_debug.py
> index 8bc91ccfde..7f816025a9 100755
> --- a/tests/functional/aarch64/test_reverse_debug.py
> +++ b/tests/functional/aarch64/test_reverse_debug.py
> @@ -1,26 +1,23 @@
> -#!/usr/bin/env python3

Please don't remove this - all the tests are expected to be directly
excecutable. (Same comment for the other files)

> -#
>  # SPDX-License-Identifier: GPL-2.0-or-later
>  #
> -# Reverse debugging test
> +# Reverse debugging test for aarch64
>  #
>  # Copyright (c) 2020 ISP RAS
> +# Copyright (c) 2025 Linaro Limited
>  #
>  # Author:
>  #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
> +#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
>  #
>  # This work is licensed under the terms of the GNU GPL, version 2 or
>  # later.  See the COPYING file in the top-level directory.
>  
> -from qemu_test import Asset, skipIfMissingImports, skipFlakyTest
> +from qemu_test import Asset, skipFlakyTest

>  from reverse_debugging import ReverseDebugging
>  
>  
> -@skipIfMissingImports('avocado.utils')
>  class ReverseDebugging_AArch64(ReverseDebugging):
>  
> -    REG_PC = 32
> -
>      ASSET_KERNEL = Asset(
>          ('https://archives.fedoraproject.org/pub/archive/fedora/linux/'
>           'releases/29/Everything/aarch64/os/images/pxeboot/vmlinuz'),
> @@ -35,4 +32,4 @@ def test_aarch64_virt(self):
>  
>  
>  if __name__ == '__main__':
> -    ReverseDebugging.main()
> +    ReverseDebugging_AArch64.main()
> diff --git a/tests/functional/ppc64/test_reverse_debug.py b/tests/functional/ppc64/test_reverse_debug.py
> index 5931adef5a..2b7b18e9a8 100755
> --- a/tests/functional/ppc64/test_reverse_debug.py
> +++ b/tests/functional/ppc64/test_reverse_debug.py
> @@ -1,41 +1,36 @@
> -#!/usr/bin/env python3
> -#
>  # SPDX-License-Identifier: GPL-2.0-or-later
>  #
> -# Reverse debugging test
> +# Reverse debugging test for ppc64
>  #
>  # Copyright (c) 2020 ISP RAS
> +# Copyright (c) 2025 Linaro Limited
>  #
>  # Author:
>  #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
> +#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
>  #
>  # This work is licensed under the terms of the GNU GPL, version 2 or
>  # later.  See the COPYING file in the top-level directory.
>  
> -from qemu_test import skipIfMissingImports, skipFlakyTest
> +from qemu_test import skipFlakyTest
>  from reverse_debugging import ReverseDebugging
>  
>  
> -@skipIfMissingImports('avocado.utils')
>  class ReverseDebugging_ppc64(ReverseDebugging):
>  
> -    REG_PC = 0x40
> -
>      @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/1992")
>      def test_ppc64_pseries(self):
>          self.set_machine('pseries')
>          # SLOF branches back to its entry point, which causes this test
>          # to take the 'hit a breakpoint again' path. That's not a problem,
>          # just slightly different than the other machines.
> -        self.endian_is_le = False
>          self.reverse_debugging()
>  
>      @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/1992")
>      def test_ppc64_powernv(self):
>          self.set_machine('powernv')
> -        self.endian_is_le = False
>          self.reverse_debugging()
>  
>  
>  if __name__ == '__main__':
> -    ReverseDebugging.main()
> +    ReverseDebugging_ppc64.main()
> diff --git a/tests/functional/x86_64/test_reverse_debug.py b/tests/functional/x86_64/test_reverse_debug.py
> index d713e91e14..408e5d1f48 100755
> --- a/tests/functional/x86_64/test_reverse_debug.py
> +++ b/tests/functional/x86_64/test_reverse_debug.py
> @@ -1,36 +1,29 @@
> -#!/usr/bin/env python3
> -#
>  # SPDX-License-Identifier: GPL-2.0-or-later
>  #
> -# Reverse debugging test
> +# Reverse debugging test for x86_64
>  #
>  # Copyright (c) 2020 ISP RAS
> +# Copyright (c) 2025 Linaro Limited
>  #
>  # Author:
>  #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
> +#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
>  #
>  # This work is licensed under the terms of the GNU GPL, version 2 or
>  # later.  See the COPYING file in the top-level directory.
>  
> -from qemu_test import skipIfMissingImports, skipFlakyTest
> +from qemu_test import skipFlakyTest
>  from reverse_debugging import ReverseDebugging
>  
>  
> -@skipIfMissingImports('avocado.utils')
>  class ReverseDebugging_X86_64(ReverseDebugging):
>  
> -    REG_PC = 0x10
> -    REG_CS = 0x12
> -    def get_pc(self, g):
> -        return self.get_reg_le(g, self.REG_PC) \
> -            + self.get_reg_le(g, self.REG_CS) * 0x10
> -
>      @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/2922")
>      def test_x86_64_pc(self):
>          self.set_machine('pc')
> -        # start with BIOS only
> +        # Start with BIOS only
>          self.reverse_debugging()
>  
>  
>  if __name__ == '__main__':
> -    ReverseDebugging.main()
> +    ReverseDebugging_X86_64.main()
> -- 
> 2.34.1
> 

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 4/4] tests/functional: Adapt arches to reverse_debugging w/o Avocado
  2025-09-22  5:43 ` [PATCH v3 4/4] tests/functional: Adapt arches to reverse_debugging " Gustavo Romero
  2025-09-22  9:33   ` Daniel P. Berrangé
@ 2025-09-22  9:34   ` Daniel P. Berrangé
  1 sibling, 0 replies; 10+ messages in thread
From: Daniel P. Berrangé @ 2025-09-22  9:34 UTC (permalink / raw)
  To: Gustavo Romero
  Cc: qemu-devel, alex.bennee, thuth, qemu-arm, manos.pitsidianakis,
	peter.maydell

On Mon, Sep 22, 2025 at 05:43:51AM +0000, Gustavo Romero wrote:
> reverse_debugging no longer depends on Avocado, so remove the import
> checks for Avocado, the per-arch endianness tweaks, and the per-arch
> register settings. All of these are now handled in the ReverseDebugging
> class.
> 
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
>  .../functional/aarch64/test_reverse_debug.py  | 13 +++++--------
>  tests/functional/ppc64/test_reverse_debug.py  | 15 +++++----------
>  tests/functional/x86_64/test_reverse_debug.py | 19 ++++++-------------
>  3 files changed, 16 insertions(+), 31 deletions(-)
> 
> diff --git a/tests/functional/aarch64/test_reverse_debug.py b/tests/functional/aarch64/test_reverse_debug.py
> index 8bc91ccfde..7f816025a9 100755
> --- a/tests/functional/aarch64/test_reverse_debug.py
> +++ b/tests/functional/aarch64/test_reverse_debug.py
> @@ -1,26 +1,23 @@
> -#!/usr/bin/env python3
> -#
>  # SPDX-License-Identifier: GPL-2.0-or-later
>  #
> -# Reverse debugging test
> +# Reverse debugging test for aarch64
>  #
>  # Copyright (c) 2020 ISP RAS
> +# Copyright (c) 2025 Linaro Limited
>  #
>  # Author:
>  #  Pavel Dovgalyuk <Pavel.Dovgalyuk@ispras.ru>
> +#  Gustavo Romero <gustavo.romero@linaro.org> (Run without Avocado)
>  #
>  # This work is licensed under the terms of the GNU GPL, version 2 or
>  # later.  See the COPYING file in the top-level directory.
>  
> -from qemu_test import Asset, skipIfMissingImports, skipFlakyTest
> +from qemu_test import Asset, skipFlakyTest
>  from reverse_debugging import ReverseDebugging
>  
>  
> -@skipIfMissingImports('avocado.utils')
>  class ReverseDebugging_AArch64(ReverseDebugging):
>  
> -    REG_PC = 32
> -
>      ASSET_KERNEL = Asset(
>          ('https://archives.fedoraproject.org/pub/archive/fedora/linux/'
>           'releases/29/Everything/aarch64/os/images/pxeboot/vmlinuz'),
> @@ -35,4 +32,4 @@ def test_aarch64_virt(self):
>  
>  
>  if __name__ == '__main__':
> -    ReverseDebugging.main()
> +    ReverseDebugging_AArch64.main()

This shouldn't be needed AFAICT ?  (Same for other files)



>      @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/2922")
>      def test_x86_64_pc(self):
>          self.set_machine('pc')
> -        # start with BIOS only
> +        # Start with BIOS only

Spurious comment change

>          self.reverse_debugging()
>  
>  
>  if __name__ == '__main__':
> -    ReverseDebugging.main()
> +    ReverseDebugging_X86_64.main()
> -- 
> 2.34.1
> 

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 1/4] python: Install pygdbmi in venv
  2025-09-22  5:43 ` [PATCH v3 1/4] python: Install pygdbmi in venv Gustavo Romero
@ 2025-09-22 11:10   ` Thomas Huth
  0 siblings, 0 replies; 10+ messages in thread
From: Thomas Huth @ 2025-09-22 11:10 UTC (permalink / raw)
  To: Gustavo Romero, qemu-devel, alex.bennee, berrange
  Cc: qemu-arm, manos.pitsidianakis, peter.maydell

On 22/09/2025 07.43, Gustavo Romero wrote:
> Install pygdbmi in Meson's venv. pygdbmi is required by functional tests
> that interact with GDB. pygdbmi size is only 21 kB.
> 
> The wheel file has been obtained with:
> 
> pyvenv/bin/pip3 download --only-binary :all: --dest . --no-cache pygdbmi
> 
> Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
> ---
>   python/wheels/pygdbmi-0.11.0.0-py3-none-any.whl | Bin 0 -> 21258 bytes
>   pythondeps.toml                                 |   1 +
>   2 files changed, 1 insertion(+)
>   create mode 100644 python/wheels/pygdbmi-0.11.0.0-py3-none-any.whl

I think we should rather avoid to add python test dependencies as wheels 
(unless it's really necessary as it was with the pycotap module that is also 
required for the "quick" tests).

It's better if we finally re-activate the "check-venv" target for the 
functional tests (we used it in the past for the avocado-based tests, too).

Could you please try whether something like this does the job for you:

diff --git a/pythondeps.toml b/pythondeps.toml
--- a/pythondeps.toml
+++ b/pythondeps.toml
@@ -33,3 +33,4 @@ sphinx_rtd_theme = { accepted = ">=0.5", installed = "1.2.2" }

  [testdeps]
  qemu.qmp = { accepted = ">=0.0.3", installed = "0.0.3" }
+pygdbmi = { accepted = ">=0.11.0.0", installed = "0.11.0.0" }
diff --git a/tests/Makefile.include b/tests/Makefile.include
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -109,7 +109,7 @@ $(FUNCTIONAL_TARGETS):
         @$(MAKE) SPEED=thorough $(subst -functional,-func,$@)

  .PHONY: check-functional
-check-functional:
+check-functional: check-venv
         @$(NINJA) precache-functional
         @QEMU_TEST_NO_DOWNLOAD=1 $(MAKE) SPEED=thorough check-func 
check-func-quick


?

  Thanks,
   Thomas



^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2025-09-22 11:11 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-22  5:43 [PATCH v3 0/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
2025-09-22  5:43 ` [PATCH v3 1/4] python: Install pygdbmi in venv Gustavo Romero
2025-09-22 11:10   ` Thomas Huth
2025-09-22  5:43 ` [PATCH v3 2/4] tests/functional: Provide GDB to the functional tests Gustavo Romero
2025-09-22  5:43 ` [PATCH v3 3/4] tests/functional: Adapt reverse_debugging to run w/o Avocado Gustavo Romero
2025-09-22  9:30   ` Alex Bennée
2025-09-22  9:30   ` Daniel P. Berrangé
2025-09-22  5:43 ` [PATCH v3 4/4] tests/functional: Adapt arches to reverse_debugging " Gustavo Romero
2025-09-22  9:33   ` Daniel P. Berrangé
2025-09-22  9:34   ` Daniel P. Berrangé

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).