refactor to remove arch-specific relocation code from dynamic linker

this was one of the main instances of ugly code duplication: all archs
use basically the same types of relocations, but roughly equivalent
logic was duplicated for each arch to account for the different naming
and numbering of relocation types and variation in whether REL or RELA
records are used.

as an added bonus, both REL and RELA are now supported on all archs,
regardless of which is used by the standard toolchain.
This commit is contained in:
Rich Felker 2014-06-18 02:44:02 -04:00
parent 94cf991bf4
commit adf94c1966
9 changed files with 189 additions and 210 deletions

View File

@ -16,42 +16,29 @@
#define LDSO_ARCH "arm" ENDIAN_SUFFIX FP_SUFFIX #define LDSO_ARCH "arm" ENDIAN_SUFFIX FP_SUFFIX
#define IS_COPY(x) ((x)==R_ARM_COPY) #define NO_LEGACY_INITFINI
#define IS_PLT(x) ((x)==R_ARM_JUMP_SLOT)
static inline int do_single_reloc( #define TPOFF_K 8
struct dso *self, unsigned char *base_addr,
size_t *reloc_addr, int type, size_t addend, static int remap_rel(int type)
Sym *sym, size_t sym_size,
struct symdef def, size_t sym_val)
{ {
switch(type) { switch(type) {
case R_ARM_ABS32: case R_ARM_ABS32:
*reloc_addr += sym_val; return REL_SYMBOLIC;
break;
case R_ARM_GLOB_DAT: case R_ARM_GLOB_DAT:
return REL_GOT;
case R_ARM_JUMP_SLOT: case R_ARM_JUMP_SLOT:
*reloc_addr = sym_val; return REL_PLT;
break;
case R_ARM_RELATIVE: case R_ARM_RELATIVE:
*reloc_addr += (size_t)base_addr; return REL_RELATIVE;
break;
case R_ARM_COPY: case R_ARM_COPY:
memcpy(reloc_addr, (void *)sym_val, sym_size); return REL_COPY;
break;
case R_ARM_TLS_DTPMOD32: case R_ARM_TLS_DTPMOD32:
*reloc_addr = def.dso ? def.dso->tls_id : self->tls_id; return REL_DTPMOD;
break;
case R_ARM_TLS_DTPOFF32: case R_ARM_TLS_DTPOFF32:
*reloc_addr += def.sym->st_value; return REL_DTPOFF;
break;
case R_ARM_TLS_TPOFF32: case R_ARM_TLS_TPOFF32:
*reloc_addr += def.sym return REL_TPOFF;
? def.sym->st_value + def.dso->tls_offset + 8
: self->tls_offset + 8;
break;
} }
return 0; return 0;
} }
#define NO_LEGACY_INITFINI

View File

@ -3,48 +3,29 @@
#define LDSO_ARCH "i386" #define LDSO_ARCH "i386"
#define IS_COPY(x) ((x)==R_386_COPY) static int remap_rel(int type)
#define IS_PLT(x) ((x)==R_386_JMP_SLOT)
static inline int do_single_reloc(
struct dso *self, unsigned char *base_addr,
size_t *reloc_addr, int type, size_t addend,
Sym *sym, size_t sym_size,
struct symdef def, size_t sym_val)
{ {
switch(type) { switch(type) {
case R_386_32: case R_386_32:
*reloc_addr += sym_val; return REL_SYMBOLIC;
break;
case R_386_PC32: case R_386_PC32:
*reloc_addr += sym_val - (size_t)reloc_addr; return REL_OFFSET;
break;
case R_386_GLOB_DAT: case R_386_GLOB_DAT:
return REL_GOT;
case R_386_JMP_SLOT: case R_386_JMP_SLOT:
*reloc_addr = sym_val; return REL_PLT;
break;
case R_386_RELATIVE: case R_386_RELATIVE:
*reloc_addr += (size_t)base_addr; return REL_RELATIVE;
break;
case R_386_COPY: case R_386_COPY:
memcpy(reloc_addr, (void *)sym_val, sym_size); return REL_COPY;
break;
case R_386_TLS_DTPMOD32: case R_386_TLS_DTPMOD32:
*reloc_addr = def.dso ? def.dso->tls_id : self->tls_id; return REL_DTPMOD;
break;
case R_386_TLS_DTPOFF32: case R_386_TLS_DTPOFF32:
*reloc_addr = def.sym->st_value; return REL_DTPOFF;
break;
case R_386_TLS_TPOFF: case R_386_TLS_TPOFF:
*reloc_addr += def.sym return REL_TPOFF;
? def.sym->st_value - def.dso->tls_offset
: 0 - self->tls_offset;
break;
case R_386_TLS_TPOFF32: case R_386_TLS_TPOFF32:
*reloc_addr += def.sym return REL_TPOFF_NEG;
? def.dso->tls_offset - def.sym->st_value
: self->tls_offset;
break;
} }
return 0; return 0;
} }

View File

@ -10,33 +10,25 @@
#define LDSO_ARCH "microblaze" ENDIAN_SUFFIX #define LDSO_ARCH "microblaze" ENDIAN_SUFFIX
#define IS_COPY(x) ((x)==R_MICROBLAZE_COPY) #define TPOFF_K 0
#define IS_PLT(x) ((x)==R_MICROBLAZE_JUMP_SLOT)
static inline int do_single_reloc( static int remap_rel(int type)
struct dso *self, unsigned char *base_addr,
size_t *reloc_addr, int type, size_t addend,
Sym *sym, size_t sym_size,
struct symdef def, size_t sym_val)
{ {
switch(type) { switch(type) {
case R_MICROBLAZE_32: case R_MICROBLAZE_32:
return REL_SYMBOLIC;
case R_MICROBLAZE_GLOB_DAT: case R_MICROBLAZE_GLOB_DAT:
return REL_GOT;
case R_MICROBLAZE_JUMP_SLOT: case R_MICROBLAZE_JUMP_SLOT:
*reloc_addr = sym_val + addend; return REL_PLT;
break;
case R_MICROBLAZE_REL: case R_MICROBLAZE_REL:
*reloc_addr = (size_t)base_addr + addend; return REL_RELATIVE;
break;
case R_MICROBLAZE_COPY: case R_MICROBLAZE_COPY:
memcpy(reloc_addr, (void *)sym_val, sym_size); return REL_COPY;
break;
case R_MICROBLAZE_TLSDTPMOD32: case R_MICROBLAZE_TLSDTPMOD32:
*reloc_addr = def.dso ? def.dso->tls_id : self->tls_id; return REL_DTPMOD;
break;
case R_MICROBLAZE_TLSDTPREL32: case R_MICROBLAZE_TLSDTPREL32:
*reloc_addr = def.sym->st_value + addend; return REL_DTPOFF;
break;
} }
return 0; return 0;
} }

View File

@ -16,37 +16,23 @@
#define LDSO_ARCH "mips" ENDIAN_SUFFIX FP_SUFFIX #define LDSO_ARCH "mips" ENDIAN_SUFFIX FP_SUFFIX
#define IS_COPY(x) ((x)==R_MIPS_COPY) #define TPOFF_K (-0x7000)
#define IS_PLT(x) 1
static inline int do_single_reloc( static int remap_rel(int type)
struct dso *self, unsigned char *base_addr,
size_t *reloc_addr, int type, size_t addend,
Sym *sym, size_t sym_size,
struct symdef def, size_t sym_val)
{ {
switch(type) { switch(type) {
case R_MIPS_JUMP_SLOT:
*reloc_addr = sym_val;
break;
case R_MIPS_REL32: case R_MIPS_REL32:
if (sym_val) *reloc_addr += sym_val; return REL_SYM_OR_REL;
else *reloc_addr += (size_t)base_addr; case R_MIPS_JUMP_SLOT:
break; return REL_PLT;
case R_MIPS_COPY: case R_MIPS_COPY:
memcpy(reloc_addr, (void *)sym_val, sym_size); return REL_COPY;
break;
case R_MIPS_TLS_DTPMOD32: case R_MIPS_TLS_DTPMOD32:
*reloc_addr = def.dso ? def.dso->tls_id : self->tls_id; return REL_DTPMOD;
break;
case R_MIPS_TLS_DTPREL32: case R_MIPS_TLS_DTPREL32:
*reloc_addr += def.sym->st_value; return REL_DTPOFF;
break;
case R_MIPS_TLS_TPREL32: case R_MIPS_TLS_TPREL32:
*reloc_addr += def.sym return REL_TPOFF;
? def.sym->st_value + def.dso->tls_offset - 0x7000
: self->tls_offset - 0x7000;
break;
} }
return 0; return 0;
} }

View File

@ -3,39 +3,27 @@
#define LDSO_ARCH "powerpc" #define LDSO_ARCH "powerpc"
#define IS_COPY(x) ((x)==R_PPC_COPY) #define TPOFF_K (-0x7000)
#define IS_PLT(x) ((x)==R_PPC_JMP_SLOT)
// see linux' arch/powerpc/include/asm/elf.h static int remap_rel(int type)
static inline int do_single_reloc(
struct dso *self, unsigned char *base_addr,
size_t *reloc_addr, int type, size_t addend,
Sym *sym, size_t sym_size,
struct symdef def, size_t sym_val)
{ {
switch(type) { switch(type) {
case R_PPC_GLOB_DAT:
case R_PPC_JMP_SLOT:
case R_PPC_ADDR32: case R_PPC_ADDR32:
*reloc_addr = sym_val + addend; return REL_SYMBOLIC;
break; case R_PPC_GLOB_DAT:
case R_PPC_COPY: return REL_GOT;
memcpy(reloc_addr, (void *)sym_val, sym_size); case R_PPC_JMP_SLOT:
break; return REL_PLT;
case R_PPC_RELATIVE: case R_PPC_RELATIVE:
*reloc_addr = (size_t)base_addr + addend; return REL_RELATIVE;
break; case R_PPC_COPY:
return REL_COPY;
case R_PPC_DTPMOD32: case R_PPC_DTPMOD32:
*reloc_addr = def.dso ? def.dso->tls_id : self->tls_id; return REL_DTPMOD;
break;
case R_PPC_DTPREL32: case R_PPC_DTPREL32:
*reloc_addr = def.sym->st_value + addend; return REL_DTPOFF;
break;
case R_PPC_TPREL32: case R_PPC_TPREL32:
*reloc_addr = (def.sym return REL_TPOFF;
? def.sym->st_value + def.dso->tls_offset
: self->tls_offset) - 0x7000 + addend;
break;
} }
return 0; return 0;
} }

View File

@ -6,41 +6,29 @@
#define LDSO_ARCH "sh" ENDIAN_SUFFIX #define LDSO_ARCH "sh" ENDIAN_SUFFIX
#define IS_COPY(x) ((x) == R_SH_COPY) #define TPOFF_K 8
#define IS_PLT(x) ((x) == R_SH_JMP_SLOT)
static inline int do_single_reloc( static int remap_rel(int type)
struct dso *self, unsigned char *base_addr,
size_t *reloc_addr, int type, size_t addend,
Sym *sym, size_t sym_size,
struct symdef def, size_t sym_val)
{ {
switch(type) { switch(type) {
case R_SH_GLOB_DAT:
case R_SH_JMP_SLOT:
case R_SH_DIR32: case R_SH_DIR32:
*reloc_addr = sym_val + addend; return REL_SYMBOLIC;
break;
case R_SH_RELATIVE:
*reloc_addr = (size_t)base_addr + addend;
break;
case R_SH_REL32: case R_SH_REL32:
*reloc_addr = sym_val + addend - (size_t)reloc_addr; return REL_OFFSET;
break; case R_SH_GLOB_DAT:
return REL_GOT;
case R_SH_JMP_SLOT:
return REL_PLT;
case R_SH_RELATIVE:
return REL_RELATIVE;
case R_SH_COPY: case R_SH_COPY:
memcpy(reloc_addr, (void *)sym_val, sym_size); return REL_COPY;
break;
case R_SH_TLS_DTPMOD32: case R_SH_TLS_DTPMOD32:
*reloc_addr = def.dso ? def.dso->tls_id : self->tls_id; return REL_DTPMOD;
break;
case R_SH_TLS_DTPOFF32: case R_SH_TLS_DTPOFF32:
*reloc_addr = def.sym->st_value + addend; return REL_DTPOFF;
break;
case R_SH_TLS_TPOFF32: case R_SH_TLS_TPOFF32:
*reloc_addr = (def.sym return REL_TPOFF;
? def.sym->st_value + def.dso->tls_offset
: self->tls_offset) + 8 + addend;
break;
} }
return 0; return 0;
} }

View File

@ -4,44 +4,37 @@
#define LDSO_ARCH "x32" #define LDSO_ARCH "x32"
#define IS_COPY(x) ((x)==R_X86_64_COPY) /* FIXME: x32 is very strange in its use of 64-bit relocation types in
#define IS_PLT(x) ((x)==R_X86_64_JUMP_SLOT) * a 32-bit environment. As long as the memory at reloc_addr is
* zero-filled prior to relocations, just treating 64-bit relocations
* as operating on 32-bit slots should be fine, but this should be
* checked. In particular, R_X86_64_64, R_X86_64_DTPOFF64, and
* R_X86_64_TPOFF64 may need checking. */
static inline int do_single_reloc( static int remap_rel(int type)
struct dso *self, unsigned char *base_addr,
size_t *reloc_addr, int type, size_t addend,
Sym *sym, size_t sym_size,
struct symdef def, size_t sym_val)
{ {
switch(type) { switch(type) {
case R_X86_64_GLOB_DAT:
case R_X86_64_JUMP_SLOT:
case R_X86_64_64: case R_X86_64_64:
*reloc_addr = sym_val + addend;
break;
case R_X86_64_32: case R_X86_64_32:
*(uint32_t *)reloc_addr = sym_val + addend; return REL_SYMBOLIC;
break;
case R_X86_64_PC32: case R_X86_64_PC32:
*reloc_addr = sym_val + addend - (size_t)reloc_addr + (size_t)base_addr; return REL_OFFSET;
break; case R_X86_64_GLOB_DAT:
return REL_GOT;
case R_X86_64_JUMP_SLOT:
return REL_PLT;
case R_X86_64_RELATIVE: case R_X86_64_RELATIVE:
*reloc_addr = (size_t)base_addr + addend; return REL_RELATIVE;
break;
case R_X86_64_COPY: case R_X86_64_COPY:
memcpy(reloc_addr, (void *)sym_val, sym_size); return REL_COPY;
break;
case R_X86_64_DTPMOD64: case R_X86_64_DTPMOD64:
*reloc_addr = def.dso ? def.dso->tls_id : self->tls_id; return REL_DTPMOD;
break;
case R_X86_64_DTPOFF64: case R_X86_64_DTPOFF64:
*reloc_addr = def.sym->st_value + addend; case R_X86_64_DTPOFF32:
break; return REL_DTPOFF;
case R_X86_64_TPOFF64: case R_X86_64_TPOFF64:
*reloc_addr = (def.sym case R_X86_64_TPOFF32:
? def.sym->st_value - def.dso->tls_offset return REL_TPOFF;
: 0 - self->tls_offset) + addend;
break;
} }
return 0; return 0;
} }

View File

@ -4,44 +4,27 @@
#define LDSO_ARCH "x86_64" #define LDSO_ARCH "x86_64"
#define IS_COPY(x) ((x)==R_X86_64_COPY) static int remap_rel(int type)
#define IS_PLT(x) ((x)==R_X86_64_JUMP_SLOT)
static inline int do_single_reloc(
struct dso *self, unsigned char *base_addr,
size_t *reloc_addr, int type, size_t addend,
Sym *sym, size_t sym_size,
struct symdef def, size_t sym_val)
{ {
switch(type) { switch(type) {
case R_X86_64_GLOB_DAT:
case R_X86_64_JUMP_SLOT:
case R_X86_64_64: case R_X86_64_64:
*reloc_addr = sym_val + addend; return REL_SYMBOLIC;
break;
case R_X86_64_32:
*(uint32_t *)reloc_addr = sym_val + addend;
break;
case R_X86_64_PC32: case R_X86_64_PC32:
*reloc_addr = sym_val + addend - (size_t)reloc_addr + (size_t)base_addr; return REL_OFFSET32;
break; case R_X86_64_GLOB_DAT:
return REL_GOT;
case R_X86_64_JUMP_SLOT:
return REL_PLT;
case R_X86_64_RELATIVE: case R_X86_64_RELATIVE:
*reloc_addr = (size_t)base_addr + addend; return REL_RELATIVE;
break;
case R_X86_64_COPY: case R_X86_64_COPY:
memcpy(reloc_addr, (void *)sym_val, sym_size); return REL_COPY;
break;
case R_X86_64_DTPMOD64: case R_X86_64_DTPMOD64:
*reloc_addr = def.dso ? def.dso->tls_id : self->tls_id; return REL_DTPMOD;
break;
case R_X86_64_DTPOFF64: case R_X86_64_DTPOFF64:
*reloc_addr = def.sym->st_value + addend; return REL_DTPOFF;
break;
case R_X86_64_TPOFF64: case R_X86_64_TPOFF64:
*reloc_addr = (def.sym return REL_TPOFF;
? def.sym->st_value - def.dso->tls_offset
: 0 - self->tls_offset) + addend;
break;
} }
return 0; return 0;
} }

View File

@ -89,6 +89,23 @@ struct symdef {
struct dso *dso; struct dso *dso;
}; };
enum {
REL_ERR,
REL_SYMBOLIC,
REL_GOT,
REL_PLT,
REL_RELATIVE,
REL_OFFSET,
REL_OFFSET32,
REL_COPY,
REL_SYM_OR_REL,
REL_TLS, /* everything past here is TLS */
REL_DTPMOD,
REL_DTPOFF,
REL_TPOFF,
REL_TPOFF_NEG,
};
#include "reloc.h" #include "reloc.h"
int __init_tp(void *); int __init_tp(void *);
@ -227,6 +244,8 @@ static struct symdef find_sym(struct dso *dso, const char *s, int need_def)
return def; return def;
} }
#define NO_INLINE_ADDEND (1<<REL_COPY | 1<<REL_GOT | 1<<REL_PLT)
static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stride) static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stride)
{ {
unsigned char *base = dso->base; unsigned char *base = dso->base;
@ -235,18 +254,34 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri
Sym *sym; Sym *sym;
const char *name; const char *name;
void *ctx; void *ctx;
int type; int astype, type;
int sym_index; int sym_index;
struct symdef def; struct symdef def;
size_t *reloc_addr;
size_t sym_val;
size_t tls_val;
size_t addend;
for (; rel_size; rel+=stride, rel_size-=stride*sizeof(size_t)) { for (; rel_size; rel+=stride, rel_size-=stride*sizeof(size_t)) {
type = R_TYPE(rel[1]); astype = R_TYPE(rel[1]);
if (!astype) continue;
type = remap_rel(astype);
if (!type) {
snprintf(errbuf, sizeof errbuf,
"Error relocating %s: unsupported relocation type %d",
dso->name, astype);
if (runtime) longjmp(*rtld_fail, 1);
dprintf(2, "%s\n", errbuf);
ldso_fail = 1;
continue;
}
sym_index = R_SYM(rel[1]); sym_index = R_SYM(rel[1]);
reloc_addr = (void *)(base + rel[0]);
if (sym_index) { if (sym_index) {
sym = syms + sym_index; sym = syms + sym_index;
name = strings + sym->st_name; name = strings + sym->st_name;
ctx = IS_COPY(type) ? head->next : head; ctx = type==REL_COPY ? head->next : head;
def = find_sym(ctx, name, IS_PLT(type)); def = find_sym(ctx, name, type==REL_PLT);
if (!def.sym && (sym->st_shndx != SHN_UNDEF if (!def.sym && (sym->st_shndx != SHN_UNDEF
|| sym->st_info>>4 != STB_WEAK)) { || sym->st_info>>4 != STB_WEAK)) {
snprintf(errbuf, sizeof errbuf, snprintf(errbuf, sizeof errbuf,
@ -260,11 +295,57 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri
} else { } else {
sym = 0; sym = 0;
def.sym = 0; def.sym = 0;
def.dso = 0; def.dso = dso;
}
addend = stride>2 ? rel[2]
: (1<<type & NO_INLINE_ADDEND) ? 0
: *reloc_addr;
sym_val = def.sym ? (size_t)def.dso->base+def.sym->st_value : 0;
tls_val = def.sym ? def.sym->st_value : 0;
switch(type) {
case REL_OFFSET:
addend -= (size_t)reloc_addr;
case REL_SYMBOLIC:
case REL_GOT:
case REL_PLT:
*reloc_addr = sym_val + addend;
break;
case REL_RELATIVE:
*reloc_addr = (size_t)base + addend;
break;
case REL_SYM_OR_REL:
if (sym) *reloc_addr = sym_val + addend;
else *reloc_addr = (size_t)base + addend;
break;
case REL_COPY:
memcpy(reloc_addr, (void *)sym_val, sym->st_size);
break;
case REL_OFFSET32:
*(uint32_t *)reloc_addr = sym_val + addend
- (size_t)reloc_addr;
break;
case REL_DTPMOD:
*reloc_addr = def.dso->tls_id;
break;
case REL_DTPOFF:
*reloc_addr = tls_val + addend;
break;
#ifdef TLS_ABOVE_TP
case REL_TPOFF:
*reloc_addr = tls_val + def.dso->tls_offset + TPOFF_K + addend;
break;
#else
case REL_TPOFF:
*reloc_addr = tls_val - def.dso->tls_offset + addend;
break;
case REL_TPOFF_NEG:
*reloc_addr = def.dso->tls_offset - tls_val + addend;
break;
#endif
} }
do_single_reloc(dso, base, (void *)(base + rel[0]), type,
stride>2 ? rel[2] : 0, sym, sym?sym->st_size:0, def,
def.sym?(size_t)(def.dso->base+def.sym->st_value):0);
} }
} }