aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile.include4
-rw-r--r--tests/data/acpi/loongarch64/virt/DSDTbin4603 -> 4603 bytes
-rw-r--r--tests/data/acpi/loongarch64/virt/DSDT.memhpbin5824 -> 5824 bytes
-rw-r--r--tests/data/acpi/loongarch64/virt/DSDT.numamembin4609 -> 4609 bytes
-rw-r--r--tests/data/acpi/loongarch64/virt/DSDT.topologybin4905 -> 4905 bytes
-rwxr-xr-xtests/docker/common.rc2
-rw-r--r--tests/docker/dockerfiles/debian-i686-cross.docker2
-rwxr-xr-xtests/functional/aarch64/test_device_passthrough.py4
-rwxr-xr-xtests/functional/aarch64/test_reverse_debug.py12
-rwxr-xr-xtests/functional/aarch64/test_rme_sbsaref.py4
-rwxr-xr-xtests/functional/aarch64/test_rme_virt.py4
-rw-r--r--tests/functional/meson.build6
-rwxr-xr-xtests/functional/ppc64/test_reverse_debug.py15
-rw-r--r--tests/functional/qemu_test/__init__.py4
-rw-r--r--tests/functional/qemu_test/asset.py1
-rw-r--r--tests/functional/qemu_test/decorators.py18
-rw-r--r--tests/functional/qemu_test/gdb.py86
-rw-r--r--tests/functional/reverse_debugging.py152
-rwxr-xr-xtests/functional/x86_64/test_reverse_debug.py15
-rwxr-xr-xtests/lcitool/refresh6
-rw-r--r--tests/tcg/aarch64/Makefile.target5
-rw-r--r--tests/tcg/aarch64/gcs.h80
-rw-r--r--tests/tcg/aarch64/gcspushm.c71
-rw-r--r--tests/tcg/aarch64/gcsss.c74
-rw-r--r--tests/tcg/aarch64/gcsstr.c48
25 files changed, 498 insertions, 115 deletions
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 62a4fc8..e47ef4d 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -105,11 +105,11 @@ check-venv: $(TESTS_VENV_TOKEN)
FUNCTIONAL_TARGETS=$(patsubst %-softmmu,check-functional-%, $(filter %-softmmu,$(TARGETS)))
.PHONY: $(FUNCTIONAL_TARGETS)
-$(FUNCTIONAL_TARGETS):
+$(FUNCTIONAL_TARGETS): check-venv
@$(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
diff --git a/tests/data/acpi/loongarch64/virt/DSDT b/tests/data/acpi/loongarch64/virt/DSDT
index b31841a..55aa34f 100644
--- a/tests/data/acpi/loongarch64/virt/DSDT
+++ b/tests/data/acpi/loongarch64/virt/DSDT
Binary files differ
diff --git a/tests/data/acpi/loongarch64/virt/DSDT.memhp b/tests/data/acpi/loongarch64/virt/DSDT.memhp
index e291200..c0955eb 100644
--- a/tests/data/acpi/loongarch64/virt/DSDT.memhp
+++ b/tests/data/acpi/loongarch64/virt/DSDT.memhp
Binary files differ
diff --git a/tests/data/acpi/loongarch64/virt/DSDT.numamem b/tests/data/acpi/loongarch64/virt/DSDT.numamem
index 07923ac..61e47e7 100644
--- a/tests/data/acpi/loongarch64/virt/DSDT.numamem
+++ b/tests/data/acpi/loongarch64/virt/DSDT.numamem
Binary files differ
diff --git a/tests/data/acpi/loongarch64/virt/DSDT.topology b/tests/data/acpi/loongarch64/virt/DSDT.topology
index 6dfbb49..b2afebc 100644
--- a/tests/data/acpi/loongarch64/virt/DSDT.topology
+++ b/tests/data/acpi/loongarch64/virt/DSDT.topology
Binary files differ
diff --git a/tests/docker/common.rc b/tests/docker/common.rc
index 752f4f3..79d533a 100755
--- a/tests/docker/common.rc
+++ b/tests/docker/common.rc
@@ -53,8 +53,8 @@ configure_qemu()
config_opts="--enable-werror \
${TARGET_LIST:+--target-list=${TARGET_LIST}} \
--prefix=$INSTALL_DIR \
- $QEMU_CONFIGURE_OPTS $EXTRA_CONFIGURE_OPTS \
$enable_rust \
+ $QEMU_CONFIGURE_OPTS $EXTRA_CONFIGURE_OPTS \
$@"
echo "Configure options:"
echo $config_opts
diff --git a/tests/docker/dockerfiles/debian-i686-cross.docker b/tests/docker/dockerfiles/debian-i686-cross.docker
index 4e8b3a8..2998764 100644
--- a/tests/docker/dockerfiles/debian-i686-cross.docker
+++ b/tests/docker/dockerfiles/debian-i686-cross.docker
@@ -178,7 +178,7 @@ ENV ABI "i686-linux-gnu"
ENV MESON_OPTS "--cross-file=i686-linux-gnu"
ENV RUST_TARGET "i686-unknown-linux-gnu"
ENV QEMU_CONFIGURE_OPTS --cross-prefix=i686-linux-gnu-
-ENV DEF_TARGET_LIST x86_64-softmmu,x86_64-linux-user,i386-softmmu,i386-linux-user
+ENV DEF_TARGET_LIST i386-softmmu,i386-linux-user
# As a final step configure the user (if env is defined)
ARG USER
ARG UID
diff --git a/tests/functional/aarch64/test_device_passthrough.py b/tests/functional/aarch64/test_device_passthrough.py
index 1743778..05a3f52 100755
--- a/tests/functional/aarch64/test_device_passthrough.py
+++ b/tests/functional/aarch64/test_device_passthrough.py
@@ -85,8 +85,8 @@ class Aarch64DevicePassthrough(QemuSystemTest):
# https://docs.kernel.org/driver-api/vfio.html#vfio-device-cde
ASSET_DEVICE_PASSTHROUGH_STACK = Asset(
('https://github.com/pbo-linaro/qemu-linux-stack/'
- 'releases/download/build/device_passthrough-c3fb84a.tar.xz'),
- '15ac2b02bed0c0ea8e3e007de0bcfdaf6fd51c1ba98213f841dc7d01d6f72f04')
+ 'releases/download/build/device_passthrough-a9612a2.tar.xz'),
+ 'f7d2f70912e7231986e6e293e1a2c4786dd02bec113a7acb6bfc619e96155455')
# This tests the device passthrough implementation, by booting a VM
# supporting it with two nvme disks attached, and launching a nested VM
diff --git a/tests/functional/aarch64/test_reverse_debug.py b/tests/functional/aarch64/test_reverse_debug.py
index 8bc91cc..ec3348c 100755
--- a/tests/functional/aarch64/test_reverse_debug.py
+++ b/tests/functional/aarch64/test_reverse_debug.py
@@ -2,36 +2,34 @@
#
# 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'),
'7e1430b81c26bdd0da025eeb8fbd77b5dc961da4364af26e771bd39f379cbbf7')
- @skipFlakyTest("https://gitlab.com/qemu-project/qemu/-/issues/2921")
def test_aarch64_virt(self):
self.set_machine('virt')
self.cpu = 'cortex-a53'
kernel_path = self.ASSET_KERNEL.fetch()
- self.reverse_debugging(args=('-kernel', kernel_path))
+ self.reverse_debugging(gdb_arch='aarch64', args=('-kernel', kernel_path))
if __name__ == '__main__':
diff --git a/tests/functional/aarch64/test_rme_sbsaref.py b/tests/functional/aarch64/test_rme_sbsaref.py
index ca892e0..6f92858 100755
--- a/tests/functional/aarch64/test_rme_sbsaref.py
+++ b/tests/functional/aarch64/test_rme_sbsaref.py
@@ -25,8 +25,8 @@ class Aarch64RMESbsaRefMachine(QemuSystemTest):
# ./build.sh && ./archive_artifacts.sh out.tar.xz
ASSET_RME_STACK_SBSA = Asset(
('https://github.com/pbo-linaro/qemu-linux-stack/'
- 'releases/download/build/rme_sbsa_release-a7f02cf.tar.xz'),
- '27d8400b11befb828d6db0cab97e7ae102d0992c928d3dfbf38b24b6cf6c324c')
+ 'releases/download/build/rme_sbsa_release-6a2dfc5.tar.xz'),
+ '5adba482aa069912292a8da746c6b21268224d9d81c97fe7c0bed690579ebdcb')
# This tests the FEAT_RME cpu implementation, by booting a VM supporting it,
# and launching a nested VM using it.
diff --git a/tests/functional/aarch64/test_rme_virt.py b/tests/functional/aarch64/test_rme_virt.py
index bb603aa..5e23773 100755
--- a/tests/functional/aarch64/test_rme_virt.py
+++ b/tests/functional/aarch64/test_rme_virt.py
@@ -23,8 +23,8 @@ class Aarch64RMEVirtMachine(QemuSystemTest):
# ./build.sh && ./archive_artifacts.sh out.tar.xz
ASSET_RME_STACK_VIRT = Asset(
('https://github.com/pbo-linaro/qemu-linux-stack/'
- 'releases/download/build/rme_release-86101e5.tar.xz'),
- 'e42fef8439badb52a071ac446fc33cff4cb7d61314c7a28fdbe61a11e1faad3a')
+ 'releases/download/build/rme_release-56bc99e.tar.xz'),
+ '0e3dc6b8a4b828dbae09c951a40dcb710eded084b32432b50c69cf4173ffa4be')
# This tests the FEAT_RME cpu implementation, by booting a VM supporting it,
# and launching a nested VM using it.
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index 2a0c5aa..725630d 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')
diff --git a/tests/functional/ppc64/test_reverse_debug.py b/tests/functional/ppc64/test_reverse_debug.py
index 5931ade..69551fb 100755
--- a/tests/functional/ppc64/test_reverse_debug.py
+++ b/tests/functional/ppc64/test_reverse_debug.py
@@ -2,39 +2,36 @@
#
# 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()
+ self.reverse_debugging(gdb_arch='powerpc:common64')
@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()
+ self.reverse_debugging(gdb_arch='powerpc:common64')
if __name__ == '__main__':
diff --git a/tests/functional/qemu_test/__init__.py b/tests/functional/qemu_test/__init__.py
index 6e666a0..3201935 100644
--- a/tests/functional/qemu_test/__init__.py
+++ b/tests/functional/qemu_test/__init__.py
@@ -15,6 +15,8 @@ from .testcase import QemuBaseTest, QemuUserTest, QemuSystemTest
from .linuxkernel import LinuxKernelTest
from .decorators import skipIfMissingCommands, skipIfNotMachine, \
skipFlakyTest, skipUntrustedTest, skipBigDataTest, skipSlowTest, \
- skipIfMissingImports, skipIfOperatingSystem, skipLockedMemoryTest
+ skipIfMissingImports, skipIfOperatingSystem, skipLockedMemoryTest, \
+ skipIfMissingEnv
from .archive import archive_extract
from .uncompress import uncompress
+from .gdb import GDB
diff --git a/tests/functional/qemu_test/asset.py b/tests/functional/qemu_test/asset.py
index 2971a98..f666125 100644
--- a/tests/functional/qemu_test/asset.py
+++ b/tests/functional/qemu_test/asset.py
@@ -225,7 +225,6 @@ class Asset:
log.addHandler(handler)
for name, asset in vars(test.__class__).items():
if name.startswith("ASSET_") and type(asset) == Asset:
- log.info("Attempting to cache '%s'" % asset)
try:
asset.fetch()
except AssetError as e:
diff --git a/tests/functional/qemu_test/decorators.py b/tests/functional/qemu_test/decorators.py
index c0d1567..b239295 100644
--- a/tests/functional/qemu_test/decorators.py
+++ b/tests/functional/qemu_test/decorators.py
@@ -11,6 +11,24 @@ from unittest import skipIf, skipUnless
from .cmd import which
'''
+Decorator to skip execution of a test if the provided
+environment variables are not set.
+Example:
+
+ @skipIfMissingEnv("QEMU_ENV_VAR0", "QEMU_ENV_VAR1")
+'''
+def skipIfMissingEnv(*vars_):
+ missing_vars = []
+ for var in vars_:
+ if os.getenv(var) == None:
+ missing_vars.append(var)
+
+ has_vars = True if len(missing_vars) == 0 else False
+
+ return skipUnless(has_vars, f"Missing env var(s): {', '.join(missing_vars)}")
+
+'''
+
Decorator to skip execution of a test if the list
of command binaries is not available in $PATH.
Example:
diff --git a/tests/functional/qemu_test/gdb.py b/tests/functional/qemu_test/gdb.py
new file mode 100644
index 0000000..558d476
--- /dev/null
+++ b/tests/functional/qemu_test/gdb.py
@@ -0,0 +1,86 @@
+# 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>
+#
+
+import re
+
+
+class GDB:
+ """Provides methods to run and capture GDB command output."""
+
+
+ def __init__(self, gdb_path, echo=True, suffix='# ', prompt="$ "):
+ from pygdbmi.gdbcontroller import GdbController
+ from pygdbmi.constants import GdbTimeoutError
+ type(self).TimeoutError = GdbTimeoutError
+
+ 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=32.0):
+ self.response = self.gdbmi.write(cmd, timeout_sec=timeout)
+ self.cmd_output = self.get_payload(self.response, kind="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()
diff --git a/tests/functional/reverse_debugging.py b/tests/functional/reverse_debugging.py
index f9a1d39..68cfcb3 100644
--- a/tests/functional/reverse_debugging.py
+++ b/tests/functional/reverse_debugging.py
@@ -1,18 +1,23 @@
-# 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
+from subprocess import check_output
-from qemu_test import LinuxKernelTest, get_qemu_img
+from qemu_test import LinuxKernelTest, get_qemu_img, GDB, \
+ skipIfMissingEnv, skipIfMissingImports
from qemu_test.ports import Ports
@@ -28,13 +33,9 @@ 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()
@@ -52,55 +53,20 @@ class ReverseDebugging(LinuxKernelTest):
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;')
+ def get_pc(gdb: GDB):
+ return gdb.cli("print $pc").get_addr()
@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
+ @skipIfMissingImports("pygdbmi") # Required by GDB class
+ @skipIfMissingEnv("QEMU_TEST_GDB")
+ def reverse_debugging(self, gdb_arch, shift=7, args=None):
+ from qemu_test import GDB
logger = logging.getLogger('replay')
@@ -111,8 +77,9 @@ class ReverseDebugging(LinuxKernelTest):
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)
+ out = check_output([qemu_img, 'create', '-f', 'qcow2', image_path, '128M'],
+ encoding='utf8')
+ logger.info("qemu-img: %s" % out)
replay_path = os.path.join(self.workdir, 'replay.bin')
@@ -129,68 +96,107 @@ class ReverseDebugging(LinuxKernelTest):
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:
+
+ try:
+ logger.info('Connecting to gdbstub...')
+ self.reverse_debugging_run(vm, port, gdb_arch, last_icount)
+ logger.info('Test passed.')
+ except GDB.TimeoutError:
+ # Convert a GDB timeout exception into a unittest failure exception.
+ raise self.failureException("Timeout while connecting to or "
+ "communicating with gdbstub...") from None
+ except Exception:
+ # Re-throw exceptions from unittest, like the ones caused by fail(),
+ # skipTest(), etc.
+ raise
+
+ def reverse_debugging_run(self, vm, port, gdb_arch, last_icount):
+ logger = logging.getLogger('replay')
+
+ gdb_cmd = os.getenv('QEMU_TEST_GDB')
+ gdb = GDB(gdb_cmd)
+
+ r = gdb.cli("set architecture").get_log()
+ if gdb_arch not in r:
+ self.skipTest(f"GDB does not support arch '{gdb_arch}'")
+
+ 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 b'ReverseContinue+' not in r:
+ 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 = self.get_pc(g)
+ pc = self.get_pc(gdb)
logger.info('saving position %x' % pc)
steps.append(pc)
- self.gdb_step(g)
+ gdb.cli("stepi")
# 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)
+ gdb.cli("reverse-stepi")
+ pc = self.get_pc(gdb)
+ if pc != addr:
+ logger.info('Invalid PC (read %x instead of %x)' % (pc, addr))
+ self.fail('Reverse stepping failed!')
# 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)
+ pc = self.get_pc(gdb)
+ if pc != addr:
+ logger.info('Invalid PC (read %x instead of %x)' % (pc, addr))
+ self.fail('Forward stepping failed!')
+ gdb.cli("stepi")
# 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')
+ gdb.cli(f"break *{hex(addr)}")
# 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')
+ # 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")
+
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)))
+ (self.get_pc(gdb), 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;')
+ gdb.cli("reverse-continue")
# assume that none of the first instructions is executed again
# breaking the order of the breakpoints
- self.check_pc(g, steps[-1])
+ pc = self.get_pc(gdb)
+ if pc != steps[-1]:
+ self.fail("'reverse-continue' did not hit the first PC in reverse order!")
+
logger.info('successfully reached %x' % steps[-1])
logger.info('exiting gdb and qemu')
+ gdb.exit()
vm.shutdown()
diff --git a/tests/functional/x86_64/test_reverse_debug.py b/tests/functional/x86_64/test_reverse_debug.py
index d713e91..2b31ae8 100755
--- a/tests/functional/x86_64/test_reverse_debug.py
+++ b/tests/functional/x86_64/test_reverse_debug.py
@@ -2,34 +2,29 @@
#
# 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
- self.reverse_debugging()
+ self.reverse_debugging(gdb_arch='x86-64')
if __name__ == '__main__':
diff --git a/tests/lcitool/refresh b/tests/lcitool/refresh
index 6459593..056cfb6 100755
--- a/tests/lcitool/refresh
+++ b/tests/lcitool/refresh
@@ -216,8 +216,6 @@ try:
generate_dockerfile("debian-i686-cross", "debian-13",
cross="i686",
trailer=cross_build("i686-linux-gnu-",
- "x86_64-softmmu,"
- "x86_64-linux-user,"
"i386-softmmu,i386-linux-user"))
# mips no longer supported in debian-13
@@ -272,8 +270,8 @@ try:
#
# Ansible package lists
#
- generate_yaml("ubuntu", "ubuntu-2204", "aarch64")
- generate_yaml("ubuntu", "ubuntu-2204", "s390x")
+ generate_yaml("ubuntu", "ubuntu-2404", "aarch64")
+ generate_yaml("ubuntu", "ubuntu-2404", "s390x")
sys.exit(0)
diff --git a/tests/tcg/aarch64/Makefile.target b/tests/tcg/aarch64/Makefile.target
index 1755874..55ce34e 100644
--- a/tests/tcg/aarch64/Makefile.target
+++ b/tests/tcg/aarch64/Makefile.target
@@ -75,6 +75,11 @@ AARCH64_TESTS += $(SME_TESTS)
$(SME_TESTS): CFLAGS += $(CROSS_AS_HAS_ARMV9_SME)
endif
+# GCS Tests
+GCS_TESTS += gcsstr gcspushm gcsss
+AARCH64_TESTS += $(GCS_TESTS)
+$(GCS_TESTS): gcs.h
+
# System Registers Tests
AARCH64_TESTS += sysregs
diff --git a/tests/tcg/aarch64/gcs.h b/tests/tcg/aarch64/gcs.h
new file mode 100644
index 0000000..6f013d0
--- /dev/null
+++ b/tests/tcg/aarch64/gcs.h
@@ -0,0 +1,80 @@
+/*
+ * Linux kernel fallback API definitions for GCS and test helpers.
+ *
+ * Copyright (c) 2025 Linaro Ltd
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+
+#ifndef PR_GET_SHADOW_STACK_STATUS
+#define PR_GET_SHADOW_STACK_STATUS 74
+#endif
+#ifndef PR_SET_SHADOW_STACK_STATUS
+#define PR_SET_SHADOW_STACK_STATUS 75
+#endif
+#ifndef PR_LOCK_SHADOW_STACK_STATUS
+#define PR_LOCK_SHADOW_STACK_STATUS 76
+#endif
+#ifndef PR_SHADOW_STACK_ENABLE
+# define PR_SHADOW_STACK_ENABLE (1 << 0)
+# define PR_SHADOW_STACK_WRITE (1 << 1)
+# define PR_SHADOW_STACK_PUSH (1 << 2)
+#endif
+#ifndef SHADOW_STACK_SET_TOKEN
+#define SHADOW_STACK_SET_TOKEN (1 << 0)
+#endif
+#ifndef SHADOW_STACK_SET_MARKER
+#define SHADOW_STACK_SET_MARKER (1 << 1)
+#endif
+#ifndef SEGV_CPERR
+#define SEGV_CPERR 10
+#endif
+#ifndef __NR_map_shadow_stack
+#define __NR_map_shadow_stack 453
+#endif
+
+/*
+ * Macros, and implement the syscall inline, lest we fail
+ * the checked return from any function call.
+ */
+#define enable_gcs(flags) \
+ do { \
+ register long num __asm__ ("x8") = __NR_prctl; \
+ register long arg1 __asm__ ("x0") = PR_SET_SHADOW_STACK_STATUS; \
+ register long arg2 __asm__ ("x1") = PR_SHADOW_STACK_ENABLE | flags; \
+ register long arg3 __asm__ ("x2") = 0; \
+ register long arg4 __asm__ ("x3") = 0; \
+ register long arg5 __asm__ ("x4") = 0; \
+ asm volatile("svc #0" \
+ : "+r"(arg1) \
+ : "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(num) \
+ : "memory", "cc"); \
+ if (arg1) { \
+ errno = -arg1; \
+ perror("PR_SET_SHADOW_STACK_STATUS"); \
+ exit(2); \
+ } \
+ } while (0)
+
+#define gcspr() \
+ ({ uint64_t *r; asm volatile("mrs %0, s3_3_c2_c5_1" : "=r"(r)); r; })
+
+#define gcsss1(val) \
+ do { \
+ asm volatile("sys #3, c7, c7, #2, %0" : : "r"(val) : "memory"); \
+ } while (0)
+
+#define gcsss2() \
+ ({ uint64_t *r; \
+ asm volatile("sysl %0, #3, c7, c7, #3" : "=r"(r) : : "memory"); r; })
diff --git a/tests/tcg/aarch64/gcspushm.c b/tests/tcg/aarch64/gcspushm.c
new file mode 100644
index 0000000..c330417
--- /dev/null
+++ b/tests/tcg/aarch64/gcspushm.c
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "gcs.h"
+
+
+#define GCSPUSHM "sys #3, c7, c7, #0, %[push]"
+#define GCSPOPM "sysl %[pop], #3, c7, c7, #1"
+
+static void test_sigsegv(int sig, siginfo_t *info, void *vuc)
+{
+ ucontext_t *uc = vuc;
+ uint64_t inst_sigsegv;
+
+ __asm__("adr %0, inst_sigsegv" : "=r"(inst_sigsegv));
+ assert(uc->uc_mcontext.pc == inst_sigsegv);
+ assert(info->si_code == SEGV_CPERR);
+ /* TODO: Dig for ESR and verify syndrome. */
+ uc->uc_mcontext.pc += 4;
+}
+
+static void test_sigill(int sig, siginfo_t *info, void *vuc)
+{
+ ucontext_t *uc = vuc;
+ uint64_t inst_sigill;
+
+ __asm__("adr %0, inst_sigill" : "=r"(inst_sigill));
+ assert(uc->uc_mcontext.pc == inst_sigill);
+ assert(info->si_code == ILL_ILLOPC);
+ uc->uc_mcontext.pc += 4;
+}
+
+int main()
+{
+ struct sigaction sa = { .sa_flags = SA_SIGINFO };
+ uint64_t old, new;
+
+ sa.sa_sigaction = test_sigsegv;
+ if (sigaction(SIGSEGV, &sa, NULL) < 0) {
+ perror("sigaction");
+ exit(1);
+ }
+
+ sa.sa_sigaction = test_sigill;
+ if (sigaction(SIGILL, &sa, NULL) < 0) {
+ perror("sigaction");
+ exit(1);
+ }
+
+ /* Pushm is disabled -- SIGILL via EC_SYSTEMREGISTERTRAP */
+ asm volatile("inst_sigill:\t" GCSPUSHM
+ : : [push] "r" (1));
+
+ enable_gcs(PR_SHADOW_STACK_PUSH);
+
+ /* Valid value -- low 2 bits clear */
+ old = 0xdeadbeeffeedcaec;
+ asm volatile(GCSPUSHM "\n\t" GCSPOPM
+ : [pop] "=r" (new)
+ : [push] "r" (old)
+ : "memory");
+ assert(old == new);
+
+ /* Invalid value -- SIGSEGV via EC_GCS */
+ asm volatile(GCSPUSHM "\n"
+ "inst_sigsegv:\t" GCSPOPM
+ : [pop] "=r" (new)
+ : [push] "r" (1)
+ : "memory");
+
+ exit(0);
+}
diff --git a/tests/tcg/aarch64/gcsss.c b/tests/tcg/aarch64/gcsss.c
new file mode 100644
index 0000000..9550c68
--- /dev/null
+++ b/tests/tcg/aarch64/gcsss.c
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "gcs.h"
+
+#define IN_PROGRESS(X) ((uint64_t)(X) | 5)
+#define CAP(X) (((uint64_t)(X) & ~0xfff) + 1)
+
+static uint64_t * __attribute__((noinline)) recurse(size_t index)
+{
+ if (index == 0) {
+ return gcspr();
+ }
+ return recurse(index - 1);
+}
+
+int main()
+{
+ void *tmp;
+ uint64_t *alt_stack, *alt_cap;
+ uint64_t *orig_pr, *orig_cap;
+ uint64_t *bottom;
+ size_t pagesize = getpagesize();
+ size_t words;
+
+ enable_gcs(0);
+ orig_pr = gcspr();
+
+ /* Allocate a guard page before and after. */
+ tmp = mmap(0, 3 * pagesize, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
+ assert(tmp != MAP_FAILED);
+
+ /* map_shadow_stack won't replace existing mappings */
+ munmap(tmp + pagesize, pagesize);
+
+ /* Allocate a new stack between the guards. */
+ alt_stack = (uint64_t *)
+ syscall(__NR_map_shadow_stack, tmp + pagesize, pagesize,
+ SHADOW_STACK_SET_TOKEN);
+ assert(alt_stack == tmp + pagesize);
+
+ words = pagesize / 8;
+ alt_cap = alt_stack + words - 1;
+
+ /* SHADOW_STACK_SET_TOKEN set the cap. */
+ assert(*alt_cap == CAP(alt_cap));
+
+ /* Swap to the alt stack, one step at a time. */
+ gcsss1(alt_cap);
+
+ assert(gcspr() == alt_cap);
+ assert(*alt_cap == IN_PROGRESS(orig_pr));
+
+ orig_cap = gcsss2();
+
+ assert(orig_cap == orig_pr - 1);
+ assert(*orig_cap == CAP(orig_cap));
+ assert(gcspr() == alt_stack + words);
+
+ /* We should be able to use the whole stack. */
+ bottom = recurse(words - 1);
+ assert(bottom == alt_stack);
+
+ /* We should be back where we started. */
+ assert(gcspr() == alt_stack + words);
+
+ /* Swap back to the original stack. */
+ gcsss1(orig_cap);
+ tmp = gcsss2();
+
+ assert(gcspr() == orig_pr);
+ assert(tmp == alt_cap);
+
+ exit(0);
+}
diff --git a/tests/tcg/aarch64/gcsstr.c b/tests/tcg/aarch64/gcsstr.c
new file mode 100644
index 0000000..b045aee
--- /dev/null
+++ b/tests/tcg/aarch64/gcsstr.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "gcs.h"
+
+/*
+ * A single garbage store to the gcs stack.
+ * The asm inside must be unique, so disallow inlining.
+ */
+void __attribute__((noinline))
+test_gcsstr(void)
+{
+ register uint64_t *ptr __asm__("x0") = gcspr();
+ /* GCSSTR x1, x0 */
+ __asm__("inst_gcsstr: .inst 0xd91f1c01" : : "r"(--ptr));
+}
+
+static void test_sigsegv(int sig, siginfo_t *info, void *vuc)
+{
+ ucontext_t *uc = vuc;
+ uint64_t inst_gcsstr;
+
+ __asm__("adr %0, inst_gcsstr" : "=r"(inst_gcsstr));
+ assert(uc->uc_mcontext.pc == inst_gcsstr);
+ assert(info->si_code == SEGV_CPERR);
+ /* TODO: Dig for ESR and verify syndrome. */
+ exit(0);
+}
+
+int main()
+{
+ struct sigaction sa = {
+ .sa_sigaction = test_sigsegv,
+ .sa_flags = SA_SIGINFO,
+ };
+
+ /* Enable GCSSTR and test the store succeeds. */
+ enable_gcs(PR_SHADOW_STACK_WRITE);
+ test_gcsstr();
+
+ /* Disable GCSSTR and test the resulting sigsegv. */
+ enable_gcs(0);
+ if (sigaction(SIGSEGV, &sa, NULL) < 0) {
+ perror("sigaction");
+ exit(1);
+ }
+ test_gcsstr();
+ abort();
+}