Files
qemu/tests/tcg/multiarch/plugin/test-plugin-set-pc.c
Florian Hofhammer 0b03c73c99 tests/tcg: add tests for qemu_plugin_set_pc API
The test plugin intercepts execution in different contexts. Without the
plugin, any of the implemented test functions would trigger an assert
and fail. With the plugin, control flow is redirected to skip the assert
and return cleanly via the qemu_plugin_set_pc() API.

Signed-off-by: Florian Hofhammer <florian.hofhammer@epfl.ch>
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260305-setpc-v5-v7-5-4c3adba52403@epfl.ch
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
2026-03-05 10:54:09 -08:00

135 lines
4.4 KiB
C

/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch>
*
* This test set exercises the qemu_plugin_set_pc() function in four different
* contexts:
* 1. in an instruction callback during normal execution,
* 2. in an instruction callback during signal handling,
* 3. in a memory access callback.
* 4. in a syscall callback,
*/
#include <assert.h>
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
/* If we issue this magic syscall, ... */
#define MAGIC_SYSCALL 4096
/* ... the plugin either jumps directly to the target address ... */
#define SETPC 0
/* ... or just updates the target address for future use in callbacks. */
#define SETTARGET 1
static int signal_handled;
void panic(const char *msg)
{
fprintf(stderr, "Panic: %s\n", msg);
abort();
}
/*
* This test executes a magic syscall which communicates two addresses to the
* plugin via the syscall arguments. Whenever we reach the "bad" instruction
* during normal execution, the plugin should redirect control flow to the
* "good" instruction instead.
*/
void test_insn(void)
{
long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_insn, &&good_insn,
NULL);
assert(ret == 0 && "Syscall filter did not return expected value");
bad_insn:
panic("PC redirection in instruction callback failed");
good_insn:
puts("PC redirection in instruction callback succeeded");
}
/*
* This signal handler communicates a "bad" and a "good" address to the plugin
* similar to the previous test, and skips to the "good" address when the "bad"
* one is reached. This serves to test whether PC redirection via
* qemu_plugin_set_pc() also works properly in a signal handler context.
*/
void usr1_handler(int signum)
{
long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_signal, &&good_signal,
NULL);
assert(ret == 0 && "Syscall filter did not return expected value");
bad_signal:
panic("PC redirection in instruction callback failed");
good_signal:
signal_handled = 1;
puts("PC redirection in instruction callback succeeded");
}
/*
* This test sends a signal to the process, which should trigger the above
* signal handler. The signal handler should then exercise the PC redirection
* functionality in the context of a signal handler, which behaves a bit
* differently from normal execution.
*/
void test_sighandler(void)
{
struct sigaction sa = {0};
sa.sa_handler = usr1_handler;
sigaction(SIGUSR1, &sa, NULL);
pid_t pid = getpid();
kill(pid, SIGUSR1);
assert(signal_handled == 1 && "Signal handler was not executed properly");
}
/*
* This test communicates a "good" address and the address of a local variable
* to the plugin. Upon accessing the local variable, the plugin should then
* redirect control flow to the "good" address via qemu_plugin_set_pc().
*/
void test_mem(void)
{
static uint32_t test = 1;
long ret = syscall(MAGIC_SYSCALL, SETTARGET, NULL, &&good_mem, &test);
assert(ret == 0 && "Syscall filter did not return expected value");
/* Ensure read access to the variable to trigger the plugin callback */
assert(test == 1);
panic("PC redirection in memory access callback failed");
good_mem:
puts("PC redirection in memory access callback succeeded");
}
/*
* This test executes a magic syscall which is intercepted and its actual
* execution skipped via the qemu_plugin_set_pc() API. In a proper plugin,
* syscall skipping would rather be implemented via the syscall filtering
* callback, but we want to make sure qemu_plugin_set_pc() works in different
* contexts.
*/
__attribute__((noreturn))
void test_syscall(void)
{
syscall(MAGIC_SYSCALL, SETPC, &&good_syscall);
panic("PC redirection in syscall callback failed");
good_syscall:
/*
* Note: we execute this test last and exit straight from here because when
* the plugin redirects control flow upon syscall, the stack frame for the
* syscall function (and potential other functions in the call chain in
* libc) is still live and the stack is not unwound properly. Thus,
* returning from here is risky and breaks on some architectures, so we
* just exit directly from this test.
*/
_exit(EXIT_SUCCESS);
}
int main(int argc, char *argv[])
{
test_insn();
test_sighandler();
test_mem();
test_syscall();
}