@@ -98,6 +98,10 @@ util-obj-y += trace/
target-obj-y += trace/
######################################################################
+# hypertrace
+target-obj-y += hypertrace/
+
+######################################################################
# guest agent
# FIXME: a few definitions from qapi-types.o/qapi-visit.o are needed
@@ -30,9 +30,12 @@
#include "tcg.h"
#include "qemu/timer.h"
#include "qemu/envlist.h"
+#include "qemu/error-report.h"
#include "exec/log.h"
#include "trace/control.h"
#include "glib-compat.h"
+#include "hypertrace/user.h"
+
int singlestep;
unsigned long mmap_min_addr;
@@ -675,6 +678,8 @@ static void usage(void)
"-strace log system calls\n"
"-trace [[enable=]<pattern>][,events=<file>][,file=<file>]\n"
" specify tracing options\n"
+ "-hypertrace [[base=]<path>][,max-clients=<uint>]\n"
+ " specify hypertrace options\n"
"\n"
"Environment variables:\n"
"QEMU_STRACE Print system calls and arguments similar to the\n"
@@ -735,6 +740,8 @@ int main(int argc, char **argv)
envlist_t *envlist = NULL;
char *trace_file = NULL;
bsd_type = target_openbsd;
+ char *hypertrace_base = NULL;
+ unsigned int hypertrace_max_clients = 0;
if (argc <= 1)
usage();
@@ -753,6 +760,7 @@ int main(int argc, char **argv)
cpu_model = NULL;
qemu_add_opts(&qemu_trace_opts);
+ qemu_add_opts(&qemu_hypertrace_opts);
optind = 1;
for (;;) {
@@ -840,6 +848,10 @@ int main(int argc, char **argv)
} else if (!strcmp(r, "trace")) {
g_free(trace_file);
trace_file = trace_opt_parse(optarg);
+ } else if (!strcmp(r, "hypertrace")) {
+ g_free(hypertrace_base);
+ hypertrace_opt_parse(optarg, &hypertrace_base,
+ &hypertrace_max_clients);
} else {
usage();
}
@@ -974,6 +986,11 @@ int main(int argc, char **argv)
target_set_brk(info->brk);
syscall_init();
signal_init();
+ if (atexit(hypertrace_fini) != 0) {
+ error_report("error: atexit: %s\n", strerror(errno));
+ abort();
+ }
+ hypertrace_init(hypertrace_base, hypertrace_size);
/* Now that we've loaded the binary, GUEST_BASE is fixed. Delay
generating the prologue until now so that the prologue can take
@@ -21,6 +21,7 @@
#include "qemu.h"
#include "qemu-common.h"
#include "bsd-mman.h"
+#include "hypertrace/user.h"
//#define DEBUG_MMAP
@@ -240,10 +241,17 @@ static abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
return addr;
}
-/* NOTE: all the constants are the HOST ones */
abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
int flags, int fd, abi_ulong offset)
{
+ return target_mmap_cpu(start, len, prot, flags, fd, offset, NULL);
+}
+
+/* NOTE: all the constants are the HOST ones */
+abi_long target_mmap_cpu(abi_ulong start, abi_ulong len, int prot,
+ int flags, int fd, abi_ulong offset,
+ CPUState *cpu)
+{
abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len;
unsigned long host_start;
@@ -285,6 +293,10 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
goto the_end;
real_start = start & qemu_host_page_mask;
+ if (!hypertrace_guest_mmap_check(fd, len, offset)) {
+ goto fail;
+ }
+
if (!(flags & MAP_FIXED)) {
abi_ulong mmap_start;
void *p;
@@ -396,6 +408,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
}
}
the_end1:
+ hypertrace_guest_mmap_apply(fd, g2h(start), cpu);
page_set_flags(start, start + len, prot | PAGE_VALID);
the_end:
#ifdef DEBUG_MMAP
@@ -26,6 +26,7 @@
#include "qemu.h"
#include "qemu-common.h"
+#include "hypertrace/user.h"
//#define DEBUG
@@ -332,6 +333,7 @@ abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1,
_mcleanup();
#endif
gdb_exit(cpu_env, arg1);
+ hypertrace_fini();
/* XXX: should free thread stack and CPU env */
_exit(arg1);
ret = 0; /* avoid warning */
@@ -369,10 +371,12 @@ abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1,
unlock_user(p, arg1, 0);
break;
case TARGET_FREEBSD_NR_mmap:
- ret = get_errno(target_mmap(arg1, arg2, arg3,
- target_to_host_bitmask(arg4, mmap_flags_tbl),
- arg5,
- arg6));
+ ret = get_errno(target_mmap_cpu(
+ arg1, arg2, arg3,
+ target_to_host_bitmask(arg4, mmap_flags_tbl),
+ arg5,
+ arg6,
+ cpu));
break;
case TARGET_FREEBSD_NR_mprotect:
ret = get_errno(target_mprotect(arg1, arg2, arg3));
@@ -430,6 +434,7 @@ abi_long do_netbsd_syscall(void *cpu_env, int num, abi_long arg1,
_mcleanup();
#endif
gdb_exit(cpu_env, arg1);
+ hypertrace_fini();
/* XXX: should free thread stack and CPU env */
_exit(arg1);
ret = 0; /* avoid warning */
@@ -455,10 +460,12 @@ abi_long do_netbsd_syscall(void *cpu_env, int num, abi_long arg1,
unlock_user(p, arg1, 0);
break;
case TARGET_NETBSD_NR_mmap:
- ret = get_errno(target_mmap(arg1, arg2, arg3,
- target_to_host_bitmask(arg4, mmap_flags_tbl),
- arg5,
- arg6));
+ ret = get_errno(target_mmap_cpu(
+ arg1, arg2, arg3,
+ target_to_host_bitmask(arg4, mmap_flags_tbl),
+ arg5,
+ arg6,
+ cpu));
break;
case TARGET_NETBSD_NR_mprotect:
ret = get_errno(target_mprotect(arg1, arg2, arg3));
@@ -505,6 +512,7 @@ abi_long do_openbsd_syscall(void *cpu_env, int num, abi_long arg1,
_mcleanup();
#endif
gdb_exit(cpu_env, arg1);
+ hypertrace_fini();
/* XXX: should free thread stack and CPU env */
_exit(arg1);
ret = 0; /* avoid warning */
@@ -530,10 +538,12 @@ abi_long do_openbsd_syscall(void *cpu_env, int num, abi_long arg1,
unlock_user(p, arg1, 0);
break;
case TARGET_OPENBSD_NR_mmap:
- ret = get_errno(target_mmap(arg1, arg2, arg3,
- target_to_host_bitmask(arg4, mmap_flags_tbl),
- arg5,
- arg6));
+ ret = get_errno(target_mmap_cpu(
+ arg1, arg2, arg3,
+ target_to_host_bitmask(arg4, mmap_flags_tbl),
+ arg5,
+ arg6,
+ cpu));
break;
case TARGET_OPENBSD_NR_mprotect:
ret = get_errno(target_mprotect(arg1, arg2, arg3));
new file mode 100644
@@ -0,0 +1,19 @@
+# -*- mode: makefile -*-
+
+target-obj-$(CONFIG_USER_ONLY) += user.o
+target-obj-y += common.o
+
+$(obj)/user.o: $(obj)/emit.c
+$(obj)/common.o: $(obj)/emit.c
+
+$(obj)/emit.c: $(obj)/emit.c-timestamp $(BUILD_DIR)/config-host.mak
+ @cmp $< $@ >/dev/null 2>&1 || cp $< $@
+$(obj)/emit.c-timestamp: $(BUILD_DIR)/config-host.mak
+ @echo "static void do_hypertrace_emit(CPUState *cpu, uint64_t arg1, uint64_t *data)" >$@
+ @echo "{" >>$@
+ @echo -n " trace_guest_hypertrace(cpu, arg1" >>$@
+ @for i in `seq $$(( $(CONFIG_HYPERTRACE_ARGS) - 1 ))`; do \
+ echo -n ", data[$$i-1]" >>$@; \
+ done
+ @echo ");" >>$@
+ @echo "}" >>$@
new file mode 100644
@@ -0,0 +1,55 @@
+/*
+ * QEMU-side management of hypertrace in user-level emulation.
+ *
+ * Copyright (C) 2016-2017 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "exec/cpu-all.h"
+#include "hypertrace/common.h"
+#include "hypertrace/trace.h"
+
+
+void hypertrace_init_config(struct hypertrace_config *config,
+ unsigned int max_clients)
+{
+ config->max_clients = max_clients;
+ config->client_args = CONFIG_HYPERTRACE_ARGS;
+ config->client_data_size = config->client_args * sizeof(uint64_t);
+
+ /* Align for both, since they can be used on softmmu and user mode */
+ int page_size = 1;
+ page_size = QEMU_ALIGN_UP(page_size, getpagesize());
+ page_size = QEMU_ALIGN_UP(page_size, TARGET_PAGE_SIZE);
+
+#if defined(CONFIG_USER_ONLY)
+ /* We need twice the number of clients (*in pages*) for the double-fault protocol */
+ config->control_size = QEMU_ALIGN_UP(
+ config->max_clients * TARGET_PAGE_SIZE * 2, page_size);
+#else
+ config->control_size = QEMU_ALIGN_UP(
+ config->max_clients * sizeof(uint64_t), page_size);
+#endif
+ config->data_size = QEMU_ALIGN_UP(
+ config->max_clients * config->client_data_size, page_size);
+}
+
+
+#include "hypertrace/emit.c"
+
+void hypertrace_emit(CPUState *cpu, uint64_t arg1, uint64_t *data)
+{
+ int i;
+ /* swap event arguments to host endianness */
+ arg1 = tswap64(arg1);
+ for (i = 0; i < CONFIG_HYPERTRACE_ARGS - 1; i++) {
+ data[i] = tswap64(data[i]);
+ }
+
+ /* emit event */
+ do_hypertrace_emit(cpu, arg1, data);
+}
new file mode 100644
@@ -0,0 +1,25 @@
+/*
+ * QEMU-side management of hypertrace in user-level emulation.
+ *
+ * Copyright (C) 2016-2017 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <stdint.h>
+#include "qemu/typedefs.h"
+
+/* NOTE: Linux's kernel headers must be synced with this */
+struct hypertrace_config {
+ uint64_t max_clients;
+ uint64_t client_args;
+ uint64_t client_data_size;
+ uint64_t control_size;
+ uint64_t data_size;
+};
+
+void hypertrace_init_config(struct hypertrace_config *config,
+ unsigned int max_clients);
+
+void hypertrace_emit(struct CPUState *cpu, uint64_t arg1, uint64_t *data);
new file mode 100644
@@ -0,0 +1,414 @@
+/*
+ * QEMU-side management of hypertrace in user-level emulation.
+ *
+ * Copyright (C) 2016-2017 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * Implementation details
+ * ======================
+ *
+ * There are 3 channels, each a regular file in the host system, and mmap'ed by
+ * the guest application.
+ *
+ * - Configuration channel: Exposes configuration parameters. Mapped once and
+ * directly readable.
+ *
+ * - Data channel: Lets guests write argument values. Each guest thread should
+ * use a different offset to avoid concurrency problems. Mapped once and
+ * directly accessible.
+ *
+ * - Control channel: Triggers the hypertrace event on a write, providing the
+ * first argument. Offset in the control channel sets the offset in the data
+ * channel. Mapped once per thread, using two pages to reliably detect
+ * accesses and their written value through a SEGV handler.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+
+#include "hypertrace/common.h"
+#include "hypertrace/user.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+#include "trace.h"
+
+
+static struct hypertrace_config config;
+static char *config_path;
+static int config_fd = -1;
+static uint64_t *qemu_config;
+
+static char *data_path;
+static int data_fd = -1;
+static uint64_t *qemu_data;
+
+static char *control_path;
+static int control_fd = -1;
+static uint64_t *qemu_control;
+static struct stat control_fd_stat;
+
+
+QemuOptsList qemu_hypertrace_opts = {
+ .name = "hypertrace",
+ .implied_opt_name = "path",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_hypertrace_opts.head),
+ .desc = {
+ {
+ .name = "path",
+ .type = QEMU_OPT_STRING,
+ },
+ {
+ .name = "max-clients",
+ .type = QEMU_OPT_NUMBER,
+ .def_value_str = "1",
+ },
+ { /* end of list */ }
+ },
+};
+
+void hypertrace_opt_parse(const char *optarg, char **base,
+ unsigned int *max_clients_)
+{
+ int max_clients;
+ QemuOpts *opts = qemu_opts_parse_noisily(qemu_find_opts("hypertrace"),
+ optarg, true);
+ if (!opts) {
+ exit(1);
+ }
+ if (qemu_opt_get(opts, "path")) {
+ *base = g_strdup(qemu_opt_get(opts, "path"));
+ } else {
+ error_report("error: -hypertrace path is mandatory");
+ exit(EXIT_FAILURE);
+ }
+ max_clients = qemu_opt_get_number(opts, "max-clients", 1);
+ if (max_clients <= 0) {
+ error_report("error: -hypertrace max-clients expects a positive number");
+ exit(EXIT_FAILURE);
+ }
+ *max_clients_ = max_clients;
+}
+
+static void init_channel(const char *base, const char *suffix, size_t size,
+ char **path, int *fd, uint64_t **addr)
+{
+ *path = g_malloc(strlen(base) + strlen(suffix) + 1);
+ sprintf(*path, "%s%s", base, suffix);
+
+ *fd = open(*path, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
+ if (*fd == -1) {
+ error_report("error: open(%s): %s", *path, strerror(errno));
+ exit(1);
+ }
+
+ off_t lres = lseek(*fd, size - 1, SEEK_SET);
+ if (lres == (off_t)-1) {
+ error_report("error: lseek(%s): %s", *path, strerror(errno));
+ abort();
+ }
+
+ char tmp;
+ ssize_t wres = write(*fd, &tmp, 1);
+ if (wres == -1) {
+ error_report("error: write(%s): %s", *path, strerror(errno));
+ abort();
+ }
+
+ if (addr) {
+ *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0);
+ if (*addr == MAP_FAILED) {
+ error_report("error: mmap(%s): %s", *path, strerror(errno));
+ abort();
+ }
+ }
+}
+
+/* Host SIGSEGV cannot be set bu user-mode guests */
+static struct sigaction sigsegv_ours;
+static struct sigaction sigsegv_next;
+static void sigsegv_handler(int signum, siginfo_t *siginfo, void *sigctxt);
+
+static struct sigaction sigint_ours;
+static struct sigaction sigint_next;
+bool sigint_user_set;
+struct sigaction sigint_user;
+
+static struct sigaction sigabrt_ours;
+static struct sigaction sigabrt_next;
+bool sigabrt_user_set;
+struct sigaction sigabrt_user;
+
+typedef void (* sigaction_t)(int, siginfo_t *, void *);
+
+static void reflect_handler(struct sigaction *ours, struct sigaction *next,
+ int signum, siginfo_t *siginfo, void *sigctxt)
+{
+ if (next->sa_flags & SA_SIGINFO) {
+ if (next->sa_sigaction != NULL) {
+ next->sa_sigaction(signum, siginfo, sigctxt);
+ } else if (next->sa_sigaction == (sigaction_t)SIG_IGN) {
+ /* ignore */
+ } else if (next->sa_sigaction == (sigaction_t)SIG_DFL) {
+ if (signal(signum, SIG_DFL) == SIG_ERR) {
+ error_report("error: signal: %s", strerror(errno));
+ abort();
+ }
+ if (raise(signum) != 0) {
+ error_report("error: raise: %s", strerror(errno));
+ abort();
+ }
+ struct sigaction tmp;
+ memcpy(&tmp, ours, sizeof(tmp));
+ if (sigaction(signum, &tmp, NULL) != 0) {
+ error_report("error: sigaction: %s", strerror(errno));
+ abort();
+ }
+ }
+ } else {
+ if (next->sa_handler != NULL) {
+ next->sa_handler(signum);
+ } else if (next->sa_handler == SIG_IGN) {
+ /* ignore */
+ } else if (next->sa_handler == SIG_DFL) {
+ if (signal(signum, SIG_DFL) == SIG_ERR) {
+ error_report("error: signal: %s", strerror(errno));
+ abort();
+ }
+ if (raise(signum) != 0) {
+ error_report("error: raise: %s", strerror(errno));
+ abort();
+ }
+ struct sigaction tmp;
+ memcpy(&tmp, ours, sizeof(tmp));
+ if (sigaction(signum, &tmp, NULL) != 0) {
+ error_report("error: sigaction: %s", strerror(errno));
+ abort();
+ }
+ }
+ }
+}
+
+static void sigint_handler(int signum, siginfo_t *siginfo, void *sigctxt)
+{
+ hypertrace_fini();
+ /* QEMU lets users override any signal handler */
+ if (sigint_user_set) {
+ reflect_handler(&sigint_ours, &sigint_user, signum, siginfo, sigctxt);
+ } else {
+ reflect_handler(&sigint_ours, &sigint_next, signum, siginfo, sigctxt);
+ }
+}
+
+static void sigabrt_handler(int signum, siginfo_t *siginfo, void *sigctxt)
+{
+ hypertrace_fini();
+ /* QEMU lets users override any signal handler */
+ if (sigabrt_user_set) {
+ reflect_handler(&sigabrt_ours, &sigabrt_user, signum, siginfo, sigctxt);
+ } else {
+ reflect_handler(&sigabrt_ours, &sigabrt_next, signum, siginfo, sigctxt);
+ }
+}
+
+void hypertrace_init(const char *base, unsigned int max_clients)
+{
+ struct hypertrace_config *pconfig;
+
+ if (base == NULL) {
+ return;
+ }
+
+ sigint_user_set = false;
+ memset(&sigint_ours, 0, sizeof(sigint_ours));
+ sigint_ours.sa_sigaction = sigint_handler;
+ sigint_ours.sa_flags = SA_SIGINFO | SA_RESTART;
+ sigemptyset(&sigint_ours.sa_mask);
+ if (sigaction(SIGINT, &sigint_ours, &sigint_next) != 0) {
+ error_report("error: sigaction(SIGINT): %s", strerror(errno));
+ abort();
+ }
+
+ sigabrt_user_set = false;
+ memset(&sigabrt_ours, 0, sizeof(sigabrt_ours));
+ sigabrt_ours.sa_sigaction = sigabrt_handler;
+ sigabrt_ours.sa_flags = SA_SIGINFO | SA_RESTART;
+ sigemptyset(&sigabrt_ours.sa_mask);
+ if (sigaction(SIGABRT, &sigabrt_ours, &sigabrt_next) != 0) {
+ error_report("error: sigaction(SIGABRT): %s", strerror(errno));
+ abort();
+ }
+
+ hypertrace_init_config(&config, max_clients);
+
+ init_channel(base, "-config", getpagesize(),
+ &config_path, &config_fd, &qemu_config);
+ pconfig = (struct hypertrace_config *)qemu_config;
+ pconfig->max_clients = tswap64(config.max_clients);
+ pconfig->client_args = tswap64(config.client_args);
+ pconfig->client_data_size = tswap64(config.client_data_size);
+ pconfig->control_size = tswap64(config.control_size);
+ pconfig->data_size = tswap64(config.data_size);
+
+ init_channel(base, "-data", config.data_size,
+ &data_path, &data_fd, &qemu_data);
+ if (fstat(data_fd, &control_fd_stat) == -1) {
+ error_report("error: fstat(hypertrace_control): %s", strerror(errno));
+ abort();
+ }
+
+ init_channel(base, "-control", config.control_size,
+ &control_path, &control_fd, &qemu_control);
+
+ if (fstat(control_fd, &control_fd_stat) == -1) {
+ error_report("error: fstat(hypertrace_control): %s", strerror(errno));
+ abort();
+ }
+
+ memset(&sigsegv_ours, 0, sizeof(sigsegv_ours));
+ sigsegv_ours.sa_sigaction = sigsegv_handler;
+ sigsegv_ours.sa_flags = SA_SIGINFO | SA_RESTART;
+ sigemptyset(&sigsegv_ours.sa_mask);
+ if (sigaction(SIGSEGV, &sigsegv_ours, &sigsegv_next) != 0) {
+ error_report("error: sigaction(SIGSEGV): %s", strerror(errno));
+ abort();
+ }
+}
+
+
+static void fini_channel(int *fd, char **path)
+{
+ if (*fd != -1) {
+ if (close(*fd) == -1) {
+ error_report("error: close: %s", strerror(errno));
+ abort();
+ }
+ if (unlink(*path) == -1) {
+ error_report("error: unlink(%s): %s", *path, strerror(errno));
+ abort();
+ }
+ *fd = -1;
+ }
+ if (*path != NULL) {
+ g_free(*path);
+ *path = NULL;
+ }
+}
+
+void hypertrace_fini(void)
+{
+ static bool atexit_in;
+ if (atexit_in) {
+ return;
+ }
+ atexit_in = true;
+
+ if (sigaction(SIGSEGV, &sigsegv_next, NULL) != 0) {
+ error_report("error: sigaction(SIGSEGV): %s", strerror(errno));
+ abort();
+ }
+ fini_channel(&config_fd, &config_path);
+ fini_channel(&data_fd, &data_path);
+ fini_channel(&control_fd, &control_path);
+}
+
+
+bool hypertrace_guest_mmap_check(int fd, unsigned long len,
+ unsigned long offset)
+{
+ struct stat s;
+ if (fstat(fd, &s) < 0) {
+ /* the control channel should never fail fstat() */
+ return true;
+ }
+
+ if (s.st_dev != control_fd_stat.st_dev ||
+ s.st_ino != control_fd_stat.st_ino) {
+ /* this is not the control channel */
+ return true;
+ }
+
+ /* check control channel is mapped correctly */
+ return len == (config.control_size) && offset == 0;
+}
+
+void hypertrace_guest_mmap_apply(int fd, void *qemu_addr, CPUState *vcpu)
+{
+ struct stat s;
+
+ if (vcpu == NULL) {
+ return;
+ }
+
+ if (fstat(fd, &s) != 0) {
+ return;
+ }
+
+ if (s.st_dev != control_fd_stat.st_dev ||
+ s.st_ino != control_fd_stat.st_ino) {
+ return;
+ }
+
+ /* it's an mmap of the control channel; split it in two and mprotect it to
+ * detect writes (cmd is written once on each part)
+ */
+ vcpu->hypertrace_control = qemu_addr;
+ if (mprotect(qemu_addr, config.control_size / 2, PROT_READ) == -1) {
+ error_report("error: mprotect(hypertrace_control): %s",
+ strerror(errno));
+ abort();
+ }
+}
+
+static void swap_control(void *from, void *to)
+{
+ if (mprotect(from, config.control_size / 2, PROT_READ | PROT_WRITE) == -1) {
+ error_report("error: mprotect(from): %s", strerror(errno));
+ abort();
+ }
+ if (mprotect(to, config.control_size / 2, PROT_READ) == -1) {
+ error_report("error: mprotect(to): %s", strerror(errno));
+ abort();
+ }
+}
+
+static void sigsegv_handler(int signum, siginfo_t *siginfo, void *sigctxt)
+{
+ CPUState *vcpu = current_cpu;
+ void *control_0 = vcpu->hypertrace_control;
+ void *control_1 = vcpu->hypertrace_control + config.control_size / 2;
+ void *control_2 = control_1 + config.control_size / 2;
+
+ if (control_0 <= siginfo->si_addr && siginfo->si_addr < control_1) {
+
+ /* 1st fault (guest will write cmd) */
+ assert(((uintptr_t)siginfo->si_addr % sizeof(TARGET_PAGE_SIZE)) == 0);
+ swap_control(control_0, control_1);
+
+ } else if (control_1 <= siginfo->si_addr && siginfo->si_addr < control_2) {
+
+ /* 2nd fault (invoke) */
+ size_t client = (siginfo->si_addr - control_1) / sizeof(uint64_t);
+ uint64_t vcontrol = ((uint64_t *)control_0)[client];
+ uint64_t *data_ptr = &qemu_data[client * config.client_data_size];
+ assert(((uintptr_t)siginfo->si_addr % sizeof(uint64_t)) == 0);
+ hypertrace_emit(current_cpu, vcontrol, data_ptr);
+ swap_control(control_1, control_0);
+
+ } else {
+
+ /* proxy to next handler */
+ reflect_handler(&sigsegv_ours, &sigsegv_next, signum, siginfo, sigctxt);
+
+ }
+}
new file mode 100644
@@ -0,0 +1,71 @@
+/*
+ * QEMU-side management of hypertrace in user-level emulation.
+ *
+ * Copyright (C) 2016-2017 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+
+/**
+ * Definition of QEMU options describing hypertrace subsystem configuration
+ */
+extern QemuOptsList qemu_hypertrace_opts;
+
+/**
+ * hypertrace_opt_parse:
+ * @optarg: Input arguments.
+ * @base: Output base path for the hypertrace channel files.
+ * @max_clients: Output maximum number of concurrent clients.
+ *
+ * Parse the commandline arguments for hypertrace.
+ */
+void hypertrace_opt_parse(const char *optarg, char **base,
+ unsigned int *max_clients);
+
+/**
+ * hypertrace_init:
+ * @base: Base path for the hypertrace channel files.
+ * @max_clients: Maximum number of concurrent clients.
+ *
+ * Initialize the backing files for the hypertrace channel.
+ */
+void hypertrace_init(const char *base, unsigned int max_clients);
+
+/**
+ * hypertrace_guest_mmap_check:
+ *
+ * Check whether the mapped file is *not* hypertrace's control channel; if it
+ * is, check it is mapped correctly.
+ *
+ * Precondition: defined(CONFIG_USER_ONLY)
+ */
+bool hypertrace_guest_mmap_check(int fd, unsigned long len,
+ unsigned long offset);
+
+/**
+ * hypertrace_guest_mmap_apply:
+ *
+ * Configure initial mprotect if mapping the control channel.
+ *
+ * Precondition: defined(CONFIG_USER_ONLY)
+ */
+void hypertrace_guest_mmap_apply(int fd, void *qemu_addr, CPUState *vcpu);
+
+/**
+ * hypertrace_fini:
+ *
+ * Remove the backing files for the hypertrace channel.
+ */
+void hypertrace_fini(void);
+
+
+/* Internal signal management */
+extern bool sigint_user_set;
+extern struct sigaction sigint_user;
+extern bool sigabrt_user_set;
+extern struct sigaction sigabrt_user;
@@ -305,6 +305,7 @@ struct qemu_work_item;
* @trace_dstate_delayed: Delayed changes to trace_dstate (includes all changes
* to @trace_dstate).
* @trace_dstate: Dynamic tracing state of events for this vCPU (bitmask).
+ * @hypertrace_control: Per-vCPU address of the hypertrace control channel.
*
* State of one CPU core or thread.
*/
@@ -377,6 +378,9 @@ struct CPUState {
DECLARE_BITMAP(trace_dstate_delayed, CPU_TRACE_DSTATE_MAX_EVENTS);
DECLARE_BITMAP(trace_dstate, CPU_TRACE_DSTATE_MAX_EVENTS);
+ /* Only used when defined(CONFIG_USER_ONLY) */
+ void *hypertrace_control;
+
/* TODO Move common fields from CPUArchState here. */
int cpu_index; /* used by alpha TCG */
uint32_t halted; /* used by alpha, cris, ppc TCG */
@@ -32,10 +32,12 @@
#include "tcg.h"
#include "qemu/timer.h"
#include "qemu/envlist.h"
+#include "qemu/error-report.h"
#include "elf.h"
#include "exec/log.h"
#include "trace/control.h"
#include "glib-compat.h"
+#include "hypertrace/user.h"
char *exec_path;
@@ -4014,6 +4016,14 @@ static void handle_arg_trace(const char *arg)
trace_file = trace_opt_parse(arg);
}
+static char *hypertrace_base;
+static unsigned int hypertrace_max_clients;
+static void handle_arg_hypertrace(const char *arg)
+{
+ g_free(hypertrace_base);
+ hypertrace_opt_parse(arg, &hypertrace_base, &hypertrace_max_clients);
+}
+
struct qemu_argument {
const char *argv;
const char *env;
@@ -4063,6 +4073,8 @@ static const struct qemu_argument arg_table[] = {
"", "Seed for pseudo-random number generator"},
{"trace", "QEMU_TRACE", true, handle_arg_trace,
"", "[[enable=]<pattern>][,events=<file>][,file=<file>]"},
+ {"hypertrace", "QEMU_HYPERTRACE", true, handle_arg_hypertrace,
+ "", "[[base=]<path>][,max-clients=<uint>]"},
{"version", "QEMU_VERSION", false, handle_arg_version,
"", "display version information and exit"},
{NULL, NULL, false, NULL, NULL, NULL}
@@ -4252,6 +4264,7 @@ int main(int argc, char **argv, char **envp)
srand(time(NULL));
qemu_add_opts(&qemu_trace_opts);
+ qemu_add_opts(&qemu_hypertrace_opts);
optind = parse_args(argc, argv);
@@ -4453,6 +4466,12 @@ int main(int argc, char **argv, char **envp)
syscall_init();
signal_init();
+ if (atexit(hypertrace_fini)) {
+ error_report("error: atexit: %s\n", strerror(errno));
+ abort();
+ }
+ hypertrace_init(hypertrace_base, hypertrace_max_clients);
+
/* Now that we've loaded the binary, GUEST_BASE is fixed. Delay
generating the prologue until now so that the prologue can take
the real value of GUEST_BASE into account. */
@@ -21,6 +21,7 @@
#include "qemu.h"
#include "qemu-common.h"
#include "translate-all.h"
+#include "hypertrace/user.h"
//#define DEBUG_MMAP
@@ -357,10 +358,18 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
}
}
-/* NOTE: all the constants are the HOST ones */
abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
int flags, int fd, abi_ulong offset)
{
+ return target_mmap_cpu(start, len, prot, flags, fd, offset, NULL);
+}
+
+
+/* NOTE: all the constants are the HOST ones */
+abi_long target_mmap_cpu(abi_ulong start, abi_ulong len, int prot,
+ int flags, int fd, abi_ulong offset,
+ CPUState *cpu)
+{
abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len;
mmap_lock();
@@ -442,6 +451,10 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
}
}
+ if (!hypertrace_guest_mmap_check(fd, len, offset)) {
+ goto fail;
+ }
+
if (!(flags & MAP_FIXED)) {
unsigned long host_start;
void *p;
@@ -553,6 +566,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
}
}
the_end1:
+ hypertrace_guest_mmap_apply(fd, g2h(start), cpu);
page_set_flags(start, start + len, prot | PAGE_VALID);
the_end:
#ifdef DEBUG_MMAP
@@ -425,6 +425,9 @@ void sparc64_get_context(CPUSPARCState *env);
int target_mprotect(abi_ulong start, abi_ulong len, int prot);
abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
int flags, int fd, abi_ulong offset);
+abi_long target_mmap_cpu(abi_ulong start, abi_ulong len, int prot,
+ int flags, int fd, abi_ulong offset,
+ CPUState *cpu);
int target_munmap(abi_ulong start, abi_ulong len);
abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
abi_ulong new_size, unsigned long flags,
@@ -23,6 +23,7 @@
#include "qemu.h"
#include "qemu-common.h"
+#include "hypertrace/user.h"
#include "target_signal.h"
#include "trace.h"
@@ -813,7 +814,16 @@ int do_sigaction(int sig, const struct target_sigaction *act,
} else {
act1.sa_sigaction = host_signal_handler;
}
- ret = sigaction(host_sig, &act1, NULL);
+
+ if (host_sig == SIGINT) {
+ memcpy(&sigint_user, &act1, sizeof(act1));
+ sigint_user_set = true;
+ } else if (host_sig == SIGABRT) {
+ memcpy(&sigabrt_user, &act1, sizeof(act1));
+ sigabrt_user_set = true;
+ } else {
+ ret = sigaction(host_sig, &act1, NULL);
+ }
}
}
return ret;
@@ -115,6 +115,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base,
#include "uname.h"
#include "qemu.h"
+#include "hypertrace/user.h"
#ifndef CLONE_IO
#define CLONE_IO 0x80000000 /* Clone io context */
@@ -7765,6 +7766,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
_mcleanup();
#endif
gdb_exit(cpu_env, arg1);
+ hypertrace_fini();
_exit(arg1);
ret = 0; /* avoid warning */
break;
@@ -9231,15 +9233,19 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
v5 = tswapal(v[4]);
v6 = tswapal(v[5]);
unlock_user(v, arg1, 0);
- ret = get_errno(target_mmap(v1, v2, v3,
- target_to_host_bitmask(v4, mmap_flags_tbl),
- v5, v6));
+ ret = get_errno(target_mmap_cpu(
+ v1, v2, v3,
+ target_to_host_bitmask(v4, mmap_flags_tbl),
+ v5, v6,
+ cpu));
}
#else
- ret = get_errno(target_mmap(arg1, arg2, arg3,
- target_to_host_bitmask(arg4, mmap_flags_tbl),
- arg5,
- arg6));
+ ret = get_errno(target_mmap_cpu(
+ arg1, arg2, arg3,
+ target_to_host_bitmask(arg4, mmap_flags_tbl),
+ arg5,
+ arg6,
+ cpu));
#endif
break;
#endif
@@ -9248,10 +9254,12 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
#ifndef MMAP_SHIFT
#define MMAP_SHIFT 12
#endif
- ret = get_errno(target_mmap(arg1, arg2, arg3,
- target_to_host_bitmask(arg4, mmap_flags_tbl),
- arg5,
- arg6 << MMAP_SHIFT));
+ ret = get_errno(target_mmap_cpu(
+ arg1, arg2, arg3,
+ target_to_host_bitmask(arg4, mmap_flags_tbl),
+ arg5,
+ arg6 << MMAP_SHIFT,
+ cpu));
break;
#endif
case TARGET_NR_munmap:
@@ -9821,6 +9829,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
_mcleanup();
#endif
gdb_exit(cpu_env, arg1);
+ hypertrace_fini();
ret = get_errno(exit_group(arg1));
break;
#endif
QEMU detects when the guest uses 'mmap' on hypertrace's control channel file, and then uses 'mprotect' to detect accesses to it, which are used to trigger tracing event "guest_hypertrace". Signed-off-by: Lluís Vilanova <vilanova@ac.upc.edu> --- Makefile.objs | 4 bsd-user/main.c | 17 ++ bsd-user/mmap.c | 15 ++ bsd-user/syscall.c | 34 ++-- hypertrace/Makefile.objs | 19 ++ hypertrace/common.c | 55 ++++++ hypertrace/common.h | 25 +++ hypertrace/user.c | 414 ++++++++++++++++++++++++++++++++++++++++++++++ hypertrace/user.h | 71 ++++++++ include/qom/cpu.h | 4 linux-user/main.c | 19 ++ linux-user/mmap.c | 16 ++ linux-user/qemu.h | 3 linux-user/signal.c | 12 + linux-user/syscall.c | 31 ++- 15 files changed, 713 insertions(+), 26 deletions(-) create mode 100644 hypertrace/Makefile.objs create mode 100644 hypertrace/common.c create mode 100644 hypertrace/common.h create mode 100644 hypertrace/user.c create mode 100644 hypertrace/user.h