On 24/02/2026 16:52, Florian Hofhammer wrote:
> The test executes a non-existent syscall, which the syscall plugin
> intercepts and redirects to a clean exit.
> Due to architecture-specific quirks, the architecture-specific Makefiles
> require setting specific compiler and linker flags in some cases.
> 
> Signed-off-by: Florian Hofhammer <[email protected]>
> ---
>  tests/tcg/arm/Makefile.target                 |  6 +++++
>  tests/tcg/hexagon/Makefile.target             |  7 +++++
>  tests/tcg/mips/Makefile.target                |  6 ++++-
>  tests/tcg/mips64/Makefile.target              | 15 +++++++++++
>  tests/tcg/mips64el/Makefile.target            | 15 +++++++++++
>  tests/tcg/mipsel/Makefile.target              | 15 +++++++++++
>  tests/tcg/multiarch/Makefile.target           | 22 ++++++++++++++--
>  .../{ => plugin}/check-plugin-output.sh       |  0
>  .../{ => plugin}/test-plugin-mem-access.c     |  0
>  .../plugin/test-plugin-skip-syscalls.c        | 26 +++++++++++++++++++
>  tests/tcg/plugins/syscall.c                   |  6 +++++
>  tests/tcg/sparc64/Makefile.target             | 16 ++++++++++++
>  12 files changed, 131 insertions(+), 3 deletions(-)
>  create mode 100644 tests/tcg/mips64/Makefile.target
>  create mode 100644 tests/tcg/mips64el/Makefile.target
>  create mode 100644 tests/tcg/mipsel/Makefile.target
>  rename tests/tcg/multiarch/{ => plugin}/check-plugin-output.sh (100%)
>  rename tests/tcg/multiarch/{ => plugin}/test-plugin-mem-access.c (100%)
>  create mode 100644 tests/tcg/multiarch/plugin/test-plugin-skip-syscalls.c
>  create mode 100644 tests/tcg/sparc64/Makefile.target
> 
> diff --git a/tests/tcg/arm/Makefile.target b/tests/tcg/arm/Makefile.target
> index 6189d7a0e2..0d8be9cd80 100644
> --- a/tests/tcg/arm/Makefile.target
> +++ b/tests/tcg/arm/Makefile.target
> @@ -78,4 +78,10 @@ sha512-vector: sha512.c
>  
>  ARM_TESTS += sha512-vector
>  
> +ifeq ($(CONFIG_PLUGIN),y)
> +# Require emitting arm32 instructions, otherwise the vCPU might accidentally
> +# try to execute Thumb instructions in arm32 mode after qemu_plugin_set_pc()
> +test-plugin-skip-syscalls: CFLAGS+=-marm
> +endif
> +
>  TESTS += $(ARM_TESTS)
> diff --git a/tests/tcg/hexagon/Makefile.target 
> b/tests/tcg/hexagon/Makefile.target
> index f86f02bb31..111dc405fa 100644
> --- a/tests/tcg/hexagon/Makefile.target
> +++ b/tests/tcg/hexagon/Makefile.target
> @@ -126,3 +126,10 @@ v73_scalar: CFLAGS += -Wno-unused-function
>  
>  hvx_histogram: hvx_histogram.c hvx_histogram_row.S
>       $(CC) $(CFLAGS) $(CROSS_CC_GUEST_CFLAGS) $^ -o $@ $(LDFLAGS)
> +
> +ifeq ($(CONFIG_PLUGIN),y)
> +# hexagon uses clang/lld which does not support -Ttext-segment but GNU ld 
> does
> +# not generally support --image-base. Therefore, the multiarch Makefile uses
> +# the GNU ld flag and we special-case here for hexagon.
> +override LDFLAG_TEXT_BASE = -Wl,--image-base=0x40000
> +endif
> diff --git a/tests/tcg/mips/Makefile.target b/tests/tcg/mips/Makefile.target
> index 5d17c1706e..d08138f17b 100644
> --- a/tests/tcg/mips/Makefile.target
> +++ b/tests/tcg/mips/Makefile.target
> @@ -9,11 +9,15 @@ MIPS_SRC=$(SRC_PATH)/tests/tcg/mips
>  VPATH                += $(MIPS_SRC)
>  
>  # hello-mips is 32 bit only
> -ifeq ($(findstring 64,$(TARGET_NAME)),)
>  MIPS_TESTS=hello-mips
>  
>  TESTS += $(MIPS_TESTS)
>  
>  hello-mips: CFLAGS+=-mno-abicalls -fno-PIC -fno-stack-protector -mabi=32
>  hello-mips: LDFLAGS+=-nostdlib
> +
> +ifeq ($(CONFIG_PLUGIN),y)
> +# qemu-mips(el) returns ENOSYS without triggering syscall plugin callbacks
> +run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
> +     $(call skip-test, $<, "qemu-mips does not execute invalid syscalls")
>  endif
> diff --git a/tests/tcg/mips64/Makefile.target 
> b/tests/tcg/mips64/Makefile.target
> new file mode 100644
> index 0000000000..5386855efc
> --- /dev/null
> +++ b/tests/tcg/mips64/Makefile.target
> @@ -0,0 +1,15 @@
> +# -*- Mode: makefile -*-
> +#
> +# MIPS64 - included from tests/tcg/Makefile.target
> +#
> +
> +MIPS64_SRC=$(SRC_PATH)/tests/tcg/mips64
> +
> +# Set search path for all sources
> +VPATH += $(MIPS64_SRC)
> +
> +ifeq ($(CONFIG_PLUGIN),y)
> +# Require no ABI calls to avoid $t9-relative .got address calculation on 
> MIPS64
> +test-plugin-skip-syscalls: CFLAGS+=-mno-abicalls -fno-pie
> +test-plugin-skip-syscalls: LDFLAGS+=-no-pie
> +endif
> diff --git a/tests/tcg/mips64el/Makefile.target 
> b/tests/tcg/mips64el/Makefile.target
> new file mode 100644
> index 0000000000..77ac8815fe
> --- /dev/null
> +++ b/tests/tcg/mips64el/Makefile.target
> @@ -0,0 +1,15 @@
> +# -*- Mode: makefile -*-
> +#
> +# MIPS64EL - included from tests/tcg/Makefile.target
> +#
> +
> +MIPS64EL_SRC=$(SRC_PATH)/tests/tcg/mips64el
> +
> +# Set search path for all sources
> +VPATH += $(MIPS64EL_SRC)
> +
> +ifeq ($(CONFIG_PLUGIN),y)
> +# Require no ABI calls to avoid $t9-relative .got address calculation on 
> MIPS64
> +test-plugin-skip-syscalls: CFLAGS+=-mno-abicalls -fno-pie
> +test-plugin-skip-syscalls: LDFLAGS+=-no-pie
> +endif
> diff --git a/tests/tcg/mipsel/Makefile.target 
> b/tests/tcg/mipsel/Makefile.target
> new file mode 100644
> index 0000000000..bf1bdb56b3
> --- /dev/null
> +++ b/tests/tcg/mipsel/Makefile.target
> @@ -0,0 +1,15 @@
> +# -*- Mode: makefile -*-
> +#
> +# MIPSEL - included from tests/tcg/Makefile.target
> +#
> +
> +MIPSEL_SRC=$(SRC_PATH)/tests/tcg/mipsel
> +
> +# Set search path for all sources
> +VPATH += $(MIPSEL_SRC)
> +
> +ifeq ($(CONFIG_PLUGIN),y)
> +# qemu-mips(el) returns ENOSYS without triggering syscall plugin callbacks
> +run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
> +     $(call skip-test, $<, "qemu-mipsel does not execute invalid syscalls")
> +endif
> diff --git a/tests/tcg/multiarch/Makefile.target 
> b/tests/tcg/multiarch/Makefile.target
> index 07d0b27bdd..2e2dcda425 100644
> --- a/tests/tcg/multiarch/Makefile.target
> +++ b/tests/tcg/multiarch/Makefile.target
> @@ -14,6 +14,10 @@ ifeq ($(filter %-linux-user, $(TARGET)),$(TARGET))
>  VPATH               += $(MULTIARCH_SRC)/linux
>  MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/linux/*.c))
>  endif
> +ifeq ($(CONFIG_PLUGIN),y)
> +VPATH               += $(MULTIARCH_SRC)/plugin
> +MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/plugin/*.c))
> +endif
>  MULTIARCH_TESTS = $(MULTIARCH_SRCS:.c=)
>  
>  #
> @@ -200,13 +204,27 @@ run-plugin-test-plugin-mem-access-with-libmem.so: \
>       PLUGIN_ARGS=$(COMMA)print-accesses=true
>  run-plugin-test-plugin-mem-access-with-libmem.so: \
>       CHECK_PLUGIN_OUTPUT_COMMAND= \
> -     $(SRC_PATH)/tests/tcg/multiarch/check-plugin-output.sh \
> +     $(SRC_PATH)/tests/tcg/multiarch/plugin/check-plugin-output.sh \
>       $(QEMU) $<
>  run-plugin-test-plugin-syscall-filter-with-libsyscall.so:
>  
>  EXTRA_RUNS_WITH_PLUGIN += run-plugin-test-plugin-mem-access-with-libmem.so \
>                         
> run-plugin-test-plugin-syscall-filter-with-libsyscall.so
> -else
> +
> +# Test plugin control flow redirection by skipping system calls
> +# (similar functionality to syscall filter but different mechanism)
> +LDFLAG_TEXT_BASE = -Wl,-Ttext-segment=0x40000
> +test-plugin-skip-syscalls: LDFLAGS += $(LDFLAG_TEXT_BASE)
> +test-plugin-skip-syscalls: LDFLAGS += -Wl,--section-start,.redirect=0x20000
> +run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
> +
> +EXTRA_RUNS_WITH_PLUGIN += 
> run-plugin-test-plugin-skip-syscalls-with-libsyscall.so
> +
> +else # CONFIG_PLUGIN=n
> +# Do not build the syscall skipping test if it's not tested with a plugin
> +# because it will simply return an error and fail the test.
> +MULTIARCH_TESTS := $(filter-out test-plugin-skip-syscalls, 
> $(MULTIARCH_TESTS))
> +
>  # test-plugin-syscall-filter needs syscall plugin to succeed
>  test-plugin-syscall-filter: CFLAGS+=-DSKIP
>  endif
> diff --git a/tests/tcg/multiarch/check-plugin-output.sh 
> b/tests/tcg/multiarch/plugin/check-plugin-output.sh
> similarity index 100%
> rename from tests/tcg/multiarch/check-plugin-output.sh
> rename to tests/tcg/multiarch/plugin/check-plugin-output.sh
> diff --git a/tests/tcg/multiarch/test-plugin-mem-access.c 
> b/tests/tcg/multiarch/plugin/test-plugin-mem-access.c
> similarity index 100%
> rename from tests/tcg/multiarch/test-plugin-mem-access.c
> rename to tests/tcg/multiarch/plugin/test-plugin-mem-access.c
> diff --git a/tests/tcg/multiarch/plugin/test-plugin-skip-syscalls.c 
> b/tests/tcg/multiarch/plugin/test-plugin-skip-syscalls.c
> new file mode 100644
> index 0000000000..1f5cbc3851
> --- /dev/null
> +++ b/tests/tcg/multiarch/plugin/test-plugin-skip-syscalls.c
> @@ -0,0 +1,26 @@
> +/*
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * This test attempts to execute an invalid syscall. The syscall test plugin
> + * should intercept this.
> + */
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +
> +void exit_success(void) __attribute__((section(".redirect"), noinline,
> +                                       noreturn, used));
> +
> +void exit_success(void) {
> +    _exit(EXIT_SUCCESS);
> +}
> +
> +int main(int argc, char *argv[]) {
> +    long ret = syscall(0xc0deUL);
> +    if (ret != 0L) {
> +        perror("");
> +    }
> +    /* We should never get here */
> +    return EXIT_FAILURE;
> +}

I'm running into an issue for all four variants of MIPS if I don't
hardcode the section but pass the function address as a syscall argument
and then use that as jump target in the plugin: according to the ABI,
the t9 register has to contain the address of the function being called.
The function prologue then calculates the gp register value (global
pointer / context pointer) based on t9, and derives the new values of t9
for any callees from gp again. As I'm currently just updating the pc
with the new API function, t9 is out of sync with the code after control
flow redirection and the binary crashes.

I think it is fair to expect a user of the API to be aware of such
pitfalls (or we can document it), but I'd of course still like to make
the tests pass. The simplest solution (theoretically) is to also set the
t9 register in the plugin callback before calling qemu_plugin_set_pc.
However, the MIPS targets do not actually expose any registers to
plugins, i.e., qemu_plugin_get_registers returns an empty GArray.

Given this behavior, I see two solutions:
1) skipping the test on MIPS, or
2) making the test code a bit more contrived to use labels within the
   same function while preventing the compiler from optimizing the
   labels away (which it does even with -O0). I've got a prototype for
   this, but the test code looks a bit contrived then:

   int main(int argc, char *argv[]) {
       int retvals[] = {EXIT_SUCCESS, EXIT_FAILURE};
       int retval_idx = 1;

       long ret = syscall(0xc0deUL, &&good);
       if (ret < 0) {
           perror("");
           goto bad;
       } else if (ret == 0xdeadbeefUL) {
           /*
            * Should never arrive here but we need this nevertheless to prevent
            * the compiler from optimizing away the label. Otherwise, the 
compiler
            * silently rewrites the label value used in the syscall to another
            * address (typically pointing to right after the function prologue).
            */
           printf("Check what's wrong, we should never arrive here!");
           assert(((uintptr_t)&&good == (uintptr_t)ret));
           /* We should absolutely never arrive here, the assert should trigger 
*/
           goto good;
       }

   bad:
       retval_idx = 1;
       goto exit;
   good:
       retval_idx = 0;
   exit:
       return retvals[retval_idx];
   }

Maybe I'm just missing something obvious, I'd be happy to get some
feedback on this. Thanks in advance!

> diff --git a/tests/tcg/plugins/syscall.c b/tests/tcg/plugins/syscall.c
> index 5658f83087..b68e3cadf4 100644
> --- a/tests/tcg/plugins/syscall.c
> +++ b/tests/tcg/plugins/syscall.c
> @@ -148,6 +148,12 @@ static void vcpu_syscall(qemu_plugin_id_t id, unsigned 
> int vcpu_index,
>              fprintf(stderr, "Error reading memory from vaddr %"PRIu64"\n", 
> a2);
>          }
>      }
> +
> +    if (num == 0xc0deUL) {
> +        /* Special syscall to test the control flow redirection 
> functionality. */
> +        qemu_plugin_outs("Marker syscall detected, jump to clean exit\n");
> +        qemu_plugin_set_pc(0x20000);
> +    }
>  }
>  
>  static void vcpu_syscall_ret(qemu_plugin_id_t id, unsigned int vcpu_idx,
> diff --git a/tests/tcg/sparc64/Makefile.target 
> b/tests/tcg/sparc64/Makefile.target
> new file mode 100644
> index 0000000000..516927a3fc
> --- /dev/null
> +++ b/tests/tcg/sparc64/Makefile.target
> @@ -0,0 +1,16 @@
> +# -*- Mode: makefile -*-
> +#
> +# Sparc64 - included from tests/tcg/Makefile.target
> +#
> +
> +SPARC64_SRC=$(SRC_PATH)/tests/tcg/sparc64
> +
> +# Set search path for all sources
> +VPATH += $(SPARC64_SRC)
> +
> +ifeq ($(CONFIG_PLUGIN),y)
> +# The defined addresses for the binary are not aligned correctly for sparc64
> +# but adjusting them breaks other architectures, so just skip it on sparc64.
> +run-plugin-test-plugin-skip-syscalls-with-libsyscall.so:
> +     $(call skip-test, $<, "qemu-sparc64 does not allow mapping at our given 
> fixed address")
> +endif

Best regards,
Florian

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

Reply via email to