From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50061) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bViTS-0000H4-4z for qemu-devel@nongnu.org; Fri, 05 Aug 2016 12:59:57 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1bViTN-00034l-KJ for qemu-devel@nongnu.org; Fri, 05 Aug 2016 12:59:54 -0400 Received: from roura.ac.upc.edu ([147.83.33.10]:52949 helo=roura.ac.upc.es) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bViTN-00034L-0P for qemu-devel@nongnu.org; Fri, 05 Aug 2016 12:59:49 -0400 From: =?utf-8?b?TGx1w61z?= Vilanova Date: Fri, 5 Aug 2016 18:59:39 +0200 Message-Id: <147041637969.2523.4570342042982870131.stgit@fimbulvetr.bsc.es> In-Reply-To: <147041636348.2523.2954972609232949598.stgit@fimbulvetr.bsc.es> References: <147041636348.2523.2954972609232949598.stgit@fimbulvetr.bsc.es> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Subject: [Qemu-devel] [PATCH 3/6] hypertrace: [*-user] Add QEMU-side proxy to "guest_hypertrace" event List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: Stefan Hajnoczi , Riku Voipio 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 traceing event "guest_hypertrace". Signed-off-by: Llu=C3=ADs Vilanova --- Makefile.objs | 4 + bsd-user/main.c | 16 +++ bsd-user/mmap.c | 2=20 bsd-user/syscall.c | 4 + hypertrace/Makefile.objs | 17 +++ hypertrace/user.c | 292 ++++++++++++++++++++++++++++++++++++++++= ++++++ hypertrace/user.h | 52 ++++++++ linux-user/main.c | 19 +++ linux-user/mmap.c | 2=20 linux-user/syscall.c | 3=20 10 files changed, 411 insertions(+) create mode 100644 hypertrace/Makefile.objs create mode 100644 hypertrace/user.c create mode 100644 hypertrace/user.h diff --git a/Makefile.objs b/Makefile.objs index 1c1b03c..f598e0e 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -104,6 +104,10 @@ util-obj-y +=3D trace/ target-obj-y +=3D trace/ =20 ###################################################################### +# hypertrace +target-obj-y +=3D hypertrace/ + +###################################################################### # guest agent =20 # 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 315ba1d..9721240 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" + =20 int singlestep; unsigned long mmap_min_addr; @@ -692,6 +695,8 @@ static void usage(void) "-strace log system calls\n" "-trace [[enable=3D]][,events=3D][,= file=3D]\n" " specify tracing options\n" + "-hypertrace [[base=3D]][,pages=3D]\n" + " specify hypertrace options\n" "\n" "Environment variables:\n" "QEMU_STRACE Print system calls and arguments similar t= o the\n" @@ -742,6 +747,8 @@ int main(int argc, char **argv) envlist_t *envlist =3D NULL; char *trace_file =3D NULL; bsd_type =3D target_openbsd; + char *hypertrace_base =3D NULL; + uint64_t hypertrace_size =3D 0; =20 if (argc <=3D 1) usage(); @@ -761,6 +768,7 @@ int main(int argc, char **argv) cpu_model =3D NULL; =20 qemu_add_opts(&qemu_trace_opts); + qemu_add_opts(&qemu_hypertrace_opts); =20 optind =3D 1; for (;;) { @@ -851,6 +859,9 @@ int main(int argc, char **argv) } else if (!strcmp(r, "trace")) { g_free(trace_file); trace_file =3D trace_opt_parse(optarg); + } else if (!strcmp(r, "hypertrace")) { + g_free(hypertrace_file); + hypertrace_opt_parse(optarg, &hypertrace_base, &hypertrace_s= ize); } else { usage(); } @@ -985,6 +996,11 @@ int main(int argc, char **argv) target_set_brk(info->brk); syscall_init(); signal_init(); + if (atexit(hypertrace_fini) !=3D 0) { + fprintf(stderr, "error: atexit: %s\n", strerror(errno)); + abort(); + } + hypertrace_init(hypertrace_base, hypertrace_size); =20 /* 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..faf255f 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" =20 //#define DEBUG_MMAP =20 @@ -407,6 +408,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, = int prot, } } the_end1: + hypertrace_guest_mmap(fd, (void *)g2h(start)); 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..275ef61 100644 --- a/bsd-user/syscall.c +++ b/bsd-user/syscall.c @@ -26,6 +26,7 @@ =20 #include "qemu.h" #include "qemu-common.h" +#include "hypertrace/user.h" =20 //#define DEBUG =20 @@ -332,6 +333,7 @@ abi_long do_freebsd_syscall(void *cpu_env, int num, a= bi_long arg1, _mcleanup(); #endif gdb_exit(cpu_env, arg1); + hypertrace_fini(); /* XXX: should free thread stack and CPU env */ _exit(arg1); ret =3D 0; /* avoid warning */ @@ -430,6 +432,7 @@ abi_long do_netbsd_syscall(void *cpu_env, int num, ab= i_long arg1, _mcleanup(); #endif gdb_exit(cpu_env, arg1); + hypertrace_fini(); /* XXX: should free thread stack and CPU env */ _exit(arg1); ret =3D 0; /* avoid warning */ @@ -505,6 +508,7 @@ abi_long do_openbsd_syscall(void *cpu_env, int num, a= bi_long arg1, _mcleanup(); #endif gdb_exit(cpu_env, arg1); + hypertrace_fini(); /* XXX: should free thread stack and CPU env */ _exit(arg1); ret =3D 0; /* avoid warning */ diff --git a/hypertrace/Makefile.objs b/hypertrace/Makefile.objs new file mode 100644 index 0000000..6eb5acf --- /dev/null +++ b/hypertrace/Makefile.objs @@ -0,0 +1,17 @@ +# -*- mode: makefile -*- + +target-obj-$(CONFIG_USER_ONLY) +=3D user.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 *data)" >$@ + @echo "{" >>$@ + @echo -n " trace_guest_hypertrace(cpu" >>$@ + @for i in `seq $(CONFIG_HYPERTRACE_ARGS)`; do \ + echo -n ", data[$$i-1]" >>$@; \ + done + @echo ");" >>$@ + @echo "}" >>$@ diff --git a/hypertrace/user.c b/hypertrace/user.c new file mode 100644 index 0000000..55df79d --- /dev/null +++ b/hypertrace/user.c @@ -0,0 +1,292 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016 Llu=C3=ADs Vilanova + * + * This work is licensed under the terms of the GNU GPL, version 2 or la= ter. + * See the COPYING file in the top-level directory. + */ + +/* + * Implementation details + * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * + * Both channels are provided as regular files in the host system, which= must be + * mmap'ed by the guest application. + * + * Data channel + * ------------ + * + * The guest must mmap a file named -data, where base is the argum= ent + * given to hypertrace_init. + * + * Regular memory accesses are used on the data channel. + * + * Control channel + * --------------- + * + * The guest must mmap a file named -control, where base is the ar= gument + * given to hypertrace_init. + * + * The first 64 bits of that memory contain the size of the data channel. + * + * The control channel is mprotect'ed by QEMU so that guest writes can b= e + * intercepted by QEMU in order to raise the "guest_hypertrace" tracing + * event. The guest must perform writes twice, one on each of two consec= utive + * pages, so that the written data can be both read by QEMU and the acce= ss + * intercepted. + */ + +#include +#include +#include +#include +#include +#include + +#include "qemu/osdep.h" +#include "cpu.h" + +#include "hypertrace/user.h" +#include "qemu/config-file.h" +#include "qemu/error-report.h" +#include "trace.h" + + +static char *data_path =3D NULL; +static char *control_path =3D NULL; +static int data_fd =3D -1; +static int control_fd =3D -1; + +static uint64_t *qemu_data =3D NULL; +static void *qemu_control_0 =3D NULL; +static void *qemu_control_1 =3D 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 =3D { + .name =3D "hypertrace", + .implied_opt_name =3D "path", + .head =3D QTAILQ_HEAD_INITIALIZER(qemu_hypertrace_opts.head), + .desc =3D { + { + .name =3D "path", + .type =3D QEMU_OPT_STRING, + }, + { + .name =3D "pages", + .type =3D QEMU_OPT_NUMBER, + .def_value_str =3D "1", + }, + { /* end of list */ } + }, +}; + +void hypertrace_opt_parse(const char *optarg, char **base, size_t *size) +{ + int pages; + QemuOpts *opts =3D qemu_opts_parse_noisily(qemu_find_opts("hypertrac= e"), + optarg, true); + if (!opts) { + exit(1); + } + if (qemu_opt_get(opts, "path")) { + *base =3D g_strdup(qemu_opt_get(opts, "path")); + } else { + *base =3D NULL; + } + pages =3D qemu_opt_get_number(opts, "pages", 1); + if (pages <=3D 0) { + error_report("Parameter 'pages' expects a positive number"); + exit(EXIT_FAILURE); + } + *size =3D pages * TARGET_PAGE_SIZE; +} + +static void init_channel(const char *base, const char *suffix, size_t si= ze, + char ** path, int *fd, uint64_t **addr) +{ + *path =3D g_malloc(strlen(base) + strlen(suffix) + 1); + sprintf(*path, "%s%s", base, suffix); + + *fd =3D open(*path, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); + if (*fd =3D=3D -1) { + error_report("error: open(%s): %s", *path, strerror(errno)); + abort(); + } + + off_t lres =3D lseek(*fd, size - 1, SEEK_SET); + if (lres =3D=3D (off_t)-1) { + error_report("error: lseek(%s): %s", *path, strerror(errno)); + abort(); + } + + char tmp; + ssize_t wres =3D write(*fd, &tmp, 1); + if (wres =3D=3D -1) { + error_report("error: write(%s): %s", *path, strerror(errno)); + abort(); + } + + if (addr) { + *addr =3D mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, *= fd, 0); + if (*addr =3D=3D 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, uint64_t data_size) +{ + if (base =3D=3D NULL) { + return; + } + + struct sigaction sigint; + memset(&sigint, 0, sizeof(sigint)); + sigint.sa_sigaction =3D fini_handler; + sigint.sa_flags =3D SA_SIGINFO | SA_RESTART; + if (sigaction(SIGINT, &sigint, NULL) !=3D 0) { + error_report("error: sigaction(SIGINT): %s", strerror(errno)); + abort(); + } + if (sigaction(SIGABRT, &sigint, NULL) !=3D 0) { + error_report("error: sigaction(SIGABRT): %s", strerror(errno)); + abort(); + } + + init_channel(base, "-data", data_size, &data_path, &data_fd, &qemu_d= ata); + uint64_t *control; + init_channel(base, "-control", getpagesize() * 2, &control_path, &co= ntrol_fd, &control); + + control[0] =3D tswap64(data_size / (CONFIG_HYPERTRACE_ARGS * sizeof(= uint64_t))); + + if (fstat(control_fd, &control_fd_stat) =3D=3D -1) { + error_report("error: fstat(hypertrace_control): %s", strerror(er= rno)); + abort(); + } + + struct sigaction segv; + memset(&segv, 0, sizeof(segv)); + segv.sa_sigaction =3D segv_handler; + segv.sa_flags =3D SA_SIGINFO | SA_RESTART; + sigemptyset(&segv.sa_mask); + + if (sigaction(SIGSEGV, &segv, &segv_next) !=3D 0) { + error_report("error: sigaction(SIGSEGV): %s", strerror(errno)); + abort(); + } +} + + +static void fini_channel(int *fd, char **path) +{ + if (*fd !=3D -1) { + if (close(*fd) =3D=3D -1) { + error_report("error: close: %s", strerror(errno)); + abort(); + } + if (unlink(*path) =3D=3D -1) { + error_report("error: unlink(%s): %s", *path, strerror(errno)= ); + abort(); + } + *fd =3D -1; + } + if (*path !=3D NULL) { + g_free(*path); + *path =3D NULL; + } +} + +void hypertrace_fini(void) +{ + static bool atexit_in =3D false; + if (atexit_in) { + return; + } + atexit_in =3D true; + + if (sigaction(SIGSEGV, &segv_next, NULL) !=3D 0) { + error_report("error: sigaction(SIGSEGV): %s", strerror(errno)); + abort(); + } + fini_channel(&data_fd, &data_path); + fini_channel(&control_fd, &control_path); +} + + +void hypertrace_guest_mmap(int fd, void *qemu_addr) +{ + struct stat s; + if (fstat(fd, &s) !=3D 0) { + return; + } + + if (s.st_dev !=3D control_fd_stat.st_dev || + s.st_ino !=3D 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) + */ + qemu_control_0 =3D qemu_addr; + qemu_control_1 =3D qemu_control_0 + getpagesize(); + if (mprotect(qemu_control_0, getpagesize(), PROT_READ) =3D=3D -1) { + error_report("error: mprotect(hypertrace_control): %s", strerror= (errno)); + abort(); + } +} + +static void swap_control(void *from, void *to) +{ + if (mprotect(from, getpagesize(), PROT_READ | PROT_WRITE) =3D=3D -1)= { + error_report("error: mprotect(from): %s", strerror(errno)); + abort(); + } + if (mprotect(to, getpagesize(), PROT_READ) =3D=3D -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) +{ + if (qemu_control_0 <=3D siginfo->si_addr && + siginfo->si_addr < qemu_control_1) { + + /* 1st fault (guest will write cmd) */ + assert(((unsigned long)siginfo->si_addr % getpagesize()) =3D=3D = sizeof(uint64_t)); + swap_control(qemu_control_0, qemu_control_1); + + } else if (qemu_control_1 <=3D siginfo->si_addr && + siginfo->si_addr < qemu_control_1 + getpagesize()) { + uint64_t vcontrol =3D ((uint64_t*)qemu_control_0)[2]; + uint64_t *data_ptr =3D &qemu_data[vcontrol * CONFIG_HYPERTRACE_A= RGS * sizeof(uint64_t)]; + + /* 2nd fault (invoke) */ + assert(((unsigned long)siginfo->si_addr % getpagesize()) =3D=3D = sizeof(uint64_t)); + hypertrace_emit(current_cpu, data_ptr); + swap_control(qemu_control_1, qemu_control_0); + + } else { + /* proxy to next handler */ + if (segv_next.sa_sigaction !=3D NULL) { + segv_next.sa_sigaction(signum, siginfo, sigctxt); + } else if (segv_next.sa_handler !=3D NULL) { + segv_next.sa_handler(signum); + } + } +} diff --git a/hypertrace/user.h b/hypertrace/user.h new file mode 100644 index 0000000..a13bae4 --- /dev/null +++ b/hypertrace/user.h @@ -0,0 +1,52 @@ +/* + * QEMU-side management of hypertrace in user-level emulation. + * + * Copyright (C) 2016 Llu=C3=ADs Vilanova + * + * This work is licensed under the terms of the GNU GPL, version 2 or la= ter. + * See the COPYING file in the top-level directory. + */ + +#include +#include + + +/** + * Definition of QEMU options describing hypertrace subsystem configurat= ion + */ +extern QemuOptsList qemu_hypertrace_opts; + +/** + * hypertrace_opt_parse: + * @optarg: Input arguments. + * @base: Output base path for the hypertrace channel files. + * @data_size: Output length in bytes for the data channel. + * + * Parse the commandline arguments for hypertrace. + */ +void hypertrace_opt_parse(const char *optarg, char **base, size_t *size)= ; + +/** + * hypertrace_init: + * @base: Base path for the hypertrace channel files. + * @data_size: Length in bytes for the data channel. + * + * Initialize the backing files for the hypertrace channel. + */ +void hypertrace_init(const char *base, uint64_t data_size); + +/** + * hypertrace_guest_mmap: + * + * Check if this mmap is for the control channel and act accordingly. + * + * Precondition: defined(CONFIG_USER_ONLY) + */ +void hypertrace_guest_mmap(int fd, void *qemu_addr); + +/** + * hypertrace_fini: + * + * Remove the backing files for the hypertrace channel. + */ +void hypertrace_fini(void); diff --git a/linux-user/main.c b/linux-user/main.c index 462e820..8f3d9cf 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" =20 char *exec_path; =20 @@ -4011,6 +4013,14 @@ static void handle_arg_trace(const char *arg) trace_file =3D trace_opt_parse(arg); } =20 +static char *hypertrace_base; +static size_t hypertrace_size; +static void handle_arg_hypertrace(const char *arg) +{ + g_free(hypertrace_base); + hypertrace_opt_parse(arg, &hypertrace_base, &hypertrace_size); +} + struct qemu_argument { const char *argv; const char *env; @@ -4060,6 +4070,8 @@ static const struct qemu_argument arg_table[] =3D { "", "Seed for pseudo-random number generator"}, {"trace", "QEMU_TRACE", true, handle_arg_trace, "", "[[enable=3D]][,events=3D][,file=3D]"}, + {"hypertrace", "QEMU_HYPERTRACE", true, handle_arg_hypertrace, + "", "[[base=3D]][,pages=3D]"}, {"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)); =20 qemu_add_opts(&qemu_trace_opts); + qemu_add_opts(&qemu_hypertrace_opts); =20 optind =3D parse_args(argc, argv); =20 @@ -4448,6 +4461,12 @@ int main(int argc, char **argv, char **envp) syscall_init(); signal_init(); =20 + if (atexit(hypertrace_fini)) { + 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 the real value of GUEST_BASE into account. */ diff --git a/linux-user/mmap.c b/linux-user/mmap.c index c4371d9..3207d98 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" =20 //#define DEBUG_MMAP =20 @@ -553,6 +554,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, = int prot, } } the_end1: + hypertrace_guest_mmap(fd, (void *)g2h(start)); page_set_flags(start, start + len, prot | PAGE_VALID); the_end: #ifdef DEBUG_MMAP diff --git a/linux-user/syscall.c b/linux-user/syscall.c index ca6a2b4..e73ec5d 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -111,6 +111,7 @@ int __clone2(int (*fn)(void *), void *child_stack_bas= e, #include "uname.h" =20 #include "qemu.h" +#include "hypertrace/user.h" =20 #define CLONE_NPTL_FLAGS2 (CLONE_SETTLS | \ CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID) @@ -7214,6 +7215,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_lon= g arg1, _mcleanup(); #endif gdb_exit(cpu_env, arg1); + hypertrace_fini(); _exit(arg1); ret =3D 0; /* avoid warning */ break; @@ -9219,6 +9221,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_lon= g arg1, _mcleanup(); #endif gdb_exit(cpu_env, arg1); + hypertrace_fini(); ret =3D get_errno(exit_group(arg1)); break; #endif