From: Ruslan Ruslichenko <Ruslan_Ruslichenko@epam.com>
Introduce Fault injection plugin for AArch64 targets. This
plugin provides a framework for testing guest OS by injecting
hardware-level faults during execution.
The plugin can be configured either statically via an XML file
at boot or dynamically at runtime via a UNIX socket.
Supported triggers:
- PC: Triggers on instruction execution at specific address.
- SYS_REG: Intercepts System Registers reads (e.g. mrs) and
modifies read results to configured value.
- RAM: Triggers on physical memory accesses.
- MMIO: Intercepts memory-mapped I/O.
- Timer: Triggers at a specific guest virtual clock time (ns).
Supported targets (injected faults):
- CPU_REG: Corrupts general-purpose CPU registers.
- RAM/MMIO: Modifies result of memory reads.
- IRQ: Inject hardware irqs on the primary INTC.
- EXCP: Inject CPU exceptions (e.g., Serror).
- Custom: Triggers device-specific fault handlers registered by
device models.
Signed-off-by: Ruslan Ruslichenko <Ruslan_Ruslichenko@epam.com>
---
contrib/plugins/fault_injection.c | 772 ++++++++++++++++++++++++++++++
contrib/plugins/meson.build | 1 +
2 files changed, 773 insertions(+)
create mode 100644 contrib/plugins/fault_injection.c
diff --git a/contrib/plugins/fault_injection.c b/contrib/plugins/fault_injection.c
new file mode 100644
index 0000000000..6fa09fd359
--- /dev/null
+++ b/contrib/plugins/fault_injection.c
@@ -0,0 +1,772 @@
+/*
+ * Fault Injection Plugin
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ctype.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "qemu/osdep.h"
+#include <qemu-plugin.h>
+
+#include "glib/gmarkup.h"
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+typedef enum {
+ TRIGGER_ON_PC = 0,
+ TRIGGER_ON_SYSREG,
+ TRIGGER_ON_RAM,
+ TRIGGER_ON_MMIO,
+ TRIGGER_ON_TIMER
+} FaultTrigger;
+
+typedef enum {
+ TARGET_EMPTY = 0,
+ TARGET_CPU_REG,
+ TARGET_RAM,
+ TARGET_MMIO,
+ TARGET_IRQ,
+ TARGET_EXCP,
+ TARGET_CUSTOM
+} FaultTarget;
+
+typedef struct {
+ FaultTarget target;
+ uint64_t target_data;
+
+ FaultTrigger trigger;
+ uint64_t trigger_condition;
+ gchar *trigger_condition_str;
+
+ uint64_t fault_data;
+ gchar *fault_name;
+
+ uint8_t size;
+ uint8_t cpu;
+ gchar *irq_type;
+} FaultConfig;
+
+typedef struct {
+ uint64_t hwaddr;
+ uint64_t value;
+ uint8_t size;
+} MmioOverrideConfig;
+
+#define FI_LOG(...) do { \
+ g_autofree gchar *__msg = g_strdup_printf(__VA_ARGS__); \
+ qemu_plugin_outs(__msg); \
+} while (0)
+
+static bool plugin_is_shutting_down = false;
+static int socket_fd = -1;
+
+static GRWLock trigger_lock;
+
+GHashTable *pc_faults;
+GHashTable *mem_faults;
+GHashTable *sys_reg_faults;
+
+static GRWLock mmio_override_lock;
+static GRWLock sysreg_lock;
+
+GHashTable *mmio_override;
+
+static struct qemu_plugin_register *gp_registers[31];
+
+static void register_pc_trigger(FaultConfig* fc);
+static void register_mmio_override(FaultConfig *fc);
+
+static void fc_free(FaultConfig *fc);
+
+static bool apply_mmio_override(uint64_t hwaddr, unsigned size, bool is_write,
+ uint64_t *value)
+{
+ g_rw_lock_reader_lock(&mmio_override_lock);
+
+ MmioOverrideConfig *conf = g_hash_table_lookup(mmio_override, &hwaddr);
+ if (!conf) {
+ g_rw_lock_reader_unlock(&mmio_override_lock);
+ return false;
+ }
+
+ *value = conf->value;
+
+ g_rw_lock_reader_unlock(&mmio_override_lock);
+
+ return true;
+}
+
+static bool mmio_override_cb(uint64_t hwaddr, unsigned size, bool is_write,
+ uint64_t *value)
+{
+ if (is_write) {
+ return false;
+ }
+
+ return apply_mmio_override(hwaddr, size, is_write, value);
+}
+
+static void cpu_write_reg(int reg_id, uint64_t value)
+{
+ g_assert(reg_id >= 0 && reg_id <= 30);
+
+ g_autoptr(GByteArray) buf = g_byte_array_new();
+
+ g_byte_array_set_size(buf, 8);
+
+ memcpy(buf->data, &value, 8);
+
+ bool success = qemu_plugin_write_register(gp_registers[reg_id], buf);
+ if (!success) {
+ FI_LOG("FI: Failed to write register\n");
+ }
+}
+
+static void cpu_write_mem(uint64_t addr, uint64_t data, uint8_t size)
+{
+ g_autoptr(GByteArray) buf = g_byte_array_new();
+
+ g_byte_array_set_size(buf, size);
+
+ memcpy(buf->data, &data, size);
+
+ bool success = qemu_plugin_write_memory_vaddr(addr, buf);
+ if (!success) {
+ FI_LOG("FI: Failed to write memory\n");
+ }
+}
+
+static void inject_irq(FaultConfig *fc)
+{
+ int irq_num = fc->target_data;
+
+ if (!fc->irq_type || !g_strcmp0(fc->irq_type, "SPI")) {
+ irq_num += 32;
+ } else if (!g_strcmp0(fc->irq_type, "PPI")) {
+ irq_num += 16;
+ } else if (!g_strcmp0(fc->irq_type, "SGI")) {
+ /* skip */
+ } else {
+ FI_LOG("FI: Unknown IRQ type: %s\n", fc->irq_type);
+ }
+
+ qemu_plugin_inject_irq(irq_num, fc->cpu, fc->fault_data);
+
+}
+
+static void inject_fault(FaultConfig* fc)
+{
+ switch (fc->target) {
+ case TARGET_CPU_REG:
+ cpu_write_reg(fc->target_data, fc->fault_data);
+ break;
+ case TARGET_RAM:
+ cpu_write_mem(fc->target_data, fc->fault_data, fc->size);
+ break;
+ case TARGET_MMIO:
+ register_mmio_override(fc);
+ break;
+ case TARGET_IRQ:
+ inject_irq(fc);
+ break;
+ case TARGET_EXCP:
+ qemu_plugin_inject_exception(fc->target_data, fc->fault_data);
+ break;
+ case TARGET_CUSTOM:
+ qemu_plugin_trigger_custom_fault(fc->fault_name,
+ &fc->target_data, &fc->fault_data);
+ break;
+ default:
+ FI_LOG("FI: Unsupported fault type\n");
+ break;
+ }
+}
+
+static void timed_fault_timer_cb(void* data)
+{
+ FaultConfig* fc = (FaultConfig*)data;
+
+ inject_fault(fc);
+
+ fc_free(fc);
+}
+
+static void vcpu_mem_cb(unsigned int vcpu_index,
+ qemu_plugin_meminfo_t info,
+ uint64_t vaddr, void *userdata)
+{
+ GSList *fault_list;
+
+ g_rw_lock_reader_lock(&trigger_lock);
+
+ fault_list = g_hash_table_lookup(mem_faults, &vaddr);
+ for (GSList *entry = fault_list; entry != NULL; entry = entry->next) {
+ FaultConfig *fc = (FaultConfig *)entry->data;
+
+ inject_fault(fc);
+ }
+
+ g_rw_lock_reader_unlock(&trigger_lock);
+}
+
+static void vcpu_insn_exec_cb(unsigned int vcpu_index, void *data)
+{
+ uint64_t insn_vaddr = (uint64_t)data;
+ GSList *fault_list;
+
+ g_rw_lock_reader_lock(&trigger_lock);
+
+ fault_list = g_hash_table_lookup(pc_faults,
+ &insn_vaddr);
+
+ for (GSList *l = fault_list; l != NULL; l = l->next) {
+ FaultConfig *fc = (FaultConfig *)l->data;
+
+ inject_fault(fc);
+ }
+
+ g_rw_lock_reader_unlock(&trigger_lock);
+}
+
+#define MRS_OPCODE 0xD5300000
+#define MRS_OPCODE_MASK 0xFFF00000
+
+static void handle_sysreg_fault(struct qemu_plugin_insn *insn, uint64_t insn_vaddr)
+{
+ FaultConfig *fc;
+ uint32_t raw_opcode;
+ size_t data_size = qemu_plugin_insn_data(insn, &raw_opcode, sizeof(raw_opcode));
+ if (data_size < sizeof(raw_opcode)) {
+ return;
+ }
+
+ uint32_t opcode = GUINT32_FROM_LE(raw_opcode);
+
+ if ((opcode & MRS_OPCODE_MASK) != MRS_OPCODE) {
+ return;
+ }
+
+ char *disas = qemu_plugin_insn_disas(insn);
+ if (!disas) {
+ return;
+ }
+
+ int dest_reg;
+ char sysreg_name[32] = { 0 };
+
+ if (sscanf(disas, "mrs x%d, %31s", &dest_reg, sysreg_name) == 2) {
+ uint64_t fault_data;
+ bool found = false;
+
+ g_rw_lock_reader_lock(&sysreg_lock);
+
+ fc = g_hash_table_lookup(sys_reg_faults, sysreg_name);
+ if (fc) {
+ fault_data = fc->fault_data;
+ found = true;
+ }
+
+ g_rw_lock_reader_unlock(&sysreg_lock);
+
+ if (found) {
+ /*
+ * WA: For CPU system registers, injecting fault to destination
+ * gp register on next PC
+ */
+ FaultConfig *dyn_pc_fault = g_new0(FaultConfig, 1);
+
+ dyn_pc_fault->trigger = TRIGGER_ON_PC;
+ dyn_pc_fault->trigger_condition = insn_vaddr + 4;
+ dyn_pc_fault->target = TARGET_CPU_REG;
+ dyn_pc_fault->target_data = dest_reg;
+ dyn_pc_fault->fault_data = fault_data;
+
+ register_pc_trigger(dyn_pc_fault);
+ }
+ }
+
+ g_free(disas);
+}
+
+static void vcpu_tb_trans_cb(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+ for(int i = 0; i < qemu_plugin_tb_n_insns(tb); i++) {
+ struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+ uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn);
+ GSList *fault_list;
+
+ qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem_cb,
+ QEMU_PLUGIN_CB_NO_REGS, QEMU_PLUGIN_MEM_RW, NULL);
+
+ handle_sysreg_fault(insn, insn_vaddr);
+
+ g_rw_lock_reader_lock(&trigger_lock);
+
+ fault_list = g_hash_table_lookup(pc_faults,
+ &insn_vaddr);
+
+ if (fault_list) {
+ qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec_cb,
+ QEMU_PLUGIN_CB_RW_REGS,
+ (void *)insn_vaddr);
+ }
+
+ g_rw_lock_reader_unlock(&trigger_lock);
+ }
+}
+
+static void vcpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index)
+{
+ if (vcpu_index) {
+ /* Init reg's and mem watchpoints only once, with CPU 0 */
+ return;
+ }
+
+ g_autoptr(GArray) reg_list = qemu_plugin_get_registers();
+
+ for (int i = 0; i < reg_list->len; ++i) {
+ qemu_plugin_reg_descriptor *rd = &g_array_index(reg_list,
+ qemu_plugin_reg_descriptor, i);
+
+ if (rd->name[0] == 'x' && isdigit(rd->name[1])) {
+ int reg_ind = atoi(&rd->name[1]);
+
+ if (reg_ind >= 0 && reg_ind <= 30) {
+ gp_registers[reg_ind] = rd->handle;
+ }
+ }
+ }
+}
+
+static void register_mmio_override(FaultConfig *fc)
+{
+ g_rw_lock_writer_lock(&mmio_override_lock);
+
+ MmioOverrideConfig *curr_conf = g_hash_table_lookup(mmio_override,
+ &fc->target_data);
+ if (curr_conf) {
+ curr_conf->value = fc->fault_data;
+ curr_conf->size = fc->size;
+ } else {
+ MmioOverrideConfig *new_conf = g_new0(MmioOverrideConfig, 1);
+
+ new_conf->hwaddr = fc->target_data;
+ new_conf->value = fc->fault_data;
+ new_conf->size = fc->size;
+
+ g_hash_table_insert(mmio_override, &new_conf->hwaddr,
+ new_conf);
+ }
+
+ g_rw_lock_writer_unlock(&mmio_override_lock);
+}
+
+static void register_sysreg_override(FaultConfig *fc)
+{
+ g_rw_lock_writer_lock(&sysreg_lock);
+
+ FaultConfig *old_fc = g_hash_table_lookup(sys_reg_faults,
+ fc->trigger_condition_str);
+ g_hash_table_replace(sys_reg_faults,
+ fc->trigger_condition_str,
+ fc);
+
+ if (old_fc) {
+ fc_free(old_fc);
+ }
+
+ g_rw_lock_writer_unlock(&sysreg_lock);
+}
+
+static void register_ram_trigger(FaultConfig* fc)
+{
+
+ g_rw_lock_writer_lock(&trigger_lock);
+
+ GSList *mem_list = g_hash_table_lookup(mem_faults, &fc->trigger_condition);
+
+ mem_list = g_slist_append(mem_list, fc);
+ g_hash_table_insert(mem_faults,
+ &fc->trigger_condition, mem_list);
+
+ g_rw_lock_writer_unlock(&trigger_lock);
+
+}
+
+static void register_pc_trigger(FaultConfig* fc)
+{
+ g_rw_lock_writer_lock(&trigger_lock);
+
+ bool duplicate = false;
+ GSList *pc_list = g_hash_table_lookup(pc_faults,
+ &fc->trigger_condition);
+
+ for (GSList *l = pc_list; l != NULL; l = l->next) {
+ FaultConfig *existing = (FaultConfig *)l->data;
+
+ if (existing->target == fc->target &&
+ existing->target_data == fc->target_data &&
+ existing->fault_data == fc->fault_data) {
+ duplicate = true;
+ break;
+ }
+ }
+
+ if (!duplicate) {
+ pc_list = g_slist_append(pc_list, fc);
+ g_hash_table_insert(pc_faults, &fc->trigger_condition,
+ pc_list);
+ } else {
+ fc_free(fc);
+ }
+
+ g_rw_lock_writer_unlock(&trigger_lock);
+
+}
+
+static bool register_fault(FaultConfig *fc)
+{
+ FaultTrigger trigger_type = fc->trigger;
+
+ if (fc->target == TARGET_CUSTOM && !fc->fault_name) {
+ FI_LOG("FI: fault_name needed for custom targets\n");
+ return false;
+ }
+
+ if (!fc->size) {
+ fc->size = sizeof(fc->fault_data);
+ }
+
+ switch (fc->trigger) {
+ case TRIGGER_ON_PC:
+ register_pc_trigger(fc);
+ break;
+ case TRIGGER_ON_SYSREG:
+ if (fc->target != TARGET_EMPTY) {
+ FI_LOG("FI: SYS_REG faults does not support target\n");
+ return false;
+ }
+
+ register_sysreg_override(fc);
+ break;
+ case TRIGGER_ON_RAM:
+ if (fc->target == TARGET_EMPTY) {
+ /* Allow short form for RAM triggers to override same memory */
+ fc->target = TARGET_RAM;
+ fc->target_data = fc->trigger_condition;
+ }
+
+ register_ram_trigger(fc);
+ break;
+ case TRIGGER_ON_MMIO:
+ if (fc->target != TARGET_EMPTY) {
+ FI_LOG("FI: No target support for MMIO trigger for now\n");
+ return false;
+ }
+
+ register_mmio_override(fc);
+ fc_free(fc);
+ break;
+ case TRIGGER_ON_TIMER:
+ if (fc->target == TARGET_CPU_REG) {
+ FI_LOG("FI: CPU_REG is invalid for TIMER trigger\n");
+ return false;
+ }
+ qemu_plugin_timer_virt_ns(fc->trigger_condition,
+ timed_fault_timer_cb, fc);
+ break;
+ default:
+ /* skip */
+ break;
+ }
+
+ if (trigger_type == TRIGGER_ON_PC || trigger_type == TRIGGER_ON_SYSREG) {
+ qemu_plugin_flush_tb_cache();
+ }
+
+ return true;
+}
+
+static void fc_free(FaultConfig *fc)
+{
+ if (!fc) {
+ return;
+ }
+
+ g_free(fc->trigger_condition_str);
+ g_free(fc->fault_name);
+ g_free(fc->irq_type);
+
+ g_free(fc);
+}
+
+static void xml_start_elem(GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ if (!g_strcmp0(element_name, "Fault")) {
+ FaultConfig *fc = g_new0(FaultConfig, 1);
+
+ for (int i = 0; attribute_names[i] != NULL; i++) {
+ const char *key = attribute_names[i];
+ const char *value = attribute_values[i];
+
+ if (!g_strcmp0(key, "target")) {
+ if (!g_strcmp0(value, "CPU_REG")) {
+ fc->target = TARGET_CPU_REG;
+ } else if (!g_strcmp0(value, "RAM")) {
+ fc->target = TARGET_RAM;
+ } else if (!g_strcmp0(value, "MMIO")) {
+ fc->target = TARGET_MMIO;
+ } else if (!g_strcmp0(value, "IRQ")) {
+ fc->target = TARGET_IRQ;
+ } else if (!g_strcmp0(value, "EXCP")) {
+ fc->target = TARGET_EXCP;
+ } else if (!g_strcmp0(value, "CUSTOM")) {
+ fc->target = TARGET_CUSTOM;
+ } else {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "FI: Unknown target type '%s'", value);
+ fc_free(fc);
+ return;
+ }
+ } else if (!g_strcmp0(key, "trigger")) {
+ if (!g_strcmp0(value, "PC")) {
+ fc->trigger = TRIGGER_ON_PC;
+ } else if (!g_strcmp0(value, "SYS_REG")) {
+ fc->trigger = TRIGGER_ON_SYSREG;
+ } else if (!g_strcmp0(value, "RAM")) {
+ fc->trigger = TRIGGER_ON_RAM;
+ } else if (!g_strcmp0(value, "MMIO")) {
+ fc->trigger = TRIGGER_ON_MMIO;
+ } else if (!g_strcmp0(value, "TIMER")) {
+ fc->trigger = TRIGGER_ON_TIMER;
+ } else {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "FI: Unknown trigger type: '%s'", value);
+ fc_free(fc);
+ return;
+ }
+ } else if (!g_strcmp0(key, "target_data")) {
+ fc->target_data = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "trigger_condition")) {
+ fc->trigger_condition_str = g_strdup(value);
+ fc->trigger_condition = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "fault_data")) {
+ fc->fault_data = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "size")) {
+ fc->size = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "cpu")) {
+ fc->cpu = strtoull(value, NULL, 0);
+ } else if (!g_strcmp0(key, "irq_type")) {
+ fc->irq_type = g_strdup(value);
+ } else if (!g_strcmp0(key, "fault_name")) {
+ fc->fault_name = g_strdup(value);
+ }
+ }
+
+ if (!register_fault(fc)) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "FI: Failed to register fault");
+ fc_free(fc);
+ return;
+ }
+ }
+}
+
+static GMarkupParser parser = {
+ .start_element = xml_start_elem,
+};
+
+static void *ipc_listener_thread(void *arg)
+{
+ char *sock_path = (char *)arg;
+ struct sockaddr_un addr;
+ int client_fd;
+ char buf[1024];
+
+ socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (socket_fd < 0) {
+ FI_LOG("Failed to create socket, err = %s\n",
+ strerror(errno));
+ return NULL;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+
+ addr.sun_family = AF_UNIX;
+ g_strlcpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1);
+
+ unlink(sock_path);
+
+ if (bind(socket_fd, &addr, sizeof(addr)) < 0) {
+ FI_LOG("Failed to create socket, err = %s\n",
+ strerror(errno));
+ close(socket_fd);
+ return NULL;
+ }
+
+ if (listen(socket_fd, 1)) {
+ FI_LOG("Listen socket failed, err = %s\n",
+ strerror(errno));
+ close(socket_fd);
+ return NULL;
+ }
+
+ while (true) {
+ client_fd = accept(socket_fd, NULL, NULL);
+
+ if (client_fd < 0) {
+ if (plugin_is_shutting_down) {
+ break;
+ }
+ continue;
+ }
+
+ GString *xml_payload = g_string_new(NULL);
+
+ memset(buf, 0, sizeof(buf));
+
+ while (true) {
+ ssize_t bytes_read = read(client_fd, buf, sizeof(buf) - 1);
+
+ if (bytes_read > 0) {
+ g_string_append_len(xml_payload, buf, bytes_read);
+ } else if (bytes_read == 0) {
+ break;
+ } else {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if (xml_payload->len > 0) {
+ GError *err = NULL;
+
+ GMarkupParseContext *ctx = g_markup_parse_context_new(&parser,
+ 0, NULL, NULL);
+
+ if (!g_markup_parse_context_parse(ctx, xml_payload->str,
+ xml_payload->len, &err)) {
+ FI_LOG("FI Error: Failed to parse dynamic XML: %s\n",
+ err->message);
+ g_error_free(err);
+ }
+
+ g_markup_parse_context_free(ctx);
+ }
+
+ g_string_free(xml_payload, TRUE);
+ close(client_fd);
+ }
+
+ unlink(sock_path);
+ g_free(sock_path);
+
+ return NULL;
+}
+
+static void plugin_exit_cb(qemu_plugin_id_t id, void *userdata)
+{
+ plugin_is_shutting_down = true;
+
+ if (socket_fd >= 0) {
+ close(socket_fd);
+ socket_fd = -1;
+ }
+}
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+ const qemu_info_t *info,
+ int argc, char **argv)
+{
+ const char *config_path = NULL;
+ const char *socket_path = NULL;
+ gchar *config;
+ gsize length;
+ GError *err = NULL;
+ bool success;
+
+ if (strcmp(info->target_name, "aarch64")) {
+ FI_LOG("FI: Target %s is not supported\n", info->target_name);
+ return 1;
+ }
+
+ for (int i = 0; i < argc; ++i) {
+ if (g_str_has_prefix(argv[i], "config=")) {
+ config_path = argv[i] + strlen("config=");
+ } else if (g_str_has_prefix(argv[i], "socket=")) {
+ socket_path = g_strdup(argv[i] + strlen("socket="));
+ }
+ }
+
+ if (!config_path && !socket_path) {
+ FI_LOG("FI: either config or socket path required\n");
+ return 1;
+ }
+
+ pc_faults = g_hash_table_new(g_int64_hash, g_int64_equal);
+ mem_faults = g_hash_table_new(g_int64_hash, g_int64_equal);
+ sys_reg_faults = g_hash_table_new(g_str_hash, g_str_equal);
+ mmio_override = g_hash_table_new(g_int64_hash, g_int64_equal);
+
+ g_rw_lock_init(&trigger_lock);
+ g_rw_lock_init(&mmio_override_lock);
+ g_rw_lock_init(&sysreg_lock);
+
+ if (config_path) {
+ if (access(config_path, R_OK)) {
+ FI_LOG("FI: can't access config file, err = %s\n",
+ strerror(errno));
+ return 1;
+ }
+
+ success = g_file_get_contents(config_path, &config,
+ &length, &err);
+ if (success) {
+ GMarkupParseContext *ctx = g_markup_parse_context_new(&parser,
+ 0, NULL, NULL);
+
+ success = g_markup_parse_context_parse(ctx, config, length, &err);
+ }
+
+ if (!success) {
+ FI_LOG("FI: failed to parse config file\n");
+ return 1;
+ }
+ }
+
+ if (socket_path) {
+ pthread_t thread_id;
+
+ pthread_create(&thread_id, NULL, ipc_listener_thread,
+ (void*)socket_path);
+ pthread_detach(thread_id);
+ }
+
+ qemu_plugin_register_vcpu_init_cb(id, vcpu_init_cb);
+ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans_cb);
+ qemu_plugin_register_mmio_override_cb(id, mmio_override_cb);
+
+ qemu_plugin_register_atexit_cb(id, plugin_exit_cb, NULL);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build
index 099319e7a1..df4d4c5177 100644
--- a/contrib/plugins/meson.build
+++ b/contrib/plugins/meson.build
@@ -12,6 +12,7 @@ contrib_plugins = [
'stoptrigger.c',
'traps.c',
'uftrace.c',
+'fault_injection.c',
]
if host_os != 'windows'
--
2.43.0
© 2016 - 2026 Red Hat, Inc.