Message ID | 20170521010130.13552-1-npiggin@gmail.com (mailing list archive) |
---|---|
State | Changes Requested |
Headers | show |
Hi Nic, Le 21/05/2017 à 03:01, Nicholas Piggin a écrit : > Implement build-time fixup of alternate feature relative addresses for > the out-of-line ("else") patch code. This is done post-link with a new > powerpc build tool that parses relocations and fixup structures, and > adjusts branch instructions. > > This gives us the ability to link patch code anywhere in the kernel, > without branches to targets outside the patch code having to be > reached directly (without a linker stub). This allows patch code to be > moved out from the head section, and avoids build failures with > unresolvable branche. Is it worth keeping this hanging in patchwork ? It seems outdated and doesn't apply. Could this me done with objtool instead ? Christophe > > The downside is increased complexity of the build steps. > > Currently: > > - The patch code must be linked with branch targets reached directly. > This is done by omitting the "x" attribute from the patch code's > section, which suppresses branch stub creation. Instead, a link error > is raised if a branch cannot reach its target directly. > > - The runtime patcher adjusts external branch targets to compensate > for the distance moved (from link to runtime location). > > After this change: > > - The patch code is linked with branch stubs allowed, by using the "x" > section attribute. This allows flexibility in patch code placement. > > - The final link is made with --emit-relocs, which emits relocations > for these branches. > > - After link, a tool fixes patch code external branches to be be a > direct branch to target, relative to their runtime location. Any > branch stubs in the patch code section are ignored. > > - After feature fixup, newer toolchains can strip off the additional > relocations. > > - The runtime patcher can move the patch code into its runtime > location with no branch adjustment. > > - After patching, original patch code can be discarded from runtime > image. > > Signed-off-by: Nicholas Piggin <npiggin@gmail.com> > --- > > I'm reposting this with some fixes required to work with current > kernels (relative exception tables). I think it's probably the right > way to go in the long term, it gives a lot of control over instruction > patching to have a build-time pass. It is more complexity though, so > I don't know whether it would be better to wait until we hit more > problems with the existing system first. > > > arch/powerpc/Makefile | 8 +- > arch/powerpc/Makefile.postlink | 24 +- > arch/powerpc/include/asm/feature-fixups.h | 5 +- > arch/powerpc/kernel/vmlinux.lds.S | 8 +- > arch/powerpc/lib/feature-fixups.c | 19 +- > arch/powerpc/tools/Makefile | 3 + > arch/powerpc/tools/relocs/.gitignore | 1 + > arch/powerpc/tools/relocs/Makefile | 12 + > arch/powerpc/tools/relocs/code-patching.c | 82 ++++ > arch/powerpc/tools/relocs/code-patching.h | 7 + > arch/powerpc/tools/relocs/elf_sections.c | 337 ++++++++++++++ > arch/powerpc/tools/relocs/elf_sections.h | 50 ++ > arch/powerpc/tools/relocs/process_relocs.c | 718 +++++++++++++++++++++++++++++ > 13 files changed, 1253 insertions(+), 21 deletions(-) > create mode 100644 arch/powerpc/tools/Makefile > create mode 100644 arch/powerpc/tools/relocs/.gitignore > create mode 100644 arch/powerpc/tools/relocs/Makefile > create mode 100644 arch/powerpc/tools/relocs/code-patching.c > create mode 100644 arch/powerpc/tools/relocs/code-patching.h > create mode 100644 arch/powerpc/tools/relocs/elf_sections.c > create mode 100644 arch/powerpc/tools/relocs/elf_sections.h > create mode 100644 arch/powerpc/tools/relocs/process_relocs.c > > diff --git a/arch/powerpc/Makefile b/arch/powerpc/Makefile > index edc682f7b462..bc4791aecd03 100644 > --- a/arch/powerpc/Makefile > +++ b/arch/powerpc/Makefile > @@ -104,6 +104,11 @@ LDFLAGS_vmlinux-$(CONFIG_RELOCATABLE) := -pie > LDFLAGS_vmlinux := $(LDFLAGS_vmlinux-y) > LDFLAGS_vmlinux += $(call ld-option,--orphan-handling=warn) > > +# --emit-relocs required for post-link fixup of alternate feature > +# text section relocations. > +LDFLAGS_vmlinux += --emit-relocs > +KBUILD_LDFLAGS_MODULE += --emit-relocs > + > ifeq ($(CONFIG_PPC64),y) > ifeq ($(call cc-option-yn,-mcmodel=medium),y) > # -mcmodel=medium breaks modules because it uses 32bit offsets from > @@ -429,6 +434,7 @@ checkbin: > false ; \ > fi > > +archscripts: scripts_basic > + $(Q)$(MAKE) $(build)=arch/powerpc/tools > > CLEAN_FILES += $(TOUT) > - > diff --git a/arch/powerpc/Makefile.postlink b/arch/powerpc/Makefile.postlink > index 5db43ebbe2df..bf9b180e3530 100644 > --- a/arch/powerpc/Makefile.postlink > +++ b/arch/powerpc/Makefile.postlink > @@ -2,7 +2,9 @@ > # Post-link powerpc pass > # =========================================================================== > # > -# 1. Check that vmlinux relocations look sane > +# 1. Invoke process_relocs to fix feature fixup alternate section branches. > +# 2. Check that vmlinux relocations look sane > +# 3. Strip static relocations (created by --emit-relocs) if binutils >= 2.27 > > PHONY := __archpost > __archpost: > @@ -23,19 +25,35 @@ else > $(CONFIG_SHELL) $(srctree)/arch/powerpc/tools/relocs_check.sh "$(OBJDUMP)" "$@" > endif > > +quiet_cmd_process_alt_ftr_relocs = FTR_ALT $@ > + cmd_process_alt_ftr_relocs = \ > + arch/powerpc/tools/relocs/process_relocs "$@" > + > +ifeq ($(call ld-ifversion, -ge, 227000000, y),y) > +quiet_cmd_strip_relocs = STRIP $@ > + cmd_strip_relocs = \ > + $(OBJCOPY) $(shell for sec in \ > + $$(readelf -St $@ | grep -B1 RELA | grep "\.rela" | \ > + cut -d"]" -f2 | sed "s/[[:space:]]//" | \ > + grep -v "^\.rela\.dyn$$" | sed "s/\.rela//") ; \ > + do echo -n "--remove-relocation=$$sec " ; done) $@ > +endif > + > # `@true` prevents complaint when there is nothing to be done > > vmlinux: FORCE > - @true > + $(call if_changed,process_alt_ftr_relocs) > ifdef CONFIG_PPC64 > $(call cmd,head_check) > endif > ifdef CONFIG_RELOCATABLE > $(call if_changed,relocs_check) > endif > + $(call if_changed,strip_relocs) > > %.ko: FORCE > - @true > + $(call if_changed,process_alt_ftr_relocs) > + $(call if_changed,strip_relocs) > > clean: > rm -f .tmp_symbols.txt > diff --git a/arch/powerpc/include/asm/feature-fixups.h b/arch/powerpc/include/asm/feature-fixups.h > index 2de2319b99e2..9f78f2f62886 100644 > --- a/arch/powerpc/include/asm/feature-fixups.h > +++ b/arch/powerpc/include/asm/feature-fixups.h > @@ -16,6 +16,9 @@ > * useable with the vdso shared library. There is also an assumption > * that values will be negative, that is, the fixup table has to be > * located after the code it fixes up. > + * > + * Please ensure that new section names, modifications to FTR_ENTRY > + * encoding, etc., is handled by arch/powerpc/tools/relocs/ code. > */ > #if defined(CONFIG_PPC64) && !defined(__powerpc64__) > /* 64 bits kernel, 32 bits code (ie. vdso32) */ > @@ -33,7 +36,7 @@ > > #define FTR_SECTION_ELSE_NESTED(label) \ > label##2: \ > - .pushsection __ftr_alt_##label,"a"; \ > + .pushsection __ftr_alt_##label,"ax"; \ > .align 2; \ > label##3: > > diff --git a/arch/powerpc/kernel/vmlinux.lds.S b/arch/powerpc/kernel/vmlinux.lds.S > index 0e78129e033a..d3a4ad960176 100644 > --- a/arch/powerpc/kernel/vmlinux.lds.S > +++ b/arch/powerpc/kernel/vmlinux.lds.S > @@ -87,8 +87,7 @@ SECTIONS > .text : AT(ADDR(.text) - LOAD_OFFSET) { > ALIGN_FUNCTION(); > #endif > - /* careful! __ftr_alt_* sections need to be close to .text */ > - *(.text.hot .text .text.fixup .text.unlikely .fixup __ftr_alt_* .ref.text); > + *(.text.hot .text .text.fixup .text.unlikely .fixup .ref.text); > SCHED_TEXT > CPUIDLE_TEXT > LOCK_TEXT > @@ -112,7 +111,6 @@ SECTIONS > *(.got2) > __got2_end = .; > #endif /* CONFIG_PPC32 */ > - > } :kernel > > . = ALIGN(PAGE_SIZE); > @@ -141,6 +139,10 @@ SECTIONS > __init_begin = .; > INIT_TEXT_SECTION(PAGE_SIZE) :kernel > > + .__ftr_alternates.text : AT(ADDR(.__ftr_alternates.text) - LOAD_OFFSET) { > + *(__ftr_alt*); > + } > + > /* .exit.text is discarded at runtime, not link time, > * to deal with references from __bug_table > */ > diff --git a/arch/powerpc/lib/feature-fixups.c b/arch/powerpc/lib/feature-fixups.c > index f3917705c686..26cfb3919589 100644 > --- a/arch/powerpc/lib/feature-fixups.c > +++ b/arch/powerpc/lib/feature-fixups.c > @@ -47,20 +47,13 @@ static unsigned int *calc_addr(struct fixup_entry *fcur, long offset) > static int patch_alt_instruction(unsigned int *src, unsigned int *dest, > unsigned int *alt_start, unsigned int *alt_end) > { > - unsigned int instr; > + unsigned int instr = *src; > > - instr = *src; > - > - if (instr_is_relative_branch(*src)) { > - unsigned int *target = (unsigned int *)branch_target(src); > - > - /* Branch within the section doesn't need translating */ > - if (target < alt_start || target >= alt_end) { > - instr = translate_branch(dest, src); > - if (!instr) > - return 1; > - } > - } > + /* > + * We used to translate relative branches here, however we now > + * do that by fixing up relocations after link with process_relocs > + * tool in arch/powerpc/tools/relocs/ > + */ > > patch_instruction(dest, instr); > > diff --git a/arch/powerpc/tools/Makefile b/arch/powerpc/tools/Makefile > new file mode 100644 > index 000000000000..38dbf0427080 > --- /dev/null > +++ b/arch/powerpc/tools/Makefile > @@ -0,0 +1,3 @@ > +always := $(hostprogs-y) $(hostprogs-m) > + > +subdir-y += relocs > diff --git a/arch/powerpc/tools/relocs/.gitignore b/arch/powerpc/tools/relocs/.gitignore > new file mode 100644 > index 000000000000..5cf4382690d2 > --- /dev/null > +++ b/arch/powerpc/tools/relocs/.gitignore > @@ -0,0 +1 @@ > +process_relocs > diff --git a/arch/powerpc/tools/relocs/Makefile b/arch/powerpc/tools/relocs/Makefile > new file mode 100644 > index 000000000000..bea662d7c1ba > --- /dev/null > +++ b/arch/powerpc/tools/relocs/Makefile > @@ -0,0 +1,12 @@ > +HOST_EXTRACFLAGS += -Wno-unused-function > +ifdef CONFIG_DEBUG_BUGVERBOSE > +HOST_EXTRACFLAGS += -DCONFIG_DEBUG_BUGVERBOSE > +endif > + > +hostprogs-y := process_relocs > + > +process_relocs-objs := process_relocs.o elf_sections.o code-patching.o > + > +always := $(hostprogs-y) > + > +HOSTLOADLIBES_process_relocs += -lelf > diff --git a/arch/powerpc/tools/relocs/code-patching.c b/arch/powerpc/tools/relocs/code-patching.c > new file mode 100644 > index 000000000000..db564a03b51c > --- /dev/null > +++ b/arch/powerpc/tools/relocs/code-patching.c > @@ -0,0 +1,82 @@ > +/* > + * Copyright 2008 Michael Ellerman, IBM Corporation. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * as published by the Free Software Foundation; either version > + * 2 of the License, or (at your option) any later version. > + */ > +#include <stdlib.h> > +#include <stdio.h> > +#include <inttypes.h> > +#include <errno.h> > +#include "code-patching.h" > + > +#define BRANCH_SET_LINK 0x1 > +#define BRANCH_ABSOLUTE 0x2 > + > +static int set_uncond_branch_target(uint32_t *insn, > + const uint64_t addr, uint64_t target) > +{ > + uint32_t i = *insn; > + int64_t offset; > + > + offset = target; > + if (!(i & BRANCH_ABSOLUTE)) > + offset = offset - addr; > + > + /* Check we can represent the target in the instruction format */ > + if (offset < -0x2000000 || offset > 0x1fffffc || offset & 0x3) > + return -EOVERFLOW; > + > + /* Mask out the flags and target, so they don't step on each other. */ > + *insn = 0x48000000 | (i & 0x3) | (offset & 0x03FFFFFC); > + > + return 0; > +} > + > +static int set_cond_branch_target(uint32_t *insn, > + const uint64_t addr, uint64_t target) > +{ > + uint32_t i = *insn; > + int64_t offset; > + > + offset = target; > + if (!(i & BRANCH_ABSOLUTE)) > + offset = offset - addr; > + > + /* Check we can represent the target in the instruction format */ > + if (offset < -0x8000 || offset > 0x7FFF || offset & 0x3) > + return -EOVERFLOW; > + > + /* Mask out the flags and target, so they don't step on each other. */ > + *insn = 0x40000000 | (i & 0x3FF0003) | (offset & 0xFFFC); > + > + return 0; > +} > + > +static uint32_t branch_opcode(uint32_t instr) > +{ > + return (instr >> 26) & 0x3F; > +} > + > +static int instr_is_branch_iform(uint32_t instr) > +{ > + return branch_opcode(instr) == 18; > +} > + > +static int instr_is_branch_bform(uint32_t instr) > +{ > + return branch_opcode(instr) == 16; > +} > + > +int set_branch_target(uint32_t *insn, > + const uint64_t addr, uint64_t target) > +{ > + if (instr_is_branch_iform(*insn)) > + return set_uncond_branch_target(insn, addr, target); > + else if (instr_is_branch_bform(*insn)) > + return set_cond_branch_target(insn, addr, target); > + > + return -EINVAL; > +} > diff --git a/arch/powerpc/tools/relocs/code-patching.h b/arch/powerpc/tools/relocs/code-patching.h > new file mode 100644 > index 000000000000..1d3cbbe99102 > --- /dev/null > +++ b/arch/powerpc/tools/relocs/code-patching.h > @@ -0,0 +1,7 @@ > +#ifndef __CODE_PATCHING_H__ > +#define __CODE_PATCHING_H__ > + > +int set_branch_target(uint32_t *insn, > + const uint64_t addr, uint64_t target); > + > +#endif > diff --git a/arch/powerpc/tools/relocs/elf_sections.c b/arch/powerpc/tools/relocs/elf_sections.c > new file mode 100644 > index 000000000000..718020d93394 > --- /dev/null > +++ b/arch/powerpc/tools/relocs/elf_sections.c > @@ -0,0 +1,337 @@ > +#define _GNU_SOURCE > +#include <assert.h> > +#include <errno.h> > +#include <string.h> > +#include <fcntl.h> > +#include <gelf.h> > +#include <elf.h> > +#include <stdio.h> > +#include <stdint.h> > +#include <unistd.h> > +#include <stdlib.h> > +#include <sys/types.h> > +#include <sys/stat.h> > +#include <sys/mman.h> > + > +#include "elf_sections.h" > + > +#define dbg_printf(...) > + > +static const char *rel_type_name(unsigned int type) > +{ > + static const char *const type_name[] = { > +#define REL_TYPE(X)[X] = #X > + REL_TYPE(R_PPC64_NONE), > + REL_TYPE(R_PPC64_ADDR32), > + REL_TYPE(R_PPC64_ADDR24), > + REL_TYPE(R_PPC64_ADDR16), > + REL_TYPE(R_PPC64_ADDR16_LO), > + REL_TYPE(R_PPC64_ADDR16_HI), > + REL_TYPE(R_PPC64_ADDR16_HA), > + REL_TYPE(R_PPC64_ADDR14), > + REL_TYPE(R_PPC64_ADDR14_BRTAKEN), > + REL_TYPE(R_PPC64_ADDR14_BRNTAKEN), > + REL_TYPE(R_PPC64_REL24), > + REL_TYPE(R_PPC64_REL14), > + REL_TYPE(R_PPC64_REL14_BRTAKEN), > + REL_TYPE(R_PPC64_REL14_BRNTAKEN), > + REL_TYPE(R_PPC64_GOT16), > + REL_TYPE(R_PPC64_GOT16_LO), > + REL_TYPE(R_PPC64_GOT16_HI), > + REL_TYPE(R_PPC64_GOT16_HA), > + REL_TYPE(R_PPC64_COPY), > + REL_TYPE(R_PPC64_GLOB_DAT), > + REL_TYPE(R_PPC64_JMP_SLOT), > + REL_TYPE(R_PPC64_RELATIVE), > + REL_TYPE(R_PPC64_UADDR32), > + REL_TYPE(R_PPC64_UADDR16), > + REL_TYPE(R_PPC64_REL32), > + REL_TYPE(R_PPC64_PLT32), > + REL_TYPE(R_PPC64_PLTREL32), > + REL_TYPE(R_PPC64_PLT16_LO), > + REL_TYPE(R_PPC64_PLT16_HI), > + REL_TYPE(R_PPC64_PLT16_HA), > + REL_TYPE(R_PPC64_SECTOFF), > + REL_TYPE(R_PPC64_SECTOFF_LO), > + REL_TYPE(R_PPC64_SECTOFF_HI), > + REL_TYPE(R_PPC64_SECTOFF_HA), > + REL_TYPE(R_PPC64_ADDR30), > + REL_TYPE(R_PPC64_ADDR64), > + REL_TYPE(R_PPC64_ADDR16_HIGHER), > + REL_TYPE(R_PPC64_ADDR16_HIGHERA), > + REL_TYPE(R_PPC64_ADDR16_HIGHEST), > + REL_TYPE(R_PPC64_ADDR16_HIGHESTA), > + REL_TYPE(R_PPC64_UADDR64), > + REL_TYPE(R_PPC64_REL64), > + REL_TYPE(R_PPC64_PLT64), > + REL_TYPE(R_PPC64_PLTREL64), > + REL_TYPE(R_PPC64_TOC16), > + REL_TYPE(R_PPC64_TOC16_LO), > + REL_TYPE(R_PPC64_TOC16_HI), > + REL_TYPE(R_PPC64_TOC16_HA), > + REL_TYPE(R_PPC64_TOC), > + REL_TYPE(R_PPC64_PLTGOT16), > + REL_TYPE(R_PPC64_PLTGOT16_LO), > + REL_TYPE(R_PPC64_PLTGOT16_HI), > + REL_TYPE(R_PPC64_PLTGOT16_HA), > + REL_TYPE(R_PPC64_ADDR16_DS), > + REL_TYPE(R_PPC64_ADDR16_LO_DS), > + REL_TYPE(R_PPC64_GOT16_DS), > + REL_TYPE(R_PPC64_GOT16_LO_DS), > + REL_TYPE(R_PPC64_PLT16_LO_DS), > + REL_TYPE(R_PPC64_SECTOFF_DS), > + REL_TYPE(R_PPC64_SECTOFF_LO_DS), > + REL_TYPE(R_PPC64_TOC16_DS), > + REL_TYPE(R_PPC64_TOC16_LO_DS), > + REL_TYPE(R_PPC64_PLTGOT16_DS), > + REL_TYPE(R_PPC64_PLTGOT16_LO_DS), > + REL_TYPE(R_PPC64_TLS), > + REL_TYPE(R_PPC64_DTPMOD64), > + REL_TYPE(R_PPC64_TPREL16), > + REL_TYPE(R_PPC64_TPREL16_LO), > + REL_TYPE(R_PPC64_TPREL16_HI), > + REL_TYPE(R_PPC64_TPREL16_HA), > + REL_TYPE(R_PPC64_TPREL64), > + REL_TYPE(R_PPC64_DTPREL16), > + REL_TYPE(R_PPC64_DTPREL16_LO), > + REL_TYPE(R_PPC64_DTPREL16_HI), > + REL_TYPE(R_PPC64_DTPREL16_HA), > + REL_TYPE(R_PPC64_DTPREL64), > + REL_TYPE(R_PPC64_GOT_TLSGD16), > + REL_TYPE(R_PPC64_GOT_TLSGD16_LO), > + REL_TYPE(R_PPC64_GOT_TLSGD16_HI), > + REL_TYPE(R_PPC64_GOT_TLSGD16_HA), > + REL_TYPE(R_PPC64_GOT_TLSLD16), > + REL_TYPE(R_PPC64_GOT_TLSLD16_LO), > + REL_TYPE(R_PPC64_GOT_TLSLD16_HI), > + REL_TYPE(R_PPC64_GOT_TLSLD16_HA), > + REL_TYPE(R_PPC64_GOT_TPREL16_DS), > + REL_TYPE(R_PPC64_GOT_TPREL16_LO_DS), > + REL_TYPE(R_PPC64_GOT_TPREL16_HI), > + REL_TYPE(R_PPC64_GOT_TPREL16_HA), > + REL_TYPE(R_PPC64_GOT_DTPREL16_DS), > + REL_TYPE(R_PPC64_GOT_DTPREL16_LO_DS), > + REL_TYPE(R_PPC64_GOT_DTPREL16_HI), > + REL_TYPE(R_PPC64_GOT_DTPREL16_HA), > + REL_TYPE(R_PPC64_TPREL16_DS), > + REL_TYPE(R_PPC64_TPREL16_LO_DS), > + REL_TYPE(R_PPC64_TPREL16_HIGHER), > + REL_TYPE(R_PPC64_TPREL16_HIGHERA), > + REL_TYPE(R_PPC64_TPREL16_HIGHEST), > + REL_TYPE(R_PPC64_TPREL16_HIGHESTA), > + REL_TYPE(R_PPC64_DTPREL16_DS), > + REL_TYPE(R_PPC64_DTPREL16_LO_DS), > + REL_TYPE(R_PPC64_DTPREL16_HIGHER), > + REL_TYPE(R_PPC64_DTPREL16_HIGHERA), > + REL_TYPE(R_PPC64_DTPREL16_HIGHEST), > + REL_TYPE(R_PPC64_DTPREL16_HIGHESTA), > + REL_TYPE(R_PPC64_TLSGD), > + REL_TYPE(R_PPC64_TLSLD), > + REL_TYPE(R_PPC64_TOCSAVE), > +/* REL_TYPE(R_PPC64_ENTRY), */ > + REL_TYPE(R_PPC64_REL16), > + REL_TYPE(R_PPC64_REL16_LO), > + REL_TYPE(R_PPC64_REL16_HI), > + REL_TYPE(R_PPC64_REL16_HA), > +#undef REL_TYPE > + }; > + const char *name = "UNKNOWN"; > + > + if (type < sizeof(type_name) / sizeof(typeof(type_name[0])) && type_name[type]) > + name = type_name[type]; > + return name; > +} > + > +static struct section *get_section(struct elf *elf, Elf_Scn *scn) > +{ > + struct section *section; > + > + section = malloc(sizeof(struct section)); > + > + section->scn = scn; > + > + if (gelf_getshdr(scn, §ion->shdr) == NULL) { > + fprintf(stderr, "gelf_getshdr failed: %s\n", elf_errmsg(-1)); > + exit(EXIT_FAILURE); > + } > + > + section->name = elf_strptr(elf->elf, elf->shstrndx, section->shdr.sh_name); > + if (section->name == NULL) { > + fprintf(stderr, "gelf_strptr failed: %s\n", elf_errmsg(-1)); > + exit(EXIT_FAILURE); > + } > + > + section->data = elf_getdata(scn, NULL); > + if (section->data) { > + assert(elf_getdata(scn, section->data) == NULL); > + } > + > + section->symtab = NULL; > + if (section->shdr.sh_type == SHT_SYMTAB) > + goto no_symtab; > + if (section->shdr.sh_type == SHT_DYNSYM) > + goto no_symtab; > + if (section->shdr.sh_type == SHT_DYNAMIC) > + goto no_symtab; > + > + /* printf("symtab index:%d\n", elf_scnshndx(scn)); ??? */ > + if (section->shdr.sh_link) { > + Elf_Scn *link_scn; > + > + link_scn = elf_getscn(elf->elf, section->shdr.sh_link); > + section->symtab = get_section(elf, link_scn); > + > + assert(section->symtab->shdr.sh_type == SHT_SYMTAB || > + section->symtab->shdr.sh_type == SHT_DYNSYM); > + } > + > +no_symtab: > + section->strtab = NULL; > + if (section->symtab == NULL) { > + if (section->shdr.sh_link) { > + Elf_Scn *link_scn; > + > + link_scn = elf_getscn(elf->elf, section->shdr.sh_link); > + section->strtab = get_section(elf, link_scn); > + > + assert(section->strtab->shdr.sh_type == SHT_STRTAB); > + } > + } > + > + return section; > +} > + > +struct symbol *elf_sections_get_symbol(struct elf *elf, struct section *section, unsigned long nr) > +{ > + struct symbol *symbol; > + Elf_Scn *scn; > + > + symbol = malloc(sizeof(struct symbol)); > + > + if (gelf_getsym(section->symtab->data, nr, &symbol->sym) == NULL) { > + fprintf(stderr, "gelf_getsym failed: %s\n", elf_errmsg(-1)); > + exit(EXIT_FAILURE); > + } > + > + scn = elf_getscn(elf->elf, symbol->sym.st_shndx); > + symbol->section = get_section(elf, scn); > + symbol->_name = symbol->section->name; > + if (symbol->sym.st_name) { > + symbol->name = elf_strptr(elf->elf, elf_ndxscn(section->symtab->strtab->scn), symbol->sym.st_name); > + symbol->_name = symbol->name; > + } else { > + symbol->name = NULL; > + } > + > + return symbol; > +} > + > +struct relocation *elf_sections_get_reloc(struct elf *elf, struct section *section, size_t n) > +{ > + struct relocation *relocation; > + > + relocation = malloc(sizeof(struct relocation)); > + > + if (section->shdr.sh_type == SHT_REL) { > + if (gelf_getrel(section->data, n, &relocation->rel) != &relocation->rel) { > + return NULL; > + } > + > + relocation->type_name = rel_type_name(GELF_R_TYPE(relocation->rel.r_info)); > + relocation->symbol = elf_sections_get_symbol(elf, section, GELF_R_SYM(relocation->rel.r_info)); > + relocation->offset = relocation->rel.r_offset; > + relocation->target = relocation->symbol->sym.st_value; > + > + } else if (section->shdr.sh_type == SHT_RELA) { > + if (gelf_getrela(section->data, n, &relocation->rela) != &relocation->rela) { > + return NULL; > + } > + > + relocation->type_name = rel_type_name(GELF_R_TYPE(relocation->rela.r_info)); > + relocation->symbol = elf_sections_get_symbol(elf, section, GELF_R_SYM(relocation->rela.r_info)); > + relocation->offset = relocation->rela.r_offset; > + relocation->target = relocation->symbol->sym.st_value; > + relocation->target += relocation->rela.r_addend; > + > + } else { > + assert(0); > + } > + > + return relocation; > +} > + > +struct elf *elf_sections_init(int fd) > +{ > + struct elf *elf; > + Elf_Scn *scn; > + > + elf = malloc(sizeof(struct elf)); > + assert(elf); > + > + if (elf_version(EV_CURRENT) == EV_NONE) { > + fprintf(stderr, "libelf not initialized: %s\n", elf_errmsg(-1)); > + exit(EXIT_FAILURE); > + } > + > + if ((elf->elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { > + fprintf(stderr, "elf_begin failed: %s\n", elf_errmsg(-1)); > + exit(EXIT_FAILURE); > + } > + > + if (elf_kind(elf->elf) != ELF_K_ELF) { > + fprintf(stderr, "Not an ELF object.\n"); > + exit(EXIT_FAILURE); > + } > + > + if (gelf_getehdr(elf->elf, &elf->ehdr) == NULL) { > + fprintf(stderr, "gelf_getehdr failed: %s\n", elf_errmsg(-1)); > + exit(EXIT_FAILURE); > + } > + > + if (elf->ehdr.e_version != EV_CURRENT) { > + fprintf(stderr, "Unknown ELF version\n"); > + exit(EXIT_FAILURE); > + } > + > + if (elf->ehdr.e_machine != EM_PPC && elf->ehdr.e_machine != EM_PPC64) { > + fprintf(stderr, "Not a PPC/PPC64 machine\n"); > + exit(EXIT_FAILURE); > + } > + > + if (elf_getshdrstrndx(elf->elf, &elf->shstrndx) != 0) { > + fprintf(stderr, "elf_getshdrstrndx failed: %s\n", elf_errmsg(-1)); > + exit(EXIT_FAILURE); > + } > + > + scn = elf_getscn(elf->elf, elf->shstrndx); > + elf->strtab = get_section(elf, scn); > + assert(elf->strtab->shdr.sh_type == SHT_STRTAB); > + > + return elf; > +} > + > +void elf_sections_exit(struct elf *elf) > +{ > + elf_end(elf->elf); > +} > + > +int elf_sections_processor(struct elf *elf, > + int (*fn)(struct section *section, void *arg), > + void *arg) > +{ > + Elf_Scn *scn; > + int err; > + > + scn = NULL ; > + while ((scn = elf_nextscn(elf->elf, scn)) != NULL) { > + struct section *section; > + > + section = get_section(elf, scn); > + > + err = fn(section, arg); > + if (err) > + return err; > + } > + > + return 0; > +} > diff --git a/arch/powerpc/tools/relocs/elf_sections.h b/arch/powerpc/tools/relocs/elf_sections.h > new file mode 100644 > index 000000000000..c3bc744efca8 > --- /dev/null > +++ b/arch/powerpc/tools/relocs/elf_sections.h > @@ -0,0 +1,50 @@ > +#ifndef __ELF_SECTIONS_H__ > +#define __ELF_SECTIONS_H__ > + > +#include <gelf.h> > +#include <elf.h> > + > +struct section { > + Elf_Scn *scn; > + GElf_Shdr shdr; > + const char *name; > + Elf_Data *data; > + > + struct section *symtab; > + struct section *strtab; > +}; > + > +struct symbol { > + GElf_Sym sym; > + struct section *section; > + const char *name; > + const char *_name; > +}; > + > +struct relocation { > + GElf_Rel rel; > + GElf_Rela rela; > + > + const char *type_name; > + struct symbol *symbol; > + > + uint64_t offset; > + uint64_t target; > +}; > + > +struct elf { > + Elf *elf; > + GElf_Ehdr ehdr; > + size_t shstrndx; > + struct section *strtab; > +}; > + > +struct symbol *elf_sections_get_symbol(struct elf *elf, struct section *section, unsigned long nr); > +struct relocation *elf_sections_get_reloc(struct elf *elf, struct section *section, size_t n); > +struct elf *elf_sections_init(int fd); > +void elf_sections_exit(struct elf *elf); > +int elf_sections_processor(struct elf *elf, > + int (*fn)(struct section *section, void *arg), > + void *arg); > + > +#endif > diff --git a/arch/powerpc/tools/relocs/process_relocs.c b/arch/powerpc/tools/relocs/process_relocs.c > new file mode 100644 > index 000000000000..a678179cfd0b > --- /dev/null > +++ b/arch/powerpc/tools/relocs/process_relocs.c > @@ -0,0 +1,718 @@ > +#define _GNU_SOURCE > +#include <assert.h> > +#include <errno.h> > +#include <string.h> > +#include <fcntl.h> > +#include <gelf.h> > +#include <elf.h> > +#include <stdio.h> > +#include <stdint.h> > +#include <unistd.h> > +#include <stdlib.h> > +#include <getopt.h> > +#include <sys/types.h> > +#include <sys/stat.h> > +#include <sys/mman.h> > +#include <asm/byteorder.h> > +#include "elf_sections.h" > +#include "code-patching.h" > + > +/* > + * This program runs through relocation data in PPC/PPC64 vmlinux ELF > + * image generated with --emit-relocs, and performs some processing and > + * checks. > + * > + * Presently, it has the following functions: > + * 1. Fix relocations for branches inside alternate feature sections > + * (the "else" patches), so that they are correct for their destination > + * address. They never get executed at their linked location. > + * > + * This is done by parsing all fixup_entry structures in the _ftr_fixup > + * sections, and keeping those with non-zero alternate patch. Then all > + * relocations in the .__ftr_alternates.text section are parsed, and those > + * matching addresses in our fixup_entry alternates patches get > + * struct insn_patch created for them. Finally, all struct insn_patch'es > + * are iterated and written to the image in-place. Care needs to be taken > + * with nested fixups, see check_and_flatten_fixup_entries(). > + * > + * 2. Check that no __ex_table or __bug_table entries point to alternate > + * sections. We don't support that at present. > + */ > + > +#define dbg_printf(...) > + > +static int is_kernel = 0; > + > +struct fixup_entry_64 { > + uint64_t mask; > + uint64_t value; > + uint64_t start_off; > + uint64_t end_off; > + uint64_t alt_start_off; > + uint64_t alt_end_off; > +} __attribute__((packed)); > + > +#define fixup_entry fixup_entry_64 > + > +struct fixup_entry_32 { > + uint32_t mask; > + uint32_t value; > + uint32_t start_off; > + uint32_t end_off; > + uint32_t alt_start_off; > + uint32_t alt_end_off; > +} __attribute__((packed)); > + > +struct exception_entry_64 { > + int32_t insn; > + int32_t fixup; > +}; > + > +struct exception_entry_32 { > + uint32_t insn; > + uint32_t fixup; > +}; > + > +struct bug_entry_64 { > + uint64_t bug_addr; > +#ifdef CONFIG_DEBUG_BUGVERBOSE > + uint64_t file; > + uint16_t line; > +#endif > + uint16_t flags; > +}; > + > +struct bug_entry_32 { > + uint32_t bug_addr; > +#ifdef CONFIG_DEBUG_BUGVERBOSE > + uint32_t file; > + uint16_t line; > +#endif > + uint16_t flags; > +}; > + > +struct insn_patch { > + uint32_t insn; /* New instruction */ > + off_t offset; /* Image location to patch */ > +}; > + > +static int is_64bit(struct elf *elf) > +{ > + return elf->ehdr.e_ident[EI_CLASS] == ELFCLASS64; > +} > + > +static int is_32bit(struct elf *elf) > +{ > + return elf->ehdr.e_ident[EI_CLASS] == ELFCLASS32; > +} > + > +static int is_le(struct elf *elf) > +{ > + return elf->ehdr.e_ident[EI_DATA] == ELFDATA2LSB; > +} > + > +static int is_be(struct elf *elf) > +{ > + return elf->ehdr.e_ident[EI_DATA] == ELFDATA2MSB; > +} > + > + > +static struct elf *elf; > + > +static uint16_t f16_to_cpu(uint16_t val) > +{ > + if (is_le(elf)) > + return __le16_to_cpu(val); > + else > + return __be16_to_cpu(val); > +} > + > +static uint32_t f32_to_cpu(uint32_t val) > +{ > + if (is_le(elf)) > + return __le32_to_cpu(val); > + else > + return __be32_to_cpu(val); > +} > + > +static uint64_t f64_to_cpu(uint64_t val) > +{ > + if (is_le(elf)) > + return __le64_to_cpu(val); > + else > + return __be64_to_cpu(val); > +} > + > +static uint16_t cpu_to_f16(uint16_t val) > +{ > + if (is_le(elf)) > + return __cpu_to_le16(val); > + else > + return __cpu_to_be16(val); > +} > + > +static uint32_t cpu_to_f32(uint32_t val) > +{ > + if (is_le(elf)) > + return __cpu_to_le32(val); > + else > + return __cpu_to_be32(val); > +} > + > +static uint64_t cpu_to_f64(uint64_t val) > +{ > + if (is_le(elf)) > + return __cpu_to_le64(val); > + else > + return __cpu_to_be64(val); > +} > + > +static struct section *ftr_alt; > + > +static unsigned int nr_fes = 0; > +static struct fixup_entry *fes = NULL; > + > +/* Could grab the start/end of .__ftr_alternates.text directly */ > +static uint64_t fe_alt_start = -1; > +static uint64_t fe_alt_end = 0; > + > +static struct fixup_entry *find_fe_altaddr(uint64_t addr) > +{ > + unsigned int i; > + > + if (addr < fe_alt_start) > + return NULL; > + if (addr >= fe_alt_end) > + return NULL; > + > + for (i = 0; i < nr_fes; i++) { > + if (addr >= fes[i].alt_start_off && addr < fes[i].alt_end_off) > + return &fes[i]; > + } > + return NULL; > +} > + > +/* > + * Fixup entries can be nested, so we need to find the final address. The > + * "if" side never needs to be fixed -- if it's nested inside an "else" part, > + * it will get fixed as part of the fixup for that else part. "else" nested > + * in "else" needs to be fixed -- possibly multiple nestings. > + * > + * The inner-most nestings always come first, so we traverse the array > + * backward, fixing up destination of nested parts according to their > + * parent, which takes care of multiple nestings without further work. > + * > + * We can also do some sanity checks here. > + */ > +static void check_and_flatten_fixup_entries(void) > +{ > + static struct fixup_entry *fe; > + unsigned int i; > + > + i = nr_fes; > + while (i) { > + static struct fixup_entry *parent; > + uint64_t nested_off; /* offset from start of parent */ > + uint64_t size; > + > + i--; > + fe = &fes[i]; > + > + parent = find_fe_altaddr(fe->start_off); > + if (!parent) { > + parent = find_fe_altaddr(fe->end_off); > + assert(!parent); /* Should be entirely contained */ > + continue; > + } > + > + size = fe->end_off - fe->start_off; > + nested_off = fe->start_off - parent->alt_start_off; > + > + dbg_printf("flattening child fixup entry [%lx-%lx]->[%lx-%lx] to parent [%lx-%lx]->[%lx-%lx] new child [%lx-%lx]->[%lx-%lx]\n", fe->alt_start_off, fe->alt_end_off, fe->start_off, fe->end_off, parent->alt_start_off, parent->alt_end_off, parent->start_off, parent->end_off, fe->alt_start_off, fe->alt_end_off, parent->start_off + nested_off, parent->start_off + nested_off + size); > + > + fe->start_off = parent->start_off + nested_off; > + fe->end_off = fe->start_off + size; > + } > + > +} > + > +static unsigned int nr_ips = 0; > +static struct insn_patch *ips = NULL; > + > +static void create_branch_patch(struct relocation *relocation, struct fixup_entry *fe) > +{ > + struct insn_patch *ip; > + uint64_t addr = relocation->offset; > + uint64_t dst_addr; > + uint64_t scn_delta; > + uint64_t offset; > + uint32_t insn; > + uint32_t *i; > + > + assert(addr >= ftr_alt->shdr.sh_addr && > + addr < ftr_alt->shdr.sh_addr + ftr_alt->shdr.sh_size); > + > + scn_delta = addr - ftr_alt->shdr.sh_addr; > + > + assert(scn_delta < ftr_alt->data->d_size); > + > + i = ftr_alt->data->d_buf + scn_delta; > + > + insn = f32_to_cpu(*i); > + > + offset = ftr_alt->shdr.sh_offset + scn_delta; > + dst_addr = addr - fe->alt_start_off + fe->start_off; > + > + if (set_branch_target(&insn, dst_addr, relocation->target)) { > + fprintf(stderr, "ftr_alt branch target out of range or not a branch. address=%llx\n", (unsigned long long)addr); > + exit(EXIT_FAILURE); > + } > + > + if (insn == *i) /* Nothing to do */ > + return; > + > + ips = realloc(ips, (nr_ips + 1) * sizeof(struct insn_patch)); > + ip = &ips[nr_ips]; > + nr_ips++; > + > + ip->insn = insn; > + ip->offset = offset; > + > + dbg_printf("update branch insn (%x->%x)\n", *i, ip->insn); > +} > + > +static int process_alt_data(struct section *section, void *arg) > +{ > + if (strcmp(section->name, ".__ftr_alternates.text") != 0) > + return 0; > + > + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); > + assert(section->shdr.sh_type == SHT_PROGBITS); > + > + ftr_alt = section; > + > + return 0; > +} > + > +static int process_fixup_entries(struct section *section, void *arg) > +{ > + Elf_Data *data; > + unsigned int nr, i; > + > + if (strstr(section->name, "_ftr_fixup") == 0) > + return 0; > + > + if (section->shdr.sh_type != SHT_PROGBITS) > + return 0; > + > + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); > + > + data = section->data; > + assert(data); > + assert(data->d_size > 0); > + > + if (is_64bit(elf)) { > + assert(data->d_size % sizeof(struct fixup_entry_64) == 0); > + nr = data->d_size / sizeof(struct fixup_entry_64); > + } else { > + assert(data->d_size % sizeof(struct fixup_entry_32) == 0); > + nr = data->d_size / sizeof(struct fixup_entry_32); > + } > + > + for (i = 0; i < nr; i++) { > + struct fixup_entry *dst; > + unsigned long idx; > + unsigned long long off; > + > + if (is_64bit(elf)) { > + struct fixup_entry_64 *src; > + > + idx = i * sizeof(struct fixup_entry_64); > + > + off = section->shdr.sh_addr + data->d_off + idx; > + src = data->d_buf + idx; > + > + if (src->alt_start_off == src->alt_end_off) > + continue; > + > + fes = realloc(fes, (nr_fes + 1) * sizeof(struct fixup_entry)); > + dst = &fes[nr_fes]; > + nr_fes++; > + > + dst->mask = f64_to_cpu(src->mask); > + dst->value = f64_to_cpu(src->value); > + dst->start_off = f64_to_cpu(src->start_off) + off; > + dst->end_off = f64_to_cpu(src->end_off) + off; > + dst->alt_start_off = f64_to_cpu(src->alt_start_off) + off; > + dst->alt_end_off = f64_to_cpu(src->alt_end_off) + off; > + > + } else { > + struct fixup_entry_32 *src; > + > + idx = i * sizeof(struct fixup_entry_32); > + > + off = section->shdr.sh_addr + data->d_off + idx; > + src = data->d_buf + idx; > + > + if (src->alt_start_off == src->alt_end_off) > + continue; > + > + fes = realloc(fes, (nr_fes + 1) * sizeof(struct fixup_entry)); > + dst = &fes[nr_fes]; > + nr_fes++; > + > + dst->mask = f32_to_cpu(src->mask); > + dst->value = f32_to_cpu(src->value); > + dst->start_off = f32_to_cpu(src->start_off) + off; > + dst->end_off = f32_to_cpu(src->end_off) + off; > + dst->alt_start_off = f32_to_cpu(src->alt_start_off) + off; > + dst->alt_end_off = f32_to_cpu(src->alt_end_off) + off; > + > + } > + if (dst->alt_start_off < fe_alt_start) > + fe_alt_start = dst->alt_start_off; > + if (dst->alt_end_off > fe_alt_end) > + fe_alt_end = dst->alt_end_off; > + > + dbg_printf("%llx fixup entry %llx:%llx (%llx-%llx) <- (%llx-%llx)\n", off, > + (unsigned long long)dst->mask, (unsigned long long)dst->value, > + (unsigned long long)dst->start_off, (unsigned long long)dst->end_off, > + (unsigned long long)dst->alt_start_off, (unsigned long long)dst->alt_end_off); > + } > + > + return 0; > +} > + > +/* > + * Check no exceptions are in "alt" sections. We don't relocate them as > + * yet. > + */ > +static int process_exception_entries(struct section *section, void *arg) > +{ > + Elf_Data *data; > + unsigned int nr, i; > + > + if (strcmp(section->name, "__ex_table") != 0) > + return 0; > + > + if (section->shdr.sh_type != SHT_PROGBITS) > + return 0; > + > + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); > + > + data = section->data; > + assert(data); > + assert(data->d_size > 0); > + > + if (is_64bit(elf)) { > + assert(data->d_size % sizeof(struct exception_entry_64) == 0); > + nr = data->d_size / sizeof(struct exception_entry_64); > + } else { > + assert(data->d_size % sizeof(struct exception_entry_32) == 0); > + nr = data->d_size / sizeof(struct exception_entry_32); > + } > + > + for (i = 0; i < nr; i++) { > + unsigned long idx; > + uint64_t exaddr; > + unsigned long long off; > + > + if (is_64bit(elf)) { > + struct exception_entry_64 *ex; > + > + idx = i * sizeof(struct exception_entry_64); > + > + off = section->shdr.sh_addr + data->d_off + idx; > + > + ex = data->d_buf + idx; > + > + exaddr = f32_to_cpu(ex->insn) + off; > + > + } else { > + struct exception_entry_32 *ex; > + > + idx = i * sizeof(struct exception_entry_32); > + > + ex = data->d_buf + idx; > + > + exaddr = f32_to_cpu(ex->insn); > + } > + > + dbg_printf("exception addr:%llx\n", exaddr); > + > + if (exaddr < fe_alt_start) > + continue; > + if (exaddr >= fe_alt_end) > + continue; > + > + /* > + * This would be the place to create exception address patches > + * if we want to support that feature > + */ > + fprintf(stderr, "ftr_alt code contains an exception entry, which is not allowed. address=%llx\n", (unsigned long long)exaddr); > + exit(EXIT_FAILURE); > + } > + > + return 0; > +} > + > +/* > + * Check no exceptions are in "alt" sections. We don't relocate them as > + * yet. > + */ > +static int process_bug_entries(struct section *section, void *arg) > +{ > + Elf_Data *data; > + unsigned int nr, i; > + > + if (strcmp(section->name, "__bug_table") != 0) > + return 0; > + > + if (section->shdr.sh_type != SHT_PROGBITS) > + return 0; > + > + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); > + > + data = section->data; > + assert(data); > + assert(data->d_size > 0); > + > + if (is_64bit(elf)) { > + assert(data->d_size % sizeof(struct bug_entry_64) == 0); > + nr = data->d_size / sizeof(struct bug_entry_64); > + } else { > + assert(data->d_size % sizeof(struct bug_entry_32) == 0); > + nr = data->d_size / sizeof(struct bug_entry_32); > + } > + > + for (i = 0; i < nr; i++) { > + unsigned long idx; > + uint64_t bugaddr; > + > + if (is_64bit(elf)) { > + struct bug_entry_64 *bug; > + > + idx = i * sizeof(struct bug_entry_64); > + > + bug = data->d_buf + idx; > + > + bugaddr = f64_to_cpu(bug->bug_addr); > + > + } else { > + struct bug_entry_32 *bug; > + > + idx = i * sizeof(struct bug_entry_32); > + > + bug = data->d_buf + idx; > + > + bugaddr = f32_to_cpu(bug->bug_addr); > + } > + > + dbg_printf("bug addr:%llx\n", bugaddr); > + > + if (bugaddr < fe_alt_start) > + continue; > + if (bugaddr >= fe_alt_end) > + continue; > + > + /* > + * This would be the place to create bug address patches if > + * we want to support that feature > + */ > + fprintf(stderr, "ftr_alt code contains a BUG entry, which is not allowed. address=%llx\n", (unsigned long long)bugaddr); > + /* Could print file, line here for verbose case */ > + exit(EXIT_FAILURE); > + } > + > + return 0; > +} > + > + > +static int process_alt_relocations(struct section *section, void *arg) > +{ > + struct relocation *relocation; > + size_t n; > + > + if (strcmp(section->name, ".rela.__ftr_alternates.text") != 0) > + return 0; > + > + assert(section->shdr.sh_type == SHT_RELA); > + > + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); > + > + n = 0; > + while ((relocation = elf_sections_get_reloc(elf, section, n)) != NULL) { > + struct fixup_entry *fe; > + > + n++; > + > + dbg_printf("%llx %s %s %llx + %llx\n", > + (unsigned long long)relocation->offset, > + relocation->type_name, > + relocation->symbol->_name, > + (unsigned long long)relocation->symbol->sym.st_value, > + (unsigned long long)relocation->rela.r_addend); > + > + fe = find_fe_altaddr(relocation->offset); > + if (fe) { > + dbg_printf("reloc has fe %llx:%llx (%llx-%llx) <- (%llx-%llx)\n", > + (unsigned long long)fe->mask, > + (unsigned long long)fe->value, > + (unsigned long long)fe->start_off, > + (unsigned long long)fe->end_off, > + (unsigned long long)fe->alt_start_off, > + (unsigned long long)fe->alt_end_off); > + > + if (relocation->target >= fe->alt_start_off && > + relocation->target < fe->alt_end_off) { > + dbg_printf(" reloc within patch code\n"); > + continue; > + } > + > + /* > + * We really should check for all branches either side > + * of fixup_entry from outside (including within > + * different fixup code). It's almost guaranteed to go > + * badly. Not just relocations, but branches too, > + * because nearby branches might get resolved without > + * a relocation. > + */ > + if (relocation->target >= ftr_alt->shdr.sh_addr && > + relocation->target < ftr_alt->shdr.sh_addr + > + ftr_alt->shdr.sh_size) { > + fprintf(stderr, "ftr_alt branch target is another ftr_alt region, which is not allowed. address=%llx\n", (unsigned long long)relocation->offset); > + exit(EXIT_FAILURE); > + } > + > + /* > + * Resolved module symbols should work. Unresolved > + * ones would need their relocations fixed in the > + * same manner as instructions are fixed. > + */ > + if (!is_kernel) { > + fprintf(stderr, "module code with alt feature relocations is currently not supported\n"); > + exit(EXIT_FAILURE); > + } > + > + create_branch_patch(relocation, fe); > + } else { > + dbg_printf(" reloc has no fe\n"); > + } > + } > + > + return 0; > +} > + > + > +int main(int argc, char *argv[]) > +{ > + int fd; > + int err; > + unsigned int i; > + struct stat stat; > + void *mem; > + int opt; > + > + if (argc != 2) > + > + while ((opt = getopt(argc, argv, "k")) != -1) { > + switch (opt) { > + case 'k': > + is_kernel = 1; > + break; > + default: > + exit(EXIT_FAILURE); > + } > + } > + > + if (!strcmp(argv[optind], "vmlinux")) > + is_kernel = 1; > + > + if (optind >= argc || optind + 1 < argc) > + exit(EXIT_FAILURE); > + > + fd = open(argv[optind], O_RDONLY, 0); > + if (fd == -1) { > + fprintf(stderr, "open %s failed: %s\n", argv[1], strerror(errno)); > + exit(EXIT_FAILURE); > + } > + > + elf = elf_sections_init(fd); > + > + err = elf_sections_processor(elf, process_alt_data, NULL); > + assert(!err); > + > + /* Gather and massage the fixup entries */ > + err = elf_sections_processor(elf, process_fixup_entries, NULL); > + assert(!err); > + > + check_and_flatten_fixup_entries(); > + > + /* We don't handle module relocations for these symbols as yet */ > + if (is_kernel) { > + /* Sanity checking */ > + err = elf_sections_processor(elf, process_exception_entries, NULL); > + assert(!err); > + > + err = elf_sections_processor(elf, process_bug_entries, NULL); > + assert(!err); > + } > + > + /* Check the relocations and create necessary instruction patches */ > + err = elf_sections_processor(elf, process_alt_relocations, NULL); > + assert(!err); > + > + /* Done with analysis phase */ > + > + elf_sections_exit(elf); > + > + if (close(fd) == -1) { > + fprintf(stderr, "close %s failed: %s\n", argv[1], strerror(errno)); > + exit(EXIT_FAILURE); > + } > + > + if (!nr_ips) { > + dbg_printf("Nothing to do.\n"); > + exit(EXIT_SUCCESS); > + } > + > + /* Now apply the instruction patches by writing to the file */ > + > + dbg_printf("%u instructions to patch.\n", nr_ips); > + > + fd = open(argv[1], O_RDWR, 0); > + if (fd == -1) { > + fprintf(stderr, "open %s failed: %s\n", argv[1], strerror(errno)); > + exit(EXIT_FAILURE); > + } > + > + if (fstat(fd, &stat) == -1) { > + perror("stat"); > + exit(EXIT_FAILURE); > + } > + > + mem = mmap(0, stat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); > + if (mem == MAP_FAILED) { > + perror("mmap"); > + exit(EXIT_FAILURE); > + } > + > + for (i = 0; i < nr_ips; i++) { > + struct insn_patch *ip = &ips[i]; > + > + assert(ip->offset < stat.st_size); > + *(uint32_t *)(mem + ip->offset) = ip->insn; > + } > + > + if (munmap(mem, stat.st_size) == -1) { > + perror("mmap"); > + exit(EXIT_FAILURE); > + } > + > + if (close(fd) == -1) { > + fprintf(stderr, "close %s failed: %s\n", argv[1], strerror(errno)); > + exit(EXIT_FAILURE); > + } > + > + exit(EXIT_SUCCESS); > +}
Le 29/01/2024 à 07:25, Sathvika Vasireddy a écrit : > Hi Christophe, Nick > > On 1/26/24 12:32 AM, Christophe Leroy wrote: >> Hi Nic, >> >> Le 21/05/2017 à 03:01, Nicholas Piggin a écrit : >>> Implement build-time fixup of alternate feature relative addresses for >>> the out-of-line ("else") patch code. This is done post-link with a new >>> powerpc build tool that parses relocations and fixup structures, and >>> adjusts branch instructions. >>> >>> This gives us the ability to link patch code anywhere in the kernel, >>> without branches to targets outside the patch code having to be >>> reached directly (without a linker stub). This allows patch code to be >>> moved out from the head section, and avoids build failures with >>> unresolvable branche. >> >> Is it worth keeping this hanging in patchwork ? It seems outdated and >> doesn't apply. Could this me done with objtool instead ? >> >> Christophe > > Yes, this can be done with objtool. I am working on this and will post > an RFC this week. > Nice. I've open an issue to track his at https://github.com/linuxppc/issues/issues/479 and have retired Nic's patch in patchwork. Christophe
diff --git a/arch/powerpc/Makefile b/arch/powerpc/Makefile index edc682f7b462..bc4791aecd03 100644 --- a/arch/powerpc/Makefile +++ b/arch/powerpc/Makefile @@ -104,6 +104,11 @@ LDFLAGS_vmlinux-$(CONFIG_RELOCATABLE) := -pie LDFLAGS_vmlinux := $(LDFLAGS_vmlinux-y) LDFLAGS_vmlinux += $(call ld-option,--orphan-handling=warn) +# --emit-relocs required for post-link fixup of alternate feature +# text section relocations. +LDFLAGS_vmlinux += --emit-relocs +KBUILD_LDFLAGS_MODULE += --emit-relocs + ifeq ($(CONFIG_PPC64),y) ifeq ($(call cc-option-yn,-mcmodel=medium),y) # -mcmodel=medium breaks modules because it uses 32bit offsets from @@ -429,6 +434,7 @@ checkbin: false ; \ fi +archscripts: scripts_basic + $(Q)$(MAKE) $(build)=arch/powerpc/tools CLEAN_FILES += $(TOUT) - diff --git a/arch/powerpc/Makefile.postlink b/arch/powerpc/Makefile.postlink index 5db43ebbe2df..bf9b180e3530 100644 --- a/arch/powerpc/Makefile.postlink +++ b/arch/powerpc/Makefile.postlink @@ -2,7 +2,9 @@ # Post-link powerpc pass # =========================================================================== # -# 1. Check that vmlinux relocations look sane +# 1. Invoke process_relocs to fix feature fixup alternate section branches. +# 2. Check that vmlinux relocations look sane +# 3. Strip static relocations (created by --emit-relocs) if binutils >= 2.27 PHONY := __archpost __archpost: @@ -23,19 +25,35 @@ else $(CONFIG_SHELL) $(srctree)/arch/powerpc/tools/relocs_check.sh "$(OBJDUMP)" "$@" endif +quiet_cmd_process_alt_ftr_relocs = FTR_ALT $@ + cmd_process_alt_ftr_relocs = \ + arch/powerpc/tools/relocs/process_relocs "$@" + +ifeq ($(call ld-ifversion, -ge, 227000000, y),y) +quiet_cmd_strip_relocs = STRIP $@ + cmd_strip_relocs = \ + $(OBJCOPY) $(shell for sec in \ + $$(readelf -St $@ | grep -B1 RELA | grep "\.rela" | \ + cut -d"]" -f2 | sed "s/[[:space:]]//" | \ + grep -v "^\.rela\.dyn$$" | sed "s/\.rela//") ; \ + do echo -n "--remove-relocation=$$sec " ; done) $@ +endif + # `@true` prevents complaint when there is nothing to be done vmlinux: FORCE - @true + $(call if_changed,process_alt_ftr_relocs) ifdef CONFIG_PPC64 $(call cmd,head_check) endif ifdef CONFIG_RELOCATABLE $(call if_changed,relocs_check) endif + $(call if_changed,strip_relocs) %.ko: FORCE - @true + $(call if_changed,process_alt_ftr_relocs) + $(call if_changed,strip_relocs) clean: rm -f .tmp_symbols.txt diff --git a/arch/powerpc/include/asm/feature-fixups.h b/arch/powerpc/include/asm/feature-fixups.h index 2de2319b99e2..9f78f2f62886 100644 --- a/arch/powerpc/include/asm/feature-fixups.h +++ b/arch/powerpc/include/asm/feature-fixups.h @@ -16,6 +16,9 @@ * useable with the vdso shared library. There is also an assumption * that values will be negative, that is, the fixup table has to be * located after the code it fixes up. + * + * Please ensure that new section names, modifications to FTR_ENTRY + * encoding, etc., is handled by arch/powerpc/tools/relocs/ code. */ #if defined(CONFIG_PPC64) && !defined(__powerpc64__) /* 64 bits kernel, 32 bits code (ie. vdso32) */ @@ -33,7 +36,7 @@ #define FTR_SECTION_ELSE_NESTED(label) \ label##2: \ - .pushsection __ftr_alt_##label,"a"; \ + .pushsection __ftr_alt_##label,"ax"; \ .align 2; \ label##3: diff --git a/arch/powerpc/kernel/vmlinux.lds.S b/arch/powerpc/kernel/vmlinux.lds.S index 0e78129e033a..d3a4ad960176 100644 --- a/arch/powerpc/kernel/vmlinux.lds.S +++ b/arch/powerpc/kernel/vmlinux.lds.S @@ -87,8 +87,7 @@ SECTIONS .text : AT(ADDR(.text) - LOAD_OFFSET) { ALIGN_FUNCTION(); #endif - /* careful! __ftr_alt_* sections need to be close to .text */ - *(.text.hot .text .text.fixup .text.unlikely .fixup __ftr_alt_* .ref.text); + *(.text.hot .text .text.fixup .text.unlikely .fixup .ref.text); SCHED_TEXT CPUIDLE_TEXT LOCK_TEXT @@ -112,7 +111,6 @@ SECTIONS *(.got2) __got2_end = .; #endif /* CONFIG_PPC32 */ - } :kernel . = ALIGN(PAGE_SIZE); @@ -141,6 +139,10 @@ SECTIONS __init_begin = .; INIT_TEXT_SECTION(PAGE_SIZE) :kernel + .__ftr_alternates.text : AT(ADDR(.__ftr_alternates.text) - LOAD_OFFSET) { + *(__ftr_alt*); + } + /* .exit.text is discarded at runtime, not link time, * to deal with references from __bug_table */ diff --git a/arch/powerpc/lib/feature-fixups.c b/arch/powerpc/lib/feature-fixups.c index f3917705c686..26cfb3919589 100644 --- a/arch/powerpc/lib/feature-fixups.c +++ b/arch/powerpc/lib/feature-fixups.c @@ -47,20 +47,13 @@ static unsigned int *calc_addr(struct fixup_entry *fcur, long offset) static int patch_alt_instruction(unsigned int *src, unsigned int *dest, unsigned int *alt_start, unsigned int *alt_end) { - unsigned int instr; + unsigned int instr = *src; - instr = *src; - - if (instr_is_relative_branch(*src)) { - unsigned int *target = (unsigned int *)branch_target(src); - - /* Branch within the section doesn't need translating */ - if (target < alt_start || target >= alt_end) { - instr = translate_branch(dest, src); - if (!instr) - return 1; - } - } + /* + * We used to translate relative branches here, however we now + * do that by fixing up relocations after link with process_relocs + * tool in arch/powerpc/tools/relocs/ + */ patch_instruction(dest, instr); diff --git a/arch/powerpc/tools/Makefile b/arch/powerpc/tools/Makefile new file mode 100644 index 000000000000..38dbf0427080 --- /dev/null +++ b/arch/powerpc/tools/Makefile @@ -0,0 +1,3 @@ +always := $(hostprogs-y) $(hostprogs-m) + +subdir-y += relocs diff --git a/arch/powerpc/tools/relocs/.gitignore b/arch/powerpc/tools/relocs/.gitignore new file mode 100644 index 000000000000..5cf4382690d2 --- /dev/null +++ b/arch/powerpc/tools/relocs/.gitignore @@ -0,0 +1 @@ +process_relocs diff --git a/arch/powerpc/tools/relocs/Makefile b/arch/powerpc/tools/relocs/Makefile new file mode 100644 index 000000000000..bea662d7c1ba --- /dev/null +++ b/arch/powerpc/tools/relocs/Makefile @@ -0,0 +1,12 @@ +HOST_EXTRACFLAGS += -Wno-unused-function +ifdef CONFIG_DEBUG_BUGVERBOSE +HOST_EXTRACFLAGS += -DCONFIG_DEBUG_BUGVERBOSE +endif + +hostprogs-y := process_relocs + +process_relocs-objs := process_relocs.o elf_sections.o code-patching.o + +always := $(hostprogs-y) + +HOSTLOADLIBES_process_relocs += -lelf diff --git a/arch/powerpc/tools/relocs/code-patching.c b/arch/powerpc/tools/relocs/code-patching.c new file mode 100644 index 000000000000..db564a03b51c --- /dev/null +++ b/arch/powerpc/tools/relocs/code-patching.c @@ -0,0 +1,82 @@ +/* + * Copyright 2008 Michael Ellerman, IBM Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include <stdlib.h> +#include <stdio.h> +#include <inttypes.h> +#include <errno.h> +#include "code-patching.h" + +#define BRANCH_SET_LINK 0x1 +#define BRANCH_ABSOLUTE 0x2 + +static int set_uncond_branch_target(uint32_t *insn, + const uint64_t addr, uint64_t target) +{ + uint32_t i = *insn; + int64_t offset; + + offset = target; + if (!(i & BRANCH_ABSOLUTE)) + offset = offset - addr; + + /* Check we can represent the target in the instruction format */ + if (offset < -0x2000000 || offset > 0x1fffffc || offset & 0x3) + return -EOVERFLOW; + + /* Mask out the flags and target, so they don't step on each other. */ + *insn = 0x48000000 | (i & 0x3) | (offset & 0x03FFFFFC); + + return 0; +} + +static int set_cond_branch_target(uint32_t *insn, + const uint64_t addr, uint64_t target) +{ + uint32_t i = *insn; + int64_t offset; + + offset = target; + if (!(i & BRANCH_ABSOLUTE)) + offset = offset - addr; + + /* Check we can represent the target in the instruction format */ + if (offset < -0x8000 || offset > 0x7FFF || offset & 0x3) + return -EOVERFLOW; + + /* Mask out the flags and target, so they don't step on each other. */ + *insn = 0x40000000 | (i & 0x3FF0003) | (offset & 0xFFFC); + + return 0; +} + +static uint32_t branch_opcode(uint32_t instr) +{ + return (instr >> 26) & 0x3F; +} + +static int instr_is_branch_iform(uint32_t instr) +{ + return branch_opcode(instr) == 18; +} + +static int instr_is_branch_bform(uint32_t instr) +{ + return branch_opcode(instr) == 16; +} + +int set_branch_target(uint32_t *insn, + const uint64_t addr, uint64_t target) +{ + if (instr_is_branch_iform(*insn)) + return set_uncond_branch_target(insn, addr, target); + else if (instr_is_branch_bform(*insn)) + return set_cond_branch_target(insn, addr, target); + + return -EINVAL; +} diff --git a/arch/powerpc/tools/relocs/code-patching.h b/arch/powerpc/tools/relocs/code-patching.h new file mode 100644 index 000000000000..1d3cbbe99102 --- /dev/null +++ b/arch/powerpc/tools/relocs/code-patching.h @@ -0,0 +1,7 @@ +#ifndef __CODE_PATCHING_H__ +#define __CODE_PATCHING_H__ + +int set_branch_target(uint32_t *insn, + const uint64_t addr, uint64_t target); + +#endif diff --git a/arch/powerpc/tools/relocs/elf_sections.c b/arch/powerpc/tools/relocs/elf_sections.c new file mode 100644 index 000000000000..718020d93394 --- /dev/null +++ b/arch/powerpc/tools/relocs/elf_sections.c @@ -0,0 +1,337 @@ +#define _GNU_SOURCE +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <gelf.h> +#include <elf.h> +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include "elf_sections.h" + +#define dbg_printf(...) + +static const char *rel_type_name(unsigned int type) +{ + static const char *const type_name[] = { +#define REL_TYPE(X)[X] = #X + REL_TYPE(R_PPC64_NONE), + REL_TYPE(R_PPC64_ADDR32), + REL_TYPE(R_PPC64_ADDR24), + REL_TYPE(R_PPC64_ADDR16), + REL_TYPE(R_PPC64_ADDR16_LO), + REL_TYPE(R_PPC64_ADDR16_HI), + REL_TYPE(R_PPC64_ADDR16_HA), + REL_TYPE(R_PPC64_ADDR14), + REL_TYPE(R_PPC64_ADDR14_BRTAKEN), + REL_TYPE(R_PPC64_ADDR14_BRNTAKEN), + REL_TYPE(R_PPC64_REL24), + REL_TYPE(R_PPC64_REL14), + REL_TYPE(R_PPC64_REL14_BRTAKEN), + REL_TYPE(R_PPC64_REL14_BRNTAKEN), + REL_TYPE(R_PPC64_GOT16), + REL_TYPE(R_PPC64_GOT16_LO), + REL_TYPE(R_PPC64_GOT16_HI), + REL_TYPE(R_PPC64_GOT16_HA), + REL_TYPE(R_PPC64_COPY), + REL_TYPE(R_PPC64_GLOB_DAT), + REL_TYPE(R_PPC64_JMP_SLOT), + REL_TYPE(R_PPC64_RELATIVE), + REL_TYPE(R_PPC64_UADDR32), + REL_TYPE(R_PPC64_UADDR16), + REL_TYPE(R_PPC64_REL32), + REL_TYPE(R_PPC64_PLT32), + REL_TYPE(R_PPC64_PLTREL32), + REL_TYPE(R_PPC64_PLT16_LO), + REL_TYPE(R_PPC64_PLT16_HI), + REL_TYPE(R_PPC64_PLT16_HA), + REL_TYPE(R_PPC64_SECTOFF), + REL_TYPE(R_PPC64_SECTOFF_LO), + REL_TYPE(R_PPC64_SECTOFF_HI), + REL_TYPE(R_PPC64_SECTOFF_HA), + REL_TYPE(R_PPC64_ADDR30), + REL_TYPE(R_PPC64_ADDR64), + REL_TYPE(R_PPC64_ADDR16_HIGHER), + REL_TYPE(R_PPC64_ADDR16_HIGHERA), + REL_TYPE(R_PPC64_ADDR16_HIGHEST), + REL_TYPE(R_PPC64_ADDR16_HIGHESTA), + REL_TYPE(R_PPC64_UADDR64), + REL_TYPE(R_PPC64_REL64), + REL_TYPE(R_PPC64_PLT64), + REL_TYPE(R_PPC64_PLTREL64), + REL_TYPE(R_PPC64_TOC16), + REL_TYPE(R_PPC64_TOC16_LO), + REL_TYPE(R_PPC64_TOC16_HI), + REL_TYPE(R_PPC64_TOC16_HA), + REL_TYPE(R_PPC64_TOC), + REL_TYPE(R_PPC64_PLTGOT16), + REL_TYPE(R_PPC64_PLTGOT16_LO), + REL_TYPE(R_PPC64_PLTGOT16_HI), + REL_TYPE(R_PPC64_PLTGOT16_HA), + REL_TYPE(R_PPC64_ADDR16_DS), + REL_TYPE(R_PPC64_ADDR16_LO_DS), + REL_TYPE(R_PPC64_GOT16_DS), + REL_TYPE(R_PPC64_GOT16_LO_DS), + REL_TYPE(R_PPC64_PLT16_LO_DS), + REL_TYPE(R_PPC64_SECTOFF_DS), + REL_TYPE(R_PPC64_SECTOFF_LO_DS), + REL_TYPE(R_PPC64_TOC16_DS), + REL_TYPE(R_PPC64_TOC16_LO_DS), + REL_TYPE(R_PPC64_PLTGOT16_DS), + REL_TYPE(R_PPC64_PLTGOT16_LO_DS), + REL_TYPE(R_PPC64_TLS), + REL_TYPE(R_PPC64_DTPMOD64), + REL_TYPE(R_PPC64_TPREL16), + REL_TYPE(R_PPC64_TPREL16_LO), + REL_TYPE(R_PPC64_TPREL16_HI), + REL_TYPE(R_PPC64_TPREL16_HA), + REL_TYPE(R_PPC64_TPREL64), + REL_TYPE(R_PPC64_DTPREL16), + REL_TYPE(R_PPC64_DTPREL16_LO), + REL_TYPE(R_PPC64_DTPREL16_HI), + REL_TYPE(R_PPC64_DTPREL16_HA), + REL_TYPE(R_PPC64_DTPREL64), + REL_TYPE(R_PPC64_GOT_TLSGD16), + REL_TYPE(R_PPC64_GOT_TLSGD16_LO), + REL_TYPE(R_PPC64_GOT_TLSGD16_HI), + REL_TYPE(R_PPC64_GOT_TLSGD16_HA), + REL_TYPE(R_PPC64_GOT_TLSLD16), + REL_TYPE(R_PPC64_GOT_TLSLD16_LO), + REL_TYPE(R_PPC64_GOT_TLSLD16_HI), + REL_TYPE(R_PPC64_GOT_TLSLD16_HA), + REL_TYPE(R_PPC64_GOT_TPREL16_DS), + REL_TYPE(R_PPC64_GOT_TPREL16_LO_DS), + REL_TYPE(R_PPC64_GOT_TPREL16_HI), + REL_TYPE(R_PPC64_GOT_TPREL16_HA), + REL_TYPE(R_PPC64_GOT_DTPREL16_DS), + REL_TYPE(R_PPC64_GOT_DTPREL16_LO_DS), + REL_TYPE(R_PPC64_GOT_DTPREL16_HI), + REL_TYPE(R_PPC64_GOT_DTPREL16_HA), + REL_TYPE(R_PPC64_TPREL16_DS), + REL_TYPE(R_PPC64_TPREL16_LO_DS), + REL_TYPE(R_PPC64_TPREL16_HIGHER), + REL_TYPE(R_PPC64_TPREL16_HIGHERA), + REL_TYPE(R_PPC64_TPREL16_HIGHEST), + REL_TYPE(R_PPC64_TPREL16_HIGHESTA), + REL_TYPE(R_PPC64_DTPREL16_DS), + REL_TYPE(R_PPC64_DTPREL16_LO_DS), + REL_TYPE(R_PPC64_DTPREL16_HIGHER), + REL_TYPE(R_PPC64_DTPREL16_HIGHERA), + REL_TYPE(R_PPC64_DTPREL16_HIGHEST), + REL_TYPE(R_PPC64_DTPREL16_HIGHESTA), + REL_TYPE(R_PPC64_TLSGD), + REL_TYPE(R_PPC64_TLSLD), + REL_TYPE(R_PPC64_TOCSAVE), +/* REL_TYPE(R_PPC64_ENTRY), */ + REL_TYPE(R_PPC64_REL16), + REL_TYPE(R_PPC64_REL16_LO), + REL_TYPE(R_PPC64_REL16_HI), + REL_TYPE(R_PPC64_REL16_HA), +#undef REL_TYPE + }; + const char *name = "UNKNOWN"; + + if (type < sizeof(type_name) / sizeof(typeof(type_name[0])) && type_name[type]) + name = type_name[type]; + return name; +} + +static struct section *get_section(struct elf *elf, Elf_Scn *scn) +{ + struct section *section; + + section = malloc(sizeof(struct section)); + + section->scn = scn; + + if (gelf_getshdr(scn, §ion->shdr) == NULL) { + fprintf(stderr, "gelf_getshdr failed: %s\n", elf_errmsg(-1)); + exit(EXIT_FAILURE); + } + + section->name = elf_strptr(elf->elf, elf->shstrndx, section->shdr.sh_name); + if (section->name == NULL) { + fprintf(stderr, "gelf_strptr failed: %s\n", elf_errmsg(-1)); + exit(EXIT_FAILURE); + } + + section->data = elf_getdata(scn, NULL); + if (section->data) { + assert(elf_getdata(scn, section->data) == NULL); + } + + section->symtab = NULL; + if (section->shdr.sh_type == SHT_SYMTAB) + goto no_symtab; + if (section->shdr.sh_type == SHT_DYNSYM) + goto no_symtab; + if (section->shdr.sh_type == SHT_DYNAMIC) + goto no_symtab; + + /* printf("symtab index:%d\n", elf_scnshndx(scn)); ??? */ + if (section->shdr.sh_link) { + Elf_Scn *link_scn; + + link_scn = elf_getscn(elf->elf, section->shdr.sh_link); + section->symtab = get_section(elf, link_scn); + + assert(section->symtab->shdr.sh_type == SHT_SYMTAB || + section->symtab->shdr.sh_type == SHT_DYNSYM); + } + +no_symtab: + section->strtab = NULL; + if (section->symtab == NULL) { + if (section->shdr.sh_link) { + Elf_Scn *link_scn; + + link_scn = elf_getscn(elf->elf, section->shdr.sh_link); + section->strtab = get_section(elf, link_scn); + + assert(section->strtab->shdr.sh_type == SHT_STRTAB); + } + } + + return section; +} + +struct symbol *elf_sections_get_symbol(struct elf *elf, struct section *section, unsigned long nr) +{ + struct symbol *symbol; + Elf_Scn *scn; + + symbol = malloc(sizeof(struct symbol)); + + if (gelf_getsym(section->symtab->data, nr, &symbol->sym) == NULL) { + fprintf(stderr, "gelf_getsym failed: %s\n", elf_errmsg(-1)); + exit(EXIT_FAILURE); + } + + scn = elf_getscn(elf->elf, symbol->sym.st_shndx); + symbol->section = get_section(elf, scn); + symbol->_name = symbol->section->name; + if (symbol->sym.st_name) { + symbol->name = elf_strptr(elf->elf, elf_ndxscn(section->symtab->strtab->scn), symbol->sym.st_name); + symbol->_name = symbol->name; + } else { + symbol->name = NULL; + } + + return symbol; +} + +struct relocation *elf_sections_get_reloc(struct elf *elf, struct section *section, size_t n) +{ + struct relocation *relocation; + + relocation = malloc(sizeof(struct relocation)); + + if (section->shdr.sh_type == SHT_REL) { + if (gelf_getrel(section->data, n, &relocation->rel) != &relocation->rel) { + return NULL; + } + + relocation->type_name = rel_type_name(GELF_R_TYPE(relocation->rel.r_info)); + relocation->symbol = elf_sections_get_symbol(elf, section, GELF_R_SYM(relocation->rel.r_info)); + relocation->offset = relocation->rel.r_offset; + relocation->target = relocation->symbol->sym.st_value; + + } else if (section->shdr.sh_type == SHT_RELA) { + if (gelf_getrela(section->data, n, &relocation->rela) != &relocation->rela) { + return NULL; + } + + relocation->type_name = rel_type_name(GELF_R_TYPE(relocation->rela.r_info)); + relocation->symbol = elf_sections_get_symbol(elf, section, GELF_R_SYM(relocation->rela.r_info)); + relocation->offset = relocation->rela.r_offset; + relocation->target = relocation->symbol->sym.st_value; + relocation->target += relocation->rela.r_addend; + + } else { + assert(0); + } + + return relocation; +} + +struct elf *elf_sections_init(int fd) +{ + struct elf *elf; + Elf_Scn *scn; + + elf = malloc(sizeof(struct elf)); + assert(elf); + + if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr, "libelf not initialized: %s\n", elf_errmsg(-1)); + exit(EXIT_FAILURE); + } + + if ((elf->elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { + fprintf(stderr, "elf_begin failed: %s\n", elf_errmsg(-1)); + exit(EXIT_FAILURE); + } + + if (elf_kind(elf->elf) != ELF_K_ELF) { + fprintf(stderr, "Not an ELF object.\n"); + exit(EXIT_FAILURE); + } + + if (gelf_getehdr(elf->elf, &elf->ehdr) == NULL) { + fprintf(stderr, "gelf_getehdr failed: %s\n", elf_errmsg(-1)); + exit(EXIT_FAILURE); + } + + if (elf->ehdr.e_version != EV_CURRENT) { + fprintf(stderr, "Unknown ELF version\n"); + exit(EXIT_FAILURE); + } + + if (elf->ehdr.e_machine != EM_PPC && elf->ehdr.e_machine != EM_PPC64) { + fprintf(stderr, "Not a PPC/PPC64 machine\n"); + exit(EXIT_FAILURE); + } + + if (elf_getshdrstrndx(elf->elf, &elf->shstrndx) != 0) { + fprintf(stderr, "elf_getshdrstrndx failed: %s\n", elf_errmsg(-1)); + exit(EXIT_FAILURE); + } + + scn = elf_getscn(elf->elf, elf->shstrndx); + elf->strtab = get_section(elf, scn); + assert(elf->strtab->shdr.sh_type == SHT_STRTAB); + + return elf; +} + +void elf_sections_exit(struct elf *elf) +{ + elf_end(elf->elf); +} + +int elf_sections_processor(struct elf *elf, + int (*fn)(struct section *section, void *arg), + void *arg) +{ + Elf_Scn *scn; + int err; + + scn = NULL ; + while ((scn = elf_nextscn(elf->elf, scn)) != NULL) { + struct section *section; + + section = get_section(elf, scn); + + err = fn(section, arg); + if (err) + return err; + } + + return 0; +} diff --git a/arch/powerpc/tools/relocs/elf_sections.h b/arch/powerpc/tools/relocs/elf_sections.h new file mode 100644 index 000000000000..c3bc744efca8 --- /dev/null +++ b/arch/powerpc/tools/relocs/elf_sections.h @@ -0,0 +1,50 @@ +#ifndef __ELF_SECTIONS_H__ +#define __ELF_SECTIONS_H__ + +#include <gelf.h> +#include <elf.h> + +struct section { + Elf_Scn *scn; + GElf_Shdr shdr; + const char *name; + Elf_Data *data; + + struct section *symtab; + struct section *strtab; +}; + +struct symbol { + GElf_Sym sym; + struct section *section; + const char *name; + const char *_name; +}; + +struct relocation { + GElf_Rel rel; + GElf_Rela rela; + + const char *type_name; + struct symbol *symbol; + + uint64_t offset; + uint64_t target; +}; + +struct elf { + Elf *elf; + GElf_Ehdr ehdr; + size_t shstrndx; + struct section *strtab; +}; + +struct symbol *elf_sections_get_symbol(struct elf *elf, struct section *section, unsigned long nr); +struct relocation *elf_sections_get_reloc(struct elf *elf, struct section *section, size_t n); +struct elf *elf_sections_init(int fd); +void elf_sections_exit(struct elf *elf); +int elf_sections_processor(struct elf *elf, + int (*fn)(struct section *section, void *arg), + void *arg); + +#endif diff --git a/arch/powerpc/tools/relocs/process_relocs.c b/arch/powerpc/tools/relocs/process_relocs.c new file mode 100644 index 000000000000..a678179cfd0b --- /dev/null +++ b/arch/powerpc/tools/relocs/process_relocs.c @@ -0,0 +1,718 @@ +#define _GNU_SOURCE +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <gelf.h> +#include <elf.h> +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <asm/byteorder.h> +#include "elf_sections.h" +#include "code-patching.h" + +/* + * This program runs through relocation data in PPC/PPC64 vmlinux ELF + * image generated with --emit-relocs, and performs some processing and + * checks. + * + * Presently, it has the following functions: + * 1. Fix relocations for branches inside alternate feature sections + * (the "else" patches), so that they are correct for their destination + * address. They never get executed at their linked location. + * + * This is done by parsing all fixup_entry structures in the _ftr_fixup + * sections, and keeping those with non-zero alternate patch. Then all + * relocations in the .__ftr_alternates.text section are parsed, and those + * matching addresses in our fixup_entry alternates patches get + * struct insn_patch created for them. Finally, all struct insn_patch'es + * are iterated and written to the image in-place. Care needs to be taken + * with nested fixups, see check_and_flatten_fixup_entries(). + * + * 2. Check that no __ex_table or __bug_table entries point to alternate + * sections. We don't support that at present. + */ + +#define dbg_printf(...) + +static int is_kernel = 0; + +struct fixup_entry_64 { + uint64_t mask; + uint64_t value; + uint64_t start_off; + uint64_t end_off; + uint64_t alt_start_off; + uint64_t alt_end_off; +} __attribute__((packed)); + +#define fixup_entry fixup_entry_64 + +struct fixup_entry_32 { + uint32_t mask; + uint32_t value; + uint32_t start_off; + uint32_t end_off; + uint32_t alt_start_off; + uint32_t alt_end_off; +} __attribute__((packed)); + +struct exception_entry_64 { + int32_t insn; + int32_t fixup; +}; + +struct exception_entry_32 { + uint32_t insn; + uint32_t fixup; +}; + +struct bug_entry_64 { + uint64_t bug_addr; +#ifdef CONFIG_DEBUG_BUGVERBOSE + uint64_t file; + uint16_t line; +#endif + uint16_t flags; +}; + +struct bug_entry_32 { + uint32_t bug_addr; +#ifdef CONFIG_DEBUG_BUGVERBOSE + uint32_t file; + uint16_t line; +#endif + uint16_t flags; +}; + +struct insn_patch { + uint32_t insn; /* New instruction */ + off_t offset; /* Image location to patch */ +}; + +static int is_64bit(struct elf *elf) +{ + return elf->ehdr.e_ident[EI_CLASS] == ELFCLASS64; +} + +static int is_32bit(struct elf *elf) +{ + return elf->ehdr.e_ident[EI_CLASS] == ELFCLASS32; +} + +static int is_le(struct elf *elf) +{ + return elf->ehdr.e_ident[EI_DATA] == ELFDATA2LSB; +} + +static int is_be(struct elf *elf) +{ + return elf->ehdr.e_ident[EI_DATA] == ELFDATA2MSB; +} + + +static struct elf *elf; + +static uint16_t f16_to_cpu(uint16_t val) +{ + if (is_le(elf)) + return __le16_to_cpu(val); + else + return __be16_to_cpu(val); +} + +static uint32_t f32_to_cpu(uint32_t val) +{ + if (is_le(elf)) + return __le32_to_cpu(val); + else + return __be32_to_cpu(val); +} + +static uint64_t f64_to_cpu(uint64_t val) +{ + if (is_le(elf)) + return __le64_to_cpu(val); + else + return __be64_to_cpu(val); +} + +static uint16_t cpu_to_f16(uint16_t val) +{ + if (is_le(elf)) + return __cpu_to_le16(val); + else + return __cpu_to_be16(val); +} + +static uint32_t cpu_to_f32(uint32_t val) +{ + if (is_le(elf)) + return __cpu_to_le32(val); + else + return __cpu_to_be32(val); +} + +static uint64_t cpu_to_f64(uint64_t val) +{ + if (is_le(elf)) + return __cpu_to_le64(val); + else + return __cpu_to_be64(val); +} + +static struct section *ftr_alt; + +static unsigned int nr_fes = 0; +static struct fixup_entry *fes = NULL; + +/* Could grab the start/end of .__ftr_alternates.text directly */ +static uint64_t fe_alt_start = -1; +static uint64_t fe_alt_end = 0; + +static struct fixup_entry *find_fe_altaddr(uint64_t addr) +{ + unsigned int i; + + if (addr < fe_alt_start) + return NULL; + if (addr >= fe_alt_end) + return NULL; + + for (i = 0; i < nr_fes; i++) { + if (addr >= fes[i].alt_start_off && addr < fes[i].alt_end_off) + return &fes[i]; + } + return NULL; +} + +/* + * Fixup entries can be nested, so we need to find the final address. The + * "if" side never needs to be fixed -- if it's nested inside an "else" part, + * it will get fixed as part of the fixup for that else part. "else" nested + * in "else" needs to be fixed -- possibly multiple nestings. + * + * The inner-most nestings always come first, so we traverse the array + * backward, fixing up destination of nested parts according to their + * parent, which takes care of multiple nestings without further work. + * + * We can also do some sanity checks here. + */ +static void check_and_flatten_fixup_entries(void) +{ + static struct fixup_entry *fe; + unsigned int i; + + i = nr_fes; + while (i) { + static struct fixup_entry *parent; + uint64_t nested_off; /* offset from start of parent */ + uint64_t size; + + i--; + fe = &fes[i]; + + parent = find_fe_altaddr(fe->start_off); + if (!parent) { + parent = find_fe_altaddr(fe->end_off); + assert(!parent); /* Should be entirely contained */ + continue; + } + + size = fe->end_off - fe->start_off; + nested_off = fe->start_off - parent->alt_start_off; + + dbg_printf("flattening child fixup entry [%lx-%lx]->[%lx-%lx] to parent [%lx-%lx]->[%lx-%lx] new child [%lx-%lx]->[%lx-%lx]\n", fe->alt_start_off, fe->alt_end_off, fe->start_off, fe->end_off, parent->alt_start_off, parent->alt_end_off, parent->start_off, parent->end_off, fe->alt_start_off, fe->alt_end_off, parent->start_off + nested_off, parent->start_off + nested_off + size); + + fe->start_off = parent->start_off + nested_off; + fe->end_off = fe->start_off + size; + } + +} + +static unsigned int nr_ips = 0; +static struct insn_patch *ips = NULL; + +static void create_branch_patch(struct relocation *relocation, struct fixup_entry *fe) +{ + struct insn_patch *ip; + uint64_t addr = relocation->offset; + uint64_t dst_addr; + uint64_t scn_delta; + uint64_t offset; + uint32_t insn; + uint32_t *i; + + assert(addr >= ftr_alt->shdr.sh_addr && + addr < ftr_alt->shdr.sh_addr + ftr_alt->shdr.sh_size); + + scn_delta = addr - ftr_alt->shdr.sh_addr; + + assert(scn_delta < ftr_alt->data->d_size); + + i = ftr_alt->data->d_buf + scn_delta; + + insn = f32_to_cpu(*i); + + offset = ftr_alt->shdr.sh_offset + scn_delta; + dst_addr = addr - fe->alt_start_off + fe->start_off; + + if (set_branch_target(&insn, dst_addr, relocation->target)) { + fprintf(stderr, "ftr_alt branch target out of range or not a branch. address=%llx\n", (unsigned long long)addr); + exit(EXIT_FAILURE); + } + + if (insn == *i) /* Nothing to do */ + return; + + ips = realloc(ips, (nr_ips + 1) * sizeof(struct insn_patch)); + ip = &ips[nr_ips]; + nr_ips++; + + ip->insn = insn; + ip->offset = offset; + + dbg_printf("update branch insn (%x->%x)\n", *i, ip->insn); +} + +static int process_alt_data(struct section *section, void *arg) +{ + if (strcmp(section->name, ".__ftr_alternates.text") != 0) + return 0; + + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); + assert(section->shdr.sh_type == SHT_PROGBITS); + + ftr_alt = section; + + return 0; +} + +static int process_fixup_entries(struct section *section, void *arg) +{ + Elf_Data *data; + unsigned int nr, i; + + if (strstr(section->name, "_ftr_fixup") == 0) + return 0; + + if (section->shdr.sh_type != SHT_PROGBITS) + return 0; + + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); + + data = section->data; + assert(data); + assert(data->d_size > 0); + + if (is_64bit(elf)) { + assert(data->d_size % sizeof(struct fixup_entry_64) == 0); + nr = data->d_size / sizeof(struct fixup_entry_64); + } else { + assert(data->d_size % sizeof(struct fixup_entry_32) == 0); + nr = data->d_size / sizeof(struct fixup_entry_32); + } + + for (i = 0; i < nr; i++) { + struct fixup_entry *dst; + unsigned long idx; + unsigned long long off; + + if (is_64bit(elf)) { + struct fixup_entry_64 *src; + + idx = i * sizeof(struct fixup_entry_64); + + off = section->shdr.sh_addr + data->d_off + idx; + src = data->d_buf + idx; + + if (src->alt_start_off == src->alt_end_off) + continue; + + fes = realloc(fes, (nr_fes + 1) * sizeof(struct fixup_entry)); + dst = &fes[nr_fes]; + nr_fes++; + + dst->mask = f64_to_cpu(src->mask); + dst->value = f64_to_cpu(src->value); + dst->start_off = f64_to_cpu(src->start_off) + off; + dst->end_off = f64_to_cpu(src->end_off) + off; + dst->alt_start_off = f64_to_cpu(src->alt_start_off) + off; + dst->alt_end_off = f64_to_cpu(src->alt_end_off) + off; + + } else { + struct fixup_entry_32 *src; + + idx = i * sizeof(struct fixup_entry_32); + + off = section->shdr.sh_addr + data->d_off + idx; + src = data->d_buf + idx; + + if (src->alt_start_off == src->alt_end_off) + continue; + + fes = realloc(fes, (nr_fes + 1) * sizeof(struct fixup_entry)); + dst = &fes[nr_fes]; + nr_fes++; + + dst->mask = f32_to_cpu(src->mask); + dst->value = f32_to_cpu(src->value); + dst->start_off = f32_to_cpu(src->start_off) + off; + dst->end_off = f32_to_cpu(src->end_off) + off; + dst->alt_start_off = f32_to_cpu(src->alt_start_off) + off; + dst->alt_end_off = f32_to_cpu(src->alt_end_off) + off; + + } + if (dst->alt_start_off < fe_alt_start) + fe_alt_start = dst->alt_start_off; + if (dst->alt_end_off > fe_alt_end) + fe_alt_end = dst->alt_end_off; + + dbg_printf("%llx fixup entry %llx:%llx (%llx-%llx) <- (%llx-%llx)\n", off, + (unsigned long long)dst->mask, (unsigned long long)dst->value, + (unsigned long long)dst->start_off, (unsigned long long)dst->end_off, + (unsigned long long)dst->alt_start_off, (unsigned long long)dst->alt_end_off); + } + + return 0; +} + +/* + * Check no exceptions are in "alt" sections. We don't relocate them as + * yet. + */ +static int process_exception_entries(struct section *section, void *arg) +{ + Elf_Data *data; + unsigned int nr, i; + + if (strcmp(section->name, "__ex_table") != 0) + return 0; + + if (section->shdr.sh_type != SHT_PROGBITS) + return 0; + + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); + + data = section->data; + assert(data); + assert(data->d_size > 0); + + if (is_64bit(elf)) { + assert(data->d_size % sizeof(struct exception_entry_64) == 0); + nr = data->d_size / sizeof(struct exception_entry_64); + } else { + assert(data->d_size % sizeof(struct exception_entry_32) == 0); + nr = data->d_size / sizeof(struct exception_entry_32); + } + + for (i = 0; i < nr; i++) { + unsigned long idx; + uint64_t exaddr; + unsigned long long off; + + if (is_64bit(elf)) { + struct exception_entry_64 *ex; + + idx = i * sizeof(struct exception_entry_64); + + off = section->shdr.sh_addr + data->d_off + idx; + + ex = data->d_buf + idx; + + exaddr = f32_to_cpu(ex->insn) + off; + + } else { + struct exception_entry_32 *ex; + + idx = i * sizeof(struct exception_entry_32); + + ex = data->d_buf + idx; + + exaddr = f32_to_cpu(ex->insn); + } + + dbg_printf("exception addr:%llx\n", exaddr); + + if (exaddr < fe_alt_start) + continue; + if (exaddr >= fe_alt_end) + continue; + + /* + * This would be the place to create exception address patches + * if we want to support that feature + */ + fprintf(stderr, "ftr_alt code contains an exception entry, which is not allowed. address=%llx\n", (unsigned long long)exaddr); + exit(EXIT_FAILURE); + } + + return 0; +} + +/* + * Check no exceptions are in "alt" sections. We don't relocate them as + * yet. + */ +static int process_bug_entries(struct section *section, void *arg) +{ + Elf_Data *data; + unsigned int nr, i; + + if (strcmp(section->name, "__bug_table") != 0) + return 0; + + if (section->shdr.sh_type != SHT_PROGBITS) + return 0; + + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); + + data = section->data; + assert(data); + assert(data->d_size > 0); + + if (is_64bit(elf)) { + assert(data->d_size % sizeof(struct bug_entry_64) == 0); + nr = data->d_size / sizeof(struct bug_entry_64); + } else { + assert(data->d_size % sizeof(struct bug_entry_32) == 0); + nr = data->d_size / sizeof(struct bug_entry_32); + } + + for (i = 0; i < nr; i++) { + unsigned long idx; + uint64_t bugaddr; + + if (is_64bit(elf)) { + struct bug_entry_64 *bug; + + idx = i * sizeof(struct bug_entry_64); + + bug = data->d_buf + idx; + + bugaddr = f64_to_cpu(bug->bug_addr); + + } else { + struct bug_entry_32 *bug; + + idx = i * sizeof(struct bug_entry_32); + + bug = data->d_buf + idx; + + bugaddr = f32_to_cpu(bug->bug_addr); + } + + dbg_printf("bug addr:%llx\n", bugaddr); + + if (bugaddr < fe_alt_start) + continue; + if (bugaddr >= fe_alt_end) + continue; + + /* + * This would be the place to create bug address patches if + * we want to support that feature + */ + fprintf(stderr, "ftr_alt code contains a BUG entry, which is not allowed. address=%llx\n", (unsigned long long)bugaddr); + /* Could print file, line here for verbose case */ + exit(EXIT_FAILURE); + } + + return 0; +} + + +static int process_alt_relocations(struct section *section, void *arg) +{ + struct relocation *relocation; + size_t n; + + if (strcmp(section->name, ".rela.__ftr_alternates.text") != 0) + return 0; + + assert(section->shdr.sh_type == SHT_RELA); + + dbg_printf("section %-4.4jd %s\n", (uintmax_t)elf_ndxscn(section->scn), section->name); + + n = 0; + while ((relocation = elf_sections_get_reloc(elf, section, n)) != NULL) { + struct fixup_entry *fe; + + n++; + + dbg_printf("%llx %s %s %llx + %llx\n", + (unsigned long long)relocation->offset, + relocation->type_name, + relocation->symbol->_name, + (unsigned long long)relocation->symbol->sym.st_value, + (unsigned long long)relocation->rela.r_addend); + + fe = find_fe_altaddr(relocation->offset); + if (fe) { + dbg_printf("reloc has fe %llx:%llx (%llx-%llx) <- (%llx-%llx)\n", + (unsigned long long)fe->mask, + (unsigned long long)fe->value, + (unsigned long long)fe->start_off, + (unsigned long long)fe->end_off, + (unsigned long long)fe->alt_start_off, + (unsigned long long)fe->alt_end_off); + + if (relocation->target >= fe->alt_start_off && + relocation->target < fe->alt_end_off) { + dbg_printf(" reloc within patch code\n"); + continue; + } + + /* + * We really should check for all branches either side + * of fixup_entry from outside (including within + * different fixup code). It's almost guaranteed to go + * badly. Not just relocations, but branches too, + * because nearby branches might get resolved without + * a relocation. + */ + if (relocation->target >= ftr_alt->shdr.sh_addr && + relocation->target < ftr_alt->shdr.sh_addr + + ftr_alt->shdr.sh_size) { + fprintf(stderr, "ftr_alt branch target is another ftr_alt region, which is not allowed. address=%llx\n", (unsigned long long)relocation->offset); + exit(EXIT_FAILURE); + } + + /* + * Resolved module symbols should work. Unresolved + * ones would need their relocations fixed in the + * same manner as instructions are fixed. + */ + if (!is_kernel) { + fprintf(stderr, "module code with alt feature relocations is currently not supported\n"); + exit(EXIT_FAILURE); + } + + create_branch_patch(relocation, fe); + } else { + dbg_printf(" reloc has no fe\n"); + } + } + + return 0; +} + + +int main(int argc, char *argv[]) +{ + int fd; + int err; + unsigned int i; + struct stat stat; + void *mem; + int opt; + + if (argc != 2) + + while ((opt = getopt(argc, argv, "k")) != -1) { + switch (opt) { + case 'k': + is_kernel = 1; + break; + default: + exit(EXIT_FAILURE); + } + } + + if (!strcmp(argv[optind], "vmlinux")) + is_kernel = 1; + + if (optind >= argc || optind + 1 < argc) + exit(EXIT_FAILURE); + + fd = open(argv[optind], O_RDONLY, 0); + if (fd == -1) { + fprintf(stderr, "open %s failed: %s\n", argv[1], strerror(errno)); + exit(EXIT_FAILURE); + } + + elf = elf_sections_init(fd); + + err = elf_sections_processor(elf, process_alt_data, NULL); + assert(!err); + + /* Gather and massage the fixup entries */ + err = elf_sections_processor(elf, process_fixup_entries, NULL); + assert(!err); + + check_and_flatten_fixup_entries(); + + /* We don't handle module relocations for these symbols as yet */ + if (is_kernel) { + /* Sanity checking */ + err = elf_sections_processor(elf, process_exception_entries, NULL); + assert(!err); + + err = elf_sections_processor(elf, process_bug_entries, NULL); + assert(!err); + } + + /* Check the relocations and create necessary instruction patches */ + err = elf_sections_processor(elf, process_alt_relocations, NULL); + assert(!err); + + /* Done with analysis phase */ + + elf_sections_exit(elf); + + if (close(fd) == -1) { + fprintf(stderr, "close %s failed: %s\n", argv[1], strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!nr_ips) { + dbg_printf("Nothing to do.\n"); + exit(EXIT_SUCCESS); + } + + /* Now apply the instruction patches by writing to the file */ + + dbg_printf("%u instructions to patch.\n", nr_ips); + + fd = open(argv[1], O_RDWR, 0); + if (fd == -1) { + fprintf(stderr, "open %s failed: %s\n", argv[1], strerror(errno)); + exit(EXIT_FAILURE); + } + + if (fstat(fd, &stat) == -1) { + perror("stat"); + exit(EXIT_FAILURE); + } + + mem = mmap(0, stat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (mem == MAP_FAILED) { + perror("mmap"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < nr_ips; i++) { + struct insn_patch *ip = &ips[i]; + + assert(ip->offset < stat.st_size); + *(uint32_t *)(mem + ip->offset) = ip->insn; + } + + if (munmap(mem, stat.st_size) == -1) { + perror("mmap"); + exit(EXIT_FAILURE); + } + + if (close(fd) == -1) { + fprintf(stderr, "close %s failed: %s\n", argv[1], strerror(errno)); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +}
Implement build-time fixup of alternate feature relative addresses for the out-of-line ("else") patch code. This is done post-link with a new powerpc build tool that parses relocations and fixup structures, and adjusts branch instructions. This gives us the ability to link patch code anywhere in the kernel, without branches to targets outside the patch code having to be reached directly (without a linker stub). This allows patch code to be moved out from the head section, and avoids build failures with unresolvable branche. The downside is increased complexity of the build steps. Currently: - The patch code must be linked with branch targets reached directly. This is done by omitting the "x" attribute from the patch code's section, which suppresses branch stub creation. Instead, a link error is raised if a branch cannot reach its target directly. - The runtime patcher adjusts external branch targets to compensate for the distance moved (from link to runtime location). After this change: - The patch code is linked with branch stubs allowed, by using the "x" section attribute. This allows flexibility in patch code placement. - The final link is made with --emit-relocs, which emits relocations for these branches. - After link, a tool fixes patch code external branches to be be a direct branch to target, relative to their runtime location. Any branch stubs in the patch code section are ignored. - After feature fixup, newer toolchains can strip off the additional relocations. - The runtime patcher can move the patch code into its runtime location with no branch adjustment. - After patching, original patch code can be discarded from runtime image. Signed-off-by: Nicholas Piggin <npiggin@gmail.com> --- I'm reposting this with some fixes required to work with current kernels (relative exception tables). I think it's probably the right way to go in the long term, it gives a lot of control over instruction patching to have a build-time pass. It is more complexity though, so I don't know whether it would be better to wait until we hit more problems with the existing system first. arch/powerpc/Makefile | 8 +- arch/powerpc/Makefile.postlink | 24 +- arch/powerpc/include/asm/feature-fixups.h | 5 +- arch/powerpc/kernel/vmlinux.lds.S | 8 +- arch/powerpc/lib/feature-fixups.c | 19 +- arch/powerpc/tools/Makefile | 3 + arch/powerpc/tools/relocs/.gitignore | 1 + arch/powerpc/tools/relocs/Makefile | 12 + arch/powerpc/tools/relocs/code-patching.c | 82 ++++ arch/powerpc/tools/relocs/code-patching.h | 7 + arch/powerpc/tools/relocs/elf_sections.c | 337 ++++++++++++++ arch/powerpc/tools/relocs/elf_sections.h | 50 ++ arch/powerpc/tools/relocs/process_relocs.c | 718 +++++++++++++++++++++++++++++ 13 files changed, 1253 insertions(+), 21 deletions(-) create mode 100644 arch/powerpc/tools/Makefile create mode 100644 arch/powerpc/tools/relocs/.gitignore create mode 100644 arch/powerpc/tools/relocs/Makefile create mode 100644 arch/powerpc/tools/relocs/code-patching.c create mode 100644 arch/powerpc/tools/relocs/code-patching.h create mode 100644 arch/powerpc/tools/relocs/elf_sections.c create mode 100644 arch/powerpc/tools/relocs/elf_sections.h create mode 100644 arch/powerpc/tools/relocs/process_relocs.c