From patchwork Mon Dec 26 20:34:54 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: 9488753 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 1054460838 for ; Mon, 26 Dec 2016 20:35:47 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F33E91FF26 for ; Mon, 26 Dec 2016 20:35:46 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E5D9C1FF8F; Mon, 26 Dec 2016 20:35:46 +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 28C951FF26 for ; Mon, 26 Dec 2016 20:35:45 +0000 (UTC) Received: from localhost ([::1]:51616 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cLbzk-0002Kz-8V for patchwork-qemu-devel@patchwork.kernel.org; Mon, 26 Dec 2016 15:35:44 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:41945) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cLbzA-0002Ez-7F for qemu-devel@nongnu.org; Mon, 26 Dec 2016 15:35:11 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cLbz6-0002yE-VN for qemu-devel@nongnu.org; Mon, 26 Dec 2016 15:35:08 -0500 Received: from roura.ac.upc.edu ([147.83.33.10]:50874 helo=roura.ac.upc.es) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cLbz6-0002xs-6c for qemu-devel@nongnu.org; Mon, 26 Dec 2016 15:35:04 -0500 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 uBQKYt7K026166; Mon, 26 Dec 2016 21:34:55 +0100 Received: from localhost (unknown [84.88.51.85]) by gw-2.ac.upc.es (Postfix) with ESMTPSA id 9E91C4BD; Mon, 26 Dec 2016 21:34:54 +0100 (CET) From: =?utf-8?b?TGx1w61z?= Vilanova To: qemu-devel@nongnu.org Date: Mon, 26 Dec 2016 21:34:54 +0100 Message-Id: <148278449426.8988.2219094135462471980.stgit@fimbulvetr.bsc.es> X-Mailer: git-send-email 2.11.0 In-Reply-To: <148278447806.8988.12706825771606357198.stgit@fimbulvetr.bsc.es> References: <148278447806.8988.12706825771606357198.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 uBQKYt7K026166 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 v4 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 | 17 ++ bsd-user/mmap.c | 15 ++ bsd-user/syscall.c | 34 +++-- hypertrace/Makefile.objs | 18 ++ hypertrace/common.c | 26 ++++ hypertrace/common.h | 23 +++ hypertrace/user.c | 332 ++++++++++++++++++++++++++++++++++++++++++++++ hypertrace/user.h | 63 +++++++++ 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, 581 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 5f0be2c3fb..5f3a68ca48 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -105,6 +105,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 714a692e6f..d42ded4fc8 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; @@ -676,6 +679,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" @@ -736,6 +741,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(); @@ -757,6 +764,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 (;;) { @@ -847,6 +855,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_file); + hypertrace_opt_parse(optarg, &hypertrace_base, + &hypertrace_max_clients); } else { usage(); } @@ -981,6 +993,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 ee5907330f..650b8203fa 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 @@ -256,10 +257,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; @@ -301,6 +309,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; @@ -412,6 +424,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..24e8fb4eea --- /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 0000000000..baca098aa0 --- /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 0000000000..6eabbd6551 --- /dev/null +++ b/hypertrace/common.h @@ -0,0 +1,23 @@ +/* + * 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 0000000000..5649c0369a --- /dev/null +++ b/hypertrace/user.c @@ -0,0 +1,332 @@ +/* + * 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; +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; + +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(); + } + + 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(); + } + + 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; + 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) + */ + 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(); + } +} + +#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) */ + assert(((unsigned long)siginfo->si_addr % sizeof(uint64_t)) == 0); + swap_control(control_0, control_1); + + } 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) */ + assert(((unsigned long)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 */ + 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); + } + } +} diff --git a/hypertrace/user.h b/hypertrace/user.h new file mode 100644 index 0000000000..b2bc60d507 --- /dev/null +++ b/hypertrace/user.h @@ -0,0 +1,63 @@ +/* + * 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 3f79a8e955..8e7b579083 100644 --- a/include/qom/cpu.h +++ b/include/qom/cpu.h @@ -296,6 +296,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. */ @@ -372,6 +373,9 @@ struct CPUState { */ unsigned long *trace_dstate; + /* 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 75b199f274..8006abb102 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; @@ -3748,6 +3750,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; @@ -3797,6 +3807,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} @@ -3989,6 +4001,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); @@ -4187,6 +4200,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 61685bf79e..52f1a54281 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 @@ -360,10 +361,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(); @@ -445,6 +454,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; @@ -556,6 +569,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 da73a01106..da10920907 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -422,6 +422,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 7b77503f94..0432001cda 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -113,6 +113,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 */ @@ -7603,6 +7604,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; @@ -9051,15 +9053,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 @@ -9068,10 +9074,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: @@ -9642,6 +9650,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