diff options
Diffstat (limited to 'ld/i386.c')
-rw-r--r-- | ld/i386.c | 624 |
1 files changed, 624 insertions, 0 deletions
diff --git a/ld/i386.c b/ld/i386.c new file mode 100644 index 000000000000..fab5643f4fe9 --- /dev/null +++ b/ld/i386.c @@ -0,0 +1,624 @@ +/*- + * Copyright (c) 2012,2013 Kai Wang + * All rights reserved. + * + * 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 "i386.h" + +ELFTC_VCSID("$Id: i386.c 2967 2013-10-12 23:58:13Z kaiwang27 $"); + +static void _create_plt_reloc(struct ld *ld, struct ld_symbol *lsb, + uint64_t offset); +static void _create_got_reloc(struct ld *ld, struct ld_symbol *lsb, + uint64_t type, uint64_t offset); +static void _create_copy_reloc(struct ld *ld, struct ld_symbol *lsb); +static void _create_dynamic_reloc(struct ld *ld, struct ld_input_section *is, + struct ld_symbol *lsb, uint64_t type, uint64_t offset); +static void _scan_reloc(struct ld *ld, struct ld_input_section *is, + struct ld_reloc_entry *lre); +static struct ld_input_section *_find_and_create_got_section(struct ld *ld, + int create); +static struct ld_input_section *_find_and_create_gotplt_section(struct ld *ld, + int create); +static struct ld_input_section *_find_and_create_plt_section(struct ld *ld, + int create); +static uint64_t _get_max_page_size(struct ld *ld); +static uint64_t _get_common_page_size(struct ld *ld); +static void _process_reloc(struct ld *ld, struct ld_input_section *is, + struct ld_reloc_entry *lre, struct ld_symbol *lsb, uint8_t *buf); +static const char *_reloc2str(uint64_t r); +static void _reserve_got_entry(struct ld *ld, struct ld_symbol *lsb, int num); +static void _reserve_gotplt_entry(struct ld *ld, struct ld_symbol *lsb); +static void _reserve_plt_entry(struct ld *ld, struct ld_symbol *lsb); +static int _is_absolute_reloc(uint64_t r); +static int _is_relative_reloc(uint64_t r); +static void _warn_pic(struct ld *ld, struct ld_reloc_entry *lre); +static uint32_t _got_offset(struct ld *ld, struct ld_symbol *lsb); + +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 const char * +_reloc2str(uint64_t r) +{ + static char s[32]; + + switch (r) { + case 0: return "R_386_NONE"; + case 1: return "R_386_32"; + case 2: return "R_386_PC32"; + case 3: return "R_386_GOT32"; + case 4: return "R_386_PLT32"; + case 5: return "R_386_COPY"; + case 6: return "R_386_GLOB_DAT"; + case 7: return "R_386_JMP_SLOT"; + case 8: return "R_386_RELATIVE"; + case 9: return "R_386_GOTOFF"; + case 10: return "R_386_GOTPC"; + case 14: return "R_386_TLS_TPOFF"; + case 15: return "R_386_TLS_IE"; + case 16: return "R_386_TLS_GOTI"; + case 17: return "R_386_TLS_LE"; + case 18: return "R_386_TLS_GD"; + case 19: return "R_386_TLS_LDM"; + case 24: return "R_386_TLS_GD_32"; + case 25: return "R_386_TLS_GD_PUSH"; + case 26: return "R_386_TLS_GD_CALL"; + case 27: return "R_386_TLS_GD_POP"; + case 28: return "R_386_TLS_LDM_32"; + case 29: return "R_386_TLS_LDM_PUSH"; + case 30: return "R_386_TLS_LDM_CALL"; + case 31: return "R_386_TLS_LDM_POP"; + case 32: return "R_386_TLS_LDO_32"; + case 33: return "R_386_TLS_IE_32"; + case 34: return "R_386_TLS_LE_32"; + case 35: return "R_386_TLS_DTPMOD32"; + case 36: return "R_386_TLS_DTPOFF32"; + case 37: return "R_386_TLS_TPOFF32"; + + default: + snprintf(s, sizeof(s), "<unkown: %ju>", r); + return (s); + } +} + +static int +_is_absolute_reloc(uint64_t r) +{ + + if (r == R_386_32) + return (1); + + return (0); +} + +static int +_is_relative_reloc(uint64_t r) +{ + + if (r == R_386_RELATIVE) + return (1); + + return (0); +} + +static void +_warn_pic(struct ld *ld, struct ld_reloc_entry *lre) +{ + struct ld_symbol *lsb; + + lsb = lre->lre_sym; + + if (lsb->lsb_bind != STB_LOCAL) + ld_warn(ld, "relocation %s against `%s' can not be used" + " by runtime linker; recompile with -fPIC", + _reloc2str(lre->lre_type), lsb->lsb_name); + else + ld_warn(ld, "relocation %s can not be used by runtime linker;" + " recompile with -fPIC", _reloc2str(lre->lre_type)); +} + +static struct ld_input_section * +_find_and_create_got_section(struct ld *ld, int create) +{ + struct ld_input_section *is; + + /* Check if the GOT section is already created. */ + is = ld_input_find_internal_section(ld, ".got"); + if (is != NULL) + return (is); + + if (create) { + is = ld_input_add_internal_section(ld, ".got"); + is->is_entsize = 4; + is->is_align = 4; + is->is_type = SHT_PROGBITS; + is->is_flags = SHF_ALLOC | SHF_WRITE; + } + + return (is); +} + +static struct ld_input_section * +_find_and_create_gotplt_section(struct ld *ld, int create) +{ + struct ld_input_section *is; + + /* Check if the GOT (for PLT) section is already created. */ + is = ld_input_find_internal_section(ld, ".got.plt"); + if (is != NULL) + return (is); + + if (create) { + is = ld_input_add_internal_section(ld, ".got.plt"); + is->is_entsize = 4; + is->is_align = 4; + is->is_type = SHT_PROGBITS; + is->is_flags = SHF_ALLOC | SHF_WRITE; + + /* Reserve space for the initial entries. */ + (void) ld_input_reserve_ibuf(is, 3); + + /* Create _GLOBAL_OFFSET_TABLE_ symbol. */ + ld_symbols_add_internal(ld, "_GLOBAL_OFFSET_TABLE_", 0, 0, + is->is_index, STB_LOCAL, STT_OBJECT, STV_HIDDEN, is, NULL); + } + + return (is); +} + +static struct ld_input_section * +_find_and_create_plt_section(struct ld *ld, int create) +{ + struct ld_input_section *is; + + /* Check if the PLT section is already created. */ + is = ld_input_find_internal_section(ld, ".plt"); + if (is != NULL) + return (is); + + if (create) { + is = ld_input_add_internal_section(ld, ".plt"); + is->is_entsize = 4; + is->is_align = 4; + is->is_type = SHT_PROGBITS; + is->is_flags = SHF_ALLOC | SHF_EXECINSTR; + + /* Reserve space for the initial entry. */ + (void) ld_input_reserve_ibuf(is, 1); + } + + return (is); +} + +static void +_reserve_got_entry(struct ld *ld, struct ld_symbol *lsb, int num) +{ + struct ld_input_section *is; + + is = _find_and_create_got_section(ld, 1); + + /* Check if the entry already has a GOT entry. */ + if (lsb->lsb_got) + return; + + /* Reserve GOT entries. */ + lsb->lsb_got_off = ld_input_reserve_ibuf(is, num); + lsb->lsb_got = 1; +} + +static void +_reserve_gotplt_entry(struct ld *ld, struct ld_symbol *lsb) +{ + struct ld_input_section *is; + uint64_t off; + + is = _find_and_create_gotplt_section(ld, 1); + + /* Reserve a GOT entry for PLT. */ + off = ld_input_reserve_ibuf(is, 1); + + /* + * Record a R_386_JMP_SLOT entry for this symbol. Note that + * we don't need to record the offset (relative to the GOT section) + * here, since the PLT relocations will be sorted later and we + * will generate GOT section according to the new order. + */ + _create_plt_reloc(ld, lsb, 0); +} + +static void +_reserve_plt_entry(struct ld *ld, struct ld_symbol *lsb) +{ + struct ld_input_section *is; + + is = _find_and_create_plt_section(ld, 1); + + (void) ld_input_reserve_ibuf(is, 1); + lsb->lsb_plt = 1; +} + +static void +_create_plt_reloc(struct ld *ld, struct ld_symbol *lsb, uint64_t offset) +{ + + ld_reloc_create_entry(ld, ".rel.plt", NULL, R_386_JMP_SLOT, + lsb, offset, 0); + + lsb->lsb_dynrel = 1; +} + +static void +_create_got_reloc(struct ld *ld, struct ld_symbol *lsb, uint64_t type, + uint64_t offset) +{ + struct ld_input_section *tis; + + tis = _find_and_create_got_section(ld, 0); + assert(tis != NULL); + + ld_reloc_create_entry(ld, ".rel.got", tis, type, lsb, offset, 0); + + if (type != R_386_RELATIVE) + lsb->lsb_dynrel = 1; +} + +static void +_create_copy_reloc(struct ld *ld, struct ld_symbol *lsb) +{ + struct ld_input_section *tis; + + ld_dynamic_reserve_dynbss_entry(ld, lsb); + + tis = ld_input_find_internal_section(ld, ".dynbss"); + assert(tis != NULL); + + ld_reloc_create_entry(ld, ".rel.bss", tis, R_386_COPY, lsb, + lsb->lsb_value, 0); + + lsb->lsb_dynrel = 1; +} + +static void +_create_dynamic_reloc(struct ld *ld, struct ld_input_section *is, + struct ld_symbol *lsb, uint64_t type, uint64_t offset) +{ + + if (lsb->lsb_bind == STB_LOCAL) { + if (is->is_flags & SHF_WRITE) + ld_reloc_create_entry(ld, ".rel.data.rel.local", + is, type, lsb, offset, 0); + else + ld_reloc_create_entry(ld, ".rel.data.rel.ro.local", + is, type, lsb, offset, 0); + } else { + if (is->is_flags & SHF_WRITE) + ld_reloc_create_entry(ld, ".rel.data.rel", + is, type, lsb, offset, 0); + else + ld_reloc_create_entry(ld, ".rel.data.rel.ro", + is, type, lsb, offset, 0); + } + + if (type != R_386_RELATIVE) + lsb->lsb_dynrel = 1; +} + +static void +_scan_reloc(struct ld *ld, struct ld_input_section *is, + struct ld_reloc_entry *lre) +{ + struct ld_symbol *lsb; + + lsb = ld_symbols_ref(lre->lre_sym); + + switch (lre->lre_type) { + case R_386_NONE: + break; + + case R_386_32: + /* + * For a local symbol, if te linker output a PIE or DSO, + * we should generate a R_386_RELATIVE reloc for R_386_32. + */ + if (lsb->lsb_bind == STB_LOCAL) { + if (ld->ld_pie || ld->ld_dso) + _create_dynamic_reloc(ld, is, lsb, + R_386_RELATIVE, lre->lre_offset); + break; + } + + /* + * For a global symbol, we probably need to generate PLE entry + * and/ore a dynamic relocation. + * + * Note here, normally the compiler will generate a PC-relative + * relocation for function calls. However, if the code retrieve + * the address of a function and call it indirectly, assembler + * will generate absolute relocation instead. That's why we + * should check if we need to create a PLT entry here. Also, if + * we're going to create the PLT entry, we should also set the + * symbol value to the address of PLT entry just in case the + * function address is used to compare with other function + * addresses. (If PLT address is used, function will have + * unified address in the main executable and DSOs) + */ + if (ld_reloc_require_plt(ld, lre)) { + if (!lsb->lsb_plt) { + _reserve_gotplt_entry(ld, lsb); + _reserve_plt_entry(ld, lsb); + } + /* + * Note here even if we have generated PLT for this + * function before, we still need to set this flag. + * It's possible that we first see the relative + * relocation then this absolute relocation, in + * other words, the same function can be called in + * different ways. + */ + lsb->lsb_func_addr = 1; + } + + if (ld_reloc_require_copy_reloc(ld, lre) && + !lsb->lsb_copy_reloc) + _create_copy_reloc(ld, lsb); + else if (ld_reloc_require_dynamic_reloc(ld, lre)) { + /* + * Check if we can relax R_386_32 to + * R_386_RELATIVE instead. + */ + if (ld_reloc_relative_relax(ld, lre)) + _create_dynamic_reloc(ld, is, lsb, + R_386_RELATIVE, lre->lre_offset); + else + _create_dynamic_reloc(ld, is, lsb, + R_386_32, lre->lre_offset); + } + + break; + + case R_386_PLT32: + /* + * In some cases we don't really need to generate a PLT + * entry, then a R_386_PLT32 relocation can be relaxed + * to a R_386_PC32 relocation. + */ + if (lsb->lsb_bind == STB_LOCAL || + !ld_reloc_require_plt(ld, lre)) { + lre->lre_type = R_386_PC32; + break; + } + + /* + * If linker outputs an normal executable and the symbol is + * defined but is not defined inside a DSO, we can generate + * a R_386_PC32 relocation instead. + */ + if (ld->ld_exec && lsb->lsb_shndx != SHN_UNDEF && + (lsb->lsb_input == NULL || + lsb->lsb_input->li_type != LIT_DSO)) { + lre->lre_type = R_386_PC32; + break; + } + + /* Create an PLT entry otherwise. */ + if (!lsb->lsb_plt) { + _reserve_gotplt_entry(ld, lsb); + _reserve_plt_entry(ld, lsb); + } + break; + + case R_386_PC32: + /* + * When R_386_PC32 apply to a global symbol, we should + * check if we need to generate PLT entry and/or a dynamic + * relocation. + */ + if (lsb->lsb_bind != STB_LOCAL) { + if (ld_reloc_require_plt(ld, lre) && !lsb->lsb_plt) { + _reserve_gotplt_entry(ld, lsb); + _reserve_plt_entry(ld, lsb); + } + + if (ld_reloc_require_copy_reloc(ld, lre) && + !lsb->lsb_copy_reloc) + _create_copy_reloc(ld, lsb); + else if (ld_reloc_require_dynamic_reloc(ld, lre)) { + /* + * We can not generate dynamic relocation for + * these PC-relative relocation since they + * are probably not supported by the runtime + * linkers. + */ + _warn_pic(ld, lre); + } + } + break; + + case R_386_GOTOFF: + case R_386_GOTPC: + /* + * These relocation types use GOT address as a base address + * and instruct the linker to build a GOT. + */ + (void) _find_and_create_got_section(ld, 1); + break; + + case R_386_GOT32: + /* + * R_386_GOT32 relocation instructs the linker to build a + * GOT and generate a GOT entry. + */ + if (!lsb->lsb_got) { + _reserve_got_entry(ld, lsb, 1); + /* + * TODO: For now we always create a R_386_GLOB_DAT + * relocation for a GOT entry. There are cases that + * the symbol's address is known at link time and + * the GOT entry value can be filled in by the program + * linker instead. + */ + if (ld_reloc_require_glob_dat(ld, lre)) + _create_got_reloc(ld, lsb, R_386_GLOB_DAT, + lsb->lsb_got_off); + else + _create_got_reloc(ld, lsb, R_386_RELATIVE, + lsb->lsb_got_off); + } + + default: + ld_warn(ld, "can not handle relocation %ju", + lre->lre_type); + break; + } +} + +static uint32_t +_got_offset(struct ld *ld, struct ld_symbol *lsb) +{ + struct ld_output_section *os; + + assert(lsb->lsb_got); + + if (ld->ld_got == NULL) { + ld->ld_got = _find_and_create_got_section(ld, 0); + assert(ld->ld_got != NULL); + } + + os = ld->ld_got->is_output; + + return (os->os_addr + ld->ld_got->is_reloff + lsb->lsb_got_off); +} + +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_state *ls; + struct ld_output *lo; + uint32_t p, s, l, g, got; + int32_t a, v; + + ls = &ld->ld_state; + + lo = ld->ld_output; + assert(lo != NULL); + + l = lsb->lsb_plt_off; + p = lre->lre_offset + is->is_output->os_addr + is->is_reloff; + got = ld->ld_got->is_output->os_addr; + s = (uint32_t) lsb->lsb_value; + READ_32(buf + lre->lre_offset, a); + + switch (lre->lre_type) { + case R_386_NONE: + break; + + case R_386_32: + v = s + a; + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_386_PC32: + if (lsb->lsb_plt) + v = l + a - p; + else + v = s + a - p; + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_386_PLT32: + if (!ls->ls_ignore_next_plt) { + v = l + a - p; + WRITE_32(buf + lre->lre_offset, v); + } else + ls->ls_ignore_next_plt = 0; + break; + + case R_386_GOT32: + g = _got_offset(ld, lsb); + v = g + a; + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_386_GOTOFF: + v = s + a - got; + WRITE_32(buf + lre->lre_offset, v); + break; + + case R_386_GOTPC: + v = got + a - p; + WRITE_32(buf + lre->lre_offset, v); + break; + + default: + ld_fatal(ld, "Relocation %d not supported", lre->lre_type); + break; + } +} + +void +i386_register(struct ld *ld) +{ + struct ld_arch *i386_arch; + + if ((i386_arch = calloc(1, sizeof(*i386_arch))) == NULL) + ld_fatal_std(ld, "calloc"); + + snprintf(i386_arch->name, sizeof(i386_arch->name), "%s", "i386"); + + i386_arch->script = i386_script; + i386_arch->get_max_page_size = _get_max_page_size; + i386_arch->get_common_page_size = _get_common_page_size; + i386_arch->scan_reloc = _scan_reloc; + i386_arch->process_reloc = _process_reloc; + i386_arch->is_absolute_reloc = _is_absolute_reloc; + i386_arch->is_relative_reloc = _is_relative_reloc; + i386_arch->reloc_is_64bit = 0; + i386_arch->reloc_is_rela = 0; + i386_arch->reloc_entsize = sizeof(Elf32_Rel); + + HASH_ADD_STR(ld->ld_arch_list, name, i386_arch); +} |