| 1 | /* |
| 2 | * Copyright (C) 2024, Simon Hamelin <simon.hamelin@grenoble-inp.org> |
| 3 | * |
| 4 | * Stop execution once a given address is reached or if the |
| 5 | * count of executed instructions reached a specified limit |
| 6 | * |
| 7 | * License: GNU GPL, version 2 or later. |
| 8 | * See the COPYING file in the top-level directory. |
| 9 | */ |
| 10 | |
| 11 | #include <assert.h> |
| 12 | #include <glib.h> |
| 13 | #include <inttypes.h> |
| 14 | #include <stdio.h> |
| 15 | #include <stdlib.h> |
| 16 | |
| 17 | #include <qemu-plugin.h> |
| 18 | |
| 19 | QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; |
| 20 | |
| 21 | /* Scoreboard to track executed instructions count */ |
| 22 | typedef struct { |
| 23 | uint64_t insn_count; |
| 24 | uint64_t current_pc; |
| 25 | } InstructionsCount; |
| 26 | static struct qemu_plugin_scoreboard *insn_count_sb; |
| 27 | static qemu_plugin_u64 insn_count; |
| 28 | static qemu_plugin_u64 current_pc; |
| 29 | |
| 30 | static uint64_t icount; |
| 31 | static int icount_exit_code; |
| 32 | |
| 33 | static bool exit_on_icount; |
| 34 | static bool exit_on_address; |
| 35 | |
| 36 | /* Map trigger addresses to exit code */ |
| 37 | static GHashTable *addrs_ht; |
| 38 | |
| 39 | typedef struct { |
| 40 | uint64_t exit_addr; |
| 41 | int exit_code; |
| 42 | } ExitInfo; |
| 43 | |
| 44 | static void exit_emulation(int return_code, char *message) |
| 45 | { |
| 46 | qemu_plugin_outs(message); |
| 47 | g_free(message); |
| 48 | exit(return_code); |
| 49 | } |
| 50 | |
| 51 | static void exit_icount_reached(unsigned int cpu_index, void *udata) |
| 52 | { |
| 53 | uint64_t insn_vaddr = qemu_plugin_u64_get(current_pc, cpu_index); |
| 54 | char *msg = g_strdup_printf("icount reached at 0x%" PRIx64 ", exiting\n", |
| 55 | insn_vaddr); |
| 56 | exit_emulation(icount_exit_code, msg); |
| 57 | } |
| 58 | |
| 59 | static void exit_address_reached(unsigned int cpu_index, void *udata) |
| 60 | { |
| 61 | ExitInfo *ei = udata; |
| 62 | g_assert(ei); |
| 63 | char *msg = g_strdup_printf("0x%" PRIx64 " reached, exiting\n", ei->exit_addr); |
| 64 | exit_emulation(ei->exit_code, msg); |
| 65 | } |
| 66 | |
| 67 | static void vcpu_tb_trans(struct qemu_plugin_tb *tb, void *userdata) |
| 68 | { |
| 69 | size_t tb_n = qemu_plugin_tb_n_insns(tb); |
| 70 | for (size_t i = 0; i < tb_n; i++) { |
| 71 | struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i); |
| 72 | uint64_t insn_vaddr = qemu_plugin_insn_vaddr(insn); |
| 73 | |
| 74 | if (exit_on_icount) { |
| 75 | /* Increment and check scoreboard for each instruction */ |
| 76 | qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( |
| 77 | insn, QEMU_PLUGIN_INLINE_ADD_U64, insn_count, 1); |
| 78 | qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( |
| 79 | insn, QEMU_PLUGIN_INLINE_STORE_U64, current_pc, insn_vaddr); |
| 80 | qemu_plugin_register_vcpu_insn_exec_cond_cb( |
| 81 | insn, exit_icount_reached, QEMU_PLUGIN_CB_NO_REGS, |
| 82 | QEMU_PLUGIN_COND_EQ, insn_count, icount + 1, NULL); |
| 83 | } |
| 84 | |
| 85 | if (exit_on_address) { |
| 86 | ExitInfo *ei = g_hash_table_lookup(addrs_ht, &insn_vaddr); |
| 87 | if (ei) { |
| 88 | /* Exit triggered by address */ |
| 89 | qemu_plugin_register_vcpu_insn_exec_cb( |
| 90 | insn, exit_address_reached, QEMU_PLUGIN_CB_NO_REGS, ei); |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | static void plugin_exit(void *p) |
| 97 | { |
| 98 | g_hash_table_destroy(addrs_ht); |
| 99 | qemu_plugin_scoreboard_free(insn_count_sb); |
| 100 | } |
| 101 | |
| 102 | QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, |
| 103 | const qemu_info_t *info, int argc, |
| 104 | char **argv) |
| 105 | { |
| 106 | addrs_ht = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free); |
| 107 | |
| 108 | insn_count_sb = qemu_plugin_scoreboard_new(sizeof(InstructionsCount)); |
| 109 | insn_count = qemu_plugin_scoreboard_u64_in_struct( |
| 110 | insn_count_sb, InstructionsCount, insn_count); |
| 111 | current_pc = qemu_plugin_scoreboard_u64_in_struct( |
| 112 | insn_count_sb, InstructionsCount, current_pc); |
| 113 | |
| 114 | for (int i = 0; i < argc; i++) { |
| 115 | char *opt = argv[i]; |
| 116 | g_auto(GStrv) tokens = g_strsplit(opt, "=", 2); |
| 117 | if (g_strcmp0(tokens[0], "icount") == 0) { |
| 118 | g_auto(GStrv) icount_tokens = g_strsplit(tokens[1], ":", 2); |
| 119 | icount = g_ascii_strtoull(icount_tokens[0], NULL, 0); |
| 120 | if (icount < 1 || g_strrstr(icount_tokens[0], "-") != NULL) { |
| 121 | fprintf(stderr, |
| 122 | "icount parsing failed: '%s' must be a positive " |
| 123 | "integer\n", |
| 124 | icount_tokens[0]); |
| 125 | return -1; |
| 126 | } |
| 127 | if (icount_tokens[1]) { |
| 128 | icount_exit_code = g_ascii_strtoull(icount_tokens[1], NULL, 0); |
| 129 | } |
| 130 | exit_on_icount = true; |
| 131 | } else if (g_strcmp0(tokens[0], "addr") == 0) { |
| 132 | g_auto(GStrv) addr_tokens = g_strsplit(tokens[1], ":", 2); |
| 133 | ExitInfo *ei = g_malloc(sizeof(ExitInfo)); |
| 134 | ei->exit_addr = g_ascii_strtoull(addr_tokens[0], NULL, 0); |
| 135 | ei->exit_code = 0; |
| 136 | if (addr_tokens[1]) { |
| 137 | ei->exit_code = g_ascii_strtoull(addr_tokens[1], NULL, 0); |
| 138 | } |
| 139 | g_hash_table_insert(addrs_ht, &ei->exit_addr, ei); |
| 140 | exit_on_address = true; |
| 141 | } else { |
| 142 | fprintf(stderr, "option parsing failed: %s\n", opt); |
| 143 | return -1; |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | if (!exit_on_icount && !exit_on_address) { |
| 148 | fprintf(stderr, "'icount' or 'addr' argument missing\n"); |
| 149 | return -1; |
| 150 | } |
| 151 | |
| 152 | /* Register translation block and exit callbacks */ |
| 153 | qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL); |
| 154 | qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); |
| 155 | |
| 156 | return 0; |
| 157 | } |