From c6b399359956bf798f685f5b3aaf47fcf02de523 Mon Sep 17 00:00:00 2001 From: laokz Date: Fri, 20 Oct 2023 20:53:32 +0800 Subject: [PATCH] add initial riscv64 support Per the RISC-V psABI, PCREL_HI20 and PCREL_LO12 must sit in the same section. PCREL_HI20 may be a global symbol and might be moved to .klp.rela section, so thouth PCREL_LO12 is a local symbol ".L0 " it must also be moved to .klp.rela section. This break the current livepatch specification, but it is inevitable. Only support openEuler LIVEPATCH_WO_FTRACE mechanism. Signed-off-by: wangjunqiang Signed-off-by: laokz --- kpatch-build/Makefile | 2 +- kpatch-build/create-diff-object.c | 40 +++++++++++++++++++++++++++--- kpatch-build/create-klp-module.c | 41 +++++++++++++++++++++++++++++++ kpatch-build/kpatch-elf.c | 26 ++++++++++++++++++++ kpatch-build/kpatch-elf.h | 1 + 5 files changed, 105 insertions(+), 5 deletions(-) diff --git a/kpatch-build/Makefile b/kpatch-build/Makefile index 5037677..f24165b 100644 --- a/kpatch-build/Makefile +++ b/kpatch-build/Makefile @@ -22,7 +22,7 @@ PLUGIN_CFLAGS := $(filter-out -Wconversion, $(CFLAGS)) PLUGIN_CFLAGS += -shared -I$(GCC_PLUGINS_DIR)/include \ -Igcc-plugins -fPIC -fno-rtti -O2 -Wall endif -ifeq ($(filter $(ARCH),s390x x86_64 ppc64le aarch64),) +ifeq ($(filter $(ARCH),s390x x86_64 ppc64le aarch64 riscv64),) $(error Unsupported architecture ${ARCH}, check https://github.com/dynup/kpatch/#supported-architectures) endif diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index 0e91513..cca2781 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -177,6 +177,7 @@ static bool is_gcc6_localentry_bundled_sym(struct kpatch_elf *kelf, case S390: return false; case ARM64: + case RISCV64: return false; default: ERROR("unsupported arch"); @@ -444,6 +445,18 @@ static int kpatch_mangled_strcmp(char *s1, char *s2) if (!strncmp(s1, "__UNIQUE_ID_", 12)) return __kpatch_unique_id_strcmp(s1, s2); +#ifdef __riscv + /* + * Normally, .L local symbols are not saved in object files. But on RISC-V it IS! + * Their names are volatile even with minor changes in other place. So we can + * not compare their names reliably. If we must do comparison, just think them + * all have the same name. + * In fact, we neither correlate them nor give them NEW status. + */ + if (!strncmp(s1, ".L", 2) && !strncmp(s2, ".L", 2)) + return 0; +#endif + while (*s1 == *s2) { if (!*s1) return 0; @@ -703,6 +716,10 @@ static bool insn_is_load_immediate(struct kpatch_elf *kelf, void *addr) break; + case RISCV64: + /* Not implemented and I think it has less meaning. */ + break; + default: ERROR("unsupported arch"); } @@ -970,6 +987,10 @@ static void kpatch_compare_symbols(struct list_head *symlist) if (sym->twin) kpatch_compare_correlated_symbol(sym); else +#ifdef __riscv + /* Don't set NEW status for .L local symbols. */ + if (strncmp(sym->name, ".L", 2)) +#endif sym->status = NEW; log_debug("symbol %s is %s\n", sym->name, status_str(sym->status)); @@ -1112,6 +1133,11 @@ static void kpatch_correlate_symbols(struct list_head *symlist_orig, list_for_each_entry(sym_orig, symlist_orig, list) { if (sym_orig->twin) continue; +#ifdef __riscv + /* Don't correlate .L local symbols. */ + if (!strncmp(sym_orig->name, ".L", 2)) + continue; +#endif list_for_each_entry(sym_patched, symlist_patched, list) { if (kpatch_mangled_strcmp(sym_orig->name, sym_patched->name) || sym_orig->type != sym_patched->type || sym_patched->twin) @@ -2327,7 +2353,7 @@ static int fixup_group_size(struct kpatch_elf *kelf, int offset) static struct special_section special_sections[] = { { .name = "__bug_table", - .arch = X86_64 | PPC64 | S390 | ARM64, + .arch = X86_64 | PPC64 | S390 | ARM64 | RISCV64, .group_size = bug_table_group_size, }, { @@ -2337,17 +2363,17 @@ static struct special_section special_sections[] = { }, { .name = "__ex_table", /* must come after .fixup */ - .arch = X86_64 | PPC64 | S390 | ARM64, + .arch = X86_64 | PPC64 | S390 | ARM64 | RISCV64, .group_size = ex_table_group_size, }, { .name = "__jump_table", - .arch = X86_64 | PPC64 | S390 | ARM64, + .arch = X86_64 | PPC64 | S390 | ARM64 | RISCV64, .group_size = jump_table_group_size, }, { .name = ".printk_index", - .arch = X86_64 | PPC64 | S390 | ARM64, + .arch = X86_64 | PPC64 | S390 | ARM64 | RISCV64, .group_size = printk_index_group_size, }, { @@ -2916,6 +2942,10 @@ static void kpatch_mark_ignored_sections(struct kpatch_elf *kelf) strsec->secsym->include = 1; name = strsec->data->d_buf + rela->addend; + /* RISC-V use st_value as offset into string table, addend always 0. */ + if (kelf->arch == RISCV64) + name += rela->sym->sym.st_value; + ignoresec = find_section_by_name(&kelf->sections, name); if (!ignoresec) ERROR("KPATCH_IGNORE_SECTION: can't find %s", name); @@ -3812,6 +3842,7 @@ static void kpatch_create_mcount_sections(struct kpatch_elf *kelf) switch(kelf->arch) { case PPC64: + case RISCV64: case ARM64: { bool found = false; @@ -4048,6 +4079,7 @@ static void kpatch_find_func_profiling_calls(struct kpatch_elf *kelf) switch(kelf->arch) { case PPC64: + case RISCV64: case ARM64: list_for_each_entry(rela, &sym->sec->rela->relas, list) { if (!strcmp(rela->sym->name, "_mcount")) { diff --git a/kpatch-build/create-klp-module.c b/kpatch-build/create-klp-module.c index b77028f..dbf4ad4 100644 --- a/kpatch-build/create-klp-module.c +++ b/kpatch-build/create-klp-module.c @@ -162,6 +162,43 @@ static struct section *find_or_add_klp_relasec(struct kpatch_elf *kelf, return relasec; } +/* + * Per the RISC-V psABI: + * + * In the linker relaxation optimization, we introduce a concept called relocation + * group; a relocation group consists of 1) relocations associated with the same + * target symbol and can be applied with the same relaxation, or 2) relocations + * with the linkage relationship (e.g. `R_RISCV_PCREL_LO12_S` linked with + * a `R_RISCV_PCREL_HI20`); all relocations in a single group must be present in + * the same section, otherwise will split into another relocation group. + * + * I think that PCREL_L012 must always be in the same section with PCREL_HI20 + * in any cases, and noticed that linux kernel does the same assumption. + * + * So here we have to move PCREL_L012 to .klp.rela section along with its PCREL_HI20 + * part. Note it's still a normal relocation entry, not a klp entry, and the kernel + * should recognize it. + */ +static void rv64_mv_lo12_to_klp_rela(struct kpatch_elf *kelf, unsigned short shndx, + unsigned int hi20offset, struct section *klp_relasec) +{ + struct rela *relalo12, *n; + struct section *sec = find_section_by_index(&kelf->sections, shndx); + + if (!sec) + ERROR("rv64_mv_lo12_to_klp_rela"); + + list_for_each_entry_safe(relalo12, n, &sec->rela->relas, list) { + if (hi20offset == relalo12->sym->sym.st_value && + (relalo12->type == R_RISCV_PCREL_LO12_I || + relalo12->type == R_RISCV_PCREL_LO12_S)) { + list_del(&relalo12->list); + list_add_tail(&relalo12->list, &klp_relasec->relas); + return; + } + } +} + /* * Create klp relocation sections and klp symbols from .kpatch.relocations * and .kpatch.symbols sections @@ -240,6 +277,10 @@ static void create_klp_relasecs_and_syms(struct kpatch_elf *kelf, struct section rela->type = krelas[index].type; rela->sym = sym; rela->addend = krelas[index].addend; + + if ((kelf->arch == RISCV64) && + (rela->type == R_RISCV_PCREL_HI20 || rela->type == R_RISCV_GOT_HI20)) + rv64_mv_lo12_to_klp_rela(kelf, dest->sym.st_shndx, dest_off, klp_relasec); } } diff --git a/kpatch-build/kpatch-elf.c b/kpatch-build/kpatch-elf.c index 572f272..40ea6a8 100644 --- a/kpatch-build/kpatch-elf.c +++ b/kpatch-build/kpatch-elf.c @@ -144,6 +144,8 @@ unsigned int absolute_rela_type(struct kpatch_elf *kelf) return R_390_64; case ARM64: return R_AARCH64_ABS64; + case RISCV64: + return R_RISCV_64; default: ERROR("unsupported arch"); } @@ -209,6 +211,7 @@ long rela_target_offset(struct kpatch_elf *kelf, struct section *relasec, switch(kelf->arch) { case PPC64: case ARM64: + case RISCV64: add_off = 0; break; case X86_64: @@ -278,6 +281,11 @@ unsigned int insn_length(struct kpatch_elf *kelf, void *addr) return 6; } + case RISCV64: + if ((insn[0] & 0x3) == 0x3) + return 4; + return 2; + default: ERROR("unsupported arch"); } @@ -508,6 +516,9 @@ struct kpatch_elf *kpatch_elf_open(const char *name) case EM_AARCH64: kelf->arch = ARM64; break; + case EM_RISCV: + kelf->arch = RISCV64; + break; default: ERROR("Unsupported target architecture"); } @@ -902,6 +913,17 @@ void kpatch_rebuild_rela_section_data(struct section *sec) size_t size; list_for_each_entry(rela, &sec->relas, list) +#ifdef __riscv + /* + * R_RISCV_ALIGN is used for LTO and shall be consumed by linker. + * Kernel'll see it as an error if encountered. + * But kpatch-build only does partial linking($LD -r) and later + * strip has no way to remove the relocation entries alone. That + * would cause it been left in the .ko falsely. + * Here we have to discard it. + */ + if(rela->type != R_RISCV_ALIGN) +#endif nr++; size = nr * sizeof(*relas); @@ -916,6 +938,10 @@ void kpatch_rebuild_rela_section_data(struct section *sec) sec->sh.sh_size = size; list_for_each_entry(rela, &sec->relas, list) { +#ifdef __riscv + if(rela->type == R_RISCV_ALIGN) + continue; +#endif relas[index].r_offset = rela->offset; relas[index].r_addend = rela->addend; relas[index].r_info = GELF_R_INFO(rela->sym->index, rela->type); diff --git a/kpatch-build/kpatch-elf.h b/kpatch-build/kpatch-elf.h index d887812..6624b2d 100644 --- a/kpatch-build/kpatch-elf.h +++ b/kpatch-build/kpatch-elf.h @@ -114,6 +114,7 @@ enum architecture { X86_64 = 0x1 << 1, S390 = 0x1 << 2, ARM64 = 0x1 << 3, + RISCV64= 0x1 << 4, }; struct kpatch_elf { -- 2.42.0