On Thu, Sep 04, 2025 at 03:46:35PM +0000, Gustavo Romero wrote:
> In this series, we leveraged the run-test.py script used in the
> check-tcg tests, making it a GDB runner capable of calling a test script
> without spawning any VMs. In this configuration, the test scripts can
> manage the VM and also import gdb, making the GDB Python API inside the
> functional test scripts.
>
> A --quiet option has been added to run-test.py so it doesn't print the
> command line used to execute GDB to the stdout. This ensures that users
> don't get confused about how to re-run the tests. One can re-run the
> test simply by copying and pasting the command line shown by Meson when
> V=1 is passed:
>
> $ make -j check-functional V=1
>
> or, alternatively, once the test run completes, the exact command found
> in the 'command:' field of the build/meson-logs/testlog-thorough.txt
> file generated by Meson. Both methods provide the correct environment
> variables required to run the test, such as the proper $PYTHONPATH.
While I like the conceptual idea of just sending human GDB commands,
instead of working with GDB protocol packets, I really dislike the
effect this has on the execution / startup of the functional tests
via use of the custom runner for a number of reasons
* The command line for launching the test outside of meson is very
complicated, so not memorable
* It makes the meson.build rules much more complicated
* Running standalone there is no TAP output available making the
test hard to debug on failure or timeout
I understand the need to spawn the test via gdb, in order to be able
to import the 'gdb' python module. Looking at what reverse_debugging.py
does, however, makes me question whether we actually need to directly
use the 'gdb' python module.
The only APIs we use are 'gdb.execute' and 'gdb.parse_and_eval'.
The latter is only used once as
gdb.parse_and_eval("$pc")
and I believe that can be changed to
gdb.execute("printf \"0x%x\", $pc", to_string=True)
IOW, all we need is 'gdb.execute("....", to_string=True)'
With a little extra helper proxy script, we can achieve this without
changing the way scripts are launched.
The script needs to listen on a UNIX socket path. When a client
connects, it should read lines of data from the client and pass
them to 'gdb.execute(..., to_string=True)' and whatever data
gdb returns should be written back to the client.
A very very crude example with no error handling would be:
#!/usr/bin/python3
import gdb
import os
import socket
sock = os.environ.get("QEMU_PROXY", "/tmp/qemu.gdb.proxy")
try:
os.unlink(sock)
except:
pass
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.bind(sock)
s.listen()
conn, addr = s.accept()
fh = conn.makefile('rw')
with conn:
while True:
line = fh.readline()
if not line:
break
data = gdb.execute(line, to_string=True)
fh.write(data)
fh.flush()
In the functional test suite, we should have a helper file
tests/functional/qemu_test/gdb.py that provides an API for
launching GDB to execute this proxy script, and an API to
execute commands by talking over this UNIX socket path.
With this, we will need no changes in the way we execute the
reverse debugging script from a test runner POV, thus avoiding
all the downsides of use of the run-test.py script. IOW, the
first 4 patches in this series go away completely. Instead we
need a patch to create the proxy script and a patch to create
the helper APIs in tests/functional/qemu_test/gdb.py, whereupon
the last patch can replace
try:
import gdb
except ModuleNotFoundError:
from sys import exit
exit("This script must be launched via tests/guest-debug/run-test.py!")
with
from qemu_test import gdb
and the earlier mentioned replacement of parse_and_eval()
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 :|