[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC] xen/arm64: livepatch: enable attaching callbacks


  • To: xen-devel@xxxxxxxxxxxxxxxxxxxx
  • From: Ryo Takakura <takakura@xxxxxxxxxxxxx>
  • Date: Mon, 29 Jun 2026 11:01:28 +0900
  • Arc-authentication-results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=valinux.co.jp; dmarc=pass action=none header.from=valinux.co.jp; dkim=pass header.d=valinux.co.jp; arc=none
  • Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=JjMi6goWu6dnJNcUo0E7KxWnlD36ags8JxTs8eDXz54=; b=lLeJHAKrA6kwcvCVKxk0GxvLaIuC0jNWWWHvfZMJbk5Wdm37s0kOe8JR4WNs4eI36n8eOrhkjfF2tlpC6KC1aORHf0o1Nsxrx+fw10jrpDu5ZIR3JzjrDPjkRu2iRmjMGOe23M4H9uH2Dt6cNUTnuccxJEo4Jxr3pQ7hx/fgYVF9mfuaTlL2stb9pdUZ7AfKaVcQrBdDkNSeUCHA6CqVPrOGfyIv9GZUa584FQC1U322Bcj2ah+0DBLdQN8lJeyzIEVDZyGzos/YeR7KKYz6avgSeywj7/vZ34Jrgqf1kb8hMLAWg9ug/taYrBAajdcMD8m47UfPAKDjf2zjTVSg3Q==
  • Arc-seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=aAaux/coelTE9SNl29mDXZUFVwSC9oXuRKTDnXpL00KsjgSZhYKWOvkqYSsJmq6FyoATMDJsAqWbWbPu1C3jTj6UTwu4ZvPSwZVFWVnBvWXuu60sUzkBInpjIF7LoFsLRacX96U6SoC/SUghbQr7mT5RqJgAJ6b6Y88RUHPECxcvhq91PdC6WF2W6M797IaZx3h96cs5rZDu/FVhJezMpReEHLrlX58+3Np5NF9nd+dGwAKs8VYtkx3GUa+uebQSqze3hPSjD7IRzT064P7t01WriAMcE8lPC8teuHo0k41ZEmuCtC0rOibo0nZOQAy0CdlCpX3IPpsxgw4sstaz6g==
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=selector1 header.d=valinux.co.jp header.i="@valinux.co.jp" header.h="From:Date:Subject:Message-Id:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck"
  • Authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=valinux.co.jp;
  • Cc: roger.pau@xxxxxxxxxx, ross.lagerwall@xxxxxxxxxx, sstabellini@xxxxxxxxxx, julien@xxxxxxx, bertrand.marquis@xxxxxxx, michal.orzel@xxxxxxx, Volodymyr_Babchuk@xxxxxxxx, andrew.cooper3@xxxxxxxxxx, anthony.perard@xxxxxxxxxx, jbeulich@xxxxxxxx, taka@xxxxxxxxxxxxx, den@xxxxxxxxxxxxx, Ryo Takakura <takakura@xxxxxxxxxxxxx>
  • Delivery-date: Mon, 29 Jun 2026 03:42:07 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

Linux ftrace allows registering callbacks which is useful
for debugging and tracing events. On Linux, it is done by
reserving function entry points at compile time which can
later be patched to branch to a trampoline.

This patch implements similar callback feature, but with
different approach using existing livepatch infrastructure.
Instead of reserving function entry points at compile time,
the traced function will be livepatched so that it branches
to the trampoline.

The role of the trampoline(illustrated below) is to preserve
the context while jumping to the tracer function, and return
back to the traced function with its context restored.

trampoline:
    Save regs
    Call tracer function
    Restore regs
    old_addr
    return old_addr + 4

One can request the feature by setting @trampoline_buf to 1
which will allocate a buffer for trampoline.

Signed-off-by: Ryo Takakura <takakura@xxxxxxxxxxxxx>
---

Hi!

For the future, I'm thinking of linux-like extensions
which help tracing and debugging by passing:
- saved registers
- caller information
- private data
- and so on ...

I would appreciate any advice or suggestion.
Thanks!

Example payload file:

#include <xen/lib.h>
#include <xen/livepatch.h>

static void my_tracer(void)
{
    printk("livepatch: do_domctl was called\n");
}

static struct livepatch_func funcs[]
    __attribute__((section(".livepatch.funcs"))) =
{
    {
        .name = "do_domctl",
        .old_size = 4572,
        .new_addr = my_tracer,
        .new_size = 32,
        .trampoline_buf = (void *)1,
        .version = LIVEPATCH_PAYLOAD_VERSION,
    }
};

Sample output:

$ tools/misc/xen-livepatch list
 ID                                     | status     | metadata
----------------------------------------+------------+---------------
trace_do_domctl                         | APPLIED    |
$ xl vcpu-list Domain-0
Name                                ID  VCPU   CPU State   Time(s) Affinity 
(Hard / Soft)
(XEN) livepatch: do_domctl was called
(XEN) livepatch: do_domctl was called
(XEN) livepatch: do_domctl was called
(XEN) livepatch: do_domctl was called
(XEN) livepatch: do_domctl was called
(XEN) livepatch: do_domctl was called
(XEN) livepatch: do_domctl was called
(XEN) livepatch: do_domctl was called
(XEN) livepatch: do_domctl was called
Domain-0                             0     0    1   -b-      67.7  all / all
Domain-0                             0     1    3   -b-     457.2  all / all
Domain-0                             0     2    2   -b-      42.4  all / all
Domain-0                             0     3    0   r--      32.4  all / all

Sincerely,
Ryo Takakura

---
 xen/arch/arm/arm64/livepatch.c      | 104 +++++++++++++++++++++++++++-
 xen/common/livepatch.c              |  40 +++++++++--
 xen/include/public/sysctl.h         |   3 +-
 xen/include/xen/livepatch.h         |  13 +++-
 xen/include/xen/livepatch_payload.h |   2 +
 5 files changed, 150 insertions(+), 12 deletions(-)

diff --git a/xen/arch/arm/arm64/livepatch.c b/xen/arch/arm/arm64/livepatch.c
index e135bd5bf9..b7c9aba94e 100644
--- a/xen/arch/arm/arm64/livepatch.c
+++ b/xen/arch/arm/arm64/livepatch.c
@@ -15,6 +15,29 @@
 #include <asm/insn.h>
 #include <asm/livepatch.h>
 
+
+#define AARCH64_REG_SP 31
+
+static uint32_t aarch64_insn_gen_stp_pre(unsigned int rt,
+                                         unsigned int rt2)
+{
+    return 0xa9800000 |
+           (((-16 / 8) & 0x7f) << 15) |
+           (rt2 << 10) |
+           (AARCH64_REG_SP << 5) |
+           rt;
+}
+
+static uint32_t aarch64_insn_gen_ldp_post(unsigned int rt,
+                                          unsigned int rt2)
+{
+    return 0xa8c00000 |
+           (((16 / 8) & 0x7f) << 15) |
+           (rt2 << 10) |
+           (AARCH64_REG_SP << 5) |
+           rt;
+}
+
 void arch_livepatch_apply(const struct livepatch_func *func,
                           struct livepatch_fstate *state)
 {
@@ -34,12 +57,87 @@ void arch_livepatch_apply(const struct livepatch_func *func,
     /* Save old ones. */
     memcpy(state->insn_buffer, func->old_addr, len);
 
-    if ( func->new_addr )
+    if ( !func->new_addr )
+    {
+        insn = aarch64_insn_gen_nop();
+    }
+    else if ( func->trampoline_buf )
+    {
+        int rc;
+        uint32_t *trampoline = func->trampoline_buf;
+        uint32_t *tp = trampoline;
+        void *orig_cont_addr = (void *)func->old_addr + len;
+        unsigned int trampoline_code_size = len + 12 * ARCH_PATCH_INSN_SIZE;
+        unsigned long trampoline_start = (unsigned long)trampoline & PAGE_MASK;
+        unsigned long trampoline_end =
+            PAGE_ALIGN((unsigned long)trampoline + trampoline_code_size);
+
+        /*
+         * Make the payload text area writeable while generating
+         * the trampoline instructions.
+         */
+        rc = modify_xen_mappings(trampoline_start, trampoline_end,
+                                 PAGE_HYPERVISOR);
+        if ( rc )
+        {
+            printk(XENLOG_ERR LIVEPATCH
+                   "Failed to make trampoline writable: %d\n", rc);
+            return;
+        }
+
+        /* Save state before calling the tracer. */
+        *tp++ = aarch64_insn_gen_stp_pre(0, 1);
+        *tp++ = aarch64_insn_gen_stp_pre(2, 3);
+        *tp++ = aarch64_insn_gen_stp_pre(4, 5);
+        *tp++ = aarch64_insn_gen_stp_pre(6, 7);
+        *tp++ = aarch64_insn_gen_stp_pre(29, 30);
+
+        /* Call user's tracing function. */
+        insn = aarch64_insn_gen_branch_imm(
+            (unsigned long)tp,
+            (unsigned long)func->new_addr,
+            AARCH64_INSN_BRANCH_LINK);
+        *tp++ = insn;
+
+        /* Restore state before continuing original function. */
+        *tp++ = aarch64_insn_gen_ldp_post(29, 30);
+        *tp++ = aarch64_insn_gen_ldp_post(6, 7);
+        *tp++ = aarch64_insn_gen_ldp_post(4, 5);
+        *tp++ = aarch64_insn_gen_ldp_post(2, 3);
+        *tp++ = aarch64_insn_gen_ldp_post(0, 1);
+
+        /* Original instruction. */
+        memcpy(tp, state->insn_buffer, len);
+        tp += len / ARCH_PATCH_INSN_SIZE;
+
+        /* Branch back to original function. */
+        insn = aarch64_insn_gen_branch_imm(
+            (unsigned long)tp,
+            (unsigned long)orig_cont_addr,
+            AARCH64_INSN_BRANCH_NOLINK);
+        *tp++ = insn;
+
+        clean_and_invalidate_dcache_va_range(trampoline, trampoline_code_size);
+
+        rc = modify_xen_mappings(trampoline_start, trampoline_end,
+                                 PAGE_HYPERVISOR_RX);
+        if ( rc )
+        {
+            printk(XENLOG_ERR LIVEPATCH
+                   "Failed to restore trampoline RX mapping: %d\n", rc);
+            return;
+        }
+
+        /* Branch from original function to trampoline. */
+        insn = aarch64_insn_gen_branch_imm(
+            (unsigned long)func->old_addr,
+            (unsigned long)func->trampoline_buf,
+            AARCH64_INSN_BRANCH_NOLINK);
+    }
+    else if ( func->new_addr )
         insn = aarch64_insn_gen_branch_imm((unsigned long)func->old_addr,
                                            (unsigned long)func->new_addr,
                                            AARCH64_INSN_BRANCH_NOLINK);
-    else
-        insn = aarch64_insn_gen_nop();
 
     /* Verified in livepatch_verify_distance. */
     ASSERT(insn != AARCH64_BREAK_FAULT);
diff --git a/xen/common/livepatch.c b/xen/common/livepatch.c
index 7515a040ad..8863ad5ca3 100644
--- a/xen/common/livepatch.c
+++ b/xen/common/livepatch.c
@@ -280,10 +280,30 @@ static int move_payload(struct payload *payload, struct 
livepatch_elf *elf)
 {
     void *text_buf, *ro_buf, *rw_buf;
     unsigned int i, rw_buf_sec, rw_buf_cnt = 0;
-    size_t size = 0;
+    const struct livepatch_elf_sec *sec;
+    const struct livepatch_func *funcs;
+    unsigned int nfuncs, trampolines_needed = 0;
+    size_t size = 0, trampoline_size = 0;
     unsigned int *offset;
     int rc = 0;
 
+    sec = livepatch_elf_sec_by_name(elf, ELF_LIVEPATCH_FUNC);
+    if ( sec )
+    {
+        funcs = sec->addr;
+        nfuncs = sec->sec->sh_size / sizeof(*funcs);
+
+        for ( i = 0; i < nfuncs; ++i )
+            if ( funcs[i].trampoline_buf == (void *)1 )
+                trampolines_needed++;
+
+        if ( trampolines_needed )
+        {
+            payload->n_trampolines = trampolines_needed;
+            trampoline_size = trampolines_needed * LIVEPATCH_TRAMPOLINE_SIZE;
+        }
+    }
+
     offset = xmalloc_array(unsigned int, elf->hdr->e_shnum);
     if ( !offset )
         return -ENOMEM;
@@ -323,8 +343,8 @@ static int move_payload(struct payload *payload, struct 
livepatch_elf *elf)
      * them on separate pages. The last one will by default fall on its
      * own page.
      */
-    size = PAGE_ALIGN(payload->text_size) + PAGE_ALIGN(payload->rw_size) +
-                      payload->ro_size;
+    size = PAGE_ALIGN(payload->text_size + trampoline_size) +
+           PAGE_ALIGN(payload->rw_size) + payload->ro_size;
 
     size = PFN_UP(size); /* Nr of pages. */
     text_buf = vmalloc_xen(size * PAGE_SIZE);
@@ -335,9 +355,12 @@ static int move_payload(struct payload *payload, struct 
livepatch_elf *elf)
         rc = -ENOMEM;
         goto out;
     }
-    rw_buf = text_buf + PAGE_ALIGN(payload->text_size);
+    rw_buf = text_buf + PAGE_ALIGN(payload->text_size + trampoline_size);
     ro_buf = rw_buf + PAGE_ALIGN(payload->rw_size);
 
+    if ( trampoline_size )
+        payload->trampoline_addr = text_buf + payload->text_size;
+
     payload->pages = size;
     payload->text_addr = text_buf;
     payload->rw_addr = rw_buf;
@@ -690,7 +713,7 @@ static int prepare_payload(struct payload *payload,
 {
     const struct livepatch_elf_sec *sec;
     const struct payload *data;
-    unsigned int i;
+    unsigned int i, trampoline_idx = 0;
     struct livepatch_func *funcs;
     struct livepatch_func *f;
     struct virtual_region *region;
@@ -737,6 +760,13 @@ static int prepare_payload(struct payload *payload,
             if ( rc )
                 return rc;
 
+            if ( f->trampoline_buf == (void *)1 )
+            {
+                f->trampoline_buf = (char *)payload->trampoline_addr +
+                                    trampoline_idx * LIVEPATCH_TRAMPOLINE_SIZE;
+                trampoline_idx++;
+            }
+
             rc = livepatch_verify_distance(f);
             if ( rc )
                 return rc;
diff --git a/xen/include/public/sysctl.h b/xen/include/public/sysctl.h
index c7cd9b4eb0..e79615d7c9 100644
--- a/xen/include/public/sysctl.h
+++ b/xen/include/public/sysctl.h
@@ -1010,10 +1010,11 @@ struct livepatch_func {
     const char *name;       /* Name of function to be patched. */
     void *new_addr;
     void *old_addr;
+    void *trampoline_buf;   /* Trampoline buffer when set to (void *)1. */
     uint32_t new_size;
     uint32_t old_size;
     uint8_t version;        /* MUST be LIVEPATCH_PAYLOAD_VERSION. */
-    uint8_t _pad[39];
+    uint8_t _pad[31];
     livepatch_expectation_t expect;
 };
 typedef struct livepatch_func livepatch_func_t;
diff --git a/xen/include/xen/livepatch.h b/xen/include/xen/livepatch.h
index 45c8924f34..7a81763cf2 100644
--- a/xen/include/xen/livepatch.h
+++ b/xen/include/xen/livepatch.h
@@ -48,6 +48,8 @@ struct xen_sysctl_livepatch_op;
 #define ELF_LIVEPATCH_POSTREVERT_HOOK ".livepatch.hooks.postrevert"
 /* Arbitrary limit for payload size and .bss section size. */
 #define LIVEPATCH_MAX_SIZE     MB(2)
+/* Size of a trampoline used for function tracing */
+#define LIVEPATCH_TRAMPOLINE_SIZE 128
 
 struct livepatch_symbol {
     const char *name;
@@ -109,13 +111,18 @@ unsigned int livepatch_insn_len(const struct 
livepatch_func *func,
 
 static inline int livepatch_verify_distance(const struct livepatch_func *func)
 {
+    const void *target;
     long offset;
     long range = ARCH_LIVEPATCH_RANGE;
 
-    if ( !func->new_addr ) /* Ignore NOPs. */
-        return 0;
+    if ( func->trampoline_buf )
+       target = func->trampoline_buf;
+    else if ( func->new_addr )
+       target = func->new_addr;
+    else
+       return 0; /* Ignore NOPs. */
 
-    offset = func->old_addr - func->new_addr;
+    offset = func->old_addr - target;
     if ( offset < -range || offset >= range )
         return -EOVERFLOW;
 
diff --git a/xen/include/xen/livepatch_payload.h 
b/xen/include/xen/livepatch_payload.h
index c6dc7cb5fa..7ed8be3fd6 100644
--- a/xen/include/xen/livepatch_payload.h
+++ b/xen/include/xen/livepatch_payload.h
@@ -52,6 +52,8 @@ struct payload {
     size_t ro_size;                      /* .. and its size (if any). */
     unsigned int pages;                  /* Total pages for [text,rw,ro]_addr 
*/
     struct list_head applied_list;       /* Linked to 'applied_list'. */
+    void *trampoline_addr;               /* Virtual address of trampoline 
area. */
+    unsigned int n_trampolines;          /* Number of trampolines to be 
allocated */
     const struct livepatch_func *funcs;  /* The array of functions to patch. */
     struct livepatch_fstate *fstate;     /* State of patched functions. */
     unsigned int nfuncs;                 /* Nr of functions to patch. */
-- 
2.34.1




 


Rackspace

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