/*- * Copyright (c) 2010-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_dynamic.h" #include "ld_file.h" #include "ld_input.h" #include "ld_output.h" #include "ld_symbols.h" #include "ld_symver.h" #include "ld_script.h" #include "ld_strtab.h" ELFTC_VCSID("$Id: ld_symbols.c 3281 2015-12-11 21:39:23Z kaiwang27 $"); #define _INIT_SYMTAB_SIZE 128 static void _load_symbols(struct ld *ld, struct ld_file *lf); static void _load_archive_symbols(struct ld *ld, struct ld_file *lf); static void _load_elf_symbols(struct ld *ld, struct ld_input *li, Elf *e); static void _unload_symbols(struct ld_input *li); static void _add_elf_symbol(struct ld *ld, struct ld_input *li, Elf *e, GElf_Sym *sym, size_t strndx, int i); static void _add_to_dynsym_table(struct ld *ld, struct ld_symbol *lsb); static void _write_to_dynsym_table(struct ld *ld, struct ld_symbol *lsb); static void _add_to_symbol_table(struct ld *ld, struct ld_symbol *lsb); static void _free_symbol_table(struct ld_symbol_table *symtab); struct ld_symbol_table *_alloc_symbol_table(struct ld *ld); static int _archive_member_extracted(struct ld_archive *la, off_t off); static struct ld_archive_member * _extract_archive_member(struct ld *ld, struct ld_file *lf, struct ld_archive *la, off_t off); static void _print_extracted_member(struct ld *ld, struct ld_archive_member *lam, struct ld_symbol *lsb); static void _resolve_and_add_symbol(struct ld *ld, struct ld_symbol *lsb); static struct ld_symbol *_alloc_symbol(struct ld *ld); static void _free_symbol(struct ld_symbol *lsb); static struct ld_symbol *_find_symbol(struct ld_symbol *tbl, char *name); static void _update_symbol(struct ld_symbol *lsb); #define _add_symbol(tbl, s) do { \ HASH_ADD_KEYPTR(hh, (tbl), (s)->lsb_longname, \ strlen((s)->lsb_longname), (s)); \ } while (0) #define _remove_symbol(tbl, s) do { \ HASH_DEL((tbl), (s)); \ } while (0) #define _resolve_symbol(_s, s) do { \ assert((_s) != (s)); \ (s)->lsb_ref_dso |= (_s)->lsb_ref_dso; \ (s)->lsb_ref_ndso |= (_s)->lsb_ref_ndso; \ if ((s)->lsb_prev != NULL) { \ (s)->lsb_prev->lsb_ref = (_s); \ (_s)->lsb_prev = (s)->lsb_prev; \ } \ (s)->lsb_prev = (_s); \ (_s)->lsb_ref = (s); \ } while (0) void ld_symbols_cleanup(struct ld *ld) { struct ld_input *li; struct ld_symbol *lsb, *_lsb; HASH_CLEAR(hh, ld->ld_sym); STAILQ_FOREACH(li, &ld->ld_lilist, li_next) { _unload_symbols(li); } if (ld->ld_ext_symbols != NULL) { STAILQ_FOREACH_SAFE(lsb, ld->ld_ext_symbols, lsb_next, _lsb) { STAILQ_REMOVE(ld->ld_ext_symbols, lsb, ld_symbol, lsb_next); _free_symbol(lsb); } free(ld->ld_ext_symbols); ld->ld_ext_symbols = NULL; } if (ld->ld_var_symbols != NULL) { STAILQ_FOREACH_SAFE(lsb, ld->ld_var_symbols, lsb_next, _lsb) { STAILQ_REMOVE(ld->ld_var_symbols, lsb, ld_symbol, lsb_next); _free_symbol(lsb); } free(ld->ld_var_symbols); ld->ld_var_symbols = NULL; } if (ld->ld_dyn_symbols != NULL) { free(ld->ld_dyn_symbols); ld->ld_dyn_symbols = NULL; } if (ld->ld_symtab != NULL) { _free_symbol_table(ld->ld_symtab); ld->ld_symtab = NULL; } if (ld->ld_strtab != NULL) { ld_strtab_free(ld->ld_strtab); ld->ld_strtab = NULL; } } void ld_symbols_add_extern(struct ld *ld, char *name) { struct ld_symbol *lsb; /* Check if the extern symbol has been added before. */ if (_find_symbol(ld->ld_sym, name) != NULL) return; lsb = _alloc_symbol(ld); if ((lsb->lsb_name = strdup(name)) == NULL) ld_fatal_std(ld, "strdup"); if ((lsb->lsb_longname = strdup(name)) == NULL) ld_fatal_std(ld, "strdup"); if (ld->ld_ext_symbols == NULL) { ld->ld_ext_symbols = malloc(sizeof(*ld->ld_ext_symbols)); if (ld->ld_ext_symbols == NULL) ld_fatal_std(ld, "malloc"); STAILQ_INIT(ld->ld_ext_symbols); } STAILQ_INSERT_TAIL(ld->ld_ext_symbols, lsb, lsb_next); _add_symbol(ld->ld_sym, lsb); } void ld_symbols_add_variable(struct ld *ld, struct ld_script_variable *ldv, unsigned provide, unsigned hidden) { struct ld_symbol *lsb; lsb = _alloc_symbol(ld); if ((lsb->lsb_name = strdup(ldv->ldv_name)) == NULL) ld_fatal_std(ld, "strdup"); if ((lsb->lsb_longname = strdup(ldv->ldv_name)) == NULL) ld_fatal_std(ld, "strdup"); lsb->lsb_var = ldv; lsb->lsb_bind = STB_GLOBAL; lsb->lsb_shndx = SHN_ABS; lsb->lsb_provide = provide; if (hidden) lsb->lsb_other = STV_HIDDEN; lsb->lsb_ref_ndso = 1; ldv->ldv_symbol = lsb; ldv->ldv_os_ref = ld->ld_scp->lds_last_os_name; ldv->ldv_os_base = ld->ld_scp->lds_base_os_name; if (ld->ld_var_symbols == NULL) { ld->ld_var_symbols = malloc(sizeof(*ld->ld_var_symbols)); if (ld->ld_var_symbols == NULL) ld_fatal_std(ld, "malloc"); STAILQ_INIT(ld->ld_var_symbols); } STAILQ_INSERT_TAIL(ld->ld_var_symbols, lsb, lsb_next); _resolve_and_add_symbol(ld, lsb); } void ld_symbols_add_internal(struct ld *ld, const char *name, uint64_t size, uint64_t value, uint16_t shndx, unsigned char bind, unsigned char type, unsigned char other, struct ld_input_section *is, struct ld_output_section *preset_os) { struct ld_symbol *lsb; lsb = _alloc_symbol(ld); if ((lsb->lsb_name = strdup(name)) == NULL) ld_fatal_std(ld, "strdup"); if ((lsb->lsb_longname = strdup(name)) == NULL) ld_fatal_std(ld, "strdup"); lsb->lsb_size = size; lsb->lsb_value = value; lsb->lsb_shndx = shndx; lsb->lsb_bind = bind; lsb->lsb_type = type; lsb->lsb_other = other; lsb->lsb_preset_os = preset_os; lsb->lsb_ref_ndso = 1; lsb->lsb_input = (is == NULL) ? NULL : is->is_input; lsb->lsb_is = is; _resolve_and_add_symbol(ld, lsb); } int ld_symbols_get_value(struct ld *ld, char *name, uint64_t *val) { struct ld_symbol *lsb; if ((lsb = _find_symbol(ld->ld_sym, name)) != NULL) *val = lsb->lsb_value; else return (-1); return (0); } void ld_symbols_resolve(struct ld *ld) { struct ld_state *ls; struct ld_file *lf; struct ld_symbol *lsb, *_lsb; if (TAILQ_EMPTY(&ld->ld_lflist)) { if (ld->ld_print_version) exit(EXIT_SUCCESS); else ld_fatal(ld, "no input files"); } ls = &ld->ld_state; lf = TAILQ_FIRST(&ld->ld_lflist); ls->ls_group_level = lf->lf_group_level; while (lf != NULL) { /* Process archive groups. */ if (lf->lf_group_level < ls->ls_group_level && ls->ls_extracted[ls->ls_group_level]) { do { lf = TAILQ_PREV(lf, ld_file_head, lf_next); } while (lf->lf_group_level >= ls->ls_group_level); lf = TAILQ_NEXT(lf, lf_next); ls->ls_extracted[ls->ls_group_level] = 0; } ls->ls_group_level = lf->lf_group_level; /* Load symbols. */ ld_file_load(ld, lf); if (ls->ls_arch_conflict) { ld_file_unload(ld, lf); return; } _load_symbols(ld, lf); ld_file_unload(ld, lf); lf = TAILQ_NEXT(lf, lf_next); } /* Print information regarding space allocated for common symbols. */ if (ld->ld_print_linkmap) { printf("\nCommon symbols:\n"); printf("%-34s %-10s %s\n", "name", "size", "file"); HASH_ITER(hh, ld->ld_sym, lsb, _lsb) { if (lsb->lsb_shndx != SHN_COMMON) continue; printf("%-34s", lsb->lsb_name); if (strlen(lsb->lsb_name) > 34) printf("\n%-34s", ""); printf(" %#-10jx %s\n", (uintmax_t) lsb->lsb_size, ld_input_get_fullname(ld, lsb->lsb_input)); } } } void ld_symbols_update(struct ld *ld) { struct ld_input *li; struct ld_symbol *lsb, *_lsb; STAILQ_FOREACH(li, &ld->ld_lilist, li_next) { if (li->li_local == NULL) continue; STAILQ_FOREACH(lsb, li->li_local, lsb_next) _update_symbol(lsb); } HASH_ITER(hh, ld->ld_sym, lsb, _lsb) { /* Skip symbols from DSOs. */ if (ld_symbols_in_dso(lsb)) continue; _update_symbol(lsb); } } void ld_symbols_build_symtab(struct ld *ld) { struct ld_output *lo; struct ld_output_section *os, *_os; struct ld_input *li; struct ld_input_section *is; struct ld_symbol *lsb, *tmp, _lsb; lo = ld->ld_output; ld->ld_symtab = _alloc_symbol_table(ld); ld->ld_strtab = ld_strtab_alloc(ld, 0); /* Create an initial symbol at the beginning of symbol table. */ _lsb.lsb_name = NULL; _lsb.lsb_size = 0; _lsb.lsb_value = 0; _lsb.lsb_shndx = SHN_UNDEF; _lsb.lsb_bind = STB_LOCAL; _lsb.lsb_type = STT_NOTYPE; _lsb.lsb_other = 0; _add_to_symbol_table(ld, &_lsb); /* Create STT_SECTION symbols. */ STAILQ_FOREACH(os, &lo->lo_oslist, os_next) { if (os->os_empty) continue; if (os->os_secsym != NULL) continue; if (os->os_rel) continue; os->os_secsym = calloc(1, sizeof(*os->os_secsym)); if (os->os_secsym == NULL) ld_fatal_std(ld, "calloc"); os->os_secsym->lsb_name = NULL; os->os_secsym->lsb_size = 0; os->os_secsym->lsb_value = os->os_addr; os->os_secsym->lsb_shndx = elf_ndxscn(os->os_scn); os->os_secsym->lsb_bind = STB_LOCAL; os->os_secsym->lsb_type = STT_SECTION; os->os_secsym->lsb_other = 0; _add_to_symbol_table(ld, os->os_secsym); /* Create STT_SECTION symbols for relocation sections. */ if (os->os_r != NULL && !ld->ld_reloc) { _os = os->os_r; _os->os_secsym = calloc(1, sizeof(*_os->os_secsym)); if (_os->os_secsym == NULL) ld_fatal_std(ld, "calloc"); _os->os_secsym->lsb_name = NULL; _os->os_secsym->lsb_size = 0; _os->os_secsym->lsb_value = _os->os_addr; _os->os_secsym->lsb_shndx = elf_ndxscn(_os->os_scn); _os->os_secsym->lsb_bind = STB_LOCAL; _os->os_secsym->lsb_type = STT_SECTION; _os->os_secsym->lsb_other = 0; _add_to_symbol_table(ld, _os->os_secsym); } } /* Copy local symbols from each input object. */ STAILQ_FOREACH(li, &ld->ld_lilist, li_next) { if (li->li_local == NULL) continue; STAILQ_FOREACH(lsb, li->li_local, lsb_next) { if (lsb->lsb_type != STT_SECTION && lsb->lsb_index != 0) _add_to_symbol_table(ld, lsb); /* * Set the symbol index of the STT_SECTION symbols * to the index of the section symbol for the * corresponding output section. The updated * symbol index will be used by the relocation * serialization function If the linker generates * relocatable object or option -emit-relocs is * specified. */ if (lsb->lsb_type == STT_SECTION) { is = lsb->lsb_is; if (is->is_output != NULL) { os = is->is_output; assert(os->os_secsym != NULL); lsb->lsb_out_index = os->os_secsym->lsb_out_index; } } } } /* Copy resolved global symbols from hash table. */ HASH_ITER(hh, ld->ld_sym, lsb, tmp) { /* Skip undefined/unreferenced symbols from DSO. */ if (ld_symbols_in_dso(lsb) && (lsb->lsb_shndx == SHN_UNDEF || !lsb->lsb_ref_ndso)) continue; /* * Skip linker script defined symbols when creating * relocatable output object. */ if (lsb->lsb_input == NULL && ld->ld_reloc) continue; /* Skip "provide" symbols that are not referenced. */ if (lsb->lsb_provide && lsb->lsb_prev == NULL) continue; if (lsb->lsb_import) { if (lsb->lsb_type == STT_FUNC && lsb->lsb_func_addr) lsb->lsb_value = lsb->lsb_plt_off; else lsb->lsb_value = 0; lsb->lsb_shndx = SHN_UNDEF; } _add_to_symbol_table(ld, lsb); } } void ld_symbols_scan(struct ld *ld) { struct ld_symbol *lsb, *tmp; ld->ld_dynsym = _alloc_symbol_table(ld); if (ld->ld_dynstr == NULL) ld->ld_dynstr = ld_strtab_alloc(ld, 0); /* Reserve space for the initial symbol. */ ld->ld_dynsym->sy_size++; HASH_ITER(hh, ld->ld_sym, lsb, tmp) { /* * Warn undefined symbols if the linker is creating an * executable. */ if ((ld->ld_exec || ld->ld_pie) && lsb->lsb_shndx == SHN_UNDEF && lsb->lsb_bind != STB_WEAK) ld_warn(ld, "undefined symbol: %s", lsb->lsb_name); /* * Allocate space for common symbols and add them to the * special input section COMMON for section layout later. */ if (lsb->lsb_shndx == SHN_COMMON) ld_input_alloc_common_symbol(ld, lsb); /* * The code below handles the dynamic symbol table. If * we are doing a -static linking, we can skip. */ if (!ld->ld_dynamic_link) continue; /* * Following symbols should not be added to the dynamic * symbol table: * * 1. Do not add undefined symbols in DSOs. */ if (ld_symbols_in_dso(lsb) && lsb->lsb_shndx == SHN_UNDEF) continue; /* * Add following symbols to the dynamic symbol table: * * 1. A symbol that is defined in a regular object and * referenced by a DSO. * * 2. A symbol that is defined in a DSO and referenced * by a regular object. * * 3. A symbol that is referenced by a dynamic relocation. * * 4. The linker creates a DSO and the symbol is defined * in a regular object and is visible externally. * */ if (lsb->lsb_ref_dso && ld_symbols_in_regular(lsb)) _add_to_dynsym_table(ld, lsb); else if (lsb->lsb_ref_ndso && ld_symbols_in_dso(lsb)) { lsb->lsb_import = 1; lsb->lsb_input->li_dso_refcnt++; ld_symver_add_verdef_refcnt(ld, lsb); _add_to_dynsym_table(ld, lsb); } else if (lsb->lsb_dynrel) _add_to_dynsym_table(ld, lsb); else if (ld->ld_dso && ld_symbols_in_regular(lsb) && lsb->lsb_other == STV_DEFAULT && ld_symver_search_version_script(ld, lsb) != 0) _add_to_dynsym_table(ld, lsb); } } void ld_symbols_finalize_dynsym(struct ld *ld) { struct ld_output *lo; struct ld_symbol *lsb, _lsb; lo = ld->ld_output; assert(lo != NULL); /* Create an initial symbol at the beginning of symbol table. */ _lsb.lsb_name = NULL; _lsb.lsb_nameindex = 0; _lsb.lsb_size = 0; _lsb.lsb_value = 0; _lsb.lsb_shndx = SHN_UNDEF; _lsb.lsb_bind = STB_LOCAL; _lsb.lsb_type = STT_NOTYPE; _lsb.lsb_other = 0; _write_to_dynsym_table(ld, &_lsb); assert(ld->ld_dyn_symbols != NULL); STAILQ_FOREACH(lsb, ld->ld_dyn_symbols, lsb_dyn) { if (lsb->lsb_import) { memcpy(&_lsb, lsb, sizeof(_lsb)); if (lsb->lsb_type == STT_FUNC && lsb->lsb_func_addr) _lsb.lsb_value = lsb->lsb_plt_off; else _lsb.lsb_value = 0; _lsb.lsb_shndx = SHN_UNDEF; _write_to_dynsym_table(ld, &_lsb); } else _write_to_dynsym_table(ld, lsb); } lo->lo_dynsym->os_info_val = ld->ld_dynsym->sy_first_nonlocal; } /* * Retrieve the resolved symbol. */ struct ld_symbol * ld_symbols_ref(struct ld_symbol *lsb) { while (lsb->lsb_ref != NULL) lsb = lsb->lsb_ref; return (lsb); } /* * Check if a symbol can be overriden (by symbols in main executable). */ int ld_symbols_overridden(struct ld *ld, struct ld_symbol *lsb) { /* Symbols can be overridden only when we are creating a DSO. */ if (!ld->ld_dso) return (0); /* Only visible symbols can be overriden. */ if (lsb->lsb_other != STV_DEFAULT) return (0); /* * Symbols converted to local by version script can not be * overridden. */ if (ld_symver_search_version_script(ld, lsb) == 0) return (0); /* TODO: other cases. */ /* Otherwise symbol can be overridden. */ return (1); } /* * Check if a symbol is defined in regular object. */ int ld_symbols_in_regular(struct ld_symbol *lsb) { return (lsb->lsb_input == NULL || lsb->lsb_input->li_type != LIT_DSO); } /* * Check if a symbol is defined in a DSO. */ int ld_symbols_in_dso(struct ld_symbol *lsb) { return (lsb->lsb_input != NULL && lsb->lsb_input->li_type == LIT_DSO); } static struct ld_symbol * _alloc_symbol(struct ld *ld) { struct ld_symbol *s; if ((s = calloc(1, sizeof(*s))) == NULL) ld_fatal_std(ld, "calloc"); return (s); } static struct ld_symbol * _find_symbol(struct ld_symbol *tbl, char *name) { struct ld_symbol *s; HASH_FIND_STR(tbl, name, s); return (s); } #define _prefer_new() do { \ _resolve_symbol(_lsb, lsb); \ _remove_symbol(ld->ld_sym, _lsb); \ _add_symbol(ld->ld_sym, lsb); \ } while (0) #define _prefer_old() _resolve_symbol(lsb, _lsb) #undef max #define max(a, b) ((a) > (b) ? (a) : (b)) static void _resolve_and_add_symbol(struct ld *ld, struct ld_symbol *lsb) { struct ld_symbol *_lsb; struct ld_symbol_defver *dv; char *name, *sn; /* "long" name is a symbol name plus a symbol version string. */ name = lsb->lsb_longname; /* "sn" stores the bare symbol name. */ sn = lsb->lsb_name; /* * Search in the symbol table for the symbol with the same name and * same version. */ if ((_lsb = _find_symbol(ld->ld_sym, name)) != NULL) goto found; /* * If there is a default version recorded for the symbol name: * * 1. If the symbol to resolve doesn't have a version, search the * symbol with the same name and with a default version. * * 2. If the symbol to resolve has the default version, search the * symbol with the same name but without a version. */ HASH_FIND_STR(ld->ld_defver, sn, dv); if (dv != NULL) { if (!strcmp(name, sn)) { if ((_lsb = _find_symbol(ld->ld_sym, dv->dv_longname)) != NULL) goto found; } else if (!strcmp(name, dv->dv_longname)) { if ((_lsb = _find_symbol(ld->ld_sym, sn)) != NULL) goto found; } } /* * This is *probably* a new symbol, add it to the symbol table * and proceed. * * Note that if one symbol has a version but another one doesn't, * and they are both undefined, there is still a chance that they are * the same symbol. We will solve that when we see the definition. */ _add_symbol(ld->ld_sym, lsb); return; found: /* * We found the same symbol in the symbol table. Now we should * decide which symbol to resolve and which symbol to keep. */ /* * Verify both symbol has the same TLS (thread local storage) * characteristics. */ if ((lsb->lsb_type == STT_TLS || _lsb->lsb_type == STT_TLS) && lsb->lsb_type != _lsb->lsb_type) ld_fatal(ld, "TLS symbol %s is non-TLS in another reference"); /* * If the symbol to resolve is undefined, we always resolve this * symbol to the symbol that is already in the table, no matter it is * defined or not. */ if (lsb->lsb_shndx == SHN_UNDEF) { _prefer_old(); return; } /* * If the symbol to resolve is a common symbol and is defined in * a regular object: * * 1. If the symbol in the table is undefined, we prefer the * common symbol. * * 2. If both symbols are common symbols, we prefer the symbol * already in the table. However if the symbol in the table * is found in a DSO, we prefer the common symbol in regular * object. The size of the symbol we decided to keep is set to * the larger one of the two. * * 3. If the symbol in the table is defined, we prefer the * defined symbol. However if the defined symbol is found * in a DSO, we prefer the common symbol in regular object. * */ if (lsb->lsb_shndx == SHN_COMMON && ld_symbols_in_regular(lsb)) { if (_lsb->lsb_shndx == SHN_UNDEF) _prefer_new(); else if (_lsb->lsb_shndx == SHN_COMMON) { if (ld_symbols_in_dso(_lsb)) { _prefer_new(); lsb->lsb_size = max(lsb->lsb_size, _lsb->lsb_size); } else { _prefer_old(); _lsb->lsb_size = max(lsb->lsb_size, _lsb->lsb_size); } } else { if (ld_symbols_in_dso(_lsb)) _prefer_new(); else _prefer_old(); } return; } /* * If the symbol to resolve is a common symbol and is defined in * a DSO: * * 1. If the symbol in the table is undefined, we prefer the common * symbol. * * 2. If the symbol in the table is also a common symbol, we prefer * the one in the table. The size of the symbol we decided to * keep is set to the larger one of the two. * * 3. If the symbol in the table is defined, we prefer the defined * symbol. */ if (lsb->lsb_shndx == SHN_COMMON && ld_symbols_in_dso(lsb)) { if (_lsb->lsb_shndx == SHN_UNDEF) _prefer_new(); else if (_lsb->lsb_shndx == SHN_COMMON) { _prefer_old(); _lsb->lsb_size = max(lsb->lsb_size, _lsb->lsb_size); } else _prefer_old(); return; } /* * Now we know the symbol to resolve is a defined symbol. If it is * defined in a regular object: * * 1. If the symbol in the table is undefined, we prefer the defined * symbol. (no doubt!) * * 2. If the symbol in the table is a common symbol, we perfer the * defined symbol. * * 3. If the symbol in the table is also a defined symbol, we need to * consider: * * a) If the symbol in the table is also defined in a regular object, * and both symbols are strong, we have a multi-definition error. * If only one of them is strong, we pick that one. If both of them * are weak, we pick the one that is already in the table. (fisrt * seen). Another case is that if one of them is a "provide" symbol, * we prefer the one that is not "provide". * * b) If the symbol in the table is defined in a DSO, we pick the one * defined in the regular object. (no matter weak or strong!) */ if (ld_symbols_in_regular(lsb)) { if (_lsb->lsb_shndx == SHN_UNDEF || _lsb->lsb_shndx == SHN_COMMON) _prefer_new(); else { if (ld_symbols_in_regular(_lsb)) { if (_lsb->lsb_provide && !lsb->lsb_provide) _prefer_new(); else if (lsb->lsb_bind == _lsb->lsb_bind) { if (lsb->lsb_bind == STB_WEAK) _prefer_old(); else ld_fatal(ld, "multiple " "definition of symbol %s", lsb->lsb_longname); } else if (lsb->lsb_bind == STB_WEAK) _prefer_old(); else _prefer_new(); } else _prefer_new(); } return; } /* * Last case, the symbol to resolve is a defined symbol in a DSO. * * 1. If the symbol in the table is undefined, we prefer the defined * symbol. (no doubt!) * * 2. If the symbol in the table is a common symbol: if it is in a * regular object and the defined DSO symbol is a function, we * prefer the common symbol. For all the other cases, we prefer * the defined symbol in the DSO. * * 3. If the symbol in the table is a defined symbol. We always pick * the symbol already in the table. (no matter it's in regular * object or DSO, strong or weak) */ if (_lsb->lsb_shndx != SHN_UNDEF && _lsb->lsb_shndx != SHN_COMMON) _prefer_old(); else if (_lsb->lsb_shndx == SHN_COMMON && ld_symbols_in_regular(_lsb) && lsb->lsb_type == STT_FUNC) _prefer_old(); else { _prefer_new(); /* * Now we added a defined symbol from DSO. Here we should * check if the DSO symbol has a default symbol version. * If so, we search the symbol table for the symbol with the * same name but without a symbol version. If there is one, * we resolve the found symbol to this newly added DSO symbol * and remove the found symbol from the table. */ HASH_FIND_STR(ld->ld_defver, sn, dv); if (dv != NULL) { if ((_lsb = _find_symbol(ld->ld_sym, sn)) != NULL) { _resolve_symbol(_lsb, lsb); _remove_symbol(ld->ld_sym, _lsb); } } } } static void _add_elf_symbol(struct ld *ld, struct ld_input *li, Elf *e, GElf_Sym *sym, size_t strndx, int i) { struct ld_symbol *lsb; struct ld_symbol_defver *dv; char *name; int j, len, ndx; unsigned char st_bind; if ((name = elf_strptr(e, strndx, sym->st_name)) == NULL) return; /* * First check if the section this symbol refers to is belong * to a section group that has been removed. */ st_bind = GELF_ST_BIND(sym->st_info); if (sym->st_shndx != SHN_UNDEF && sym->st_shndx != SHN_COMMON && sym->st_shndx != SHN_ABS && sym->st_shndx < li->li_shnum - 1 && li->li_is[sym->st_shndx].is_discard) { st_bind = GELF_ST_BIND(sym->st_info); if (st_bind == STB_GLOBAL || st_bind == STB_WEAK) { /* * For symbol with STB_GLOBAL or STB_WEAK binding, * we convert it to an undefined symbol. */ sym->st_shndx = SHN_UNDEF; } else { /* * Local symbols are discarded, if the section they * refer to are removed. */ return; } } lsb = _alloc_symbol(ld); if ((lsb->lsb_name = strdup(name)) == NULL) ld_fatal_std(ld, "strdup"); lsb->lsb_value = sym->st_value; lsb->lsb_size = sym->st_size; lsb->lsb_bind = GELF_ST_BIND(sym->st_info); lsb->lsb_type = GELF_ST_TYPE(sym->st_info); lsb->lsb_other = sym->st_other; lsb->lsb_shndx = sym->st_shndx; lsb->lsb_index = i; lsb->lsb_input = li; lsb->lsb_ver = NULL; if (lsb->lsb_shndx != SHN_UNDEF && lsb->lsb_shndx != SHN_ABS) { if (lsb->lsb_shndx == SHN_COMMON) lsb->lsb_is = &li->li_is[li->li_shnum - 1]; else { assert(lsb->lsb_shndx < li->li_shnum - 1); lsb->lsb_is = &li->li_is[lsb->lsb_shndx]; } } if (li->li_type == LIT_DSO) lsb->lsb_ref_dso = 1; else lsb->lsb_ref_ndso = 1; /* Find out symbol version info. */ j = 0; if (li->li_file->lf_type == LFT_DSO && li->li_vername != NULL && li->li_versym != NULL && (size_t) i < li->li_versym_sz) { j = li->li_versym[i]; ndx = j & ~0x8000; if ((size_t) ndx < li->li_vername_sz) { lsb->lsb_ver = li->li_vername[ndx]; #if 0 printf("symbol: %s ver: %s\n", lsb->lsb_name, lsb->lsb_ver); #endif if (j >= 2 && (j & 0x8000) == 0 && lsb->lsb_shndx != SHN_UNDEF) lsb->lsb_default = 1; } } /* Build "long" symbol name which is used for hash key. */ if (lsb->lsb_ver == NULL || j < 2) { lsb->lsb_longname = strdup(lsb->lsb_name); if (lsb->lsb_longname == NULL) ld_fatal_std(ld, "strdup"); } else { len = strlen(lsb->lsb_name) + strlen(lsb->lsb_ver) + 2; if ((lsb->lsb_longname = malloc(len)) == NULL) ld_fatal_std(ld, "malloc"); snprintf(lsb->lsb_longname, len, "%s@%s", lsb->lsb_name, lsb->lsb_ver); } /* Keep track of default versions. */ if (lsb->lsb_default) { if ((dv = calloc(1, sizeof(*dv))) == NULL) ld_fatal(ld, "calloc"); dv->dv_name = lsb->lsb_name; dv->dv_longname = lsb->lsb_longname; dv->dv_ver = lsb->lsb_ver; HASH_ADD_KEYPTR(hh, ld->ld_defver, dv->dv_name, strlen(dv->dv_name), dv); } /* * Insert symbol to input object internal symbol list and * perform symbol resolving. */ ld_input_add_symbol(ld, li, lsb); if (lsb->lsb_bind != STB_LOCAL) _resolve_and_add_symbol(ld, lsb); } static int _archive_member_extracted(struct ld_archive *la, off_t off) { struct ld_archive_member *_lam; HASH_FIND(hh, la->la_m, &off, sizeof(off), _lam); if (_lam != NULL) return (1); return (0); } static struct ld_archive_member * _extract_archive_member(struct ld *ld, struct ld_file *lf, struct ld_archive *la, off_t off) { Elf *e; Elf_Arhdr *arhdr; struct ld_archive_member *lam; struct ld_input *li; if (elf_rand(lf->lf_elf, off) == 0) ld_fatal(ld, "%s: elf_rand failed: %s", lf->lf_name, elf_errmsg(-1)); if ((e = elf_begin(-1, ELF_C_READ, lf->lf_elf)) == NULL) ld_fatal(ld, "%s: elf_begin failed: %s", lf->lf_name, elf_errmsg(-1)); if ((arhdr = elf_getarhdr(e)) == NULL) ld_fatal(ld, "%s: elf_getarhdr failed: %s", lf->lf_name, elf_errmsg(-1)); /* Keep record of extracted members. */ if ((lam = calloc(1, sizeof(*lam))) == NULL) ld_fatal_std(ld, "calloc"); lam->lam_ar_name = strdup(lf->lf_name); if (lam->lam_ar_name == NULL) ld_fatal_std(ld, "strdup"); lam->lam_name = strdup(arhdr->ar_name); if (lam->lam_name == NULL) ld_fatal_std(ld, "strdup"); lam->lam_off = off; HASH_ADD(hh, la->la_m, lam_off, sizeof(lam->lam_off), lam); /* Allocate input object for this member. */ li = ld_input_alloc(ld, lf, lam->lam_name); li->li_lam = lam; lam->lam_input = li; /* Load the symbols of this member. */ _load_elf_symbols(ld, li, e); elf_end(e); return (lam); } static void _print_extracted_member(struct ld *ld, struct ld_archive_member *lam, struct ld_symbol *lsb) { struct ld_state *ls; char *c1, *c2; ls = &ld->ld_state; if (!ls->ls_archive_mb_header) { printf("Extracted archive members:\n\n"); ls->ls_archive_mb_header = 1; } c1 = ld_input_get_fullname(ld, lam->lam_input); c2 = ld_input_get_fullname(ld, lsb->lsb_input); printf("%-30s", c1); if (strlen(c1) >= 30) { printf("\n%-30s", ""); } printf("%s (%s)\n", c2, lsb->lsb_name); } static void _load_archive_symbols(struct ld *ld, struct ld_file *lf) { struct ld_state *ls; struct ld_archive *la; struct ld_archive_member *lam; struct ld_symbol *lsb; Elf_Arsym *as; size_t c; int extracted, i; assert(lf != NULL && lf->lf_type == LFT_ARCHIVE); assert(lf->lf_ar != NULL); ls = &ld->ld_state; la = lf->lf_ar; if ((as = elf_getarsym(lf->lf_elf, &c)) == NULL) ld_fatal(ld, "%s: elf_getarsym failed: %s", lf->lf_name, elf_errmsg(-1)); do { extracted = 0; for (i = 0; (size_t) i < c; i++) { if (as[i].as_name == NULL) break; if (_archive_member_extracted(la, as[i].as_off)) continue; if ((lsb = _find_symbol(ld->ld_sym, as[i].as_name)) != NULL) { lam = _extract_archive_member(ld, lf, la, as[i].as_off); extracted = 1; ls->ls_extracted[ls->ls_group_level] = 1; if (ld->ld_print_linkmap) _print_extracted_member(ld, lam, lsb); } } } while (extracted); } static void _load_elf_symbols(struct ld *ld, struct ld_input *li, Elf *e) { struct ld_input_section *is; Elf_Scn *scn_sym, *scn_dynamic; Elf_Scn *scn_versym, *scn_verneed, *scn_verdef; Elf_Data *d; GElf_Sym sym; GElf_Shdr shdr; size_t dyn_strndx, strndx; int elferr, i; /* Load section list from input object. */ ld_input_init_sections(ld, li, e); strndx = dyn_strndx = SHN_UNDEF; scn_sym = scn_versym = scn_verneed = scn_verdef = scn_dynamic = NULL; for (i = 0; (uint64_t) i < li->li_shnum - 1; i++) { is = &li->li_is[i]; if (li->li_type == LIT_DSO) { if (is->is_type == SHT_DYNSYM) { scn_sym = elf_getscn(e, is->is_index); strndx = is->is_link; } else if (is->is_type == SHT_SUNW_versym) scn_versym = elf_getscn(e, is->is_index); else if (is->is_type == SHT_SUNW_verneed) scn_verneed = elf_getscn(e, is->is_index); else if (is->is_type == SHT_SUNW_verdef) scn_verdef = elf_getscn(e, is->is_index); else if (is->is_type == SHT_DYNAMIC) { scn_dynamic = elf_getscn(e, is->is_index); dyn_strndx = is->is_link; } } else { if (is->is_type == SHT_SYMTAB) { scn_sym = elf_getscn(e, is->is_index); strndx = is->is_link; } } } if (scn_sym == NULL || strndx == SHN_UNDEF) return; ld_symver_load_symbol_version_info(ld, li, e, scn_versym, scn_verneed, scn_verdef); if (scn_dynamic != NULL) ld_dynamic_load_dso_dynamic(ld, li, e, scn_dynamic, dyn_strndx); if (gelf_getshdr(scn_sym, &shdr) != &shdr) { ld_warn(ld, "%s: gelf_getshdr failed: %s", li->li_name, elf_errmsg(-1)); return; } (void) elf_errno(); if ((d = elf_getdata(scn_sym, NULL)) == NULL) { elferr = elf_errno(); if (elferr != 0) ld_warn(ld, "%s: elf_getdata failed: %s", li->li_name, elf_errmsg(elferr)); /* Empty symbol table section? */ return; } li->li_symnum = d->d_size / shdr.sh_entsize; for (i = 0; (uint64_t) i < li->li_symnum; i++) { if (gelf_getsym(d, i, &sym) != &sym) ld_warn(ld, "%s: gelf_getsym failed: %s", li->li_name, elf_errmsg(-1)); _add_elf_symbol(ld, li, e, &sym, strndx, i); } } static void _load_symbols(struct ld *ld, struct ld_file *lf) { if (lf->lf_type == LFT_ARCHIVE) _load_archive_symbols(ld, lf); else { lf->lf_input = ld_input_alloc(ld, lf, lf->lf_name); _load_elf_symbols(ld, lf->lf_input, lf->lf_elf); } } static void _unload_symbols(struct ld_input *li) { int i; if (li->li_symindex == NULL) return; for (i = 0; (uint64_t) i < li->li_symnum; i++) _free_symbol(li->li_symindex[i]); } static void _free_symbol(struct ld_symbol *lsb) { if (lsb == NULL) return; free(lsb->lsb_name); free(lsb->lsb_longname); free(lsb); } static void _update_symbol(struct ld_symbol *lsb) { struct ld_input_section *is; struct ld_output_section *os; if (lsb->lsb_preset_os != NULL) { lsb->lsb_value = lsb->lsb_preset_os->os_addr; lsb->lsb_shndx = elf_ndxscn(lsb->lsb_preset_os->os_scn); return; } if (lsb->lsb_shndx == SHN_ABS) return; if (lsb->lsb_input != NULL) { is = lsb->lsb_is; if (is == NULL || (os = is->is_output) == NULL) return; lsb->lsb_value += os->os_addr + is->is_reloff; lsb->lsb_shndx = elf_ndxscn(os->os_scn); } } struct ld_symbol_table * _alloc_symbol_table(struct ld *ld) { struct ld_symbol_table *symtab; if ((symtab = calloc(1, sizeof(*ld->ld_symtab))) == NULL) ld_fatal_std(ld, "calloc"); return (symtab); } static void _add_to_dynsym_table(struct ld *ld, struct ld_symbol *lsb) { assert(ld->ld_dynsym != NULL && ld->ld_dynstr != NULL); if (ld->ld_dyn_symbols == NULL) { ld->ld_dyn_symbols = malloc(sizeof(*ld->ld_dyn_symbols)); if (ld->ld_dyn_symbols == NULL) ld_fatal_std(ld, "malloc"); STAILQ_INIT(ld->ld_dyn_symbols); } STAILQ_INSERT_TAIL(ld->ld_dyn_symbols, lsb, lsb_dyn); lsb->lsb_nameindex = ld_strtab_insert_no_suffix(ld, ld->ld_dynstr, lsb->lsb_name); lsb->lsb_dyn_index = ld->ld_dynsym->sy_size++; } static void _write_to_dynsym_table(struct ld *ld, struct ld_symbol *lsb) { struct ld_output *lo; struct ld_symbol_table *symtab; Elf32_Sym *s32; Elf64_Sym *s64; size_t es; assert(lsb != NULL); assert(ld->ld_dynsym != NULL && ld->ld_dynstr != NULL); symtab = ld->ld_dynsym; lo = ld->ld_output; assert(lo != NULL); es = (lo->lo_ec == ELFCLASS32) ? sizeof(Elf32_Sym) : sizeof(Elf64_Sym); /* Allocate buffer for the dynsym table. */ if (symtab->sy_buf == NULL) { symtab->sy_buf = malloc(symtab->sy_size * es); symtab->sy_write_pos = 0; } if (lo->lo_ec == ELFCLASS32) { s32 = symtab->sy_buf; s32 += symtab->sy_write_pos; s32->st_name = lsb->lsb_nameindex; s32->st_info = ELF32_ST_INFO(lsb->lsb_bind, lsb->lsb_type); s32->st_other = lsb->lsb_other; s32->st_shndx = lsb->lsb_shndx; s32->st_value = lsb->lsb_value; s32->st_size = lsb->lsb_size; } else { s64 = symtab->sy_buf; s64 += symtab->sy_write_pos; s64->st_name = lsb->lsb_nameindex; s64->st_info = ELF64_ST_INFO(lsb->lsb_bind, lsb->lsb_type); s64->st_other = lsb->lsb_other; s64->st_shndx = lsb->lsb_shndx; s64->st_value = lsb->lsb_value; s64->st_size = lsb->lsb_size; } /* Remember the index for the first non-local symbol. */ if (symtab->sy_first_nonlocal == 0 && lsb->lsb_bind != STB_LOCAL) symtab->sy_first_nonlocal = symtab->sy_write_pos; symtab->sy_write_pos++; } static void _add_to_symbol_table(struct ld *ld, struct ld_symbol *lsb) { struct ld_output *lo; struct ld_symbol_table *symtab; struct ld_strtab *strtab; Elf32_Sym *s32; Elf64_Sym *s64; size_t es; assert(lsb != NULL); assert(ld->ld_symtab != NULL && ld->ld_strtab != NULL); symtab = ld->ld_symtab; strtab = ld->ld_strtab; lo = ld->ld_output; assert(lo != NULL); es = (lo->lo_ec == ELFCLASS32) ? sizeof(Elf32_Sym) : sizeof(Elf64_Sym); /* Allocate/Reallocate buffer for the symbol table. */ if (symtab->sy_buf == NULL) { symtab->sy_size = 0; symtab->sy_cap = _INIT_SYMTAB_SIZE; symtab->sy_buf = malloc(symtab->sy_cap * es); if (symtab->sy_buf == NULL) ld_fatal_std(ld, "malloc"); } else if (symtab->sy_size >= symtab->sy_cap) { symtab->sy_cap *= 2; symtab->sy_buf = realloc(symtab->sy_buf, symtab->sy_cap * es); if (symtab->sy_buf == NULL) ld_fatal_std(ld, "relloc"); } /* * Insert the symbol into the symbol table and the symbol name to * the assoicated name string table. */ lsb->lsb_nameindex = ld_strtab_insert_no_suffix(ld, strtab, lsb->lsb_name); if (lo->lo_ec == ELFCLASS32) { s32 = symtab->sy_buf; s32 += symtab->sy_size; s32->st_name = lsb->lsb_nameindex; s32->st_info = ELF32_ST_INFO(lsb->lsb_bind, lsb->lsb_type); s32->st_other = lsb->lsb_other; s32->st_shndx = lsb->lsb_shndx; s32->st_value = lsb->lsb_value; s32->st_size = lsb->lsb_size; } else { s64 = symtab->sy_buf; s64 += symtab->sy_size; s64->st_name = lsb->lsb_nameindex; s64->st_info = ELF64_ST_INFO(lsb->lsb_bind, lsb->lsb_type); s64->st_other = lsb->lsb_other; s64->st_shndx = lsb->lsb_shndx; s64->st_value = lsb->lsb_value; s64->st_size = lsb->lsb_size; } /* Remember the index for the first non-local symbol. */ if (symtab->sy_first_nonlocal == 0 && lsb->lsb_bind != STB_LOCAL) symtab->sy_first_nonlocal = symtab->sy_size; lsb->lsb_out_index = symtab->sy_size; symtab->sy_size++; } static void _free_symbol_table(struct ld_symbol_table *symtab) { if (symtab == NULL) return; free(symtab->sy_buf); free(symtab); }