2c9873c323819b16f41a6f9a683a0dd4876256d0
[centaur.git] / src / modelops / relocate.c
1 #include <assert.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <libelfu/libelfu.h>
5
6
7 static void* subFindByName(ElfuElf *me, ElfuScn *ms, void *aux1, void *aux2)
8 {
9   char *name = (char*)aux1;
10   (void)aux2;
11
12   if (elfu_mScnName(me, ms)) {
13     if (!strcmp(elfu_mScnName(me, ms), name)) {
14       return ms;
15     }
16   }
17
18   /* Continue */
19   return NULL;
20 }
21
22 /* Hazard a guess where a function may be found in the PLT */
23 static GElf_Word pltLookupVal(ElfuElf *metarget, char *name)
24 {
25   ElfuScn *relplt;
26   ElfuScn *plt;
27   ElfuRel *rel;
28   GElf_Word j;
29
30   relplt = elfu_mScnForall(metarget, subFindByName, ".rel.plt", NULL);
31   if (!relplt) {
32     ELFU_WARN("dynsymLookupVal: Could not find .rel.plt section in destination ELF.\n");
33     return 0;
34   }
35
36   plt = elfu_mScnForall(metarget, subFindByName, ".plt", NULL);
37   if (!plt) {
38     ELFU_WARN("dynsymLookupVal: Could not find .plt section in destination ELF.\n");
39     return 0;
40   }
41
42
43   /* Look up name. If the j-th entry in .rel.plt has the name we are
44    * looking for, we assume that the (j+1)-th entry in .plt is machine
45    * code to jump to the function.
46    * Your mileage may vary, but it works on my GNU binaries. */
47   assert(relplt->linkptr);
48   j = 0;
49   CIRCLEQ_FOREACH(rel, &relplt->reltab.rels, elem) {
50     GElf_Word i;
51     ElfuSym *sym;
52     char *symname;
53
54     j++;
55
56     /* We only consider runtime relocations for functions.
57      * Technically, these relocations write the functions' addresses
58      * to the GOT, not the PLT, after the dynamic linker has found
59      * them. */
60     if ((metarget->elfclass == ELFCLASS32 && rel->type != R_386_JMP_SLOT)
61         || (metarget->elfclass == ELFCLASS64 && rel->type != R_X86_64_JUMP_SLOT)) {
62       continue;
63     }
64
65     /* Get the (rel->sym)-th symbol from the symbol table that
66      * .rel.plt points to. */
67     sym = CIRCLEQ_FIRST(&relplt->linkptr->symtab.syms);
68     for (i = 1; i < rel->sym; i++) {
69       sym = CIRCLEQ_NEXT(sym, elem);
70     }
71
72     symname = ELFU_SYMSTR(relplt->linkptr, sym->name);
73     if (!strcmp(symname, name)) {
74       /* If this is the symbol we are looking for, then in an x86 binary
75        * the jump to the dynamic symbol is probably at offset (j * 16)
76        * from the start of the PLT, where j is the PLT entry and 16 is
77        * the number of bytes the machine code in a PLT entry take. */
78       GElf_Addr addr = plt->shdr.sh_addr + (16 * j);
79       ELFU_DEBUG("dynsymLookupVal: Guessing symbol '%s' is in destination memory at %jx (PLT entry #%d).\n", name, addr, j);
80       return addr;
81     }
82   }
83
84   ELFU_WARN("dynsymLookupVal: Could not find symbol '%s' in destination ELF.\n", name);
85
86   return 0;
87 }
88
89
90 static GElf_Word symtabLookupVal(ElfuElf *metarget, ElfuScn *msst, GElf_Word entry)
91 {
92   GElf_Word i;
93   ElfuSym *sym;
94   char *symname;
95
96   assert(metarget);
97   assert(msst);
98   assert(entry > 0);
99   assert(!CIRCLEQ_EMPTY(&msst->symtab.syms));
100
101   sym = CIRCLEQ_FIRST(&msst->symtab.syms);
102   for (i = 1; i < entry; i++) {
103     sym = CIRCLEQ_NEXT(sym, elem);
104   }
105   symname = ELFU_SYMSTR(msst, sym->name);
106
107   switch (sym->type) {
108     case STT_NOTYPE:
109     case STT_OBJECT:
110     case STT_FUNC:
111       if (sym->scnptr) {
112         ElfuScn *newscn = elfu_mScnByOldscn(metarget, sym->scnptr);
113         assert(newscn);
114         return newscn->shdr.sh_addr + sym->value;
115       } else if (sym->shndx == SHN_UNDEF) {
116         /* Look the symbol up in .rel.plt. If it cannot be found there then
117          * .rel.dyn may need to be expanded with a COPY relocation so the
118          * dynamic linker fixes up the (TODO). */
119         return pltLookupVal(metarget, symname);
120       } else if (sym->shndx == SHN_ABS) {
121         return sym->value;
122       } else {
123         ELFU_WARN("symtabLookupVal: Symbol binding COMMON is not supported, using 0.\n");
124         return 0;
125       }
126       break;
127     case STT_SECTION:
128       assert(sym->scnptr);
129       assert(elfu_mScnByOldscn(metarget, sym->scnptr));
130       return elfu_mScnByOldscn(metarget, sym->scnptr)->shdr.sh_addr;
131     case STT_FILE:
132       ELFU_WARN("symtabLookupVal: Symbol type FILE is not supported, using 0.\n");
133       return 0;
134     default:
135       ELFU_WARN("symtabLookupVal: Unknown symbol type %d for %s.\n", sym->type, symname);
136       return 0;
137   }
138 }
139
140 void elfu_mRelocate32(ElfuElf *metarget, ElfuScn *mstarget, ElfuScn *msrt)
141 {
142   ElfuRel *rel;
143
144   assert(mstarget);
145   assert(msrt);
146
147   ELFU_DEBUG("Relocating in section of type %d size %jx\n",
148              mstarget->shdr.sh_type,
149              mstarget->shdr.sh_size);
150
151   CIRCLEQ_FOREACH(rel, &msrt->reltab.rels, elem) {
152     Elf32_Word *dest32 = (Elf32_Word*)(((char*)(mstarget->data.d_buf)) + rel->offset);
153     Elf64_Word *dest64 = (Elf64_Word*)(((char*)(mstarget->data.d_buf)) + rel->offset);
154
155
156     if (metarget->elfclass == ELFCLASS32) {
157       Elf32_Word a32 = rel->addendUsed ? rel->addend : *dest32;
158       Elf32_Addr p32 = mstarget->shdr.sh_addr + rel->offset;
159       Elf32_Addr s32 = symtabLookupVal(metarget, msrt->linkptr, rel->sym);
160       switch(rel->type) {
161         case R_386_NONE:
162           ELFU_DEBUG("Skipping relocation: R_386_NONE\n");
163           break;
164         case R_386_32:
165           *dest32 = s32 + a32;
166           break;
167         case R_386_PC32:
168           *dest32 = s32 + a32 - p32;
169           break;
170
171         default:
172           ELFU_DEBUG("Skipping relocation: Unknown type %d\n", rel->type);
173       }
174     } else if (metarget->elfclass == ELFCLASS64) {
175       /* x86-64 only uses RELA with explicit addend. */
176       assert(rel->addendUsed);
177       Elf64_Word a64 = rel->addend;
178       Elf64_Addr p64 = mstarget->shdr.sh_addr + rel->offset;
179       Elf64_Addr s64 = symtabLookupVal(metarget, msrt->linkptr, rel->sym);
180
181       switch(rel->type) {
182         case R_X86_64_NONE:
183           ELFU_DEBUG("Skipping relocation: R_386_NONE\n");
184           break;
185         case R_X86_64_64:
186           *dest64 = s64 + a64;
187           break;
188         case R_X86_64_PC32:
189           *dest32 = s64 + a64 - p64;
190           break;
191         case R_X86_64_32:
192           *dest32 = s64 + a64;
193           break;
194
195         default:
196           ELFU_DEBUG("Skipping relocation: Unknown type %d", rel->type);
197       }
198     }
199
200
201   }
202 }