OpenBSD-4.6/gnu/usr.bin/ld/lib.c

Compare this file to the similar file:
Show the results in this format:

/* * $OpenBSD: lib.c,v 1.13 2008/02/12 21:17:53 miod Exp $	- library routines*/
/*
 */

#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/time.h>
#include <err.h>
#include <fcntl.h>
#include <ar.h>
#include <ranlib.h>
#include <a.out.h>
#include <stab.h>
#include <string.h>
#include <dirent.h>
#include <ctype.h>

#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/<namelen>, with name as the
	 * first <namelen> 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;
}