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(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) 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(qemu_plugin_id_t id, 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); 154 qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); 155 156 return 0; 157 } 158