/* * $OpenBSD: lib.c,v 1.13 2008/02/12 21:17:53 miod Exp $ - library routines*/ /* */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ld.h" static void linear_library(int, struct file_entry *); static void symdef_library(int, struct file_entry *, int); static struct file_entry *decode_library_subfile(int, struct file_entry *, int, int *); /* * Search the library ENTRY, already open on descriptor FD. This means * deciding which library members to load, making a chain of `struct * file_entry' for those members, and entering their global symbols in the * hash table. */ void search_library(int fd, struct file_entry *entry) { int member_length; char *name; struct file_entry *subentry; if (!(link_mode & FORCEARCHIVE) && !undefined_global_sym_count) return; /* Examine its first member, which starts SARMAG bytes in. */ subentry = decode_library_subfile(fd, entry, SARMAG, &member_length); if (!subentry) return; name = subentry->filename; free(subentry); /* Search via __.SYMDEF if that exists, else linearly. */ if (!strcmp(name, "__.SYMDEF")) symdef_library(fd, entry, member_length); else linear_library(fd, entry); } /* * Construct and return a file_entry for a library member. The library's * file_entry is library_entry, and the library is open on FD. * SUBFILE_OFFSET is the byte index in the library of this member's header. * We store the length of the member into *LENGTH_LOC. */ static struct file_entry * decode_library_subfile(int fd, struct file_entry *library_entry, int subfile_offset, int *length_loc) { int bytes_read; int namelen; int member_length, content_length; int starting_offset; char *name; struct ar_hdr hdr1; struct file_entry *subentry; lseek(fd, subfile_offset, 0); bytes_read = read(fd, &hdr1, sizeof hdr1); if (!bytes_read) return 0; /* end of archive */ if (sizeof hdr1 != bytes_read) errx(1, "%s: malformed library archive", get_file_name(library_entry)); if (sscanf(hdr1.ar_size, "%d", &member_length) != 1) errx(1, "%s: malformatted header of archive member: %.*s", get_file_name(library_entry), (int) sizeof(hdr1.ar_name), hdr1.ar_name); subentry = (struct file_entry *) xmalloc(sizeof(struct file_entry)); bzero(subentry, sizeof(struct file_entry)); for (namelen = 0; namelen < sizeof hdr1.ar_name && hdr1.ar_name[namelen] != 0 && hdr1.ar_name[namelen] != ' ' && hdr1.ar_name[namelen] != '/'; namelen++); starting_offset = subfile_offset + sizeof hdr1; content_length = member_length; #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ if (strncmp(hdr1.ar_name, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && isdigit(hdr1.ar_name[sizeof(AR_EFMT1) - 1])) { namelen = atoi(&hdr1.ar_name[sizeof(AR_EFMT1) - 1]); name = (char *)xmalloc(namelen + 1); if (read(fd, name, namelen) != namelen) errx(1, "%s: malformatted archive member: %.*s", get_file_name(library_entry), (int) sizeof(hdr1.ar_name), hdr1.ar_name); name[namelen] = 0; content_length -= namelen; starting_offset += namelen; } else #endif { name = (char *)xmalloc(namelen + 1); strncpy(name, hdr1.ar_name, namelen); name[namelen] = 0; } subentry->filename = name; subentry->local_sym_name = name; subentry->starting_offset = starting_offset; subentry->superfile = library_entry; subentry->total_size = content_length; #if 0 subentry->symbols = 0; subentry->strings = 0; subentry->subfiles = 0; subentry->chain = 0; subentry->flags = 0; #endif (*length_loc) = member_length; return subentry; } static int subfile_wanted_p(struct file_entry *); /* * Search a library that has a __.SYMDEF member. FD is a descriptor on * which the library is open. The file pointer is assumed to point at the * __.SYMDEF data. ENTRY is the library's file_entry. MEMBER_LENGTH is the * length of the __.SYMDEF data. */ static void symdef_library(int fd, struct file_entry *entry, int member_length) { int *symdef_data = (int *) xmalloc(member_length); struct ranlib *symdef_base; char *sym_name_base; int nsymdefs; int length_of_strings; int not_finished; int bytes_read; int i; struct file_entry *prev = 0; int prev_offset = 0; bytes_read = read(fd, symdef_data, member_length); if (bytes_read != member_length) errx(1, "%s: malformatted __.SYMDEF", get_file_name(entry)); nsymdefs = md_swap_long(*symdef_data) / sizeof(struct ranlib); if (nsymdefs < 0 || nsymdefs * sizeof(struct ranlib) + 2 * sizeof(int) > member_length) errx(1, "%s: malformatted __.SYMDEF", get_file_name(entry)); symdef_base = (struct ranlib *) (symdef_data + 1); length_of_strings = md_swap_long(*(int *) (symdef_base + nsymdefs)); if (length_of_strings < 0 || nsymdefs * sizeof(struct ranlib) + length_of_strings + 2 * sizeof(int) > member_length) errx(1, "%s: malformatted __.SYMDEF", get_file_name(entry)); sym_name_base = sizeof(int) + (char *) (symdef_base + nsymdefs); /* Check all the string indexes for validity. */ md_swapin_ranlib_hdr(symdef_base, nsymdefs); for (i = 0; i < nsymdefs; i++) { int index = symdef_base[i].ran_un.ran_strx; if (index < 0 || index >= length_of_strings || (index && *(sym_name_base + index - 1))) errx(1, "%s: malformatted __.SYMDEF", get_file_name(entry)); } /* * Search the symdef data for members to load. Do this until one * whole pass finds nothing to load. */ not_finished = 1; while (not_finished) { not_finished = 0; /* * Scan all the symbols mentioned in the symdef for ones that * we need. Load the library members that contain such * symbols. */ for (i = 0; (i < nsymdefs && ((link_mode & FORCEARCHIVE) || undefined_global_sym_count || common_defined_global_count)); i++) { symbol *sp; int junk; int j; int offset = symdef_base[i].ran_off; struct file_entry *subentry; if (symdef_base[i].ran_un.ran_strx < 0) continue; sp = getsym_soft(sym_name_base + symdef_base[i].ran_un.ran_strx); /* * If we find a symbol that appears to be needed, * think carefully about the archive member that the * symbol is in. */ /* * Per Mike Karels' recommendation, we no longer load * library files if the only reference(s) that would * be satisfied are 'common' references. This * prevents some problems with name pollution (e.g. a * global common 'utime' linked to a function). * * If we're not forcing the archive in then we don't * need to bother if: we've never heard of the symbol, * or if it is already defined. The last clause causes * archive members to be searched for definitions * satisfying undefined shared object symbols. */ if (!(link_mode & FORCEARCHIVE) && (!sp || sp->defined || (!(sp->flags & GS_REFERENCED) && !sp->sorefs))) continue; /* * Don't think carefully about any archive member * more than once in a given pass. */ if (prev_offset == offset) continue; prev_offset = offset; /* * Read the symbol table of the archive member. */ subentry = decode_library_subfile(fd, entry, offset, &junk); if (subentry == 0) errx(1, "invalid offset for %s in symbol table of %s", sym_name_base + symdef_base[i].ran_un.ran_strx, entry->filename); read_entry_symbols(fd, subentry); subentry->strings = (char *) xmalloc(subentry->string_size); read_entry_strings(fd, subentry); /* * Now scan the symbol table and decide whether to * load. */ if (!(link_mode & FORCEARCHIVE) && !subfile_wanted_p(subentry)) { free(subentry->symbols); free(subentry->strings); free(subentry); } else { /* * This member is needed; load it. Since we * are loading something on this pass, we * must make another pass through the symdef * data. */ not_finished = 1; read_entry_relocation(fd, subentry); enter_file_symbols(subentry); if (prev) prev->chain = subentry; else entry->subfiles = subentry; prev = subentry; /* * Clear out this member's symbols from the * symdef data so that following passes won't * waste time on them. */ for (j = 0; j < nsymdefs; j++) { if (symdef_base[j].ran_off == offset) symdef_base[j].ran_un.ran_strx = -1; } /* * We'll read the strings again * if we need them. */ free(subentry->strings); subentry->strings = 0; } } } free(symdef_data); } /* * Search a library that has no __.SYMDEF. ENTRY is the library's file_entry. * FD is the descriptor it is open on. */ static void linear_library(int fd, struct file_entry *entry) { struct file_entry *prev = 0; int this_subfile_offset = SARMAG; while ((link_mode & FORCEARCHIVE) || undefined_global_sym_count || common_defined_global_count) { int member_length; struct file_entry *subentry; subentry = decode_library_subfile(fd, entry, this_subfile_offset, &member_length); if (!subentry) return; read_entry_symbols(fd, subentry); subentry->strings = (char *)xmalloc(subentry->string_size); read_entry_strings(fd, subentry); if (!(link_mode & FORCEARCHIVE) && !subfile_wanted_p(subentry)) { free(subentry->symbols); free(subentry->strings); free(subentry); } else { read_entry_relocation(fd, subentry); enter_file_symbols(subentry); if (prev) prev->chain = subentry; else entry->subfiles = subentry; prev = subentry; free(subentry->strings); subentry->strings = 0; } this_subfile_offset += member_length + sizeof(struct ar_hdr); if (this_subfile_offset & 1) this_subfile_offset++; } } /* * ENTRY is an entry for a library member. Its symbols have been read into * core, but not entered. Return nonzero if we ought to load this member. */ static int subfile_wanted_p(struct file_entry *entry) { struct localsymbol *lsp, *lspend; #ifdef DOLLAR_KLUDGE int dollar_cond = 0; #endif lspend = entry->symbols + entry->nsymbols; for (lsp = entry->symbols; lsp < lspend; lsp++) { struct nlist *p = &lsp->nzlist.nlist; int type = p->n_type; char *name = p->n_un.n_strx + entry->strings; symbol *sp = getsym_soft(name); /* * If the symbol has an interesting definition, we could * potentially want it. */ if (! (type & N_EXT) || (type == (N_UNDF | N_EXT) && p->n_value == 0 #ifdef DOLLAR_KLUDGE && name[1] != '$' #endif ) #ifdef SET_ELEMENT_P || SET_ELEMENT_P(type) || set_element_prefixed_p(name) #endif ) continue; #ifdef DOLLAR_KLUDGE if (name[1] == '$') { sp = getsym_soft(&name[2]); dollar_cond = 1; if (!sp) continue; if (sp->flags & SP_REFERENCED) { if (write_map) { print_file_name(entry, stdout); fprintf(stdout, " needed due to $-conditional %s\n", name); } return 1; } continue; } #endif /* * If this symbol has not been hashed, we can't be * looking for it. */ if (!sp) continue; /* * We don't load a file if it merely satisfies a * common reference (see explanation above in * symdef_library()). */ if ((sp->flags & GS_REFERENCED) && !sp->defined) { /* * This is a symbol we are looking for. It * is either not yet defined or defined as a * common. */ #ifdef DOLLAR_KLUDGE if (dollar_cond) continue; #endif if (type == (N_UNDF | N_EXT)) { /* * Symbol being defined as common. * Remember this, but don't load * subfile just for this. */ /* * If it didn't used to be common, up * the count of common symbols. */ if (!sp->common_size) common_defined_global_count++; if (sp->common_size < p->n_value) sp->common_size = p->n_value; if (!sp->defined) undefined_global_sym_count--; sp->defined = type; continue; } if (sp->flags & GS_WEAK) /* Weak symbols don't pull archive members */ continue; if (write_map) { print_file_name(entry, stdout); fprintf(stdout, " needed due to %s\n", sp->name); } return 1; } else if (!sp->defined && sp->sorefs) { /* * Check for undefined symbols or commons * in shared objects. */ struct localsymbol *lsp; for (lsp = sp->sorefs; lsp; lsp = lsp->next) { int type = lsp->nzlist.nlist.n_type; if ( (type & N_EXT) && (type & N_STAB) == 0 && type != (N_UNDF | N_EXT)) break; /* We don't need it */ } if (lsp != NULL) /* * We have a worthy definition in a shared * object that was specified ahead of the * archive we're examining now. So, punt. */ continue; /* * At this point, we have an undefined shared * object reference. Again, if the archive member * defines a common we just note the its size. * Otherwise, the member gets included. */ if (type == (N_UNDF|N_EXT) && p->n_value) { /* * New symbol is common, just takes its * size, but don't load. */ sp->common_size = p->n_value; sp->defined = type; continue; } /* * THIS STILL MISSES the case where one shared * object defines a common and the next defines * more strongly; fix this someday by making * `struct glosym' and enter_global_ref() more * symmetric. */ if (write_map) { print_file_name(entry, stdout); fprintf(stdout, " needed due to shared lib ref %s (%d)\n", sp->name, lsp ? lsp->nzlist.nlist.n_type : -1); } return 1; } } return 0; } /* * Read the symbols of dynamic entity ENTRY into core. Assume it is already * open, on descriptor FD. */ void read_shared_object(int fd, struct file_entry *entry) { struct _dynamic dyn; struct section_dispatch_table sdt; struct nlist *np; struct nzlist *nzp; int n, i, has_nz = 0; if (!(entry->flags & E_HEADER_VALID)) read_header(fd, entry); /* Read DYNAMIC structure (first in data segment) */ if (lseek(fd, text_offset(entry) + entry->header.a_text, L_SET) == (off_t)-1) err(1, "%s: lseek", get_file_name(entry)); if (read(fd, &dyn, sizeof dyn) != sizeof dyn) { errx(1, "%s: premature EOF reading _dynamic", get_file_name(entry)); } md_swapin__dynamic(&dyn); /* Check version */ switch (dyn.d_version) { default: errx(1, "%s: unsupported _DYNAMIC version: %d", get_file_name(entry), dyn.d_version); break; case LD_VERSION_SUN: break; case LD_VERSION_BSD: has_nz = 1; break; } /* Read Section Dispatch Table (from data segment) */ if (lseek(fd, text_offset(entry) + (long)dyn.d_un.d_sdt - (DATA_START(entry->header) - N_DATOFF(entry->header)), L_SET) == (off_t)-1) err(1, "%s: lseek", get_file_name(entry)); if (read(fd, &sdt, sizeof sdt) != sizeof sdt) errx(1, "%s: premature EOF reading sdt", get_file_name(entry)); md_swapin_section_dispatch_table(&sdt); /* Read symbols (text segment) */ n = sdt.sdt_strings - sdt.sdt_nzlist; entry->nsymbols = n / (has_nz ? sizeof(struct nzlist) : sizeof(struct nlist)); nzp = (struct nzlist *)(np = (struct nlist *)alloca (n)); entry->symbols = (struct localsymbol *) xmalloc(entry->nsymbols * sizeof(struct localsymbol)); if (lseek(fd, text_offset(entry) + (long)sdt.sdt_nzlist - (TEXT_START(entry->header) - N_TXTOFF(entry->header)), L_SET) == (off_t)-1) err(1, "%s: lseek", get_file_name(entry)); if (read(fd, (char *)nzp, n) != n) errx(1, "%s: premature EOF reading symbols ", get_file_name(entry)); if (has_nz) { md_swapin_zsymbols(nzp, entry->nsymbols); } else { md_swapin_symbols(np, entry->nsymbols); } /* Convert to structs localsymbol */ for (i = 0; i < entry->nsymbols; i++) { if (has_nz) { entry->symbols[i].nzlist = *nzp++; } else { entry->symbols[i].nzlist.nlist = *np++; entry->symbols[i].nzlist.nz_size = 0; } entry->symbols[i].symbol = NULL; entry->symbols[i].next = NULL; entry->symbols[i].entry = entry; entry->symbols[i].gotslot_offset = -1; entry->symbols[i].flags = 0; } /* Read strings (text segment) */ n = entry->string_size = sdt.sdt_str_sz; entry->strings = (char *)alloca(n); entry->strings_offset = text_offset(entry) + sdt.sdt_strings; if (lseek(fd, entry->strings_offset - (TEXT_START(entry->header) - N_TXTOFF(entry->header)), L_SET) == (off_t)-1) err(1, "%s: lseek", get_file_name(entry)); if (read(fd, entry->strings, n) != n) errx(1, "%s: premature EOF reading strings", get_file_name(entry)); enter_file_symbols (entry); entry->strings = 0; /* * Load any subsidiary shared objects. */ if (sdt.sdt_sods) { struct sod sod; off_t offset; struct file_entry *prev = NULL; offset = (off_t)sdt.sdt_sods; while (1) { struct file_entry *subentry; char *libname, name[MAXPATHLEN]; /*XXX*/ if (lseek(fd, offset - (TEXT_START(entry->header) - N_TXTOFF(entry->header)), L_SET) == (off_t)-1) err(1, "%s: lseek", get_file_name(entry)); if (read(fd, &sod, sizeof(sod)) != sizeof(sod)) errx(1, "%s: premature EOF reding sod", get_file_name(entry)); md_swapin_sod(&sod, 1); if (lseek(fd, (off_t)sod.sod_name - (TEXT_START(entry->header) - N_TXTOFF(entry->header)), L_SET) == (off_t)-1) err(1, "%s: lseek", get_file_name(entry)); (void)read(fd, name, sizeof(name)); /*XXX*/ /* Optimization */ if (sod.sod_library && will_see_later(name)) { if ((offset = (off_t)sod.sod_next) == 0) break; else continue; } subentry = (struct file_entry *) xmalloc(sizeof(struct file_entry)); bzero(subentry, sizeof(struct file_entry)); subentry->superfile = entry; subentry->flags = E_SECONDCLASS; if (sod.sod_library) { int sod_major = sod.sod_major; int sod_minor = sod.sod_minor; libname = findshlib(name, &sod_major, &sod_minor, 0); if (libname == NULL) errx(1,"no shared -l%s.%d.%d available", name, sod.sod_major, sod.sod_minor); subentry->filename = libname; subentry->local_sym_name = concat("-l", name, ""); } else { subentry->filename = strdup(name); subentry->local_sym_name = strdup(name); } /* Sanity check */ if (strcmp(subentry->filename, subentry->superfile->filename) == 0) errx(1, "loop in library dependencies: %s", subentry->filename); read_file_symbols(subentry); if (prev) prev->chain = subentry; else entry->subfiles = subentry; prev = subentry; fd = file_open(entry); if ((offset = (off_t)sod.sod_next) == 0) break; } } #ifdef SUN_COMPAT if (link_mode & SILLYARCHIVE) { char *cp, *sa_name; char armag[SARMAG]; int fd; struct file_entry *subentry; sa_name = strdup(entry->filename); if (sa_name == NULL) goto out; cp = sa_name + strlen(sa_name) - 1; while (cp > sa_name) { if (!isdigit(*cp) && *cp != '.') break; --cp; } if (cp <= sa_name || *cp != 'o') { /* Not in `libxxx.so.n.m' form */ free(sa_name); goto out; } *cp = 'a'; if ((fd = open(sa_name, O_RDONLY, 0)) < 0) goto out; /* Read archive magic */ bzero(armag, SARMAG); (void)read(fd, armag, SARMAG); (void)close(fd); if (strncmp(armag, ARMAG, SARMAG) != 0) { warnx("%s: malformed silly archive", get_file_name(entry)); goto out; } subentry = (struct file_entry *) xmalloc(sizeof(struct file_entry)); bzero(subentry, sizeof(struct file_entry)); entry->silly_archive = subentry; subentry->superfile = entry; subentry->filename = sa_name; subentry->local_sym_name = sa_name; subentry->flags |= E_IS_LIBRARY; search_library(file_open(subentry), subentry); out: ; } #endif } #undef major #undef minor int findlib(struct file_entry *p) { int i; int fd = -1; int major = -1, minor = -1; char *cp, *fname = NULL; if (!(p->flags & E_SEARCH_DYNAMIC)) goto dot_a; fname = findshlib(p->filename, &major, &minor, 1); if (fname && (fd = open(fname, O_RDONLY, 0)) >= 0) { p->filename = fname; p->lib_major = major; p->lib_minor = minor; p->flags &= ~E_SEARCH_DIRS; return fd; } (void)free(fname); dot_a: p->flags &= ~E_SEARCH_DYNAMIC; if ((cp = strrchr(p->filename, '/'))) { char *tmp; *cp++ = '\0'; tmp = concat(p->filename, "/lib", cp); fname = concat(tmp, ".a", ""); free(tmp); *(--cp) = '/'; } else fname = concat("lib", p->filename, ".a"); for (i = 0; i < n_search_dirs; i++) { char *path = concat(search_dirs[i], "/", fname); fd = open(path, O_RDONLY, 0); if (fd >= 0) { p->filename = path; p->flags &= ~E_SEARCH_DIRS; break; } (void)free(path); } (void)free(fname); return fd; }