[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

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.