xref: /openbmc/qemu/contrib/plugins/stoptrigger.c (revision 2e1cacfb)
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 } InstructionsCount;
25 static struct qemu_plugin_scoreboard *insn_count_sb;
26 static qemu_plugin_u64 insn_count;
27 
28 static uint64_t icount;
29 static int icount_exit_code;
30 
31 static bool exit_on_icount;
32 static bool exit_on_address;
33 
34 /* Map trigger addresses to exit code */
35 static GHashTable *addrs_ht;
36 
37 static void exit_emulation(int return_code, char *message)
38 {
39     qemu_plugin_outs(message);
40     g_free(message);
41     exit(return_code);
42 }
43 
44 static void exit_icount_reached(unsigned int cpu_index, void *udata)
45 {
46     uint64_t insn_vaddr = GPOINTER_TO_UINT(udata);
47     char *msg = g_strdup_printf("icount reached at 0x%" PRIx64 ", exiting\n",
48                                 insn_vaddr);
49 
50     exit_emulation(icount_exit_code, msg);
51 }
52 
53 static void exit_address_reached(unsigned int cpu_index, void *udata)
54 {
55     uint64_t insn_vaddr = GPOINTER_TO_UINT(udata);
56     char *msg = g_strdup_printf("0x%" PRIx64 " reached, exiting\n", insn_vaddr);
57     int exit_code;
58 
59     exit_code = GPOINTER_TO_INT(
60         g_hash_table_lookup(addrs_ht, GUINT_TO_POINTER(insn_vaddr)));
61 
62     exit_emulation(exit_code, msg);
63 }
64 
65 static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
66 {
67     size_t tb_n = qemu_plugin_tb_n_insns(tb);
68     for (size_t i = 0; i < tb_n; i++) {
69         struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
70         gpointer insn_vaddr = GUINT_TO_POINTER(qemu_plugin_insn_vaddr(insn));
71 
72         if (exit_on_icount) {
73             /* Increment and check scoreboard for each instruction */
74             qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
75                 insn, QEMU_PLUGIN_INLINE_ADD_U64, insn_count, 1);
76             qemu_plugin_register_vcpu_insn_exec_cond_cb(
77                 insn, exit_icount_reached, QEMU_PLUGIN_CB_NO_REGS,
78                 QEMU_PLUGIN_COND_EQ, insn_count, icount + 1, insn_vaddr);
79         }
80 
81         if (exit_on_address) {
82             if (g_hash_table_contains(addrs_ht, insn_vaddr)) {
83                 /* Exit triggered by address */
84                 qemu_plugin_register_vcpu_insn_exec_cb(
85                     insn, exit_address_reached, QEMU_PLUGIN_CB_NO_REGS,
86                     insn_vaddr);
87             }
88         }
89     }
90 }
91 
92 static void plugin_exit(qemu_plugin_id_t id, void *p)
93 {
94     g_hash_table_destroy(addrs_ht);
95     qemu_plugin_scoreboard_free(insn_count_sb);
96 }
97 
98 QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
99                                            const qemu_info_t *info, int argc,
100                                            char **argv)
101 {
102     addrs_ht = g_hash_table_new(NULL, g_direct_equal);
103 
104     insn_count_sb = qemu_plugin_scoreboard_new(sizeof(InstructionsCount));
105     insn_count = qemu_plugin_scoreboard_u64_in_struct(
106         insn_count_sb, InstructionsCount, insn_count);
107 
108     for (int i = 0; i < argc; i++) {
109         char *opt = argv[i];
110         g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
111         if (g_strcmp0(tokens[0], "icount") == 0) {
112             g_auto(GStrv) icount_tokens = g_strsplit(tokens[1], ":", 2);
113             icount = g_ascii_strtoull(icount_tokens[0], NULL, 0);
114             if (icount < 1 || g_strrstr(icount_tokens[0], "-") != NULL) {
115                 fprintf(stderr,
116                         "icount parsing failed: '%s' must be a positive "
117                         "integer\n",
118                         icount_tokens[0]);
119                 return -1;
120             }
121             if (icount_tokens[1]) {
122                 icount_exit_code = g_ascii_strtoull(icount_tokens[1], NULL, 0);
123             }
124             exit_on_icount = true;
125         } else if (g_strcmp0(tokens[0], "addr") == 0) {
126             g_auto(GStrv) addr_tokens = g_strsplit(tokens[1], ":", 2);
127             uint64_t exit_addr = g_ascii_strtoull(addr_tokens[0], NULL, 0);
128             int exit_code = 0;
129             if (addr_tokens[1]) {
130                 exit_code = g_ascii_strtoull(addr_tokens[1], NULL, 0);
131             }
132             g_hash_table_insert(addrs_ht, GUINT_TO_POINTER(exit_addr),
133                                 GINT_TO_POINTER(exit_code));
134             exit_on_address = true;
135         } else {
136             fprintf(stderr, "option parsing failed: %s\n", opt);
137             return -1;
138         }
139     }
140 
141     if (!exit_on_icount && !exit_on_address) {
142         fprintf(stderr, "'icount' or 'addr' argument missing\n");
143         return -1;
144     }
145 
146     /* Register translation block and exit callbacks */
147     qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
148     qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
149 
150     return 0;
151 }
152