[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v2 01/11] x86emul: catch exceptions occurring in stubs
On 01/02/17 11:12, Jan Beulich wrote: > Before adding more use of stubs cloned from decoded guest insns, guard > ourselves against mistakes there: Should an exception (with the > noteworthy exception of #PF) occur inside the stub, forward it to the > guest. Why exclude #PF ? Nothing in a stub should be hitting a pagefault in the first place. > > Since the exception fixup table entry can't encode the address of the > faulting insn itself, attach it to the return address instead. This at > once provides a convenient place to hand the exception information > back: The return address is being overwritten by it before branching to > the recovery code. > > Take the opportunity and (finally!) add symbol resolution to the > respective log messages (the new one is intentionally not being coded > that way, as it covers stub addresses only, which don't have symbols > associated). > > Also take the opportunity and make search_one_extable() static again. > > Suggested-by: Andrew Cooper <andrew.cooper3@xxxxxxxxxx> > Signed-off-by: Jan Beulich <jbeulich@xxxxxxxx> > --- > There's one possible caveat here: A stub invocation immediately > followed by another instruction having fault revovery attached to it > would not work properly, as the table lookup can only ever find one of > the two entries. Such CALL instructions would then need to be followed > by a NOP for disambiguation (even if only a slim chance exists for the > compiler to emit things that way). Why key on return address at all? %rip being in the stubs should be good enough. > > TBD: Instead of adding a 2nd search_exception_table() invocation to > do_trap(), we may want to consider moving the existing one down: > Xen code (except when executing stubs) shouldn't be raising #MF > or #XM, and hence fixups attached to instructions shouldn't care > about getting invoked for those. With that, doing the HVM special > case for them before running search_exception_table() would be > fine. > > Note that the two SIMD related stub invocations in the insn emulator > intentionally don't get adjusted here, as subsequent patches will > replace them anyway. > > --- a/xen/arch/x86/extable.c > +++ b/xen/arch/x86/extable.c > @@ -6,6 +6,7 @@ > #include <xen/sort.h> > #include <xen/spinlock.h> > #include <asm/uaccess.h> > +#include <xen/domain_page.h> > #include <xen/virtual_region.h> > #include <xen/livepatch.h> > > @@ -62,7 +63,7 @@ void __init sort_exception_tables(void) > sort_exception_table(__start___pre_ex_table, __stop___pre_ex_table); > } > > -unsigned long > +static unsigned long > search_one_extable(const struct exception_table_entry *first, > const struct exception_table_entry *last, > unsigned long value) > @@ -85,15 +86,88 @@ search_one_extable(const struct exceptio > } > > unsigned long > -search_exception_table(unsigned long addr) > +search_exception_table(const struct cpu_user_regs *regs, bool check_stub) > { > - const struct virtual_region *region = find_text_region(addr); > + const struct virtual_region *region = find_text_region(regs->rip); > + unsigned long stub = this_cpu(stubs.addr); > > if ( region && region->ex ) > - return search_one_extable(region->ex, region->ex_end - 1, addr); > + return search_one_extable(region->ex, region->ex_end - 1, regs->rip); > + > + if ( check_stub && > + regs->rip >= stub + STUB_BUF_SIZE / 2 && > + regs->rip < stub + STUB_BUF_SIZE && > + regs->rsp > (unsigned long)&check_stub && > + regs->rsp < (unsigned long)get_cpu_info() ) How much do we care about accidentally clobbering %rsp in a stub? If we encounter a fault with %rip in the stubs, we should terminate obviously if %rsp it outside of the main stack. Nothing good can come from continuing. > + { > + unsigned long retptr = *(unsigned long *)regs->rsp; > + > + region = find_text_region(retptr); > + retptr = region && region->ex > + ? search_one_extable(region->ex, region->ex_end - 1, retptr) > + : 0; > + if ( retptr ) > + { > + /* > + * Put trap number and error code on the stack (in place of the > + * original return address) for recovery code to pick up. > + */ > + *(unsigned long *)regs->rsp = regs->error_code | > + ((uint64_t)(uint8_t)regs->entry_vector << 32); > + return retptr; I have found an alternative which has proved very neat in XTF. By calling the stub like this: asm volatile ("call *%[stub]" : "=a" (exn) : "a" (0)); and having this fixup write straight into %rax, the stub ends up behaving as having an unsigned long return value. This avoids the need for any out-of-line code recovering the exception information and redirecting back as if the call had completed normally. http://xenbits.xen.org/gitweb/?p=xtf.git;a=blob;f=include/arch/x86/exinfo.h;hb=master One subtle trap I fell over is you also need a valid bit to help distinguish #DE, which always has an error code of 0. > + } > + } > + > + return 0; > +} > + > +#ifndef NDEBUG > +static int __init stub_selftest(void) > +{ > + static const struct { > + uint8_t opc[4]; > + uint64_t rax; > + union stub_exception_token res; > + } tests[] __initconst = { > + { .opc = { 0x0f, 0xb9, 0xc3, 0xc3 }, /* ud1 */ > + .res.fields.trapnr = TRAP_invalid_op }, > + { .opc = { 0x90, 0x02, 0x00, 0xc3 }, /* nop; add (%rax),%al */ > + .rax = 0x0123456789abcdef, > + .res.fields.trapnr = TRAP_gp_fault }, > + { .opc = { 0x02, 0x04, 0x04, 0xc3 }, /* add (%rsp,%rax),%al */ > + .rax = 0xfedcba9876543210, > + .res.fields.trapnr = TRAP_stack_error }, > + }; > + unsigned long addr = this_cpu(stubs.addr) + STUB_BUF_SIZE / 2; > + unsigned int i; > + > + for ( i = 0; i < ARRAY_SIZE(tests); ++i ) > + { > + uint8_t *ptr = map_domain_page(_mfn(this_cpu(stubs.mfn))) + > + (addr & ~PAGE_MASK); > + unsigned long res = ~0; > + > + memset(ptr, 0xcc, STUB_BUF_SIZE / 2); > + memcpy(ptr, tests[i].opc, ARRAY_SIZE(tests[i].opc)); > + unmap_domain_page(ptr); > + > + asm volatile ( "call *%[stb]\n" > + ".Lret%=:\n\t" > + ".pushsection .fixup,\"ax\"\n" > + ".Lfix%=:\n\t" > + "pop %[exn]\n\t" > + "jmp .Lret%=\n\t" > + ".popsection\n\t" > + _ASM_EXTABLE(.Lret%=, .Lfix%=) > + : [exn] "+m" (res) > + : [stb] "rm" (addr), "a" (tests[i].rax)); > + ASSERT(res == tests[i].res.raw); > + } > > return 0; > } > +__initcall(stub_selftest); > +#endif > > unsigned long > search_pre_exception_table(struct cpu_user_regs *regs) > --- a/xen/arch/x86/traps.c > +++ b/xen/arch/x86/traps.c > @@ -802,10 +802,10 @@ void do_trap(struct cpu_user_regs *regs) > return; > } > > - if ( likely((fixup = search_exception_table(regs->rip)) != 0) ) > + if ( likely((fixup = search_exception_table(regs, false)) != 0) ) > { > - dprintk(XENLOG_ERR, "Trap %d: %p -> %p\n", > - trapnr, _p(regs->rip), _p(fixup)); > + dprintk(XENLOG_ERR, "Trap %u: %p [%ps] -> %p\n", > + trapnr, _p(regs->rip), _p(regs->rip), _p(fixup)); > this_cpu(last_extable_addr) = regs->rip; > regs->rip = fixup; > return; > @@ -820,6 +820,15 @@ void do_trap(struct cpu_user_regs *regs) > return; > } > > + if ( likely((fixup = search_exception_table(regs, true)) != 0) ) > + { > + dprintk(XENLOG_ERR, "Trap %u: %p -> %p\n", > + trapnr, _p(regs->rip), _p(fixup)); > + this_cpu(last_extable_addr) = regs->rip; > + regs->rip = fixup; > + return; > + } > + > hardware_trap: > if ( debugger_trap_fatal(trapnr, regs) ) > return; > @@ -1567,7 +1576,7 @@ void do_invalid_op(struct cpu_user_regs > } > > die: > - if ( (fixup = search_exception_table(regs->rip)) != 0 ) > + if ( (fixup = search_exception_table(regs, true)) != 0 ) > { > this_cpu(last_extable_addr) = regs->rip; > regs->rip = fixup; > @@ -1897,7 +1906,7 @@ void do_page_fault(struct cpu_user_regs > if ( pf_type != real_fault ) > return; > > - if ( likely((fixup = search_exception_table(regs->rip)) != 0) ) > + if ( likely((fixup = search_exception_table(regs, false)) != 0) ) > { > perfc_incr(copy_user_faults); > if ( unlikely(regs->error_code & PFEC_reserved_bit) ) > @@ -3841,10 +3850,10 @@ void do_general_protection(struct cpu_us > > gp_in_kernel: > > - if ( likely((fixup = search_exception_table(regs->rip)) != 0) ) > + if ( likely((fixup = search_exception_table(regs, true)) != 0) ) > { > - dprintk(XENLOG_INFO, "GPF (%04x): %p -> %p\n", > - regs->error_code, _p(regs->rip), _p(fixup)); > + dprintk(XENLOG_INFO, "GPF (%04x): %p [%ps] -> %p\n", > + regs->error_code, _p(regs->rip), _p(regs->rip), _p(fixup)); > this_cpu(last_extable_addr) = regs->rip; > regs->rip = fixup; > return; > @@ -4120,7 +4129,7 @@ void do_debug(struct cpu_user_regs *regs > * watchpoint set on it. No need to bump EIP; the only faulting > * trap is an instruction breakpoint, which can't happen to us. > */ > - WARN_ON(!search_exception_table(regs->rip)); > + WARN_ON(!search_exception_table(regs, false)); > } > goto out; > } > --- a/xen/arch/x86/x86_emulate/x86_emulate.c > +++ b/xen/arch/x86/x86_emulate/x86_emulate.c > @@ -676,14 +676,34 @@ do{ asm volatile ( > #define __emulate_1op_8byte(_op, _dst, _eflags) > #endif /* __i386__ */ > > +#ifdef __XEN__ > +# define invoke_stub(pre, post, constraints...) do { \ > + union stub_exception_token res_ = { .raw = ~0 }; \ > + asm volatile ( pre "\n\tcall *%[stub]\n\t" post "\n" \ > + ".Lret%=:\n\t" \ > + ".pushsection .fixup,\"ax\"\n" \ > + ".Lfix%=:\n\t" \ > + "pop %[exn]\n\t" \ > + "jmp .Lret%=\n\t" \ > + ".popsection\n\t" \ > + _ASM_EXTABLE(.Lret%=, .Lfix%=) \ > + : [exn] "+g" (res_), constraints, \ > + [stub] "rm" (stub.func) ); \ > + generate_exception_if(~res_.raw, res_.fields.trapnr, \ > + res_.fields.ec); \ > +} while (0) > +#else > +# define invoke_stub(pre, post, constraints...) \ > + asm volatile ( pre "\n\tcall *%[stub]\n\t" post \ > + : constraints, [stub] "rm" (stub.func) ) > +#endif > + > #define emulate_stub(dst, src...) do { \ > unsigned long tmp; \ > - asm volatile ( _PRE_EFLAGS("[efl]", "[msk]", "[tmp]") \ > - "call *%[stub];" \ > - _POST_EFLAGS("[efl]", "[msk]", "[tmp]") \ > - : dst, [tmp] "=&r" (tmp), [efl] "+g" (_regs._eflags) \ > - : [stub] "r" (stub.func), \ > - [msk] "i" (EFLAGS_MASK), ## src ); \ > + invoke_stub(_PRE_EFLAGS("[efl]", "[msk]", "[tmp]"), \ > + _POST_EFLAGS("[efl]", "[msk]", "[tmp]"), \ > + dst, [tmp] "=&r" (tmp), [efl] "+g" (_regs._eflags) \ > + : [msk] "i" (EFLAGS_MASK), ## src); \ > } while (0) > > /* Fetch next part of the instruction being emulated. */ > @@ -929,8 +949,7 @@ do { > unsigned int nr_ = sizeof((uint8_t[]){ bytes }); \ > fic.insn_bytes = nr_; \ > memcpy(get_stub(stub), ((uint8_t[]){ bytes, 0xc3 }), nr_ + 1); \ > - asm volatile ( "call *%[stub]" : "+m" (fic) : \ > - [stub] "rm" (stub.func) ); \ > + invoke_stub("", "", "=m" (fic) : "m" (fic)); \ > put_stub(stub); \ > } while (0) > > @@ -940,13 +959,11 @@ do { > unsigned long tmp_; \ > fic.insn_bytes = nr_; \ > memcpy(get_stub(stub), ((uint8_t[]){ bytes, 0xc3 }), nr_ + 1); \ > - asm volatile ( _PRE_EFLAGS("[eflags]", "[mask]", "[tmp]") \ > - "call *%[func];" \ > - _POST_EFLAGS("[eflags]", "[mask]", "[tmp]") \ > - : [eflags] "+g" (_regs._eflags), \ > - [tmp] "=&r" (tmp_), "+m" (fic) \ > - : [func] "rm" (stub.func), \ > - [mask] "i" (EFLG_ZF|EFLG_PF|EFLG_CF) ); \ > + invoke_stub(_PRE_EFLAGS("[eflags]", "[mask]", "[tmp]"), \ > + _POST_EFLAGS("[eflags]", "[mask]", "[tmp]"), \ > + [eflags] "+g" (_regs._eflags), [tmp] "=&r" (tmp_), \ > + "+m" (fic) \ > + : [mask] "i" (EFLG_ZF|EFLG_PF|EFLG_CF)); \ > put_stub(stub); \ > } while (0) > > --- a/xen/include/asm-x86/uaccess.h > +++ b/xen/include/asm-x86/uaccess.h > @@ -275,7 +275,16 @@ extern struct exception_table_entry __st > extern struct exception_table_entry __start___pre_ex_table[]; > extern struct exception_table_entry __stop___pre_ex_table[]; > > -extern unsigned long search_exception_table(unsigned long); > +union stub_exception_token { > + struct { > + uint32_t ec; ec only needs to be 16 bits wide, which very helpfully lets it fit into an unsigned long, even for 32bit builds, and 8 bits at the top for extra metadata. ~Andrew > + uint8_t trapnr; > + } fields; > + uint64_t raw; > +}; > + > +extern unsigned long search_exception_table(const struct cpu_user_regs *regs, > + bool check_stub); > extern void sort_exception_tables(void); > extern void sort_exception_table(struct exception_table_entry *start, > const struct exception_table_entry *stop); > > _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxx https://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |