xref: /qemu/contrib/plugins/stoptrigger.c (revision 70ce076fa6dff60585c229a4b641b13e64bf03cf)
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