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 <npig...@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);
+}