plugins: extend execlog to filter matches

Sometimes the whole execlog is just two much so add the ability to
filter by instruction opcode or address.

[AJB: this shows for example

 qemu-system-aarch64 -display none -serial mon:stdio \
   -M virt -cpu max \
   -semihosting-config enable=on \
   -kernel ./tests/tcg/aarch64-softmmu/memory-sve \
   -plugin ./contrib/plugins/libexeclog.so,ifilter=st1w,afilter=0x40001808 -d plugin -D plugin.out

the st1w SVE instruction is not instrumenting its stores.]

Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Alexandre Iooss <erdnaxe@crans.org>
Cc: Robert Henry <robhenry@microsoft.com>
Cc: Aaron Lindsay <aaron@os.amperecomputing.com>
Message-Id: <20220929114231.583801-36-alex.bennee@linaro.org>
This commit is contained in:
Alex Bennée 2022-09-29 12:42:15 +01:00
parent 90bbf9d9db
commit b7855bf65f
2 changed files with 90 additions and 15 deletions

View file

@ -20,6 +20,9 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
/* Store last executed instruction on each vCPU as a GString */ /* Store last executed instruction on each vCPU as a GString */
GArray *last_exec; GArray *last_exec;
static GPtrArray *imatches;
static GArray *amatches;
/** /**
* Add memory read or write information to current instruction log * Add memory read or write information to current instruction log
*/ */
@ -85,12 +88,13 @@ static void vcpu_insn_exec(unsigned int cpu_index, void *udata)
static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
{ {
struct qemu_plugin_insn *insn; struct qemu_plugin_insn *insn;
uint64_t insn_vaddr; bool skip = (imatches || amatches) ? true : false;
uint32_t insn_opcode;
char *insn_disas;
size_t n = qemu_plugin_tb_n_insns(tb); size_t n = qemu_plugin_tb_n_insns(tb);
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
char *insn_disas;
uint64_t insn_vaddr;
/* /*
* `insn` is shared between translations in QEMU, copy needed data here. * `insn` is shared between translations in QEMU, copy needed data here.
* `output` is never freed as it might be used multiple times during * `output` is never freed as it might be used multiple times during
@ -99,20 +103,52 @@ static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
* a limitation for CISC architectures. * a limitation for CISC architectures.
*/ */
insn = qemu_plugin_tb_get_insn(tb, i); insn = qemu_plugin_tb_get_insn(tb, i);
insn_vaddr = qemu_plugin_insn_vaddr(insn);
insn_opcode = *((uint32_t *)qemu_plugin_insn_data(insn));
insn_disas = qemu_plugin_insn_disas(insn); insn_disas = qemu_plugin_insn_disas(insn);
char *output = g_strdup_printf("0x%"PRIx64", 0x%"PRIx32", \"%s\"", insn_vaddr = qemu_plugin_insn_vaddr(insn);
insn_vaddr, insn_opcode, insn_disas);
/* Register callback on memory read or write */ /*
qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem, * If we are filtering we better check out if we have any
QEMU_PLUGIN_CB_NO_REGS, * hits. The skip "latches" so we can track memory accesses
QEMU_PLUGIN_MEM_RW, NULL); * after the instruction we care about.
*/
if (skip && imatches) {
int j;
for (j = 0; j < imatches->len && skip; j++) {
char *m = g_ptr_array_index(imatches, j);
if (g_str_has_prefix(insn_disas, m)) {
skip = false;
}
}
}
if (skip && amatches) {
int j;
for (j = 0; j < amatches->len && skip; j++) {
uint64_t v = g_array_index(amatches, uint64_t, j);
if (v == insn_vaddr) {
skip = false;
}
}
}
if (skip) {
g_free(insn_disas);
} else {
uint32_t insn_opcode;
insn_opcode = *((uint32_t *)qemu_plugin_insn_data(insn));
char *output = g_strdup_printf("0x%"PRIx64", 0x%"PRIx32", \"%s\"",
insn_vaddr, insn_opcode, insn_disas);
/* Register callback on memory read or write */
qemu_plugin_register_vcpu_mem_cb(insn, vcpu_mem,
QEMU_PLUGIN_CB_NO_REGS,
QEMU_PLUGIN_MEM_RW, NULL);
/* Register callback on instruction */
qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec,
QEMU_PLUGIN_CB_NO_REGS, output);
}
/* Register callback on instruction */
qemu_plugin_register_vcpu_insn_exec_cb(insn, vcpu_insn_exec,
QEMU_PLUGIN_CB_NO_REGS, output);
} }
} }
@ -132,6 +168,25 @@ static void plugin_exit(qemu_plugin_id_t id, void *p)
} }
} }
/* Add a match to the array of matches */
static void parse_insn_match(char *match)
{
if (!imatches) {
imatches = g_ptr_array_new();
}
g_ptr_array_add(imatches, match);
}
static void parse_vaddr_match(char *match)
{
uint64_t v = g_ascii_strtoull(match, NULL, 16);
if (!amatches) {
amatches = g_array_new(false, true, sizeof(uint64_t));
}
g_array_append_val(amatches, v);
}
/** /**
* Install the plugin * Install the plugin
*/ */
@ -145,6 +200,19 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
*/ */
last_exec = g_array_new(FALSE, FALSE, sizeof(GString *)); last_exec = g_array_new(FALSE, FALSE, sizeof(GString *));
for (int i = 0; i < argc; i++) {
char *opt = argv[i];
g_autofree char **tokens = g_strsplit(opt, "=", 2);
if (g_strcmp0(tokens[0], "ifilter") == 0) {
parse_insn_match(tokens[1]);
} else if (g_strcmp0(tokens[0], "afilter") == 0) {
parse_vaddr_match(tokens[1]);
} else {
fprintf(stderr, "option parsing failed: %s\n", opt);
return -1;
}
}
/* Register translation block and exit callbacks */ /* Register translation block and exit callbacks */
qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);

View file

@ -346,7 +346,7 @@ The execlog tool traces executed instructions with memory access. It can be used
for debugging and security analysis purposes. for debugging and security analysis purposes.
Please be aware that this will generate a lot of output. Please be aware that this will generate a lot of output.
The plugin takes no argument:: The plugin needs default argument::
qemu-system-arm $(QEMU_ARGS) \ qemu-system-arm $(QEMU_ARGS) \
-plugin ./contrib/plugins/libexeclog.so -d plugin -plugin ./contrib/plugins/libexeclog.so -d plugin
@ -364,6 +364,13 @@ which will output an execution trace following this structure::
0, 0xd34, 0xf9c8f000, "bl #0x10c8" 0, 0xd34, 0xf9c8f000, "bl #0x10c8"
0, 0x10c8, 0xfff96c43, "ldr r3, [r0, #0x44]", load, 0x200000e4, RAM 0, 0x10c8, 0xfff96c43, "ldr r3, [r0, #0x44]", load, 0x200000e4, RAM
the output can be filtered to only track certain instructions or
addresses using the `ifilter` or `afilter` options. You can stack the
arguments if required::
qemu-system-arm $(QEMU_ARGS) \
-plugin ./contrib/plugins/libexeclog.so,ifilter=st1w,afilter=0x40001808 -d plugin
- contrib/plugins/cache.c - contrib/plugins/cache.c
Cache modelling plugin that measures the performance of a given L1 cache Cache modelling plugin that measures the performance of a given L1 cache