plugins: move the more involved plugins to contrib
authorAlex Bennée <alex.bennee@linaro.org>
Wed, 9 Sep 2020 11:27:41 +0000 (12:27 +0100)
committerAlex Bennée <alex.bennee@linaro.org>
Thu, 10 Sep 2020 09:47:03 +0000 (10:47 +0100)
We have an exploding complexity problem in the testing so lets just
move the more involved plugins into contrib. tests/plugins still exist
for the basic plugins that exercise the API. We restore the old
pre-meson style Makefile for contrib as it also doubles as a guide for
out-of-tree plugin builds.

While we are at it add some examples to the documentation and a
specific plugins build target.

Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Message-Id: <20200909112742.25730-11-alex.bennee@linaro.org>

16 files changed:
MAINTAINERS
Makefile
configure
contrib/plugins/Makefile [new file with mode: 0644]
contrib/plugins/hotblocks.c [new file with mode: 0644]
contrib/plugins/hotpages.c [new file with mode: 0644]
contrib/plugins/howvec.c [new file with mode: 0644]
contrib/plugins/lockstep.c [new file with mode: 0644]
docs/devel/tcg-plugins.rst
tests/Makefile.include
tests/plugin/hotblocks.c [deleted file]
tests/plugin/hotpages.c [deleted file]
tests/plugin/howvec.c [deleted file]
tests/plugin/lockstep.c [deleted file]
tests/plugin/meson.build
tests/tcg/Makefile.target

index 7d0a5e91e4feabb9e330c4f80ff3621e450d7848..018c4f94a9384985280161c869795b1870bc32c2 100644 (file)
@@ -2745,7 +2745,8 @@ M: Alex Bennée <alex.bennee@linaro.org>
 S: Maintained
 F: docs/devel/tcg-plugins.rst
 F: plugins/
-F: tests/plugin
+F: tests/plugin/
+F: contrib/plugins/
 
 AArch64 TCG target
 M: Richard Henderson <richard.henderson@linaro.org>
index d6c5c9fdef1a915dfc9f5a6448978aaa3d56a954..b63f7dce529d21080d7252888aab4b80f10bde73 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -106,6 +106,12 @@ config-host.mak: $(SRC_PATH)/configure $(SRC_PATH)/pc-bios $(SRC_PATH)/VERSION
 # Force configure to re-run if the API symbols are updated
 ifeq ($(CONFIG_PLUGIN),y)
 config-host.mak: $(SRC_PATH)/plugins/qemu-plugins.symbols
+
+.PHONY: plugins
+plugins:
+       $(call quiet-command,\
+               $(MAKE) $(SUBDIR_MAKEFLAGS) -C contrib/plugins V="$(V)", \
+               "BUILD", "example plugins")
 endif
 
 else
@@ -256,6 +262,11 @@ help:
        $(call print-help,cscope,Generate cscope index)
        $(call print-help,sparse,Run sparse on the QEMU source)
        @echo  ''
+ifeq ($(CONFIG_PLUGIN),y)
+       @echo  'Plugin targets:'
+       $(call print-help,plugins,Build the example TCG plugins)
+       @echo  ''
+endif
        @echo  'Cleaning targets:'
        $(call print-help,clean,Remove most generated files but keep the config)
        $(call print-help,distclean,Remove all generated files)
index 2b5492a0d63f74bd2f93cca883c732e1c5b06899..2b6a1196da504d53254fb74093118c442b4e1f53 100755 (executable)
--- a/configure
+++ b/configure
@@ -7855,6 +7855,7 @@ DIRS="$DIRS tests/qtest tests/qemu-iotests tests/vm tests/fp tests/qgraph"
 DIRS="$DIRS docs docs/interop fsdev scsi"
 DIRS="$DIRS pc-bios/optionrom pc-bios/s390-ccw"
 DIRS="$DIRS roms/seabios"
+DIRS="$DIRS contrib/plugins/"
 LINKS="Makefile"
 LINKS="$LINKS tests/tcg/lm32/Makefile"
 LINKS="$LINKS tests/tcg/Makefile.target"
@@ -7866,6 +7867,7 @@ LINKS="$LINKS .gdbinit scripts" # scripts needed by relative path in .gdbinit
 LINKS="$LINKS tests/acceptance tests/data"
 LINKS="$LINKS tests/qemu-iotests/check"
 LINKS="$LINKS python"
+LINKS="$LINKS contrib/plugins/Makefile "
 UNLINK="pc-bios/keymaps"
 for bios_file in \
     $source_path/pc-bios/*.bin \
diff --git a/contrib/plugins/Makefile b/contrib/plugins/Makefile
new file mode 100644 (file)
index 0000000..7801b08
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- Mode: makefile -*-
+#
+# This Makefile example is fairly independent from the main makefile
+# so users can take and adapt it for their build. We only really
+# include config-host.mak so we don't have to repeat probing for
+# cflags that the main configure has already done for us.
+#
+
+BUILD_DIR := $(CURDIR)/../..
+
+include $(BUILD_DIR)/config-host.mak
+
+VPATH += $(SRC_PATH)/contrib/plugins
+
+NAMES :=
+NAMES += hotblocks
+NAMES += hotpages
+NAMES += howvec
+NAMES += lockstep
+
+SONAMES := $(addsuffix .so,$(addprefix lib,$(NAMES)))
+
+# The main QEMU uses Glib extensively so it's perfectly fine to use it
+# in plugins (which many example do).
+CFLAGS = $(GLIB_CFLAGS)
+CFLAGS += -fPIC
+CFLAGS += $(if $(findstring no-psabi,$(QEMU_CFLAGS)),-Wpsabi)
+CFLAGS += -I$(SRC_PATH)/include/qemu
+
+all: $(SONAMES)
+
+%.o: %.c
+       $(CC) $(CFLAGS) -c -o $@ $<
+
+lib%.so: %.o
+       $(CC) -shared -Wl,-soname,$@ -o $@ $^ $(LDLIBS)
+
+clean:
+       rm -f *.o *.so *.d
+       rm -Rf .libs
+
+.PHONY: all clean
diff --git a/contrib/plugins/hotblocks.c b/contrib/plugins/hotblocks.c
new file mode 100644 (file)
index 0000000..3942a2c
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org>
+ *
+ * License: GNU GPL, version 2 or later.
+ *   See the COPYING file in the top-level directory.
+ */
+#include <inttypes.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <glib.h>
+
+#include <qemu-plugin.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+static bool do_inline;
+
+/* Plugins need to take care of their own locking */
+static GMutex lock;
+static GHashTable *hotblocks;
+static guint64 limit = 20;
+
+/*
+ * Counting Structure
+ *
+ * The internals of the TCG are not exposed to plugins so we can only
+ * get the starting PC for each block. We cheat this slightly by
+ * xor'ing the number of instructions to the hash to help
+ * differentiate.
+ */
+typedef struct {
+    uint64_t start_addr;
+    uint64_t exec_count;
+    int      trans_count;
+    unsigned long insns;
+} ExecCount;
+
+static gint cmp_exec_count(gconstpointer a, gconstpointer b)
+{
+    ExecCount *ea = (ExecCount *) a;
+    ExecCount *eb = (ExecCount *) b;
+    return ea->exec_count > eb->exec_count ? -1 : 1;
+}
+
+static void plugin_exit(qemu_plugin_id_t id, void *p)
+{
+    g_autoptr(GString) report = g_string_new("collected ");
+    GList *counts, *it;
+    int i;
+
+    g_mutex_lock(&lock);
+    g_string_append_printf(report, "%d entries in the hash table\n",
+                           g_hash_table_size(hotblocks));
+    counts = g_hash_table_get_values(hotblocks);
+    it = g_list_sort(counts, cmp_exec_count);
+
+    if (it) {
+        g_string_append_printf(report, "pc, tcount, icount, ecount\n");
+
+        for (i = 0; i < limit && it->next; i++, it = it->next) {
+            ExecCount *rec = (ExecCount *) it->data;
+            g_string_append_printf(report, "%#016"PRIx64", %d, %ld, %"PRId64"\n",
+                                   rec->start_addr, rec->trans_count,
+                                   rec->insns, rec->exec_count);
+        }
+
+        g_list_free(it);
+        g_mutex_unlock(&lock);
+    }
+
+    qemu_plugin_outs(report->str);
+}
+
+static void plugin_init(void)
+{
+    hotblocks = g_hash_table_new(NULL, g_direct_equal);
+}
+
+static void vcpu_tb_exec(unsigned int cpu_index, void *udata)
+{
+    ExecCount *cnt;
+    uint64_t hash = (uint64_t) udata;
+
+    g_mutex_lock(&lock);
+    cnt = (ExecCount *) g_hash_table_lookup(hotblocks, (gconstpointer) hash);
+    /* should always succeed */
+    g_assert(cnt);
+    cnt->exec_count++;
+    g_mutex_unlock(&lock);
+}
+
+/*
+ * When do_inline we ask the plugin to increment the counter for us.
+ * Otherwise a helper is inserted which calls the vcpu_tb_exec
+ * callback.
+ */
+static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+    ExecCount *cnt;
+    uint64_t pc = qemu_plugin_tb_vaddr(tb);
+    unsigned long insns = qemu_plugin_tb_n_insns(tb);
+    uint64_t hash = pc ^ insns;
+
+    g_mutex_lock(&lock);
+    cnt = (ExecCount *) g_hash_table_lookup(hotblocks, (gconstpointer) hash);
+    if (cnt) {
+        cnt->trans_count++;
+    } else {
+        cnt = g_new0(ExecCount, 1);
+        cnt->start_addr = pc;
+        cnt->trans_count = 1;
+        cnt->insns = insns;
+        g_hash_table_insert(hotblocks, (gpointer) hash, (gpointer) cnt);
+    }
+
+    g_mutex_unlock(&lock);
+
+    if (do_inline) {
+        qemu_plugin_register_vcpu_tb_exec_inline(tb, QEMU_PLUGIN_INLINE_ADD_U64,
+                                                 &cnt->exec_count, 1);
+    } else {
+        qemu_plugin_register_vcpu_tb_exec_cb(tb, vcpu_tb_exec,
+                                             QEMU_PLUGIN_CB_NO_REGS,
+                                             (void *)hash);
+    }
+}
+
+QEMU_PLUGIN_EXPORT
+int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
+                        int argc, char **argv)
+{
+    if (argc && strcmp(argv[0], "inline") == 0) {
+        do_inline = true;
+    }
+
+    plugin_init();
+
+    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
+    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
+    return 0;
+}
diff --git a/contrib/plugins/hotpages.c b/contrib/plugins/hotpages.c
new file mode 100644 (file)
index 0000000..ecd6c18
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org>
+ *
+ * Hot Pages - show which pages saw the most memory accesses.
+ *
+ * License: GNU GPL, version 2 or later.
+ *   See the COPYING file in the top-level directory.
+ */
+
+#include <inttypes.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <glib.h>
+
+#include <qemu-plugin.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+static uint64_t page_size = 4096;
+static uint64_t page_mask;
+static int limit = 50;
+static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW;
+static bool track_io;
+
+enum sort_type {
+    SORT_RW = 0,
+    SORT_R,
+    SORT_W,
+    SORT_A
+};
+
+static int sort_by = SORT_RW;
+
+typedef struct {
+    uint64_t page_address;
+    int cpu_read;
+    int cpu_write;
+    uint64_t reads;
+    uint64_t writes;
+} PageCounters;
+
+static GMutex lock;
+static GHashTable *pages;
+
+static gint cmp_access_count(gconstpointer a, gconstpointer b)
+{
+    PageCounters *ea = (PageCounters *) a;
+    PageCounters *eb = (PageCounters *) b;
+    int r;
+    switch (sort_by) {
+    case SORT_RW:
+        r = (ea->reads + ea->writes) > (eb->reads + eb->writes) ? -1 : 1;
+        break;
+    case SORT_R:
+        r = ea->reads > eb->reads ? -1 : 1;
+        break;
+    case SORT_W:
+        r = ea->writes > eb->writes ? -1 : 1;
+        break;
+    case SORT_A:
+        r = ea->page_address > eb->page_address ? -1 : 1;
+        break;
+    default:
+        g_assert_not_reached();
+    }
+    return r;
+}
+
+
+static void plugin_exit(qemu_plugin_id_t id, void *p)
+{
+    g_autoptr(GString) report = g_string_new("Addr, RCPUs, Reads, WCPUs, Writes\n");
+    int i;
+    GList *counts;
+
+    counts = g_hash_table_get_values(pages);
+    if (counts && g_list_next(counts)) {
+        GList *it;
+
+        it = g_list_sort(counts, cmp_access_count);
+
+        for (i = 0; i < limit && it->next; i++, it = it->next) {
+            PageCounters *rec = (PageCounters *) it->data;
+            g_string_append_printf(report,
+                                   "%#016"PRIx64", 0x%04x, %"PRId64
+                                   ", 0x%04x, %"PRId64"\n",
+                                   rec->page_address,
+                                   rec->cpu_read, rec->reads,
+                                   rec->cpu_write, rec->writes);
+        }
+        g_list_free(it);
+    }
+
+    qemu_plugin_outs(report->str);
+}
+
+static void plugin_init(void)
+{
+    page_mask = (page_size - 1);
+    pages = g_hash_table_new(NULL, g_direct_equal);
+}
+
+static void vcpu_haddr(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo,
+                       uint64_t vaddr, void *udata)
+{
+    struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(meminfo, vaddr);
+    uint64_t page;
+    PageCounters *count;
+
+    /* We only get a hwaddr for system emulation */
+    if (track_io) {
+        if (hwaddr && qemu_plugin_hwaddr_is_io(hwaddr)) {
+            page = vaddr;
+        } else {
+            return;
+        }
+    } else {
+        if (hwaddr && !qemu_plugin_hwaddr_is_io(hwaddr)) {
+            page = (uint64_t) qemu_plugin_hwaddr_device_offset(hwaddr);
+        } else {
+            page = vaddr;
+        }
+    }
+    page &= ~page_mask;
+
+    g_mutex_lock(&lock);
+    count = (PageCounters *) g_hash_table_lookup(pages, GUINT_TO_POINTER(page));
+
+    if (!count) {
+        count = g_new0(PageCounters, 1);
+        count->page_address = page;
+        g_hash_table_insert(pages, GUINT_TO_POINTER(page), (gpointer) count);
+    }
+    if (qemu_plugin_mem_is_store(meminfo)) {
+        count->writes++;
+        count->cpu_write |= (1 << cpu_index);
+    } else {
+        count->reads++;
+        count->cpu_read |= (1 << cpu_index);
+    }
+
+    g_mutex_unlock(&lock);
+}
+
+static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+    size_t n = qemu_plugin_tb_n_insns(tb);
+    size_t i;
+
+    for (i = 0; i < n; i++) {
+        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+        qemu_plugin_register_vcpu_mem_cb(insn, vcpu_haddr,
+                                         QEMU_PLUGIN_CB_NO_REGS,
+                                         rw, NULL);
+    }
+}
+
+QEMU_PLUGIN_EXPORT
+int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
+                        int argc, char **argv)
+{
+    int i;
+
+    for (i = 0; i < argc; i++) {
+        char *opt = argv[i];
+        if (g_strcmp0(opt, "reads") == 0) {
+            sort_by = SORT_R;
+        } else if (g_strcmp0(opt, "writes") == 0) {
+            sort_by = SORT_W;
+        } else if (g_strcmp0(opt, "address") == 0) {
+            sort_by = SORT_A;
+        } else if (g_strcmp0(opt, "io") == 0) {
+            track_io = true;
+        } else if (g_str_has_prefix(opt, "pagesize=")) {
+            page_size = g_ascii_strtoull(opt + 9, NULL, 10);
+        } else {
+            fprintf(stderr, "option parsing failed: %s\n", opt);
+            return -1;
+        }
+    }
+
+    plugin_init();
+
+    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
+    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
+    return 0;
+}
diff --git a/contrib/plugins/howvec.c b/contrib/plugins/howvec.c
new file mode 100644 (file)
index 0000000..3b9a693
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org>
+ *
+ * How vectorised is this code?
+ *
+ * Attempt to measure the amount of vectorisation that has been done
+ * on some code by counting classes of instruction.
+ *
+ * License: GNU GPL, version 2 or later.
+ *   See the COPYING file in the top-level directory.
+ */
+#include <inttypes.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <glib.h>
+
+#include <qemu-plugin.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+typedef enum {
+    COUNT_CLASS,
+    COUNT_INDIVIDUAL,
+    COUNT_NONE
+} CountType;
+
+static int limit = 50;
+static bool do_inline;
+static bool verbose;
+
+static GMutex lock;
+static GHashTable *insns;
+
+typedef struct {
+    const char *class;
+    const char *opt;
+    uint32_t mask;
+    uint32_t pattern;
+    CountType what;
+    uint64_t count;
+} InsnClassExecCount;
+
+typedef struct {
+    char *insn;
+    uint32_t opcode;
+    uint64_t count;
+    InsnClassExecCount *class;
+} InsnExecCount;
+
+/*
+ * Matchers for classes of instructions, order is important.
+ *
+ * Your most precise match must be before looser matches. If no match
+ * is found in the table we can create an individual entry.
+ *
+ * 31..28 27..24 23..20 19..16 15..12 11..8 7..4 3..0
+ */
+static InsnClassExecCount aarch64_insn_classes[] = {
+    /* "Reserved"" */
+    { "  UDEF",              "udef",   0xffff0000, 0x00000000, COUNT_NONE},
+    { "  SVE",               "sve",    0x1e000000, 0x04000000, COUNT_CLASS},
+    { "Reserved",            "res",    0x1e000000, 0x00000000, COUNT_CLASS},
+    /* Data Processing Immediate */
+    { "  PCrel addr",        "pcrel",  0x1f000000, 0x10000000, COUNT_CLASS},
+    { "  Add/Sub (imm,tags)","asit",   0x1f800000, 0x11800000, COUNT_CLASS},
+    { "  Add/Sub (imm)",     "asi",    0x1f000000, 0x11000000, COUNT_CLASS},
+    { "  Logical (imm)",     "logi",   0x1f800000, 0x12000000, COUNT_CLASS},
+    { "  Move Wide (imm)",   "movwi",  0x1f800000, 0x12800000, COUNT_CLASS},
+    { "  Bitfield",          "bitf",   0x1f800000, 0x13000000, COUNT_CLASS},
+    { "  Extract",           "extr",   0x1f800000, 0x13800000, COUNT_CLASS},
+    { "Data Proc Imm",       "dpri",   0x1c000000, 0x10000000, COUNT_CLASS},
+    /* Branches */
+    { "  Cond Branch (imm)", "cndb",   0xfe000000, 0x54000000, COUNT_CLASS},
+    { "  Exception Gen",     "excp",   0xff000000, 0xd4000000, COUNT_CLASS},
+    { "    NOP",             "nop",    0xffffffff, 0xd503201f, COUNT_NONE},
+    { "  Hints",             "hint",   0xfffff000, 0xd5032000, COUNT_CLASS},
+    { "  Barriers",          "barr",   0xfffff000, 0xd5033000, COUNT_CLASS},
+    { "  PSTATE",            "psta",   0xfff8f000, 0xd5004000, COUNT_CLASS},
+    { "  System Insn",       "sins",   0xffd80000, 0xd5080000, COUNT_CLASS},
+    { "  System Reg",        "sreg",   0xffd00000, 0xd5100000, COUNT_CLASS},
+    { "  Branch (reg)",      "breg",   0xfe000000, 0xd6000000, COUNT_CLASS},
+    { "  Branch (imm)",      "bimm",   0x7c000000, 0x14000000, COUNT_CLASS},
+    { "  Cmp & Branch",      "cmpb",   0x7e000000, 0x34000000, COUNT_CLASS},
+    { "  Tst & Branch",      "tstb",   0x7e000000, 0x36000000, COUNT_CLASS},
+    { "Branches",            "branch", 0x1c000000, 0x14000000, COUNT_CLASS},
+    /* Loads and Stores */
+    { "  AdvSimd ldstmult",  "advlsm", 0xbfbf0000, 0x0c000000, COUNT_CLASS},
+    { "  AdvSimd ldstmult++","advlsmp",0xbfb00000, 0x0c800000, COUNT_CLASS},
+    { "  AdvSimd ldst",      "advlss", 0xbf9f0000, 0x0d000000, COUNT_CLASS},
+    { "  AdvSimd ldst++",    "advlssp",0xbf800000, 0x0d800000, COUNT_CLASS},
+    { "  ldst excl",         "ldstx",  0x3f000000, 0x08000000, COUNT_CLASS},
+    { "    Prefetch",        "prfm",   0xff000000, 0xd8000000, COUNT_CLASS},
+    { "  Load Reg (lit)",    "ldlit",  0x1b000000, 0x18000000, COUNT_CLASS},
+    { "  ldst noalloc pair", "ldstnap",0x3b800000, 0x28000000, COUNT_CLASS},
+    { "  ldst pair",         "ldstp",  0x38000000, 0x28000000, COUNT_CLASS},
+    { "  ldst reg",          "ldstr",  0x3b200000, 0x38000000, COUNT_CLASS},
+    { "  Atomic ldst",       "atomic", 0x3b200c00, 0x38200000, COUNT_CLASS},
+    { "  ldst reg (reg off)","ldstro", 0x3b200b00, 0x38200800, COUNT_CLASS},
+    { "  ldst reg (pac)",    "ldstpa", 0x3b200200, 0x38200800, COUNT_CLASS},
+    { "  ldst reg (imm)",    "ldsti",  0x3b000000, 0x39000000, COUNT_CLASS},
+    { "Loads & Stores",      "ldst",   0x0a000000, 0x08000000, COUNT_CLASS},
+    /* Data Processing Register */
+    { "Data Proc Reg",       "dprr",   0x0e000000, 0x0a000000, COUNT_CLASS},
+    /* Scalar FP */
+    { "Scalar FP ",          "fpsimd", 0x0e000000, 0x0e000000, COUNT_CLASS},
+    /* Unclassified */
+    { "Unclassified",        "unclas", 0x00000000, 0x00000000, COUNT_CLASS},
+};
+
+static InsnClassExecCount sparc32_insn_classes[] = {
+    { "Call",                "call",   0xc0000000, 0x40000000, COUNT_CLASS},
+    { "Branch ICond",        "bcc",    0xc1c00000, 0x00800000, COUNT_CLASS},
+    { "Branch Fcond",        "fbcc",   0xc1c00000, 0x01800000, COUNT_CLASS},
+    { "SetHi",               "sethi",  0xc1c00000, 0x01000000, COUNT_CLASS},
+    { "FPU ALU",             "fpu",    0xc1f00000, 0x81a00000, COUNT_CLASS},
+    { "ALU",                 "alu",    0xc0000000, 0x80000000, COUNT_CLASS},
+    { "Load/Store",          "ldst",   0xc0000000, 0xc0000000, COUNT_CLASS},
+    /* Unclassified */
+    { "Unclassified",        "unclas", 0x00000000, 0x00000000, COUNT_INDIVIDUAL},
+};
+
+static InsnClassExecCount sparc64_insn_classes[] = {
+    { "SetHi & Branches",     "op0",   0xc0000000, 0x00000000, COUNT_CLASS},
+    { "Call",                 "op1",   0xc0000000, 0x40000000, COUNT_CLASS},
+    { "Arith/Logical/Move",   "op2",   0xc0000000, 0x80000000, COUNT_CLASS},
+    { "Arith/Logical/Move",   "op3",   0xc0000000, 0xc0000000, COUNT_CLASS},
+    /* Unclassified */
+    { "Unclassified",        "unclas", 0x00000000, 0x00000000, COUNT_INDIVIDUAL},
+};
+
+/* Default matcher for currently unclassified architectures */
+static InsnClassExecCount default_insn_classes[] = {
+    { "Unclassified",        "unclas", 0x00000000, 0x00000000, COUNT_INDIVIDUAL},
+};
+
+typedef struct {
+    const char *qemu_target;
+    InsnClassExecCount *table;
+    int table_sz;
+} ClassSelector;
+
+static ClassSelector class_tables[] =
+{
+    { "aarch64", aarch64_insn_classes, ARRAY_SIZE(aarch64_insn_classes) },
+    { "sparc",   sparc32_insn_classes, ARRAY_SIZE(sparc32_insn_classes) },
+    { "sparc64", sparc64_insn_classes, ARRAY_SIZE(sparc64_insn_classes) },
+    { NULL, default_insn_classes, ARRAY_SIZE(default_insn_classes) },
+};
+
+static InsnClassExecCount *class_table;
+static int class_table_sz;
+
+static gint cmp_exec_count(gconstpointer a, gconstpointer b)
+{
+    InsnExecCount *ea = (InsnExecCount *) a;
+    InsnExecCount *eb = (InsnExecCount *) b;
+    return ea->count > eb->count ? -1 : 1;
+}
+
+static void free_record(gpointer data)
+{
+    InsnExecCount *rec = (InsnExecCount *) data;
+    g_free(rec->insn);
+    g_free(rec);
+}
+
+static void plugin_exit(qemu_plugin_id_t id, void *p)
+{
+    g_autoptr(GString) report = g_string_new("Instruction Classes:\n");
+    int i;
+    GList *counts;
+    InsnClassExecCount *class = NULL;
+
+    for (i = 0; i < class_table_sz; i++) {
+        class = &class_table[i];
+        switch (class->what) {
+        case COUNT_CLASS:
+            if (class->count || verbose) {
+                g_string_append_printf(report, "Class: %-24s\t(%ld hits)\n",
+                                       class->class,
+                                       class->count);
+            }
+            break;
+        case COUNT_INDIVIDUAL:
+            g_string_append_printf(report, "Class: %-24s\tcounted individually\n",
+                                   class->class);
+            break;
+        case COUNT_NONE:
+            g_string_append_printf(report, "Class: %-24s\tnot counted\n",
+                                   class->class);
+            break;
+        default:
+            break;
+        }
+    }
+
+    counts = g_hash_table_get_values(insns);
+    if (counts && g_list_next(counts)) {
+        g_string_append_printf(report,"Individual Instructions:\n");
+        counts = g_list_sort(counts, cmp_exec_count);
+
+        for (i = 0; i < limit && g_list_next(counts);
+             i++, counts = g_list_next(counts)) {
+            InsnExecCount *rec = (InsnExecCount *) counts->data;
+            g_string_append_printf(report,
+                                   "Instr: %-24s\t(%ld hits)\t(op=%#08x/%s)\n",
+                                   rec->insn,
+                                   rec->count,
+                                   rec->opcode,
+                                   rec->class ?
+                                   rec->class->class : "un-categorised");
+        }
+        g_list_free(counts);
+    }
+
+    g_hash_table_destroy(insns);
+
+    qemu_plugin_outs(report->str);
+}
+
+static void plugin_init(void)
+{
+    insns = g_hash_table_new_full(NULL, g_direct_equal, NULL, &free_record);
+}
+
+static void vcpu_insn_exec_before(unsigned int cpu_index, void *udata)
+{
+    uint64_t *count = (uint64_t *) udata;
+    (*count)++;
+}
+
+static uint64_t * find_counter(struct qemu_plugin_insn *insn)
+{
+    int i;
+    uint64_t *cnt = NULL;
+    uint32_t opcode;
+    InsnClassExecCount *class = NULL;
+
+    /*
+     * We only match the first 32 bits of the instruction which is
+     * fine for most RISCs but a bit limiting for CISC architectures.
+     * They would probably benefit from a more tailored plugin.
+     * However we can fall back to individual instruction counting.
+     */
+    opcode = *((uint32_t *)qemu_plugin_insn_data(insn));
+
+    for (i = 0; !cnt && i < class_table_sz; i++) {
+        class = &class_table[i];
+        uint32_t masked_bits = opcode & class->mask;
+        if (masked_bits == class->pattern) {
+            break;
+        }
+    }
+
+    g_assert(class);
+
+    switch (class->what) {
+    case COUNT_NONE:
+        return NULL;
+    case COUNT_CLASS:
+        return &class->count;
+    case COUNT_INDIVIDUAL:
+    {
+        InsnExecCount *icount;
+
+        g_mutex_lock(&lock);
+        icount = (InsnExecCount *) g_hash_table_lookup(insns,
+                                                       GUINT_TO_POINTER(opcode));
+
+        if (!icount) {
+            icount = g_new0(InsnExecCount, 1);
+            icount->opcode = opcode;
+            icount->insn = qemu_plugin_insn_disas(insn);
+            icount->class = class;
+
+            g_hash_table_insert(insns, GUINT_TO_POINTER(opcode),
+                                (gpointer) icount);
+        }
+        g_mutex_unlock(&lock);
+
+        return &icount->count;
+    }
+    default:
+        g_assert_not_reached();
+    }
+
+    return NULL;
+}
+
+static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+    size_t n = qemu_plugin_tb_n_insns(tb);
+    size_t i;
+
+    for (i = 0; i < n; i++) {
+        uint64_t *cnt;
+        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
+        cnt = find_counter(insn);
+
+        if (cnt) {
+            if (do_inline) {
+                qemu_plugin_register_vcpu_insn_exec_inline(
+                    insn, QEMU_PLUGIN_INLINE_ADD_U64, cnt, 1);
+            } else {
+                qemu_plugin_register_vcpu_insn_exec_cb(
+                    insn, vcpu_insn_exec_before, QEMU_PLUGIN_CB_NO_REGS, cnt);
+            }
+        }
+    }
+}
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+                                           const qemu_info_t *info,
+                                           int argc, char **argv)
+{
+    int i;
+
+    /* Select a class table appropriate to the guest architecture */
+    for (i = 0; i < ARRAY_SIZE(class_tables); i++) {
+        ClassSelector *entry = &class_tables[i];
+        if (!entry->qemu_target ||
+            strcmp(entry->qemu_target, info->target_name) == 0) {
+            class_table = entry->table;
+            class_table_sz = entry->table_sz;
+            break;
+        }
+    }
+
+    for (i = 0; i < argc; i++) {
+        char *p = argv[i];
+        if (strcmp(p, "inline") == 0) {
+            do_inline = true;
+        } else if (strcmp(p, "verbose") == 0) {
+            verbose = true;
+        } else {
+            int j;
+            CountType type = COUNT_INDIVIDUAL;
+            if (*p == '!') {
+                type = COUNT_NONE;
+                p++;
+            }
+            for (j = 0; j < class_table_sz; j++) {
+                if (strcmp(p, class_table[j].opt) == 0) {
+                    class_table[j].what = type;
+                    break;
+                }
+            }
+        }
+    }
+
+    plugin_init();
+
+    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
+    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
+    return 0;
+}
diff --git a/contrib/plugins/lockstep.c b/contrib/plugins/lockstep.c
new file mode 100644 (file)
index 0000000..a696673
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+ * Lockstep Execution Plugin
+ *
+ * Allows you to execute two QEMU instances in lockstep and report
+ * when their execution diverges. This is mainly useful for developers
+ * who want to see where a change to TCG code generation has
+ * introduced a subtle and hard to find bug.
+ *
+ * Caveats:
+ *   - single-threaded linux-user apps only with non-deterministic syscalls
+ *   - no MTTCG enabled system emulation (icount may help)
+ *
+ * While icount makes things more deterministic it doesn't mean a
+ * particular run may execute the exact same sequence of blocks. An
+ * asynchronous event (for example X11 graphics update) may cause a
+ * block to end early and a new partial block to start. This means
+ * serial only test cases are a better bet. -d nochain may also help.
+ *
+ * This code is not thread safe!
+ *
+ * Copyright (c) 2020 Linaro Ltd
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <glib.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <qemu-plugin.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+/* saved so we can uninstall later */
+static qemu_plugin_id_t our_id;
+
+static unsigned long bb_count;
+static unsigned long insn_count;
+
+/* Information about a translated block */
+typedef struct {
+    uint64_t pc;
+    uint64_t insns;
+} BlockInfo;
+
+/* Information about an execution state in the log */
+typedef struct {
+    BlockInfo *block;
+    unsigned long insn_count;
+    unsigned long block_count;
+} ExecInfo;
+
+/* The execution state we compare */
+typedef struct {
+    uint64_t pc;
+    unsigned long insn_count;
+} ExecState;
+
+typedef struct {
+    GSList *log_pos;
+    int distance;
+} DivergeState;
+
+/* list of translated block info */
+static GSList *blocks;
+
+/* execution log and points of divergence */
+static GSList *log, *divergence_log;
+
+static int socket_fd;
+static char *path_to_unlink;
+
+static bool verbose;
+
+static void plugin_cleanup(qemu_plugin_id_t id)
+{
+    /* Free our block data */
+    g_slist_free_full(blocks, &g_free);
+    g_slist_free_full(log, &g_free);
+    g_slist_free(divergence_log);
+
+    close(socket_fd);
+    if (path_to_unlink) {
+        unlink(path_to_unlink);
+    }
+}
+
+static void plugin_exit(qemu_plugin_id_t id, void *p)
+{
+    g_autoptr(GString) out = g_string_new("No divergence :-)\n");
+    g_string_append_printf(out, "Executed %ld/%d blocks\n",
+                           bb_count, g_slist_length(log));
+    g_string_append_printf(out, "Executed ~%ld instructions\n", insn_count);
+    qemu_plugin_outs(out->str);
+
+    plugin_cleanup(id);
+}
+
+static void report_divergance(ExecState *us, ExecState *them)
+{
+    DivergeState divrec = { log, 0 };
+    g_autoptr(GString) out = g_string_new("");
+    bool diverged = false;
+
+    /*
+     * If we have diverged before did we get back on track or are we
+     * totally loosing it?
+     */
+    if (divergence_log) {
+        DivergeState *last = (DivergeState *) divergence_log->data;
+        GSList *entry;
+
+        for (entry = log; g_slist_next(entry); entry = g_slist_next(entry)) {
+            if (entry == last->log_pos) {
+                break;
+            }
+            divrec.distance++;
+        }
+
+        /*
+         * If the last two records are so close it is likely we will
+         * not recover synchronisation with the other end.
+         */
+        if (divrec.distance == 1 && last->distance == 1) {
+            diverged = true;
+        }
+    }
+    divergence_log = g_slist_prepend(divergence_log,
+                                     g_memdup(&divrec, sizeof(divrec)));
+
+    /* Output short log entry of going out of sync... */
+    if (verbose || divrec.distance == 1 || diverged) {
+        g_string_printf(out, "@ %#016lx vs %#016lx (%d/%d since last)\n",
+                        us->pc, them->pc, g_slist_length(divergence_log),
+                        divrec.distance);
+        qemu_plugin_outs(out->str);
+    }
+
+    if (diverged) {
+        int i;
+        GSList *entry;
+
+        g_string_printf(out, "Δ insn_count @ %#016lx (%ld) vs %#016lx (%ld)\n",
+                        us->pc, us->insn_count, them->pc, them->insn_count);
+
+        for (entry = log, i = 0;
+             g_slist_next(entry) && i < 5;
+             entry = g_slist_next(entry), i++) {
+            ExecInfo *prev = (ExecInfo *) entry->data;
+            g_string_append_printf(out,
+                                   "  previously @ %#016lx/%ld (%ld insns)\n",
+                                   prev->block->pc, prev->block->insns,
+                                   prev->insn_count);
+        }
+        qemu_plugin_outs(out->str);
+        qemu_plugin_outs("too much divergence... giving up.");
+        qemu_plugin_uninstall(our_id, plugin_cleanup);
+    }
+}
+
+static void vcpu_tb_exec(unsigned int cpu_index, void *udata)
+{
+    BlockInfo *bi = (BlockInfo *) udata;
+    ExecState us, them;
+    ssize_t bytes;
+    ExecInfo *exec;
+
+    us.pc = bi->pc;
+    us.insn_count = insn_count;
+
+    /*
+     * Write our current position to the other end. If we fail the
+     * other end has probably died and we should shut down gracefully.
+     */
+    bytes = write(socket_fd, &us, sizeof(ExecState));
+    if (bytes < sizeof(ExecState)) {
+        qemu_plugin_outs(bytes < 0 ?
+                         "problem writing to socket" :
+                         "wrote less than expected to socket");
+        qemu_plugin_uninstall(our_id, plugin_cleanup);
+        return;
+    }
+
+    /*
+     * Now read where our peer has reached. Again a failure probably
+     * indicates the other end died and we should close down cleanly.
+     */
+    bytes = read(socket_fd, &them, sizeof(ExecState));
+    if (bytes < sizeof(ExecState)) {
+        qemu_plugin_outs(bytes < 0 ?
+                         "problem reading from socket" :
+                         "read less than expected");
+        qemu_plugin_uninstall(our_id, plugin_cleanup);
+        return;
+    }
+
+    /*
+     * Compare and report if we have diverged.
+     */
+    if (us.pc != them.pc) {
+        report_divergance(&us, &them);
+    }
+
+    /*
+     * Assume this block will execute fully and record it
+     * in the execution log.
+     */
+    insn_count += bi->insns;
+    bb_count++;
+    exec = g_new0(ExecInfo, 1);
+    exec->block = bi;
+    exec->insn_count = insn_count;
+    exec->block_count = bb_count;
+    log = g_slist_prepend(log, exec);
+}
+
+static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
+{
+    BlockInfo *bi = g_new0(BlockInfo, 1);
+    bi->pc = qemu_plugin_tb_vaddr(tb);
+    bi->insns = qemu_plugin_tb_n_insns(tb);
+
+    /* save a reference so we can free later */
+    blocks = g_slist_prepend(blocks, bi);
+    qemu_plugin_register_vcpu_tb_exec_cb(tb, vcpu_tb_exec,
+                                         QEMU_PLUGIN_CB_NO_REGS, (void *)bi);
+}
+
+
+/*
+ * Instead of encoding master/slave status into what is essentially
+ * two peers we shall just take the simple approach of checking for
+ * the existence of the pipe and assuming if it's not there we are the
+ * first process.
+ */
+static bool setup_socket(const char *path)
+{
+    struct sockaddr_un sockaddr;
+    int fd;
+
+    fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (fd < 0) {
+        perror("create socket");
+        return false;
+    }
+
+    sockaddr.sun_family = AF_UNIX;
+    g_strlcpy(sockaddr.sun_path, path, sizeof(sockaddr.sun_path) - 1);
+    if (bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
+        perror("bind socket");
+        close(fd);
+        return false;
+    }
+
+    /* remember to clean-up */
+    path_to_unlink = g_strdup(path);
+
+    if (listen(fd, 1) < 0) {
+        perror("listen socket");
+        close(fd);
+        return false;
+    }
+
+    socket_fd = accept(fd, NULL, NULL);
+    if (socket_fd < 0 && errno != EINTR) {
+        perror("accept socket");
+        return false;
+    }
+
+    qemu_plugin_outs("setup_socket::ready\n");
+
+    return true;
+}
+
+static bool connect_socket(const char *path)
+{
+    int fd;
+    struct sockaddr_un sockaddr;
+
+    fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (fd < 0) {
+        perror("create socket");
+        return false;
+    }
+
+    sockaddr.sun_family = AF_UNIX;
+    g_strlcpy(sockaddr.sun_path, path, sizeof(sockaddr.sun_path) - 1);
+
+    if (connect(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
+        perror("failed to connect");
+        return false;
+    }
+
+    qemu_plugin_outs("connect_socket::ready\n");
+
+    socket_fd = fd;
+    return true;
+}
+
+static bool setup_unix_socket(const char *path)
+{
+    if (g_file_test(path, G_FILE_TEST_EXISTS)) {
+        return connect_socket(path);
+    } else {
+        return setup_socket(path);
+    }
+}
+
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+                                           const qemu_info_t *info,
+                                           int argc, char **argv)
+{
+    int i;
+
+    if (!argc || !argv[0]) {
+        qemu_plugin_outs("Need a socket path to talk to other instance.");
+        return -1;
+    }
+
+    for (i = 0; i < argc; i++) {
+        char *p = argv[i];
+        if (strcmp(p, "verbose") == 0) {
+            verbose = true;
+        } else if (!setup_unix_socket(argv[0])) {
+            qemu_plugin_outs("Failed to setup socket for communications.");
+            return -1;
+        }
+    }
+
+    our_id = id;
+
+    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
+    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
+    return 0;
+}
index a05990906cc09b9b764858dc0fa0fbf9d50b373f..0568dfa6a49d9c18132b9ba27a5ecb3f84544b53 100644 (file)
@@ -134,3 +134,149 @@ longer want to instrument the code. This operation is asynchronous
 which means callbacks may still occur after the uninstall operation is
 requested. The plugin isn't completely uninstalled until the safe work
 has executed while all vCPUs are quiescent.
+
+Example Plugins
+===============
+
+There are a number of plugins included with QEMU and you are
+encouraged to contribute your own plugins plugins upstream. There is a
+`contrib/plugins` directory where they can go.
+
+- tests/plugins
+
+These are some basic plugins that are used to test and exercise the
+API during the `make check-tcg` target.
+
+- contrib/plugins/hotblocks.c
+
+The hotblocks plugin allows you to examine the where hot paths of
+execution are in your program. Once the program has finished you will
+get a sorted list of blocks reporting the starting PC, translation
+count, number of instructions and execution count. This will work best
+with linux-user execution as system emulation tends to generate
+re-translations as blocks from different programs get swapped in and
+out of system memory.
+
+If your program is single-threaded you can use the `inline` option for
+slightly faster (but not thread safe) counters.
+
+Example::
+
+  ./aarch64-linux-user/qemu-aarch64 \
+    -plugin contrib/plugins/libhotblocks.so -d plugin \
+    ./tests/tcg/aarch64-linux-user/sha1
+  SHA1=15dd99a1991e0b3826fede3deffc1feba42278e6
+  collected 903 entries in the hash table
+  pc, tcount, icount, ecount
+  0x0000000041ed10, 1, 5, 66087
+  0x000000004002b0, 1, 4, 66087
+  ...
+
+- contrib/plugins/hotpages.c
+
+Similar to hotblocks but this time tracks memory accesses::
+
+  ./aarch64-linux-user/qemu-aarch64 \
+    -plugin contrib/plugins/libhotpages.so -d plugin \
+    ./tests/tcg/aarch64-linux-user/sha1
+  SHA1=15dd99a1991e0b3826fede3deffc1feba42278e6
+  Addr, RCPUs, Reads, WCPUs, Writes
+  0x000055007fe000, 0x0001, 31747952, 0x0001, 8835161
+  0x000055007ff000, 0x0001, 29001054, 0x0001, 8780625
+  0x00005500800000, 0x0001, 687465, 0x0001, 335857
+  0x0000000048b000, 0x0001, 130594, 0x0001, 355
+  0x0000000048a000, 0x0001, 1826, 0x0001, 11
+
+- contrib/plugins/howvec.c
+
+This is an instruction classifier so can be used to count different
+types of instructions. It has a number of options to refine which get
+counted. You can give an argument for a class of instructions to break
+it down fully, so for example to see all the system registers
+accesses::
+
+  ./aarch64-softmmu/qemu-system-aarch64 $(QEMU_ARGS) \
+    -append "root=/dev/sda2 systemd.unit=benchmark.service" \
+    -smp 4 -plugin ./contrib/plugins/libhowvec.so,arg=sreg -d plugin
+
+which will lead to a sorted list after the class breakdown::
+
+  Instruction Classes:
+  Class:   UDEF                   not counted
+  Class:   SVE                    (68 hits)
+  Class:   PCrel addr             (47789483 hits)
+  Class:   Add/Sub (imm)          (192817388 hits)
+  Class:   Logical (imm)          (93852565 hits)
+  Class:   Move Wide (imm)        (76398116 hits)
+  Class:   Bitfield               (44706084 hits)
+  Class:   Extract                (5499257 hits)
+  Class:   Cond Branch (imm)      (147202932 hits)
+  Class:   Exception Gen          (193581 hits)
+  Class:     NOP                  not counted
+  Class:   Hints                  (6652291 hits)
+  Class:   Barriers               (8001661 hits)
+  Class:   PSTATE                 (1801695 hits)
+  Class:   System Insn            (6385349 hits)
+  Class:   System Reg             counted individually
+  Class:   Branch (reg)           (69497127 hits)
+  Class:   Branch (imm)           (84393665 hits)
+  Class:   Cmp & Branch           (110929659 hits)
+  Class:   Tst & Branch           (44681442 hits)
+  Class:   AdvSimd ldstmult       (736 hits)
+  Class:   ldst excl              (9098783 hits)
+  Class:   Load Reg (lit)         (87189424 hits)
+  Class:   ldst noalloc pair      (3264433 hits)
+  Class:   ldst pair              (412526434 hits)
+  Class:   ldst reg (imm)         (314734576 hits)
+  Class: Loads & Stores           (2117774 hits)
+  Class: Data Proc Reg            (223519077 hits)
+  Class: Scalar FP                (31657954 hits)
+  Individual Instructions:
+  Instr: mrs x0, sp_el0           (2682661 hits)  (op=0xd5384100/  System Reg)
+  Instr: mrs x1, tpidr_el2        (1789339 hits)  (op=0xd53cd041/  System Reg)
+  Instr: mrs x2, tpidr_el2        (1513494 hits)  (op=0xd53cd042/  System Reg)
+  Instr: mrs x0, tpidr_el2        (1490823 hits)  (op=0xd53cd040/  System Reg)
+  Instr: mrs x1, sp_el0           (933793 hits)   (op=0xd5384101/  System Reg)
+  Instr: mrs x2, sp_el0           (699516 hits)   (op=0xd5384102/  System Reg)
+  Instr: mrs x4, tpidr_el2        (528437 hits)   (op=0xd53cd044/  System Reg)
+  Instr: mrs x30, ttbr1_el1       (480776 hits)   (op=0xd538203e/  System Reg)
+  Instr: msr ttbr1_el1, x30       (480713 hits)   (op=0xd518203e/  System Reg)
+  Instr: msr vbar_el1, x30        (480671 hits)   (op=0xd518c01e/  System Reg)
+  ...
+
+To find the argument shorthand for the class you need to examine the
+source code of the plugin at the moment, specifically the `*opt`
+argument in the InsnClassExecCount tables.
+
+- contrib/plugins/lockstep.c
+
+This is a debugging tool for developers who want to find out when and
+where execution diverges after a subtle change to TCG code generation.
+It is not an exact science and results are likely to be mixed once
+asynchronous events are introduced. While the use of -icount can
+introduce determinism to the execution flow it doesn't always follow
+the translation sequence will be exactly the same. Typically this is
+caused by a timer firing to service the GUI causing a block to end
+early. However in some cases it has proved to be useful in pointing
+people at roughly where execution diverges. The only argument you need
+for the plugin is a path for the socket the two instances will
+communicate over::
+
+
+  ./sparc-softmmu/qemu-system-sparc -monitor none -parallel none \
+    -net none -M SS-20 -m 256 -kernel day11/zImage.elf \
+    -plugin ./contrib/plugins/liblockstep.so,arg=lockstep-sparc.sock \
+  -d plugin,nochain
+
+which will eventually report::
+
+  qemu-system-sparc: warning: nic lance.0 has no peer
+  @ 0x000000ffd06678 vs 0x000000ffd001e0 (2/1 since last)
+  @ 0x000000ffd07d9c vs 0x000000ffd06678 (3/1 since last)
+  Δ insn_count @ 0x000000ffd07d9c (809900609) vs 0x000000ffd06678 (809900612)
+    previously @ 0x000000ffd06678/10 (809900609 insns)
+    previously @ 0x000000ffd001e0/4 (809900599 insns)
+    previously @ 0x000000ffd080ac/2 (809900595 insns)
+    previously @ 0x000000ffd08098/5 (809900593 insns)
+    previously @ 0x000000ffd080c0/1 (809900588 insns)
+
index 2baebc179e78918201776af576231fa21bff9c9d..40d909badcb81095d0e6731c9d969a0bc4610a51 100644 (file)
@@ -50,7 +50,7 @@ RUN_TCG_TARGET_RULES=$(patsubst %,run-tcg-tests-%, $(TARGET_DIRS))
 $(foreach PROBE_TARGET,$(TARGET_DIRS),                                 \
        $(eval -include $(SRC_PATH)/tests/tcg/Makefile.prereqs))
 
-build-tcg-tests-%: $(if $(CONFIG_PLUGIN),plugins)
+build-tcg-tests-%: $(if $(CONFIG_PLUGIN),test-plugins)
        $(call quiet-command,$(MAKE) $(SUBDIR_MAKEFLAGS) \
                -f $(SRC_PATH)/tests/tcg/Makefile.qemu \
                SRC_PATH=$(SRC_PATH) \
diff --git a/tests/plugin/hotblocks.c b/tests/plugin/hotblocks.c
deleted file mode 100644 (file)
index 3942a2c..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org>
- *
- * License: GNU GPL, version 2 or later.
- *   See the COPYING file in the top-level directory.
- */
-#include <inttypes.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <inttypes.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <glib.h>
-
-#include <qemu-plugin.h>
-
-QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
-
-static bool do_inline;
-
-/* Plugins need to take care of their own locking */
-static GMutex lock;
-static GHashTable *hotblocks;
-static guint64 limit = 20;
-
-/*
- * Counting Structure
- *
- * The internals of the TCG are not exposed to plugins so we can only
- * get the starting PC for each block. We cheat this slightly by
- * xor'ing the number of instructions to the hash to help
- * differentiate.
- */
-typedef struct {
-    uint64_t start_addr;
-    uint64_t exec_count;
-    int      trans_count;
-    unsigned long insns;
-} ExecCount;
-
-static gint cmp_exec_count(gconstpointer a, gconstpointer b)
-{
-    ExecCount *ea = (ExecCount *) a;
-    ExecCount *eb = (ExecCount *) b;
-    return ea->exec_count > eb->exec_count ? -1 : 1;
-}
-
-static void plugin_exit(qemu_plugin_id_t id, void *p)
-{
-    g_autoptr(GString) report = g_string_new("collected ");
-    GList *counts, *it;
-    int i;
-
-    g_mutex_lock(&lock);
-    g_string_append_printf(report, "%d entries in the hash table\n",
-                           g_hash_table_size(hotblocks));
-    counts = g_hash_table_get_values(hotblocks);
-    it = g_list_sort(counts, cmp_exec_count);
-
-    if (it) {
-        g_string_append_printf(report, "pc, tcount, icount, ecount\n");
-
-        for (i = 0; i < limit && it->next; i++, it = it->next) {
-            ExecCount *rec = (ExecCount *) it->data;
-            g_string_append_printf(report, "%#016"PRIx64", %d, %ld, %"PRId64"\n",
-                                   rec->start_addr, rec->trans_count,
-                                   rec->insns, rec->exec_count);
-        }
-
-        g_list_free(it);
-        g_mutex_unlock(&lock);
-    }
-
-    qemu_plugin_outs(report->str);
-}
-
-static void plugin_init(void)
-{
-    hotblocks = g_hash_table_new(NULL, g_direct_equal);
-}
-
-static void vcpu_tb_exec(unsigned int cpu_index, void *udata)
-{
-    ExecCount *cnt;
-    uint64_t hash = (uint64_t) udata;
-
-    g_mutex_lock(&lock);
-    cnt = (ExecCount *) g_hash_table_lookup(hotblocks, (gconstpointer) hash);
-    /* should always succeed */
-    g_assert(cnt);
-    cnt->exec_count++;
-    g_mutex_unlock(&lock);
-}
-
-/*
- * When do_inline we ask the plugin to increment the counter for us.
- * Otherwise a helper is inserted which calls the vcpu_tb_exec
- * callback.
- */
-static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
-{
-    ExecCount *cnt;
-    uint64_t pc = qemu_plugin_tb_vaddr(tb);
-    unsigned long insns = qemu_plugin_tb_n_insns(tb);
-    uint64_t hash = pc ^ insns;
-
-    g_mutex_lock(&lock);
-    cnt = (ExecCount *) g_hash_table_lookup(hotblocks, (gconstpointer) hash);
-    if (cnt) {
-        cnt->trans_count++;
-    } else {
-        cnt = g_new0(ExecCount, 1);
-        cnt->start_addr = pc;
-        cnt->trans_count = 1;
-        cnt->insns = insns;
-        g_hash_table_insert(hotblocks, (gpointer) hash, (gpointer) cnt);
-    }
-
-    g_mutex_unlock(&lock);
-
-    if (do_inline) {
-        qemu_plugin_register_vcpu_tb_exec_inline(tb, QEMU_PLUGIN_INLINE_ADD_U64,
-                                                 &cnt->exec_count, 1);
-    } else {
-        qemu_plugin_register_vcpu_tb_exec_cb(tb, vcpu_tb_exec,
-                                             QEMU_PLUGIN_CB_NO_REGS,
-                                             (void *)hash);
-    }
-}
-
-QEMU_PLUGIN_EXPORT
-int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
-                        int argc, char **argv)
-{
-    if (argc && strcmp(argv[0], "inline") == 0) {
-        do_inline = true;
-    }
-
-    plugin_init();
-
-    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
-    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
-    return 0;
-}
diff --git a/tests/plugin/hotpages.c b/tests/plugin/hotpages.c
deleted file mode 100644 (file)
index ecd6c18..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org>
- *
- * Hot Pages - show which pages saw the most memory accesses.
- *
- * License: GNU GPL, version 2 or later.
- *   See the COPYING file in the top-level directory.
- */
-
-#include <inttypes.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <inttypes.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <glib.h>
-
-#include <qemu-plugin.h>
-
-QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
-
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
-
-static uint64_t page_size = 4096;
-static uint64_t page_mask;
-static int limit = 50;
-static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW;
-static bool track_io;
-
-enum sort_type {
-    SORT_RW = 0,
-    SORT_R,
-    SORT_W,
-    SORT_A
-};
-
-static int sort_by = SORT_RW;
-
-typedef struct {
-    uint64_t page_address;
-    int cpu_read;
-    int cpu_write;
-    uint64_t reads;
-    uint64_t writes;
-} PageCounters;
-
-static GMutex lock;
-static GHashTable *pages;
-
-static gint cmp_access_count(gconstpointer a, gconstpointer b)
-{
-    PageCounters *ea = (PageCounters *) a;
-    PageCounters *eb = (PageCounters *) b;
-    int r;
-    switch (sort_by) {
-    case SORT_RW:
-        r = (ea->reads + ea->writes) > (eb->reads + eb->writes) ? -1 : 1;
-        break;
-    case SORT_R:
-        r = ea->reads > eb->reads ? -1 : 1;
-        break;
-    case SORT_W:
-        r = ea->writes > eb->writes ? -1 : 1;
-        break;
-    case SORT_A:
-        r = ea->page_address > eb->page_address ? -1 : 1;
-        break;
-    default:
-        g_assert_not_reached();
-    }
-    return r;
-}
-
-
-static void plugin_exit(qemu_plugin_id_t id, void *p)
-{
-    g_autoptr(GString) report = g_string_new("Addr, RCPUs, Reads, WCPUs, Writes\n");
-    int i;
-    GList *counts;
-
-    counts = g_hash_table_get_values(pages);
-    if (counts && g_list_next(counts)) {
-        GList *it;
-
-        it = g_list_sort(counts, cmp_access_count);
-
-        for (i = 0; i < limit && it->next; i++, it = it->next) {
-            PageCounters *rec = (PageCounters *) it->data;
-            g_string_append_printf(report,
-                                   "%#016"PRIx64", 0x%04x, %"PRId64
-                                   ", 0x%04x, %"PRId64"\n",
-                                   rec->page_address,
-                                   rec->cpu_read, rec->reads,
-                                   rec->cpu_write, rec->writes);
-        }
-        g_list_free(it);
-    }
-
-    qemu_plugin_outs(report->str);
-}
-
-static void plugin_init(void)
-{
-    page_mask = (page_size - 1);
-    pages = g_hash_table_new(NULL, g_direct_equal);
-}
-
-static void vcpu_haddr(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo,
-                       uint64_t vaddr, void *udata)
-{
-    struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(meminfo, vaddr);
-    uint64_t page;
-    PageCounters *count;
-
-    /* We only get a hwaddr for system emulation */
-    if (track_io) {
-        if (hwaddr && qemu_plugin_hwaddr_is_io(hwaddr)) {
-            page = vaddr;
-        } else {
-            return;
-        }
-    } else {
-        if (hwaddr && !qemu_plugin_hwaddr_is_io(hwaddr)) {
-            page = (uint64_t) qemu_plugin_hwaddr_device_offset(hwaddr);
-        } else {
-            page = vaddr;
-        }
-    }
-    page &= ~page_mask;
-
-    g_mutex_lock(&lock);
-    count = (PageCounters *) g_hash_table_lookup(pages, GUINT_TO_POINTER(page));
-
-    if (!count) {
-        count = g_new0(PageCounters, 1);
-        count->page_address = page;
-        g_hash_table_insert(pages, GUINT_TO_POINTER(page), (gpointer) count);
-    }
-    if (qemu_plugin_mem_is_store(meminfo)) {
-        count->writes++;
-        count->cpu_write |= (1 << cpu_index);
-    } else {
-        count->reads++;
-        count->cpu_read |= (1 << cpu_index);
-    }
-
-    g_mutex_unlock(&lock);
-}
-
-static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
-{
-    size_t n = qemu_plugin_tb_n_insns(tb);
-    size_t i;
-
-    for (i = 0; i < n; i++) {
-        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
-        qemu_plugin_register_vcpu_mem_cb(insn, vcpu_haddr,
-                                         QEMU_PLUGIN_CB_NO_REGS,
-                                         rw, NULL);
-    }
-}
-
-QEMU_PLUGIN_EXPORT
-int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
-                        int argc, char **argv)
-{
-    int i;
-
-    for (i = 0; i < argc; i++) {
-        char *opt = argv[i];
-        if (g_strcmp0(opt, "reads") == 0) {
-            sort_by = SORT_R;
-        } else if (g_strcmp0(opt, "writes") == 0) {
-            sort_by = SORT_W;
-        } else if (g_strcmp0(opt, "address") == 0) {
-            sort_by = SORT_A;
-        } else if (g_strcmp0(opt, "io") == 0) {
-            track_io = true;
-        } else if (g_str_has_prefix(opt, "pagesize=")) {
-            page_size = g_ascii_strtoull(opt + 9, NULL, 10);
-        } else {
-            fprintf(stderr, "option parsing failed: %s\n", opt);
-            return -1;
-        }
-    }
-
-    plugin_init();
-
-    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
-    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
-    return 0;
-}
diff --git a/tests/plugin/howvec.c b/tests/plugin/howvec.c
deleted file mode 100644 (file)
index 3b9a693..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org>
- *
- * How vectorised is this code?
- *
- * Attempt to measure the amount of vectorisation that has been done
- * on some code by counting classes of instruction.
- *
- * License: GNU GPL, version 2 or later.
- *   See the COPYING file in the top-level directory.
- */
-#include <inttypes.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <inttypes.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <glib.h>
-
-#include <qemu-plugin.h>
-
-QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
-
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
-
-typedef enum {
-    COUNT_CLASS,
-    COUNT_INDIVIDUAL,
-    COUNT_NONE
-} CountType;
-
-static int limit = 50;
-static bool do_inline;
-static bool verbose;
-
-static GMutex lock;
-static GHashTable *insns;
-
-typedef struct {
-    const char *class;
-    const char *opt;
-    uint32_t mask;
-    uint32_t pattern;
-    CountType what;
-    uint64_t count;
-} InsnClassExecCount;
-
-typedef struct {
-    char *insn;
-    uint32_t opcode;
-    uint64_t count;
-    InsnClassExecCount *class;
-} InsnExecCount;
-
-/*
- * Matchers for classes of instructions, order is important.
- *
- * Your most precise match must be before looser matches. If no match
- * is found in the table we can create an individual entry.
- *
- * 31..28 27..24 23..20 19..16 15..12 11..8 7..4 3..0
- */
-static InsnClassExecCount aarch64_insn_classes[] = {
-    /* "Reserved"" */
-    { "  UDEF",              "udef",   0xffff0000, 0x00000000, COUNT_NONE},
-    { "  SVE",               "sve",    0x1e000000, 0x04000000, COUNT_CLASS},
-    { "Reserved",            "res",    0x1e000000, 0x00000000, COUNT_CLASS},
-    /* Data Processing Immediate */
-    { "  PCrel addr",        "pcrel",  0x1f000000, 0x10000000, COUNT_CLASS},
-    { "  Add/Sub (imm,tags)","asit",   0x1f800000, 0x11800000, COUNT_CLASS},
-    { "  Add/Sub (imm)",     "asi",    0x1f000000, 0x11000000, COUNT_CLASS},
-    { "  Logical (imm)",     "logi",   0x1f800000, 0x12000000, COUNT_CLASS},
-    { "  Move Wide (imm)",   "movwi",  0x1f800000, 0x12800000, COUNT_CLASS},
-    { "  Bitfield",          "bitf",   0x1f800000, 0x13000000, COUNT_CLASS},
-    { "  Extract",           "extr",   0x1f800000, 0x13800000, COUNT_CLASS},
-    { "Data Proc Imm",       "dpri",   0x1c000000, 0x10000000, COUNT_CLASS},
-    /* Branches */
-    { "  Cond Branch (imm)", "cndb",   0xfe000000, 0x54000000, COUNT_CLASS},
-    { "  Exception Gen",     "excp",   0xff000000, 0xd4000000, COUNT_CLASS},
-    { "    NOP",             "nop",    0xffffffff, 0xd503201f, COUNT_NONE},
-    { "  Hints",             "hint",   0xfffff000, 0xd5032000, COUNT_CLASS},
-    { "  Barriers",          "barr",   0xfffff000, 0xd5033000, COUNT_CLASS},
-    { "  PSTATE",            "psta",   0xfff8f000, 0xd5004000, COUNT_CLASS},
-    { "  System Insn",       "sins",   0xffd80000, 0xd5080000, COUNT_CLASS},
-    { "  System Reg",        "sreg",   0xffd00000, 0xd5100000, COUNT_CLASS},
-    { "  Branch (reg)",      "breg",   0xfe000000, 0xd6000000, COUNT_CLASS},
-    { "  Branch (imm)",      "bimm",   0x7c000000, 0x14000000, COUNT_CLASS},
-    { "  Cmp & Branch",      "cmpb",   0x7e000000, 0x34000000, COUNT_CLASS},
-    { "  Tst & Branch",      "tstb",   0x7e000000, 0x36000000, COUNT_CLASS},
-    { "Branches",            "branch", 0x1c000000, 0x14000000, COUNT_CLASS},
-    /* Loads and Stores */
-    { "  AdvSimd ldstmult",  "advlsm", 0xbfbf0000, 0x0c000000, COUNT_CLASS},
-    { "  AdvSimd ldstmult++","advlsmp",0xbfb00000, 0x0c800000, COUNT_CLASS},
-    { "  AdvSimd ldst",      "advlss", 0xbf9f0000, 0x0d000000, COUNT_CLASS},
-    { "  AdvSimd ldst++",    "advlssp",0xbf800000, 0x0d800000, COUNT_CLASS},
-    { "  ldst excl",         "ldstx",  0x3f000000, 0x08000000, COUNT_CLASS},
-    { "    Prefetch",        "prfm",   0xff000000, 0xd8000000, COUNT_CLASS},
-    { "  Load Reg (lit)",    "ldlit",  0x1b000000, 0x18000000, COUNT_CLASS},
-    { "  ldst noalloc pair", "ldstnap",0x3b800000, 0x28000000, COUNT_CLASS},
-    { "  ldst pair",         "ldstp",  0x38000000, 0x28000000, COUNT_CLASS},
-    { "  ldst reg",          "ldstr",  0x3b200000, 0x38000000, COUNT_CLASS},
-    { "  Atomic ldst",       "atomic", 0x3b200c00, 0x38200000, COUNT_CLASS},
-    { "  ldst reg (reg off)","ldstro", 0x3b200b00, 0x38200800, COUNT_CLASS},
-    { "  ldst reg (pac)",    "ldstpa", 0x3b200200, 0x38200800, COUNT_CLASS},
-    { "  ldst reg (imm)",    "ldsti",  0x3b000000, 0x39000000, COUNT_CLASS},
-    { "Loads & Stores",      "ldst",   0x0a000000, 0x08000000, COUNT_CLASS},
-    /* Data Processing Register */
-    { "Data Proc Reg",       "dprr",   0x0e000000, 0x0a000000, COUNT_CLASS},
-    /* Scalar FP */
-    { "Scalar FP ",          "fpsimd", 0x0e000000, 0x0e000000, COUNT_CLASS},
-    /* Unclassified */
-    { "Unclassified",        "unclas", 0x00000000, 0x00000000, COUNT_CLASS},
-};
-
-static InsnClassExecCount sparc32_insn_classes[] = {
-    { "Call",                "call",   0xc0000000, 0x40000000, COUNT_CLASS},
-    { "Branch ICond",        "bcc",    0xc1c00000, 0x00800000, COUNT_CLASS},
-    { "Branch Fcond",        "fbcc",   0xc1c00000, 0x01800000, COUNT_CLASS},
-    { "SetHi",               "sethi",  0xc1c00000, 0x01000000, COUNT_CLASS},
-    { "FPU ALU",             "fpu",    0xc1f00000, 0x81a00000, COUNT_CLASS},
-    { "ALU",                 "alu",    0xc0000000, 0x80000000, COUNT_CLASS},
-    { "Load/Store",          "ldst",   0xc0000000, 0xc0000000, COUNT_CLASS},
-    /* Unclassified */
-    { "Unclassified",        "unclas", 0x00000000, 0x00000000, COUNT_INDIVIDUAL},
-};
-
-static InsnClassExecCount sparc64_insn_classes[] = {
-    { "SetHi & Branches",     "op0",   0xc0000000, 0x00000000, COUNT_CLASS},
-    { "Call",                 "op1",   0xc0000000, 0x40000000, COUNT_CLASS},
-    { "Arith/Logical/Move",   "op2",   0xc0000000, 0x80000000, COUNT_CLASS},
-    { "Arith/Logical/Move",   "op3",   0xc0000000, 0xc0000000, COUNT_CLASS},
-    /* Unclassified */
-    { "Unclassified",        "unclas", 0x00000000, 0x00000000, COUNT_INDIVIDUAL},
-};
-
-/* Default matcher for currently unclassified architectures */
-static InsnClassExecCount default_insn_classes[] = {
-    { "Unclassified",        "unclas", 0x00000000, 0x00000000, COUNT_INDIVIDUAL},
-};
-
-typedef struct {
-    const char *qemu_target;
-    InsnClassExecCount *table;
-    int table_sz;
-} ClassSelector;
-
-static ClassSelector class_tables[] =
-{
-    { "aarch64", aarch64_insn_classes, ARRAY_SIZE(aarch64_insn_classes) },
-    { "sparc",   sparc32_insn_classes, ARRAY_SIZE(sparc32_insn_classes) },
-    { "sparc64", sparc64_insn_classes, ARRAY_SIZE(sparc64_insn_classes) },
-    { NULL, default_insn_classes, ARRAY_SIZE(default_insn_classes) },
-};
-
-static InsnClassExecCount *class_table;
-static int class_table_sz;
-
-static gint cmp_exec_count(gconstpointer a, gconstpointer b)
-{
-    InsnExecCount *ea = (InsnExecCount *) a;
-    InsnExecCount *eb = (InsnExecCount *) b;
-    return ea->count > eb->count ? -1 : 1;
-}
-
-static void free_record(gpointer data)
-{
-    InsnExecCount *rec = (InsnExecCount *) data;
-    g_free(rec->insn);
-    g_free(rec);
-}
-
-static void plugin_exit(qemu_plugin_id_t id, void *p)
-{
-    g_autoptr(GString) report = g_string_new("Instruction Classes:\n");
-    int i;
-    GList *counts;
-    InsnClassExecCount *class = NULL;
-
-    for (i = 0; i < class_table_sz; i++) {
-        class = &class_table[i];
-        switch (class->what) {
-        case COUNT_CLASS:
-            if (class->count || verbose) {
-                g_string_append_printf(report, "Class: %-24s\t(%ld hits)\n",
-                                       class->class,
-                                       class->count);
-            }
-            break;
-        case COUNT_INDIVIDUAL:
-            g_string_append_printf(report, "Class: %-24s\tcounted individually\n",
-                                   class->class);
-            break;
-        case COUNT_NONE:
-            g_string_append_printf(report, "Class: %-24s\tnot counted\n",
-                                   class->class);
-            break;
-        default:
-            break;
-        }
-    }
-
-    counts = g_hash_table_get_values(insns);
-    if (counts && g_list_next(counts)) {
-        g_string_append_printf(report,"Individual Instructions:\n");
-        counts = g_list_sort(counts, cmp_exec_count);
-
-        for (i = 0; i < limit && g_list_next(counts);
-             i++, counts = g_list_next(counts)) {
-            InsnExecCount *rec = (InsnExecCount *) counts->data;
-            g_string_append_printf(report,
-                                   "Instr: %-24s\t(%ld hits)\t(op=%#08x/%s)\n",
-                                   rec->insn,
-                                   rec->count,
-                                   rec->opcode,
-                                   rec->class ?
-                                   rec->class->class : "un-categorised");
-        }
-        g_list_free(counts);
-    }
-
-    g_hash_table_destroy(insns);
-
-    qemu_plugin_outs(report->str);
-}
-
-static void plugin_init(void)
-{
-    insns = g_hash_table_new_full(NULL, g_direct_equal, NULL, &free_record);
-}
-
-static void vcpu_insn_exec_before(unsigned int cpu_index, void *udata)
-{
-    uint64_t *count = (uint64_t *) udata;
-    (*count)++;
-}
-
-static uint64_t * find_counter(struct qemu_plugin_insn *insn)
-{
-    int i;
-    uint64_t *cnt = NULL;
-    uint32_t opcode;
-    InsnClassExecCount *class = NULL;
-
-    /*
-     * We only match the first 32 bits of the instruction which is
-     * fine for most RISCs but a bit limiting for CISC architectures.
-     * They would probably benefit from a more tailored plugin.
-     * However we can fall back to individual instruction counting.
-     */
-    opcode = *((uint32_t *)qemu_plugin_insn_data(insn));
-
-    for (i = 0; !cnt && i < class_table_sz; i++) {
-        class = &class_table[i];
-        uint32_t masked_bits = opcode & class->mask;
-        if (masked_bits == class->pattern) {
-            break;
-        }
-    }
-
-    g_assert(class);
-
-    switch (class->what) {
-    case COUNT_NONE:
-        return NULL;
-    case COUNT_CLASS:
-        return &class->count;
-    case COUNT_INDIVIDUAL:
-    {
-        InsnExecCount *icount;
-
-        g_mutex_lock(&lock);
-        icount = (InsnExecCount *) g_hash_table_lookup(insns,
-                                                       GUINT_TO_POINTER(opcode));
-
-        if (!icount) {
-            icount = g_new0(InsnExecCount, 1);
-            icount->opcode = opcode;
-            icount->insn = qemu_plugin_insn_disas(insn);
-            icount->class = class;
-
-            g_hash_table_insert(insns, GUINT_TO_POINTER(opcode),
-                                (gpointer) icount);
-        }
-        g_mutex_unlock(&lock);
-
-        return &icount->count;
-    }
-    default:
-        g_assert_not_reached();
-    }
-
-    return NULL;
-}
-
-static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
-{
-    size_t n = qemu_plugin_tb_n_insns(tb);
-    size_t i;
-
-    for (i = 0; i < n; i++) {
-        uint64_t *cnt;
-        struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
-        cnt = find_counter(insn);
-
-        if (cnt) {
-            if (do_inline) {
-                qemu_plugin_register_vcpu_insn_exec_inline(
-                    insn, QEMU_PLUGIN_INLINE_ADD_U64, cnt, 1);
-            } else {
-                qemu_plugin_register_vcpu_insn_exec_cb(
-                    insn, vcpu_insn_exec_before, QEMU_PLUGIN_CB_NO_REGS, cnt);
-            }
-        }
-    }
-}
-
-QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
-                                           const qemu_info_t *info,
-                                           int argc, char **argv)
-{
-    int i;
-
-    /* Select a class table appropriate to the guest architecture */
-    for (i = 0; i < ARRAY_SIZE(class_tables); i++) {
-        ClassSelector *entry = &class_tables[i];
-        if (!entry->qemu_target ||
-            strcmp(entry->qemu_target, info->target_name) == 0) {
-            class_table = entry->table;
-            class_table_sz = entry->table_sz;
-            break;
-        }
-    }
-
-    for (i = 0; i < argc; i++) {
-        char *p = argv[i];
-        if (strcmp(p, "inline") == 0) {
-            do_inline = true;
-        } else if (strcmp(p, "verbose") == 0) {
-            verbose = true;
-        } else {
-            int j;
-            CountType type = COUNT_INDIVIDUAL;
-            if (*p == '!') {
-                type = COUNT_NONE;
-                p++;
-            }
-            for (j = 0; j < class_table_sz; j++) {
-                if (strcmp(p, class_table[j].opt) == 0) {
-                    class_table[j].what = type;
-                    break;
-                }
-            }
-        }
-    }
-
-    plugin_init();
-
-    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
-    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
-    return 0;
-}
diff --git a/tests/plugin/lockstep.c b/tests/plugin/lockstep.c
deleted file mode 100644 (file)
index a696673..0000000
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Lockstep Execution Plugin
- *
- * Allows you to execute two QEMU instances in lockstep and report
- * when their execution diverges. This is mainly useful for developers
- * who want to see where a change to TCG code generation has
- * introduced a subtle and hard to find bug.
- *
- * Caveats:
- *   - single-threaded linux-user apps only with non-deterministic syscalls
- *   - no MTTCG enabled system emulation (icount may help)
- *
- * While icount makes things more deterministic it doesn't mean a
- * particular run may execute the exact same sequence of blocks. An
- * asynchronous event (for example X11 graphics update) may cause a
- * block to end early and a new partial block to start. This means
- * serial only test cases are a better bet. -d nochain may also help.
- *
- * This code is not thread safe!
- *
- * Copyright (c) 2020 Linaro Ltd
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include <glib.h>
-#include <inttypes.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <stdio.h>
-#include <errno.h>
-
-#include <qemu-plugin.h>
-
-QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
-
-/* saved so we can uninstall later */
-static qemu_plugin_id_t our_id;
-
-static unsigned long bb_count;
-static unsigned long insn_count;
-
-/* Information about a translated block */
-typedef struct {
-    uint64_t pc;
-    uint64_t insns;
-} BlockInfo;
-
-/* Information about an execution state in the log */
-typedef struct {
-    BlockInfo *block;
-    unsigned long insn_count;
-    unsigned long block_count;
-} ExecInfo;
-
-/* The execution state we compare */
-typedef struct {
-    uint64_t pc;
-    unsigned long insn_count;
-} ExecState;
-
-typedef struct {
-    GSList *log_pos;
-    int distance;
-} DivergeState;
-
-/* list of translated block info */
-static GSList *blocks;
-
-/* execution log and points of divergence */
-static GSList *log, *divergence_log;
-
-static int socket_fd;
-static char *path_to_unlink;
-
-static bool verbose;
-
-static void plugin_cleanup(qemu_plugin_id_t id)
-{
-    /* Free our block data */
-    g_slist_free_full(blocks, &g_free);
-    g_slist_free_full(log, &g_free);
-    g_slist_free(divergence_log);
-
-    close(socket_fd);
-    if (path_to_unlink) {
-        unlink(path_to_unlink);
-    }
-}
-
-static void plugin_exit(qemu_plugin_id_t id, void *p)
-{
-    g_autoptr(GString) out = g_string_new("No divergence :-)\n");
-    g_string_append_printf(out, "Executed %ld/%d blocks\n",
-                           bb_count, g_slist_length(log));
-    g_string_append_printf(out, "Executed ~%ld instructions\n", insn_count);
-    qemu_plugin_outs(out->str);
-
-    plugin_cleanup(id);
-}
-
-static void report_divergance(ExecState *us, ExecState *them)
-{
-    DivergeState divrec = { log, 0 };
-    g_autoptr(GString) out = g_string_new("");
-    bool diverged = false;
-
-    /*
-     * If we have diverged before did we get back on track or are we
-     * totally loosing it?
-     */
-    if (divergence_log) {
-        DivergeState *last = (DivergeState *) divergence_log->data;
-        GSList *entry;
-
-        for (entry = log; g_slist_next(entry); entry = g_slist_next(entry)) {
-            if (entry == last->log_pos) {
-                break;
-            }
-            divrec.distance++;
-        }
-
-        /*
-         * If the last two records are so close it is likely we will
-         * not recover synchronisation with the other end.
-         */
-        if (divrec.distance == 1 && last->distance == 1) {
-            diverged = true;
-        }
-    }
-    divergence_log = g_slist_prepend(divergence_log,
-                                     g_memdup(&divrec, sizeof(divrec)));
-
-    /* Output short log entry of going out of sync... */
-    if (verbose || divrec.distance == 1 || diverged) {
-        g_string_printf(out, "@ %#016lx vs %#016lx (%d/%d since last)\n",
-                        us->pc, them->pc, g_slist_length(divergence_log),
-                        divrec.distance);
-        qemu_plugin_outs(out->str);
-    }
-
-    if (diverged) {
-        int i;
-        GSList *entry;
-
-        g_string_printf(out, "Δ insn_count @ %#016lx (%ld) vs %#016lx (%ld)\n",
-                        us->pc, us->insn_count, them->pc, them->insn_count);
-
-        for (entry = log, i = 0;
-             g_slist_next(entry) && i < 5;
-             entry = g_slist_next(entry), i++) {
-            ExecInfo *prev = (ExecInfo *) entry->data;
-            g_string_append_printf(out,
-                                   "  previously @ %#016lx/%ld (%ld insns)\n",
-                                   prev->block->pc, prev->block->insns,
-                                   prev->insn_count);
-        }
-        qemu_plugin_outs(out->str);
-        qemu_plugin_outs("too much divergence... giving up.");
-        qemu_plugin_uninstall(our_id, plugin_cleanup);
-    }
-}
-
-static void vcpu_tb_exec(unsigned int cpu_index, void *udata)
-{
-    BlockInfo *bi = (BlockInfo *) udata;
-    ExecState us, them;
-    ssize_t bytes;
-    ExecInfo *exec;
-
-    us.pc = bi->pc;
-    us.insn_count = insn_count;
-
-    /*
-     * Write our current position to the other end. If we fail the
-     * other end has probably died and we should shut down gracefully.
-     */
-    bytes = write(socket_fd, &us, sizeof(ExecState));
-    if (bytes < sizeof(ExecState)) {
-        qemu_plugin_outs(bytes < 0 ?
-                         "problem writing to socket" :
-                         "wrote less than expected to socket");
-        qemu_plugin_uninstall(our_id, plugin_cleanup);
-        return;
-    }
-
-    /*
-     * Now read where our peer has reached. Again a failure probably
-     * indicates the other end died and we should close down cleanly.
-     */
-    bytes = read(socket_fd, &them, sizeof(ExecState));
-    if (bytes < sizeof(ExecState)) {
-        qemu_plugin_outs(bytes < 0 ?
-                         "problem reading from socket" :
-                         "read less than expected");
-        qemu_plugin_uninstall(our_id, plugin_cleanup);
-        return;
-    }
-
-    /*
-     * Compare and report if we have diverged.
-     */
-    if (us.pc != them.pc) {
-        report_divergance(&us, &them);
-    }
-
-    /*
-     * Assume this block will execute fully and record it
-     * in the execution log.
-     */
-    insn_count += bi->insns;
-    bb_count++;
-    exec = g_new0(ExecInfo, 1);
-    exec->block = bi;
-    exec->insn_count = insn_count;
-    exec->block_count = bb_count;
-    log = g_slist_prepend(log, exec);
-}
-
-static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
-{
-    BlockInfo *bi = g_new0(BlockInfo, 1);
-    bi->pc = qemu_plugin_tb_vaddr(tb);
-    bi->insns = qemu_plugin_tb_n_insns(tb);
-
-    /* save a reference so we can free later */
-    blocks = g_slist_prepend(blocks, bi);
-    qemu_plugin_register_vcpu_tb_exec_cb(tb, vcpu_tb_exec,
-                                         QEMU_PLUGIN_CB_NO_REGS, (void *)bi);
-}
-
-
-/*
- * Instead of encoding master/slave status into what is essentially
- * two peers we shall just take the simple approach of checking for
- * the existence of the pipe and assuming if it's not there we are the
- * first process.
- */
-static bool setup_socket(const char *path)
-{
-    struct sockaddr_un sockaddr;
-    int fd;
-
-    fd = socket(AF_UNIX, SOCK_STREAM, 0);
-    if (fd < 0) {
-        perror("create socket");
-        return false;
-    }
-
-    sockaddr.sun_family = AF_UNIX;
-    g_strlcpy(sockaddr.sun_path, path, sizeof(sockaddr.sun_path) - 1);
-    if (bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
-        perror("bind socket");
-        close(fd);
-        return false;
-    }
-
-    /* remember to clean-up */
-    path_to_unlink = g_strdup(path);
-
-    if (listen(fd, 1) < 0) {
-        perror("listen socket");
-        close(fd);
-        return false;
-    }
-
-    socket_fd = accept(fd, NULL, NULL);
-    if (socket_fd < 0 && errno != EINTR) {
-        perror("accept socket");
-        return false;
-    }
-
-    qemu_plugin_outs("setup_socket::ready\n");
-
-    return true;
-}
-
-static bool connect_socket(const char *path)
-{
-    int fd;
-    struct sockaddr_un sockaddr;
-
-    fd = socket(AF_UNIX, SOCK_STREAM, 0);
-    if (fd < 0) {
-        perror("create socket");
-        return false;
-    }
-
-    sockaddr.sun_family = AF_UNIX;
-    g_strlcpy(sockaddr.sun_path, path, sizeof(sockaddr.sun_path) - 1);
-
-    if (connect(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
-        perror("failed to connect");
-        return false;
-    }
-
-    qemu_plugin_outs("connect_socket::ready\n");
-
-    socket_fd = fd;
-    return true;
-}
-
-static bool setup_unix_socket(const char *path)
-{
-    if (g_file_test(path, G_FILE_TEST_EXISTS)) {
-        return connect_socket(path);
-    } else {
-        return setup_socket(path);
-    }
-}
-
-
-QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
-                                           const qemu_info_t *info,
-                                           int argc, char **argv)
-{
-    int i;
-
-    if (!argc || !argv[0]) {
-        qemu_plugin_outs("Need a socket path to talk to other instance.");
-        return -1;
-    }
-
-    for (i = 0; i < argc; i++) {
-        char *p = argv[i];
-        if (strcmp(p, "verbose") == 0) {
-            verbose = true;
-        } else if (!setup_unix_socket(argv[0])) {
-            qemu_plugin_outs("Failed to setup socket for communications.");
-            return -1;
-        }
-    }
-
-    our_id = id;
-
-    qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
-    qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
-    return 0;
-}
index dbbdcbaa6708d5f84fe9bd6c9b4296e35373f609..1eacfa6e35522a9c4dd1326744e8e2e2645bdf7a 100644 (file)
@@ -1,7 +1,7 @@
 t = []
-foreach i : ['bb', 'empty', 'insn', 'mem', 'hotblocks', 'howvec', 'hotpages', 'lockstep']
+foreach i : ['bb', 'empty', 'insn', 'mem']
   t += shared_module(i, files(i + '.c'),
                      include_directories: '../../include/qemu',
                      dependencies: glib)
 endforeach
-alias_target('plugins', t)
+alias_target('test-plugins', t)
index 4b2b696fcee324254ffa46b35b563831613d5728..2ae86776cdc16f99719924fc9f0c8c8ab031afe8 100644 (file)
@@ -129,8 +129,7 @@ ifeq ($(CONFIG_PLUGIN),y)
 PLUGIN_SRC=$(SRC_PATH)/tests/plugin
 PLUGIN_LIB=../../plugin
 VPATH+=$(PLUGIN_LIB)
-PLUGINS=$(filter-out liblockstep.so,\
-               $(patsubst %.c, lib%.so, $(notdir $(wildcard $(PLUGIN_SRC)/*.c))))
+PLUGINS=$(patsubst %.c, lib%.so, $(notdir $(wildcard $(PLUGIN_SRC)/*.c)))
 
 # We need to ensure expand the run-plugin-TEST-with-PLUGIN
 # pre-requistes manually here as we can't use stems to handle it. We