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.
diff --git a/src/ldso/dynlink.c b/src/ldso/dynlink.c
index 1cb3fb4..94531b3 100644
--- a/src/ldso/dynlink.c
+++ b/src/ldso/dynlink.c
@@ -89,6 +89,23 @@
 	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"
 
 int __init_tp(void *);
@@ -227,6 +244,8 @@
 	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)
 {
 	unsigned char *base = dso->base;
@@ -235,18 +254,34 @@
 	Sym *sym;
 	const char *name;
 	void *ctx;
-	int type;
+	int astype, type;
 	int sym_index;
 	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)) {
-		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]);
+		reloc_addr = (void *)(base + rel[0]);
 		if (sym_index) {
 			sym = syms + sym_index;
 			name = strings + sym->st_name;
-			ctx = IS_COPY(type) ? head->next : head;
-			def = find_sym(ctx, name, IS_PLT(type));
+			ctx = type==REL_COPY ? head->next : head;
+			def = find_sym(ctx, name, type==REL_PLT);
 			if (!def.sym && (sym->st_shndx != SHN_UNDEF
 			    || sym->st_info>>4 != STB_WEAK)) {
 				snprintf(errbuf, sizeof errbuf,
@@ -260,11 +295,57 @@
 		} else {
 			sym = 0;
 			def.sym = 0;
-			def.dso = 0;
+			def.dso = dso;
 		}
-		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);
+
+		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
+		}
 	}
 }