From: Valerio Aimale <vale...@aimale.com> --- Makefile.target | 2 +- hmp-commands.hx | 14 ++++ hmp.c | 9 +++ hmp.h | 1 + memory-access.c | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ memory-access.h | 21 ++++++ qapi-schema.json | 28 ++++++++ qmp-commands.hx | 23 +++++++ 8 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 memory-access.c create mode 100644 memory-access.h
diff --git a/Makefile.target b/Makefile.target index 962d004..940ab51 100644 --- a/Makefile.target +++ b/Makefile.target @@ -131,7 +131,7 @@ endif #CONFIG_BSD_USER ######################################################### # System emulator target ifdef CONFIG_SOFTMMU -obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o numa.o +obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o numa.o memory-access.o obj-y += qtest.o bootdevice.o obj-y += hw/ obj-$(CONFIG_KVM) += kvm-all.o diff --git a/hmp-commands.hx b/hmp-commands.hx index 3a4ae39..37d3bb5 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -807,6 +807,20 @@ save to disk physical memory dump starting at @var{addr} of size @var{size}. ETEXI { + .name = "pmemaccess", + .args_type = "path:s", + .params = "file", + .help = "open A UNIX Socket access to physical memory at 'path'", + .mhandler.cmd = hmp_pmemaccess, + }, + +STEXI +@item pmemaccess @var{file} +@findex pmemaccess +Mount guest physical memory image at 'path' +ETEXI + + { .name = "boot_set", .args_type = "bootdevice:s", .params = "bootdevice", diff --git a/hmp.c b/hmp.c index 5048eee..444dbf6 100644 --- a/hmp.c +++ b/hmp.c @@ -916,6 +916,15 @@ void hmp_pmemsave(Monitor *mon, const QDict *qdict) hmp_handle_error(mon, &err); } +void hmp_pmemaccess(Monitor *mon, const QDict *qdict) +{ + const char *path = qdict_get_str(qdict, "path"); + Error *err = NULL; + + qmp_pmemaccess(path, &err); + hmp_handle_error(mon, &err); +} + void hmp_ringbuf_write(Monitor *mon, const QDict *qdict) { const char *chardev = qdict_get_str(qdict, "device"); diff --git a/hmp.h b/hmp.h index 81656c3..8225deb 100644 --- a/hmp.h +++ b/hmp.h @@ -47,6 +47,7 @@ void hmp_system_powerdown(Monitor *mon, const QDict *qdict); void hmp_cpu(Monitor *mon, const QDict *qdict); void hmp_memsave(Monitor *mon, const QDict *qdict); void hmp_pmemsave(Monitor *mon, const QDict *qdict); +void hmp_pmemaccess(Monitor *mon, const QDict *qdict); void hmp_ringbuf_write(Monitor *mon, const QDict *qdict); void hmp_ringbuf_read(Monitor *mon, const QDict *qdict); void hmp_cont(Monitor *mon, const QDict *qdict); diff --git a/memory-access.c b/memory-access.c new file mode 100644 index 0000000..58099a8 --- /dev/null +++ b/memory-access.c @@ -0,0 +1,206 @@ +/* + * Access guest physical memory via a domain socket. + * + * Copyright (C) 2011 Sandia National Laboratories + * Original Author: Bryan D. Payne (bdpa...@acm.org) + * + * Refurbished for modern QEMU by Valerio Aimale (vale...@aimale.com), in 2015 + */ + +#include "memory-access.h" +//#include "cpu-all.h" +#include "qemu-common.h" +#include "exec/cpu-common.h" +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <pthread.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <signal.h> +#include <stdint.h> + +struct request{ + uint8_t type; // 0 quit, 1 read, 2 write, ... rest reserved + uint64_t address; // address to read from OR write to + uint64_t length; // number of bytes to read OR write +}; + +static uint64_t +connection_read_memory (uint64_t user_paddr, void *buf, uint64_t user_len) +{ + hwaddr paddr = (hwaddr) user_paddr; + hwaddr len = (hwaddr) user_len; + void *guestmem = cpu_physical_memory_map(paddr, &len, 0); + if (!guestmem){ + return 0; + } + memcpy(buf, guestmem, len); + cpu_physical_memory_unmap(guestmem, len, 0, len); + + return len; +} + +static uint64_t +connection_write_memory (uint64_t user_paddr, void *buf, uint64_t user_len) +{ + hwaddr paddr = (hwaddr) user_paddr; + hwaddr len = (hwaddr) user_len; + void *guestmem = cpu_physical_memory_map(paddr, &len, 1); + if (!guestmem){ + return 0; + } + memcpy(guestmem, buf, len); + cpu_physical_memory_unmap(guestmem, len, 0, len); + + return len; +} + +static void +send_success_ack (int connection_fd) +{ + uint8_t success = 1; + int nbytes = write(connection_fd, &success, 1); + if (1 != nbytes){ + fprintf(stderr, "Qemu pmemaccess: failed to send success ack\n"); + } +} + +static void +send_fail_ack (int connection_fd) +{ + uint8_t fail = 0; + int nbytes = write(connection_fd, &fail, 1); + if (1 != nbytes){ + fprintf(stderr, "Qemu pmemaccess: failed to send fail ack\n"); + } +} + +static void +connection_handler (int connection_fd) +{ + int nbytes; + struct request req; + + while (1){ + // client request should match the struct request format + nbytes = read(connection_fd, &req, sizeof(struct request)); + if (nbytes != sizeof(struct request)){ + // error + continue; + } + else if (req.type == 0){ + // request to quit, goodbye + break; + } + else if (req.type == 1){ + // request to read + char *buf = malloc(req.length + 1); + nbytes = connection_read_memory(req.address, buf, req.length); + if (nbytes != req.length){ + // read failure, return failure message + buf[req.length] = 0; // set last byte to 0 for failure + nbytes = write(connection_fd, buf, 1); + } + else{ + // read success, return bytes + buf[req.length] = 1; // set last byte to 1 for success + nbytes = write(connection_fd, buf, nbytes + 1); + } + free(buf); + } + else if (req.type == 2){ + // request to write + void *write_buf = malloc(req.length); + nbytes = read(connection_fd, &write_buf, req.length); + if (nbytes != req.length){ + // failed reading the message to write + send_fail_ack(connection_fd); + } + else{ + // do the write + nbytes = connection_write_memory(req.address, write_buf, req.length); + if (nbytes == req.length){ + send_success_ack(connection_fd); + } + else{ + send_fail_ack(connection_fd); + } + } + free(write_buf); + } + else{ + // unknown command + fprintf(stderr, "Qemu pmemaccess: ignoring unknown command (%d)\n", req.type); + char *buf = malloc(1); + buf[0] = 0; + nbytes = write(connection_fd, buf, 1); + free(buf); + } + } + + close(connection_fd); +} + +static void * +memory_access_thread (void *p) +{ + struct sockaddr_un address; + int socket_fd, connection_fd; + socklen_t address_length; + struct pmemaccess_args *pargs = (struct pmemaccess_args *)p; + + socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (socket_fd < 0){ + error_setg(pargs->errp, "Qemu pmemaccess: socket failed"); + goto error_exit; + } + unlink(pargs->path); + address.sun_family = AF_UNIX; + address_length = sizeof(address.sun_family) + sprintf(address.sun_path, "%s", (char *) pargs->path); + + if (bind(socket_fd, (struct sockaddr *) &address, address_length) != 0){ + error_setg(pargs->errp, "Qemu pmemaccess: bind failed"); + goto error_exit; + } + if (listen(socket_fd, 0) != 0){ + error_setg(pargs->errp, "Qemu pmemaccess: listen failed"); + goto error_exit; + } + + connection_fd = accept(socket_fd, (struct sockaddr *) &address, &address_length); + connection_handler(connection_fd); + + close(socket_fd); + unlink(pargs->path); + free(pargs->path); + free(pargs); +error_exit: + return NULL; +} + +void +qmp_pmemaccess (const char *path, Error **errp) +{ + pthread_t thread; + sigset_t set, oldset; + struct pmemaccess_args *pargs; + + // create the args struct + pargs = (struct pmemaccess_args *) malloc(sizeof(struct pmemaccess_args)); + pargs->errp = errp; + // create a copy of path that we can safely use + pargs->path = malloc(strlen(path) + 1); + memcpy(pargs->path, path, strlen(path) + 1); + + // start the thread + sigfillset(&set); + pthread_sigmask(SIG_SETMASK, &set, &oldset); + pthread_create(&thread, NULL, memory_access_thread, pargs); + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + +} diff --git a/memory-access.h b/memory-access.h new file mode 100644 index 0000000..ff3d2f9 --- /dev/null +++ b/memory-access.h @@ -0,0 +1,21 @@ +/* + * Mount guest physical memory using FUSE. + * + * Author: Valerio G. Aimale <vale...@aimale.com> + */ + +#ifndef MEMORY_ACCESS_H +#define MEMORY_ACCESS_H + +#include "qapi-types.h" +#include "qapi/qmp/qdict.h" +#include "qapi/error.h" + +void qmp_pmemaccess (const char *path, Error **errp); + +struct pmemaccess_args { + char *path; + Error **errp; +}; + +#endif /* MEMORY_ACCESS_H */ diff --git a/qapi-schema.json b/qapi-schema.json index a386605..24d4070 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1427,6 +1427,34 @@ 'data': {'val': 'int', 'size': 'int', 'filename': 'str'} } ## +# @pmemaccess: +# +# Open A UNIX Socket access to physical memory +# +# @path: the name of the UNIX socket pipe +# +# Returns: Nothing on success +# +# Since: 2.4.0.1 +# +# Notes: Derived from previously existing patches. When command +# succeeds connect to the open socket. Write a binary structure to +# the socket as: +# +# struct request { +# uint8_t type; // 0 quit, 1 read, 2 write, ... rest reserved +# uint64_t address; // address to read from OR write to +# uint64_t length; // number of bytes to read OR write +# }; +# +# If it is a read operation, Qemu will return lenght+1 bytes. Read lenght+1 +# bytes. the last byte will be a 1 for success, or a 0 for failure. +# +## +{ 'command': 'pmemaccess', + 'data': {'path': 'str'} } + +## # @cont: # # Resume guest VCPU execution. diff --git a/qmp-commands.hx b/qmp-commands.hx index d2ba800..26e4a51 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -472,6 +472,29 @@ Example: EQMP { + .name = "pmemaccess", + .args_type = "path:s", + .mhandler.cmd_new = qmp_marshal_input_pmemaccess, + }, + +SQMP +pmemaccess +---------- + +Open A UNIX Socket access to physical memory + +Arguments: + +- "path": mount point path (json-string) + +Example: + +-> { "execute": "pmemaccess", + "arguments": { "path": "/tmp/guestname" } } +<- { "return": {} } + +EQMP + { .name = "inject-nmi", .args_type = "", .mhandler.cmd_new = qmp_marshal_inject_nmi, -- 2.1.4