From patchwork Thu Jul 27 07:06:54 2017 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: 9866361 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 101A26038F for ; Thu, 27 Jul 2017 07:08:14 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F2A93287E7 for ; Thu, 27 Jul 2017 07:08:13 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E5C08287F0; Thu, 27 Jul 2017 07:08:13 +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 F25CC287E7 for ; Thu, 27 Jul 2017 07:08:11 +0000 (UTC) Received: from localhost ([::1]:41461 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dacu2-0006Is-H3 for patchwork-qemu-devel@patchwork.kernel.org; Thu, 27 Jul 2017 03:08:10 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:60535) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dactA-0006Ik-4h for qemu-devel@nongnu.org; Thu, 27 Jul 2017 03:07:20 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1dact6-0006Y0-0J for qemu-devel@nongnu.org; Thu, 27 Jul 2017 03:07:16 -0400 Received: from roura.ac.upc.es ([147.83.33.10]:57175) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dact4-0006X1-Us for qemu-devel@nongnu.org; Thu, 27 Jul 2017 03:07:11 -0400 Received: from correu-1.ac.upc.es (correu-1.ac.upc.es [147.83.30.91]) by roura.ac.upc.es (8.13.8/8.13.8) with ESMTP id v6R772rS017516; Thu, 27 Jul 2017 09:07:02 +0200 Received: from localhost (unknown [31.210.188.120]) by correu-1.ac.upc.es (Postfix) with ESMTPSA id 358D81E0; Thu, 27 Jul 2017 09:06:56 +0200 (CEST) From: =?utf-8?b?TGx1w61z?= Vilanova To: qemu-devel@nongnu.org Date: Thu, 27 Jul 2017 10:06:54 +0300 Message-Id: <150113921467.25904.1277295741238473226.stgit@frigg.lan> X-Mailer: git-send-email 2.13.2 In-Reply-To: <150113848793.25904.12006041120076726256.stgit@frigg.lan> References: <150113848793.25904.12006041120076726256.stgit@frigg.lan> 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 v6R772rS017516 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x [fuzzy] X-Received-From: 147.83.33.10 Subject: [Qemu-devel] [PATCH v6 3/5] 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 , Laurent Vivier , Luiz Capitulino , Stefan Hajnoczi 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 | 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 diff --git a/Makefile.objs b/Makefile.objs index ce9a60137b..57479fa738 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -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 diff --git a/bsd-user/main.c b/bsd-user/main.c index fa9c012c9f..f9f0ac0673 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -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=]][,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" @@ -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 diff --git a/bsd-user/mmap.c b/bsd-user/mmap.c index 7f2018ede0..6a549a3553 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 @@ -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 diff --git a/bsd-user/syscall.c b/bsd-user/syscall.c index 66492aaf5d..f88f21876c 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,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)); diff --git a/hypertrace/Makefile.objs b/hypertrace/Makefile.objs new file mode 100644 index 0000000000..2bda105c2f --- /dev/null +++ b/hypertrace/Makefile.objs @@ -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 "}" >>$@ diff --git a/hypertrace/common.c b/hypertrace/common.c new file mode 100644 index 0000000000..af1b8ef26e --- /dev/null +++ b/hypertrace/common.c @@ -0,0 +1,55 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016-2017 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 "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); +} diff --git a/hypertrace/common.h b/hypertrace/common.h new file mode 100644 index 0000000000..cd295bdf76 --- /dev/null +++ b/hypertrace/common.h @@ -0,0 +1,25 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016-2017 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 "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); diff --git a/hypertrace/user.c b/hypertrace/user.c new file mode 100644 index 0000000000..72717f9357 --- /dev/null +++ b/hypertrace/user.c @@ -0,0 +1,414 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016-2017 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; +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); + + } +} diff --git a/hypertrace/user.h b/hypertrace/user.h new file mode 100644 index 0000000000..e3f131c675 --- /dev/null +++ b/hypertrace/user.h @@ -0,0 +1,71 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016-2017 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: + * + * 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; diff --git a/include/qom/cpu.h b/include/qom/cpu.h index 25eefea7ab..bb382a0e6a 100644 --- a/include/qom/cpu.h +++ b/include/qom/cpu.h @@ -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 */ diff --git a/linux-user/main.c b/linux-user/main.c index ad03c9e8b2..edfe190f61 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; @@ -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=]][,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} @@ -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. */ diff --git a/linux-user/mmap.c b/linux-user/mmap.c index 4888f53139..ade8759af9 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -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 diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 4edd7d0c08..67cf051d27 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -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, diff --git a/linux-user/signal.c b/linux-user/signal.c index 3d18d1b3ee..9a21302331 100644 --- a/linux-user/signal.c +++ b/linux-user/signal.c @@ -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; diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 003943b736..17d76f5d39 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -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