[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH RFC v1] x86/emulate: Send vm_event form emulate
This patch aims to have mem access vm events sent from the emulator. This is useful in the case of page-walks that have to emulate instructions in access denied pages. We use hvmemul_map_linear_addr() ro intercept r/w access and hvmemul_insn_fetch() to intercept exec access. First we try to send a vm event and if the event is sent then emulation returns X86EMUL_ACCESS_EXCEPTION. If the event is not sent then the emulation goes on as expected. Signed-off-by: Alexandru Isaila <aisaila@xxxxxxxxxxxxxxx> --- xen/arch/x86/hvm/emulate.c | 298 +++++++++++++++++-------- xen/arch/x86/hvm/vm_event.c | 2 +- xen/arch/x86/mm/mem_access.c | 4 +- xen/arch/x86/x86_emulate/x86_emulate.h | 1 + xen/include/asm-x86/hvm/emulate.h | 4 +- 5 files changed, 212 insertions(+), 97 deletions(-) diff --git a/xen/arch/x86/hvm/emulate.c b/xen/arch/x86/hvm/emulate.c index 2d02ef1521..f43aed379b 100644 --- a/xen/arch/x86/hvm/emulate.c +++ b/xen/arch/x86/hvm/emulate.c @@ -15,6 +15,7 @@ #include <xen/paging.h> #include <xen/trace.h> #include <xen/vm_event.h> +#include <xen/monitor.h> #include <asm/event.h> #include <asm/i387.h> #include <asm/xstate.h> @@ -26,6 +27,8 @@ #include <asm/hvm/support.h> #include <asm/hvm/svm/svm.h> #include <asm/vm_event.h> +#include <asm/altp2m.h> +#include "../mm/mm-locks.h" static void hvmtrace_io_assist(const ioreq_t *p) { @@ -530,6 +533,157 @@ static int hvmemul_do_mmio_addr(paddr_t mmio_gpa, return hvmemul_do_io_addr(1, mmio_gpa, reps, size, dir, df, ram_gpa); } +static bool hvmemul_send_vm_event(paddr_t gpa, unsigned long gla, gfn_t gfn, + uint32_t pfec, struct hvm_emulate_ctxt *ctxt) +{ + p2m_access_t access = p2m_access_n; + struct p2m_domain *p2m = NULL; + vm_event_request_t req = {}; + p2m_type_t p2mt; + mfn_t mfn; + + if ( !ctxt->send_event || !pfec ) + return false; + + p2m = p2m_get_hostp2m(current->domain); + if ( altp2m_active(current->domain) ) + p2m = p2m_get_altp2m(current); + if ( !p2m ) + p2m = p2m_get_hostp2m(current->domain); + + gfn_lock(p2m, gfn, 0); + mfn = p2m->get_entry(p2m, gfn, &p2mt, &access, 0, NULL, NULL); + gfn_unlock(p2m, gfn, 0); + + if ( mfn_eq(mfn, INVALID_MFN) ) + return false; + + switch ( access ) { + case p2m_access_x: + case p2m_access_rx: + if ( pfec & PFEC_write_access ) + req.u.mem_access.flags = MEM_ACCESS_R | MEM_ACCESS_W; + break; + case p2m_access_w: + case p2m_access_rw: + if ( pfec & PFEC_insn_fetch ) + req.u.mem_access.flags = MEM_ACCESS_X; + break; + case p2m_access_r: + case p2m_access_n: + if ( pfec & PFEC_write_access ) + req.u.mem_access.flags |= MEM_ACCESS_R | MEM_ACCESS_W; + if ( pfec & PFEC_insn_fetch ) + req.u.mem_access.flags |= MEM_ACCESS_X; + break; + default: + return false; + } + + if ( !req.u.mem_access.flags ) + return false; //no violation + + req.reason = VM_EVENT_REASON_MEM_ACCESS; + req.u.mem_access.gfn = gfn_x(gfn); + req.u.mem_access.flags |= MEM_ACCESS_FAULT_WITH_GLA | MEM_ACCESS_GLA_VALID; + req.u.mem_access.gla = gla; + req.u.mem_access.offset = gpa & ((1 << PAGE_SHIFT) - 1); + + if ( monitor_traps(current, true, &req) < 0 ) + return false; + + return true; +} + +/* + * Convert addr from linear to physical form, valid over the range + * [addr, addr + *reps * bytes_per_rep]. *reps is adjusted according to + * the valid computed range. It is always >0 when X86EMUL_OKAY is returned. + * @pfec indicates the access checks to be performed during page-table walks. +*/ +static int hvmemul_linear_to_phys( + unsigned long addr, + paddr_t *paddr, + unsigned int bytes_per_rep, + unsigned long *reps, + uint32_t pfec, + struct hvm_emulate_ctxt *hvmemul_ctxt) +{ + struct vcpu *curr = current; + unsigned long pfn, npfn, done, todo, i, offset = addr & ~PAGE_MASK; + int reverse; + + /* + * Clip repetitions to a sensible maximum. This avoids extensive looping in + * this function while still amortising the cost of I/O trap-and-emulate. + */ + *reps = min_t(unsigned long, *reps, 4096); + + /* With no paging it's easy: linear == physical. */ + if ( !(curr->arch.hvm.guest_cr[0] & X86_CR0_PG) ) + { + *paddr = addr; + return X86EMUL_OKAY; + } + + /* Reverse mode if this is a backwards multi-iteration string operation. */ + reverse = (hvmemul_ctxt->ctxt.regs->eflags & X86_EFLAGS_DF) && (*reps > 1); + + if ( reverse && ((PAGE_SIZE - offset) < bytes_per_rep) ) + { + /* Do page-straddling first iteration forwards via recursion. */ + paddr_t _paddr; + unsigned long one_rep = 1; + int rc = hvmemul_linear_to_phys( + addr, &_paddr, bytes_per_rep, &one_rep, pfec, hvmemul_ctxt); + if ( rc != X86EMUL_OKAY ) + return rc; + pfn = _paddr >> PAGE_SHIFT; + } + else if ( (pfn = paging_gva_to_gfn(curr, addr, &pfec)) == gfn_x(INVALID_GFN) ) + { + if ( pfec & (PFEC_page_paged | PFEC_page_shared) ) + return X86EMUL_RETRY; + *reps = 0; + x86_emul_pagefault(pfec, addr, &hvmemul_ctxt->ctxt); + return X86EMUL_EXCEPTION; + } + + done = reverse ? bytes_per_rep + offset : PAGE_SIZE - offset; + todo = *reps * bytes_per_rep; + for ( i = 1; done < todo; i++ ) + { + /* Get the next PFN in the range. */ + addr += reverse ? -PAGE_SIZE : PAGE_SIZE; + npfn = paging_gva_to_gfn(curr, addr, &pfec); + + /* Is it contiguous with the preceding PFNs? If not then we're done. */ + if ( (npfn == gfn_x(INVALID_GFN)) || + (npfn != (pfn + (reverse ? -i : i))) ) + { + if ( pfec & (PFEC_page_paged | PFEC_page_shared) ) + return X86EMUL_RETRY; + done /= bytes_per_rep; + if ( done == 0 ) + { + ASSERT(!reverse); + if ( npfn != gfn_x(INVALID_GFN) ) + return X86EMUL_UNHANDLEABLE; + *reps = 0; + x86_emul_pagefault(pfec, addr & PAGE_MASK, &hvmemul_ctxt->ctxt); + return X86EMUL_EXCEPTION; + } + *reps = done; + break; + } + + done += PAGE_SIZE; + } + + *paddr = ((paddr_t)pfn << PAGE_SHIFT) | offset; + return X86EMUL_OKAY; +} + /* * Map the frame(s) covering an individual linear access, for writeable * access. May return NULL for MMIO, or ERR_PTR(~X86EMUL_*) for other errors @@ -577,6 +731,7 @@ static void *hvmemul_map_linear_addr( pagefault_info_t pfinfo; p2m_type_t p2mt; unsigned long addr = i ? (linear + (i << PAGE_SHIFT)) & PAGE_MASK : linear; + gfn_t gfn; if ( hvmemul_ctxt->ctxt.addr_size < 64 ) addr = (uint32_t)addr; @@ -585,7 +740,7 @@ static void *hvmemul_map_linear_addr( ASSERT(mfn_x(*mfn) == 0); res = hvm_translate_get_page(curr, addr, true, pfec, - &pfinfo, &page, NULL, &p2mt); + &pfinfo, &page, &gfn, &p2mt); switch ( res ) { @@ -615,6 +770,23 @@ static void *hvmemul_map_linear_addr( if ( pfec & PFEC_write_access ) { + unsigned long reps = 1; + struct hvm_emulate_ctxt old; + int rc = 0; + paddr_t gpa; + + old = *hvmemul_ctxt; + rc = hvmemul_linear_to_phys( + addr, &gpa, bytes, &reps, pfec, hvmemul_ctxt); + if ( rc == X86EMUL_EXCEPTION ) + *hvmemul_ctxt = old; + + if ( hvmemul_send_vm_event(gpa, addr, gfn, pfec, hvmemul_ctxt) ) + { + err = ERR_PTR(~X86EMUL_ACCESS_EXCEPTION); + goto out; + } + if ( p2m_is_discard_write(p2mt) ) { err = ERR_PTR(~X86EMUL_OKAY); @@ -694,96 +866,6 @@ static void hvmemul_unmap_linear_addr( #endif } -/* - * Convert addr from linear to physical form, valid over the range - * [addr, addr + *reps * bytes_per_rep]. *reps is adjusted according to - * the valid computed range. It is always >0 when X86EMUL_OKAY is returned. - * @pfec indicates the access checks to be performed during page-table walks. - */ -static int hvmemul_linear_to_phys( - unsigned long addr, - paddr_t *paddr, - unsigned int bytes_per_rep, - unsigned long *reps, - uint32_t pfec, - struct hvm_emulate_ctxt *hvmemul_ctxt) -{ - struct vcpu *curr = current; - unsigned long pfn, npfn, done, todo, i, offset = addr & ~PAGE_MASK; - int reverse; - - /* - * Clip repetitions to a sensible maximum. This avoids extensive looping in - * this function while still amortising the cost of I/O trap-and-emulate. - */ - *reps = min_t(unsigned long, *reps, 4096); - - /* With no paging it's easy: linear == physical. */ - if ( !(curr->arch.hvm.guest_cr[0] & X86_CR0_PG) ) - { - *paddr = addr; - return X86EMUL_OKAY; - } - - /* Reverse mode if this is a backwards multi-iteration string operation. */ - reverse = (hvmemul_ctxt->ctxt.regs->eflags & X86_EFLAGS_DF) && (*reps > 1); - - if ( reverse && ((PAGE_SIZE - offset) < bytes_per_rep) ) - { - /* Do page-straddling first iteration forwards via recursion. */ - paddr_t _paddr; - unsigned long one_rep = 1; - int rc = hvmemul_linear_to_phys( - addr, &_paddr, bytes_per_rep, &one_rep, pfec, hvmemul_ctxt); - if ( rc != X86EMUL_OKAY ) - return rc; - pfn = _paddr >> PAGE_SHIFT; - } - else if ( (pfn = paging_gva_to_gfn(curr, addr, &pfec)) == gfn_x(INVALID_GFN) ) - { - if ( pfec & (PFEC_page_paged | PFEC_page_shared) ) - return X86EMUL_RETRY; - *reps = 0; - x86_emul_pagefault(pfec, addr, &hvmemul_ctxt->ctxt); - return X86EMUL_EXCEPTION; - } - - done = reverse ? bytes_per_rep + offset : PAGE_SIZE - offset; - todo = *reps * bytes_per_rep; - for ( i = 1; done < todo; i++ ) - { - /* Get the next PFN in the range. */ - addr += reverse ? -PAGE_SIZE : PAGE_SIZE; - npfn = paging_gva_to_gfn(curr, addr, &pfec); - - /* Is it contiguous with the preceding PFNs? If not then we're done. */ - if ( (npfn == gfn_x(INVALID_GFN)) || - (npfn != (pfn + (reverse ? -i : i))) ) - { - if ( pfec & (PFEC_page_paged | PFEC_page_shared) ) - return X86EMUL_RETRY; - done /= bytes_per_rep; - if ( done == 0 ) - { - ASSERT(!reverse); - if ( npfn != gfn_x(INVALID_GFN) ) - return X86EMUL_UNHANDLEABLE; - *reps = 0; - x86_emul_pagefault(pfec, addr & PAGE_MASK, &hvmemul_ctxt->ctxt); - return X86EMUL_EXCEPTION; - } - *reps = done; - break; - } - - done += PAGE_SIZE; - } - - *paddr = ((paddr_t)pfn << PAGE_SHIFT) | offset; - return X86EMUL_OKAY; -} - - static int hvmemul_virtual_to_linear( enum x86_segment seg, unsigned long offset, @@ -1225,6 +1307,36 @@ int hvmemul_insn_fetch( container_of(ctxt, struct hvm_emulate_ctxt, ctxt); /* Careful, as offset can wrap or truncate WRT insn_buf_eip. */ uint8_t insn_off = offset - hvmemul_ctxt->insn_buf_eip; + paddr_t gpa; + uint32_t pfec = PFEC_page_present | PFEC_insn_fetch; + unsigned long addr, reps = 1; + int rc =0; + struct hvm_emulate_ctxt old; + + rc = hvmemul_virtual_to_linear( + seg, offset, bytes, &reps, hvm_access_insn_fetch, hvmemul_ctxt, &addr); + if ( rc == X86EMUL_EXCEPTION ) + { + x86_emul_reset_event(ctxt); + rc = X86EMUL_OKAY; + } + + if ( hvmemul_ctxt->seg_reg[x86_seg_ss].dpl == 3 ) + pfec |= PFEC_user_mode; + + old = *hvmemul_ctxt; + rc = hvmemul_linear_to_phys( + addr, &gpa, bytes, &reps, pfec, hvmemul_ctxt); + if ( rc == X86EMUL_EXCEPTION ) + { + *hvmemul_ctxt = old; + rc = X86EMUL_OKAY; + } + + if ( gpa ) + if ( hvmemul_send_vm_event(gpa, addr, gaddr_to_gfn(gpa), + pfec, hvmemul_ctxt) ) + return X86EMUL_ACCESS_EXCEPTION; /* * Fall back if requested bytes are not in the prefetch cache. @@ -2493,13 +2605,13 @@ int hvm_emulate_one_mmio(unsigned long mfn, unsigned long gla) } void hvm_emulate_one_vm_event(enum emul_kind kind, unsigned int trapnr, - unsigned int errcode) + unsigned int errcode, bool send_event) { struct hvm_emulate_ctxt ctx = {{ 0 }}; int rc; hvm_emulate_init_once(&ctx, NULL, guest_cpu_user_regs()); - + ctx.send_event = send_event; switch ( kind ) { case EMUL_KIND_NOWRITE: diff --git a/xen/arch/x86/hvm/vm_event.c b/xen/arch/x86/hvm/vm_event.c index 0df8ab40e6..bdc65da3ed 100644 --- a/xen/arch/x86/hvm/vm_event.c +++ b/xen/arch/x86/hvm/vm_event.c @@ -87,7 +87,7 @@ void hvm_vm_event_do_resume(struct vcpu *v) kind = EMUL_KIND_SET_CONTEXT_INSN; hvm_emulate_one_vm_event(kind, TRAP_invalid_op, - X86_EVENT_NO_EC); + X86_EVENT_NO_EC, false); v->arch.vm_event->emulate_flags = 0; } diff --git a/xen/arch/x86/mm/mem_access.c b/xen/arch/x86/mm/mem_access.c index 56c06a4fc6..43dfdf02b5 100644 --- a/xen/arch/x86/mm/mem_access.c +++ b/xen/arch/x86/mm/mem_access.c @@ -214,8 +214,8 @@ bool p2m_mem_access_check(paddr_t gpa, unsigned long gla, d->arch.monitor.inguest_pagefault_disabled && npfec.kind != npfec_kind_with_gla ) /* don't send a mem_event */ { - hvm_emulate_one_vm_event(EMUL_KIND_NORMAL, TRAP_invalid_op, X86_EVENT_NO_EC); - + hvm_emulate_one_vm_event(EMUL_KIND_NORMAL, TRAP_invalid_op, + X86_EVENT_NO_EC, true); return true; } diff --git a/xen/arch/x86/x86_emulate/x86_emulate.h b/xen/arch/x86/x86_emulate/x86_emulate.h index 55a9e0ed51..a9829913a4 100644 --- a/xen/arch/x86/x86_emulate/x86_emulate.h +++ b/xen/arch/x86/x86_emulate/x86_emulate.h @@ -162,6 +162,7 @@ struct x86_emul_fpu_aux { #define X86EMUL_UNRECOGNIZED X86EMUL_UNIMPLEMENTED /* (cmpxchg accessor): CMPXCHG failed. */ #define X86EMUL_CMPXCHG_FAILED 7 +#define X86EMUL_ACCESS_EXCEPTION 8 /* FPU sub-types which may be requested via ->get_fpu(). */ enum x86_emulate_fpu_type { diff --git a/xen/include/asm-x86/hvm/emulate.h b/xen/include/asm-x86/hvm/emulate.h index 26a01e83a4..721e175b04 100644 --- a/xen/include/asm-x86/hvm/emulate.h +++ b/xen/include/asm-x86/hvm/emulate.h @@ -47,6 +47,7 @@ struct hvm_emulate_ctxt { uint32_t intr_shadow; bool_t set_context; + bool send_event; }; enum emul_kind { @@ -63,7 +64,8 @@ int hvm_emulate_one( struct hvm_emulate_ctxt *hvmemul_ctxt); void hvm_emulate_one_vm_event(enum emul_kind kind, unsigned int trapnr, - unsigned int errcode); + unsigned int errcode, + bool send_event); /* Must be called once to set up hvmemul state. */ void hvm_emulate_init_once( struct hvm_emulate_ctxt *hvmemul_ctxt, -- 2.17.1 _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxxxxxxxxx https://lists.xenproject.org/mailman/listinfo/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |