| 1 | /* |
| 2 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | * |
| 4 | * Copyright (C) 2026, Florian Hofhammer <florian.hofhammer@epfl.ch> |
| 5 | * |
| 6 | * This test set exercises the qemu_plugin_set_pc() function in four different |
| 7 | * contexts: |
| 8 | * 1. in an instruction callback during normal execution, |
| 9 | * 2. in an instruction callback during signal handling, |
| 10 | * 3. in a memory access callback. |
| 11 | * 4. in a syscall callback, |
| 12 | */ |
| 13 | #include <assert.h> |
| 14 | #include <signal.h> |
| 15 | #include <stdint.h> |
| 16 | #include <stdlib.h> |
| 17 | #include <stdio.h> |
| 18 | #include <unistd.h> |
| 19 | |
| 20 | /* If we issue this magic syscall, ... */ |
| 21 | #define MAGIC_SYSCALL 4096 |
| 22 | /* ... the plugin either jumps directly to the target address ... */ |
| 23 | #define SETPC 0 |
| 24 | /* ... or just updates the target address for future use in callbacks. */ |
| 25 | #define SETTARGET 1 |
| 26 | |
| 27 | static int signal_handled; |
| 28 | |
| 29 | void panic(const char *msg) |
| 30 | { |
| 31 | fprintf(stderr, "Panic: %s\n", msg); |
| 32 | abort(); |
| 33 | } |
| 34 | |
| 35 | /* |
| 36 | * This test executes a magic syscall which communicates two addresses to the |
| 37 | * plugin via the syscall arguments. Whenever we reach the "bad" instruction |
| 38 | * during normal execution, the plugin should redirect control flow to the |
| 39 | * "good" instruction instead. |
| 40 | */ |
| 41 | void test_insn(void) |
| 42 | { |
| 43 | long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_insn, &&good_insn, |
| 44 | NULL); |
| 45 | assert(ret == 0 && "Syscall filter did not return expected value"); |
| 46 | bad_insn: |
| 47 | panic("PC redirection in instruction callback failed"); |
| 48 | good_insn: |
| 49 | puts("PC redirection in instruction callback succeeded"); |
| 50 | } |
| 51 | |
| 52 | /* |
| 53 | * This signal handler communicates a "bad" and a "good" address to the plugin |
| 54 | * similar to the previous test, and skips to the "good" address when the "bad" |
| 55 | * one is reached. This serves to test whether PC redirection via |
| 56 | * qemu_plugin_set_pc() also works properly in a signal handler context. |
| 57 | */ |
| 58 | void usr1_handler(int signum) |
| 59 | { |
| 60 | long ret = syscall(MAGIC_SYSCALL, SETTARGET, &&bad_signal, &&good_signal, |
| 61 | NULL); |
| 62 | assert(ret == 0 && "Syscall filter did not return expected value"); |
| 63 | bad_signal: |
| 64 | panic("PC redirection in instruction callback failed"); |
| 65 | good_signal: |
| 66 | signal_handled = 1; |
| 67 | puts("PC redirection in instruction callback succeeded"); |
| 68 | } |
| 69 | |
| 70 | /* |
| 71 | * This test sends a signal to the process, which should trigger the above |
| 72 | * signal handler. The signal handler should then exercise the PC redirection |
| 73 | * functionality in the context of a signal handler, which behaves a bit |
| 74 | * differently from normal execution. |
| 75 | */ |
| 76 | void test_sighandler(void) |
| 77 | { |
| 78 | struct sigaction sa = {0}; |
| 79 | sa.sa_handler = usr1_handler; |
| 80 | sigaction(SIGUSR1, &sa, NULL); |
| 81 | pid_t pid = getpid(); |
| 82 | kill(pid, SIGUSR1); |
| 83 | assert(signal_handled == 1 && "Signal handler was not executed properly"); |
| 84 | } |
| 85 | |
| 86 | /* |
| 87 | * This test communicates a "good" address and the address of a local variable |
| 88 | * to the plugin. Upon accessing the local variable, the plugin should then |
| 89 | * redirect control flow to the "good" address via qemu_plugin_set_pc(). |
| 90 | */ |
| 91 | void test_mem(void) |
| 92 | { |
| 93 | static uint32_t test = 1; |
| 94 | long ret = syscall(MAGIC_SYSCALL, SETTARGET, NULL, &&good_mem, &test); |
| 95 | assert(ret == 0 && "Syscall filter did not return expected value"); |
| 96 | /* Ensure read access to the variable to trigger the plugin callback */ |
| 97 | assert(test == 1); |
| 98 | panic("PC redirection in memory access callback failed"); |
| 99 | good_mem: |
| 100 | puts("PC redirection in memory access callback succeeded"); |
| 101 | } |
| 102 | |
| 103 | /* |
| 104 | * This test executes a magic syscall which is intercepted and its actual |
| 105 | * execution skipped via the qemu_plugin_set_pc() API. In a proper plugin, |
| 106 | * syscall skipping would rather be implemented via the syscall filtering |
| 107 | * callback, but we want to make sure qemu_plugin_set_pc() works in different |
| 108 | * contexts. |
| 109 | */ |
| 110 | __attribute__((noreturn)) |
| 111 | void test_syscall(void) |
| 112 | { |
| 113 | syscall(MAGIC_SYSCALL, SETPC, &&good_syscall); |
| 114 | panic("PC redirection in syscall callback failed"); |
| 115 | good_syscall: |
| 116 | /* |
| 117 | * Note: we execute this test last and exit straight from here because when |
| 118 | * the plugin redirects control flow upon syscall, the stack frame for the |
| 119 | * syscall function (and potential other functions in the call chain in |
| 120 | * libc) is still live and the stack is not unwound properly. Thus, |
| 121 | * returning from here is risky and breaks on some architectures, so we |
| 122 | * just exit directly from this test. |
| 123 | */ |
| 124 | _exit(EXIT_SUCCESS); |
| 125 | } |
| 126 | |
| 127 | |
| 128 | int main(int argc, char *argv[]) |
| 129 | { |
| 130 | test_insn(); |
| 131 | test_sighandler(); |
| 132 | test_mem(); |
| 133 | test_syscall(); |
| 134 | } |