[PATCH] livepatch: Support ftrace location with an offset

From: Petr Mladek
Date: Thu Dec 03 2015 - 07:28:19 EST


ftrace_set_filter_ip() function requires the exact ftrace location
as a parameter. It is the same as the function address on x86 and s390.
But it is after the TOC load and LR save on powerpc.

This patch adds klp_ftrace_location() arch-specific function that
will compute the ftrace location from the function address.

I thought about handling this with a constant (define) but I
am not sure if it always would be a constant. I also thought
about a weak function. But this seems to be a good compromise.
We are ready for complications and the compiler still might
optimize it.

I made it livepatch-specific to keep it simple. Ftrace supports
more locations of the handler, e.g. Mcount on x86. But livepatching
does not support most of them. Also ftrace does not know the offset
easily because it gets the ftrace locations from __mcount_loc
object section.

Signed-off-by: Petr Mladek <pmladek@xxxxxxxx>
---
arch/powerpc/include/asm/livepatch.h | 10 ++++++++++
arch/s390/include/asm/livepatch.h | 9 +++++++++
arch/x86/include/asm/livepatch.h | 10 ++++++++++
kernel/livepatch/core.c | 10 +++++++---
4 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/arch/powerpc/include/asm/livepatch.h b/arch/powerpc/include/asm/livepatch.h
index 3200c1152122..aa7e15dae58c 100644
--- a/arch/powerpc/include/asm/livepatch.h
+++ b/arch/powerpc/include/asm/livepatch.h
@@ -34,6 +34,16 @@ static inline int klp_check_compiler_support(void)
extern int klp_write_module_reloc(struct module *mod, unsigned long type,
unsigned long loc, unsigned long value);

+/*
+ * LivePatching works on PPC only when the kernel is compiled with
+ * -mprofile-kernel. The ftrace handler is called after the TOC load
+ * and LR save (16 bytes).
+ */
+static inline unsigned long klp_ftrace_location(unsigned long faddr)
+{
+ return faddr + 16;
+}
+
static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip)
{
regs->nip = ip;
diff --git a/arch/s390/include/asm/livepatch.h b/arch/s390/include/asm/livepatch.h
index 7aa799134a11..b853a1e48d04 100644
--- a/arch/s390/include/asm/livepatch.h
+++ b/arch/s390/include/asm/livepatch.h
@@ -32,6 +32,15 @@ static inline int klp_write_module_reloc(struct module *mod, unsigned long
return -ENOSYS;
}

+/*
+ * LivePatching works only when the ftrace handler is called from the first
+ * instruction of the patched function on s390.
+ */
+static inline unsigned long klp_ftrace_location(unsigned long faddr)
+{
+ return faddr;
+}
+
static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip)
{
regs->psw.addr = ip;
diff --git a/arch/x86/include/asm/livepatch.h b/arch/x86/include/asm/livepatch.h
index 19c099afa861..9a8c84d3fae2 100644
--- a/arch/x86/include/asm/livepatch.h
+++ b/arch/x86/include/asm/livepatch.h
@@ -36,6 +36,16 @@ static inline int klp_check_compiler_support(void)
int klp_write_module_reloc(struct module *mod, unsigned long type,
unsigned long loc, unsigned long value);

+
+/*
+ * LivePatching works only when ftrace uses fentry on x86. Therefore
+ * the ftrace location is the same as the address of the function.
+ */
+static inline unsigned long klp_ftrace_location(unsigned long faddr)
+{
+ return faddr;
+}
+
static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip)
{
regs->ip = ip;
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index db545cbcdb89..f9dc8889e1f1 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -364,8 +364,10 @@ static void klp_disable_func(struct klp_func *func)
return;

if (list_is_singular(&ops->func_stack)) {
+ unsigned long ftrace_loc = klp_ftrace_location(func->old_addr);
+
WARN_ON(unregister_ftrace_function(&ops->fops));
- WARN_ON(ftrace_set_filter_ip(&ops->fops, func->old_addr, 1, 0));
+ WARN_ON(ftrace_set_filter_ip(&ops->fops, ftrace_loc, 1, 0));

list_del_rcu(&func->stack_node);
list_del(&ops->node);
@@ -390,6 +392,8 @@ static int klp_enable_func(struct klp_func *func)

ops = klp_find_ops(func->old_addr);
if (!ops) {
+ unsigned long ftrace_loc = klp_ftrace_location(func->old_addr);
+
ops = kzalloc(sizeof(*ops), GFP_KERNEL);
if (!ops)
return -ENOMEM;
@@ -404,7 +408,7 @@ static int klp_enable_func(struct klp_func *func)
INIT_LIST_HEAD(&ops->func_stack);
list_add_rcu(&func->stack_node, &ops->func_stack);

- ret = ftrace_set_filter_ip(&ops->fops, func->old_addr, 0, 0);
+ ret = ftrace_set_filter_ip(&ops->fops, ftrace_loc, 0, 0);
if (ret) {
pr_err("failed to set ftrace filter for function '%s' (%d)\n",
func->old_name, ret);
@@ -415,7 +419,7 @@ static int klp_enable_func(struct klp_func *func)
if (ret) {
pr_err("failed to register ftrace handler for function '%s' (%d)\n",
func->old_name, ret);
- ftrace_set_filter_ip(&ops->fops, func->old_addr, 1, 0);
+ ftrace_set_filter_ip(&ops->fops, ftrace_loc, 1, 0);
goto err;
}

--
1.8.5.6

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/