summaryrefslogtreecommitdiff
path: root/outelf.c
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2002-04-30 20:52:49 +0000
committerH. Peter Anvin <hpa@zytor.com>2002-04-30 20:52:49 +0000
commit76690a12ad212d1f77cd1f71d7ac5a9de6eaefb6 (patch)
tree6d1fe5322184b3e53a9378ce65f6a95f05b59f28 /outelf.c
parent6768eb71d8debde65562619c938b997aea1bd9f9 (diff)
downloadnasm-76690a12ad212d1f77cd1f71d7ac5a9de6eaefb6.tar.gz
nasm-76690a12ad212d1f77cd1f71d7ac5a9de6eaefb6.tar.bz2
nasm-76690a12ad212d1f77cd1f71d7ac5a9de6eaefb6.zip
NASM 0.96
Diffstat (limited to 'outelf.c')
-rw-r--r--outelf.c363
1 files changed, 327 insertions, 36 deletions
diff --git a/outelf.c b/outelf.c
index 3c7f276..6f6c1be 100644
--- a/outelf.c
+++ b/outelf.c
@@ -18,18 +18,33 @@
#ifdef OF_ELF
+/*
+ * Relocation types.
+ */
+#define R_386_32 1 /* ordinary absolute relocation */
+#define R_386_PC32 2 /* PC-relative relocation */
+#define R_386_GOT32 3 /* an offset into GOT */
+#define R_386_PLT32 4 /* a PC-relative offset into PLT */
+#define R_386_GOTOFF 9 /* an offset from GOT base */
+#define R_386_GOTPC 10 /* a PC-relative offset _to_ GOT */
+
struct Reloc {
struct Reloc *next;
long address; /* relative to _start_ of section */
long symbol; /* ELF symbol info thingy */
- int relative; /* TRUE or FALSE */
+ int type; /* type of relocation */
};
struct Symbol {
long strpos; /* string table position of name */
long section; /* section ID of the symbol */
- int type; /* TRUE or FALSE */
- long value; /* address, or COMMON variable size */
+ int type; /* symbol type */
+ long value; /* address, or COMMON variable align */
+ long size; /* size of symbol */
+ long globnum; /* symbol table offset if global */
+ struct Symbol *next; /* list of globals in each section */
+ struct Symbol *nextfwd; /* list of unresolved-size symbols */
+ char *name; /* used temporarily if in above list */
};
#define SHT_PROGBITS 1
@@ -50,6 +65,7 @@ struct Section {
struct SAA *rel;
long rellen;
struct Reloc *head, **tail;
+ struct Symbol *gsyms; /* global symbols in section */
};
#define SECT_DELTA 32
@@ -72,15 +88,22 @@ static unsigned long strslen;
static FILE *elffp;
static efunc error;
+static evalfunc evaluate;
+
+static struct Symbol *fwds;
static char elf_module[FILENAME_MAX];
+extern struct ofmt of_elf;
+
#define SHN_ABS 0xFFF1
#define SHN_COMMON 0xFFF2
#define SHN_UNDEF 0
#define SYM_SECTION 0x04
#define SYM_GLOBAL 0x10
+#define SYM_DATA 0x01
+#define SYM_FUNCTION 0x02
#define GLOBAL_TEMP_BASE 6 /* bigger than any constant sym id */
@@ -107,9 +130,19 @@ static struct SAA *elf_build_symtab (long *, long *);
static struct SAA *elf_build_reltab (long *, struct Reloc *);
static void add_sectname (char *, char *);
-static void elf_init(FILE *fp, efunc errfunc, ldfunc ldef) {
+/*
+ * Special section numbers which are used to define ELF special
+ * symbols, which can be used with WRT to provide PIC relocation
+ * types.
+ */
+static long elf_gotpc_sect, elf_gotoff_sect;
+static long elf_got_sect, elf_plt_sect;
+static long elf_sym_sect;
+
+static void elf_init(FILE *fp, efunc errfunc, ldfunc ldef, evalfunc eval) {
elffp = fp;
error = errfunc;
+ evaluate = eval;
(void) ldef; /* placate optimisers */
sects = NULL;
nsects = sectlen = 0;
@@ -123,6 +156,20 @@ static void elf_init(FILE *fp, efunc errfunc, ldfunc ldef) {
shstrtab = NULL;
shstrtablen = shstrtabsize = 0;;
add_sectname ("", "");
+
+ fwds = NULL;
+
+ elf_gotpc_sect = seg_alloc();
+ ldef("..gotpc", elf_gotpc_sect+1, 0L, NULL, FALSE, FALSE, &of_elf, error);
+ elf_gotoff_sect = seg_alloc();
+ ldef("..gotoff", elf_gotoff_sect+1, 0L, NULL, FALSE, FALSE,&of_elf,error);
+ elf_got_sect = seg_alloc();
+ ldef("..got", elf_got_sect+1, 0L, NULL, FALSE, FALSE, &of_elf, error);
+ elf_plt_sect = seg_alloc();
+ ldef("..plt", elf_plt_sect+1, 0L, NULL, FALSE, FALSE, &of_elf, error);
+ elf_sym_sect = seg_alloc();
+ ldef("..sym", elf_sym_sect+1, 0L, NULL, FALSE, FALSE, &of_elf, error);
+
def_seg = seg_alloc();
}
@@ -179,6 +226,7 @@ static int elf_make_section (char *name, int type, int flags, int align) {
s->type = type;
s->flags = flags;
s->align = align;
+ s->gsyms = NULL;
if (nsects >= sectlen)
sects = nasm_realloc (sects, (sectlen += SECT_DELTA)*sizeof(*sects));
@@ -286,15 +334,60 @@ static long elf_section_names (char *name, int pass, int *bits) {
}
static void elf_deflabel (char *name, long segment, long offset,
- int is_global) {
+ int is_global, char *special) {
int pos = strslen;
struct Symbol *sym;
+ int special_used = FALSE;
if (name[0] == '.' && name[1] == '.' && name[2] != '@') {
- error (ERR_NONFATAL, "unrecognised special symbol `%s'", name);
+ /*
+ * This is a NASM special symbol. We never allow it into
+ * the ELF symbol table, even if it's a valid one. If it
+ * _isn't_ a valid one, we should barf immediately.
+ */
+ if (strcmp(name, "..gotpc") && strcmp(name, "..gotoff") &&
+ strcmp(name, "..got") && strcmp(name, "..plt") &&
+ strcmp(name, "..sym"))
+ error (ERR_NONFATAL, "unrecognised special symbol `%s'", name);
return;
}
+ if (is_global == 3) {
+ struct Symbol **s;
+ /*
+ * Fix up a forward-reference symbol size from the first
+ * pass.
+ */
+ for (s = &fwds; *s; s = &(*s)->nextfwd)
+ if (!strcmp((*s)->name, name)) {
+ struct tokenval tokval;
+ expr *e;
+ char *p = special;
+
+ while (*p && !isspace(*p)) p++;
+ while (*p && isspace(*p)) p++;
+ stdscan_reset();
+ stdscan_bufptr = p;
+ tokval.t_type = TOKEN_INVALID;
+ e = evaluate(stdscan, NULL, &tokval, NULL, 1, error, NULL);
+ if (e) {
+ if (!is_simple(e))
+ error (ERR_NONFATAL, "cannot use relocatable"
+ " expression as symbol size");
+ else
+ (*s)->size = reloc_value(e);
+ }
+
+ /*
+ * Remove it from the list of unresolved sizes.
+ */
+ nasm_free ((*s)->name);
+ *s = (*s)->nextfwd;
+ return;
+ }
+ return; /* it wasn't an important one */
+ }
+
saa_wbytes (strs, name, (long)(1+strlen(name)));
strslen += 1+strlen(name);
@@ -302,6 +395,7 @@ static void elf_deflabel (char *name, long segment, long offset,
sym->strpos = pos;
sym->type = is_global ? SYM_GLOBAL : 0;
+ sym->size = 0;
if (segment == NO_SEG)
sym->section = SHN_ABS;
else {
@@ -322,21 +416,96 @@ static void elf_deflabel (char *name, long segment, long offset,
}
if (is_global == 2) {
- sym->value = offset;
+ sym->size = offset;
+ sym->value = 0;
sym->section = SHN_COMMON;
+ /*
+ * We have a common variable. Check the special text to see
+ * if it's a valid number and power of two; if so, store it
+ * as the alignment for the common variable.
+ */
+ if (special) {
+ int err;
+ sym->value = readnum (special, &err);
+ if (err)
+ error(ERR_NONFATAL, "alignment constraint `%s' is not a"
+ " valid number", special);
+ else if ( (sym->value | (sym->value-1)) != 2*sym->value - 1)
+ error(ERR_NONFATAL, "alignment constraint `%s' is not a"
+ " power of two", special);
+ }
+ special_used = TRUE;
} else
sym->value = (sym->section == SHN_UNDEF ? 0 : offset);
if (sym->type == SYM_GLOBAL) {
if (sym->section == SHN_UNDEF || sym->section == SHN_COMMON)
bsym = raa_write (bsym, segment, nglobs);
+ else {
+ /*
+ * This is a global symbol; so we must add it to the linked
+ * list of global symbols in its section. We'll push it on
+ * the beginning of the list, because it doesn't matter
+ * much which end we put it on and it's easier like this.
+ *
+ * In addition, we check the special text for symbol
+ * type and size information.
+ */
+ sym->next = sects[sym->section-1]->gsyms;
+ sects[sym->section-1]->gsyms = sym;
+
+ if (special) {
+ int n = strcspn(special, " ");
+
+ if (!nasm_strnicmp(special, "function", n))
+ sym->type |= SYM_FUNCTION;
+ else if (!nasm_strnicmp(special, "data", n) ||
+ !nasm_strnicmp(special, "object", n))
+ sym->type |= SYM_DATA;
+ else
+ error(ERR_NONFATAL, "unrecognised symbol type `%.*s'",
+ n, special);
+ if (special[n]) {
+ struct tokenval tokval;
+ expr *e;
+ int fwd = FALSE;
+
+ while (special[n] && isspace(special[n]))
+ n++;
+ /*
+ * We have a size expression; attempt to
+ * evaluate it.
+ */
+ stdscan_reset();
+ stdscan_bufptr = special+n;
+ tokval.t_type = TOKEN_INVALID;
+ e = evaluate(stdscan, NULL, &tokval, &fwd, 0, error, NULL);
+ if (fwd) {
+ sym->nextfwd = fwds;
+ fwds = sym;
+ sym->name = nasm_strdup(name);
+ } else if (e) {
+ if (!is_simple(e))
+ error (ERR_NONFATAL, "cannot use relocatable"
+ " expression as symbol size");
+ else
+ sym->size = reloc_value(e);
+ }
+ }
+ special_used = TRUE;
+ }
+ }
+ sym->globnum = nglobs;
nglobs++;
} else
nlocals++;
+
+ if (special && !special_used)
+ error(ERR_NONFATAL, "no special symbol features supported here");
}
static void elf_add_reloc (struct Section *sect, long segment,
- int relative) {
+ int type) {
struct Reloc *r;
r = *sect->tail = nasm_malloc(sizeof(struct Reloc));
@@ -355,23 +524,106 @@ static void elf_add_reloc (struct Section *sect, long segment,
if (!r->symbol)
r->symbol = GLOBAL_TEMP_BASE + raa_read(bsym, segment);
}
- r->relative = relative;
+ r->type = type;
+
+ sect->nrelocs++;
+}
+
+/*
+ * This routine deals with ..got and ..sym relocations: the more
+ * complicated kinds. In shared-library writing, some relocations
+ * with respect to global symbols must refer to the precise symbol
+ * rather than referring to an offset from the base of the section
+ * _containing_ the symbol. Such relocations call to this routine,
+ * which searches the symbol list for the symbol in question.
+ *
+ * R_386_GOT32 references require the _exact_ symbol address to be
+ * used; R_386_32 references can be at an offset from the symbol.
+ * The boolean argument `exact' tells us this.
+ *
+ * Return value is the adjusted value of `addr', having become an
+ * offset from the symbol rather than the section. Should always be
+ * zero when returning from an exact call.
+ *
+ * Limitation: if you define two symbols at the same place,
+ * confusion will occur.
+ *
+ * Inefficiency: we search, currently, using a linked list which
+ * isn't even necessarily sorted.
+ */
+static long elf_add_gsym_reloc (struct Section *sect,
+ long segment, long offset,
+ int type, int exact) {
+ struct Reloc *r;
+ struct Section *s;
+ struct Symbol *sym, *sm;
+ int i;
+
+ /*
+ * First look up the segment/offset pair and find a global
+ * symbol corresponding to it. If it's not one of our segments,
+ * then it must be an external symbol, in which case we're fine
+ * doing a normal elf_add_reloc after first sanity-checking
+ * that the offset from the symbol is zero.
+ */
+ s = NULL;
+ for (i=0; i<nsects; i++)
+ if (segment == sects[i]->index) {
+ s = sects[i];
+ break;
+ }
+ if (!s) {
+ if (exact && offset != 0)
+ error (ERR_NONFATAL, "unable to find a suitable global symbol"
+ " for this reference");
+ else
+ elf_add_reloc (sect, segment, type);
+ return offset;
+ }
+
+ if (exact) {
+ /*
+ * Find a symbol pointing _exactly_ at this one.
+ */
+ for (sym = s->gsyms; sym; sym = sym->next)
+ if (sym->value == offset)
+ break;
+ } else {
+ /*
+ * Find the nearest symbol below this one.
+ */
+ sym = NULL;
+ for (sm = s->gsyms; sm; sm = sm->next)
+ if (sm->value <= offset && (!sym || sm->value > sym->value))
+ sym = sm;
+ }
+ if (!sym && exact) {
+ error (ERR_NONFATAL, "unable to find a suitable global symbol"
+ " for this reference");
+ return 0;
+ }
+
+ r = *sect->tail = nasm_malloc(sizeof(struct Reloc));
+ sect->tail = &r->next;
+ r->next = NULL;
+
+ r->address = sect->len;
+ r->symbol = GLOBAL_TEMP_BASE + sym->globnum;
+ r->type = type;
sect->nrelocs++;
+
+ return offset - sym->value;
}
static void elf_out (long segto, void *data, unsigned long type,
long segment, long wrt) {
struct Section *s;
long realbytes = type & OUT_SIZMASK;
+ long addr;
unsigned char mydata[4], *p;
int i;
- if (wrt != NO_SEG) {
- wrt = NO_SEG; /* continue to do _something_ */
- error (ERR_NONFATAL, "WRT not supported by ELF output format");
- }
-
type &= OUT_TYPMASK;
/*
@@ -421,20 +673,45 @@ static void elf_out (long segto, void *data, unsigned long type,
error(ERR_PANIC, "OUT_RAWDATA with other than NO_SEG");
elf_sect_write (s, data, realbytes);
} else if (type == OUT_ADDRESS) {
- if (wrt != NO_SEG)
- error(ERR_NONFATAL, "ELF format does not support WRT types");
+ addr = *(long *)data;
if (segment != NO_SEG) {
if (segment % 2) {
error(ERR_NONFATAL, "ELF format does not support"
" segment base references");
- } else
- elf_add_reloc (s, segment, FALSE);
+ } else {
+ if (wrt == NO_SEG) {
+ elf_add_reloc (s, segment, R_386_32);
+ } else if (wrt == elf_gotpc_sect+1) {
+ /*
+ * The user will supply GOT relative to $$. ELF
+ * will let us have GOT relative to $. So we
+ * need to fix up the data item by $-$$.
+ */
+ addr += s->len;
+ elf_add_reloc (s, segment, R_386_GOTPC);
+ } else if (wrt == elf_gotoff_sect+1) {
+ elf_add_reloc (s, segment, R_386_GOTOFF);
+ } else if (wrt == elf_got_sect+1) {
+ addr = elf_add_gsym_reloc (s, segment, addr,
+ R_386_GOT32, TRUE);
+ } else if (wrt == elf_sym_sect+1) {
+ addr = elf_add_gsym_reloc (s, segment, addr,
+ R_386_32, FALSE);
+ } else if (wrt == elf_plt_sect+1) {
+ error(ERR_NONFATAL, "ELF format cannot produce non-PC-"
+ "relative PLT references");
+ } else {
+ error (ERR_NONFATAL, "ELF format does not support this"
+ " use of WRT");
+ wrt = NO_SEG; /* we can at least _try_ to continue */
+ }
+ }
}
p = mydata;
- if (realbytes == 2 && segment != NO_SEG)
- error (ERR_NONFATAL, "ELF format does not support 16-bit"
+ if (realbytes != 4 && segment != NO_SEG)
+ error (ERR_NONFATAL, "ELF format does not support non-32-bit"
" relocations");
- WRITELONG (p, *(long *)data);
+ WRITELONG (p, addr);
elf_sect_write (s, mydata, realbytes);
} else if (type == OUT_REL2ADR) {
error (ERR_NONFATAL, "ELF format does not support 16-bit"
@@ -445,8 +722,22 @@ static void elf_out (long segto, void *data, unsigned long type,
if (segment != NO_SEG && segment % 2) {
error(ERR_NONFATAL, "ELF format does not support"
" segment base references");
- } else
- elf_add_reloc (s, segment, TRUE);
+ } else {
+ if (wrt == NO_SEG) {
+ elf_add_reloc (s, segment, R_386_PC32);
+ } else if (wrt == elf_plt_sect+1) {
+ elf_add_reloc (s, segment, R_386_PLT32);
+ } else if (wrt == elf_gotpc_sect+1 ||
+ wrt == elf_gotoff_sect+1 ||
+ wrt == elf_got_sect+1) {
+ error(ERR_NONFATAL, "ELF format cannot produce PC-"
+ "relative GOT references");
+ } else {
+ error (ERR_NONFATAL, "ELF format does not support this"
+ " use of WRT");
+ wrt = NO_SEG; /* we can at least _try_ to continue */
+ }
+ }
p = mydata;
WRITELONG (p, *(long*)data - realbytes);
elf_sect_write (s, mydata, 4L);
@@ -614,16 +905,13 @@ static struct SAA *elf_build_symtab (long *len, long *local) {
*/
saa_rewind (syms);
while ( (sym = saa_rstruct (syms)) ) {
- if (sym->type == SYM_GLOBAL)
+ if (sym->type & SYM_GLOBAL)
continue;
p = entry;
WRITELONG (p, sym->strpos);
WRITELONG (p, sym->value);
- if (sym->section == SHN_COMMON)
- WRITELONG (p, sym->value);
- else
- WRITELONG (p, 0);
- WRITESHORT (p, 0); /* local non-typed thing */
+ WRITELONG (p, sym->size);
+ WRITESHORT (p, sym->type); /* local non-typed thing */
WRITESHORT (p, sym->section);
saa_wbytes (s, entry, 16L);
*len += 16;
@@ -635,16 +923,13 @@ static struct SAA *elf_build_symtab (long *len, long *local) {
*/
saa_rewind (syms);
while ( (sym = saa_rstruct (syms)) ) {
- if (sym->type != SYM_GLOBAL)
+ if (!(sym->type & SYM_GLOBAL))
continue;
p = entry;
WRITELONG (p, sym->strpos);
WRITELONG (p, sym->value);
- if (sym->section == SHN_COMMON)
- WRITELONG (p, sym->value);
- else
- WRITELONG (p, 0);
- WRITESHORT (p, SYM_GLOBAL); /* global non-typed thing */
+ WRITELONG (p, sym->size);
+ WRITESHORT (p, sym->type); /* global non-typed thing */
WRITESHORT (p, sym->section);
saa_wbytes (s, entry, 16L);
*len += 16;
@@ -671,7 +956,7 @@ static struct SAA *elf_build_reltab (long *len, struct Reloc *r) {
p = entry;
WRITELONG (p, r->address);
- WRITELONG (p, (sym << 8) + (r->relative ? 2 : 1));
+ WRITELONG (p, (sym << 8) + r->type);
saa_wbytes (s, entry, 8L);
*len += 8;
@@ -737,9 +1022,15 @@ static void elf_filename (char *inname, char *outname, efunc error) {
standard_extension (inname, outname, ".o", error);
}
+static char *elf_stdmac[] = {
+ "%define __SECT__ [section .text]",
+ NULL
+};
+
struct ofmt of_elf = {
"ELF32 (i386) object files (e.g. Linux)",
"elf",
+ elf_stdmac,
elf_init,
elf_out,
elf_deflabel,