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 <[email protected]>
---
tests/tcg/arm/Makefile.target | 6 +++++
tests/tcg/hexagon/Makefile.target | 7 +++++
tests/tcg/mips/Makefile.target | 6 ++++-
tests/tcg/mips64/Makefile.target | 15 +++++++++++
tests/tcg/mips64el/Makefile.target | 15 +++++++++++
tests/tcg/mipsel/Makefile.target | 15 +++++++++++
tests/tcg/multiarch/Makefile.target | 22 ++++++++++++++--
.../{ => plugin}/check-plugin-output.sh | 0
.../{ => plugin}/test-plugin-mem-access.c | 0
.../plugin/test-plugin-skip-syscalls.c | 26 +++++++++++++++++++
tests/tcg/plugins/syscall.c | 6 +++++
tests/tcg/sparc64/Makefile.target | 16 ++++++++++++
12 files changed, 131 insertions(+), 3 deletions(-)
create mode 100644 tests/tcg/mips64/Makefile.target
create mode 100644 tests/tcg/mips64el/Makefile.target
create mode 100644 tests/tcg/mipsel/Makefile.target
rename tests/tcg/multiarch/{ => plugin}/check-plugin-output.sh (100%)
rename tests/tcg/multiarch/{ => plugin}/test-plugin-mem-access.c (100%)
create mode 100644 tests/tcg/multiarch/plugin/test-plugin-skip-syscalls.c
create mode 100644 tests/tcg/sparc64/Makefile.target
diff --git a/tests/tcg/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