diff -r 2c4c77b64b02 -r 378fba25130d tools/misc/Makefile --- a/tools/misc/Makefile Wed Jan 05 19:36:17 2011 -0800 +++ b/tools/misc/Makefile Fri Jan 07 10:02:16 2011 -0800 @@ -11,7 +11,7 @@ CFLAGS += $(INCLUDES) HDRS = $(wildcard *.h) TARGETS-y := xenperf xenpm xen-tmem-list-parse gtraceview gtracestat xenlockprof xenwatchdogd -TARGETS-$(CONFIG_X86) += xen-detect xen-hvmctx xen-hvmcrash +TARGETS-$(CONFIG_X86) += xen-detect xen-hvmctx xen-hvmcrash xen-access TARGETS-$(CONFIG_MIGRATE) += xen-hptool TARGETS := $(TARGETS-y) @@ -24,7 +24,7 @@ INSTALL_BIN-$(CONFIG_X86) += xen-detect INSTALL_BIN := $(INSTALL_BIN-y) INSTALL_SBIN-y := xm xen-bugtool xen-python-path xend xenperf xsview xenpm xen-tmem-list-parse gtraceview gtracestat xenlockprof xenwatchdogd -INSTALL_SBIN-$(CONFIG_X86) += xen-hvmctx xen-hvmcrash +INSTALL_SBIN-$(CONFIG_X86) += xen-hvmctx xen-hvmcrash xen-access INSTALL_SBIN-$(CONFIG_MIGRATE) += xen-hptool INSTALL_SBIN := $(INSTALL_SBIN-y) @@ -51,7 +51,7 @@ clean: %.o: %.c $(HDRS) Makefile $(CC) -c $(CFLAGS) -o $@ $< -xen-hvmctx xen-hvmcrash xenperf xenpm gtracestat xenlockprof xen-hptool xenwatchdogd: %: %.o Makefile +xen-hvmctx xen-hvmcrash xenperf xenpm gtracestat xenlockprof xen-hptool xenwatchdogd xen-access: %: %.o Makefile $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS_libxenctrl) $(LDLIBS_libxenguest) $(LDLIBS_libxenstore) gtraceview: %: %.o Makefile diff -r 2c4c77b64b02 -r 378fba25130d tools/misc/xen-access.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/misc/xen-access.c Fri Jan 07 10:02:16 2011 -0800 @@ -0,0 +1,668 @@ +/* + * xen-access.c + * + * Exercises the basic per-page access mechanisms + * + * Copyright (c) 2011 Virtuata, Inc. + * Copyright (c) 2009 by Citrix Systems, Inc. (Patrick Colp), based on + * xenpaging.c + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +//#if 0 +#undef DPRINTF +#define DPRINTF(a, b...) fprintf(stderr, a, ## b) +//#endif + +/* Spinlock and mem event definitions */ + +#define SPIN_LOCK_UNLOCKED 0 + +#define ADDR (*(volatile long *) addr) +/** + * test_and_set_bit - Set a bit and return its old value + * @nr: Bit to set + * @addr: Address to count from + * + * This operation is atomic and cannot be reordered. + * It also implies a memory barrier. + */ +static inline int test_and_set_bit(int nr, volatile void *addr) +{ + int oldbit; + + asm volatile ( + "btsl %2,%1\n\tsbbl %0,%0" + : "=r" (oldbit), "=m" (ADDR) + : "Ir" (nr), "m" (ADDR) : "memory"); + return oldbit; +} + +typedef int spinlock_t; + +static inline void spin_lock(spinlock_t *lock) +{ + while ( test_and_set_bit(1, lock) ); +} + +static inline void spin_lock_init(spinlock_t *lock) +{ + *lock = SPIN_LOCK_UNLOCKED; +} + +static inline void spin_unlock(spinlock_t *lock) +{ + *lock = SPIN_LOCK_UNLOCKED; +} + +static inline int spin_trylock(spinlock_t *lock) +{ + return !test_and_set_bit(1, lock); +} + +#define mem_event_ring_lock_init(_m) spin_lock_init(&(_m)->ring_lock) +#define mem_event_ring_lock(_m) spin_lock(&(_m)->ring_lock) +#define mem_event_ring_unlock(_m) spin_unlock(&(_m)->ring_lock) + +typedef struct mem_event { + domid_t domain_id; + xc_evtchn *xce_handle; + int port; + mem_event_back_ring_t back_ring; + mem_event_shared_page_t *shared_page; + void *ring_page; + spinlock_t ring_lock; +} mem_event_t; + +typedef struct xc_platform_info { + unsigned long max_mfn; + unsigned long hvirt_start; + unsigned int pt_levels; + unsigned int guest_width; +} xc_platform_info_t; + +typedef struct xenaccess { + xc_interface *xc_handle; + + xc_platform_info_t *platform_info; + xc_domaininfo_t *domain_info; + + mem_event_t mem_event; +} xenaccess_t; + +static int interrupted; + +static void close_handler(int sig) +{ + interrupted = sig; +} + +int xc_wait_for_event_or_timeout(xc_interface *xch, xc_evtchn *xce, unsigned long ms) +{ + struct pollfd fd = { .fd = xc_evtchn_fd(xce), .events = POLLIN | POLLERR }; + int port; + int rc; + + rc = poll(&fd, 1, ms); + if ( rc == -1 ) + { + if (errno == EINTR) + return 0; + + ERROR("Poll exited with an error"); + goto err; + } + + if ( rc == 1 ) + { + port = xc_evtchn_pending(xce); + if ( port == -1 ) + { + ERROR("Failed to read port from event channel"); + goto err; + } + + rc = xc_evtchn_unmask(xce, port); + if ( rc != 0 ) + { + ERROR("Failed to unmask event channel port"); + goto err; + } + } + else + port = -1; + + return port; + + err: + return -errno; +} + +static void *init_page(void) +{ + void *buffer; + int ret; + + /* Allocated page memory */ + ret = posix_memalign(&buffer, PAGE_SIZE, PAGE_SIZE); + if ( ret != 0 ) + goto out_alloc; + + /* Lock buffer in memory so it can't be paged out */ + ret = mlock(buffer, PAGE_SIZE); + if ( ret != 0 ) + goto out_lock; + + return buffer; + + munlock(buffer, PAGE_SIZE); + out_lock: + free(buffer); + out_alloc: + return NULL; +} + +xenaccess_t *xenaccess_init(xc_interface **xch_r, domid_t domain_id) +{ + xenaccess_t *xenaccess; + xc_interface *xch; + int rc; + + xch = xc_interface_open(NULL, NULL, 0); + if ( !xch ) + goto err_iface; + + DPRINTF("xenaccess init\n"); + *xch_r = xch; + + /* Allocate memory */ + xenaccess = malloc(sizeof(xenaccess_t)); + memset(xenaccess, 0, sizeof(xenaccess_t)); + + /* Open connection to xen */ + xenaccess->xc_handle = xch; + + /* Set domain id */ + xenaccess->mem_event.domain_id = domain_id; + + /* Initialise shared page */ + xenaccess->mem_event.shared_page = init_page(); + if ( xenaccess->mem_event.shared_page == NULL ) + { + ERROR("Error initialising shared page"); + goto err; + } + + /* Initialise ring page */ + xenaccess->mem_event.ring_page = init_page(); + if ( xenaccess->mem_event.ring_page == NULL ) + { + ERROR("Error initialising ring page"); + goto err; + } + + + /* Initialise ring */ + SHARED_RING_INIT((mem_event_sring_t *)xenaccess->mem_event.ring_page); + BACK_RING_INIT(&xenaccess->mem_event.back_ring, + (mem_event_sring_t *)xenaccess->mem_event.ring_page, + PAGE_SIZE); + + /* Initialise lock */ + mem_event_ring_lock_init(&xenaccess->mem_event); + + /* Initialise Xen */ + rc = xc_mem_event_enable(xenaccess->xc_handle, xenaccess->mem_event.domain_id, + xenaccess->mem_event.shared_page, + xenaccess->mem_event.ring_page); + if ( rc != 0 ) + { + switch ( errno ) { + case EBUSY: + ERROR("xenaccess is (or was) active on this domain"); + break; + case ENODEV: + ERROR("EPT not supported for this guest"); + break; + default: + perror("Error initialising shared page"); + break; + } + goto err; + } + + /* Open event channel */ + xenaccess->mem_event.xce_handle = xc_evtchn_open(NULL, 0); + if ( xenaccess->mem_event.xce_handle == NULL ) + { + ERROR("Failed to open event channel"); + goto err; + } + + /* Bind event notification */ + rc = xc_evtchn_bind_interdomain(xenaccess->mem_event.xce_handle, + xenaccess->mem_event.domain_id, + xenaccess->mem_event.shared_page->port); + if ( rc < 0 ) + { + ERROR("Failed to bind event channel"); + goto err; + } + + xenaccess->mem_event.port = rc; + + /* Get platform info */ + xenaccess->platform_info = malloc(sizeof(xc_platform_info_t)); + if ( xenaccess->platform_info == NULL ) + { + ERROR("Error allocating memory for platform info"); + goto err; + } + + rc = get_platform_info(xenaccess->xc_handle, domain_id, + &xenaccess->platform_info->max_mfn, + &xenaccess->platform_info->hvirt_start, + &xenaccess->platform_info->pt_levels, + &xenaccess->platform_info->guest_width); + if ( rc != 1 ) + { + ERROR("Error getting platform info"); + goto err; + } + + /* Get domaininfo */ + xenaccess->domain_info = malloc(sizeof(xc_domaininfo_t)); + if ( xenaccess->domain_info == NULL ) + { + ERROR("Error allocating memory for domain info"); + goto err; + } + + rc = xc_domain_getinfolist(xenaccess->xc_handle, domain_id, 1, + xenaccess->domain_info); + if ( rc != 1 ) + { + ERROR("Error getting domain info"); + goto err; + } + + DPRINTF("max_pages = %"PRIx64"\n", xenaccess->domain_info->max_pages); + + return xenaccess; + + err: + if ( xenaccess ) + { + if ( xenaccess->mem_event.shared_page ) + { + munlock(xenaccess->mem_event.shared_page, PAGE_SIZE); + free(xenaccess->mem_event.shared_page); + } + + if ( xenaccess->mem_event.ring_page ) + { + munlock(xenaccess->mem_event.ring_page, PAGE_SIZE); + free(xenaccess->mem_event.ring_page); + } + + free(xenaccess->platform_info); + free(xenaccess->domain_info); + free(xenaccess); + } + + err_iface: + return NULL; +} + +int xenaccess_teardown(xc_interface *xch, xenaccess_t *xenaccess) +{ + int rc; + + if ( xenaccess == NULL ) + return 0; + + /* Tear down domain xenaccess in Xen */ + rc = xc_mem_event_disable(xenaccess->xc_handle, xenaccess->mem_event.domain_id); + if ( rc != 0 ) + { + ERROR("Error tearing down domain xenaccess in xen"); + } + + /* Unbind VIRQ */ + rc = xc_evtchn_unbind(xenaccess->mem_event.xce_handle, xenaccess->mem_event.port); + if ( rc != 0 ) + { + ERROR("Error unbinding event port"); + } + xenaccess->mem_event.port = -1; + + /* Close event channel */ + rc = xc_evtchn_close(xenaccess->mem_event.xce_handle); + if ( rc != 0 ) + { + ERROR("Error closing event channel"); + } + xenaccess->mem_event.xce_handle = NULL; + + /* Close connection to Xen */ + rc = xc_interface_close(xenaccess->xc_handle); + if ( rc != 0 ) + { + ERROR("Error closing connection to xen"); + } + xenaccess->xc_handle = NULL; + + return 0; +} + +int get_request(mem_event_t *mem_event, mem_event_request_t *req) +{ + mem_event_back_ring_t *back_ring; + RING_IDX req_cons; + + mem_event_ring_lock(mem_event); + + back_ring = &mem_event->back_ring; + req_cons = back_ring->req_cons; + + /* Copy request */ + memcpy(req, RING_GET_REQUEST(back_ring, req_cons), sizeof(*req)); + req_cons++; + + /* Update ring */ + back_ring->req_cons = req_cons; + back_ring->sring->req_event = req_cons + 1; + + mem_event_ring_unlock(mem_event); + + return 0; +} + +static int put_response(mem_event_t *mem_event, mem_event_response_t *rsp) +{ + mem_event_back_ring_t *back_ring; + RING_IDX rsp_prod; + + mem_event_ring_lock(mem_event); + + back_ring = &mem_event->back_ring; + rsp_prod = back_ring->rsp_prod_pvt; + + /* Copy response */ + memcpy(RING_GET_RESPONSE(back_ring, rsp_prod), rsp, sizeof(*rsp)); + rsp_prod++; + + /* Update ring */ + back_ring->rsp_prod_pvt = rsp_prod; + RING_PUSH_RESPONSES(back_ring); + + mem_event_ring_unlock(mem_event); + + return 0; +} + +static int xenaccess_resume_page(xenaccess_t *paging, mem_event_response_t *rsp) +{ + int ret; + + /* Put the page info on the ring */ + ret = put_response(&paging->mem_event, rsp); + if ( ret != 0 ) + goto out; + + /* Tell Xen page is ready */ + ret = xc_mem_access_resume(paging->xc_handle, paging->mem_event.domain_id, + rsp->gfn); + ret = xc_evtchn_notify(paging->mem_event.xce_handle, + paging->mem_event.port); + + out: + return ret; +} + +void usage(char* progname) +{ + fprintf(stderr, + "Usage: %s [-m] write|exec|int3\n" + "\n" + "Logs first page writes, execs, or int3 traps that occur on the domain.\n" + "\n" + "-m requires this program to run, or else the domain may pause\n", + progname); +} + +int main(int argc, char *argv[]) +{ + struct sigaction act; + domid_t domain_id; + xenaccess_t *xenaccess; + mem_event_request_t req; + mem_event_response_t rsp; + int rc = -1; + int rc1; + xc_interface *xch; + hvmmem_access_t default_access = HVMMEM_access_rwx; + hvmmem_access_t after_first_access = HVMMEM_access_rwx; + int required = 0; + int int3 = 0; + int shutting_down = 0; + + char* progname = argv[0]; + argv++; + argc--; + + if ( argc == 3 && argv[0][0] == '-' ) + { + argv++; + argc--; + + if ( !strcmp(argv[0], "-m") ) + required = 1; + else + { + usage(progname); + return -1; + } + } + + if ( argc != 2 ) + { + usage(progname); + return -1; + } + + domain_id = atoi(argv[0]); + argv++; + argc--; + + if ( !strcmp(argv[0], "write") ) + { + default_access = HVMMEM_access_rx; + after_first_access = HVMMEM_access_rwx; + } + else if ( !strcmp(argv[0], "exec") ) + { + default_access = HVMMEM_access_rw; + after_first_access = HVMMEM_access_rwx; + } + else if ( !strcmp(argv[0], "int3") ) + { + int3 = 1; + } + else + { + usage(argv[0]); + return -1; + } + + xenaccess = xenaccess_init(&xch, domain_id); + if ( xenaccess == NULL ) + { + ERROR("Error initialising xenaccess"); + return 1; + } + + DPRINTF("starting %s %u\n", argv[0], domain_id); + + /* ensure that if we get a signal, we'll do cleanup, then exit */ + act.sa_handler = close_handler; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + sigaction(SIGALRM, &act, NULL); + + /* Set whether the access listener is required */ + xc_domain_set_access_required(xch, domain_id, required); + + /* Set the default access type and convert all pages to it */ + rc = xc_hvm_set_mem_access(xch, domain_id, default_access, ~0ull, 0); + rc = xc_hvm_set_mem_access(xch, domain_id, default_access, 0, xenaccess->domain_info->max_pages); + + if ( int3 ) + rc = xc_set_hvm_param(xch, domain_id, HVM_PARAM_MEMORY_EVENT_INT3, HVMPME_mode_sync); + else + rc = xc_set_hvm_param(xch, domain_id, HVM_PARAM_MEMORY_EVENT_INT3, HVMPME_mode_disabled); + + /* Wait for access */ + for (;;) + { + if ( interrupted ) + { + DPRINTF("xenaccess shutting down on signal %d\n", interrupted); + + /* Unregister for every event */ + rc = xc_hvm_set_mem_access(xch, domain_id, HVMMEM_access_rwx, ~0ull, 0); + rc = xc_hvm_set_mem_access(xch, domain_id, HVMMEM_access_rwx, 0, xenaccess->domain_info->max_pages); + rc = xc_set_hvm_param(xch, domain_id, HVM_PARAM_MEMORY_EVENT_INT3, HVMPME_mode_disabled); + + shutting_down = 1; + } + + rc = xc_wait_for_event_or_timeout(xch, xenaccess->mem_event.xce_handle, 100); + if ( rc < -1 ) + { + ERROR("Error getting event"); + interrupted = -1; + continue; + } + else if ( rc != -1 ) + { + DPRINTF("Got event from Xen\n"); + } + + while ( RING_HAS_UNCONSUMED_REQUESTS(&xenaccess->mem_event.back_ring) ) + { + hvmmem_access_t access; + + rc = get_request(&xenaccess->mem_event, &req); + if ( rc != 0 ) + { + ERROR("Error getting request"); + interrupted = -1; + continue; + } + + memset( &rsp, 0, sizeof (rsp) ); + rsp.vcpu_id = req.vcpu_id; + rsp.flags = req.flags; + + switch (req.reason) { + case MEM_EVENT_REASON_VIOLATION: + rc = xc_hvm_get_mem_access(xch, domain_id, req.gfn, &access); + + printf("PAGE ACCESS: %c%c%c for GFN %lx (offset %06lx) gla %016lx (vcpu %d)\n", + req.access_r ? 'r' : '-', + req.access_w ? 'w' : '-', + req.access_x ? 'x' : '-', + req.gfn, + req.offset, + req.gla, + req.vcpu_id); + + if ( default_access != after_first_access ) + rc = xc_hvm_set_mem_access(xch, domain_id, after_first_access, req.gfn, 1); + + + rsp.gfn = req.gfn; + rsp.p2mt = req.p2mt; + break; + case MEM_EVENT_REASON_INT3: + printf("INT3: rip=%016lx, gfn=%lx (vcpu %d)\n", req.gla, req.gfn, + req.vcpu_id); + + /* Reinject */ + rc = xc_hvm_inject_trap(xch, domain_id, req.vcpu_id, 3, -1, 0); + + break; + default: + fprintf(stderr, "UNKNOWN REASON CODE %d\n", req.reason); + } + + rc = xenaccess_resume_page(xenaccess, &rsp); + if ( rc != 0 ) + { + ERROR("Error resuming page"); + interrupted = -1; + continue; + } + } + + if ( shutting_down ) + break; + } + DPRINTF("xenaccess shut down on signal %d\n", interrupted); + + /* Tear down domain xenaccess */ + rc1 = xenaccess_teardown(xch, xenaccess); + if ( rc1 != 0 ) + ERROR("Error tearing down xenaccess"); + + if ( rc == 0 ) + rc = rc1; + + xc_interface_close(xch); + + DPRINTF("xenaccess exit code %d\n", rc); + return rc; +} + + +/* + * Local variables: + * mode: C + * c-set-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */