|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [RFC 1/5] xen/arm64: livepatch: enable attaching callbacks
Linux ftrace allows registering callbacks which is useful
for debugging and tracing events. On Linux, it is done by
reserving NOPs at function entry at compile time which
can later be patched with a branch instruction to a trampoline.
This patch introduces similar callback feature adopting
linux-like approach of reserving 2 NOPs at function entry.
When user requests the feature, the NOPs will be livepatched with:
mov x9, lr
bl xen_livepatch_trace_caller
where xen_livepatch_trace_caller serves as a trampoline
for saving registers before calling callback functions.
Callback functions receive two arguments:
ip: Address of the traced function
parent_ip: Address where the traced function was called
One can request the feature by specifying the section
for struct livepatch_func as .livepatch.traces.
Signed-off-by: Ryo Takakura <takakura@xxxxxxxxxxxxx>
---
xen/arch/arm/Kconfig | 13 +++++
xen/arch/arm/arch.mk | 3 ++
xen/arch/arm/arm64/Makefile | 1 +
xen/arch/arm/arm64/insn.c | 5 ++
xen/arch/arm/arm64/livepatch-asm.S | 68 +++++++++++++++++++++++++++
xen/arch/arm/arm64/livepatch.c | 34 ++++++++++++++
xen/arch/arm/include/asm/arm64/insn.h | 1 +
xen/common/Kconfig | 3 ++
xen/common/livepatch.c | 68 +++++++++++++++++++++++++--
xen/include/xen/livepatch.h | 8 ++++
xen/include/xen/livepatch_payload.h | 7 +--
11 files changed, 205 insertions(+), 6 deletions(-)
create mode 100644 xen/arch/arm/arm64/livepatch-asm.S
diff --git a/xen/arch/arm/Kconfig b/xen/arch/arm/Kconfig
index b9c5c40678..37fdbcc416 100644
--- a/xen/arch/arm/Kconfig
+++ b/xen/arch/arm/Kconfig
@@ -286,6 +286,19 @@ config PCI_PASSTHROUGH
help
This option enables PCI device passthrough
+config ARM64_PATCHABLE_FUNCTION_ENTRY
+ bool "Reserve two NOPs at function entry for tracing"
+ depends on ARM_64 && LIVEPATCH && $(cc-option,-fpatchable-function-entry=2)
+ select HAS_LIVEPATCH_TRACE
+ default n
+ help
+ This option compiles Xen with -fpatchable-function-entry=2, which
+ reserves two NOP instructions at the beginning of functions. The NOPs
+ will be livepatched with a branch instruction to callback functions
+ registered by user.
+
+ Enable this to use livepatch tracing feature.
+
endmenu
menu "ARM errata workaround via the alternative framework"
diff --git a/xen/arch/arm/arch.mk b/xen/arch/arm/arch.mk
index dea8dbd18a..940f1217c4 100644
--- a/xen/arch/arm/arch.mk
+++ b/xen/arch/arm/arch.mk
@@ -19,6 +19,9 @@ endif
CFLAGS-$(CONFIG_ARM_64) += -mgeneral-regs-only # No fp registers etc
$(call cc-option-add,CFLAGS-$(CONFIG_ARM_64),CC,-mno-outline-atomics)
+# Reserve entries for livepatch tracing
+CFLAGS-$(CONFIG_ARM64_PATCHABLE_FUNCTION_ENTRY) += -fpatchable-function-entry=2
+
ifneq ($(filter command line environment,$(origin CONFIG_EARLY_PRINTK)),)
$(error You must use 'make menuconfig' to enable/disable early printk now)
endif
diff --git a/xen/arch/arm/arm64/Makefile b/xen/arch/arm/arm64/Makefile
index 6491c5350b..7f52b10e66 100644
--- a/xen/arch/arm/arm64/Makefile
+++ b/xen/arch/arm/arm64/Makefile
@@ -12,6 +12,7 @@ obj-y += entry.o
obj-y += head.o
obj-y += insn.o
obj-$(CONFIG_LIVEPATCH) += livepatch.o
+obj-$(CONFIG_ARM64_PATCHABLE_FUNCTION_ENTRY) += livepatch-asm.o
obj-y += smc.o
obj-y += smpboot.o
obj-$(CONFIG_ARM64_SVE) += sve.o sve-asm.o
diff --git a/xen/arch/arm/arm64/insn.c b/xen/arch/arm/arm64/insn.c
index 6b97a84ba7..dc3ddc6351 100644
--- a/xen/arch/arm/arm64/insn.c
+++ b/xen/arch/arm/arm64/insn.c
@@ -218,6 +218,11 @@ u32 __kprobes aarch64_insn_gen_nop(void)
return aarch64_insn_gen_hint(AARCH64_INSN_HINT_NOP);
}
+u32 aarch64_insn_gen_move_reg(uint32_t rd, uint32_t rm)
+{
+ return 0xaa0003e0 | (rm << 16) | rd;
+}
+
/*
* Decode the imm field of a branch, and return the byte offset as a
* signed value (so it can be used when computing a new branch
diff --git a/xen/arch/arm/arm64/livepatch-asm.S
b/xen/arch/arm/arm64/livepatch-asm.S
new file mode 100644
index 0000000000..4f94e791a3
--- /dev/null
+++ b/xen/arch/arm/arm64/livepatch-asm.S
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * xen/arch/arm/arm64/livepatch-asm.S
+ *
+ * Trampoline for livepatch tracer.
+ *
+ * If livepatch tracer is enabled, function entries are placed
+ * with two NOPs. arch_livepatch_apply_trace() will be livepatching
+ * those NOPs to:
+ *
+ * mov x9, x30
+ * bl xen_livepatch_trace_caller
+ *
+ * x9 = traced function's original LR
+ * x30 = traced function + 8
+ */
+FUNC(xen_livepatch_trace_caller)
+ /*
+ * Registers to save/restore:
+ *
+ * x0-x8 Function arguments
+ * x9 Original LR
+ * x29 Original FP
+ * x30 PC to return
+ */
+ sub sp, sp, #96
+
+ /* Save function arguments */
+ stp x0, x1, [sp, #0]
+ stp x2, x3, [sp, #16]
+ stp x4, x5, [sp, #32]
+ stp x6, x7, [sp, #48]
+ str x8, [sp, #64]
+
+ /* Save LR, FP, PC */
+ str x9, [sp, #72]
+ str x29, [sp, #80]
+ str x30, [sp, #88]
+
+ /*
+ * xen_livepatch_trace_dispatcher(ip, parent_ip)
+ *
+ * ip: Address of the traced function.
+ * parent_ip: Address where the traced function was called.
+ */
+ sub x0, x30, #8
+ mov x1, x9
+ bl xen_livepatch_trace_dispatcher
+
+ /* Restore function arguments */
+ ldp x0, x1, [sp, #0]
+ ldp x2, x3, [sp, #16]
+ ldp x4, x5, [sp, #32]
+ ldp x6, x7, [sp, #48]
+ ldr x8, [sp, #64]
+
+ /* Restore FP, LR, PC */
+ ldr x29, [sp, #80]
+ ldr x30, [sp, #72]
+ ldr x9, [sp, #88]
+
+ /* Restore SP */
+ add sp, sp, #96
+
+ /* ret x9 */
+ .inst 0xd65f0120
+ sb
+END(xen_livepatch_trace_caller)
diff --git a/xen/arch/arm/arm64/livepatch.c b/xen/arch/arm/arm64/livepatch.c
index 39159ba8b5..17753f4463 100644
--- a/xen/arch/arm/arm64/livepatch.c
+++ b/xen/arch/arm/arm64/livepatch.c
@@ -14,6 +14,10 @@
#include <asm/bitops.h>
#include <asm/insn.h>
+#ifdef CONFIG_ARM64_PATCHABLE_FUNCTION_ENTRY
+extern void xen_livepatch_trace_caller(void);
+#endif
+
void arch_livepatch_apply(const struct livepatch_func *func,
struct livepatch_fstate *state)
{
@@ -492,6 +496,36 @@ int arch_livepatch_perform_rela(struct livepatch_elf *elf,
return -EINVAL;
}
+#ifdef CONFIG_ARM64_PATCHABLE_FUNCTION_ENTRY
+void arch_livepatch_apply_trace(const struct livepatch_func *trace)
+{
+ uint32_t *new_ptr;
+ uint32_t insns[2];
+
+ new_ptr = trace->old_addr - (void *)_start + vmap_of_xen_text;
+
+ insns[0] = aarch64_insn_gen_move_reg(9, 30); /* mov x9, lr */
+ insns[1] = aarch64_insn_gen_branch_imm((unsigned long)trace->old_addr +
ARCH_PATCH_INSN_SIZE,
+ (unsigned
long)xen_livepatch_trace_caller,
+ AARCH64_INSN_BRANCH_LINK);
+
+ memcpy(new_ptr, insns, sizeof(insns));
+ clean_and_invalidate_dcache_va_range(new_ptr, sizeof(insns));
+}
+
+void arch_livepatch_revert_trace(const struct livepatch_func *trace)
+{
+ void *new_ptr = trace->old_addr - (void *)_start + vmap_of_xen_text;
+ uint32_t insns[2] = {
+ aarch64_insn_gen_nop(),
+ aarch64_insn_gen_nop(),
+ };
+
+ memcpy(new_ptr, insns, sizeof(insns));
+ clean_and_invalidate_dcache_va_range(new_ptr, sizeof(insns));
+}
+#endif /* CONFIG_ARM64_PATCHABLE_FUNCTION_ENTRY */
+
/*
* Local variables:
* mode: C
diff --git a/xen/arch/arm/include/asm/arm64/insn.h
b/xen/arch/arm/include/asm/arm64/insn.h
index ab290030ab..a83d8de0e4 100644
--- a/xen/arch/arm/include/asm/arm64/insn.h
+++ b/xen/arch/arm/include/asm/arm64/insn.h
@@ -82,6 +82,7 @@ u32 aarch64_insn_gen_branch_imm(unsigned long pc, unsigned
long addr,
enum aarch64_insn_branch_type type);
u32 aarch64_insn_gen_hint(enum aarch64_insn_hint_op op);
u32 aarch64_insn_gen_nop(void);
+u32 aarch64_insn_gen_move_reg(uint32_t rd, uint32_t rm);
/* Wrapper for common code */
static inline bool insn_is_branch_imm(u32 insn)
diff --git a/xen/common/Kconfig b/xen/common/Kconfig
index 5b289e444f..7cf6937c2f 100644
--- a/xen/common/Kconfig
+++ b/xen/common/Kconfig
@@ -605,6 +605,9 @@ config TRACEBUFFER
to be collected at run time for debugging or performance analysis.
Memory and execution overhead when not active is minimal.
+config HAS_LIVEPATCH_TRACE
+ bool
+
config LLC_COLORING
bool "Last Level Cache (LLC) coloring" if EXPERT
depends on HAS_LLC_COLORING
diff --git a/xen/common/livepatch.c b/xen/common/livepatch.c
index 7515a040ad..70f601846f 100644
--- a/xen/common/livepatch.c
+++ b/xen/common/livepatch.c
@@ -198,6 +198,41 @@ static const char *cf_check livepatch_symbols_lookup(
return n;
}
+#ifdef CONFIG_HAS_LIVEPATCH_TRACE
+void __attribute__((patchable_function_entry(0, 0)))
+xen_livepatch_trace_dispatcher(unsigned long ip, unsigned long parent_ip)
+{
+ const struct payload *data;
+ unsigned int i;
+ const struct livepatch_func *f;
+
+ rcu_read_lock(&rcu_payload_lock);
+
+ list_for_each_entry_rcu ( data, &payload_list, list )
+ {
+ if ( data->state != LIVEPATCH_STATE_APPLIED || !data->is_trace )
+ continue;
+
+ for ( i = 0; i < data->nfuncs; ++i )
+ {
+ f = &data->funcs[i];
+
+ if ( (unsigned long)f->old_addr != ip )
+ continue;
+
+ ASSERT(f->new_addr);
+ ((livepatch_trace_func_t)f->new_addr)((unsigned long)f->old_addr,
parent_ip);
+
+ goto out;
+ }
+ }
+
+out:
+ rcu_read_unlock(&rcu_payload_lock);
+}
+
+#endif /* CONFIG_HAS_LIVEPATCH_TRACE */
+
/* Lookup function's old address if not already resolved. */
static int resolve_old_address(struct livepatch_func *f,
const struct livepatch_elf *elf)
@@ -551,6 +586,9 @@ static int check_patching_sections(const struct
livepatch_elf *elf)
{
unsigned int i;
static const char *const names[] = { ELF_LIVEPATCH_FUNC,
+#ifdef CONFIG_HAS_LIVEPATCH_TRACE
+ ELF_LIVEPATCH_TRACES,
+#endif
ELF_LIVEPATCH_LOAD_HOOKS,
ELF_LIVEPATCH_UNLOAD_HOOKS,
ELF_LIVEPATCH_PREAPPLY_HOOK,
@@ -696,7 +734,14 @@ static int prepare_payload(struct payload *payload,
struct virtual_region *region;
int rc;
- sec = livepatch_elf_sec_by_name(elf, ELF_LIVEPATCH_FUNC);
+#ifdef CONFIG_HAS_LIVEPATCH_TRACE
+ sec = livepatch_elf_sec_by_name(elf, ELF_LIVEPATCH_TRACES);
+ payload->is_trace = !!sec;
+
+ if ( !sec )
+#endif
+ sec = livepatch_elf_sec_by_name(elf, ELF_LIVEPATCH_FUNC);
+
if ( sec )
{
if ( !section_ok(elf, sec, sizeof(*payload->funcs)) )
@@ -721,6 +766,13 @@ static int prepare_payload(struct payload *payload,
return -EOPNOTSUPP;
}
+ if ( payload->is_trace && !f->new_addr )
+ {
+ printk(XENLOG_ERR LIVEPATCH "%s: Trace new_addr is NULL\n",
+ elf->name);
+ return -EINVAL;
+ }
+
/* 'old_addr', 'new_addr', 'new_size' can all be zero. */
if ( !f->old_size )
{
@@ -1460,7 +1512,12 @@ static int apply_payload(struct payload *data)
continue;
}
- arch_livepatch_apply(func, state);
+#ifdef CONFIG_HAS_LIVEPATCH_TRACE
+ if ( data->is_trace )
+ arch_livepatch_apply_trace(func);
+ else
+#endif
+ arch_livepatch_apply(func, state);
state->applied = LIVEPATCH_FUNC_APPLIED;
}
@@ -1509,7 +1566,12 @@ int revert_payload(struct payload *data)
continue;
}
- arch_livepatch_revert(func, state);
+#ifdef CONFIG_HAS_LIVEPATCH_TRACE
+ if ( data->is_trace )
+ arch_livepatch_revert_trace(func);
+ else
+#endif
+ arch_livepatch_revert(func, state);
state->applied = LIVEPATCH_FUNC_NOT_APPLIED;
}
diff --git a/xen/include/xen/livepatch.h b/xen/include/xen/livepatch.h
index 5dc8c61d37..ae34c938d6 100644
--- a/xen/include/xen/livepatch.h
+++ b/xen/include/xen/livepatch.h
@@ -26,6 +26,7 @@ struct xen_sysctl_livepatch_op;
#define LIVEPATCH "livepatch: "
/* ELF payload special section names. */
#define ELF_LIVEPATCH_FUNC ".livepatch.funcs"
+#define ELF_LIVEPATCH_TRACES ".livepatch.traces"
#define ELF_LIVEPATCH_DEPENDS ".livepatch.depends"
#define ELF_LIVEPATCH_XEN_DEPENDS ".livepatch.xen_depends"
#define ELF_BUILD_ID_NOTE ".note.gnu.build-id"
@@ -86,6 +87,11 @@ void arch_livepatch_init(void);
int arch_livepatch_verify_func(const struct livepatch_func *func);
+typedef void (*livepatch_trace_func_t)(unsigned long ip,
+ unsigned long parent_ip);
+void xen_livepatch_trace_dispatcher(unsigned long ip,
+ unsigned long parent_ip);
+
static inline
unsigned int livepatch_insn_len(const struct livepatch_func *func,
const struct livepatch_fstate *state)
@@ -120,8 +126,10 @@ void arch_livepatch_revive(void);
void arch_livepatch_apply(const struct livepatch_func *func,
struct livepatch_fstate *state);
+void arch_livepatch_apply_trace(const struct livepatch_func *trace);
void arch_livepatch_revert(const struct livepatch_func *func,
struct livepatch_fstate *state);
+void arch_livepatch_revert_trace(const struct livepatch_func *trace);
void arch_livepatch_post_action(void);
void arch_livepatch_mask(void);
diff --git a/xen/include/xen/livepatch_payload.h
b/xen/include/xen/livepatch_payload.h
index c6dc7cb5fa..3a81abe0de 100644
--- a/xen/include/xen/livepatch_payload.h
+++ b/xen/include/xen/livepatch_payload.h
@@ -52,9 +52,10 @@ 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'. */
- 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. */
+ const struct livepatch_func *funcs; /* The array of functions to patch or
trace. */
+ struct livepatch_fstate *fstate; /* State of patched/traced functions.
*/
+ unsigned int nfuncs; /* Nr of functions to patch or trace.
*/
+ bool is_trace; /* Is a trace payload. */
const struct livepatch_symbol *symtab; /* All symbols. */
const char *strtab; /* Pointer to .strtab. */
struct virtual_region region; /* symbol, bug.frame patching and
--
2.34.1
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |