Implement a dynamic linker.
authorwhitequark <whitequark@whitequark.org>
Thu, 30 Jul 2015 23:38:20 +0000 (02:38 +0300)
committerwhitequark <whitequark@whitequark.org>
Thu, 30 Jul 2015 23:38:38 +0000 (02:38 +0300)
software/include/dyld/dyld.h [new file with mode: 0644]
software/include/dyld/elf.h
software/include/dyld/link.h
software/libdyld/Makefile [new file with mode: 0644]
software/libdyld/dyld.c [new file with mode: 0644]

diff --git a/software/include/dyld/dyld.h b/software/include/dyld/dyld.h
new file mode 100644 (file)
index 0000000..eeb2062
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef __DYLD_H
+#define __DYLD_H
+
+#include <elf.h>
+
+struct dyld_info {
+    Elf32_Addr base;
+    void *init;
+    const char *strtab;
+    Elf32_Sym *symtab;
+    struct {
+        Elf32_Word nbucket;
+        Elf32_Word nchain;
+        Elf32_Word *bucket;
+        Elf32_Word *chain;
+    } hash;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int dyld_load(void *shlib, Elf32_Addr base,
+              Elf32_Addr (*resolve_import)(const char *),
+              struct dyld_info *info, const char **error_out);
+void *dyld_lookup(const char *symbol, struct dyld_info *info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DYLD_H */
index 8ffb06722beda1b5d2beb0028ea7a9197413564e..c84c28a1ed00de8c09a9c44930f75657b7116fc4 100644 (file)
@@ -3297,6 +3297,44 @@ typedef Elf32_Addr Elf32_Conflict;
 
 #define R_TILEGX_NUM           130
 
+/* OR1K relocations */
+#define R_OR1K_NONE 0
+#define R_OR1K_32 1
+#define R_OR1K_16 2
+#define R_OR1K_8 3
+#define R_OR1K_LO_16_IN_INSN 4
+#define R_OR1K_HI_16_IN_INSN 5
+#define R_OR1K_INSN_REL_26 6
+#define R_OR1K_GNU_VTENTRY 7
+#define R_OR1K_GNU_VTINHERIT 8
+#define R_OR1K_32_PCREL 9
+#define R_OR1K_16_PCREL 10
+#define R_OR1K_8_PCREL 11
+#define R_OR1K_GOTPC_HI16 12
+#define R_OR1K_GOTPC_LO16 13
+#define R_OR1K_GOT16 14
+#define R_OR1K_PLT26 15
+#define R_OR1K_GOTOFF_HI16 16
+#define R_OR1K_GOTOFF_LO16 17
+#define R_OR1K_COPY 18
+#define R_OR1K_GLOB_DAT 19
+#define R_OR1K_JMP_SLOT 20
+#define R_OR1K_RELATIVE 21
+#define R_OR1K_TLS_GD_HI16 22
+#define R_OR1K_TLS_GD_LO16 23
+#define R_OR1K_TLS_LDM_HI16 24
+#define R_OR1K_TLS_LDM_LO16 25
+#define R_OR1K_TLS_LDO_HI16 26
+#define R_OR1K_TLS_LDO_LO16 27
+#define R_OR1K_TLS_IE_HI16 28
+#define R_OR1K_TLS_IE_LO16 29
+#define R_OR1K_TLS_LE_HI16 30
+#define R_OR1K_TLS_LE_LO16 31
+#define R_OR1K_TLS_TPOFF 32
+#define R_OR1K_TLS_DTPOFF 33
+#define R_OR1K_TLS_DTPMOD 34
+
+#define R_OR1K_NUM 35
 
 #ifdef __cplusplus
 }
index 24bac433571a3ac415b60331a05fa8dd379a14ff..effa32b8b2f1638c56405d9ec39c3811f9c5729f 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef __LIMITS_H
-#define __LIMITS_H
+#ifndef __LINK_H
+#define __LINK_H
 
 #include <stddef.h>
 #include <elf.h>
@@ -25,4 +25,4 @@ extern int dl_iterate_phdr (int (*__callback) (struct dl_phdr_info *,
 }
 #endif
 
-#endif /* __LIMITS_H */
+#endif /* __LINK_H */
diff --git a/software/libdyld/Makefile b/software/libdyld/Makefile
new file mode 100644 (file)
index 0000000..27a29db
--- /dev/null
@@ -0,0 +1,19 @@
+MSCDIR=../..
+include $(MSCDIR)/software/common.mak
+
+COMMONFLAGS += -I$(MSCDIR)/software/include/dyld
+
+OBJECTS=dyld.o
+
+all: libdyld.a
+
+# pull in dependency info for *existing* .o files
+-include $(OBJECTS:.o=.d)
+
+libdyld.a: $(OBJECTS)
+       $(AR) crs libdyld.a $(OBJECTS)
+
+.PHONY: clean
+
+clean:
+       $(RM) $(OBJECTS) $(OBJECTS:.o=.d) libdyld.a .*~ *~
diff --git a/software/libdyld/dyld.c b/software/libdyld/dyld.c
new file mode 100644 (file)
index 0000000..04cdbc6
--- /dev/null
@@ -0,0 +1,166 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dyld.h>
+
+static int fixup_rela(Elf32_Addr base, Elf32_Rela *rela,
+                      const char *strtab, Elf32_Sym *symtab,
+                      Elf32_Addr (*resolve_import)(const char *),
+                      const char **error_out) {
+    Elf32_Sym *sym = NULL;
+    if(ELF32_R_SYM(rela->r_info) != 0)
+        sym = &symtab[ELF32_R_SYM(rela->r_info)];
+    Elf32_Addr value;
+
+    switch(ELF32_R_TYPE(rela->r_info)) {
+        case R_OR1K_RELATIVE:
+        value = base + (sym ? sym->st_value : 0) + rela->r_addend;
+        break;
+
+        case R_OR1K_JMP_SLOT:
+        value = resolve_import(&strtab[sym->st_name]);
+        if(value == 0) {
+            static char error[256];
+            error[scnprintf(error, sizeof(error), "ELF object has an unresolved symbol: %s",
+                            &strtab[sym->st_name])] = 0;
+            *error_out = error;
+            return 0;
+        }
+        break;
+
+        default:
+        *error_out = "ELF object uses an unsupported relocation type";
+        return 0;
+    }
+
+    *(Elf32_Addr*)(base + rela->r_offset) = value;
+
+    return 1;
+}
+
+int dyld_load(void *shlib, Elf32_Addr base,
+              Elf32_Addr (*resolve_import)(const char *),
+              struct dyld_info *info, const char **error_out) {
+    Elf32_Ehdr *ehdr = (Elf32_Ehdr *)shlib;
+
+    const unsigned char expected_ident[EI_NIDENT] = {
+        ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3,
+        ELFCLASS32, ELFDATA2MSB, EV_CURRENT,
+        ELFOSABI_NONE, /* ABI version */ 0
+    };
+    if(memcmp(ehdr->e_ident, expected_ident, EI_NIDENT) ||
+       ehdr->e_type != ET_DYN) {
+        *error_out = "ELF object is not a shared library";
+        return 0;
+    }
+
+#ifdef __or1k__
+    if(ehdr->e_machine != EM_OPENRISC) {
+        *error_out = "ELF object does not contain OpenRISC machine code";
+        return 0;
+    }
+#else
+#error Unsupported architecture
+#endif
+
+    Elf32_Phdr *phdr = (Elf32_Phdr *)((intptr_t)shlib + ehdr->e_phoff);
+    Elf32_Dyn *dyn = NULL;
+    for(int i = 0; i < ehdr->e_phnum; i++) {
+        if(phdr[i].p_type == PT_DYNAMIC)
+            dyn = (Elf32_Dyn *)((intptr_t)shlib + phdr[i].p_offset);
+
+        memcpy((void*)(base + phdr[i].p_vaddr),
+               (void*)((intptr_t)shlib + phdr[i].p_offset),
+               phdr[i].p_filesz);
+    }
+
+    if(dyn == NULL) {
+        *error_out = "ELF object does not have a PT_DYNAMIC header";
+        return 0;
+    }
+
+    char *strtab = NULL;
+    Elf32_Sym *symtab = NULL;
+    Elf32_Rela *rela = NULL, *pltrel = NULL;
+    Elf32_Word *hash = NULL, init = 0;
+    size_t syment = sizeof(Elf32_Sym), relaent = sizeof(Elf32_Rela),
+           relanum = 0, pltrelnum = 0;
+    while(dyn->d_tag != DT_NULL) {
+        switch(dyn->d_tag) {
+            case DT_STRTAB:   strtab    = (char *)(base + dyn->d_un.d_ptr); break;
+            case DT_SYMTAB:   symtab    = (Elf32_Sym *)(base + dyn->d_un.d_ptr); break;
+            case DT_SYMENT:   syment    = dyn->d_un.d_val; break;
+            case DT_RELA:     rela      = (Elf32_Rela *)(base + dyn->d_un.d_ptr); break;
+            case DT_RELAENT:  relaent   = dyn->d_un.d_val; break;
+            case DT_RELASZ:   relanum   = dyn->d_un.d_val / sizeof(Elf32_Rela); break;
+            case DT_JMPREL:   pltrel    = (Elf32_Rela *)(base + dyn->d_un.d_ptr); break;
+            case DT_PLTRELSZ: pltrelnum = dyn->d_un.d_val / sizeof(Elf32_Rela); break;
+            case DT_HASH:     hash      = (Elf32_Word *)dyn->d_un.d_val; break;
+            case DT_INIT:     init      = dyn->d_un.d_val; break;
+
+            case DT_REL:
+            *error_out = "ELF object uses Rel relocations, which are not supported";
+            return 0;
+        }
+
+        ++dyn;
+    }
+
+    if(symtab == NULL || syment == 0 || strtab == NULL) {
+        *error_out = "ELF object must contain a symbol table";
+        return 0;
+    }
+
+    if(syment != sizeof(Elf32_Sym) || relaent != sizeof(Elf32_Rela)) {
+        *error_out = "ELF object uses an unknown format for symbols and relocations";
+        return 0;
+    }
+
+    for(int i = 0; i < relanum; i++) {
+        if(!fixup_rela(base, &rela[i], strtab, symtab, resolve_import, error_out))
+            return 0;
+    }
+
+    for(int i = 0; i < pltrelnum; i++) {
+        if(!fixup_rela(base, &pltrel[i], strtab, symtab, resolve_import, error_out))
+            return 0;
+    }
+
+    info->base         = base;
+    info->init         = (void*)(base + init);
+    info->strtab       = strtab;
+    info->symtab       = symtab;
+    info->hash.nbucket = hash[0];
+    info->hash.nchain  = hash[1];
+    info->hash.bucket  = &hash[2];
+    info->hash.chain   = &hash[2 + info->hash.nbucket];
+
+    return 1;
+}
+
+static unsigned long elf_hash(const unsigned char *name)
+{
+    unsigned long h = 0, g;
+    while(*name) {
+        h = (h << 4) + *name++;
+        if((g = h & 0xf0000000)) {
+            h ^= g >> 24;
+            h &= ~g;
+        }
+    }
+    return h;
+}
+
+void *dyld_lookup(const char *symbol, struct dyld_info *info) {
+    unsigned hash = elf_hash((const unsigned char*) symbol);
+    unsigned index = info->hash.bucket[hash % info->hash.nbucket];
+    while(strcmp(&info->strtab[info->symtab[index].st_name], symbol) &&
+          info->hash.chain[index] != STN_UNDEF)
+        index = info->hash.chain[index];
+
+    if(info->hash.chain[index] != STN_UNDEF) {
+        return (void*)(info->base + info->symtab[index].st_value);
+    } else {
+        return NULL;
+    }
+}