[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v1 08/11] xsplice: Implement support for applying patches
On Tue, Nov 03, 2015 at 06:16:05PM +0000, Ross Lagerwall wrote: > Implement support for the apply, revert and replace actions. > > To perform and action on a payload, the hypercall sets up a data > structure to schedule the work. A hook is added in all the > return-to-guest paths to check for work to do and execute it if needed. > In this way, patches can be applied with all CPUs idle and without > stacks. The first CPU to do_xsplice() becomes the master and triggers a > reschedule softirq to trigger all the other CPUs to enter do_xsplice() > with no stack. Once all CPUs have rendezvoused, all CPUs disable IRQs > and NMIs are ignored. The system is then quiscient and the master > performs the action. After this, all CPUs enable IRQs and NMIs are > re-enabled. > > The action to perform is one of: > - APPLY: For each function in the module, store the first 5 bytes of the > old function and replace it with a jump to the new function. > - REVERT: Copy the previously stored bytes into the first 5 bytes of the > old function. > - REPLACE: Revert each applied module and then apply the new module. > > To prevent a deadlock with any other barrier in the system, the master > will wait for up to 30ms before timing out. I've taken some > measurements and found the patch application to take about 100 Îs on a > 72 CPU system, whether idle or fully loaded. > > Signed-off-by: Ross Lagerwall <ross.lagerwall@xxxxxxxxxx> > --- > xen/arch/arm/xsplice.c | 8 ++ > xen/arch/x86/domain.c | 4 + > xen/arch/x86/hvm/svm/svm.c | 2 + > xen/arch/x86/hvm/vmx/vmcs.c | 2 + > xen/arch/x86/xsplice.c | 19 ++++ > xen/common/xsplice.c | 264 > ++++++++++++++++++++++++++++++++++++++++++-- > xen/include/asm-arm/nmi.h | 13 +++ > xen/include/xen/xsplice.h | 7 +- > 8 files changed, 306 insertions(+), 13 deletions(-) > > diff --git a/xen/arch/arm/xsplice.c b/xen/arch/arm/xsplice.c > index 8d85fa9..3c34eb3 100644 > --- a/xen/arch/arm/xsplice.c > +++ b/xen/arch/arm/xsplice.c > @@ -3,6 +3,14 @@ > #include <xen/xsplice_elf.h> > #include <xen/xsplice.h> > > +void xsplice_apply_jmp(struct xsplice_patch_func *func) > +{ > +} > + > +void xsplice_revert_jmp(struct xsplice_patch_func *func) > +{ > +} > + > int xsplice_verify_elf(uint8_t *data, ssize_t len) > { > return -ENOSYS; > diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c > index fe3be30..4420cfc 100644 > --- a/xen/arch/x86/domain.c > +++ b/xen/arch/x86/domain.c > @@ -36,6 +36,7 @@ > #include <xen/cpu.h> > #include <xen/wait.h> > #include <xen/guest_access.h> > +#include <xen/xsplice.h> > #include <public/sysctl.h> > #include <asm/regs.h> > #include <asm/mc146818rtc.h> > @@ -120,6 +121,7 @@ static void idle_loop(void) > (*pm_idle)(); > do_tasklet(); > do_softirq(); > + do_xsplice(); > } > } > > @@ -136,6 +138,7 @@ void startup_cpu_idle_loop(void) > > static void noreturn continue_idle_domain(struct vcpu *v) > { > + do_xsplice(); > reset_stack_and_jump(idle_loop); > } > > @@ -143,6 +146,7 @@ static void noreturn continue_nonidle_domain(struct vcpu > *v) > { > check_wakeup_from_wait(); > mark_regs_dirty(guest_cpu_user_regs()); > + do_xsplice(); > reset_stack_and_jump(ret_from_intr); > } > > diff --git a/xen/arch/x86/hvm/svm/svm.c b/xen/arch/x86/hvm/svm/svm.c > index 8de41fa..65bf7e9 100644 > --- a/xen/arch/x86/hvm/svm/svm.c > +++ b/xen/arch/x86/hvm/svm/svm.c > @@ -26,6 +26,7 @@ > #include <xen/hypercall.h> > #include <xen/domain_page.h> > #include <xen/xenoprof.h> > +#include <xen/xsplice.h> > #include <asm/current.h> > #include <asm/io.h> > #include <asm/paging.h> > @@ -1071,6 +1072,7 @@ static void noreturn svm_do_resume(struct vcpu *v) > > hvm_do_resume(v); > > + do_xsplice(); > reset_stack_and_jump(svm_asm_do_resume); > } > > diff --git a/xen/arch/x86/hvm/vmx/vmcs.c b/xen/arch/x86/hvm/vmx/vmcs.c > index 4ea1ad1..d996f47 100644 > --- a/xen/arch/x86/hvm/vmx/vmcs.c > +++ b/xen/arch/x86/hvm/vmx/vmcs.c > @@ -25,6 +25,7 @@ > #include <xen/kernel.h> > #include <xen/keyhandler.h> > #include <xen/vm_event.h> > +#include <xen/xsplice.h> > #include <asm/current.h> > #include <asm/cpufeature.h> > #include <asm/processor.h> > @@ -1685,6 +1686,7 @@ void vmx_do_resume(struct vcpu *v) > } > > hvm_do_resume(v); > + do_xsplice(); > reset_stack_and_jump(vmx_asm_do_vmentry); > } > > diff --git a/xen/arch/x86/xsplice.c b/xen/arch/x86/xsplice.c > index dbff0d5..31e4124 100644 > --- a/xen/arch/x86/xsplice.c > +++ b/xen/arch/x86/xsplice.c > @@ -3,6 +3,25 @@ > #include <xen/xsplice_elf.h> > #include <xen/xsplice.h> > > +#define PATCH_INSN_SIZE 5 > + > +void xsplice_apply_jmp(struct xsplice_patch_func *func) Don't we want for it to be 'int' > +{ > + uint32_t val; > + uint8_t *old_ptr; > + > + old_ptr = (uint8_t *)func->old_addr; > + memcpy(func->undo, old_ptr, PATCH_INSN_SIZE); And perhaps use something which can catch an exception (#GP) so that this can error out? > + *old_ptr++ = 0xe9; /* Relative jump */ > + val = func->new_addr - func->old_addr - PATCH_INSN_SIZE; > + memcpy(old_ptr, &val, sizeof val); > +} > + > +void xsplice_revert_jmp(struct xsplice_patch_func *func) > +{ > + memcpy((void *)func->old_addr, func->undo, PATCH_INSN_SIZE); > +} > + > int xsplice_verify_elf(uint8_t *data, ssize_t len) > { > > diff --git a/xen/common/xsplice.c b/xen/common/xsplice.c > index 5e88c55..4476be5 100644 > --- a/xen/common/xsplice.c > +++ b/xen/common/xsplice.c > @@ -11,16 +11,21 @@ > #include <xen/guest_access.h> > #include <xen/stdbool.h> > #include <xen/sched.h> > +#include <xen/softirq.h> > #include <xen/lib.h> > +#include <xen/wait.h> > #include <xen/xsplice_elf.h> > #include <xen/xsplice.h> > #include <public/sysctl.h> > > #include <asm/event.h> > +#include <asm/nmi.h> > > static DEFINE_SPINLOCK(payload_list_lock); > static LIST_HEAD(payload_list); > > +static LIST_HEAD(applied_list); > + > static unsigned int payload_cnt; > static unsigned int payload_version = 1; > > @@ -29,15 +34,34 @@ struct payload { > int32_t rc; /* 0 or -EXX. */ > > struct list_head list; /* Linked to 'payload_list'. */ > + struct list_head applied_list; /* Linked to 'applied_list'. */ > > + struct xsplice_patch_func *funcs; > + int nfuncs; unsigned int; > void *module_address; > size_t module_pages; > > char id[XEN_XSPLICE_NAME_SIZE + 1]; /* Name of it. */ > }; > > +/* Defines an outstanding patching action. */ > +struct xsplice_work > +{ > + atomic_t semaphore; /* Used for rendezvous */ > + atomic_t irq_semaphore; /* Used to signal all IRQs disabled */ > + struct payload *data; /* The payload on which to act */ > + volatile bool_t do_work; /* Signals work to do */ > + volatile bool_t ready; /* Signals all CPUs synchronized */ > + uint32_t cmd; /* Action request. XSPLICE_ACTION_* */ Now since you have a pointer to 'data' can't you follow that for the cmd? Or at least the 'data->state'? Missing full stops. > +}; > + > +static DEFINE_SPINLOCK(xsplice_work_lock); > +/* There can be only one outstanding patching action. */ > +static struct xsplice_work xsplice_work; > + > static int load_module(struct payload *payload, uint8_t *raw, ssize_t len); > static void free_module(struct payload *payload); > +static int schedule_work(struct payload *data, uint32_t cmd); > > static const char *state2str(int32_t state) > { > @@ -341,28 +365,22 @@ static int xsplice_action(xen_sysctl_xsplice_action_t > *action) > case XSPLICE_ACTION_REVERT: > if ( data->state == XSPLICE_STATE_APPLIED ) > { > - /* No implementation yet. */ > - data->state = XSPLICE_STATE_CHECKED; > - data->rc = 0; > - rc = 0; > + data->rc = -EAGAIN; > + rc = schedule_work(data, action->cmd); > } > break; > case XSPLICE_ACTION_APPLY: > if ( (data->state == XSPLICE_STATE_CHECKED) ) > { > - /* No implementation yet. */ > - data->state = XSPLICE_STATE_APPLIED; > - data->rc = 0; > - rc = 0; > + data->rc = -EAGAIN; > + rc = schedule_work(data, action->cmd); > } > break; > case XSPLICE_ACTION_REPLACE: > if ( data->state == XSPLICE_STATE_CHECKED ) > { > - /* No implementation yet. */ > - data->state = XSPLICE_STATE_CHECKED; > - data->rc = 0; > - rc = 0; > + data->rc = -EAGAIN; > + rc = schedule_work(data, action->cmd); > } > break; > default: > @@ -637,6 +655,24 @@ static int perform_relocs(struct xsplice_elf *elf) > return 0; > } > > +static int find_special_sections(struct payload *payload, > + struct xsplice_elf *elf) > +{ > + struct xsplice_elf_sec *sec; > + > + sec = xsplice_elf_sec_by_name(elf, ".xsplice.funcs"); > + if ( !sec ) > + { > + printk(XENLOG_ERR ".xsplice.funcs is missing\n"); > + return -1; > + } > + > + payload->funcs = (struct xsplice_patch_func *)sec->load_addr; > + payload->nfuncs = sec->sec->sh_size / (sizeof *payload->funcs); > + > + return 0; > +} That looks like it should belong to another patch? > + > static int load_module(struct payload *payload, uint8_t *raw, ssize_t len) > { > struct xsplice_elf elf; > @@ -662,6 +698,10 @@ static int load_module(struct payload *payload, uint8_t > *raw, ssize_t len) > if ( rc ) > goto err_module; > > + rc = find_special_sections(payload, &elf); > + if ( rc ) > + goto err_module; > + Ditto? > return 0; > > err_module: > @@ -672,6 +712,206 @@ static int load_module(struct payload *payload, uint8_t > *raw, ssize_t len) > return rc; > } > > + > +/* > + * The following functions get the CPUs into an appropriate state and > + * apply (or revert) each of the module's functions. s/module/payload/ > + */ > + > +/* > + * This function is executed having all other CPUs with no stack and IRQs > + * disabled. Well, there is some stack. For example from 'cpu_idle' - you have the 'cpu_idle' on the stack. > + */ > +static int apply_payload(struct payload *data) > +{ > + int i; unsigned int > + > + printk(XENLOG_DEBUG "Applying payload: %s\n", data->id); > + > + for ( i = 0; i < data->nfuncs; i++ ) > + xsplice_apply_jmp(data->funcs + i); And if this returns an error then we could skip adding it to the applied_list.. > + Also the patching in Linux seems to do some icache purging. Should we use that? > + list_add_tail(&data->applied_list, &applied_list); > + > + return 0; > +} > + > +/* > + * This function is executed having all other CPUs with no stack and IRQs > + * disabled. > + */ > +static int revert_payload(struct payload *data) > +{ > + int i; unsigned int i; > + > + printk(XENLOG_DEBUG "Reverting payload: %s\n", data->id); > + > + for ( i = 0; i < data->nfuncs; i++ ) > + xsplice_revert_jmp(data->funcs + i); > + > + list_del(&data->applied_list); > + > + return 0; > +} > + > +/* Must be holding the payload_list lock */ Missing full stop. Should that lock be called something else now? (Because it is certainly not protecting the list anymore - but also the scheduling action). > +static int schedule_work(struct payload *data, uint32_t cmd) > +{ > + /* Fail if an operation is already scheduled */ > + if ( xsplice_work.do_work ) > + return -EAGAIN; > + > + xsplice_work.cmd = cmd; > + xsplice_work.data = data; > + atomic_set(&xsplice_work.semaphore, 0); > + atomic_set(&xsplice_work.irq_semaphore, 0); > + xsplice_work.ready = false; > + smp_mb(); > + xsplice_work.do_work = true; > + smp_mb(); So this is your 'GO GO' signal right? I think you may want to have 'smb_wmb()' > + > + return 0; > +} > + /me laughs. What a way to 'fix' the NMI watchdog. > +static int mask_nmi_callback(const struct cpu_user_regs *regs, int cpu) > +{ > + return 1; > +} > + > +static void reschedule_fn(void *unused) > +{ > + smp_mb(); /* Synchronize with setting do_work */ > + raise_softirq(SCHEDULE_SOFTIRQ); > +} > + > +/* > + * The main function which manages the work of quiescing the system and > + * patching code. > + */ > +void do_xsplice(void) > +{ > + int id; unsigned int id; > + unsigned int total_cpus; > + nmi_callback_t saved_nmi_callback; > + > + /* Fast path: no work to do */ Missing full stop. > + if ( likely(!xsplice_work.do_work) ) > + return; > + > + ASSERT(local_irq_is_enabled()); > + > + spin_lock(&xsplice_work_lock); > + id = atomic_read(&xsplice_work.semaphore); > + atomic_inc(&xsplice_work.semaphore); > + spin_unlock(&xsplice_work_lock); Could you use 'atomic_inc_and_test' and then you can get rid of the spinlock. > + > + total_cpus = num_online_cpus(); Which could change across these invocations.. Perhaps during these calls we need to lock up CPU up/down code? > + > + if ( id == 0 ) > + { Can you just make this its own function? Perhaps call it 'xsplice_do_single' or such? > + s_time_t timeout, start; > + > + /* Trigger other CPUs to execute do_xsplice */ Missing full stop. > + smp_call_function(reschedule_fn, NULL, 0); > + > + /* Wait for other CPUs with a timeout */ Missing full stop. > + start = NOW(); > + timeout = start + MILLISECS(30); Nah. That should be gotten from the XSPLICE_ACTION_APPLY 'time' parameter - which has an 'timeout' in it. > + while ( atomic_read(&xsplice_work.semaphore) != total_cpus && > + NOW() < timeout ) > + cpu_relax(); > + > + if ( atomic_read(&xsplice_work.semaphore) == total_cpus ) > + { > + struct payload *data2; s/data2/data/ ? > + > + /* "Mask" NMIs */ > + saved_nmi_callback = set_nmi_callback(mask_nmi_callback); > + > + /* All CPUs are waiting, now signal to disable IRQs */ > + xsplice_work.ready = true; > + smp_mb(); > + > + /* Wait for irqs to be disabled */ > + while ( atomic_read(&xsplice_work.irq_semaphore) != (total_cpus > - 1) ) > + cpu_relax(); > + > + local_irq_disable(); > + /* Now this function should be the only one on any stack. > + * No need to lock the payload list or applied list. */ > + switch ( xsplice_work.cmd ) > + { > + case XSPLICE_ACTION_APPLY: > + xsplice_work.data->rc = > apply_payload(xsplice_work.data); > + if ( xsplice_work.data->rc == 0 ) > + xsplice_work.data->state = XSPLICE_STATE_APPLIED; > + break; > + case XSPLICE_ACTION_REVERT: > + xsplice_work.data->rc = > revert_payload(xsplice_work.data); > + if ( xsplice_work.data->rc == 0 ) > + xsplice_work.data->state = XSPLICE_STATE_CHECKED; > + break; > + case XSPLICE_ACTION_REPLACE: > + list_for_each_entry ( data2, &payload_list, list ) > + { > + if ( data2->state != XSPLICE_STATE_APPLIED ) > + continue; > + > + data2->rc = revert_payload(data2); > + if ( data2->rc == 0 ) > + data2->state = XSPLICE_STATE_CHECKED; > + else > + { > + xsplice_work.data->rc = -EINVAL; Why not copy the error code (from data2->rc?) > + break; > + } > + } > + if ( xsplice_work.data->rc != -EINVAL ) And here you can just check for zero. > + { > + xsplice_work.data->rc = > apply_payload(xsplice_work.data); > + if ( xsplice_work.data->rc == 0 ) > + xsplice_work.data->state = > XSPLICE_STATE_APPLIED; > + } > + break; > + default: > + xsplice_work.data->rc = -EINVAL; > + break; > + } > + > + local_irq_enable(); > + set_nmi_callback(saved_nmi_callback); > + } > + else > + { > + xsplice_work.data->rc = -EBUSY; > + } > + > + xsplice_work.do_work = 0; > + smp_mb(); /* Synchronize with waiting CPUs */ Missing full stop. > + } > + else > + { > + /* Wait for all CPUs to rendezvous */ Missing full stop > + while ( xsplice_work.do_work && !xsplice_work.ready ) > + { > + cpu_relax(); > + smp_mb(); > + } > + > + /* Disable IRQs and signal */ Missing full stop. > + local_irq_disable(); > + atomic_inc(&xsplice_work.irq_semaphore); > + > + /* Wait for patching to complete */ Missing full stop. > + while ( xsplice_work.do_work ) > + { > + cpu_relax(); > + smp_mb(); > + } > + local_irq_enable(); > + } > +} > + > static int __init xsplice_init(void) > { > register_keyhandler('x', xsplice_printall, "print xsplicing info", 1); > diff --git a/xen/include/asm-arm/nmi.h b/xen/include/asm-arm/nmi.h > index a60587e..82aff35 100644 > --- a/xen/include/asm-arm/nmi.h > +++ b/xen/include/asm-arm/nmi.h > @@ -4,6 +4,19 @@ > #define register_guest_nmi_callback(a) (-ENOSYS) > #define unregister_guest_nmi_callback() (-ENOSYS) > > +typedef int (*nmi_callback_t)(const struct cpu_user_regs *regs, int cpu); > + > +/** > + * set_nmi_callback > + * > + * Set a handler for an NMI. Only one handler may be > + * set. Return the old nmi callback handler. > + */ > +static inline nmi_callback_t set_nmi_callback(nmi_callback_t callback) > +{ > + return NULL; > +} > + > #endif /* ASM_NMI_H */ > /* > * Local variables: > diff --git a/xen/include/xen/xsplice.h b/xen/include/xen/xsplice.h > index a3946a3..507829c 100644 > --- a/xen/include/xen/xsplice.h > +++ b/xen/include/xen/xsplice.h > @@ -11,7 +11,8 @@ struct xsplice_patch_func { > unsigned long old_addr; > unsigned long old_size; > char *name; > - unsigned char undo[8]; > + uint8_t undo[8]; > + uint8_t pad[56]; This should be in a different patch. As part of the "xsplice: Implement payload loading" > }; > > struct xen_sysctl_xsplice_op; > @@ -19,6 +20,8 @@ int xsplice_control(struct xen_sysctl_xsplice_op *); > > extern void xsplice_printall(unsigned char key); > > +void do_xsplice(void); > + > /* Arch hooks */ > int xsplice_verify_elf(uint8_t *data, ssize_t len); > int xsplice_perform_rel(struct xsplice_elf *elf, > @@ -27,5 +30,7 @@ int xsplice_perform_rel(struct xsplice_elf *elf, > int xsplice_perform_rela(struct xsplice_elf *elf, > struct xsplice_elf_sec *base, > struct xsplice_elf_sec *rela); > +void xsplice_apply_jmp(struct xsplice_patch_func *func); > +void xsplice_revert_jmp(struct xsplice_patch_func *func); > > #endif /* __XEN_XSPLICE_H__ */ > -- > 2.4.3 > > > _______________________________________________ > Xen-devel mailing list > Xen-devel@xxxxxxxxxxxxx > http://lists.xen.org/xen-devel _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxx http://lists.xen.org/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |