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