[PATCH v2 2/2] ARM: allow modules outside of bl range
Ard Biesheuvel
ard.biesheuvel at linaro.org
Mon Nov 24 04:59:23 PST 2014
On 24 November 2014 at 13:55, Ard Biesheuvel <ard.biesheuvel at linaro.org> wrote:
> Loading modules far away from the kernel in memory is problematic
> because the 'bl' instruction only has limited reach, and modules are not
> built with PLTs. Instead of using the -mlong-calls option (which affects
> all compiler emitted bl instructions, but not the ones in assembler),
> this patch allocates some additional space at module load time, and
> populates it with PLT like entries when encountering relocations that
> are out of reach.
>
> This should work with all relocations against symbols exported by the
> kernel, including those resulting from GCC generated function calls for
> ftrace etc.
>
> The module memory size increases by about 5% on average, regardless of
> whether any PLT entries were actually needed. However, due to the page
> based rounding that occurs when allocating module memory, the average
> memory footprint increase is negligible.
>
> This is largely based on the ia64 implementation.
>
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel at linaro.org>
> ---
> Now with support for Thumb-2, and back to using the module area at
> first and switching to the vmalloc area only when needed.
>
Cover letter omitted by accident; changes since v1:
- add Kconfig symbol to enable the feature, defaults to off
- dropped unreachable 'return 0' after BUG()
- split handling of unsupported interworking to avoid lifting those
restrictions by accident
(we could potentially improve interworking handling later by routing
unsupported relocations through the PLT as well)
> Estimation of 5% bloat based on random sample of 46 modules built in
> Thumb-2 mode, using a L1 line size of 64 bytes (see table below).
> Note that there is only a single instance (*) where the size increase
> results in one additional page to be allocated.
>
> MODULE SIZE #PLT PLTSIZE BLOAT
> xfrm6_mode_transport 1264 2 128 11.27%
> seqiv 2628 17 256 10.79%
> lcd 2735 20 256 10.33%
> xfrm6_mode_tunnel 1432 5 128 9.82%
> ctr 2905 19 256 9.66%
> deflate 1513 8 128 9.24%
> md5 1591 4 128 8.75%
> xfrm_ipcomp 3186 21 256 8.74%
> arc4 1606 3 128 8.66%
> xfrm6_mode_beet 1612 4 128 8.63%
> sha1_generic 1640 6 128 8.47%
> tunnel6 1717 8 128 8.06%
> snd_soc_tegra20_spdif 3532 22 256 7.81%
> tunnel4 1822 8 128 7.56%
> exynos_rng 1837 15 128 7.49%
> ipcomp6 1856 10 128 7.41%
> omap3_rom_rng 1877 11 128 7.32%
> rng_core 3761 23 256 7.30%
> cbc 1926 13 128 7.12%
> msm_rng 2052 10 128 6.65%
> hmac 2267 16 128 5.98%
> esp6 4652 27 256 5.82%
> ah6 4785 27 256 5.65%
> authenc 4865 22 256 5.55%
> ip_tunnel 10223 52 512 5.27%
> authencesn 5313 21 256 5.06%
> ccm 5656 27 256 4.74%
> xfrm6_tunnel 2999 11 128 4.46%
> sit 12063 56 512 4.43%
> ansi_cprng 3146 9 128 4.24%
> rt2x00usb 6731 26 256 3.95%
> ip6_tunnel 13977 53 512 3.80%
> brcmutil 3581 10 128 3.71%
> omap_rng 3678 14 128 3.61%
> xfrm_algo 4005 2 128 3.30%
> mip6 4225 15 128 3.12%
> rt2x00lib 27173 56 512 1.92%
> ipv6 219496 330 2688 1.24% (*)
> rt2800usb 12894 15 128 1.00%
> brcmfmac 125129 138 1152 0.93%
> zlib_deflate 14598 2 128 0.88%
> des_generic 16971 2 128 0.76%
> cfg80211 132574 111 896 0.68%
> mac80211 211721 155 1280 0.61%
> rt2800lib 50751 20 256 0.51%
> crc_ccitt 1086 0 0 0.00%
> ---
> arch/arm/Kconfig | 17 +++-
> arch/arm/Makefile | 4 +
> arch/arm/include/asm/module.h | 12 ++-
> arch/arm/kernel/Makefile | 1 +
> arch/arm/kernel/module-plts.c | 182 ++++++++++++++++++++++++++++++++++++++++++
> arch/arm/kernel/module.c | 30 ++++++-
> arch/arm/kernel/module.lds | 4 +
> 7 files changed, 247 insertions(+), 3 deletions(-)
> create mode 100644 arch/arm/kernel/module-plts.c
> create mode 100644 arch/arm/kernel/module.lds
>
> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
> index 89c4b5ccc68d..9edb69ff0d38 100644
> --- a/arch/arm/Kconfig
> +++ b/arch/arm/Kconfig
> @@ -57,7 +57,7 @@ config ARM
> select HAVE_KPROBES if !XIP_KERNEL
> select HAVE_KRETPROBES if (HAVE_KPROBES)
> select HAVE_MEMBLOCK
> - select HAVE_MOD_ARCH_SPECIFIC if ARM_UNWIND
> + select HAVE_MOD_ARCH_SPECIFIC
> select HAVE_OPROFILE if (HAVE_PERF_EVENTS)
> select HAVE_PERF_EVENTS
> select HAVE_PERF_REGS
> @@ -1705,6 +1705,21 @@ config HAVE_ARCH_TRANSPARENT_HUGEPAGE
> config ARCH_WANT_GENERAL_HUGETLB
> def_bool y
>
> +config ARM_MODULE_PLTS
> + bool "Use PLTs to allow module memory to spill over into vmalloc area"
> + depends on MODULES
> + help
> + Allocate PLTs when loading modules so that jumps and calls whose
> + targets are too far away for their relative offsets to be encoded
> + in the instructions themselves can be bounced via veneers in the
> + module's PLT. This allows modules to be allocated in the generic
> + vmalloc area after the dedicated module memory area has been
> + exhausted. The modules will use slightly more memory, but after
> + rounding up to page size, the actual memory footprint is usually
> + the same.
> +
> + Say y if you are getting out of memory errors while loading modules
> +
> source "mm/Kconfig"
>
> config FORCE_MAX_ZONEORDER
> diff --git a/arch/arm/Makefile b/arch/arm/Makefile
> index 034a94904d69..04c43ea7a031 100644
> --- a/arch/arm/Makefile
> +++ b/arch/arm/Makefile
> @@ -19,6 +19,10 @@ LDFLAGS_vmlinux += --be8
> LDFLAGS_MODULE += --be8
> endif
>
> +ifeq ($(CONFIG_ARM_MODULE_PLTS),y)
> +LDFLAGS_MODULE += -T $(srctree)/arch/arm/kernel/module.lds
> +endif
> +
> OBJCOPYFLAGS :=-O binary -R .comment -S
> GZFLAGS :=-9
> #KBUILD_CFLAGS +=-pipe
> diff --git a/arch/arm/include/asm/module.h b/arch/arm/include/asm/module.h
> index ed690c49ef93..dbdf35ccc2d0 100644
> --- a/arch/arm/include/asm/module.h
> +++ b/arch/arm/include/asm/module.h
> @@ -16,11 +16,21 @@ enum {
> ARM_SEC_UNLIKELY,
> ARM_SEC_MAX,
> };
> +#endif
>
> struct mod_arch_specific {
> +#ifdef CONFIG_ARM_UNWIND
> struct unwind_table *unwind[ARM_SEC_MAX];
> -};
> #endif
> +#ifdef CONFIG_ARM_MODULE_PLTS
> + struct elf32_shdr *core_plt;
> + struct elf32_shdr *init_plt;
> + int core_plt_count;
> + int init_plt_count;
> +#endif
> +};
> +
> +u32 get_plt(struct module *mod, unsigned long loc, Elf32_Addr val);
>
> /*
> * Add the ARM architecture version to the version magic string
> diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
> index 38ddd9f83d0e..4c5d1a5982dd 100644
> --- a/arch/arm/kernel/Makefile
> +++ b/arch/arm/kernel/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_CPU_IDLE) += cpuidle.o
> obj-$(CONFIG_ISA_DMA_API) += dma.o
> obj-$(CONFIG_FIQ) += fiq.o fiqasm.o
> obj-$(CONFIG_MODULES) += armksyms.o module.o
> +obj-$(CONFIG_ARM_MODULE_PLTS) += module-plts.o
> obj-$(CONFIG_ARTHUR) += arthur.o
> obj-$(CONFIG_ISA_DMA) += dma-isa.o
> obj-$(CONFIG_PCI) += bios32.o isa.o
> diff --git a/arch/arm/kernel/module-plts.c b/arch/arm/kernel/module-plts.c
> new file mode 100644
> index 000000000000..ff662cfee139
> --- /dev/null
> +++ b/arch/arm/kernel/module-plts.c
> @@ -0,0 +1,182 @@
> +/*
> + * Copyright (C) 2014 Linaro Ltd. <ard.biesheuvel at linaro.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/elf.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <asm/cache.h>
> +#include <asm/opcodes.h>
> +
> +#define PLT_ENT_STRIDE L1_CACHE_BYTES
> +#define PLT_ENT_COUNT (PLT_ENT_STRIDE / sizeof(u32))
> +#define PLT_ENT_SIZE (sizeof(struct plt_entries) / PLT_ENT_COUNT)
> +
> +#ifdef CONFIG_THUMB2_KERNEL
> +#define PLT_ENT_LDR __opcode_to_mem_thumb32(0xf8dff000 | \
> + (PLT_ENT_STRIDE - 4))
> +#else
> +#define PLT_ENT_LDR __opcode_to_mem_arm(0xe59ff000 | \
> + (PLT_ENT_STRIDE - 8))
> +#endif
> +
> +struct plt_entries {
> + u32 ldr[PLT_ENT_COUNT];
> + u32 lit[PLT_ENT_COUNT];
> +};
> +
> +static bool in_init(const struct module *mod, u32 addr)
> +{
> + return addr - (u32)mod->module_init < mod->init_size;
> +}
> +
> +u32 get_plt(struct module *mod, unsigned long loc, Elf32_Addr val)
> +{
> + struct plt_entries *plt, *plt_end;
> + int c, *count;
> +
> + if (in_init(mod, loc)) {
> + plt = (void *)mod->arch.init_plt->sh_addr;
> + plt_end = (void *)plt + mod->arch.init_plt->sh_size;
> + count = &mod->arch.init_plt_count;
> + } else {
> + plt = (void *)mod->arch.core_plt->sh_addr;
> + plt_end = (void *)plt + mod->arch.core_plt->sh_size;
> + count = &mod->arch.core_plt_count;
> + }
> +
> + /* Look for an existing entry pointing to 'val' */
> + for (c = *count; plt < plt_end; c -= PLT_ENT_COUNT, plt++) {
> + int i;
> +
> + if (!c) {
> + /* Populate a new set of entries */
> + *plt = (struct plt_entries){
> + { [0 ... PLT_ENT_COUNT - 1] = PLT_ENT_LDR, },
> + { val, }
> + };
> + ++*count;
> + return (u32)plt->ldr;
> + }
> + for (i = 0; i < PLT_ENT_COUNT; i++) {
> + if (!plt->lit[i]) {
> + plt->lit[i] = val;
> + ++*count;
> + }
> + if (plt->lit[i] == val)
> + return (u32)&plt->ldr[i];
> + }
> + }
> + BUG();
> +}
> +
> +static int duplicate_rel(Elf32_Addr base, const Elf32_Rel *rel, int num,
> + u32 mask)
> +{
> + u32 *loc1, *loc2;
> + int i;
> +
> + for (i = 0; i < num; i++) {
> + if (rel[i].r_info != rel[num].r_info)
> + continue;
> +
> + /*
> + * Identical relocation types against identical symbols can
> + * still result in different PLT entries if the addend in the
> + * place is different. So resolve the target of the relocation
> + * to compare the values.
> + */
> + loc1 = (u32 *)(base + rel[i].r_offset);
> + loc2 = (u32 *)(base + rel[num].r_offset);
> + if (((*loc1 ^ *loc2) & mask) == 0)
> + return 1;
> + }
> + return 0;
> +}
> +
> +/* Count how many PLT entries we may need */
> +static unsigned int count_plts(Elf32_Addr base, const Elf32_Rel *rel, int num)
> +{
> + unsigned int ret = 0;
> + int i;
> +
> + /*
> + * Sure, this is order(n^2), but it's usually short, and not
> + * time critical
> + */
> + for (i = 0; i < num; i++)
> + switch (ELF32_R_TYPE(rel[i].r_info)) {
> + case R_ARM_CALL:
> + case R_ARM_PC24:
> + case R_ARM_JUMP24:
> + if (!duplicate_rel(base, rel, i,
> + __opcode_to_mem_arm(0x00ffffff)))
> + ret++;
> + break;
> + case R_ARM_THM_CALL:
> + case R_ARM_THM_JUMP24:
> + if (!duplicate_rel(base, rel, i,
> + __opcode_to_mem_thumb32(0x07ff2fff)))
> + ret++;
> + }
> + return ret;
> +}
> +
> +int module_frob_arch_sections(Elf_Ehdr *ehdr, Elf_Shdr *sechdrs,
> + char *secstrings, struct module *mod)
> +{
> + unsigned long core_plts = 0, init_plts = 0;
> + Elf32_Shdr *s, *sechdrs_end = sechdrs + ehdr->e_shnum;
> +
> + /*
> + * To store the PLTs, we expand the .text section for core module code
> + * and the .init.text section for initialization code.
> + */
> + for (s = sechdrs; s < sechdrs_end; ++s)
> + if (strcmp(".core.plt", secstrings + s->sh_name) == 0)
> + mod->arch.core_plt = s;
> + else if (strcmp(".init.plt", secstrings + s->sh_name) == 0)
> + mod->arch.init_plt = s;
> +
> + if (!mod->arch.core_plt || !mod->arch.init_plt) {
> + pr_err("%s: sections missing\n", mod->name);
> + return -ENOEXEC;
> + }
> +
> + for (s = sechdrs + 1; s < sechdrs_end; ++s) {
> + const Elf32_Rel *rels = (void *)ehdr + s->sh_offset;
> + int numrels = s->sh_size / sizeof(Elf32_Rel);
> + Elf32_Shdr *dstsec = sechdrs + s->sh_info;
> +
> + if (s->sh_type != SHT_REL)
> + continue;
> +
> + if (strstr(secstrings + s->sh_name, ".init"))
> + init_plts += count_plts(dstsec->sh_addr, rels, numrels);
> + else
> + core_plts += count_plts(dstsec->sh_addr, rels, numrels);
> + }
> +
> + mod->arch.core_plt->sh_type = SHT_NOBITS;
> + mod->arch.core_plt->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
> + mod->arch.core_plt->sh_addralign = L1_CACHE_BYTES;
> + mod->arch.core_plt->sh_size = round_up(core_plts * PLT_ENT_SIZE,
> + sizeof(struct plt_entries));
> + mod->arch.core_plt_count = 0;
> +
> + mod->arch.init_plt->sh_type = SHT_NOBITS;
> + mod->arch.init_plt->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
> + mod->arch.init_plt->sh_addralign = L1_CACHE_BYTES;
> + mod->arch.init_plt->sh_size = round_up(init_plts * PLT_ENT_SIZE,
> + sizeof(struct plt_entries));
> + mod->arch.init_plt_count = 0;
> + pr_debug("%s: core.plt=%x, init.plt=%x\n", __func__,
> + mod->arch.core_plt->sh_size, mod->arch.init_plt->sh_size);
> + return 0;
> +}
> +
> diff --git a/arch/arm/kernel/module.c b/arch/arm/kernel/module.c
> index 16804df4857c..df76daaecd24 100644
> --- a/arch/arm/kernel/module.c
> +++ b/arch/arm/kernel/module.c
> @@ -40,7 +40,12 @@
> #ifdef CONFIG_MMU
> void *module_alloc(unsigned long size)
> {
> - return __vmalloc_node_range(size, 1, MODULES_VADDR, MODULES_END,
> + void *p = __vmalloc_node_range(size, 1, MODULES_VADDR, MODULES_END,
> + GFP_KERNEL, PAGE_KERNEL_EXEC, NUMA_NO_NODE,
> + __builtin_return_address(0));
> + if (!IS_ENABLED(CONFIG_ARM_MODULE_PLTS) || p)
> + return p;
> + return __vmalloc_node_range(size, 1, VMALLOC_START, VMALLOC_END,
> GFP_KERNEL, PAGE_KERNEL_EXEC, NUMA_NO_NODE,
> __builtin_return_address(0));
> }
> @@ -110,6 +115,19 @@ apply_relocate(Elf32_Shdr *sechdrs, const char *strtab, unsigned int symindex,
> offset -= 0x04000000;
>
> offset += sym->st_value - loc;
> +
> + /*
> + * Route through a PLT entry if 'offset' exceeds the
> + * supported range. Note that 'offset + loc + 8'
> + * contains the absolute jump target, i.e.,
> + * @sym + addend, corrected for the +8 PC bias.
> + */
> + if (IS_ENABLED(CONFIG_ARM_MODULE_PLTS) &&
> + (offset <= (s32)0xfe000000 ||
> + offset >= (s32)0x02000000))
> + offset = get_plt(module, loc, offset + loc + 8)
> + - loc - 8;
> +
> if (offset <= (s32)0xfe000000 ||
> offset >= (s32)0x02000000) {
> pr_err("%s: section %u reloc %u sym '%s': relocation %u out of range (%#lx -> %#x)\n",
> @@ -203,6 +221,16 @@ apply_relocate(Elf32_Shdr *sechdrs, const char *strtab, unsigned int symindex,
> offset -= 0x02000000;
> offset += sym->st_value - loc;
>
> + /*
> + * Route through a PLT entry if 'offset' exceeds the
> + * supported range.
> + */
> + if (IS_ENABLED(CONFIG_ARM_MODULE_PLTS) &&
> + (offset <= (s32)0xff000000 ||
> + offset >= (s32)0x01000000))
> + offset = get_plt(module, loc, offset + loc + 4)
> + - loc - 4;
> +
> if (offset <= (s32)0xff000000 ||
> offset >= (s32)0x01000000) {
> pr_err("%s: section %u reloc %u sym '%s': relocation %u out of range (%#lx -> %#x)\n",
> diff --git a/arch/arm/kernel/module.lds b/arch/arm/kernel/module.lds
> new file mode 100644
> index 000000000000..3682fa107918
> --- /dev/null
> +++ b/arch/arm/kernel/module.lds
> @@ -0,0 +1,4 @@
> +SECTIONS {
> + .core.plt : { BYTE(0) }
> + .init.plt : { BYTE(0) }
> +}
> --
> 1.8.3.2
>
More information about the linux-arm-kernel
mailing list