From patchwork Mon Sep 5 18:56:38 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Llu=C3=ADs_Vilanova?= X-Patchwork-Id: 9315077 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id ACA336075E for ; Mon, 5 Sep 2016 19:10:52 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 978CA28A98 for ; Mon, 5 Sep 2016 19:10:52 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 882B828AC8; Mon, 5 Sep 2016 19:10:52 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id B8ECF28A98 for ; Mon, 5 Sep 2016 19:10:50 +0000 (UTC) Received: from localhost ([::1]:56488 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bgzI9-0008Dg-Q8 for patchwork-qemu-devel@patchwork.kernel.org; Mon, 05 Sep 2016 15:10:49 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:44846) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bgz4j-0005X8-Nh for qemu-devel@nongnu.org; Mon, 05 Sep 2016 14:57:01 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1bgz4f-0008Qh-Eg for qemu-devel@nongnu.org; Mon, 05 Sep 2016 14:56:56 -0400 Received: from roura.ac.upc.edu ([147.83.33.10]:57354 helo=roura.ac.upc.es) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bgz4e-0008Qb-PC for qemu-devel@nongnu.org; Mon, 05 Sep 2016 14:56:53 -0400 Received: from gw-2.ac.upc.es (gw-2.ac.upc.es [147.83.30.8]) by roura.ac.upc.es (8.13.8/8.13.8) with ESMTP id u85IudfG001956; Mon, 5 Sep 2016 20:56:39 +0200 Received: from localhost (unknown [84.88.51.85]) by gw-2.ac.upc.es (Postfix) with ESMTPSA id CE1C23B1; Mon, 5 Sep 2016 20:56:38 +0200 (CEST) From: =?utf-8?b?TGx1w61z?= Vilanova To: qemu-devel@nongnu.org Date: Mon, 5 Sep 2016 20:56:38 +0200 Message-Id: <147310179851.10840.1397173676543898200.stgit@fimbulvetr.bsc.es> X-Mailer: git-send-email 2.9.3 In-Reply-To: <147310178240.10840.14758930096407696981.stgit@fimbulvetr.bsc.es> References: <147310178240.10840.14758930096407696981.stgit@fimbulvetr.bsc.es> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 X-MIME-Autoconverted: from 8bit to quoted-printable by roura.ac.upc.es id u85IudfG001956 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-Received-From: 147.83.33.10 Subject: [Qemu-devel] [PATCH v2 3/6] hypertrace: [*-user] Add QEMU-side proxy to "guest_hypertrace" event X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Riku Voipio , Stefan Hajnoczi , Luiz Capitulino Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP 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 --- Makefile.objs | 4 + bsd-user/main.c | 16 ++ bsd-user/mmap.c | 15 ++ bsd-user/syscall.c | 31 +++- hypertrace/Makefile.objs | 18 ++ hypertrace/common.c | 26 ++++ hypertrace/common.h | 24 +++ hypertrace/user.c | 339 ++++++++++++++++++++++++++++++++++++++++++++++ hypertrace/user.h | 61 ++++++++ include/qom/cpu.h | 4 + linux-user/main.c | 19 +++ linux-user/mmap.c | 17 ++ linux-user/qemu.h | 3 linux-user/syscall.c | 31 +++- 14 files changed, 583 insertions(+), 25 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 diff --git a/Makefile.objs b/Makefile.objs index 3db04b9..56b41c7 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -104,6 +104,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 diff --git a/bsd-user/main.c b/bsd-user/main.c index 0fb08e4..0d0a850 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -31,9 +31,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; @@ -694,6 +697,8 @@ static void usage(void) "-strace log system calls\n" "-trace [[enable=]][,events=][,file=]\n" " specify tracing options\n" + "-hypertrace [[base=]][,max-clients=]\n" + " specify hypertrace options\n" "\n" "Environment variables:\n" "QEMU_STRACE Print system calls and arguments similar to the\n" @@ -744,6 +749,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(); @@ -763,6 +770,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 (;;) { @@ -853,6 +861,9 @@ 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_file); + hypertrace_opt_parse(optarg, &hypertrace_base, &hypertrace_max_clients); } else { usage(); } @@ -987,6 +998,11 @@ int main(int argc, char **argv) target_set_brk(info->brk); syscall_init(); signal_init(); + if (atexit(hypertrace_fini) != 0) { + fprintf(stderr, "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 diff --git a/bsd-user/mmap.c b/bsd-user/mmap.c index 610f91b..d8ffc1e 100644 --- a/bsd-user/mmap.c +++ b/bsd-user/mmap.c @@ -21,6 +21,7 @@ #include "qemu.h" #include "qemu-common.h" #include "bsd-mman.h" +#include "hypertrace/user.h" //#define DEBUG_MMAP @@ -251,10 +252,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; @@ -296,6 +304,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; @@ -407,6 +419,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 diff --git a/bsd-user/syscall.c b/bsd-user/syscall.c index 66492aa..d354e7f 100644 --- a/bsd-user/syscall.c +++ b/bsd-user/syscall.c @@ -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,11 @@ 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 +433,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 +459,11 @@ 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 +510,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 +536,11 @@ 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)); diff --git a/hypertrace/Makefile.objs b/hypertrace/Makefile.objs new file mode 100644 index 0000000..24e8fb4 --- /dev/null +++ b/hypertrace/Makefile.objs @@ -0,0 +1,18 @@ +# -*- mode: makefile -*- + +target-obj-$(CONFIG_USER_ONLY) += user.o +target-obj-y += common.o + +$(obj)/user.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 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 "}" >>$@ diff --git a/hypertrace/common.c b/hypertrace/common.c new file mode 100644 index 0000000..baca098 --- /dev/null +++ b/hypertrace/common.c @@ -0,0 +1,26 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016 Lluís Vilanova + * + * 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 "qemu-common.h" +#include "cpu.h" +#include "hypertrace/common.h" +#include "qemu/osdep.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); + config->control_size = QEMU_ALIGN_UP( + config->max_clients * sizeof(uint64_t), TARGET_PAGE_SIZE); + config->data_size = QEMU_ALIGN_UP( + config->max_clients * config->client_data_size, TARGET_PAGE_SIZE); +} diff --git a/hypertrace/common.h b/hypertrace/common.h new file mode 100644 index 0000000..dfce161 --- /dev/null +++ b/hypertrace/common.h @@ -0,0 +1,24 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016 Lluís Vilanova + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#if !defined(__KERNEL__) +#include +#endif + +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); diff --git a/hypertrace/user.c b/hypertrace/user.c new file mode 100644 index 0000000..0e07283 --- /dev/null +++ b/hypertrace/user.c @@ -0,0 +1,339 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016 Lluís Vilanova + * + * 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 +#include +#include +#include +#include +#include + +#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 = NULL; +static int config_fd = -1; +static uint64_t *qemu_config = NULL; + +static char *data_path = NULL; +static int data_fd = -1; +static uint64_t *qemu_data = NULL; + +static char *control_path = NULL; +static int control_fd = -1; +static uint64_t *qemu_control = NULL; +static struct stat control_fd_stat; + +struct sigaction segv_next; +static void segv_handler(int signum, siginfo_t *siginfo, void *sigctxt); + + +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 { + *base = NULL; + } + max_clients = qemu_opt_get_number(opts, "pages", 1); + if (max_clients <= 0) { + error_report("Parameter '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)); + abort(); + } + + 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(); + } + } +} + +static void fini_handler(int signum, siginfo_t *siginfo, void *sigctxt) +{ + hypertrace_fini(); +} + +void hypertrace_init(const char *base, unsigned int max_clients) +{ + struct sigaction sigint; + struct hypertrace_config *pconfig; + + if (base == NULL) { + return; + } + + memset(&sigint, 0, sizeof(sigint)); + sigint.sa_sigaction = fini_handler; + sigint.sa_flags = SA_SIGINFO | SA_RESTART; + if (sigaction(SIGINT, &sigint, NULL) != 0) { + error_report("error: sigaction(SIGINT): %s", strerror(errno)); + abort(); + } + if (sigaction(SIGABRT, &sigint, NULL) != 0) { + error_report("error: sigaction(SIGABRT): %s", strerror(errno)); + abort(); + } + + hypertrace_init_config(&config, max_clients); + /* We need twice the space for the double-fault protocol */ + config.control_size *= 2; + + init_channel(base, "-config", TARGET_PAGE_SIZE, &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(); + } + printf("data: dev=%ld ino=%ld ptr=%p\n", + control_fd_stat.st_dev, control_fd_stat.st_ino, qemu_data); + + 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(); + } + printf("control: dev=%ld ino=%ld ptr=%p\n", + control_fd_stat.st_dev, control_fd_stat.st_ino, qemu_control); + + struct sigaction segv; + memset(&segv, 0, sizeof(segv)); + segv.sa_sigaction = segv_handler; + segv.sa_flags = SA_SIGINFO | SA_RESTART; + sigemptyset(&segv.sa_mask); + + if (sigaction(SIGSEGV, &segv, &segv_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 = false; + if (atexit_in) { + return; + } + atexit_in = true; + + if (sigaction(SIGSEGV, &segv_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) { + return true; + } + + if (s.st_dev != control_fd_stat.st_dev || + s.st_ino != control_fd_stat.st_ino) { + return true; + } + + 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) + */ + printf("cpu->hypertrace_control=%p %lu %lu\n", qemu_addr, s.st_dev, s.st_ino); + vcpu->hypertrace_control = qemu_addr; + if (mprotect(vcpu->hypertrace_control, 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) +{ + printf("mprotect: RW %p %lu\n", from, config.control_size / 2); + if (mprotect(from, config.control_size / 2, PROT_READ | PROT_WRITE) == -1) { + error_report("error: mprotect(from): %s", strerror(errno)); + abort(); + } + printf("mprotect: R %p %lu\n", to, config.control_size / 2); + if (mprotect(to, config.control_size / 2, PROT_READ) == -1) { + error_report("error: mprotect(to): %s", strerror(errno)); + abort(); + } +} + +#include "hypertrace/emit.c" + +static void segv_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) */ + printf("SEGV 1\n"); + assert(((unsigned long)siginfo->si_addr % sizeof(uint64_t)) == 0); + swap_control(control_0, control_1); + printf("SEGV 1!\n"); + + } else if (control_1 <= siginfo->si_addr && siginfo->si_addr < control_2) { + 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]; + + /* 2nd fault (invoke) */ + printf("SEGV 2\n"); + assert(((unsigned long)siginfo->si_addr % sizeof(uint64_t)) == 0); + hypertrace_emit(current_cpu, vcontrol, data_ptr); + swap_control(control_1, control_0); + printf("SEGV 2!\n"); + + } else { + /* proxy to next handler */ + printf("SEGV ?\n"); + if (segv_next.sa_sigaction != NULL) { + segv_next.sa_sigaction(signum, siginfo, sigctxt); + } else if (segv_next.sa_handler != NULL) { + segv_next.sa_handler(signum); + } + printf("SEGV ?!\n"); + } +} diff --git a/hypertrace/user.h b/hypertrace/user.h new file mode 100644 index 0000000..9c1f44b --- /dev/null +++ b/hypertrace/user.h @@ -0,0 +1,61 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016 Lluís Vilanova + * + * 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 +#include + + +/** + * 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: + * + * Verify argument validity when mapping the control channel. + * + * 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); diff --git a/include/qom/cpu.h b/include/qom/cpu.h index ce0c406..c83570a 100644 --- a/include/qom/cpu.h +++ b/include/qom/cpu.h @@ -283,6 +283,7 @@ struct qemu_work_item { * @work_mutex: Lock to prevent multiple access to queued_work_*. * @queued_work_first: First asynchronous work pending. * @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. */ @@ -353,6 +354,9 @@ struct CPUState { /* Used for events with 'vcpu' and *without* the 'disabled' properties */ DECLARE_BITMAP(trace_dstate, TRACE_VCPU_EVENT_COUNT); + /* 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 */ diff --git a/linux-user/main.c b/linux-user/main.c index f2f4d2f..5316a25 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -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; @@ -4011,6 +4013,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; @@ -4060,6 +4070,8 @@ static const struct qemu_argument arg_table[] = { "", "Seed for pseudo-random number generator"}, {"trace", "QEMU_TRACE", true, handle_arg_trace, "", "[[enable=]][,events=][,file=]"}, + {"hypertrace", "QEMU_HYPERTRACE", true, handle_arg_hypertrace, + "", "[[base=]][,max-clients=]"}, {"version", "QEMU_VERSION", false, handle_arg_version, "", "display version information and exit"}, {NULL, NULL, false, NULL, NULL, NULL} @@ -4250,6 +4262,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); @@ -4448,6 +4461,12 @@ int main(int argc, char **argv, char **envp) syscall_init(); signal_init(); + if (atexit(hypertrace_fini)) { + fprintf(stderr, "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. */ diff --git a/linux-user/mmap.c b/linux-user/mmap.c index c4371d9..cfb159c 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -23,6 +23,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,8 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, } } the_end1: + printf("mmap "TARGET_ABI_FMT_lx" "TARGET_ABI_FMT_lu"\n", start, len); + hypertrace_guest_mmap_apply(fd, g2h(start), cpu); page_set_flags(start, start + len, prot | PAGE_VALID); the_end: #ifdef DEBUG_MMAP diff --git a/linux-user/qemu.h b/linux-user/qemu.h index bef465d..968a6b4 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -411,6 +411,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, diff --git a/linux-user/syscall.c b/linux-user/syscall.c index ca06943..3e8d8fd 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -111,6 +111,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base, #include "uname.h" #include "qemu.h" +#include "hypertrace/user.h" #define CLONE_NPTL_FLAGS2 (CLONE_SETTLS | \ CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID) @@ -7372,6 +7373,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; @@ -8824,15 +8826,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 @@ -8841,10 +8847,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: @@ -9377,6 +9385,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