Guess where unknown functions are in .rel.plt
[centaur.git] / src / model / fromFile.c
index 26ce6a7b11a2d7385ff70c7a1fb3f3ca59b4a360..23105e60a76f541a4dc251bca53c0dfa4f0a69b1 100644 (file)
-#include <stdio.h>
+#include <assert.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/types.h>
+#include <libelfu/libelfu.h>
 
-#include <libelf.h>
-#include <gelf.h>
 
-#include <libelfu/libelfu.h>
+static char* symstr(ElfuScn *symtab, size_t off)
+{
+  assert(symtab);
+  assert(symtab->linkptr);
+  assert(symtab->linkptr->data.d_buf);
+  assert(off < symtab->linkptr->data.d_size);
+
+  return &(((char*)symtab->linkptr->data.d_buf)[off]);
+}
+
+
+static ElfuSymtab* symtabFromScn32(ElfuScn *ms, ElfuScn**origScnArr)
+{
+  ElfuSymtab *st;
+  size_t i;
+
+  assert(ms);
+  assert(ms->data.d_buf);
+  assert(origScnArr);
+
+
+  st = malloc(sizeof(*st));
+  if (!st) {
+    ELFU_WARN("elfu_mSymtabFromScn32: malloc() failed for st.\n");
+    goto ERROR;
+  }
+
+  CIRCLEQ_INIT(&st->syms);
+
+
+  for (i = 1; (i + 1) * sizeof(Elf32_Sym) <= ms->shdr.sh_size; i++) {
+    Elf32_Sym *cursym = &(((Elf32_Sym*)ms->data.d_buf)[i]);
+    ElfuSym *sym = malloc(sizeof(*sym));
+    assert(sym);
+
+    sym->name = symstr(ms, cursym->st_name);
+    sym->value = cursym->st_value;
+    sym->size = cursym->st_size;
+    sym->bind = ELF32_ST_BIND(cursym->st_info);
+    sym->type = ELF32_ST_TYPE(cursym->st_info);
+    sym->other = cursym->st_other;
+
+    switch (cursym->st_shndx) {
+      case SHN_UNDEF:
+      case SHN_ABS:
+      case SHN_COMMON:
+        sym->scnptr = NULL;
+        sym->shndx = cursym->st_shndx;
+        break;
+      default:
+        sym->scnptr = origScnArr[cursym->st_shndx - 1];
+        break;
+    }
+
+
+    CIRCLEQ_INSERT_TAIL(&st->syms, sym, elem);
+  }
+
+
+  return st;
+
+  ERROR:
+  if (st) {
+    free(st);
+  }
+  return NULL;
+}
+
+
+static ElfuReltab* reltabFromScn32(ElfuScn *ms)
+{
+  ElfuReltab *rt;
+  size_t i;
+
+  assert(ms);
+  assert(ms->data.d_buf);
+
+
+  rt = malloc(sizeof(*rt));
+  if (!rt) {
+    ELFU_WARN("elfu_mReltabFromScn32: malloc() failed for rt.\n");
+    goto ERROR;
+  }
+
+  CIRCLEQ_INIT(&rt->rels);
+
+
+  for (i = 0; (i + 1) * sizeof(Elf32_Rel) <= ms->shdr.sh_size; i++) {
+    Elf32_Rel *currel = &(((Elf32_Rel*)ms->data.d_buf)[i]);
+    ElfuRel *rel;
+
+    rel = malloc(sizeof(*rel));
+    assert(rel);
+
+    rel->offset = currel->r_offset;
+
+    rel->sym = ELF32_R_SYM(currel->r_info);
+    rel->type = ELF32_R_TYPE(currel->r_info);
+
+    rel->addendUsed = 0;
+    rel->addend = 0;
+
+    CIRCLEQ_INSERT_TAIL(&rt->rels, rel, elem);
+  }
+
+
+  return rt;
+
+  ERROR:
+  if (rt) {
+    free(rt);
+  }
+  return NULL;
+}
+
+
+static int cmpScnOffs(const void *ms1, const void *ms2)
+{
+  assert(ms1);
+  assert(ms2);
+
+  ElfuScn *s1 = *(ElfuScn**)ms1;
+  ElfuScn *s2 = *(ElfuScn**)ms2;
+
+  assert(s1);
+  assert(s2);
+
+
+  if (s1->shdr.sh_offset < s2->shdr.sh_offset) {
+    return -1;
+  } else if (s1->shdr.sh_offset == s2->shdr.sh_offset) {
+    return 0;
+  } else /* if (s1->shdr.sh_offset > s2->shdr.sh_offset) */ {
+    return 1;
+  }
+}
+
+
+
+static ElfuPhdr* parentPhdr(ElfuElf *me, ElfuScn *ms)
+{
+  ElfuPhdr *mp;
+
+  assert(me);
+  assert(ms);
+
+  CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
+    if (mp->phdr.p_type != PT_LOAD) {
+      continue;
+    }
+
+    if (PHDR_CONTAINS_SCN_IN_MEMORY(&mp->phdr, &ms->shdr)) {
+      return mp;
+    }
+
+    /* Give sections a second chance if they do not have any sh_addr
+     * at all. */
+    /* Actually we don't, because it's ambiguous.
+     * Re-enable for experiments with strangely-formatted files.
+    if (ms->shdr.sh_addr == 0
+        && PHDR_CONTAINS_SCN_IN_FILE(&mp->phdr, &ms->shdr)
+        && OFFS_END(ms->shdr.sh_offset, ms->shdr.sh_size)
+            <= OFFS_END(mp->phdr.p_offset, mp->phdr.p_memsz)) {
+      return mp;
+    }
+    */
+  }
+
+  return NULL;
+}
 
 
-ElfuPhdr* elfu_modelFromPhdr(GElf_Phdr *phdr)
+static ElfuPhdr* modelFromPhdr(GElf_Phdr *phdr)
 {
   ElfuPhdr *mp;
 
+  assert(phdr);
+
   mp = malloc(sizeof(ElfuPhdr));
   if (!mp) {
+    ELFU_WARN("modelFromPhdr: malloc() failed for ElfuPhdr.\n");
     return NULL;
   }
 
   mp->phdr = *phdr;
 
+  CIRCLEQ_INIT(&mp->childScnList);
+  CIRCLEQ_INIT(&mp->childPhdrList);
+
   return mp;
 }
 
 
-ElfuScn* elfu_modelFromSection(Elf_Scn *scn)
+static ElfuScn* modelFromSection(Elf_Scn *scn)
 {
   ElfuScn *ms;
-  Elf_Data *data;
+
+  assert(scn);
 
   ms = malloc(sizeof(ElfuScn));
   if (!ms) {
-    return NULL;
+    ELFU_WARN("modelFromSection: malloc() failed for ElfuScn.\n");
+    goto ERROR;
   }
 
-  CIRCLEQ_INIT(&ms->dataList);
 
+  assert(gelf_getshdr(scn, &ms->shdr) == &ms->shdr);
 
-  if (gelf_getshdr(scn, &ms->shdr) != &ms->shdr) {
-    fprintf(stderr, "gelf_getshdr() failed: %s\n", elf_errmsg(-1));
-    goto out;
-  }
 
+  /* Copy each data part in source segment */
+  ms->data.d_align = 1;
+  ms->data.d_buf  = NULL;
+  ms->data.d_off  = 0;
+  ms->data.d_type = ELF_T_BYTE;
+  ms->data.d_size = ms->shdr.sh_size;
+  ms->data.d_version = elf_version(EV_NONE);
+  if (ms->shdr.sh_type != SHT_NOBITS
+      && ms->shdr.sh_size > 0) {
+    Elf_Data *data;
+
+    ms->data.d_buf = malloc(ms->shdr.sh_size);
+    if (!ms->data.d_buf) {
+      ELFU_WARN("modelFromSection: malloc() failed for data buffer (%jx bytes).\n", ms->shdr.sh_size);
+      goto ERROR;
+    }
 
+    /* A non-empty section should contain at least one data block. */
+    data = elf_rawdata(scn, NULL);
+    assert(data);
 
-  /* Copy each data part in source segment */
-  data = elf_rawdata(scn, NULL);
-  while (data) {
-    ElfuData *md;
+    ms->data.d_align = data->d_align;
+    ms->data.d_type = data->d_type;
+    ms->data.d_version = data->d_version;
+
+    while (data) {
+      if (data->d_off + data->d_size > ms->shdr.sh_size) {
+        ELFU_WARN("modelFromSection: libelf delivered a bogus data blob. Skipping\n");
+      } else {
+        memcpy(ms->data.d_buf + data->d_off, data->d_buf, data->d_size);
+      }
 
-    md = malloc(sizeof(ElfuData));
-    if (!md) {
-      goto out;
+      data = elf_rawdata(scn, data);
     }
+  }
 
-    md->data = *data;
+  ms->linkptr = NULL;
+  ms->infoptr = NULL;
 
-    CIRCLEQ_INSERT_TAIL(&ms->dataList, md, elem);
+  ms->oldptr = NULL;
+
+  ms->symtab = NULL;
+  ms->reltab = NULL;
 
-    data = elf_rawdata(scn, data);
-  }
 
   return ms;
 
-  out:
-  // FIXME
+  ERROR:
+  if (ms) {
+    free(ms);
+  }
   return NULL;
 }
 
 
 
 
-ElfuElf* elfu_modelFromElf(Elf *e)
+ElfuElf* elfu_mFromElf(Elf *e)
 {
   ElfuElf *me;
-  Elf_Scn *scn;
   size_t shstrndx;
-  size_t i, n;
+  size_t i, numPhdr, numShdr;
+  ElfuScn **secArray = NULL;
 
-  if (elf_getshdrstrndx(e, &shstrndx) != 0) {
-    shstrndx = 0;
+  assert(e);
+  if (elfu_eCheck(e)) {
+    goto ERROR;
   }
 
   me = malloc(sizeof(ElfuElf));
   if (!me) {
-    return NULL;
+    ELFU_WARN("elfu_mFromElf: malloc() failed for ElfuElf.\n");
+    goto ERROR;
   }
 
-  CIRCLEQ_INIT(&me->scnList);
+
+  /* General stuff */
   CIRCLEQ_INIT(&me->phdrList);
+  CIRCLEQ_INIT(&me->orphanScnList);
   me->shstrtab = NULL;
 
-  /*
-   * General stuff
-   */
   me->elfclass = gelf_getclass(e);
-  if (me->elfclass == ELFCLASSNONE) {
-    fprintf(stderr, "getclass() failed: %s\n", elf_errmsg(-1));
+  assert(me->elfclass != ELFCLASSNONE);
+  assert(gelf_getehdr(e, &me->ehdr) == &me->ehdr);
+
+
+  /* Get the section string table index */
+  if (elf_getshdrstrndx(e, &shstrndx) != 0) {
+    shstrndx = 0;
   }
 
-  if (!gelf_getehdr(e, &me->ehdr)) {
-    fprintf(stderr, "gelf_getehdr() failed: %s\n", elf_errmsg(-1));
-    goto out;
+
+  /* Load segments */
+  assert(!elf_getphdrnum(e, &numPhdr));
+  for (i = 0; i < numPhdr; i++) {
+    GElf_Phdr phdr;
+    ElfuPhdr *mp;
+
+    assert(gelf_getphdr(e, i, &phdr) == &phdr);
+
+    mp = modelFromPhdr(&phdr);
+    if (!mp) {
+      goto ERROR;
+    }
+
+    CIRCLEQ_INSERT_TAIL(&me->phdrList, mp, elem);
+  }
+
+  if (numPhdr > 0) {
+    ElfuPhdr *mp;
+
+    /* Find PHDR -> PHDR dependencies (needs sorted sections) */
+    CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
+      ElfuPhdr *mp2;
+
+      if (mp->phdr.p_type != PT_LOAD) {
+        continue;
+      }
+
+      CIRCLEQ_FOREACH(mp2, &me->phdrList, elem) {
+        if (mp2 == mp) {
+          continue;
+        }
+
+        if (mp->phdr.p_vaddr <= mp2->phdr.p_vaddr
+            && OFFS_END(mp2->phdr.p_vaddr, mp2->phdr.p_memsz) <= OFFS_END(mp->phdr.p_vaddr, mp->phdr.p_memsz)) {
+          CIRCLEQ_INSERT_TAIL(&mp->childPhdrList, mp2, elemChildPhdr);
+        }
+      }
+    }
   }
 
 
-  /*
-   * Sections
-   */
-  scn = elf_getscn(e, 1);
-  i = 1;
-  while (scn) {
-    ElfuScn *ms = elfu_modelFromSection(scn);
+  /* Load sections */
+  assert(!elf_getshdrnum(e, &numShdr));
+  if (numShdr > 1) {
+    secArray = malloc((numShdr - 1) * sizeof(*secArray));
+    if (!secArray) {
+      ELFU_WARN("elfu_mFromElf: malloc() failed for secArray.\n");
+      goto ERROR;
+    }
+
+    for (i = 1; i < numShdr; i++) {
+      Elf_Scn *scn;
+      ElfuScn *ms;
+
+      scn = elf_getscn(e, i);
+      assert(scn);
+
+      ms = modelFromSection(scn);
+      if (!ms) {
+        goto ERROR;
+      }
+
+      secArray[i-1] =  ms;
 
-    if (ms) {
-      CIRCLEQ_INSERT_TAIL(&me->scnList, ms, elem);
       if (i == shstrndx) {
         me->shstrtab = ms;
       }
-    } else {
-      goto out;
     }
 
-    scn = elf_nextscn(e, scn);
-    i++;
-  }
 
+    /* Find sh_link and sh_info dependencies (needs sections in original order) */
+    for (i = 0; i < numShdr - 1; i++) {
+      ElfuScn *ms = secArray[i];
+
+      switch (ms->shdr.sh_type) {
+        case SHT_REL:
+        case SHT_RELA:
+          if (ms->shdr.sh_info > 0) {
+            ms->infoptr = secArray[ms->shdr.sh_info - 1];
+          }
+        case SHT_DYNAMIC:
+        case SHT_HASH:
+        case SHT_SYMTAB:
+        case SHT_DYNSYM:
+        case SHT_GNU_versym:
+        case SHT_GNU_verdef:
+        case SHT_GNU_verneed:
+          if (ms->shdr.sh_link > 0) {
+            ms->linkptr = secArray[ms->shdr.sh_link - 1];
+          }
+      }
+    }
 
 
-  /*
-   * Segments
-   */
-  if (elf_getphdrnum(e, &n)) {
-    fprintf(stderr, "elf_getphdrnum() failed: %s\n", elf_errmsg(-1));
-  }
+    /* Parse symtabs (needs sections in original order) */
+    for (i = 0; i < numShdr - 1; i++) {
+      ElfuScn *ms = secArray[i];
+
+      switch (ms->shdr.sh_type) {
+        case SHT_SYMTAB:
+        case SHT_DYNSYM:
+          if (me->elfclass == ELFCLASS32) {
+            ms->symtab = symtabFromScn32(ms, secArray);
+          } else if (me->elfclass == ELFCLASS64) {
+            // TODO
+          }
+          assert(ms->symtab);
+          break;
+      }
+    }
 
-  for (i = 0; i < n; i++) {
-    GElf_Phdr phdr;
-    ElfuPhdr *mp;
 
-    if (gelf_getphdr(e, i, &phdr) != &phdr) {
-      fprintf(stderr, "gelf_getphdr() failed for #%d: %s\n", i, elf_errmsg(-1));
-      break;
+    /* Parse relocations */
+    for (i = 0; i < numShdr - 1; i++) {
+      ElfuScn *ms = secArray[i];
+
+      switch (ms->shdr.sh_type) {
+        case SHT_REL:
+          if (me->elfclass == ELFCLASS32) {
+            ms->reltab = reltabFromScn32(ms);
+          } else if (me->elfclass == ELFCLASS64) {
+            // TODO
+          }
+          assert(ms->reltab);
+          break;
+        case SHT_RELA:
+          if (me->elfclass == ELFCLASS32) {
+            // TODO
+          } else if (me->elfclass == ELFCLASS64) {
+            // TODO
+          }
+          assert(ms->reltab);
+          break;
+      }
     }
 
-    mp = elfu_modelFromPhdr(&phdr);
 
-    if (mp) {
-      CIRCLEQ_INSERT_TAIL(&me->phdrList, mp, elem);
-    } else {
-      goto out;
+    /* Sort sections by file offset */
+    qsort(secArray, numShdr - 1, sizeof(*secArray), cmpScnOffs);
+
+
+    /* Find PHDR -> Section dependencies (needs sorted sections) */
+    for (i = 0; i < numShdr - 1; i++) {
+      ElfuScn *ms = secArray[i];
+
+      ElfuPhdr *parent = parentPhdr(me, ms);
+
+      if (parent) {
+        GElf_Off shaddr = parent->phdr.p_vaddr +
+                         (ms->shdr.sh_offset - parent->phdr.p_offset);
+
+        if (ms->shdr.sh_addr == 0) {
+          ms->shdr.sh_addr = shaddr;
+        } else {
+          assert(ms->shdr.sh_addr == shaddr);
+        }
+
+        CIRCLEQ_INSERT_TAIL(&parent->childScnList, ms, elemChildScn);
+      } else {
+        CIRCLEQ_INSERT_TAIL(&me->orphanScnList, ms, elemChildScn);
+      }
     }
   }
 
 
-
   return me;
 
-  out:
-  // FIXME
+
+  ERROR:
+  if (secArray) {
+    free(secArray);
+  }
+  if (me) {
+    // TODO: Free data structures
+  }
+
+  ELFU_WARN("elfu_mFromElf: Failed to load file.\n");
   return NULL;
 }