Skip to contentSkip to conversation

livepatch: arm64: add support for livepatch on arm64

RFC PATCH New Li Bin 3 participants 9 messages Dec 14, 2015 – Dec 17, 2015

Subsystems (3)
lore
Share & tools
lore

Li Bin

author
#0 lore raw

cover livepatch: arm64: add support for livepatch on arm64

This patchset depends on the on-going gcc feature "-fprolog-pad=N", which will generate a pad of N nops at the beginning of each function.

Livepatch on arm64 can using the feature (that always placing one nop at the beginning of the function). And when enable/disable func patching, just modify the pad code to nop or branch. And that NOP and B instruction are both safe instructions on arm64 which called "concurrent modification and execution of instructions", that can be executed by one thread of execution as they are being modified by another thread of execution without requiring explicit synchronization.

And this method will improve performance significantly compared with the method based on ftrace, especially for the critical function being frequently called.

Li Bin (3):
livepatch: allow arch specific implementation
livepatch: module: arm64: extract the relocation code for reuse
livepatch: arm64: add support for livepatch on arm64

 Makefile                           |    7 +-
 arch/arm64/Kconfig                 |    4 +
 arch/arm64/include/asm/livepatch.h |   45 +++++
 arch/arm64/include/asm/module.h    |    3 +
 arch/arm64/kernel/Makefile         |    1 +
 arch/arm64/kernel/livepatch.c      |  123 ++++++++++++
 arch/arm64/kernel/module.c         |  360 ++++++++++++++++++------------------
 kernel/livepatch/Kconfig           |   10 +-
 kernel/livepatch/core.c            |   45 +++--
 9 files changed, 403 insertions(+), 195 deletions(-)
 create mode 100644 arch/arm64/include/asm/livepatch.h
 create mode 100644 arch/arm64/kernel/livepatch.c
author
#1 lore raw

RFC PATCH 1/3 livepatch: allow arch specific implementation

Allow klp_enable_func/klp_disable_func be specific implementation for different arch.

---
 kernel/livepatch/core.c |   45 +++++++++++++++++++++++++++++----------------
 1 files changed, 29 insertions(+), 16 deletions(-)
kernel/livepatch/core.c +29 -16
index db545cb..0a8cf96 100644
@@ -350,15 +350,10 @@ unlock:
 	rcu_read_unlock();
 }
 
-static void klp_disable_func(struct klp_func *func)
+void __weak arch_klp_disable_func(struct klp_func *func)
 {
 	struct klp_ops *ops;
 
-	if (WARN_ON(func->state != KLP_ENABLED))
-		return;
-	if (WARN_ON(!func->old_addr))
-		return;
-
 	ops = klp_find_ops(func->old_addr);
 	if (WARN_ON(!ops))
 		return;
@@ -373,21 +368,23 @@ static void klp_disable_func(struct klp_func *func)
 	} else {
 		list_del_rcu(&func->stack_node);
 	}
+}
+static void klp_disable_func(struct klp_func *func)
+{
+	if (WARN_ON(func->state != KLP_ENABLED))
+		return;
+	if (WARN_ON(!func->old_addr))
+		return;
 
+	arch_klp_disable_func(func);
 	func->state = KLP_DISABLED;
 }
 
-static int klp_enable_func(struct klp_func *func)
+int __weak arch_klp_enable_func(struct klp_func *func)
 {
 	struct klp_ops *ops;
 	int ret;
 
-	if (WARN_ON(!func->old_addr))
-		return -EINVAL;
-
-	if (WARN_ON(func->state != KLP_DISABLED))
-		return -EINVAL;
-
 	ops = klp_find_ops(func->old_addr);
 	if (!ops) {
 		ops = kzalloc(sizeof(*ops), GFP_KERNEL);
@@ -424,10 +421,7 @@ static int klp_enable_func(struct klp_func *func)
 		list_add_rcu(&func->stack_node, &ops->func_stack);
 	}
 
-	func->state = KLP_ENABLED;
-
 	return 0;
-
 err:
 	list_del_rcu(&func->stack_node);
 	list_del(&ops->node);
@@ -435,6 +429,25 @@ err:
 	return ret;
 }
 
+static int klp_enable_func(struct klp_func *func)
+{
+	int ret;
+
+	if (WARN_ON(!func->old_addr))
+		return -EINVAL;
+
+	if (WARN_ON(func->state != KLP_DISABLED))
+		return -EINVAL;
+
+	ret = arch_klp_enable_func(func);
+	if (ret)
+		return ret;
+
+	func->state = KLP_ENABLED;
+
+	return 0;
+}
+
 static void klp_disable_object(struct klp_object *obj)
 {
 	struct klp_func *func;
Signature · 1 line
1.7.1
author
#2 lore raw

RFC PATCH 2/3 livepatch: module: arm64: extract the relocation code for reuse

In reply to Li Bin

Livepatch can reuse the relocation codes of module loader, this patch extract it.

---
 arch/arm64/include/asm/module.h |    3 +
 arch/arm64/kernel/module.c      |  360 ++++++++++++++++++++-------------------
 2 files changed, 187 insertions(+), 176 deletions(-)
arch/arm64/include/asm/module.h +3 -0
index e80e232..78ac36e 100644
@@ -20,4 +20,7 @@
 
 #define MODULE_ARCH_VERMAGIC	"aarch64"
 
+extern int static_relocate(struct module *mod, unsigned long type,
+			   void * loc, unsigned long value);
+
 #endif /* __ASM_MODULE_H */
arch/arm64/kernel/module.c +184 -176

386 lines not on this page · raw message

Signature · 1 line
1.7.1
#3 lore raw

RFC PATCH 2/3 livepatch: module: arm64: extract the relocation code for reuse

Quoted 4 lines · Li Bin

Livepatch can reuse the relocation codes of module loader, this patch extract it.

FYI, this patch may be obsoleted by Jessica Yu's patches which are still under discussion:

[RFC PATCH v2 0/6] (mostly) Arch-independent livepatch

Quoted 413 lines
---
 arch/arm64/include/asm/module.h |    3 +
 arch/arm64/kernel/module.c      |  360 ++++++++++++++++++++-------------------
 2 files changed, 187 insertions(+), 176 deletions(-)
arch/arm64/include/asm/module.h +3 -0
index e80e232..78ac36e 100644
@@ -20,4 +20,7 @@
 
 #define MODULE_ARCH_VERMAGIC	"aarch64"
 
+extern int static_relocate(struct module *mod, unsigned long type,
+			   void * loc, unsigned long value);
+
 #endif /* __ASM_MODULE_H */
arch/arm64/kernel/module.c +184 -176
index f4bc779..6d1a1e3 100644
@@ -203,6 +203,184 @@ static int reloc_insn_imm(enum aarch64_reloc_op op, void *place, u64 val,
 	return 0;
 }
 
+int static_relocate(struct module *me, unsigned long type, void *loc,
+		    unsigned long val)
+{
+	int ovf = 0;
+	bool overflow_check = true;
+	/* Perform the static relocation. */
+	switch (type) {
+	/* Null relocations. */
+	case R_ARM_NONE:
+	case R_AARCH64_NONE:
+		ovf = 0;
+		break;
+
+		/* Data relocations. */
+	case R_AARCH64_ABS64:
+		overflow_check = false;
+		ovf = reloc_data(RELOC_OP_ABS, loc, val, 64);
+		break;
+	case R_AARCH64_ABS32:
+		ovf = reloc_data(RELOC_OP_ABS, loc, val, 32);
+		break;
+	case R_AARCH64_ABS16:
+		ovf = reloc_data(RELOC_OP_ABS, loc, val, 16);
+		break;
+	case R_AARCH64_PREL64:
+		overflow_check = false;
+		ovf = reloc_data(RELOC_OP_PREL, loc, val, 64);
+		break;
+	case R_AARCH64_PREL32:
+		ovf = reloc_data(RELOC_OP_PREL, loc, val, 32);
+		break;
+	case R_AARCH64_PREL16:
+		ovf = reloc_data(RELOC_OP_PREL, loc, val, 16);
+		break;
+
+		/* MOVW instruction relocations. */
+	case R_AARCH64_MOVW_UABS_G0_NC:
+		overflow_check = false;
+	case R_AARCH64_MOVW_UABS_G0:
+		ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 0,
+				      AARCH64_INSN_IMM_16);
+		break;
+	case R_AARCH64_MOVW_UABS_G1_NC:
+		overflow_check = false;
+	case R_AARCH64_MOVW_UABS_G1:
+		ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 16,
+				      AARCH64_INSN_IMM_16);
+		break;
+	case R_AARCH64_MOVW_UABS_G2_NC:
+		overflow_check = false;
+	case R_AARCH64_MOVW_UABS_G2:
+		ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 32,
+				      AARCH64_INSN_IMM_16);
+		break;
+	case R_AARCH64_MOVW_UABS_G3:
+		/* We're using the top bits so we can't overflow. */
+		overflow_check = false;
+		ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 48,
+				      AARCH64_INSN_IMM_16);
+		break;
+	case R_AARCH64_MOVW_SABS_G0:
+		ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 0,
+				      AARCH64_INSN_IMM_MOVNZ);
+		break;
+	case R_AARCH64_MOVW_SABS_G1:
+		ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 16,
+				      AARCH64_INSN_IMM_MOVNZ);
+		break;
+	case R_AARCH64_MOVW_SABS_G2:
+		ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 32,
+				      AARCH64_INSN_IMM_MOVNZ);
+		break;
+	case R_AARCH64_MOVW_PREL_G0_NC:
+		overflow_check = false;
+		ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 0,
+				      AARCH64_INSN_IMM_MOVK);
+		break;
+	case R_AARCH64_MOVW_PREL_G0:
+		ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 0,
+				      AARCH64_INSN_IMM_MOVNZ);
+		break;
+	case R_AARCH64_MOVW_PREL_G1_NC:
+		overflow_check = false;
+		ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 16,
+				      AARCH64_INSN_IMM_MOVK);
+		break;
+	case R_AARCH64_MOVW_PREL_G1:
+		ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 16,
+				      AARCH64_INSN_IMM_MOVNZ);
+		break;
+	case R_AARCH64_MOVW_PREL_G2_NC:
+		overflow_check = false;
+		ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 32,
+				      AARCH64_INSN_IMM_MOVK);
+		break;
+	case R_AARCH64_MOVW_PREL_G2:
+		ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 32,
+				      AARCH64_INSN_IMM_MOVNZ);
+		break;
+	case R_AARCH64_MOVW_PREL_G3:
+		/* We're using the top bits so we can't overflow. */
+		overflow_check = false;
+		ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 48,
+				      AARCH64_INSN_IMM_MOVNZ);
+		break;
+
+		/* Immediate instruction relocations. */
+	case R_AARCH64_LD_PREL_LO19:
+		ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 19,
+				     AARCH64_INSN_IMM_19);
+		break;
+	case R_AARCH64_ADR_PREL_LO21:
+		ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 0, 21,
+				     AARCH64_INSN_IMM_ADR);
+		break;
+#ifndef CONFIG_ARM64_ERRATUM_843419
+	case R_AARCH64_ADR_PREL_PG_HI21_NC:
+		overflow_check = false;
+	case R_AARCH64_ADR_PREL_PG_HI21:
+		ovf = reloc_insn_imm(RELOC_OP_PAGE, loc, val, 12, 21,
+				     AARCH64_INSN_IMM_ADR);
+		break;
+#endif
+	case R_AARCH64_ADD_ABS_LO12_NC:
+	case R_AARCH64_LDST8_ABS_LO12_NC:
+		overflow_check = false;
+		ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 0, 12,
+				     AARCH64_INSN_IMM_12);
+		break;
+	case R_AARCH64_LDST16_ABS_LO12_NC:
+		overflow_check = false;
+		ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 1, 11,
+				     AARCH64_INSN_IMM_12);
+		break;
+	case R_AARCH64_LDST32_ABS_LO12_NC:
+		overflow_check = false;
+		ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 2, 10,
+				     AARCH64_INSN_IMM_12);
+		break;
+	case R_AARCH64_LDST64_ABS_LO12_NC:
+		overflow_check = false;
+		ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 3, 9,
+				     AARCH64_INSN_IMM_12);
+		break;
+	case R_AARCH64_LDST128_ABS_LO12_NC:
+		overflow_check = false;
+		ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 4, 8,
+				     AARCH64_INSN_IMM_12);
+		break;
+	case R_AARCH64_TSTBR14:
+		ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 14,
+				     AARCH64_INSN_IMM_14);
+		break;
+	case R_AARCH64_CONDBR19:
+		ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 19,
+				     AARCH64_INSN_IMM_19);
+		break;
+	case R_AARCH64_JUMP26:
+	case R_AARCH64_CALL26:
+		ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 26,
+				     AARCH64_INSN_IMM_26);
+		break;
+
+	default:
+		pr_err("module %s: unsupported RELA relocation: %lu\n",
+			me->name, type);
+		return -ENOEXEC;
+	}
+
+	if (overflow_check && ovf == -ERANGE) {
+		pr_err("module %s: overflow in relocation type %lu val %lx\n",
+			me->name, type, val);
+		return -ENOEXEC;
+	}
+
+	return 0;
+}
+
 int apply_relocate_add(Elf64_Shdr *sechdrs,
 		       const char *strtab,
 		       unsigned int symindex,
@@ -210,12 +388,11 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
 		       struct module *me)
 {
 	unsigned int i;
-	int ovf;
-	bool overflow_check;
 	Elf64_Sym *sym;
 	void *loc;
 	u64 val;
 	Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;
+	int type, ret;
 
 	for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
 		/* loc corresponds to P in the AArch64 ELF document. */
@@ -229,184 +406,15 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
 		/* val corresponds to (S + A) in the AArch64 ELF document. */
 		val = sym->st_value + rel[i].r_addend;
 
-		/* Check for overflow by default. */
-		overflow_check = true;
-
-		/* Perform the static relocation. */
-		switch (ELF64_R_TYPE(rel[i].r_info)) {
-		/* Null relocations. */
-		case R_ARM_NONE:
-		case R_AARCH64_NONE:
-			ovf = 0;
-			break;
-
-		/* Data relocations. */
-		case R_AARCH64_ABS64:
-			overflow_check = false;
-			ovf = reloc_data(RELOC_OP_ABS, loc, val, 64);
-			break;
-		case R_AARCH64_ABS32:
-			ovf = reloc_data(RELOC_OP_ABS, loc, val, 32);
-			break;
-		case R_AARCH64_ABS16:
-			ovf = reloc_data(RELOC_OP_ABS, loc, val, 16);
-			break;
-		case R_AARCH64_PREL64:
-			overflow_check = false;
-			ovf = reloc_data(RELOC_OP_PREL, loc, val, 64);
-			break;
-		case R_AARCH64_PREL32:
-			ovf = reloc_data(RELOC_OP_PREL, loc, val, 32);
-			break;
-		case R_AARCH64_PREL16:
-			ovf = reloc_data(RELOC_OP_PREL, loc, val, 16);
-			break;
-
-		/* MOVW instruction relocations. */
-		case R_AARCH64_MOVW_UABS_G0_NC:
-			overflow_check = false;
-		case R_AARCH64_MOVW_UABS_G0:
-			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 0,
-					      AARCH64_INSN_IMM_16);
-			break;
-		case R_AARCH64_MOVW_UABS_G1_NC:
-			overflow_check = false;
-		case R_AARCH64_MOVW_UABS_G1:
-			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 16,
-					      AARCH64_INSN_IMM_16);
-			break;
-		case R_AARCH64_MOVW_UABS_G2_NC:
-			overflow_check = false;
-		case R_AARCH64_MOVW_UABS_G2:
-			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 32,
-					      AARCH64_INSN_IMM_16);
-			break;
-		case R_AARCH64_MOVW_UABS_G3:
-			/* We're using the top bits so we can't overflow. */
-			overflow_check = false;
-			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 48,
-					      AARCH64_INSN_IMM_16);
-			break;
-		case R_AARCH64_MOVW_SABS_G0:
-			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 0,
-					      AARCH64_INSN_IMM_MOVNZ);
-			break;
-		case R_AARCH64_MOVW_SABS_G1:
-			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 16,
-					      AARCH64_INSN_IMM_MOVNZ);
-			break;
-		case R_AARCH64_MOVW_SABS_G2:
-			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 32,
-					      AARCH64_INSN_IMM_MOVNZ);
-			break;
-		case R_AARCH64_MOVW_PREL_G0_NC:
-			overflow_check = false;
-			ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 0,
-					      AARCH64_INSN_IMM_MOVK);
-			break;
-		case R_AARCH64_MOVW_PREL_G0:
-			ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 0,
-					      AARCH64_INSN_IMM_MOVNZ);
-			break;
-		case R_AARCH64_MOVW_PREL_G1_NC:
-			overflow_check = false;
-			ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 16,
-					      AARCH64_INSN_IMM_MOVK);
-			break;
-		case R_AARCH64_MOVW_PREL_G1:
-			ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 16,
-					      AARCH64_INSN_IMM_MOVNZ);
-			break;
-		case R_AARCH64_MOVW_PREL_G2_NC:
-			overflow_check = false;
-			ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 32,
-					      AARCH64_INSN_IMM_MOVK);
-			break;
-		case R_AARCH64_MOVW_PREL_G2:
-			ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 32,
-					      AARCH64_INSN_IMM_MOVNZ);
-			break;
-		case R_AARCH64_MOVW_PREL_G3:
-			/* We're using the top bits so we can't overflow. */
-			overflow_check = false;
-			ovf = reloc_insn_movw(RELOC_OP_PREL, loc, val, 48,
-					      AARCH64_INSN_IMM_MOVNZ);
-			break;
-
-		/* Immediate instruction relocations. */
-		case R_AARCH64_LD_PREL_LO19:
-			ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 19,
-					     AARCH64_INSN_IMM_19);
-			break;
-		case R_AARCH64_ADR_PREL_LO21:
-			ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 0, 21,
-					     AARCH64_INSN_IMM_ADR);
-			break;
-#ifndef CONFIG_ARM64_ERRATUM_843419
-		case R_AARCH64_ADR_PREL_PG_HI21_NC:
-			overflow_check = false;
-		case R_AARCH64_ADR_PREL_PG_HI21:
-			ovf = reloc_insn_imm(RELOC_OP_PAGE, loc, val, 12, 21,
-					     AARCH64_INSN_IMM_ADR);
-			break;
-#endif
-		case R_AARCH64_ADD_ABS_LO12_NC:
-		case R_AARCH64_LDST8_ABS_LO12_NC:
-			overflow_check = false;
-			ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 0, 12,
-					     AARCH64_INSN_IMM_12);
-			break;
-		case R_AARCH64_LDST16_ABS_LO12_NC:
-			overflow_check = false;
-			ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 1, 11,
-					     AARCH64_INSN_IMM_12);
-			break;
-		case R_AARCH64_LDST32_ABS_LO12_NC:
-			overflow_check = false;
-			ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 2, 10,
-					     AARCH64_INSN_IMM_12);
-			break;
-		case R_AARCH64_LDST64_ABS_LO12_NC:
-			overflow_check = false;
-			ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 3, 9,
-					     AARCH64_INSN_IMM_12);
-			break;
-		case R_AARCH64_LDST128_ABS_LO12_NC:
-			overflow_check = false;
-			ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 4, 8,
-					     AARCH64_INSN_IMM_12);
-			break;
-		case R_AARCH64_TSTBR14:
-			ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 14,
-					     AARCH64_INSN_IMM_14);
-			break;
-		case R_AARCH64_CONDBR19:
-			ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 19,
-					     AARCH64_INSN_IMM_19);
-			break;
-		case R_AARCH64_JUMP26:
-		case R_AARCH64_CALL26:
-			ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 26,
-					     AARCH64_INSN_IMM_26);
-			break;
-
-		default:
-			pr_err("module %s: unsupported RELA relocation: %llu\n",
-			       me->name, ELF64_R_TYPE(rel[i].r_info));
-			return -ENOEXEC;
-		}
-
-		if (overflow_check && ovf == -ERANGE)
-			goto overflow;
+		type = ELF64_R_TYPE(rel[i].r_info);
 
+		/* Check for overflow by default. */
+		ret = static_relocate(me, type, loc, val);
+		if (ret)
+			return ret;
 	}
 
 	return 0;
-
-overflow:
-	pr_err("module %s: overflow in relocation type %d val %Lx\n",
-	       me->name, (int)ELF64_R_TYPE(rel[i].r_info), val);
-	return -ENOEXEC;
 }
 
 int module_finalize(const Elf_Ehdr *hdr,
Signature · 1 line
1.7.1
Signature · 1 line
Josh
author
#4 lore raw

RFC PATCH 3/3 livepatch: arm64: add support for livepatch on arm64

In reply to Li Bin

This patch add support for livepach on arm64 based on the gcc -fprolog-pad feature (that always placing one nop at the beginning of the function). And when enable/disable func patching, just modify the pad code to nop or branch. And that NOP and B instruction are both safe instructions on arm64 which called "concurrent modification and execution of instructions", that can be executed by one thread of execution as they are being modified by another thread of execution without requiring explicit synchronization.

---
 Makefile                           |    7 ++-
 arch/arm64/Kconfig                 |    4 +
 arch/arm64/include/asm/livepatch.h |   45 +++++++++++++
 arch/arm64/kernel/Makefile         |    1 +
 arch/arm64/kernel/livepatch.c      |  123 ++++++++++++++++++++++++++++++++++++
 kernel/livepatch/Kconfig           |   10 +++-
 6 files changed, 187 insertions(+), 3 deletions(-)
 create mode 100644 arch/arm64/include/asm/livepatch.h
 create mode 100644 arch/arm64/kernel/livepatch.c
Makefile +5 -2
index d644f6e..6171fb8 100644
@@ -727,8 +727,11 @@ export CC_FLAGS_FTRACE
 ifdef CONFIG_HAVE_FENTRY
 CC_USING_FENTRY	:= $(call cc-option, -mfentry -DCC_USING_FENTRY)
 endif
-KBUILD_CFLAGS	+= $(CC_FLAGS_FTRACE) $(CC_USING_FENTRY)
-KBUILD_AFLAGS	+= $(CC_USING_FENTRY)
+ifdef CONFIG_HAVE_PROLOG_PAD
+CC_USING_PROLOG_PAD := $(call cc-option, -fprolog-pad=1 -DCC_USING_PROLOG_PAD)
+endif
+KBUILD_CFLAGS  += $(CC_FLAGS_FTRACE) $(CC_USING_FENTRY) $(CC_USING_PROLOG_PAD)
+KBUILD_AFLAGS  += $(CC_USING_FENTRY) $(CC_USING_PROLOG_PAD)
 ifdef CONFIG_DYNAMIC_FTRACE
 	ifdef CONFIG_HAVE_C_RECORDMCOUNT
 		BUILD_C_RECORDMCOUNT := y
arch/arm64/Kconfig +4 -0
index 871f217..85e01b1 100644
@@ -66,6 +66,7 @@ config ARM64
 	select HAVE_DYNAMIC_FTRACE
 	select HAVE_EFFICIENT_UNALIGNED_ACCESS
 	select HAVE_FTRACE_MCOUNT_RECORD
+	select HAVE_PROLOG_PAD
 	select HAVE_FUNCTION_TRACER
 	select HAVE_FUNCTION_GRAPH_TRACER
 	select HAVE_GENERIC_DMA_COHERENT
@@ -92,6 +93,7 @@ config ARM64
 	select SPARSE_IRQ
 	select SYSCTL_EXCEPTION_TRACE
 	select HAVE_CONTEXT_TRACKING
+	select HAVE_LIVEPATCH
 	help
 	  ARM 64-bit (AArch64) Linux support.
 
@@ -183,6 +185,8 @@ source "init/Kconfig"
 
 source "kernel/Kconfig.freezer"
 
+source "kernel/livepatch/Kconfig"
+
 source "arch/arm64/Kconfig.platforms"
 
 menu "Bus support"
arch/arm64/include/asm/livepatch.h new file +45 -0

45 lines not on this page · raw message

arch/arm64/kernel/Makefile +1 -0
index 474691f..59a415d 100644
@@ -29,6 +29,7 @@ arm64-obj-$(CONFIG_COMPAT)		+= sys32.o kuser32.o signal32.o 	\
 					   sys_compat.o entry32.o		\
 					   ../../arm/kernel/opcodes.o
 arm64-obj-$(CONFIG_FUNCTION_TRACER)	+= ftrace.o entry-ftrace.o
+arm64-obj-$(CONFIG_LIVEPATCH) += livepatch.o
 arm64-obj-$(CONFIG_MODULES)		+= arm64ksyms.o module.o
 arm64-obj-$(CONFIG_PERF_EVENTS)		+= perf_regs.o perf_callchain.o
 arm64-obj-$(CONFIG_HW_PERF_EVENTS)	+= perf_event.o
arch/arm64/kernel/livepatch.c new file +123 -0

123 lines not on this page · raw message

kernel/livepatch/Kconfig +9 -1
index 0450225..287bb79 100644
@@ -1,3 +1,11 @@
+config HAVE_PROLOG_PAD
+	bool
+
+config PROLOG_PAD
+	bool
+	depends on HAVE_PROLOG_PAD
+	default y
+
 config HAVE_LIVEPATCH
 	bool
 	help
@@ -5,7 +13,7 @@ config HAVE_LIVEPATCH
 
 config LIVEPATCH
 	bool "Kernel Live Patching"
-	depends on DYNAMIC_FTRACE_WITH_REGS
+	depends on DYNAMIC_FTRACE_WITH_REGS || PROLOG_PAD
 	depends on MODULES
 	depends on SYSFS
 	depends on KALLSYMS_ALL
Signature · 1 line
1.7.1
#5 lore raw

cover livepatch: arm64: add support for livepatch on arm64

In reply to Li Bin
Quoted 11 lines · Li Bin

This patchset depends on the on-going gcc feature "-fprolog-pad=N", which will generate a pad of N nops at the beginning of each function.

Livepatch on arm64 can using the feature (that always placing one nop at the beginning of the function). And when enable/disable func patching, just modify the pad code to nop or branch. And that NOP and B instruction are both safe instructions on arm64 which called "concurrent modification and execution of instructions", that can be executed by one thread of execution as they are being modified by another thread of execution without requiring explicit synchronization.

Quoted 3 lines

And this method will improve performance significantly compared with the method based on ftrace, especially for the critical function being frequently called.

I think the arch-specific code in this patch set would be greatly affected by the consistency model when it finally arrives (but that's currently gated by getting stacktool merged, which should be soon).

So I think it would be a good idea to wait until then before doing something like this.

Is it possible to use ftrace instead? That would be a much better short term solution.

Signature · 1 line
Josh
maintainer
#6 lore raw

cover livepatch: arm64: add support for livepatch on arm64

In reply to Li Bin
Quoted 11 lines · Li Bin

This patchset depends on the on-going gcc feature "-fprolog-pad=N", which will generate a pad of N nops at the beginning of each function.

Livepatch on arm64 can using the feature (that always placing one nop at the beginning of the function). And when enable/disable func patching, just modify the pad code to nop or branch. And that NOP and B instruction are both safe instructions on arm64 which called "concurrent modification and execution of instructions", that can be executed by one thread of execution as they are being modified by another thread of execution without requiring explicit synchronization.

Quoted 3 lines

And this method will improve performance significantly compared with the method based on ftrace, especially for the critical function being frequently called.

It sounds like a great feature for ftrace. If the new prologue is usable for LivePatching, it should be usable to call the ftrace handler as well. If you teach ftrace to use the new prologue, you will not need all these crazy arch-specific hacks for LivePatching. Then both ftrace and livepatch will benefit from the feature.

I suggest to read the ftrace documentation in Documentation/trace/, especially ftrace-design.txt. I have never ported ftrace to a new architecture. I guess that you need to teach scripts/recordmcount.c
to find the new location. Also you might need to update
arch/arm/kernel/ftrace.c. Also please make sure that ftrace supports DYNAMIC_FTRACE_WITH_REGS on Arm.

It is harder but the right way to go. Your current patch set looks like a hack.

Best Regards,
Petr

author
#7 lore raw

cover livepatch: arm64: add support for livepatch on arm64

Quoted 21 lines · Petr Mladek
Quoted 11 lines · Li Bin

This patchset depends on the on-going gcc feature "-fprolog-pad=N", which will generate a pad of N nops at the beginning of each function.

Livepatch on arm64 can using the feature (that always placing one nop at the beginning of the function). And when enable/disable func patching, just modify the pad code to nop or branch. And that NOP and B instruction are both safe instructions on arm64 which called "concurrent modification and execution of instructions", that can be executed by one thread of execution as they are being modified by another thread of execution without requiring explicit synchronization.

Quoted 3 lines

And this method will improve performance significantly compared with the method based on ftrace, especially for the critical function being frequently called.

It sounds like a great feature for ftrace. If the new prologue is usable for LivePatching, it should be usable to call the ftrace handler as well. If you teach ftrace to use the new prologue, you will not need all these crazy arch-specific hacks for LivePatching. Then both ftrace and livepatch will benefit from the feature.

Quoted 6 lines

I suggest to read the ftrace documentation in Documentation/trace/, especially ftrace-design.txt. I have never ported ftrace to a new architecture. I guess that you need to teach scripts/recordmcount.c
to find the new location. Also you might need to update
arch/arm/kernel/ftrace.c. Also please make sure that ftrace supports DYNAMIC_FTRACE_WITH_REGS on Arm.

Hi Petr,
I had posted one method for livepatch implementaion on arm64 based on ftrace with DYNAMIC_FTRACE_WITH_REGS.
https://lwn.net/Articles/646317/

This requires GCC changes to support it, and I propose a method that implement -mfentry feature which following some other arch such as x86/mips/s390 etc. This method is architecture-specific, and Maxim Kuvyrkov propose a new method for gcc that implement a target-independent option -fprolog-pad=N, which will generate a pad of N nops at the beginning of each function.
https://gcc.gnu.org/ml/gcc/2015-05/msg00267.html

And based on this, DYNAMIC_FTRACE_WITH_REGS feature can be implemented as mentioned in:
http://lists.infradead.org/pipermail/linux-arm-kernel/2015-November/386815.html

And in this patchset, I only concern the performance for livepatch on arm64, and I hope this also can be resolved by improving ftrace, and thanks to Steve that he pointed that he is working on a way to make ftrace a bit better for livepatch.

Thanks,
Li Bin

Quoted 8 lines

It is harder but the right way to go. Your current patch set looks like a hack.

Best Regards,
Petr

.

maintainer
#8 lore raw

cover livepatch: arm64: add support for livepatch on arm64

Quoted 31 lines · Li Bin
Quoted 21 lines · Petr Mladek
Quoted 11 lines · Li Bin

This patchset depends on the on-going gcc feature "-fprolog-pad=N", which will generate a pad of N nops at the beginning of each function.

Livepatch on arm64 can using the feature (that always placing one nop at the beginning of the function). And when enable/disable func patching, just modify the pad code to nop or branch. And that NOP and B instruction are both safe instructions on arm64 which called "concurrent modification and execution of instructions", that can be executed by one thread of execution as they are being modified by another thread of execution without requiring explicit synchronization.

Quoted 3 lines

And this method will improve performance significantly compared with the method based on ftrace, especially for the critical function being frequently called.

It sounds like a great feature for ftrace. If the new prologue is usable for LivePatching, it should be usable to call the ftrace handler as well. If you teach ftrace to use the new prologue, you will not need all these crazy arch-specific hacks for LivePatching. Then both ftrace and livepatch will benefit from the feature.

Quoted 6 lines

I suggest to read the ftrace documentation in Documentation/trace/, especially ftrace-design.txt. I have never ported ftrace to a new architecture. I guess that you need to teach scripts/recordmcount.c
to find the new location. Also you might need to update
arch/arm/kernel/ftrace.c. Also please make sure that ftrace supports DYNAMIC_FTRACE_WITH_REGS on Arm.

Quoted 4 lines

Hi Petr,
I had posted one method for livepatch implementaion on arm64 based on ftrace with DYNAMIC_FTRACE_WITH_REGS.
https://lwn.net/Articles/646317/

I see. What are the plans with this patchset, please?

Quoted 10 lines

This requires GCC changes to support it, and I propose a method that implement -mfentry feature which following some other arch such as x86/mips/s390 etc. This method is architecture-specific, and Maxim Kuvyrkov propose a new method for gcc that implement a target-independent option -fprolog-pad=N, which will generate a pad of N nops at the beginning of each function.
https://gcc.gnu.org/ml/gcc/2015-05/msg00267.html

And based on this, DYNAMIC_FTRACE_WITH_REGS feature can be implemented as mentioned in:
http://lists.infradead.org/pipermail/linux-arm-kernel/2015-November/386815.html

What will happen if DYNAMIC_FTRACE_WITH_REGS is implemented using -fprolog-pad=N, please? Will ftrace and livepatch try to modify the same location? Or do you plan to revert all these changes then?

Quoted 3 lines

And in this patchset, I only concern the performance for livepatch on arm64, and I hope this also can be resolved by improving ftrace, and thanks to Steve that he pointed that he is working on a way to make ftrace a bit better for livepatch.

What is the exact performance optimization? Is it that you call the new functiion (from the patch) dirrectly instead of using the generic ftrace handler? This will break once ftrace start using this location. Also this will get more problematic once we have the consistency mode as pointed out by Josh.

Please, note that there are currently three features that need to modify the function entry code: Ftrace, Kprobes, and LivePatch. They need to be coordinated. This is why also Kprobes are called via Ftrace when available.

Your patch set looks like a temporary hack to me. I suggest you to concentrace on implemention DYNAMIC_FTRACE_WITH_REGS on Arm. If you are not happy with the ftrace performance, you might work on improving it. All three features: Ftrace, Kprobes, and LivePatch will benefit from it. Modyfying the code directly from LivePatch looks like a way to a maintenance hell.

Best Regards,
Petr