1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * MIPS Relocation Data Generator
4 *
5 * Copyright (c) 2017 Imagination Technologies Ltd.
6 */
7
8 #include <assert.h>
9 #include <elf.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <inttypes.h>
13 #include <limits.h>
14 #include <stdbool.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <sys/mman.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20
21 #include <asm/relocs.h>
22
23 #define hdr_field(pfx, idx, field) ({ \
24 uint64_t _val; \
25 unsigned int _size; \
26 \
27 if (is_64) { \
28 _val = pfx##hdr64[idx].field; \
29 _size = sizeof(pfx##hdr64[0].field); \
30 } else { \
31 _val = pfx##hdr32[idx].field; \
32 _size = sizeof(pfx##hdr32[0].field); \
33 } \
34 \
35 switch (_size) { \
36 case 1: \
37 break; \
38 case 2: \
39 _val = is_be ? be16toh(_val) : le16toh(_val); \
40 break; \
41 case 4: \
42 _val = is_be ? be32toh(_val) : le32toh(_val); \
43 break; \
44 case 8: \
45 _val = is_be ? be64toh(_val) : le64toh(_val); \
46 break; \
47 } \
48 \
49 _val; \
50 })
51
52 #define set_hdr_field(pfx, idx, field, val) ({ \
53 uint64_t _val; \
54 unsigned int _size; \
55 \
56 if (is_64) \
57 _size = sizeof(pfx##hdr64[0].field); \
58 else \
59 _size = sizeof(pfx##hdr32[0].field); \
60 \
61 switch (_size) { \
62 case 1: \
63 _val = val; \
64 break; \
65 case 2: \
66 _val = is_be ? htobe16(val) : htole16(val); \
67 break; \
68 case 4: \
69 _val = is_be ? htobe32(val) : htole32(val); \
70 break; \
71 case 8: \
72 _val = is_be ? htobe64(val) : htole64(val); \
73 break; \
74 default: \
75 /* We should never reach here */ \
76 _val = 0; \
77 assert(0); \
78 break; \
79 } \
80 \
81 if (is_64) \
82 pfx##hdr64[idx].field = _val; \
83 else \
84 pfx##hdr32[idx].field = _val; \
85 })
86
87 #define ehdr_field(field) \
88 hdr_field(e, 0, field)
89 #define phdr_field(idx, field) \
90 hdr_field(p, idx, field)
91 #define shdr_field(idx, field) \
92 hdr_field(s, idx, field)
93
94 #define set_phdr_field(idx, field, val) \
95 set_hdr_field(p, idx, field, val)
96 #define set_shdr_field(idx, field, val) \
97 set_hdr_field(s, idx, field, val)
98
99 #define shstr(idx) (&shstrtab[idx])
100
101 bool is_64, is_be;
102 uint64_t text_base;
103
104 struct mips_reloc {
105 uint8_t type;
106 uint64_t offset;
107 } *relocs;
108 size_t relocs_sz, relocs_idx;
109
add_reloc(unsigned int type,uint64_t off)110 static int add_reloc(unsigned int type, uint64_t off)
111 {
112 struct mips_reloc *new;
113 size_t new_sz;
114
115 switch (type) {
116 case R_MIPS_NONE:
117 case R_MIPS_LO16:
118 case R_MIPS_PC16:
119 case R_MIPS_HIGHER:
120 case R_MIPS_HIGHEST:
121 case R_MIPS_PC21_S2:
122 case R_MIPS_PC26_S2:
123 /* Skip these relocs */
124 return 0;
125
126 default:
127 break;
128 }
129
130 if (relocs_idx == relocs_sz) {
131 new_sz = relocs_sz ? relocs_sz * 2 : 128;
132 new = realloc(relocs, new_sz * sizeof(*relocs));
133 if (!new) {
134 fprintf(stderr, "Out of memory\n");
135 return -ENOMEM;
136 }
137
138 relocs = new;
139 relocs_sz = new_sz;
140 }
141
142 relocs[relocs_idx++] = (struct mips_reloc){
143 .type = type,
144 .offset = off,
145 };
146
147 return 0;
148 }
149
parse_mips32_rel(const void * _rel)150 static int parse_mips32_rel(const void *_rel)
151 {
152 const Elf32_Rel *rel = _rel;
153 uint32_t off, type;
154
155 off = is_be ? be32toh(rel->r_offset) : le32toh(rel->r_offset);
156 off -= text_base;
157
158 type = is_be ? be32toh(rel->r_info) : le32toh(rel->r_info);
159 type = ELF32_R_TYPE(type);
160
161 return add_reloc(type, off);
162 }
163
parse_mips64_rela(const void * _rel)164 static int parse_mips64_rela(const void *_rel)
165 {
166 const Elf64_Rela *rel = _rel;
167 uint64_t off, type;
168
169 off = is_be ? be64toh(rel->r_offset) : le64toh(rel->r_offset);
170 off -= text_base;
171
172 type = rel->r_info >> (64 - 8);
173
174 return add_reloc(type, off);
175 }
176
output_uint(uint8_t ** buf,uint64_t val)177 static void output_uint(uint8_t **buf, uint64_t val)
178 {
179 uint64_t tmp;
180
181 do {
182 tmp = val & 0x7f;
183 val >>= 7;
184 tmp |= !!val << 7;
185 *(*buf)++ = tmp;
186 } while (val);
187 }
188
compare_relocs(const void * a,const void * b)189 static int compare_relocs(const void *a, const void *b)
190 {
191 const struct mips_reloc *ra = a, *rb = b;
192
193 return ra->offset - rb->offset;
194 }
195
main(int argc,char * argv[])196 int main(int argc, char *argv[])
197 {
198 unsigned int i, j, i_rel_shdr, sh_type, sh_entsize, sh_entries;
199 size_t rel_size, rel_actual_size;
200 const char *shstrtab, *sh_name, *rel_pfx;
201 int (*parse_fn)(const void *rel);
202 uint8_t *buf_start, *buf;
203 const Elf32_Ehdr *ehdr32;
204 const Elf64_Ehdr *ehdr64;
205 uintptr_t sh_offset;
206 Elf32_Shdr *shdr32;
207 Elf64_Shdr *shdr64;
208 struct stat st;
209 int err, fd;
210 void *elf;
211 bool skip;
212
213 fd = open(argv[1], O_RDWR);
214 if (fd == -1) {
215 fprintf(stderr, "Unable to open input file %s\n", argv[1]);
216 err = errno;
217 goto out_ret;
218 }
219
220 err = fstat(fd, &st);
221 if (err) {
222 fprintf(stderr, "Unable to fstat() input file\n");
223 goto out_close_fd;
224 }
225
226 elf = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
227 if (elf == MAP_FAILED) {
228 fprintf(stderr, "Unable to mmap() input file\n");
229 err = errno;
230 goto out_close_fd;
231 }
232
233 ehdr32 = elf;
234 ehdr64 = elf;
235
236 if (memcmp(&ehdr32->e_ident[EI_MAG0], ELFMAG, SELFMAG)) {
237 fprintf(stderr, "Input file is not an ELF\n");
238 err = -EINVAL;
239 goto out_free_relocs;
240 }
241
242 if (ehdr32->e_ident[EI_VERSION] != EV_CURRENT) {
243 fprintf(stderr, "Unrecognised ELF version\n");
244 err = -EINVAL;
245 goto out_free_relocs;
246 }
247
248 switch (ehdr32->e_ident[EI_CLASS]) {
249 case ELFCLASS32:
250 is_64 = false;
251 break;
252 case ELFCLASS64:
253 is_64 = true;
254 break;
255 default:
256 fprintf(stderr, "Unrecognised ELF class\n");
257 err = -EINVAL;
258 goto out_free_relocs;
259 }
260
261 switch (ehdr32->e_ident[EI_DATA]) {
262 case ELFDATA2LSB:
263 is_be = false;
264 break;
265 case ELFDATA2MSB:
266 is_be = true;
267 break;
268 default:
269 fprintf(stderr, "Unrecognised ELF data encoding\n");
270 err = -EINVAL;
271 goto out_free_relocs;
272 }
273
274 if (ehdr_field(e_type) != ET_EXEC) {
275 fprintf(stderr, "Input ELF is not an executable\n");
276 printf("type 0x%" PRIx64 "\n", ehdr_field(e_type));
277 err = -EINVAL;
278 goto out_free_relocs;
279 }
280
281 if (ehdr_field(e_machine) != EM_MIPS) {
282 fprintf(stderr, "Input ELF does not target MIPS\n");
283 err = -EINVAL;
284 goto out_free_relocs;
285 }
286
287 shdr32 = elf + ehdr_field(e_shoff);
288 shdr64 = elf + ehdr_field(e_shoff);
289 shstrtab = elf + shdr_field(ehdr_field(e_shstrndx), sh_offset);
290
291 i_rel_shdr = UINT_MAX;
292 for (i = 0; i < ehdr_field(e_shnum); i++) {
293 sh_name = shstr(shdr_field(i, sh_name));
294
295 if (!strcmp(sh_name, ".data.reloc")) {
296 i_rel_shdr = i;
297 continue;
298 }
299
300 if (!strcmp(sh_name, ".text")) {
301 text_base = shdr_field(i, sh_addr);
302 continue;
303 }
304 }
305 if (i_rel_shdr == UINT_MAX) {
306 fprintf(stderr, "Unable to find .rel section\n");
307 err = -EINVAL;
308 goto out_free_relocs;
309 }
310 if (!text_base) {
311 fprintf(stderr, "Unable to find .text base address\n");
312 err = -EINVAL;
313 goto out_free_relocs;
314 }
315
316 rel_pfx = is_64 ? ".rela" : ".rel";
317
318 for (i = 0; i < ehdr_field(e_shnum); i++) {
319 sh_type = shdr_field(i, sh_type);
320 if ((sh_type != SHT_REL) && (sh_type != SHT_RELA))
321 continue;
322
323 sh_name = shstr(shdr_field(i, sh_name));
324 if (strncmp(sh_name, rel_pfx, strlen(rel_pfx))) {
325 fprintf(stderr, "WARNING: Unexpected reloc section name '%s'\n", sh_name);
326 continue;
327 }
328 if (!strcmp(sh_name, ".rel") || !strcmp(sh_name, ".rel.dyn"))
329 continue;
330
331 /*
332 * Skip reloc sections which either don't correspond to another
333 * section in the ELF, or whose corresponding section isn't
334 * loaded as part of the U-Boot binary (ie. doesn't have the
335 * alloc flags set).
336 */
337 skip = true;
338 for (j = 0; j < ehdr_field(e_shnum); j++) {
339 if (strcmp(&sh_name[strlen(rel_pfx)], shstr(shdr_field(j, sh_name))))
340 continue;
341
342 skip = !(shdr_field(j, sh_flags) & SHF_ALLOC);
343 break;
344 }
345 if (skip)
346 continue;
347
348 sh_offset = shdr_field(i, sh_offset);
349 sh_entsize = shdr_field(i, sh_entsize);
350 sh_entries = shdr_field(i, sh_size) / sh_entsize;
351
352 if (sh_type == SHT_REL) {
353 if (is_64) {
354 fprintf(stderr, "REL-style reloc in MIPS64 ELF?\n");
355 err = -EINVAL;
356 goto out_free_relocs;
357 } else {
358 parse_fn = parse_mips32_rel;
359 }
360 } else {
361 if (is_64) {
362 parse_fn = parse_mips64_rela;
363 } else {
364 fprintf(stderr, "RELA-style reloc in MIPS32 ELF?\n");
365 err = -EINVAL;
366 goto out_free_relocs;
367 }
368 }
369
370 for (j = 0; j < sh_entries; j++) {
371 err = parse_fn(elf + sh_offset + (j * sh_entsize));
372 if (err)
373 goto out_free_relocs;
374 }
375 }
376
377 /* Sort relocs in ascending order of offset */
378 qsort(relocs, relocs_idx, sizeof(*relocs), compare_relocs);
379
380 /* Make reloc offsets relative to their predecessor */
381 for (i = relocs_idx - 1; i > 0; i--)
382 relocs[i].offset -= relocs[i - 1].offset;
383
384 /* Write the relocations to the .rel section */
385 buf = buf_start = elf + shdr_field(i_rel_shdr, sh_offset);
386 for (i = 0; i < relocs_idx; i++) {
387 output_uint(&buf, relocs[i].type);
388 output_uint(&buf, relocs[i].offset >> 2);
389 }
390
391 /* Write a terminating R_MIPS_NONE (0) */
392 output_uint(&buf, R_MIPS_NONE);
393
394 /* Ensure the relocs didn't overflow the .rel section */
395 rel_size = shdr_field(i_rel_shdr, sh_size);
396 rel_actual_size = buf - buf_start;
397 if (rel_actual_size > rel_size) {
398 fprintf(stderr, "Relocations overflow available space of 0x%zx (required 0x%zx)!\n",
399 rel_size, rel_actual_size);
400 fprintf(stderr, "Please adjust CONFIG_MIPS_RELOCATION_TABLE_SIZE to at least 0x%zx\n",
401 (rel_actual_size + 0x100) & ~0xFF);
402 err = -ENOMEM;
403 goto out_free_relocs;
404 }
405
406 /* Make sure data is written back to the file */
407 err = msync(elf, st.st_size, MS_SYNC);
408 if (err) {
409 fprintf(stderr, "Failed to msync: %d\n", errno);
410 goto out_free_relocs;
411 }
412
413 out_free_relocs:
414 free(relocs);
415 munmap(elf, st.st_size);
416 out_close_fd:
417 close(fd);
418 out_ret:
419 return err;
420 }
421