diff options
Diffstat (limited to 'ld/ld_input.c')
-rw-r--r-- | ld/ld_input.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/ld/ld_input.c b/ld/ld_input.c new file mode 100644 index 0000000000000..bcef6109216fb --- /dev/null +++ b/ld/ld_input.c @@ -0,0 +1,653 @@ +/*- + * Copyright (c) 2011-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_file.h" +#include "ld_input.h" +#include "ld_symbols.h" + +ELFTC_VCSID("$Id: ld_input.c 2960 2013-08-25 03:13:07Z kaiwang27 $"); + +/* + * Support routines for input section handling. + */ + +static void _discard_section_group(struct ld *ld, struct ld_input *li, + Elf_Scn *scn); +static off_t _offset_sort(struct ld_archive_member *a, + struct ld_archive_member *b); + +#define _MAX_INTERNAL_SECTIONS 16 + +void +ld_input_init(struct ld *ld) +{ + struct ld_input *li; + struct ld_input_section *is; + + assert(STAILQ_EMPTY(&ld->ld_lilist)); + + /* + * Create an internal pseudo input object to hold internal + * input sections. + */ + + li = ld_input_alloc(ld, NULL, NULL); + + li->li_is = calloc(_MAX_INTERNAL_SECTIONS, + sizeof(struct ld_input_section)); + if (li->li_is == NULL) + ld_fatal_std(ld, "calloc"); + + STAILQ_INSERT_TAIL(&ld->ld_lilist, li, li_next); + + /* + * Create an initial SHT_NULL section for the pseudo input object, + * so all the internal sections will have valid section index. + * (other than SHN_UNDEF) + */ + is = &li->li_is[li->li_shnum]; + if ((is->is_name = strdup("")) == NULL) + ld_fatal_std(ld, "strdup"); + is->is_input = li; + is->is_type = SHT_NULL; + is->is_index = li->li_shnum; + li->li_shnum++; +} + +struct ld_input_section * +ld_input_add_internal_section(struct ld *ld, const char *name) +{ + struct ld_input *li; + struct ld_input_section *is; + + li = STAILQ_FIRST(&ld->ld_lilist); + assert(li != NULL); + + if (li->li_shnum >= _MAX_INTERNAL_SECTIONS) + ld_fatal(ld, "Internal: not enough buffer for internal " + "sections"); + + is = &li->li_is[li->li_shnum]; + if ((is->is_name = strdup(name)) == NULL) + ld_fatal_std(ld, "strdup"); + is->is_input = li; + is->is_index = li->li_shnum; + + /* Use a hash table to accelerate lookup for internal sections. */ + HASH_ADD_KEYPTR(hh, li->li_istbl, is->is_name, strlen(is->is_name), + is); + + li->li_shnum++; + + return (is); +} + +struct ld_input_section * +ld_input_find_internal_section(struct ld *ld, const char *name) +{ + struct ld_input *li; + struct ld_input_section *is; + char _name[32]; + + li = STAILQ_FIRST(&ld->ld_lilist); + assert(li != NULL); + + snprintf(_name, sizeof(_name), "%s", name); + HASH_FIND_STR(li->li_istbl, _name, is); + + return (is); +} + +uint64_t +ld_input_reserve_ibuf(struct ld_input_section *is, uint64_t n) +{ + uint64_t off; + + assert(is->is_entsize != 0); + + off = is->is_size; + is->is_size += n * is->is_entsize; + + return (off); +} + +void +ld_input_alloc_internal_section_buffers(struct ld *ld) +{ + struct ld_input *li; + struct ld_input_section *is; + int i; + + li = STAILQ_FIRST(&ld->ld_lilist); + assert(li != NULL); + + for (i = 0; (uint64_t) i < li->li_shnum; i++) { + is = &li->li_is[i]; + + if (is->is_type == SHT_NOBITS || is->is_size == 0 || + is->is_dynrel) + continue; + + if ((is->is_ibuf = malloc(is->is_size)) == NULL) + ld_fatal_std(ld, "malloc"); + } +} + +void +ld_input_cleanup(struct ld *ld) +{ + struct ld_input *li, *_li; + int i; + + STAILQ_FOREACH_SAFE(li, &ld->ld_lilist, li_next, _li) { + STAILQ_REMOVE(&ld->ld_lilist, li, ld_input, li_next); + if (li->li_symindex) + free(li->li_symindex); + if (li->li_local) + free(li->li_local); + if (li->li_versym) + free(li->li_versym); + if (li->li_vername) { + for (i = 0; (size_t) i < li->li_vername_sz; i++) + if (li->li_vername[i]) + free(li->li_vername[i]); + free(li->li_vername); + } + if (li->li_is) + free(li->li_is); + if (li->li_fullname) + free(li->li_fullname); + if (li->li_name) + free(li->li_name); + if (li->li_soname) + free(li->li_soname); + free(li); + } +} + +void +ld_input_add_symbol(struct ld *ld, struct ld_input *li, struct ld_symbol *lsb) +{ + + if (li->li_symindex == NULL) { + assert(li->li_symnum != 0); + li->li_symindex = calloc(li->li_symnum, + sizeof(*li->li_symindex)); + if (li->li_symindex == NULL) + ld_fatal_std(ld, "calloc"); + } + + li->li_symindex[lsb->lsb_index] = lsb; + + if (lsb->lsb_bind == STB_LOCAL) { + if (li->li_local == NULL) { + li->li_local = calloc(1, sizeof(*li->li_local)); + if (li->li_local == NULL) + ld_fatal_std(ld, "calloc"); + STAILQ_INIT(li->li_local); + } + STAILQ_INSERT_TAIL(li->li_local, lsb, lsb_next); + } +} + +struct ld_input * +ld_input_alloc(struct ld *ld, struct ld_file *lf, const char *name) +{ + struct ld_input *li; + + if ((li = calloc(1, sizeof(*li))) == NULL) + ld_fatal_std(ld, "calloc"); + + if (name != NULL && (li->li_name = strdup(name)) == NULL) + ld_fatal_std(ld, "strdup"); + + li->li_file = lf; + + if (lf != NULL) { + switch (lf->lf_type) { + case LFT_ARCHIVE: + case LFT_RELOCATABLE: + li->li_type = LIT_RELOCATABLE; + break; + case LFT_DSO: + li->li_type = LIT_DSO; + break; + case LFT_BINARY: + case LFT_UNKNOWN: + default: + li->li_type = LIT_UNKNOWN; + break; + } + } else + li->li_type = LIT_RELOCATABLE; + + return (li); +} + +char * +ld_input_get_fullname(struct ld *ld, struct ld_input *li) +{ + struct ld_archive_member *lam; + size_t len; + + if (li->li_fullname != NULL) + return (li->li_fullname); + + if (li->li_lam == NULL) + return (li->li_name); + + lam = li->li_lam; + len = strlen(lam->lam_ar_name) + strlen(lam->lam_name) + 3; + if ((li->li_fullname = malloc(len)) == NULL) + ld_fatal_std(ld, "malloc"); + snprintf(li->li_fullname, len, "%s(%s)", lam->lam_ar_name, + lam->lam_name); + + return (li->li_fullname); +} + +void +ld_input_link_objects(struct ld *ld) +{ + struct ld_file *lf; + struct ld_archive_member *lam, *tmp; + struct ld_input *li; + + TAILQ_FOREACH(lf, &ld->ld_lflist, lf_next) { + if (lf->lf_ar != NULL) { + HASH_SORT(lf->lf_ar->la_m, _offset_sort); + HASH_ITER(hh, lf->lf_ar->la_m, lam, tmp) { + li = lam->lam_input; + if (li != NULL) + STAILQ_INSERT_TAIL(&ld->ld_lilist, li, + li_next); + } + } else { + li = lf->lf_input; + if (li != NULL) + STAILQ_INSERT_TAIL(&ld->ld_lilist, li, li_next); + } + } +} + +void * +ld_input_get_section_rawdata(struct ld *ld, struct ld_input_section *is) +{ + Elf *e; + Elf_Scn *scn; + Elf_Data *d; + struct ld_input *li; + char *buf; + int elferr; + + li = is->is_input; + e = li->li_elf; + assert(e != NULL); + + if ((scn = elf_getscn(e, is->is_index)) == NULL) + ld_fatal(ld, "%s(%s): elf_getscn failed: %s", li->li_name, + is->is_name, elf_errmsg(-1)); + + (void) elf_errno(); + if ((d = elf_rawdata(scn, NULL)) == NULL) { + elferr = elf_errno(); + if (elferr != 0) + ld_warn(ld, "%s(%s): elf_rawdata failed: %s", + li->li_name, is->is_name, elf_errmsg(elferr)); + return (NULL); + } + + if (d->d_buf == NULL || d->d_size == 0) + return (NULL); + + if ((buf = malloc(d->d_size)) == NULL) + ld_fatal_std(ld, "malloc"); + + memcpy(buf, d->d_buf, d->d_size); + + return (buf); +} + +void +ld_input_load(struct ld *ld, struct ld_input *li) +{ + struct ld_state *ls; + struct ld_file *lf; + struct ld_archive_member *lam; + + if (li->li_file == NULL) + return; + + assert(li->li_elf == NULL); + ls = &ld->ld_state; + if (li->li_file != ls->ls_file) { + if (ls->ls_file != NULL) + ld_file_unload(ld, ls->ls_file); + ld_file_load(ld, li->li_file); + } + lf = li->li_file; + if (lf->lf_ar != NULL) { + assert(li->li_lam != NULL); + lam = li->li_lam; + if (elf_rand(lf->lf_elf, lam->lam_off) != lam->lam_off) + ld_fatal(ld, "%s: elf_rand: %s", lf->lf_name, + elf_errmsg(-1)); + if ((li->li_elf = elf_begin(-1, ELF_C_READ, lf->lf_elf)) == + NULL) + ld_fatal(ld, "%s: elf_begin: %s", lf->lf_name, + elf_errmsg(-1)); + } else + li->li_elf = lf->lf_elf; +} + +void +ld_input_unload(struct ld *ld, struct ld_input *li) +{ + struct ld_file *lf; + + (void) ld; + + if (li->li_file == NULL) + return; + + assert(li->li_elf != NULL); + lf = li->li_file; + if (lf->lf_ar != NULL) + (void) elf_end(li->li_elf); + li->li_elf = NULL; +} + +void +ld_input_init_sections(struct ld *ld, struct ld_input *li, Elf *e) +{ + struct ld_input_section *is; + struct ld_section_group *sg; + Elf_Scn *scn, *_scn; + Elf_Data *d, *_d; + char *name; + GElf_Shdr sh; + GElf_Sym sym; + size_t shstrndx, strndx, ndx; + int elferr; + + _d = NULL; + strndx = 0; + + if (elf_getshdrnum(e, &li->li_shnum) < 0) + ld_fatal(ld, "%s: elf_getshdrnum: %s", li->li_name, + elf_errmsg(-1)); + + /* Allocate one more pseudo section to hold common symbols */ + li->li_shnum++; + + assert(li->li_is == NULL); + if ((li->li_is = calloc(li->li_shnum, sizeof(*is))) == NULL) + ld_fatal_std(ld, "%s: calloc: %s", li->li_name); + + if (elf_getshdrstrndx(e, &shstrndx) < 0) + ld_fatal(ld, "%s: elf_getshdrstrndx: %s", li->li_name, + elf_errmsg(-1)); + + (void) elf_errno(); + scn = NULL; + while ((scn = elf_nextscn(e, scn)) != NULL) { + if (gelf_getshdr(scn, &sh) != &sh) + ld_fatal(ld, "%s: gelf_getshdr: %s", li->li_name, + elf_errmsg(-1)); + + if ((name = elf_strptr(e, shstrndx, sh.sh_name)) == NULL) + ld_fatal(ld, "%s: elf_strptr: %s", li->li_name, + elf_errmsg(-1)); + + if ((ndx = elf_ndxscn(scn)) == SHN_UNDEF) + ld_fatal(ld, "%s: elf_ndxscn: %s", li->li_name, + elf_errmsg(-1)); + + if (ndx >= li->li_shnum - 1) + ld_fatal(ld, "%s: section index of '%s' section is" + " invalid", li->li_name, name); + + is = &li->li_is[ndx]; + if ((is->is_name = strdup(name)) == NULL) + ld_fatal_std(ld, "%s: calloc", li->li_name); + is->is_off = sh.sh_offset; + is->is_size = sh.sh_size; + is->is_entsize = sh.sh_entsize; + is->is_addr = sh.sh_addr; + is->is_align = sh.sh_addralign; + is->is_type = sh.sh_type; + is->is_flags = sh.sh_flags; + is->is_link = sh.sh_link; + is->is_info = sh.sh_info; + is->is_index = elf_ndxscn(scn); + is->is_shrink = 0; + is->is_input = li; + + /* + * Section groups are identified by their signatures. + * A section group's signature is used to compare with the + * the section groups that are already added. If a match + * is found, the sections included in this section group + * should be discarded. + * + * Note that since signatures are stored in the symbol + * table, in order to handle that here we have to load + * the symbol table earlier. + */ + if (is->is_type == SHT_GROUP) { + is->is_discard = 1; + if (_d == NULL) { + _scn = elf_getscn(e, is->is_link); + if (_scn == NULL) { + ld_warn(ld, "%s: elf_getscn failed" + " with the `sh_link' of group" + " section %ju as index: %s", + li->li_name, ndx, elf_errmsg(-1)); + continue; + } + if (gelf_getshdr(_scn, &sh) != &sh) { + ld_warn(ld, "%s: gelf_getshdr: %s", + li->li_name, elf_errmsg(-1)); + continue; + } + strndx = sh.sh_link; + (void) elf_errno(); + _d = elf_getdata(_scn, NULL); + if (_d == NULL) { + elferr = elf_errno(); + if (elferr != 0) + ld_warn(ld, "%s: elf_getdata" + " failed: %s", li->li_name, + elf_errmsg(elferr)); + continue; + } + } + if (gelf_getsym(_d, is->is_info, &sym) != &sym) { + ld_warn(ld, "%s: gelf_getsym failed (section" + " group signature): %s", li->li_name, + elf_errmsg(-1)); + continue; + } + if ((name = elf_strptr(e, strndx, sym.st_name)) == + NULL) { + ld_warn(ld, "%s: elf_strptr failed (section" + " group signature): %s", li->li_name, + elf_errmsg(-1)); + continue; + } + + /* + * Search the currently added section groups for the + * signature. If found, this section group should not + * be added and the sections it contains should be + * discarded. If not found, we add this section group + * to the set. + */ + HASH_FIND_STR(ld->ld_sg, name, sg); + if (sg != NULL) + _discard_section_group(ld, li, scn); + else { + if ((sg = calloc(1, sizeof(*sg))) == NULL) + ld_fatal_std(ld, "%s: calloc", + li->li_name); + if ((sg->sg_name = strdup(name)) == NULL) + ld_fatal_std(ld, "%s: strdup", + li->li_name); + HASH_ADD_KEYPTR(hh, ld->ld_sg, sg->sg_name, + strlen(sg->sg_name), sg); + } + } + + /* + * Check for informational sections which should not + * be included in the output object, process them + * and mark them as discarded if need. + */ + + if (strcmp(is->is_name, ".note.GNU-stack") == 0) { + ld->ld_gen_gnustack = 1; + if (is->is_flags & SHF_EXECINSTR) + ld->ld_stack_exec = 1; + is->is_discard = 1; + continue; + } + + /* + * The content of input .eh_frame section is preloaded for + * output .eh_frame optimization. + */ + if (strcmp(is->is_name, ".eh_frame") == 0) { + if ((d = elf_rawdata(scn, NULL)) == NULL) { + elferr = elf_errno(); + if (elferr != 0) + ld_warn(ld, "%s(%s): elf_rawdata " + "failed: %s", li->li_name, + is->is_name, elf_errmsg(elferr)); + continue; + } + + if (d->d_buf == NULL || d->d_size == 0) + continue; + + if ((is->is_ehframe = malloc(d->d_size)) == NULL) + ld_fatal_std(ld, "malloc"); + + memcpy(is->is_ehframe, d->d_buf, d->d_size); + is->is_ibuf = is->is_ehframe; + } + } + elferr = elf_errno(); + if (elferr != 0) + ld_fatal(ld, "%s: elf_nextscn failed: %s", li->li_name, + elf_errmsg(elferr)); +} + +void +ld_input_alloc_common_symbol(struct ld *ld, struct ld_symbol *lsb) +{ + struct ld_input *li; + struct ld_input_section *is; + + li = lsb->lsb_input; + if (li == NULL) + return; /* unlikely */ + + /* + * Do not allocate memory for common symbols when the linker + * creates a relocatable output object, unless option -d is + * specified. + */ + if (ld->ld_reloc && !ld->ld_common_alloc) + return; + + is = &li->li_is[li->li_shnum - 1]; + if (is->is_name == NULL) { + /* + * Create a pseudo section named COMMON to keep track of + * common symbols. + */ + if ((is->is_name = strdup("COMMON")) == NULL) + ld_fatal_std(ld, "%s: calloc", li->li_name); + is->is_off = 0; + is->is_size = 0; + is->is_entsize = 0; + is->is_align = 1; + is->is_type = SHT_NOBITS; + is->is_flags = SHF_ALLOC | SHF_WRITE; + is->is_link = 0; + is->is_info = 0; + is->is_index = SHN_COMMON; + is->is_input = li; + } + + /* + * Allocate space for this symbol in the pseudo COMMON section. + * Properly handle the alignment. (For common symbols, symbol + * value stores the required alignment) + */ + if (lsb->lsb_value > is->is_align) + is->is_align = lsb->lsb_value; + is->is_size = roundup(is->is_size, is->is_align); + lsb->lsb_value = is->is_size; + is->is_size += lsb->lsb_size; +} + +static void +_discard_section_group(struct ld *ld, struct ld_input *li, Elf_Scn *scn) +{ + Elf_Data *d; + uint32_t *w; + int elferr, i; + + (void) elf_errno(); + if ((d = elf_getdata(scn, NULL)) == NULL) { + elferr = elf_errno(); + if (elferr != 0) + ld_warn(ld, "%s: elf_getdata failed (section group):" + " %s", li->li_name, elf_errmsg(elferr)); + return; + } + + if (d->d_buf == NULL || d->d_size == 0) + return; + + w = d->d_buf; + if ((*w & GRP_COMDAT) == 0) + return; + + for (i = 1; (size_t) i < d->d_size / 4; i++) { + if (w[i] < li->li_shnum - 1) + li->li_is[w[i]].is_discard = 1; + } +} + +static off_t +_offset_sort(struct ld_archive_member *a, struct ld_archive_member *b) +{ + + return (a->lam_off - b->lam_off); +} |