 14fd492b89
			
		
	
	
		14fd492b89
		
	
	
	
	
		
			
			We originally naively treated expansion as safe because we expected each new CPU/thread to appear in order. However the -M raspi2 model triggered a case where a new high cpu_index thread started executing just before a smaller one. Clean this up by converting the GArray into the simpler GPtrArray and then holding a lock for the expansion. Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Cc: Alexandre Iooss <erdnaxe@crans.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-Id: <20221027183637.2772968-29-alex.bennee@linaro.org>
		
			
				
	
	
		
			247 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2021, Alexandre Iooss <erdnaxe@crans.org>
 | |
|  *
 | |
|  * Log instruction execution with memory access.
 | |
|  *
 | |
|  * License: GNU GPL, version 2 or later.
 | |
|  *   See the COPYING file in the top-level directory.
 | |
|  */
 | |
| #include <glib.h>
 | |
| #include <inttypes.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #include <qemu-plugin.h>
 | |
| 
 | |
| QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
 | |
| 
 | |
| /* Store last executed instruction on each vCPU as a GString */
 | |
| static GPtrArray *last_exec;
 | |
| static GMutex expand_array_lock;
 | |
| 
 | |
| static GPtrArray *imatches;
 | |
| static GArray *amatches;
 | |
| 
 | |
| /*
 | |
|  * Expand last_exec array.
 | |
|  *
 | |
|  * As we could have multiple threads trying to do this we need to
 | |
|  * serialise the expansion under a lock. Threads accessing already
 | |
|  * created entries can continue without issue even if the ptr array
 | |
|  * gets reallocated during resize.
 | |
|  */
 | |
| static void expand_last_exec(int cpu_index)
 | |
| {
 | |
|     g_mutex_lock(&expand_array_lock);
 | |
|     while (cpu_index >= last_exec->len) {
 | |
|         GString *s = g_string_new(NULL);
 | |
|         g_ptr_array_add(last_exec, s);
 | |
|     }
 | |
|     g_mutex_unlock(&expand_array_lock);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add memory read or write information to current instruction log
 | |
|  */
 | |
| static void vcpu_mem(unsigned int cpu_index, qemu_plugin_meminfo_t info,
 | |
|                      uint64_t vaddr, void *udata)
 | |
| {
 | |
|     GString *s;
 | |
| 
 | |
|     /* Find vCPU in array */
 | |
|     g_assert(cpu_index < last_exec->len);
 | |
|     s = g_ptr_array_index(last_exec, cpu_index);
 | |
| 
 | |
|     /* Indicate type of memory access */
 | |
|     if (qemu_plugin_mem_is_store(info)) {
 | |
|         g_string_append(s, ", store");
 | |
|     } else {
 | |
|         g_string_append(s, ", load");
 | |
|     }
 | |
| 
 | |
|     /* If full system emulation log physical address and device name */
 | |
|     struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(info, vaddr);
 | |
|     if (hwaddr) {
 | |
|         uint64_t addr = qemu_plugin_hwaddr_phys_addr(hwaddr);
 | |
|         const char *name = qemu_plugin_hwaddr_device_name(hwaddr);
 | |
|         g_string_append_printf(s, ", 0x%08"PRIx64", %s", addr, name);
 | |
|     } else {
 | |
|         g_string_append_printf(s, ", 0x%08"PRIx64, vaddr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Log instruction execution
 | |
|  */
 | |
| static void vcpu_insn_exec(unsigned int cpu_index, void *udata)
 | |
| {
 | |
|     GString *s;
 | |
| 
 | |
|     /* Find or create vCPU in array */
 | |
|     if (cpu_index >= last_exec->len) {
 | |
|         expand_last_exec(cpu_index);
 | |
|     }
 | |
|     s = g_ptr_array_index(last_exec, cpu_index);
 | |
| 
 | |
|     /* Print previous instruction in cache */
 | |
|     if (s->len) {
 | |
|         qemu_plugin_outs(s->str);
 | |
|         qemu_plugin_outs("\n");
 | |
|     }
 | |
| 
 | |
|     /* Store new instruction in cache */
 | |
|     /* vcpu_mem will add memory access information to last_exec */
 | |
|     g_string_printf(s, "%u, ", cpu_index);
 | |
|     g_string_append(s, (char *)udata);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * On translation block new translation
 | |
|  *
 | |
|  * QEMU convert code by translation block (TB). By hooking here we can then hook
 | |
|  * a callback on each instruction and memory access.
 | |
|  */
 | |
| static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
 | |
| {
 | |
|     struct qemu_plugin_insn *insn;
 | |
|     bool skip = (imatches || amatches);
 | |
| 
 | |
|     size_t n = qemu_plugin_tb_n_insns(tb);
 | |
|     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.
 | |
|          * `output` is never freed as it might be used multiple times during
 | |
|          * the emulation lifetime.
 | |
|          * We only consider the first 32 bits of the instruction, this may be
 | |
|          * a limitation for CISC architectures.
 | |
|          */
 | |
|         insn = qemu_plugin_tb_get_insn(tb, i);
 | |
|         insn_disas = qemu_plugin_insn_disas(insn);
 | |
|         insn_vaddr = qemu_plugin_insn_vaddr(insn);
 | |
| 
 | |
|         /*
 | |
|          * If we are filtering we better check out if we have any
 | |
|          * hits. The skip "latches" so we can track memory accesses
 | |
|          * 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);
 | |
| 
 | |
|             /* reset skip */
 | |
|             skip = (imatches || amatches);
 | |
|         }
 | |
| 
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * On plugin exit, print last instruction in cache
 | |
|  */
 | |
| static void plugin_exit(qemu_plugin_id_t id, void *p)
 | |
| {
 | |
|     guint i;
 | |
|     GString *s;
 | |
|     for (i = 0; i < last_exec->len; i++) {
 | |
|         s = g_ptr_array_index(last_exec, i);
 | |
|         if (s->str) {
 | |
|             qemu_plugin_outs(s->str);
 | |
|             qemu_plugin_outs("\n");
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* 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
 | |
|  */
 | |
| QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
 | |
|                                            const qemu_info_t *info, int argc,
 | |
|                                            char **argv)
 | |
| {
 | |
|     /*
 | |
|      * Initialize dynamic array to cache vCPU instruction. In user mode
 | |
|      * we don't know the size before emulation.
 | |
|      */
 | |
|     if (info->system_emulation) {
 | |
|         last_exec = g_ptr_array_sized_new(info->system.max_vcpus);
 | |
|     } else {
 | |
|         last_exec = g_ptr_array_new();
 | |
|     }
 | |
| 
 | |
|     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 */
 | |
|     qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
 | |
|     qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
 | |
| 
 | |
|     return 0;
 | |
| }
 |