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 <florian.hofhammer@epfl.ch>
---
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;
+}
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
--
2.53.0
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 <florian.hofhammer@epfl.ch>
> ---
> 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
On 2/25/26 8:21 AM, Florian Hofhammer wrote:
> 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 <florian.hofhammer@epfl.ch>
>> ---
>> 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/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.
>
A lot of things can go wrong when jumping to a different context than
the current one, that's why setjmp/longjmp exist. You can always add a
comment for this "This function only changes pc and does not guarantee
other registers representing context will have a proper value when
updating it".
A reason why I pushed for using value labels, was to stay in the same
context, and avoid the kind of issues you ran into.
> 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!
>
Can you post the exact code you had where labels are optimized away?
On which arch was it?
I tried something similar but didn't see the second one disappear.
I wonder if it's related to compiler detecting g_assert_not_reached() is
a "noreturn" function, thus it doesn't expect to go past it, and
eliminates dead code. You can try with assert(0) or moving
g_assert_not_reached() to another function "crash()" instead, that is
not marked as noreturn.
> Best regards,
> Florian
Regards,
Pierrick
On 25/02/2026 18:30, Pierrick Bouvier wrote:
> On 2/25/26 8:21 AM, Florian Hofhammer wrote:
>> 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 <florian.hofhammer@epfl.ch>
>>> ---
>>> 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/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.
>>
>
> A lot of things can go wrong when jumping to a different context than the current one, that's why setjmp/longjmp exist. You can always add a comment for this "This function only changes pc and does not guarantee other registers representing context will have a proper value when updating it".
>
> A reason why I pushed for using value labels, was to stay in the same context, and avoid the kind of issues you ran into.
>
>> 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!
>>
>
> Can you post the exact code you had where labels are optimized away?
> On which arch was it?
This happens across architectures but I've verified with x86, aarch64,
and riscv64. Below the C code and generated assembly, compiled with gcc
-O0 -o test.s -S test.c (adding -fno-dce and -fno-tree-dce does not
change anything and they should be included in -O0 anyway):
test.c:
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
syscall(4096, &&good);
return EXIT_FAILURE;
good:
return EXIT_SUCCESS;
}
test.s:
.file "test.c"
.text
.globl main
.type main, @function
main:
.LFB6:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
.L2:
leaq .L2(%rip), %rax
movq %rax, %rsi
movl $4096, %edi
movl $0, %eax
call syscall@PLT
movl $1, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size main, .-main
.ident "GCC: (GNU) 15.2.1 20260209"
.section .note.GNU-stack,"",@progbits
As you can see, the "good" label and correspondingly the second return
are optimized away, and the compiler replaces the syscall argument
&&good with the address of the .L2 assembly label. This effectively then
causes an infinite loop of the syscall being called over and over again
and redirecting the PC to right after the function prologue.
The assembly output for aarch64 and riscv64 is analogous.
> I tried something similar but didn't see the second one disappear.
>
> I wonder if it's related to compiler detecting g_assert_not_reached() is a "noreturn" function, thus it doesn't expect to go past it, and eliminates dead code. You can try with assert(0) or moving g_assert_not_reached() to another function "crash()" instead, that is not marked as noreturn.
As I mentioned above, even turning off dead code elimination didn't
resolve the issue. But this was actually a good pointer nevertheless:
just wrapping the exit in a separate function like below solves the
issue. It's just that the compiler detected that the label is after a
return and deemed it unreachable (obviously not knowing about the
semantics of the syscall). Below is the modified code that works across
architectures and that I'll use in the end:
void exit_failure(void) {
_exit(EXIT_FAILURE);
}
void exit_success(void) {
_exit(EXIT_SUCCESS);
}
int main(int argc, char *argv[]) {
syscall(4096, &&good);
exit_failure();
good:
exit_success();
}
>
>> Best regards,
>> Florian
>
> Regards,
> Pierrick
On 2/26/26 12:30 AM, Florian Hofhammer wrote:
> On 25/02/2026 18:30, Pierrick Bouvier wrote:
>> On 2/25/26 8:21 AM, Florian Hofhammer wrote:
>>> 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 <florian.hofhammer@epfl.ch>
>>>> ---
>>>> 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/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.
>>>
>>
>> A lot of things can go wrong when jumping to a different context than the current one, that's why setjmp/longjmp exist. You can always add a comment for this "This function only changes pc and does not guarantee other registers representing context will have a proper value when updating it".
>>
>> A reason why I pushed for using value labels, was to stay in the same context, and avoid the kind of issues you ran into.
>>
>>> 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!
>>>
>>
>> Can you post the exact code you had where labels are optimized away?
>> On which arch was it?
>
> This happens across architectures but I've verified with x86, aarch64,
> and riscv64. Below the C code and generated assembly, compiled with gcc
> -O0 -o test.s -S test.c (adding -fno-dce and -fno-tree-dce does not
> change anything and they should be included in -O0 anyway):
>
> test.c:
> #include <stdlib.h>
> #include <unistd.h>
>
> int main(int argc, char *argv[]) {
>
> syscall(4096, &&good);
> return EXIT_FAILURE;
> good:
> return EXIT_SUCCESS;
>
> }
>
> test.s:
> .file "test.c"
> .text
> .globl main
> .type main, @function
> main:
> .LFB6:
> .cfi_startproc
> pushq %rbp
> .cfi_def_cfa_offset 16
> .cfi_offset 6, -16
> movq %rsp, %rbp
> .cfi_def_cfa_register 6
> subq $16, %rsp
> movl %edi, -4(%rbp)
> movq %rsi, -16(%rbp)
> .L2:
> leaq .L2(%rip), %rax
> movq %rax, %rsi
> movl $4096, %edi
> movl $0, %eax
> call syscall@PLT
> movl $1, %eax
> leave
> .cfi_def_cfa 7, 8
> ret
> .cfi_endproc
> .LFE6:
> .size main, .-main
> .ident "GCC: (GNU) 15.2.1 20260209"
> .section .note.GNU-stack,"",@progbits
>
> As you can see, the "good" label and correspondingly the second return
> are optimized away, and the compiler replaces the syscall argument
> &&good with the address of the .L2 assembly label. This effectively then
> causes an infinite loop of the syscall being called over and over again
> and redirecting the PC to right after the function prologue.
> The assembly output for aarch64 and riscv64 is analogous.
>
yes, that's the effect of noreturn attribute. Luckily, gcc is not "smart
enough" at O0 to propage this attribute through function calls.
>> I tried something similar but didn't see the second one disappear.
>>
>> I wonder if it's related to compiler detecting g_assert_not_reached() is a "noreturn" function, thus it doesn't expect to go past it, and eliminates dead code. You can try with assert(0) or moving g_assert_not_reached() to another function "crash()" instead, that is not marked as noreturn.
>
> As I mentioned above, even turning off dead code elimination didn't
> resolve the issue. But this was actually a good pointer nevertheless:
> just wrapping the exit in a separate function like below solves the
> issue. It's just that the compiler detected that the label is after a
> return and deemed it unreachable (obviously not knowing about the
> semantics of the syscall). Below is the modified code that works across
> architectures and that I'll use in the end:
>
> void exit_failure(void) {
> _exit(EXIT_FAILURE);
> }
>
> void exit_success(void) {
> _exit(EXIT_SUCCESS);
> }
>
> int main(int argc, char *argv[]) {
> syscall(4096, &&good);
> exit_failure();
> good:
> exit_success();
> }
>
As mentioned in another comment, you might prefer to have something like:
```
void test_from_syscall() {
syscall(4096, &&good);
exit_failure();
good:
return;
}
void test_from_sighandler() {
...
}
void test_from_instruction() {
...
}
/* So we can combine all tests in a single one */
int main(int argc, char *argv[]) {
test_from_syscall();
test_from_sighandler();
test_from_instruction();
}
```
If that's too complicated for any reason, feel free to have 3 different
tests. It's just nicer to have this, but not mandatory.
>>
>>> Best regards,
>>> Florian
>>
>> Regards,
>> Pierrick
Regards,
Pierrick
On 2/25/26 9:30 AM, Pierrick Bouvier wrote:
> On 2/25/26 8:21 AM, Florian Hofhammer wrote:
>> 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 <florian.hofhammer@epfl.ch>
>>> ---
>>> 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/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.
>>
>
> A lot of things can go wrong when jumping to a different context than
> the current one, that's why setjmp/longjmp exist. You can always add a
> comment for this "This function only changes pc and does not guarantee
> other registers representing context will have a proper value when
> updating it".
>
> A reason why I pushed for using value labels, was to stay in the same
> context, and avoid the kind of issues you ran into.
>
>> 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!
>>
>
> Can you post the exact code you had where labels are optimized away?
> On which arch was it?
> I tried something similar but didn't see the second one disappear.
>
> I wonder if it's related to compiler detecting g_assert_not_reached() is
> a "noreturn" function, thus it doesn't expect to go past it, and
> eliminates dead code. You can try with assert(0) or moving
> g_assert_not_reached() to another function "crash()" instead, that is
> not marked as noreturn.
>
I'm pretty sure that's the reason:
Given this code:
void crash(void)
{
g_assert_not_reached();
}
int main() {
crash();
printf("Hello World\n");
}
resulting assembly contains a call to puts.
Meanwhile:
int main() {
g_assert_not_reached();
printf("Hello World\n");
}
Doesn't.
gcc does dead code elimination even in 0 for cases like this, or if you
have if (0) { ... } also. Beyond code size optimization purpose, it's
useful to remove dependency to symbols that are really in dead code, I
discovered recently we rely on that behaviour in QEMU (for various
X_enabled global symbols).
>> Best regards,
>> Florian
>
> Regards,
> Pierrick
On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
> ---
> 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
>
> +++ 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;
> +}
After reviewing patch 2, I think it could be nice to test also the path
redirecting pc not from a syscall, in addition to what this test already
does.
You can reuse syscall mechanic to communicate with plugin to share
address that should crash and which destination it should reach instead.
And while at it, you can add the same thing from a signal handler
(reusing syscall trick), so it should cover most of "special" cases.
You can merge all test cases in a single test.c.
Also, you can create a new set_pc plugin combining all this, because
other test cases don't really belong to tests/tcg/plugins/syscall.c.
test.c:
int main(int argc, char *argv[]) {
syscall(..., &&skip, &&dest);
error:
g_assert_not_reached();
dest:
return EXIT_SUCCESS;
}
plugin.c:
static void* error;
static void* dest;
void on_syscall(num, a1, a2) {
if (num == magic) {
error = a1;
dest = a2;
}
return true;
}
void insn_exec(..., void* pc) {
if (pc == error) {
qemu_plugin_set_pc(dest);
}
}
Regards,
Pierrick
On 24/02/2026 22:28, Pierrick Bouvier wrote:
> On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
>> ---
>> 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
>>
>> +++ 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;
>> +}
> After reviewing patch 2, I think it could be nice to test also the path redirecting pc not from a syscall, in addition to what this test already does.
>
> You can reuse syscall mechanic to communicate with plugin to share address that should crash and which destination it should reach instead.
>
> And while at it, you can add the same thing from a signal handler (reusing syscall trick), so it should cover most of "special" cases.
>
> You can merge all test cases in a single test.c.
> Also, you can create a new set_pc plugin combining all this, because other test cases don't really belong to tests/tcg/plugins/syscall.c.
>
> test.c:
> int main(int argc, char *argv[]) {
> syscall(..., &&skip, &&dest);
> error:
> g_assert_not_reached();
> dest:
> return EXIT_SUCCESS;
> }
>
> plugin.c:
> static void* error;
> static void* dest;
>
> void on_syscall(num, a1, a2) {
> if (num == magic) {
> error = a1;
> dest = a2;
> }
> return true;
> }
>
> void insn_exec(..., void* pc) {
> if (pc == error) {
> qemu_plugin_set_pc(dest);
> }
> }
>
> Regards,
> Pierrick
Makes sense, I'll factor that out and add additional tests for the
functionality.
Best regards,
Florian
On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
> ---
> 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
> +++ 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;
> +}
> 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);
An even better alternative is to use a value label, which is a gcc
extension, and you would not even need another function. Just pass it as
first parameter of syscall, and jump to this address directly from
syscall filter.
int main(int argc, char *argv[]) {
long ret = syscall(0xc0deUL, &&set_pc_dest);
/* We should never get here */
return EXIT_FAILURE;
set_pc_dest:
return EXIT_SUCCESS;
}
More details:
https://www.amulettechnologies.com/boosting-bytecode-efficiency-the-power-of-gccs-label-as-value/
https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
Regards,
Pierrick
On 24/02/2026 21:35, Pierrick Bouvier wrote:
> On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
>> ---
>> 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
>> +++ 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;
>> +}
>> 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);
>
> An even better alternative is to use a value label, which is a gcc extension, and you would not even need another function. Just pass it as first parameter of syscall, and jump to this address directly from syscall filter.
>
> int main(int argc, char *argv[]) {
> long ret = syscall(0xc0deUL, &&set_pc_dest);
> /* We should never get here */
> return EXIT_FAILURE;
> set_pc_dest:
> return EXIT_SUCCESS;
> }
>
> More details:
> https://www.amulettechnologies.com/boosting-bytecode-efficiency-the-power-of-gccs-label-as-value/
> https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
>
> Regards,
> Pierrick
Thanks for the idea, I didn't think about this. I'll check it out!
Best regards,
Florian
On 25/02/2026 08:59, Florian Hofhammer wrote:
> On 24/02/2026 21:35, Pierrick Bouvier wrote:
>> On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
>>> ---
>>> 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
>>> +++ 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;
>>> +}
>>> 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);
>>
>> An even better alternative is to use a value label, which is a gcc extension, and you would not even need another function. Just pass it as first parameter of syscall, and jump to this address directly from syscall filter.
>>
>> int main(int argc, char *argv[]) {
>> long ret = syscall(0xc0deUL, &&set_pc_dest);
>> /* We should never get here */
>> return EXIT_FAILURE;
>> set_pc_dest:
>> return EXIT_SUCCESS;
>> }
>>
>> More details:
>> https://www.amulettechnologies.com/boosting-bytecode-efficiency-the-power-of-gccs-label-as-value/
>> https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
>>
>> Regards,
>> Pierrick
>
> Thanks for the idea, I didn't think about this. I'll check it out!
>
> Best regards,
> Florian
Fun finding: GCC optimizes the label and the second return away and
there's no way to turn this behavior off, so the test doesn't work with
value labels. I'm nevertheless changing to just passing the function
address as a paremeter, which is cleaner than using a hardcoded address.
Best regards,
Florian
On 2/25/26 3:49 AM, Florian Hofhammer wrote:
> On 25/02/2026 08:59, Florian Hofhammer wrote:
>> On 24/02/2026 21:35, Pierrick Bouvier wrote:
>>> On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
>>>> ---
>>>> 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
>>>> +++ 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;
>>>> +}
>>>> 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);
>>>
>>> An even better alternative is to use a value label, which is a gcc extension, and you would not even need another function. Just pass it as first parameter of syscall, and jump to this address directly from syscall filter.
>>>
>>> int main(int argc, char *argv[]) {
>>> long ret = syscall(0xc0deUL, &&set_pc_dest);
>>> /* We should never get here */
>>> return EXIT_FAILURE;
>>> set_pc_dest:
>>> return EXIT_SUCCESS;
>>> }
>>>
>>> More details:
>>> https://www.amulettechnologies.com/boosting-bytecode-efficiency-the-power-of-gccs-label-as-value/
>>> https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
>>>
>>> Regards,
>>> Pierrick
>>
>> Thanks for the idea, I didn't think about this. I'll check it out!
>>
>> Best regards,
>> Florian
>
> Fun finding: GCC optimizes the label and the second return away and
> there's no way to turn this behavior off, so the test doesn't work with
> value labels. I'm nevertheless changing to just passing the function
> address as a paremeter, which is cleaner than using a hardcoded address.
>
Oh interesting. Is the test compiled in O0?
Yes, you can pass the function address as well. I would be happy with a
hardcoded address, but the major pain is all those flags per arch.
> Best regards,
> Florian
On 2/25/26 9:07 AM, Pierrick Bouvier wrote:
> On 2/25/26 3:49 AM, Florian Hofhammer wrote:
>> On 25/02/2026 08:59, Florian Hofhammer wrote:
>>> On 24/02/2026 21:35, Pierrick Bouvier wrote:
>>>> On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
>>>>> ---
>>>>> 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
>>>>> +++ 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;
>>>>> +}
>>>>> 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);
>>>>
>>>> An even better alternative is to use a value label, which is a gcc extension, and you would not even need another function. Just pass it as first parameter of syscall, and jump to this address directly from syscall filter.
>>>>
>>>> int main(int argc, char *argv[]) {
>>>> long ret = syscall(0xc0deUL, &&set_pc_dest);
>>>> /* We should never get here */
>>>> return EXIT_FAILURE;
>>>> set_pc_dest:
>>>> return EXIT_SUCCESS;
>>>> }
>>>>
>>>> More details:
>>>> https://www.amulettechnologies.com/boosting-bytecode-efficiency-the-power-of-gccs-label-as-value/
>>>> https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
>>>>
>>>> Regards,
>>>> Pierrick
>>>
>>> Thanks for the idea, I didn't think about this. I'll check it out!
>>>
>>> Best regards,
>>> Florian
>>
>> Fun finding: GCC optimizes the label and the second return away and
>> there's no way to turn this behavior off, so the test doesn't work with
>> value labels. I'm nevertheless changing to just passing the function
>> address as a paremeter, which is cleaner than using a hardcoded address.
>>
>
> Oh interesting. Is the test compiled in O0?
Just saw your answer below :)
> Yes, you can pass the function address as well. I would be happy with a
> hardcoded address, but the major pain is all those flags per arch.
>
>> Best regards,
>> Florian
>
On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
> ---
> 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/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")
I ran into this while pulling syscall filter API recently, and found
4096 as syscall number, which seems to make all architectures happy.
See 948ffdd79b78702239aace2d32d4f581913299b3 for more details.
On 24/02/2026 21:24, Pierrick Bouvier wrote:
> On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
>> ---
>> 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/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")
>
> I ran into this while pulling syscall filter API recently, and found 4096 as syscall number, which seems to make all architectures happy.
>
> See 948ffdd79b78702239aace2d32d4f581913299b3 for more details.
Please correct me if I'm wrong, but is this really sane? The comment in
the referenced commit says that 4096 isn't used in any ISA, but on the
MIPS O32 ABI, syscall numbers start at 4000. 4096 therefore corresponds
to getpriority, which would be shadowed by using this syscall number.
On 2/25/26 6:58 AM, Florian Hofhammer wrote:
> On 24/02/2026 21:24, Pierrick Bouvier wrote:
>> On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
>>> ---
>>> 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/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")
>>
>> I ran into this while pulling syscall filter API recently, and found 4096 as syscall number, which seems to make all architectures happy.
>>
>> See 948ffdd79b78702239aace2d32d4f581913299b3 for more details.
>
> Please correct me if I'm wrong, but is this really sane? The comment in
> the referenced commit says that 4096 isn't used in any ISA, but on the
> MIPS O32 ABI, syscall numbers start at 4000. 4096 therefore corresponds
> to getpriority, which would be shadowed by using this syscall number.
I would definitely not recommend using this value in a general purpose
plugin to implement "hypercalls", it would not be sane.
That said, for the current limited test context where we now this
syscall is not called, I would say it's ok. And if it should get broken
in the future, we would catch it and could find another magic number.
Of course, if you have a better idea of syscall number, I would be very
happy to follow another rationale, as mine was mostly try and error. :)
Regards,
Pierrick
On 25/02/2026 18:04, Pierrick Bouvier wrote:
> On 2/25/26 6:58 AM, Florian Hofhammer wrote:
>> On 24/02/2026 21:24, Pierrick Bouvier wrote:
>>> On 2/24/26 7:52 AM, 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 <florian.hofhammer@epfl.ch>
>>>> ---
>>>> 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/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")
>>>
>>> I ran into this while pulling syscall filter API recently, and found 4096 as syscall number, which seems to make all architectures happy.
>>>
>>> See 948ffdd79b78702239aace2d32d4f581913299b3 for more details.
>>
>> Please correct me if I'm wrong, but is this really sane? The comment in
>> the referenced commit says that 4096 isn't used in any ISA, but on the
>> MIPS O32 ABI, syscall numbers start at 4000. 4096 therefore corresponds
>> to getpriority, which would be shadowed by using this syscall number.
>
> I would definitely not recommend using this value in a general purpose plugin to implement "hypercalls", it would not be sane.
>
> That said, for the current limited test context where we now this syscall is not called, I would say it's ok. And if it should get broken in the future, we would catch it and could find another magic number.
Fair, I suppose I was overcomplicating this unnecessarily. :)
> Of course, if you have a better idea of syscall number, I would be very happy to follow another rationale, as mine was mostly try and error. :)
>
> Regards,
> Pierrick
Best regards,
Florian
On Tue, Feb 24, 2026 at 10:01 AM Florian Hofhammer
<florian.hofhammer@epfl.ch> 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 <florian.hofhammer@epfl.ch>
> ---
for hexagon:
Reviewed-by: Brian Cain <brian.cain@oss.qualcomm.com>
> 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;
> +}
> 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
> --
> 2.53.0
>
© 2016 - 2026 Red Hat, Inc.