From ebb5e78cc63417a35254a791de66e1cc84f963cc Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Wed, 21 Oct 2015 09:54:38 +0100 Subject: MIPS: Initial implementation of a VDSO Add an initial implementation of a proper (i.e. an ELF shared library) VDSO. With this commit it does not export any symbols, it only replaces the current signal return trampoline page. A later commit will add user implementations of gettimeofday()/clock_gettime(). To support both new toolchains and old ones which don't generate ABI flags section, we define its content manually and then use a tool (genvdso) to patch up the section to have the correct name and type. genvdso also extracts symbol offsets ({,rt_}sigreturn) needed by the kernel, and generates a C file containing a "struct mips_vdso_image" containing both the VDSO data and these offsets. This C file is compiled into the kernel. On 64-bit kernels we require a different VDSO for each supported ABI, so we may build up to 3 different VDSOs. The VDSO to use is selected by the mips_abi structure. A kernel/user shared data page is created and mapped below the VDSO image. This is currently empty, but will be used by the user time function implementations which are added later. [markos.chandras@imgtec.com: - Add more comments - Move abi detection in genvdso.h since it's the get_symbol function that needs it. - Add an R6 specific way to calculate the base address of VDSO in order to avoid the branch instruction which affects performance. - Do not patch .gnu.attributes since it's not needed for dynamic linking. - Simplify Makefile a little bit. - checkpatch fixes - Restrict VDSO support for binutils < 2.25 for pre-R6 - Include atomic64.h for O32 variant on MIPS64] Signed-off-by: Alex Smith Signed-off-by: Markos Chandras Cc: Matthew Fortune Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/11337/ Signed-off-by: Ralf Baechle --- arch/mips/vdso/.gitignore | 4 + arch/mips/vdso/Makefile | 160 +++++++++++++++++++++++++ arch/mips/vdso/elf.S | 68 +++++++++++ arch/mips/vdso/genvdso.c | 293 +++++++++++++++++++++++++++++++++++++++++++++ arch/mips/vdso/genvdso.h | 187 +++++++++++++++++++++++++++++ arch/mips/vdso/sigreturn.S | 49 ++++++++ arch/mips/vdso/vdso.h | 80 +++++++++++++ arch/mips/vdso/vdso.lds.S | 100 ++++++++++++++++ 8 files changed, 941 insertions(+) create mode 100644 arch/mips/vdso/.gitignore create mode 100644 arch/mips/vdso/Makefile create mode 100644 arch/mips/vdso/elf.S create mode 100644 arch/mips/vdso/genvdso.c create mode 100644 arch/mips/vdso/genvdso.h create mode 100644 arch/mips/vdso/sigreturn.S create mode 100644 arch/mips/vdso/vdso.h create mode 100644 arch/mips/vdso/vdso.lds.S (limited to 'arch/mips/vdso') diff --git a/arch/mips/vdso/.gitignore b/arch/mips/vdso/.gitignore new file mode 100644 index 0000000..5286a7d --- /dev/null +++ b/arch/mips/vdso/.gitignore @@ -0,0 +1,4 @@ +*.so* +vdso-*image.c +genvdso +vdso*.lds diff --git a/arch/mips/vdso/Makefile b/arch/mips/vdso/Makefile new file mode 100644 index 0000000..ef5f348 --- /dev/null +++ b/arch/mips/vdso/Makefile @@ -0,0 +1,160 @@ +# Objects to go into the VDSO. +obj-vdso-y := elf.o gettimeofday.o sigreturn.o + +# Common compiler flags between ABIs. +ccflags-vdso := \ + $(filter -I%,$(KBUILD_CFLAGS)) \ + $(filter -E%,$(KBUILD_CFLAGS)) \ + $(filter -march=%,$(KBUILD_CFLAGS)) +cflags-vdso := $(ccflags-vdso) \ + $(filter -W%,$(filter-out -Wa$(comma)%,$(KBUILD_CFLAGS))) \ + -O2 -g -fPIC -fno-common -fno-builtin -G 0 -DDISABLE_BRANCH_PROFILING \ + $(call cc-option, -fno-stack-protector) +aflags-vdso := $(ccflags-vdso) \ + $(filter -I%,$(KBUILD_CFLAGS)) \ + $(filter -E%,$(KBUILD_CFLAGS)) \ + -D__ASSEMBLY__ -Wa,-gdwarf-2 + +# +# For the pre-R6 code in arch/mips/vdso/vdso.h for locating +# the base address of VDSO, the linker will emit a R_MIPS_PC32 +# relocation in binutils > 2.25 but it will fail with older versions +# because that relocation is not supported for that symbol. As a result +# of which we are forced to disable the VDSO symbols when building +# with < 2.25 binutils on pre-R6 kernels. For more references on why we +# can't use other methods to get the base address of VDSO please refer to +# the comments on that file. +# +ifndef CONFIG_CPU_MIPSR6 + ifeq ($(call ld-ifversion, -gt, 22400000, y),) + $(warning MIPS VDSO requires binutils > 2.24) + obj-vdso-y := $(filter-out gettimeofday.o, $(obj-vdso-y)) + ccflags-vdso += -DDISABLE_MIPS_VDSO + endif +endif + +# VDSO linker flags. +VDSO_LDFLAGS := \ + -Wl,-Bsymbolic -Wl,--no-undefined -Wl,-soname=linux-vdso.so.1 \ + -nostdlib -shared \ + $(call cc-ldoption, -Wl$(comma)--hash-style=sysv) \ + $(call cc-ldoption, -Wl$(comma)--build-id) + +GCOV_PROFILE := n + +# +# Shared build commands. +# + +quiet_cmd_vdsold = VDSO $@ + cmd_vdsold = $(CC) $(c_flags) $(VDSO_LDFLAGS) \ + -Wl,-T $(filter %.lds,$^) $(filter %.o,$^) -o $@ + +hostprogs-y := genvdso + +quiet_cmd_genvdso = GENVDSO $@ +define cmd_genvdso + cp $< $(<:%.dbg=%) && \ + $(OBJCOPY) -S $< $(<:%.dbg=%) && \ + $(obj)/genvdso $< $(<:%.dbg=%) $@ $(VDSO_NAME) +endef + +# +# Build native VDSO. +# + +native-abi := $(filter -mabi=%,$(KBUILD_CFLAGS)) + +targets += $(obj-vdso-y) +targets += vdso.lds vdso.so.dbg vdso.so vdso-image.c + +obj-vdso := $(obj-vdso-y:%.o=$(obj)/%.o) + +$(obj-vdso): KBUILD_CFLAGS := $(cflags-vdso) $(native-abi) +$(obj-vdso): KBUILD_AFLAGS := $(aflags-vdso) $(native-abi) + +$(obj)/vdso.lds: KBUILD_CPPFLAGS := $(native-abi) + +$(obj)/vdso.so.dbg: $(obj)/vdso.lds $(obj-vdso) FORCE + $(call if_changed,vdsold) + +$(obj)/vdso-image.c: $(obj)/vdso.so.dbg $(obj)/genvdso FORCE + $(call if_changed,genvdso) + +obj-y += vdso-image.o + +# +# Build O32 VDSO. +# + +# Define these outside the ifdef to ensure they are picked up by clean. +targets += $(obj-vdso-y:%.o=%-o32.o) +targets += vdso-o32.lds vdso-o32.so.dbg vdso-o32.so vdso-o32-image.c + +ifdef CONFIG_MIPS32_O32 + +obj-vdso-o32 := $(obj-vdso-y:%.o=$(obj)/%-o32.o) + +$(obj-vdso-o32): KBUILD_CFLAGS := $(cflags-vdso) -mabi=32 +$(obj-vdso-o32): KBUILD_AFLAGS := $(aflags-vdso) -mabi=32 + +$(obj)/%-o32.o: $(src)/%.S FORCE + $(call if_changed_dep,as_o_S) + +$(obj)/%-o32.o: $(src)/%.c FORCE + $(call cmd,force_checksrc) + $(call if_changed_rule,cc_o_c) + +$(obj)/vdso-o32.lds: KBUILD_CPPFLAGS := -mabi=32 +$(obj)/vdso-o32.lds: $(src)/vdso.lds.S FORCE + $(call if_changed_dep,cpp_lds_S) + +$(obj)/vdso-o32.so.dbg: $(obj)/vdso-o32.lds $(obj-vdso-o32) FORCE + $(call if_changed,vdsold) + +$(obj)/vdso-o32-image.c: VDSO_NAME := o32 +$(obj)/vdso-o32-image.c: $(obj)/vdso-o32.so.dbg $(obj)/genvdso FORCE + $(call if_changed,genvdso) + +obj-y += vdso-o32-image.o + +endif + +# +# Build N32 VDSO. +# + +targets += $(obj-vdso-y:%.o=%-n32.o) +targets += vdso-n32.lds vdso-n32.so.dbg vdso-n32.so vdso-n32-image.c + +ifdef CONFIG_MIPS32_N32 + +obj-vdso-n32 := $(obj-vdso-y:%.o=$(obj)/%-n32.o) + +$(obj-vdso-n32): KBUILD_CFLAGS := $(cflags-vdso) -mabi=n32 +$(obj-vdso-n32): KBUILD_AFLAGS := $(aflags-vdso) -mabi=n32 + +$(obj)/%-n32.o: $(src)/%.S FORCE + $(call if_changed_dep,as_o_S) + +$(obj)/%-n32.o: $(src)/%.c FORCE + $(call cmd,force_checksrc) + $(call if_changed_rule,cc_o_c) + +$(obj)/vdso-n32.lds: KBUILD_CPPFLAGS := -mabi=n32 +$(obj)/vdso-n32.lds: $(src)/vdso.lds.S FORCE + $(call if_changed_dep,cpp_lds_S) + +$(obj)/vdso-n32.so.dbg: $(obj)/vdso-n32.lds $(obj-vdso-n32) FORCE + $(call if_changed,vdsold) + +$(obj)/vdso-n32-image.c: VDSO_NAME := n32 +$(obj)/vdso-n32-image.c: $(obj)/vdso-n32.so.dbg $(obj)/genvdso FORCE + $(call if_changed,genvdso) + +obj-y += vdso-n32-image.o + +endif + +# FIXME: Need install rule for debug. +# Needs to deal with dependency for generation of dbg by cmd_genvdso... diff --git a/arch/mips/vdso/elf.S b/arch/mips/vdso/elf.S new file mode 100644 index 0000000..be37bbb --- /dev/null +++ b/arch/mips/vdso/elf.S @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 Imagination Technologies + * Author: Alex Smith + * + * 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 "vdso.h" + +#include +#include + +ELFNOTE_START(Linux, 0, "a") + .long LINUX_VERSION_CODE +ELFNOTE_END + +/* + * The .MIPS.abiflags section must be defined with the FP ABI flags set + * to 'any' to be able to link with both old and new libraries. + * Newer toolchains are capable of automatically generating this, but we want + * to work with older toolchains as well. Therefore, we define the contents of + * this section here (under different names), and then genvdso will patch + * it to have the correct name and type. + * + * We base the .MIPS.abiflags section on preprocessor definitions rather than + * CONFIG_* because we need to match the particular ABI we are building the + * VDSO for. + * + * See https://dmz-portal.mips.com/wiki/MIPS_O32_ABI_-_FR0_and_FR1_Interlinking + * for the .MIPS.abiflags section description. + */ + + .section .mips_abiflags, "a" + .align 3 +__mips_abiflags: + .hword 0 /* version */ + .byte __mips /* isa_level */ + + /* isa_rev */ +#ifdef __mips_isa_rev + .byte __mips_isa_rev +#else + .byte 0 +#endif + + /* gpr_size */ +#ifdef __mips64 + .byte 2 /* AFL_REG_64 */ +#else + .byte 1 /* AFL_REG_32 */ +#endif + + /* cpr1_size */ +#if (defined(__mips_isa_rev) && __mips_isa_rev >= 6) || defined(__mips64) + .byte 2 /* AFL_REG_64 */ +#else + .byte 1 /* AFL_REG_32 */ +#endif + + .byte 0 /* cpr2_size (AFL_REG_NONE) */ + .byte 0 /* fp_abi (Val_GNU_MIPS_ABI_FP_ANY) */ + .word 0 /* isa_ext */ + .word 0 /* ases */ + .word 0 /* flags1 */ + .word 0 /* flags2 */ diff --git a/arch/mips/vdso/genvdso.c b/arch/mips/vdso/genvdso.c new file mode 100644 index 0000000..530a36f --- /dev/null +++ b/arch/mips/vdso/genvdso.c @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2015 Imagination Technologies + * Author: Alex Smith + * + * 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. + */ + +/* + * This tool is used to generate the real VDSO images from the raw image. It + * first patches up the MIPS ABI flags and GNU attributes sections defined in + * elf.S to have the correct name and type. It then generates a C source file + * to be compiled into the kernel containing the VDSO image data and a + * mips_vdso_image struct for it, including symbol offsets extracted from the + * image. + * + * We need to be passed both a stripped and unstripped VDSO image. The stripped + * image is compiled into the kernel, but we must also patch up the unstripped + * image's ABI flags sections so that it can be installed and used for + * debugging. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Define these in case the system elf.h is not new enough to have them. */ +#ifndef SHT_GNU_ATTRIBUTES +# define SHT_GNU_ATTRIBUTES 0x6ffffff5 +#endif +#ifndef SHT_MIPS_ABIFLAGS +# define SHT_MIPS_ABIFLAGS 0x7000002a +#endif + +enum { + ABI_O32 = (1 << 0), + ABI_N32 = (1 << 1), + ABI_N64 = (1 << 2), + + ABI_ALL = ABI_O32 | ABI_N32 | ABI_N64, +}; + +/* Symbols the kernel requires offsets for. */ +static struct { + const char *name; + const char *offset_name; + unsigned int abis; +} vdso_symbols[] = { + { "__vdso_sigreturn", "off_sigreturn", ABI_O32 }, + { "__vdso_rt_sigreturn", "off_rt_sigreturn", ABI_ALL }, + {} +}; + +static const char *program_name; +static const char *vdso_name; +static unsigned char elf_class; +static unsigned int elf_abi; +static bool need_swap; +static FILE *out_file; + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define HOST_ORDER ELFDATA2LSB +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define HOST_ORDER ELFDATA2MSB +#endif + +#define BUILD_SWAP(bits) \ + static uint##bits##_t swap_uint##bits(uint##bits##_t val) \ + { \ + return need_swap ? bswap_##bits(val) : val; \ + } + +BUILD_SWAP(16) +BUILD_SWAP(32) +BUILD_SWAP(64) + +#define __FUNC(name, bits) name##bits +#define _FUNC(name, bits) __FUNC(name, bits) +#define FUNC(name) _FUNC(name, ELF_BITS) + +#define __ELF(x, bits) Elf##bits##_##x +#define _ELF(x, bits) __ELF(x, bits) +#define ELF(x) _ELF(x, ELF_BITS) + +/* + * Include genvdso.h twice with ELF_BITS defined differently to get functions + * for both ELF32 and ELF64. + */ + +#define ELF_BITS 64 +#include "genvdso.h" +#undef ELF_BITS + +#define ELF_BITS 32 +#include "genvdso.h" +#undef ELF_BITS + +static void *map_vdso(const char *path, size_t *_size) +{ + int fd; + struct stat stat; + void *addr; + const Elf32_Ehdr *ehdr; + + fd = open(path, O_RDWR); + if (fd < 0) { + fprintf(stderr, "%s: Failed to open '%s': %s\n", program_name, + path, strerror(errno)); + return NULL; + } + + if (fstat(fd, &stat) != 0) { + fprintf(stderr, "%s: Failed to stat '%s': %s\n", program_name, + path, strerror(errno)); + return NULL; + } + + addr = mmap(NULL, stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + 0); + if (addr == MAP_FAILED) { + fprintf(stderr, "%s: Failed to map '%s': %s\n", program_name, + path, strerror(errno)); + return NULL; + } + + /* ELF32/64 header formats are the same for the bits we're checking. */ + ehdr = addr; + + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { + fprintf(stderr, "%s: '%s' is not an ELF file\n", program_name, + path); + return NULL; + } + + elf_class = ehdr->e_ident[EI_CLASS]; + switch (elf_class) { + case ELFCLASS32: + case ELFCLASS64: + break; + default: + fprintf(stderr, "%s: '%s' has invalid ELF class\n", + program_name, path); + return NULL; + } + + switch (ehdr->e_ident[EI_DATA]) { + case ELFDATA2LSB: + case ELFDATA2MSB: + need_swap = ehdr->e_ident[EI_DATA] != HOST_ORDER; + break; + default: + fprintf(stderr, "%s: '%s' has invalid ELF data order\n", + program_name, path); + return NULL; + } + + if (swap_uint16(ehdr->e_machine) != EM_MIPS) { + fprintf(stderr, + "%s: '%s' has invalid ELF machine (expected EM_MIPS)\n", + program_name, path); + return NULL; + } else if (swap_uint16(ehdr->e_type) != ET_DYN) { + fprintf(stderr, + "%s: '%s' has invalid ELF type (expected ET_DYN)\n", + program_name, path); + return NULL; + } + + *_size = stat.st_size; + return addr; +} + +static bool patch_vdso(const char *path, void *vdso) +{ + if (elf_class == ELFCLASS64) + return patch_vdso64(path, vdso); + else + return patch_vdso32(path, vdso); +} + +static bool get_symbols(const char *path, void *vdso) +{ + if (elf_class == ELFCLASS64) + return get_symbols64(path, vdso); + else + return get_symbols32(path, vdso); +} + +int main(int argc, char **argv) +{ + const char *dbg_vdso_path, *vdso_path, *out_path; + void *dbg_vdso, *vdso; + size_t dbg_vdso_size, vdso_size, i; + + program_name = argv[0]; + + if (argc < 4 || argc > 5) { + fprintf(stderr, + "Usage: %s []\n", + program_name); + return EXIT_FAILURE; + } + + dbg_vdso_path = argv[1]; + vdso_path = argv[2]; + out_path = argv[3]; + vdso_name = (argc > 4) ? argv[4] : ""; + + dbg_vdso = map_vdso(dbg_vdso_path, &dbg_vdso_size); + if (!dbg_vdso) + return EXIT_FAILURE; + + vdso = map_vdso(vdso_path, &vdso_size); + if (!vdso) + return EXIT_FAILURE; + + /* Patch both the VDSOs' ABI flags sections. */ + if (!patch_vdso(dbg_vdso_path, dbg_vdso)) + return EXIT_FAILURE; + if (!patch_vdso(vdso_path, vdso)) + return EXIT_FAILURE; + + if (msync(dbg_vdso, dbg_vdso_size, MS_SYNC) != 0) { + fprintf(stderr, "%s: Failed to sync '%s': %s\n", program_name, + dbg_vdso_path, strerror(errno)); + return EXIT_FAILURE; + } else if (msync(vdso, vdso_size, MS_SYNC) != 0) { + fprintf(stderr, "%s: Failed to sync '%s': %s\n", program_name, + vdso_path, strerror(errno)); + return EXIT_FAILURE; + } + + out_file = fopen(out_path, "w"); + if (!out_file) { + fprintf(stderr, "%s: Failed to open '%s': %s\n", program_name, + out_path, strerror(errno)); + return EXIT_FAILURE; + } + + fprintf(out_file, "/* Automatically generated - do not edit */\n"); + fprintf(out_file, "#include \n"); + fprintf(out_file, "#include \n"); + fprintf(out_file, "#include \n"); + + /* Write out the stripped VDSO data. */ + fprintf(out_file, + "static unsigned char vdso_data[PAGE_ALIGN(%zu)] __page_aligned_data = {\n\t", + vdso_size); + for (i = 0; i < vdso_size; i++) { + if (!(i % 10)) + fprintf(out_file, "\n\t"); + fprintf(out_file, "0x%02x, ", ((unsigned char *)vdso)[i]); + } + fprintf(out_file, "\n};\n"); + + /* Preallocate a page array. */ + fprintf(out_file, + "static struct page *vdso_pages[PAGE_ALIGN(%zu) / PAGE_SIZE];\n", + vdso_size); + + fprintf(out_file, "struct mips_vdso_image vdso_image%s%s = {\n", + (vdso_name[0]) ? "_" : "", vdso_name); + fprintf(out_file, "\t.data = vdso_data,\n"); + fprintf(out_file, "\t.size = PAGE_ALIGN(%zu),\n", vdso_size); + fprintf(out_file, "\t.mapping = {\n"); + fprintf(out_file, "\t\t.name = \"[vdso]\",\n"); + fprintf(out_file, "\t\t.pages = vdso_pages,\n"); + fprintf(out_file, "\t},\n"); + + /* Calculate and write symbol offsets to */ + if (!get_symbols(dbg_vdso_path, dbg_vdso)) { + unlink(out_path); + return EXIT_FAILURE; + } + + fprintf(out_file, "};\n"); + + return EXIT_SUCCESS; +} diff --git a/arch/mips/vdso/genvdso.h b/arch/mips/vdso/genvdso.h new file mode 100644 index 0000000..9433472 --- /dev/null +++ b/arch/mips/vdso/genvdso.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2015 Imagination Technologies + * Author: Alex Smith + * + * 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. + */ + +static inline bool FUNC(patch_vdso)(const char *path, void *vdso) +{ + const ELF(Ehdr) *ehdr = vdso; + void *shdrs; + ELF(Shdr) *shdr; + char *shstrtab, *name; + uint16_t sh_count, sh_entsize, i; + unsigned int local_gotno, symtabno, gotsym; + ELF(Dyn) *dyn = NULL; + + shdrs = vdso + FUNC(swap_uint)(ehdr->e_shoff); + sh_count = swap_uint16(ehdr->e_shnum); + sh_entsize = swap_uint16(ehdr->e_shentsize); + + shdr = shdrs + (sh_entsize * swap_uint16(ehdr->e_shstrndx)); + shstrtab = vdso + FUNC(swap_uint)(shdr->sh_offset); + + for (i = 0; i < sh_count; i++) { + shdr = shdrs + (i * sh_entsize); + name = shstrtab + swap_uint32(shdr->sh_name); + + /* + * Ensure there are no relocation sections - ld.so does not + * relocate the VDSO so if there are relocations things will + * break. + */ + switch (swap_uint32(shdr->sh_type)) { + case SHT_REL: + case SHT_RELA: + fprintf(stderr, + "%s: '%s' contains relocation sections\n", + program_name, path); + return false; + case SHT_DYNAMIC: + dyn = vdso + FUNC(swap_uint)(shdr->sh_offset); + break; + } + + /* Check for existing sections. */ + if (strcmp(name, ".MIPS.abiflags") == 0) { + fprintf(stderr, + "%s: '%s' already contains a '.MIPS.abiflags' section\n", + program_name, path); + return false; + } + + if (strcmp(name, ".mips_abiflags") == 0) { + strcpy(name, ".MIPS.abiflags"); + shdr->sh_type = swap_uint32(SHT_MIPS_ABIFLAGS); + shdr->sh_entsize = shdr->sh_size; + } + } + + /* + * Ensure the GOT has no entries other than the standard 2, for the same + * reason we check that there's no relocation sections above. + * The standard two entries are: + * - Lazy resolver + * - Module pointer + */ + if (dyn) { + local_gotno = symtabno = gotsym = 0; + + while (FUNC(swap_uint)(dyn->d_tag) != DT_NULL) { + switch (FUNC(swap_uint)(dyn->d_tag)) { + /* + * This member holds the number of local GOT entries. + */ + case DT_MIPS_LOCAL_GOTNO: + local_gotno = FUNC(swap_uint)(dyn->d_un.d_val); + break; + /* + * This member holds the number of entries in the + * .dynsym section. + */ + case DT_MIPS_SYMTABNO: + symtabno = FUNC(swap_uint)(dyn->d_un.d_val); + break; + /* + * This member holds the index of the first dynamic + * symbol table entry that corresponds to an entry in + * the GOT. + */ + case DT_MIPS_GOTSYM: + gotsym = FUNC(swap_uint)(dyn->d_un.d_val); + break; + } + + dyn++; + } + + if (local_gotno > 2 || symtabno - gotsym) { + fprintf(stderr, + "%s: '%s' contains unexpected GOT entries\n", + program_name, path); + return false; + } + } + + return true; +} + +static inline bool FUNC(get_symbols)(const char *path, void *vdso) +{ + const ELF(Ehdr) *ehdr = vdso; + void *shdrs, *symtab; + ELF(Shdr) *shdr; + const ELF(Sym) *sym; + char *strtab, *name; + uint16_t sh_count, sh_entsize, st_count, st_entsize, i, j; + uint64_t offset; + uint32_t flags; + + shdrs = vdso + FUNC(swap_uint)(ehdr->e_shoff); + sh_count = swap_uint16(ehdr->e_shnum); + sh_entsize = swap_uint16(ehdr->e_shentsize); + + for (i = 0; i < sh_count; i++) { + shdr = shdrs + (i * sh_entsize); + + if (swap_uint32(shdr->sh_type) == SHT_SYMTAB) + break; + } + + if (i == sh_count) { + fprintf(stderr, "%s: '%s' has no symbol table\n", program_name, + path); + return false; + } + + /* Get flags */ + flags = swap_uint32(ehdr->e_flags); + if (elf_class == ELFCLASS64) + elf_abi = ABI_N64; + else if (flags & EF_MIPS_ABI2) + elf_abi = ABI_N32; + else + elf_abi = ABI_O32; + + /* Get symbol table. */ + symtab = vdso + FUNC(swap_uint)(shdr->sh_offset); + st_entsize = FUNC(swap_uint)(shdr->sh_entsize); + st_count = FUNC(swap_uint)(shdr->sh_size) / st_entsize; + + /* Get string table. */ + shdr = shdrs + (swap_uint32(shdr->sh_link) * sh_entsize); + strtab = vdso + FUNC(swap_uint)(shdr->sh_offset); + + /* Write offsets for symbols needed by the kernel. */ + for (i = 0; vdso_symbols[i].name; i++) { + if (!(vdso_symbols[i].abis & elf_abi)) + continue; + + for (j = 0; j < st_count; j++) { + sym = symtab + (j * st_entsize); + name = strtab + swap_uint32(sym->st_name); + + if (!strcmp(name, vdso_symbols[i].name)) { + offset = FUNC(swap_uint)(sym->st_value); + + fprintf(out_file, + "\t.%s = 0x%" PRIx64 ",\n", + vdso_symbols[i].offset_name, offset); + break; + } + } + + if (j == st_count) { + fprintf(stderr, + "%s: '%s' is missing required symbol '%s'\n", + program_name, path, vdso_symbols[i].name); + return false; + } + } + + return true; +} diff --git a/arch/mips/vdso/sigreturn.S b/arch/mips/vdso/sigreturn.S new file mode 100644 index 0000000..715bf59 --- /dev/null +++ b/arch/mips/vdso/sigreturn.S @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 Imagination Technologies + * Author: Alex Smith + * + * 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 "vdso.h" + +#include + +#include +#include + + .section .text + .cfi_sections .debug_frame + +LEAF(__vdso_rt_sigreturn) + .cfi_startproc + .frame sp, 0, ra + .mask 0x00000000, 0 + .fmask 0x00000000, 0 + .cfi_signal_frame + + li v0, __NR_rt_sigreturn + syscall + + .cfi_endproc + END(__vdso_rt_sigreturn) + +#if _MIPS_SIM == _MIPS_SIM_ABI32 + +LEAF(__vdso_sigreturn) + .cfi_startproc + .frame sp, 0, ra + .mask 0x00000000, 0 + .fmask 0x00000000, 0 + .cfi_signal_frame + + li v0, __NR_sigreturn + syscall + + .cfi_endproc + END(__vdso_sigreturn) + +#endif diff --git a/arch/mips/vdso/vdso.h b/arch/mips/vdso/vdso.h new file mode 100644 index 0000000..0bb6b1a --- /dev/null +++ b/arch/mips/vdso/vdso.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 Imagination Technologies + * Author: Alex Smith + * + * 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 + +#if _MIPS_SIM != _MIPS_SIM_ABI64 && defined(CONFIG_64BIT) + +/* Building 32-bit VDSO for the 64-bit kernel. Fake a 32-bit Kconfig. */ +#undef CONFIG_64BIT +#define CONFIG_32BIT 1 +#ifndef __ASSEMBLY__ +#include +#endif +#endif + +#ifndef __ASSEMBLY__ + +#include +#include +#include + +static inline unsigned long get_vdso_base(void) +{ + unsigned long addr; + + /* + * We can't use cpu_has_mips_r6 since it needs the cpu_data[] + * kernel symbol. + */ +#ifdef CONFIG_CPU_MIPSR6 + /* + * lapc is an alias to addiupc reg, - . + * + * We can't use addiupc because there is no label-label + * support for the addiupc reloc + */ + __asm__("lapc %0, _start \n" + : "=r" (addr) : :); +#else + /* + * Get the base load address of the VDSO. We have to avoid generating + * relocations and references to the GOT because ld.so does not peform + * relocations on the VDSO. We use the current offset from the VDSO base + * and perform a PC-relative branch which gives the absolute address in + * ra, and take the difference. The assembler chokes on + * "li %0, _start - .", so embed the offset as a word and branch over + * it. + * + */ + + __asm__( + " .set push \n" + " .set noreorder \n" + " bal 1f \n" + " nop \n" + " .word _start - . \n" + "1: lw %0, 0($31) \n" + " " STR(PTR_ADDU) " %0, $31, %0 \n" + " .set pop \n" + : "=r" (addr) + : + : "$31"); +#endif /* CONFIG_CPU_MIPSR6 */ + + return addr; +} + +static inline const union mips_vdso_data *get_vdso_data(void) +{ + return (const union mips_vdso_data *)(get_vdso_base() - PAGE_SIZE); +} + +#endif /* __ASSEMBLY__ */ diff --git a/arch/mips/vdso/vdso.lds.S b/arch/mips/vdso/vdso.lds.S new file mode 100644 index 0000000..21655b6 --- /dev/null +++ b/arch/mips/vdso/vdso.lds.S @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015 Imagination Technologies + * Author: Alex Smith + * + * 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 + +#if _MIPS_SIM == _MIPS_SIM_ABI64 +OUTPUT_FORMAT("elf64-tradlittlemips", "elf64-tradbigmips", "elf64-tradlittlemips") +#elif _MIPS_SIM == _MIPS_SIM_NABI32 +OUTPUT_FORMAT("elf32-ntradlittlemips", "elf32-ntradbigmips", "elf32-ntradlittlemips") +#else +OUTPUT_FORMAT("elf32-tradlittlemips", "elf32-tradbigmips", "elf32-tradlittlemips") +#endif + +OUTPUT_ARCH(mips) + +SECTIONS +{ + PROVIDE(_start = .); + . = SIZEOF_HEADERS; + + /* + * In order to retain compatibility with older toolchains we provide the + * ABI flags section ourself. Newer assemblers will automatically + * generate .MIPS.abiflags sections so we discard such input sections, + * and then manually define our own section here. genvdso will patch + * this section to have the correct name/type. + */ + .mips_abiflags : { *(.mips_abiflags) } :text :abiflags + + .reginfo : { *(.reginfo) } :text :reginfo + + .hash : { *(.hash) } :text + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + + .note : { *(.note.*) } :text :note + + .text : { *(.text*) } :text + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + + .eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr + .eh_frame : { KEEP (*(.eh_frame)) } :text + + .dynamic : { *(.dynamic) } :text :dynamic + + .rodata : { *(.rodata*) } :text + + _end = .; + PROVIDE(end = .); + + /DISCARD/ : { + *(.MIPS.abiflags) + *(.gnu.attributes) + *(.note.GNU-stack) + *(.data .data.* .gnu.linkonce.d.* .sdata*) + *(.bss .sbss .dynbss .dynsbss) + } +} + +PHDRS +{ + /* + * Provide a PT_MIPS_ABIFLAGS header to assign the ABI flags section + * to. We can specify the header type directly here so no modification + * is needed later on. + */ + abiflags 0x70000003; + + /* + * The ABI flags header must exist directly after the PT_INTERP header, + * so we must explicitly place the PT_MIPS_REGINFO header after it to + * stop the linker putting one in at the start. + */ + reginfo 0x70000000; + + text PT_LOAD FLAGS(5) FILEHDR PHDRS; /* PF_R|PF_X */ + dynamic PT_DYNAMIC FLAGS(4); /* PF_R */ + note PT_NOTE FLAGS(4); /* PF_R */ + eh_frame_hdr PT_GNU_EH_FRAME; +} + +VERSION +{ + LINUX_2.6 { + local: *; + }; +} -- cgit v1.1