diff options
Diffstat (limited to 'ld/mips.c')
-rw-r--r-- | ld/mips.c | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/ld/mips.c b/ld/mips.c new file mode 100644 index 0000000..59a9221 --- /dev/null +++ b/ld/mips.c @@ -0,0 +1,403 @@ +/*- + * Copyright (c) 2015 Serge Vakulenko + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ld.h" +#include "ld_arch.h" +#include "ld_dynamic.h" +#include "ld_input.h" +#include "ld_output.h" +#include "ld_reloc.h" +#include "ld_symbols.h" +#include "ld_utils.h" +#include "mips.h" + +#define EF_MIPS_ABI2 0x00000020 /* n32 abi */ +#define EF_MIPS_ABI 0x00007000 /* Application binary interface */ +#define E_MIPS_ABI_O32 0x00001000 /* MIPS 32 bit ABI (UCODE) */ +#define E_MIPS_ABI_O64 0x00002000 /* UCODE MIPS 64 bit ABI */ +#define E_MIPS_ABI_EABI32 0x00003000 /* Embedded ABI for 32-bit */ +#define E_MIPS_ABI_EABI64 0x00004000 /* Embedded ABI for 64-bit */ + +#define EF_MIPS_ASE_MDMX 0x08000000 /* MDMX multimedia extensions */ +#define EF_MIPS_ASE_M16 0x04000000 /* MIPS16e ISA extensions */ +#define EF_MIPS_ASE_MICROMIPS 0x02000000 /* MicroMIPS architecture */ + +#define EF_MIPS_ARCH_1 0x00000000 /* MIPS I instruction set */ +#define EF_MIPS_ARCH_2 0x10000000 /* MIPS II instruction set */ +#define EF_MIPS_ARCH_3 0x20000000 /* MIPS III instruction set */ +#define EF_MIPS_ARCH_4 0x30000000 /* MIPS IV instruction set */ +#define EF_MIPS_ARCH_5 0x40000000 /* Never introduced */ +#define EF_MIPS_ARCH_32 0x50000000 /* Mips32 Revision 1 */ +#define EF_MIPS_ARCH_64 0x60000000 /* Mips64 Revision 1 */ +#define EF_MIPS_ARCH_32R2 0x70000000 /* Mips32 Revision 2 */ +#define EF_MIPS_ARCH_64R2 0x80000000 /* Mips64 Revision 2 */ + +ELFTC_VCSID("$Id$"); + +static void +_scan_reloc(struct ld *ld, struct ld_input_section *is, + struct ld_reloc_entry *lre) +{ + + (void) is; + + switch (lre->lre_type) { + + case R_MIPS_NONE: + case R_MIPS_32: + case R_MIPS_26: + case R_MIPS_PC16: + case R_MIPS_GPREL16: + case R_MIPS_HI16: + case R_MIPS_LO16: + break; + + default: + ld_warn(ld, "can not handle relocation %ju", + lre->lre_type); + break; + } +} + +static void +_process_reloc(struct ld *ld, struct ld_input_section *is, + struct ld_reloc_entry *lre, struct ld_symbol *lsb, uint8_t *buf) +{ + struct ld_output *lo = ld->ld_output; + uint32_t pc, s; + int32_t a, v, la; + static uint64_t gp; + static char gp_name[] = "_gp"; + + assert(lo != NULL); + + pc = lre->lre_offset + is->is_output->os_addr + is->is_reloff; + s = (uint32_t) lsb->lsb_value; + READ_32(buf + lre->lre_offset, a); + + switch (lre->lre_type) { + + case R_MIPS_NONE: + break; + + case R_MIPS_32: + /* 32-bit byte address. */ + v = s + a; + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_MIPS_26: + /* Word address at lower 26 bits. */ + s += (a & 0x3ffffff) << 2; + v = (a & ~0x3ffffff) | ((s >> 2) & 0x3ffffff); + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_MIPS_PC16: + /* PC-relative word address at lower 16 bits. */ + s += ((a & 0xffff) << 2) - pc; + v = (a & ~0xffff) | ((s >> 2) & 0xffff); + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_MIPS_GPREL16: + /* GP-relative byte address at lower 16 bits. */ + if (! gp && ld_symbols_get_value(ld, gp_name, &gp) < 0) + ld_fatal(ld, "symbol _gp is undefined"); + + s += (int16_t)(a & 0xffff) - gp; + v = (a & ~0xffff) | (s & 0xffff); + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_MIPS_HI16: + /* 16-bit high part of address pair. */ + if (! STAILQ_NEXT(lre, lre_next) || + STAILQ_NEXT(lre, lre_next)->lre_type != R_MIPS_LO16) + ld_fatal(ld, "no LO16 after HI16 relocation"); + READ_32(buf + STAILQ_NEXT(lre, lre_next)->lre_offset, la); + s += (a << 16) + (int16_t)la; + v = (a & ~0xffff) | (((s - (int16_t)s) >> 16) & 0xffff); + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_MIPS_LO16: + /* 16-bit low part of address pair. */ + s += (int16_t)a; + v = (a & ~0xffff) | (s & 0xffff); + WRITE_32(buf + lre->lre_offset, v); + break; + + default: + ld_fatal(ld, "Relocation %d not supported", lre->lre_type); + break; + } +} + +/* + * Map flags into a valid MIPS architecture level value. + */ +static unsigned +_map_arch(unsigned flags) +{ + flags &= EF_MIPS_ARCH; + + switch (flags) { + default: + case EF_MIPS_ARCH_1: + return EF_MIPS_ARCH_1; + case EF_MIPS_ARCH_2: + case EF_MIPS_ARCH_3: + case EF_MIPS_ARCH_4: + case EF_MIPS_ARCH_5: + case EF_MIPS_ARCH_32: + case EF_MIPS_ARCH_64: + case EF_MIPS_ARCH_32R2: + case EF_MIPS_ARCH_64R2: + return flags; + } +} + +/* + * Merge architecture levels of two files. + */ +static unsigned +_merge_arch(unsigned old_arch, unsigned new_arch) +{ + unsigned base, extended; + + if (old_arch < new_arch) { + base = old_arch; + extended = new_arch; + } else if (old_arch > new_arch) { + base = new_arch; + extended = old_arch; + } else + return old_arch; + + switch (extended) { + default: + case EF_MIPS_ARCH_1: + case EF_MIPS_ARCH_2: + case EF_MIPS_ARCH_3: + case EF_MIPS_ARCH_4: + case EF_MIPS_ARCH_5: + return extended; + + case EF_MIPS_ARCH_32: + if (base <= EF_MIPS_ARCH_2) + return EF_MIPS_ARCH_32; + return EF_MIPS_ARCH_64; + + case EF_MIPS_ARCH_64: + return EF_MIPS_ARCH_64; + + case EF_MIPS_ARCH_32R2: + if (base <= EF_MIPS_ARCH_2 || base == EF_MIPS_ARCH_32) + return EF_MIPS_ARCH_32R2; + return EF_MIPS_ARCH_64R2; + + case EF_MIPS_ARCH_64R2: + return EF_MIPS_ARCH_64R2; + } +} + +static const char* +_abi_name(int flags) +{ + switch (flags & EF_MIPS_ABI) { + case 0: + return (flags & EF_MIPS_ABI2) ? "N32" : "none"; + case E_MIPS_ABI_O32: + return "O32"; + case E_MIPS_ABI_O64: + return "O64"; + case E_MIPS_ABI_EABI32: + return "EABI32"; + case E_MIPS_ABI_EABI64: + return "EABI64"; + default: + return "Unknown"; + } +} + +/* + * Merge options of application binary interface. + */ +static unsigned +_merge_abi(struct ld *ld, unsigned new_flags) +{ + int old = ld->ld_arch->flags & EF_MIPS_ABI; + int new = new_flags & EF_MIPS_ABI; + + if (old == 0) + return new; + + if (new != old && new != 0) + ld_fatal(ld, "ABI mismatch: linking '%s' module with previous '%s' modules", + _abi_name(new_flags), _abi_name(ld->ld_arch->flags)); + + return old; +} + +/* + * Merge options of application-specific extensions. + */ +static unsigned +_merge_ase(struct ld *ld, unsigned new_flags) +{ + int old_micro = ld->ld_arch->flags & EF_MIPS_ASE_MICROMIPS; + int new_micro = new_flags & EF_MIPS_ASE_MICROMIPS; + int old_m16 = ld->ld_arch->flags & EF_MIPS_ASE_M16; + int new_m16 = new_flags & EF_MIPS_ASE_M16; + + if ((old_m16 && new_micro) || (old_micro && new_m16)) + ld_fatal(ld, "ASE mismatch: linking '%s' module with previous '%s' modules", + new_m16 ? "MIPS16" : "microMIPS", + old_micro ? "microMIPS" : "MIPS16"); + return old_micro | new_micro | old_m16 | new_m16; +} + +/* + * Merge architecture-specific flags of the file to be linked + * into a resulting value for output file. + */ +static void +_merge_flags(struct ld *ld, unsigned new_flags) +{ + struct ld_arch *la = ld->ld_arch; + unsigned value; + + /* At least one .noreorder directive appeared in the source. */ + la->flags |= new_flags & EF_MIPS_NOREORDER; + + /* Merge position-independent flags. */ + if (((new_flags & (EF_MIPS_PIC | EF_MIPS_CPIC)) != 0) != + ((la->flags & (EF_MIPS_PIC | EF_MIPS_CPIC)) != 0)) + ld_warn(ld, "linking PIC files with non-PIC files"); + if (new_flags & (EF_MIPS_PIC | EF_MIPS_CPIC)) + la->flags |= EF_MIPS_CPIC; + if (! (new_flags & EF_MIPS_PIC)) + la->flags &= ~EF_MIPS_PIC; + + /* Merge architecture level. */ + value = _merge_arch(_map_arch(la->flags), _map_arch(new_flags)); + la->flags &= ~EF_MIPS_ARCH; + la->flags |= value; + + /* Merge ABI options. */ + value = _merge_abi(ld, new_flags); + la->flags &= ~EF_MIPS_ABI; + la->flags |= value; + + /* Merge application-specific extensions. */ + value = _merge_ase(ld, new_flags); + la->flags &= ~EF_MIPS_ARCH_ASE; + la->flags |= value; +} + +static uint64_t +_get_max_page_size(struct ld *ld) +{ + + (void) ld; + + return 0x1000; +} + +static uint64_t +_get_common_page_size(struct ld *ld) +{ + + (void) ld; + + return 0x1000; +} + +static int +_is_absolute_reloc(uint64_t r) +{ + if (r == R_MIPS_32) + return 1; + + return 0; +} + +static int +_is_relative_reloc(uint64_t r) +{ + if (r == R_MIPS_REL32) + return 1; + + return 0; +} + +void +mips_register(struct ld *ld) +{ + struct ld_arch *mips_little_endian, *mips_big_endian; + + if ((mips_little_endian = calloc(1, sizeof(*mips_little_endian))) == NULL) + ld_fatal_std(ld, "calloc"); + if ((mips_big_endian = calloc(1, sizeof(*mips_big_endian))) == NULL) + ld_fatal_std(ld, "calloc"); + + /* + * Little endian. + */ + snprintf(mips_little_endian->name, sizeof(mips_little_endian->name), "%s", "littlemips"); + + mips_little_endian->script = littlemips_script; + mips_little_endian->get_max_page_size = _get_max_page_size; + mips_little_endian->get_common_page_size = _get_common_page_size; + mips_little_endian->scan_reloc = _scan_reloc; + mips_little_endian->process_reloc = _process_reloc; + mips_little_endian->is_absolute_reloc = _is_absolute_reloc; + mips_little_endian->is_relative_reloc = _is_relative_reloc; + mips_little_endian->merge_flags = _merge_flags; + mips_little_endian->reloc_is_64bit = 0; + mips_little_endian->reloc_is_rela = 0; + mips_little_endian->reloc_entsize = sizeof(Elf32_Rel); + + /* + * Big endian. + */ + snprintf(mips_big_endian->name, sizeof(mips_big_endian->name), "%s", "bigmips"); + + mips_big_endian->script = bigmips_script; + mips_big_endian->get_max_page_size = _get_max_page_size; + mips_big_endian->get_common_page_size = _get_common_page_size; + mips_big_endian->scan_reloc = _scan_reloc; + mips_big_endian->process_reloc = _process_reloc; + mips_big_endian->is_absolute_reloc = _is_absolute_reloc; + mips_big_endian->is_relative_reloc = _is_relative_reloc; + mips_little_endian->merge_flags = _merge_flags; + mips_big_endian->reloc_is_64bit = 0; + mips_big_endian->reloc_is_rela = 0; + mips_big_endian->reloc_entsize = sizeof(Elf32_Rel); + + HASH_ADD_STR(ld->ld_arch_list, name, mips_little_endian); + HASH_ADD_STR(ld->ld_arch_list, name, mips_big_endian); +} |