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

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

/* * $OpenBSD: warnings.c,v 1.12 2008/02/12 21:17:53 miod Exp $*/
/*
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/time.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ar.h>
#include <ranlib.h>
#include <a.out.h>
#include <stab.h>
#include <string.h>
#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#include "ld.h"

static int reported_undefineds;

/*
 * Print the filename of ENTRY on OUTFILE (a stdio stream),
 * and then a newline.
 */

void
prline_file_name(struct file_entry *entry, FILE *outfile)
{
	print_file_name (entry, outfile);
	fprintf (outfile, "\n");
}

/*
 * Print the filename of ENTRY on OUTFILE (a stdio stream).
 */

void
print_file_name(struct file_entry *entry, FILE *outfile)
{
	if (entry == NULL) {
		fprintf (outfile, "NULL");
	}

	if (entry->superfile) {
		print_file_name (entry->superfile, outfile);
		fprintf (outfile, "(%s)", entry->filename);
	} else
		fprintf (outfile, "%s", entry->filename);
}

/*
 * Return the filename of entry as a string (malloc'd for the purpose)
 */

char *
get_file_name(struct file_entry *entry)
{
	char *result, *supfile;
	size_t len;

	if (entry == NULL) {
		return (char *)strdup("NULL");
	}

	if (entry->superfile) {
		supfile = get_file_name(entry->superfile);
		len = strlen(supfile) + 1 + strlen(entry->filename) + 2;
		result = (char *)
			xmalloc(len);
		(void)snprintf(result, len, "%s(%s)", supfile,
		    entry->filename);
		free(supfile);

	} else {
		result = (char *)xstrdup(entry->filename);
	}
	return result;
}

/* Print a complete or partial map of the output file. */

static void	describe_file_sections(struct file_entry *, void *);
static void	list_file_locals(struct file_entry *, void *);

void
print_symbols(FILE *outfile)
{
	fprintf(outfile, "\nFiles:\n\n");
	each_file(describe_file_sections, (void *)outfile);

	fprintf(outfile, "\nGlobal symbols:\n\n");
	FOR_EACH_SYMBOL(i, sp) {
		fprintf(outfile, "  %s: ", sp->name);
		if (!(sp->flags & GS_REFERENCED))
			fprintf(outfile, "unreferenced");
		else if (sp->so_defined)
			fprintf(outfile, "sodefined");
		else if (!sp->defined)
			fprintf(outfile, "undefined");
		else if (sp->defined == (N_UNDF|N_EXT))
			fprintf(outfile, "common: size %#x", sp->common_size);
		else
			fprintf(outfile, "type %d, value %#lx, size %#x",
				sp->defined, sp->value, sp->size);
		if (sp->alias)
			fprintf(outfile, ", aliased to %s", sp->alias->name);
		fprintf(outfile, "\n");
	} END_EACH_SYMBOL;

	each_file(list_file_locals, (void *)outfile);
}

static void
describe_file_sections(struct file_entry *entry, void *arg)
{
	FILE *outfile = arg;

	fprintf(outfile, "  ");
	print_file_name(entry, outfile);
	if (entry->flags & (E_JUST_SYMS | E_DYNAMIC))
		fprintf(outfile, " symbols only\n");
	else
		fprintf(outfile, " text %x(%x), data %x(%x), bss %x(%x) hex\n",
			entry->text_start_address, entry->header.a_text,
			entry->data_start_address, entry->header.a_data,
			entry->bss_start_address, entry->header.a_bss);
}

static void
list_file_locals(struct file_entry *entry, void *arg)
{
	FILE			*outfile = arg;
	struct localsymbol	*lsp, *lspend;

	entry->strings = (char *)alloca(entry->string_size);
	read_entry_strings (file_open(entry), entry);

	fprintf (outfile, "\nLocal symbols of ");
	print_file_name (entry, outfile);
	fprintf (outfile, ":\n\n");

	lspend = entry->symbols + entry->nsymbols;
	for (lsp = entry->symbols; lsp < lspend; lsp++) {
		struct nlist *p = &lsp->nzlist.nlist;
		/*
		 * If this is a definition,
		 * update it if necessary by this file's start address.
		 */
		if (!(p->n_type & (N_STAB | N_EXT)))
			fprintf(outfile, "  %s: 0x%lx\n",
				entry->strings + p->n_un.n_strx, p->n_value);
	}

	entry->strings = 0;		/* All done with them.  */
}


/* Static vars for do_warnings and subroutines of it */
static int list_unresolved_refs;	/* List unresolved refs */
static int list_multiple_defs;		/* List multiple definitions */

static struct line_debug_entry *init_debug_scan(int, struct file_entry *);
static int	next_debug_entry(int, struct line_debug_entry *);

/*
 * Structure for communication between do_file_warnings and it's
 * helper routines.  Will in practice be an array of three of these:
 * 0) Current line, 1) Next line, 2) Source file info.
 */
struct line_debug_entry
{
	int			line;
	char			*filename;
	struct localsymbol	*sym;
};

/*
 * Helper routines for do_file_warnings.
 */

/*
 * Return an integer less than, equal to, or greater than 0 as per the
 * relation between the two relocation entries.  Used by qsort.
 */

static int
reloc_cmp(const void *arg1, const void *arg2)
{
	const struct relocation_info *rel1 = arg1;
	const struct relocation_info *rel2 = arg2;
	return RELOC_ADDRESS(rel1) - RELOC_ADDRESS(rel2);
}

/*
 * Moves to the next debugging symbol in the file.  USE_DATA_SYMBOLS
 * determines the type of the debugging symbol to look for (DSLINE or
 * SLINE).  STATE_POINTER keeps track of the old and new locatiosn in
 * the file.  It assumes that state_pointer[1] is valid; ie
 * that it.sym points into some entry in the symbol table.  If
 * state_pointer[1].sym == 0, this routine should not be called.
 */

static int
next_debug_entry(int use_data_symbols, struct line_debug_entry state_pointer[3])
{
	struct line_debug_entry	*current = state_pointer,
				*next = state_pointer + 1,
				/* Used to store source file */
				*source = state_pointer + 2;

	struct file_entry	*entry = (struct file_entry *)source->sym;
	struct localsymbol	*lspend = entry->symbols + entry->nsymbols;


	current->sym = next->sym;
	current->line = next->line;
	current->filename = next->filename;

	while (++(next->sym) < lspend) {

		struct nlist	*np = &next->sym->nzlist.nlist;

		/*
		 * n_type is a char, and N_SOL, N_EINCL and N_BINCL are > 0x80,
		 * so may look negative...therefore, must mask to low bits
		 */
		switch (np->n_type & 0xff) {
		case N_SLINE:
			if (use_data_symbols)
				continue;
			next->line = np->n_desc;
			return 1;
		case N_DSLINE:
			if (!use_data_symbols)
				continue;
			next->line = np->n_desc;
			return 1;
#ifdef HAVE_SUN_STABS
		case N_EINCL:
			next->filename = source->filename;
			continue;
#endif
		case N_SO:
			source->filename = np->n_un.n_strx + entry->strings;
			source->line++;
#ifdef HAVE_SUN_STABS
		case N_BINCL:
#endif
		case N_SOL:
			next->filename = np->n_un.n_strx + entry->strings;
		default:
			continue;
		}
	}
	next->sym = (struct localsymbol *)0;
	return 0;
}

/*
 * Create a structure to save the state of a scan through the debug symbols.
 * USE_DATA_SYMBOLS is set if we should be scanning for DSLINE's instead of
 * SLINE's. ENTRY is the file entry which points at the symbols to use.
 */

static struct line_debug_entry *
init_debug_scan(int use_data_symbols, struct file_entry *entry)
{
	struct localsymbol	*lsp, *lspend;
	struct line_debug_entry *state_pointer, *current, *next, *source;

	state_pointer = (struct line_debug_entry *)
		xmalloc(3 * sizeof(*state_pointer));

	current = state_pointer,
	next = state_pointer + 1,
	source = state_pointer + 2;	/* Used to store source file */

	lspend = entry->symbols+entry->nsymbols;

	for (lsp = entry->symbols; lsp < lspend; lsp++)
		if (lsp->nzlist.nlist.n_type == N_SO)
			break;

	if (lsp >= lspend) {
		/* I believe this translates to "We lose" */
		current->filename = next->filename = entry->filename;
		current->line = next->line = -1;
		current->sym = next->sym = (struct localsymbol *)0;
		return state_pointer;
	}
	next->line = source->line = 0;
	next->filename = source->filename
			= (lsp->nzlist.nlist.n_un.n_strx + entry->strings);
	source->sym = (struct localsymbol *)entry;
	next->sym = lsp;

	/* To setup next */
	next_debug_entry(use_data_symbols, state_pointer);

	if (!next->sym) {	/* No line numbers for this section; */
		/* setup output results as appropriate */
		if (source->line) {
			current->filename = source->filename = entry->filename;
			current->line = -1;	/* Don't print lineno */
		} else {
			current->filename = source->filename;
			current->line = 0;
		}
		return state_pointer;
	}
	/* To setup current */
	next_debug_entry(use_data_symbols, state_pointer);

	return state_pointer;
}

/*
 * Takes an ADDRESS (in either text or data space) and a STATE_POINTER which
 * describes the current location in the implied scan through the debug
 * symbols within the file which ADDRESS is within, and returns the source
 * line number which corresponds to ADDRESS.
 */

static int
address_to_line(unsigned long address, struct line_debug_entry state_pointer[3])
{
	struct line_debug_entry	*current, *next, *tmp_pointer;
	int			use_data_symbols;

	current = state_pointer;
	next = state_pointer + 1;

	if (next->sym)
		use_data_symbols =
			(next->sym->nzlist.nlist.n_type & N_TYPE) == N_DATA;
	else
		return current->line;

	/* Go back to the beginning if we've already passed it. */
	if (current->sym->nzlist.nlist.n_value > address) {
		tmp_pointer = init_debug_scan(use_data_symbols,
					      (struct file_entry *)
					      ((state_pointer + 2)->sym));
		state_pointer[0] = tmp_pointer[0];
		state_pointer[1] = tmp_pointer[1];
		state_pointer[2] = tmp_pointer[2];
		free(tmp_pointer);
	}

	/* If we're still in a bad way, return -1, meaning invalid line. */
	if (current->sym->nzlist.nlist.n_value > address)
		return -1;

	while (next->sym
	       && next->sym->nzlist.nlist.n_value <= address
	       && next_debug_entry(use_data_symbols, state_pointer));

	return current->line;
}


/* Macros for manipulating bitvectors.  */
#define	BIT_SET_P(bv, index)	((bv)[(index) >> 3] & 1 << ((index) & 0x7))
#define	SET_BIT(bv, index)	((bv)[(index) >> 3] |= 1 << ((index) & 0x7))

/*
 * This routine will scan through the relocation data of file ENTRY, printing
 * out references to undefined symbols and references to symbols defined in
 * files with N_WARNING symbols.  If DATA_SEGMENT is non-zero, it will scan
 * the data relocation segment (and use N_DSLINE symbols to track line
 * number); otherwise it will scan the text relocation segment.  Warnings
 * will be printed on the output stream OUTFILE.  Eventually, every nlist
 * symbol mapped through will be marked in the NLIST_BITVECTOR, so we don't
 * repeat ourselves when we scan the nlists themselves.
 */

static void
do_relocation_warnings(struct file_entry *entry, int data_segment,
		       FILE *outfile, unsigned char *nlist_bitvector)
{
	struct relocation_info	*rp, *erp;
	int			start_of_segment;
	struct localsymbol	*start_of_syms;
	struct line_debug_entry	*state_pointer, *current;
	/* Assigned to generally static values; should not be written into.  */
	char *errfmt;
	/*
	 * Assigned to alloca'd values cand copied into; should be freed when
	 * done.
	 */
	char *errmsg;
	int  invalidate_line_number;

	rp = data_segment ? entry->datarel : entry->textrel;
	erp = data_segment ? (rp + entry->ndatarel) : (rp + entry->ntextrel);
	start_of_syms = entry->symbols;
	start_of_segment = (data_segment ?
		entry->data_start_address :
		entry->text_start_address);
	state_pointer = init_debug_scan(data_segment != 0, entry);
	current = state_pointer;

	/*
	 * We need to sort the relocation info here.  Sheesh, so much effort
	 * for one lousy error optimization.
	 */
	qsort(rp, erp - rp, sizeof(rp[0]), reloc_cmp);

	for (; rp < erp; rp++) {
		struct localsymbol *lsp;
		symbol *g;

		/*
		 * If the relocation isn't resolved through a symbol, continue.
		 */
		if (!RELOC_EXTERN_P(rp))
			continue;

		lsp = &entry->symbols[RELOC_SYMBOL(rp)];

		/*
		 * Local symbols shouldn't ever be used by relocation info,
		 * so the next should be safe. This is, of course, wrong.
		 * References to local BSS symbols can be the targets of
		 * relocation info, and they can (must) be resolved through
		 * symbols.  However, these must be defined properly, (the
		 * assembler would have caught it otherwise), so we can
		 * ignore these cases.
		 */

		if ((g = lsp->symbol) == NULL)
			continue;

		if (!(lsp->nzlist.nz_type & N_EXT) &&
		    !SET_ELEMENT_P(lsp->nzlist.nz_type)) {
			warnx("internal error: `%s' N_EXT not set", g->name);
			continue;
		}

		errmsg = 0;

		if (!g->defined && !g->so_defined && list_unresolved_refs) {
			/* Mark as being noted by relocation warning pass. */
			SET_BIT(nlist_bitvector, lsp - start_of_syms);

			if (g->undef_refs == 0)
				reported_undefineds++;
			if (g->undef_refs >= MAX_UREFS_PRINTED)
				/* Listed too many */
				continue;
			/* Undefined symbol which we should mention */

			if (++(g->undef_refs) == MAX_UREFS_PRINTED) {
				errfmt = "More undefined symbol %s refs follow";
				invalidate_line_number = 1;
			} else {
				errfmt =
			"Undefined symbol `%s' referenced from %s segment";
				invalidate_line_number = 0;
			}
		} else {	/* Defined */
			/* Potential symbol warning here */
			if (!g->warning)
				continue;

			if (BIT_SET_P(nlist_bitvector, lsp - start_of_syms))
				continue;

			/* Mark as being noted by relocation warning pass.  */
			SET_BIT(nlist_bitvector, lsp - start_of_syms);

			errfmt = 0;
			errmsg = g->warning;
			invalidate_line_number = 0;
		}


		/* If errfmt == 0, errmsg has already been defined.  */
		if (errfmt != 0) {
			char	       *nm;
			size_t		len;

			nm = g->name;
			len = strlen(errfmt) + strlen(nm) + 1;
			errmsg = (char *)
				xmalloc(len);
			snprintf(errmsg, len, errfmt, nm,
			    data_segment?"data":"text");
			if (nm != g->name)
				free(nm);
		}
		address_to_line(RELOC_ADDRESS(rp) + start_of_segment,
				state_pointer);

		if (current->line >= 0)
			fprintf(outfile, "%s:%d: %s\n",
				current->filename,
				invalidate_line_number ? 0 : current->line,
				errmsg);
		else
			fprintf(outfile, "%s: %s\n", current->filename, errmsg);

		if (errfmt != 0)
			free(errmsg);
	}

	free(state_pointer);
}

/*
 * Print on OUTFILE a list of all warnings generated by references and/or
 * definitions in the file ENTRY.  List source file and line number if
 * possible, just the .o file if not.
 */

static void
do_file_warnings(struct file_entry *entry, void *arg)
{
	int	nsym;
	int	i;
	char	*errfmt, *file_name;
	int	line_number;
	int	dont_allow_symbol_name;
	u_char	*nlist_bitvector;
	struct line_debug_entry	*text_scan, *data_scan;
	FILE	*outfile = arg;

	nsym = entry->nsymbols;
	nlist_bitvector = (u_char *)alloca((nsym >> 3) + 1);
	bzero(nlist_bitvector, (nsym >> 3) + 1);

	/* Read in the strings */
	entry->strings = (char *)alloca(entry->string_size);
	read_entry_strings(file_open(entry), entry);

	if (!(entry->flags & E_DYNAMIC)) {
		/* Do text warnings based on a scan through the reloc info. */
		do_relocation_warnings(entry, 0, outfile, nlist_bitvector);

		/* Do data warnings based on a scan through the reloc info. */
		do_relocation_warnings(entry, 1, outfile, nlist_bitvector);
	}

	/*
	 * Scan through all of the nlist entries in this file and pick up
	 * anything that the scan through the relocation stuff didn't.
	 */
	text_scan = init_debug_scan(0, entry);
	data_scan = init_debug_scan(1, entry);

	for (i = 0; i < nsym; i++) {
		struct nlist *np;
		symbol *g;

		g = entry->symbols[i].symbol;
		np = &entry->symbols[i].nzlist.nlist;

		if (g == NULL)
			continue;

		if (!(np->n_type & N_EXT) && !SET_ELEMENT_P(np->n_type)) {
			warnx("internal error: `%s' N_EXT not set", g->name);
			continue;
		}

		if (!(g->flags & GS_REFERENCED)) {
#if 0
			/* Check for undefined shobj symbols */
			struct localsymbol	*lsp;
			int			type;

			for (lsp = g->sorefs; lsp; lsp = lsp->next) {
				type = lsp->nzlist.nz_type;
				if ((type & N_EXT) &&
						type != (N_UNDF | N_EXT)) {
					break;
				}
			}
			if (type == (N_UNDF | N_EXT)) {
				fprintf(stderr,
					"Undefined symbol %s referenced from %s\n",
					g->name,
					get_file_name(entry));
			}
#endif
			continue;
		}

		dont_allow_symbol_name = 0;

		if (list_multiple_defs && g->mult_defs) {

			errfmt = "Definition of symbol `%s' (multiply defined)";
			switch (np->n_type) {
			case N_TEXT | N_EXT:
				line_number =
					address_to_line(np->n_value, text_scan);
				file_name = text_scan[0].filename;
				break;

			case N_DATA | N_EXT:
				line_number =
					address_to_line(np->n_value, data_scan);
				file_name = data_scan[0].filename;
				break;

			case N_SETA | N_EXT:
			case N_SETT | N_EXT:
			case N_SETD | N_EXT:
			case N_SETB | N_EXT:
				if (g->mult_defs == 2)
					continue;
				errfmt =
	"First set element definition of symbol `%s' (multiply defined)";
				line_number = -1;
				break;

			case N_SIZE | N_EXT:
				errfmt =
	"Size element definition of symbol `%s' (multiply defined)";
				line_number = -1;
				break;

			case N_INDR | N_EXT:
				errfmt =
	"Alias definition of symbol `%s' (multiply defined)";
				line_number = -1;
				break;

			case N_UNDF | N_EXT:
				/* Don't print out multiple defs at references.*/
				continue;

			default:
				warnx("%s: unexpected multiple definitions "
				      "of symbol `%s', type %#x",
				      get_file_name(entry),
				      g->name, np->n_type);
				line_number = -1;
				break;
			}

		} else if (BIT_SET_P(nlist_bitvector, i)) {
			continue;
		} else if (list_unresolved_refs &&
			   !g->defined && !g->so_defined) {

			if (g->undef_refs == 0)
				reported_undefineds++;
			if (g->undef_refs >= MAX_UREFS_PRINTED)
				continue;
			if (++(g->undef_refs) == MAX_UREFS_PRINTED)
				errfmt = "More undefined `%s' refs follow";
			else
				errfmt = "Undefined symbol `%s' referenced";
			line_number = -1;
		} else if (g->def_lsp && g->def_lsp->entry != entry &&
			   !(entry->flags & E_DYNAMIC) &&
			   g->def_lsp->entry->flags & E_SECONDCLASS) {
			if (g->undef_refs == 0)
			    reported_undefineds++;
			if (g->undef_refs >= MAX_UREFS_PRINTED)
				continue;
			if (++(g->undef_refs) == MAX_UREFS_PRINTED) {
				errfmt = "More undefined `%s' refs follow";
				line_number = -1;
			} else {
				fprintf(outfile,
			    "%s: Undefined symbol `%s' referenced (use %s ?)\n",
				    get_file_name(entry),
				    g->name,
				    g->def_lsp->entry->local_sym_name);
				continue;
			}
		} else if (g->warning) {
			/*
			 * There are two cases in which we don't want to do
			 * this. The first is if this is a definition instead
			 * of a reference. The second is if it's the reference
			 * used by the warning stabs itself.
			 */
			if (np->n_type != (N_EXT | N_UNDF) ||
			    (entry->symbols[i].flags & LS_WARNING))
				continue;

			errfmt = g->warning;
			line_number = -1;
			dont_allow_symbol_name = 1;
		} else
			continue;

		if (line_number == -1)
			fprintf(outfile, "%s: ", get_file_name(entry));
		else
			fprintf(outfile, "%s:%d: ", file_name, line_number);

		if (dont_allow_symbol_name)
			fprintf(outfile, "%s", errfmt);
		else
			fprintf(outfile, errfmt, g->name);

		fputc('\n', outfile);
	}
	free(text_scan);
	free(data_scan);
	entry->strings = 0;	/* Since it will disappear anyway. */
}

int
do_warnings(FILE *outfile)
{

	list_unresolved_refs = !relocatable_output &&
		( (undefined_global_sym_count - undefined_weak_sym_count) > 0
		   || undefined_shobj_sym_count
		);
	list_multiple_defs = multiple_def_count != 0;

	if (!(list_unresolved_refs ||
	      list_warning_symbols ||
	      list_multiple_defs))
		/* No need to run this routine */
		return 1;

	if (entry_symbol && !entry_symbol->defined)
		fprintf(outfile, "Undefined entry symbol `%s'\n",
			entry_symbol->name);

	each_file(do_file_warnings, (void *)outfile);

	if (list_unresolved_refs &&
	    reported_undefineds !=
	    (undefined_global_sym_count - undefined_weak_sym_count))
		warnx("internal consistency check failure: "
		      "# undefined symbols %d, accounted for %d",
		      (undefined_global_sym_count - undefined_weak_sym_count),
		      reported_undefineds);

	if (list_unresolved_refs || list_multiple_defs)
		return 0;

	return 1;
}