/* Copyright (C) 2003 MontaVista Software, Inc. Written by Daniel Jacobowitz , 2003 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, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "prelinktab.h" #include "reloc.h" #include "reloc-info.h" #include "ld-libs.h" struct search_path { int maxlen, count, allocated; char **dirs; }; struct search_path ld_dirs, ld_library_search_path; int host_paths; void string_to_path (struct search_path *path, const char *string); const char *argp_program_version = PRELINK_RTLD_PROG PKGVERSION " 1.0"; const char *argp_program_bug_address = REPORT_BUGS_TO; static char argp_doc[] = PRELINK_RTLD_PROG " -- program to simulate the runtime linker"; #define OPT_SYSROOT 0x8c #define OPT_LIBRARY_PATH 0x8e #define OPT_TARGET_PATHS 0x8f static struct argp_option options[] = { {"library-path", OPT_LIBRARY_PATH, "LIBRARY_PATH", 0, "Set library search path to LIBRARY_PATH" }, {"root", OPT_SYSROOT, "ROOT_PATH", 0, "Prefix all paths with ROOT_PATH" }, {"target-paths", OPT_TARGET_PATHS, 0, 0, "Specified paths are based on ROOT_PATH" }, { 0 } }; static error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case OPT_SYSROOT: sysroot = arg; break; case OPT_LIBRARY_PATH: string_to_path(&ld_library_search_path, arg); break; case OPT_TARGET_PATHS: host_paths = 0; break; default: return ARGP_ERR_UNKNOWN; } return 0; } #ifndef PT_TLS #define PT_TLS 7 /* Thread-local storage segment */ #endif #ifndef R_ARM_TLS_DTPMOD32 #define R_ARM_TLS_DTPMOD32 17 /* ID of module containing symbol */ #define R_ARM_TLS_DTPOFF32 18 /* Offset in TLS block */ #define R_ARM_TLS_TPOFF32 19 /* Offset in static TLS block */ #endif /* This function returns the same constants expected by glibc's symbol lookup routines. This is slightly different from the equivalent routines in prelink. It should return PLT for any relocation where an undefined symbol in the application should be ignored: typically, this means any jump slot or TLS relocations, but not copy relocations. Don't return the prelinker's RTYPE_CLASS_TLS. */ int reloc_type_class (int type, int machine) { switch (machine) { case EM_386: switch (type) { case R_386_COPY: return ELF_RTYPE_CLASS_COPY; case R_386_JMP_SLOT: case R_386_TLS_DTPMOD32: case R_386_TLS_DTPOFF32: case R_386_TLS_TPOFF32: case R_386_TLS_TPOFF: return ELF_RTYPE_CLASS_PLT; default: return 0; } case EM_X86_64: switch (type) { case R_X86_64_COPY: return ELF_RTYPE_CLASS_COPY; case R_X86_64_JUMP_SLOT: case R_X86_64_DTPMOD64: case R_X86_64_DTPOFF64: case R_X86_64_TPOFF64: case R_X86_64_DTPOFF32: case R_X86_64_TPOFF32: return ELF_RTYPE_CLASS_PLT; default: return 0; } case EM_ARM: switch (type) { case R_ARM_COPY: return ELF_RTYPE_CLASS_COPY; case R_ARM_JUMP_SLOT: case R_ARM_TLS_DTPMOD32: case R_ARM_TLS_DTPOFF32: case R_ARM_TLS_TPOFF32: return ELF_RTYPE_CLASS_PLT; default: return 0; } case EM_SH: switch (type) { case R_SH_COPY: return ELF_RTYPE_CLASS_COPY; case R_SH_JMP_SLOT: return ELF_RTYPE_CLASS_PLT; default: return 0; } case EM_PPC: switch (type) { case R_PPC_COPY: return ELF_RTYPE_CLASS_COPY; case R_PPC_JMP_SLOT: return ELF_RTYPE_CLASS_PLT; default: if (type >= R_PPC_DTPMOD32 && type <= R_PPC_DTPREL32) return ELF_RTYPE_CLASS_PLT; return 0; } case EM_PPC64: switch (type) { case R_PPC64_COPY: return ELF_RTYPE_CLASS_COPY; case R_PPC64_ADDR24: return ELF_RTYPE_CLASS_PLT; default: if (type >= R_PPC64_DTPMOD64 && type <= R_PPC64_TPREL16_HIGHESTA) return ELF_RTYPE_CLASS_PLT; return 0; } case EM_MIPS: switch (type) { case R_MIPS_COPY: return ELF_RTYPE_CLASS_COPY; case R_MIPS_JUMP_SLOT: case R_MIPS_TLS_DTPMOD32: case R_MIPS_TLS_DTPREL32: case R_MIPS_TLS_TPREL32: return ELF_RTYPE_CLASS_PLT; default: return 0; } case EM_SPARC: case EM_SPARC32PLUS: switch (type) { case R_SPARC_COPY: return ELF_RTYPE_CLASS_COPY; case R_SPARC_JMP_SLOT: case R_SPARC_TLS_DTPMOD32: case R_SPARC_TLS_DTPOFF32: case R_SPARC_TLS_TPOFF32: case R_SPARC_TLS_LE_HIX22: case R_SPARC_TLS_LE_LOX10: return ELF_RTYPE_CLASS_PLT; default: return 0; } case EM_SPARCV9: switch (type) { case R_SPARC_COPY: return ELF_RTYPE_CLASS_COPY; case R_SPARC_JMP_SLOT: case R_SPARC_TLS_DTPMOD64: case R_SPARC_TLS_DTPOFF64: case R_SPARC_TLS_TPOFF64: case R_SPARC_TLS_LE_HIX22: case R_SPARC_TLS_LE_LOX10: return ELF_RTYPE_CLASS_PLT; default: return 0; } default: printf ("Unknown architecture!\n"); exit (1); return 0; } } int is_ldso_soname (const char *soname) { if (! strcmp (soname, "ld-linux.so.2") || ! strcmp (soname, "ld-linux.so.3") || ! strcmp (soname, "ld.so.1") || ! strcmp (soname, "ld-linux-ia64.so.2") || ! strcmp (soname, "ld-linux-x86-64.so.2") || ! strcmp (soname, "ld64.so.1")) return 1; return 0; } struct needed_list { struct dso_list *ent; struct needed_list *next; }; struct dso_list { DSO *dso; struct ldlibs_link_map *map; struct dso_list *next, *prev; struct needed_list *needed, *needed_tail; const char *name; struct dso_list *loader; const char *canon_filename; }; static int dso_open_error = 0; static void free_needed (struct needed_list *p) { struct needed_list *old_p = p; while (old_p) { old_p = p->next; free (p); p = old_p; } } static struct dso_list * in_dso_list (struct dso_list *dso_list, const char *soname, const char *filename) { while (dso_list != NULL) { if (dso_list->dso != NULL) { if (strcmp (dso_list->dso->soname, soname) == 0) return dso_list; } if (strcmp (dso_list->name, soname) == 0) return dso_list; if (filename && dso_list->canon_filename && strcmp (dso_list->canon_filename, filename) == 0) return dso_list; dso_list = dso_list->next; } return NULL; } static int in_needed_list (struct needed_list *needed_list, const char *soname) { while (needed_list != NULL) { if (needed_list->ent->dso != NULL && strcmp (needed_list->ent->dso->soname, soname) == 0) return 1; needed_list = needed_list->next; } return 0; } /****/ void add_dir (struct search_path *path, const char *dir, int dirlen) { if (path->allocated == 0) { path->allocated = 5; path->dirs = malloc (sizeof (char *) * 5); } else if (path->count == path->allocated) { path->allocated *= 2; path->dirs = realloc (path->dirs, sizeof (char *) * path->allocated); } path->dirs[path->count] = malloc (dirlen + 1); memcpy (path->dirs[path->count], dir, dirlen); path->dirs[path->count++][dirlen] = 0; if (path->maxlen < dirlen) path->maxlen = dirlen; } void free_path (struct search_path *path) { if (path->allocated) { int i; for (i = 0; i < path->count; i++) free (path->dirs[i]); free (path->dirs); } } void load_ld_so_conf (int use_64bit, int use_mipsn32) { int fd; FILE *conf; char buf[1024]; memset (&ld_dirs, 0, sizeof (ld_dirs)); /* Only use the correct machine, to prevent mismatches if we have both /lib/ld.so and /lib64/ld.so on x86-64. */ if (use_64bit) { add_dir (&ld_dirs, "/lib64/tls", strlen ("/lib64/tls")); add_dir (&ld_dirs, "/lib64", strlen ("/lib64")); add_dir (&ld_dirs, "/usr/lib64/tls", strlen ("/usr/lib64/tls")); add_dir (&ld_dirs, "/usr/lib64", strlen ("/usr/lib64")); } else if (use_mipsn32) { add_dir (&ld_dirs, "/lib32/tls", strlen ("/lib32/tls")); add_dir (&ld_dirs, "/lib32", strlen ("/lib32")); add_dir (&ld_dirs, "/usr/lib32/tls", strlen ("/usr/lib32/tls")); add_dir (&ld_dirs, "/usr/lib32", strlen ("/usr/lib32")); } else { add_dir (&ld_dirs, "/lib/tls", strlen ("/lib/tls")); add_dir (&ld_dirs, "/lib", strlen ("/lib")); add_dir (&ld_dirs, "/usr/lib/tls", strlen ("/usr/lib/tls")); add_dir (&ld_dirs, "/usr/lib", strlen ("/usr/lib")); } fd = wrap_open ("/etc/ld.so.conf", O_RDONLY); if (fd == -1) return; conf = fdopen (fd, "r"); while (fgets (buf, 1024, conf) != NULL) { int len; char *p; p = strchr (buf, '#'); if (p) *p = 0; len = strlen (buf); while (len > 0 && isspace (buf[len - 1])) buf[--len] = 0; if (len > 0) add_dir (&ld_dirs, buf, len); } fclose (conf); } void string_to_path (struct search_path *path, const char *string) { const char *start, *end, *end_tmp; start = string; while (1) { end = start; while (*end && *end != ':' && *end != ';') end ++; /* Eliminate any trailing '/' characters, but be sure to leave a leading slash if someeone wants / in their RPATH. */ end_tmp = end; while (end_tmp > start + 1 && end_tmp[-1] == '/') end_tmp --; add_dir (path, start, end_tmp - start); if (*end == 0) break; /* Skip the separator. */ start = end + 1; } } char * find_lib_in_path (struct search_path *path, const char *soname, int elfclass, int machine) { char *ret; int i; int alt_machine; switch (machine) { case EM_SPARC: alt_machine = EM_SPARC32PLUS; break; case EM_SPARC32PLUS: alt_machine = EM_SPARC; break; default: alt_machine = machine; break; } ret = malloc (strlen (soname) + 2 + path->maxlen); for (i = 0; i < path->count; i++) { sprintf (ret, "%s/%s", path->dirs[i], soname); if (wrap_access (ret, F_OK) == 0) { DSO *dso = open_dso (ret); int dso_class = gelf_getclass (dso->elf); int dso_machine = (dso_class == ELFCLASS32) ? elf32_getehdr (dso->elf)->e_machine : elf64_getehdr (dso->elf)->e_machine; if (dso == NULL) continue; /* Skip 32-bit libraries when looking for 64-bit. Also skip libraries for alternative machines. */ if (gelf_getclass (dso->elf) != elfclass || (dso_machine != machine && dso_machine != alt_machine)) { close_dso (dso); continue; } close_dso (dso); return ret; } } free (ret); return NULL; } char * find_lib_by_soname (const char *soname, struct dso_list *loader, int elfclass, int machine) { char *ret; if (strchr (soname, '/')) return strdup (soname); if (loader->dso->info[DT_RUNPATH] == 0) { /* Search DT_RPATH all the way up. */ struct dso_list *loader_p = loader; while (loader_p) { if (loader_p->dso->info[DT_RPATH]) { struct search_path r_path; const char *rpath = get_data (loader_p->dso, loader_p->dso->info[DT_STRTAB] + loader_p->dso->info[DT_RPATH], NULL, NULL); memset (&r_path, 0, sizeof (r_path)); string_to_path (&r_path, rpath); ret = find_lib_in_path (&r_path, soname, elfclass, machine); free_path (&r_path); if (ret) return ret; } loader_p = loader_p->loader; } } ret = find_lib_in_path (&ld_library_search_path, soname, elfclass, machine); if (ret) return ret; if (loader->dso->info[DT_RUNPATH]) { struct search_path r_path; const char *rpath = get_data (loader->dso, loader->dso->info[DT_STRTAB] + loader->dso->info[DT_RUNPATH], NULL, NULL); memset (&r_path, 0, sizeof (r_path)); string_to_path (&r_path, rpath); ret = find_lib_in_path (&r_path, soname, elfclass, machine); free_path (&r_path); if (ret) return ret; } ret = find_lib_in_path (&ld_dirs, soname, elfclass, machine); if (ret) return ret; return NULL; } static struct dso_list * load_dsos (DSO *dso, int host_paths) { struct dso_list *dso_list, *dso_list_tail, *cur_dso_ent, *new_dso_ent; struct stat64 st; dso_list = malloc (sizeof (struct dso_list)); dso_list->dso = dso; dso_list->next = NULL; dso_list->prev = NULL; dso_list->needed = NULL; dso_list->name = dso->filename; dso_list->loader = NULL; if (host_paths) dso_list->canon_filename = canonicalize_file_name (dso->filename); else dso_list->canon_filename = prelink_canonicalize (dso->filename, &st); if (dso_list->canon_filename == NULL) dso_list->canon_filename = strdup (dso->filename); cur_dso_ent = dso_list_tail = dso_list; while (cur_dso_ent != NULL) { DSO *cur_dso, *new_dso; Elf_Scn *scn; Elf_Data *data; GElf_Dyn dyn; cur_dso = cur_dso_ent->dso; if (cur_dso == NULL) { cur_dso_ent = cur_dso_ent->next; continue; } scn = cur_dso->scn[cur_dso->dynamic]; data = NULL; while ((data = elf_getdata (scn, data)) != NULL) { int ndx, maxndx; maxndx = data->d_size / cur_dso->shdr[cur_dso->dynamic].sh_entsize; for (ndx = 0; ndx < maxndx; ++ndx) { gelfx_getdyn (cur_dso->elf, data, ndx, &dyn); if (dyn.d_tag == DT_NULL) break; if (dyn.d_tag == DT_NEEDED) { char *new_name=NULL, *new_canon_name=NULL; const char *soname = get_data (cur_dso, cur_dso->info[DT_STRTAB] + dyn.d_un.d_val, NULL, NULL); new_dso_ent = in_dso_list (dso_list, soname, NULL); if (new_dso_ent == NULL) { int machine; int class = gelf_getclass (dso->elf); machine = (class == ELFCLASS32) ? elf32_getehdr (dso->elf)->e_machine : elf64_getehdr (dso->elf)->e_machine; new_name = find_lib_by_soname (soname, cur_dso_ent, class, machine); if (new_name == 0 || wrap_access (new_name, R_OK) < 0) { dso_open_error ++; new_dso_ent = malloc (sizeof (struct dso_list)); dso_list_tail->next = new_dso_ent; dso_list_tail->next->prev = dso_list_tail; dso_list_tail = dso_list_tail->next; dso_list_tail->next = NULL; dso_list_tail->dso = NULL; dso_list_tail->needed = NULL; dso_list_tail->name = soname; dso_list_tail->loader = NULL; dso_list_tail->canon_filename = soname; continue; } /* See if the filename we found has already been opened (possibly under a different SONAME via some symlink). */ new_canon_name = prelink_canonicalize (new_name, NULL); if (new_canon_name == NULL) new_canon_name = strdup (new_name); new_dso_ent = in_dso_list (dso_list, soname, new_canon_name); } else if (new_dso_ent->dso == NULL) continue; if (new_dso_ent == NULL) { new_dso = open_dso (new_name); free (new_name); new_dso_ent = malloc (sizeof (struct dso_list)); dso_list_tail->next = new_dso_ent; dso_list_tail->next->prev = dso_list_tail; dso_list_tail = dso_list_tail->next; dso_list_tail->next = NULL; dso_list_tail->dso = new_dso; dso_list_tail->needed = NULL; dso_list_tail->loader = cur_dso_ent; dso_list_tail->canon_filename = new_canon_name; if (is_ldso_soname (new_dso->soname)) dso_list_tail->name = new_dso->filename; else if (strcmp (new_dso->soname, new_dso->filename) == 0) /* new_dso->soname might be a full path if the library had no SONAME. Use the original SONAME instead. */ dso_list_tail->name = soname; else /* Use the new SONAME if possible, in case some library links to this one using an incorrect SONAME. */ dso_list_tail->name = new_dso->soname; } if (!cur_dso_ent->needed) { cur_dso_ent->needed = malloc (sizeof (struct needed_list)); cur_dso_ent->needed_tail = cur_dso_ent->needed; cur_dso_ent->needed_tail->ent = new_dso_ent; cur_dso_ent->needed_tail->next = NULL; } else if (!in_needed_list (cur_dso_ent->needed, soname)) { cur_dso_ent->needed_tail->next = malloc (sizeof (struct needed_list)); cur_dso_ent->needed_tail = cur_dso_ent->needed_tail->next; cur_dso_ent->needed_tail->ent = new_dso_ent; cur_dso_ent->needed_tail->next = NULL; } continue; } if (dyn.d_tag == DT_FILTER || dyn.d_tag == DT_AUXILIARY) { // big fat warning; } } } cur_dso_ent = cur_dso_ent->next; } return dso_list; } static void get_version_info (DSO *dso, struct ldlibs_link_map *map) { int i; Elf_Data *data; int ndx_high; const char *strtab = map->l_info[DT_STRTAB]; /* Fortunately, 32-bit and 64-bit ELF use the same Verneed and Verdef structures, so this function will work for either. */ Elf64_Verneed *verneed; Elf64_Verdef *verdef; map->l_versyms = NULL; if (dso->info_set_mask & (1ULL << DT_VERNEED_BIT)) { i = addr_to_sec (dso, dso->info_DT_VERNEED); data = elf_getdata (dso->scn[i], NULL); verneed = data->d_buf; } else verneed = NULL; if (dso->info_set_mask & (1ULL << DT_VERDEF_BIT)) { i = addr_to_sec (dso, dso->info_DT_VERDEF); data = elf_getdata (dso->scn[i], NULL); verdef = data->d_buf; } else verdef = NULL; ndx_high = 0; if (verneed) { Elf64_Verneed *ent = verneed; Elf64_Vernaux *aux; while (1) { aux = (Elf64_Vernaux *) ((char *) ent + ent->vn_aux); while (1) { if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high) ndx_high = aux->vna_other & 0x7fff; if (aux->vna_next == 0) break; aux = (Elf64_Vernaux *) ((char *) aux + aux->vna_next); } if (ent->vn_next == 0) break; ent = (Elf64_Verneed *) ((char *) ent + ent->vn_next); } } if (verdef) { Elf64_Verdef *ent = verdef; while (1) { if ((unsigned int) (ent->vd_ndx & 0x7fff) > ndx_high) ndx_high = ent->vd_ndx & 0x7fff; if (ent->vd_next == 0) break; ent = (Elf64_Verdef *) ((char *) ent + ent->vd_next); } } if (ndx_high) { map->l_versions = (struct r_found_version *) calloc (ndx_high + 1, sizeof (struct r_found_version)); map->l_nversions = ndx_high + 1; i = addr_to_sec (dso, dso->info_DT_VERSYM); data = elf_getdata (dso->scn[i], NULL); map->l_versyms = data->d_buf; if (verneed) { Elf64_Verneed *ent = verneed; while (1) { Elf64_Vernaux *aux; aux = (Elf64_Vernaux *) ((char *) ent + ent->vn_aux); while (1) { Elf64_Half ndx = aux->vna_other & 0x7fff; map->l_versions[ndx].hash = aux->vna_hash; map->l_versions[ndx].hidden = aux->vna_other & 0x8000; map->l_versions[ndx].name = &strtab[aux->vna_name]; map->l_versions[ndx].filename = &strtab[ent->vn_file]; if (aux->vna_next == 0) break; aux = (Elf64_Vernaux *) ((char *) aux + aux->vna_next); } if (ent->vn_next == 0) break; ent = (Elf64_Verneed *) ((char *) ent + ent->vn_next); } } if (verdef) { Elf64_Verdef *ent = verdef; Elf64_Verdaux *aux; while (1) { aux = (Elf64_Verdaux *) ((char *) ent + ent->vd_aux); if ((ent->vd_flags & VER_FLG_BASE) == 0) { /* The name of the base version should not be available for matching a versioned symbol. */ Elf64_Half ndx = ent->vd_ndx & 0x7fff; map->l_versions[ndx].hash = ent->vd_hash; map->l_versions[ndx].name = &strtab[aux->vda_name]; map->l_versions[ndx].filename = NULL; } if (ent->vd_next == 0) break; ent = (Elf64_Verdef *) ((char *) ent + ent->vd_next); } } } } const char *rtld_progname; static Elf64_Addr load_addr = 0xdead0000; static void create_ldlibs_link_map (struct dso_list *cur_dso_ent) { struct ldlibs_link_map *map = malloc (sizeof (struct ldlibs_link_map)); DSO *dso = cur_dso_ent->dso; int i; Elf_Data *data; Elf_Symndx *hash; memset (map, 0, sizeof (*map)); cur_dso_ent->map = map; if (is_ldso_soname (cur_dso_ent->dso->soname)) { map->l_name = dso->filename; rtld_progname = dso->filename; } else map->l_name = dso->soname; map->l_soname = dso->soname; map->filename = dso->filename; if (dso->ehdr.e_type == ET_EXEC) map->l_type = lt_executable; else map->l_type = lt_library; /* FIXME: gelfify, endianness issues */ /* and leaks? */ i = addr_to_sec (dso, dso->info[DT_SYMTAB]); data = elf_getdata (dso->scn[i], NULL); map->l_info[DT_SYMTAB] = data->d_buf; i = addr_to_sec (dso, dso->info[DT_STRTAB]); data = elf_getdata (dso->scn[i], NULL); map->l_info[DT_STRTAB] = data->d_buf; map->l_gnu_hash = dynamic_info_is_set (dso, DT_GNU_HASH_BIT); if (map->l_gnu_hash) i = addr_to_sec (dso, dso->info_DT_GNU_HASH); else i = addr_to_sec (dso, dso->info[DT_HASH]); data = elf_getdata (dso->scn[i], NULL); hash = data->d_buf; map->l_nbuckets = hash[0]; if (map->l_gnu_hash) { map->l_nmaskwords = hash[2]; map->l_maskword64 = gelf_getclass (dso->elf) == ELFCLASS64; map->l_shift = hash[3]; map->l_maskwords = hash + 4; map->l_buckets = hash + 4 + (map->l_nmaskwords << map->l_maskword64); map->l_chain = map->l_buckets + map->l_nbuckets - hash[1]; } else { map->l_buckets = hash + 2; map->l_chain = map->l_buckets + map->l_nbuckets; } get_version_info (dso, map); map->l_map_start = load_addr; load_addr += 0x1000; map->sym_base = dso->info[DT_SYMTAB] - dso->base; for (i = 0; i < dso->ehdr.e_phnum; ++i) if (dso->phdr[i].p_type == PT_TLS) { map->l_tls_blocksize = dso->phdr[i].p_memsz; map->l_tls_align = dso->phdr[i].p_align; if (map->l_tls_align == 0) map->l_tls_firstbyte_offset = 0; else map->l_tls_firstbyte_offset = dso->phdr[i].p_vaddr & (map->l_tls_align - 1); break; } } struct { void *symptr; int rtypeclass; } cache; void do_reloc (DSO *dso, struct ldlibs_link_map *map, struct r_scope_elem *scope, GElf_Word sym, GElf_Word type) { struct r_found_version *ver; int rtypeclass; void *symptr; const char *name; Elf64_Word st_name; if (map->l_versyms) { int vernum = map->l_versyms[sym] & 0x7fff; ver = &map->l_versions[vernum]; } else ver = NULL; rtypeclass = reloc_type_class (type, dso->ehdr.e_machine); if (gelf_getclass (dso->elf) == ELFCLASS32) { Elf32_Sym *sym32 = &((Elf32_Sym *)map->l_info[DT_SYMTAB])[sym]; if (ELF32_ST_BIND (sym32->st_info) == STB_LOCAL) return; symptr = sym32; st_name = sym32->st_name; } else { Elf64_Sym *sym64 = &((Elf64_Sym *)map->l_info[DT_SYMTAB])[sym]; if (ELF64_ST_BIND (sym64->st_info) == STB_LOCAL) return; symptr = sym64; st_name = sym64->st_name; } if (cache.symptr == symptr && cache.rtypeclass == rtypeclass) return; cache.symptr = symptr; cache.rtypeclass = rtypeclass; name = ((const char *)map->l_info[DT_STRTAB]) + st_name; if (gelf_getclass (dso->elf) == ELFCLASS32) { if (ver && ver->hash) rtld_lookup_symbol_versioned (name, symptr, scope, ver, rtypeclass, map, dso->ehdr.e_machine); else rtld_lookup_symbol (name, symptr, scope, rtypeclass, map, dso->ehdr.e_machine); } else { if (ver && ver->hash) rtld_lookup_symbol_versioned64 (name, symptr, scope, ver, rtypeclass, map, dso->ehdr.e_machine); else rtld_lookup_symbol64 (name, symptr, scope, rtypeclass, map, dso->ehdr.e_machine); } } void do_rel_section (DSO *dso, struct ldlibs_link_map *map, struct r_scope_elem *scope, int tag, int section) { Elf_Data *data; int ndx, maxndx, sym, type; data = elf_getdata (dso->scn[section], NULL); maxndx = data->d_size / dso->shdr[section].sh_entsize; for (ndx = 0; ndx < maxndx; ndx++) { if (tag == DT_REL) { GElf_Rel rel; gelfx_getrel (dso->elf, data, ndx, &rel); sym = reloc_r_sym (dso, rel.r_info); type = reloc_r_type (dso, rel.r_info); } else { GElf_Rela rela; gelfx_getrela (dso->elf, data, ndx, &rela); sym = reloc_r_sym (dso, rela.r_info); type = reloc_r_type (dso, rela.r_info); } if (sym != 0) do_reloc (dso, map, scope, sym, type); } } void do_relocs (DSO *dso, struct ldlibs_link_map *map, struct r_scope_elem *scope, int tag) { GElf_Addr rel_start, rel_end; GElf_Addr pltrel_start, pltrel_end; int first, last; /* Load the DT_REL or DT_RELA section. */ if (dso->info[tag] != 0) { rel_start = dso->info[tag]; rel_end = rel_start + dso->info[tag == DT_REL ? DT_RELSZ : DT_RELASZ]; first = addr_to_sec (dso, rel_start); last = addr_to_sec (dso, rel_end - 1); while (first <= last) do_rel_section (dso, map, scope, tag, first++); /* If the DT_JMPREL relocs are of the same type and not included, load them too. Assume they overlap completely or not at all, and are in at most a single section. They also need to be adjacent. */ if (dso->info[DT_PLTREL] == tag) { pltrel_start = dso->info[DT_JMPREL]; pltrel_end = pltrel_start + dso->info[DT_PLTRELSZ]; if (pltrel_start < rel_start || pltrel_start >= rel_end) do_rel_section (dso, map, scope, tag, addr_to_sec (dso, pltrel_start)); } } else if (dso->info[DT_PLTREL] == tag) do_rel_section (dso, map, scope, tag, addr_to_sec (dso, dso->info[DT_JMPREL])); } /* MIPS GOTs are not handled by normal relocations. Instead, entry X in the global GOT is associated with symbol DT_MIPS_GOTSYM + X. For the purposes of symbol lookup and conflict resolution, we need to act as though entry X had a dynamic relocation against symbol DT_MIPS_GOTSYM + X. */ void do_mips_global_got_relocs (DSO *dso, struct ldlibs_link_map *map, struct r_scope_elem *scope) { GElf_Word i; for (i = dso->info_DT_MIPS_GOTSYM; i < dso->info_DT_MIPS_SYMTABNO; i++) do_reloc (dso, map, scope, i, R_MIPS_REL32); } void handle_relocs_in_entry (struct dso_list *entry, struct dso_list *dso_list) { GElf_Word i; do_relocs (entry->dso, entry->map, dso_list->map->l_local_scope, DT_REL); do_relocs (entry->dso, entry->map, dso_list->map->l_local_scope, DT_RELA); if (entry->dso->ehdr.e_machine == EM_MIPS) do_mips_global_got_relocs (entry->dso, entry->map, dso_list->map->l_local_scope); } void handle_relocs (DSO *dso, struct dso_list *dso_list) { struct dso_list *ldso, *tail; /* do them all last to first. skip the dynamic linker; then do it last in glibc this is conditional on the opencount; but every binary should be linked to libc and thereby have an opencount for ld.so... besides, that's the only way it would get on our dso list. */ tail = dso_list; while (tail->next) tail = tail->next; ldso = NULL; while (tail) { if (is_ldso_soname (tail->dso->soname)) ldso = tail; else handle_relocs_in_entry (tail, dso_list); tail = tail->prev; } if (ldso) handle_relocs_in_entry (ldso, dso_list); } void add_to_scope (struct r_scope_elem *scope, struct dso_list *ent) { struct needed_list *n; int i; for (i = 0; i < scope->r_nlist; i++) if (scope->r_list[i] == ent->map) return; scope->r_list[scope->r_nlist++] = ent->map; n = ent->needed; while (n) { add_to_scope (scope, n->ent); n = n->next; } } void build_local_scope (struct dso_list *ent, int max) { ent->map->l_local_scope = malloc (sizeof (struct r_scope_elem)); ent->map->l_local_scope->r_list = malloc (sizeof (struct ldlibs_link_map *) * max); ent->map->l_local_scope->r_nlist = 0; add_to_scope (ent->map->l_local_scope, ent); } /* Assign TLS offsets for every loaded library. This code is taken almost directly from glibc! */ #define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) static void determine_tlsoffsets (int e_machine, struct r_scope_elem *search_list) { uint64_t freetop = 0; uint64_t freebottom = 0; uint64_t offset; uint64_t modid = 1; int i; /* This comes from each architecture's ABI. If TLS_TCB_AT_TP, then set offset to -1; if TLS_DTV_AT_TP, then set offset to TLS_TCB_SIZE. */ switch (e_machine) { case EM_X86_64: offset = -1; break; case EM_386: offset = -1; break; case EM_SH: offset = 8; break; case EM_PPC: offset = 0; break; case EM_PPC64: offset = 0; break; case EM_ARM: offset = 8; break; case EM_MIPS: offset = 0; break; case EM_SPARC: case EM_SPARC32PLUS: offset = -1; break; case EM_SPARCV9: offset = -1; break; default: /* Hope there's no TLS! */ for (i = 0; i < search_list->r_nlist; i++) { struct ldlibs_link_map *map = search_list->r_list[i]; if (map->l_tls_blocksize > 0) error (1, 0, "TLS encountered on an unsupported architecture"); } return; } /* Loop over the loaded DSOs. We use the symbol search order; this should be the same as glibc's ordering, which traverses l_next. It's somewhat important that we use both the same ordering to assign module IDs and the same algorithm to assign offsets, because the prelinker will resolve all relocations using these offsets... and then glibc will recalculate them. Future dynamic relocations in any loaded modules will use glibc's values. Also if we take too much space here, glibc won't allocate enough static TLS area to hold it. */ if (offset == (uint64_t) -1) { /* We simply start with zero. */ offset = 0; for (i = 0; i < search_list->r_nlist; i++) { struct ldlibs_link_map *map = search_list->r_list[i]; uint64_t firstbyte = (-map->l_tls_firstbyte_offset & (map->l_tls_align - 1)); uint64_t off; if (map->l_tls_blocksize == 0) continue; map->l_tls_modid = modid++; if (freebottom - freetop >= map->l_tls_blocksize) { off = roundup (freetop + map->l_tls_blocksize - firstbyte, map->l_tls_align) + firstbyte; if (off <= freebottom) { freetop = off; map->l_tls_offset = off; continue; } } off = roundup (offset + map->l_tls_blocksize - firstbyte, map->l_tls_align) + firstbyte; if (off > offset + map->l_tls_blocksize + (freebottom - freetop)) { freetop = offset; freebottom = off - map->l_tls_blocksize; } offset = off; map->l_tls_offset = off; } } else { for (i = 0; i < search_list->r_nlist; i++) { struct ldlibs_link_map *map = search_list->r_list[i]; uint64_t firstbyte = (-map->l_tls_firstbyte_offset & (map->l_tls_align - 1)); uint64_t off; if (map->l_tls_blocksize == 0) continue; map->l_tls_modid = modid++; if (map->l_tls_blocksize <= freetop - freebottom) { off = roundup (freebottom, map->l_tls_align); if (off - freebottom < firstbyte) off += map->l_tls_align; if (off + map->l_tls_blocksize - firstbyte <= freetop) { map->l_tls_offset = off - firstbyte; freebottom = (off + map->l_tls_blocksize - firstbyte); continue; } } off = roundup (offset, map->l_tls_align); if (off - offset < firstbyte) off += map->l_tls_align; map->l_tls_offset = off - firstbyte; if (off - firstbyte - offset > freetop - freebottom) { freebottom = offset; freetop = off - firstbyte; } offset = off + map->l_tls_blocksize - firstbyte; } } } static struct argp argp = { options, parse_opt, "[FILES]", argp_doc }; struct ldlibs_link_map *requested_map; static void process_one_dso (DSO *dso, int host_paths); int main(int argc, char **argv) { int remaining; int multiple = 0; host_paths = 1; sysroot = getenv ("PRELINK_SYSROOT"); #ifdef DEFAULT_SYSROOT if (sysroot == NULL) { extern char *make_relative_prefix (const char *, const char *, const char *); sysroot = make_relative_prefix (argv[0], BINDIR, DEFAULT_SYSROOT); } #endif elf_version (EV_CURRENT); argp_parse (&argp, argc, argv, 0, &remaining, 0); if (sysroot) sysroot = canonicalize_file_name (sysroot); if (remaining == argc) error (1, 0, "missing file arguments\nTry `%s: --help' for more information.", argv[0]); if ((argc-remaining) >= 2) multiple = 1; while (remaining < argc) { DSO *dso = NULL; int i, fd; if (host_paths) fd = open (argv[remaining], O_RDONLY); else fd = wrap_open (argv[remaining], O_RDONLY); if (fd >= 0) dso = fdopen_dso (fd, argv[remaining]); if (dso == NULL) error (1, errno, "Could not open %s", argv[remaining]); load_ld_so_conf (gelf_getclass (dso->elf) == ELFCLASS64, ( dso->ehdr.e_machine == EM_MIPS) && ( dso->ehdr.e_flags & EF_MIPS_ABI2 ) ); if (multiple) printf ("%s:\n", argv[remaining]); for (i = 0; i < dso->ehdr.e_phnum; ++i) if (dso->phdr[i].p_type == PT_INTERP) break; /* If there are no PT_INTERP segments, it is statically linked. */ if (dso->ehdr.e_type == ET_EXEC && i == dso->ehdr.e_phnum) printf ("\tnot a dynamic executable\n"); else process_one_dso (dso, host_paths); remaining++; } return 0; } static void process_one_dso (DSO *dso, int host_paths) { struct dso_list *dso_list, *cur_dso_ent, *old_dso_ent; const char *req = getenv ("RTLD_TRACE_PRELINKING"); int i, flag; int process_relocs = 0; /* Close enough. Really it's if LD_WARN is "" and RTLD_TRACE_PRELINKING. */ if (getenv ("LD_WARN") == 0 && req != NULL) process_relocs = 1; dso_list = load_dsos (dso, host_paths); cur_dso_ent = dso_list; i = 0; while (cur_dso_ent) { if (cur_dso_ent->dso) { create_ldlibs_link_map (cur_dso_ent); if (req && strcmp (req, cur_dso_ent->dso->filename) == 0) requested_map = cur_dso_ent->map; i++; } cur_dso_ent = cur_dso_ent->next; } dso_list->map->l_local_scope = malloc (sizeof (struct r_scope_elem)); dso_list->map->l_local_scope->r_list = malloc (sizeof (struct ldlibs_link_map *) * i); dso_list->map->l_local_scope->r_nlist = i; cur_dso_ent = dso_list; i = 0; while (cur_dso_ent) { if (cur_dso_ent->dso) { dso_list->map->l_local_scope->r_list[i] = cur_dso_ent->map; if (cur_dso_ent != dso_list) build_local_scope (cur_dso_ent, dso_list->map->l_local_scope->r_nlist); i++; } cur_dso_ent = cur_dso_ent->next; } determine_tlsoffsets (dso->ehdr.e_machine, dso_list->map->l_local_scope); cur_dso_ent = dso_list; flag = 0; /* In ldd mode, do not show the application. Note that we do show it in list-loaded-objects RTLD_TRACE_PRELINK mode. */ if (req == NULL && cur_dso_ent) cur_dso_ent = cur_dso_ent->next; while (cur_dso_ent) { char *filename; if (host_paths && sysroot && cur_dso_ent->dso) { const char *rooted_filename; if (cur_dso_ent->dso->filename[0] == '/') rooted_filename = cur_dso_ent->dso->filename; else rooted_filename = cur_dso_ent->canon_filename; filename = malloc (strlen (rooted_filename) + strlen (sysroot) + 1); strcpy (filename, sysroot); strcat (filename, rooted_filename); } else if (cur_dso_ent->dso) filename = strdup (cur_dso_ent->dso->filename); else filename = NULL; /* The difference between the two numbers must be dso->base, and the first number must be unique. */ if (cur_dso_ent->dso == NULL) printf ("\t%s => not found\n", cur_dso_ent->name); else if (gelf_getclass (cur_dso_ent->dso->elf) == ELFCLASS32) { if (process_relocs) { printf ("\t%s => %s (0x%08x, 0x%08x)", cur_dso_ent->name, filename, (uint32_t) cur_dso_ent->map->l_map_start, (uint32_t) (cur_dso_ent->map->l_map_start - cur_dso_ent->dso->base)); if (cur_dso_ent->map->l_tls_modid) printf (" TLS(0x%x, 0x%08x)", (uint32_t) cur_dso_ent->map->l_tls_modid, (uint32_t) cur_dso_ent->map->l_tls_offset); printf ("\n"); } else printf ("\t%s => %s (0x%08x)\n", cur_dso_ent->name, filename, (uint32_t) cur_dso_ent->map->l_map_start); } else { if (process_relocs) { printf ("\t%s => %s (0x%016" HOST_LONG_LONG_FORMAT "x, 0x%016" HOST_LONG_LONG_FORMAT "x)", cur_dso_ent->name, filename, (unsigned long long) cur_dso_ent->map->l_map_start, (unsigned long long) (cur_dso_ent->map->l_map_start - cur_dso_ent->dso->base)); if (cur_dso_ent->map->l_tls_modid) printf (" TLS(0x%x, 0x%016" HOST_LONG_LONG_FORMAT "x)", (uint32_t) cur_dso_ent->map->l_tls_modid, (unsigned long long) cur_dso_ent->map->l_tls_offset); printf ("\n"); } else printf ("\t%s => %s (0x%08x)\n", cur_dso_ent->name, filename, (uint32_t) cur_dso_ent->map->l_map_start); } if (filename) free (filename); cur_dso_ent = cur_dso_ent->next; flag = 1; } if (dso_open_error) exit (1); if (process_relocs) handle_relocs (dso_list->dso, dso_list); cur_dso_ent = dso_list; while (cur_dso_ent) { if (cur_dso_ent->dso) close_dso (cur_dso_ent->dso); old_dso_ent = cur_dso_ent; cur_dso_ent = cur_dso_ent->next; if (old_dso_ent->needed) free_needed (old_dso_ent->needed); free (old_dso_ent); } }