freebsd-skq/gnu/usr.bin/ld/warnings.c
nate b9aa930e44 Weak symbol support from NetBSD. This should bring us in sync with the
NetBSD ld code except for local changes for dlopen() and friends and
the hashing on the minor value of the shlibs.  We should be binary
compatible now with all their libraries.

Obtained from: NetBSD
1995-03-04 17:49:20 +00:00

759 lines
19 KiB
C

/*
* $Id: warnings.c,v 1.9 1994/12/23 22:30:57 nate Exp $
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/errno.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 (entry, outfile)
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 (entry, outfile)
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 (entry)
struct file_entry *entry;
{
char *result, *supfile;
if (entry == NULL) {
return (char *)strdup("NULL");
}
if (entry->superfile) {
supfile = get_file_name(entry->superfile);
result = (char *)
xmalloc(strlen(supfile) + strlen(entry->filename) + 3);
(void)sprintf(result, "%s(%s)", supfile, entry->filename);
free(supfile);
} else {
result = (char *)xmalloc(strlen(entry->filename) + 1);
strcpy(result, entry->filename);
}
return result;
}
/* Print a complete or partial map of the output file. */
static void describe_file_sections __P((struct file_entry *, FILE *));
static void list_file_locals __P((struct file_entry *, FILE *));
void
print_symbols(outfile)
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 %#x, 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(entry, outfile)
struct file_entry *entry;
FILE *outfile;
{
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 (entry, outfile)
struct file_entry *entry;
FILE *outfile;
{
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++) {
register 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%x\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 __P((int, struct file_entry *));
static int next_debug_entry __P((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(rel1, rel2)
struct relocation_info *rel1, *rel2;
{
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(use_data_symbols, state_pointer)
register int use_data_symbols;
/* Next must be passed by reference! */
struct line_debug_entry state_pointer[3];
{
register 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(use_data_symbols, entry)
int use_data_symbols;
struct file_entry *entry;
{
register 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(address, state_pointer)
unsigned long address;
/* Next must be passed by reference! */
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(entry, data_segment, outfile, nlist_bitvector)
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++) {
register struct localsymbol *lsp;
register 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;
nm = g->name;
errmsg = (char *)
xmalloc(strlen(errfmt) + strlen(nm) + 1);
sprintf(errmsg, 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.
*/
void
do_file_warnings (entry, outfile)
struct file_entry *entry;
FILE *outfile;
{
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;
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;
register 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);
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) {
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(outfile)
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("Spurious undefined symbols: "
"# undefined symbols %d, reported %d",
(undefined_global_sym_count - undefined_weak_sym_count),
reported_undefineds);
if (list_unresolved_refs || list_multiple_defs)
return 0;
return 1;
}