1 /*
2  * Copyright (c) 2024 BayLibre SAS
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/llext/elf.h>
8 #include <zephyr/llext/llext.h>
9 #include <zephyr/logging/log.h>
10 #include <zephyr/sys/util.h>
11 #include <zephyr/sys/byteorder.h>
12 
13 LOG_MODULE_REGISTER(elf, CONFIG_LLEXT_LOG_LEVEL);
14 
15 #define R_ARM_NONE     0
16 #define R_AARCH64_NONE 256
17 
18 /* Static data relocations */
19 #define R_AARCH64_ABS64  257
20 #define R_AARCH64_ABS32  258
21 #define R_AARCH64_ABS16  259
22 #define R_AARCH64_PREL64 260
23 #define R_AARCH64_PREL32 261
24 #define R_AARCH64_PREL16 262
25 
26 /* Static relocations */
27 #define R_AARCH64_MOVW_UABS_G0    263
28 #define R_AARCH64_MOVW_UABS_G0_NC 264
29 #define R_AARCH64_MOVW_UABS_G1    265
30 #define R_AARCH64_MOVW_UABS_G1_NC 266
31 #define R_AARCH64_MOVW_UABS_G2    267
32 #define R_AARCH64_MOVW_UABS_G2_NC 268
33 #define R_AARCH64_MOVW_UABS_G3    269
34 #define R_AARCH64_MOVW_SABS_G0    270
35 #define R_AARCH64_MOVW_SABS_G1    271
36 #define R_AARCH64_MOVW_SABS_G2    272
37 #define R_AARCH64_MOVW_PREL_G0    287
38 #define R_AARCH64_MOVW_PREL_G0_NC 288
39 #define R_AARCH64_MOVW_PREL_G1    289
40 #define R_AARCH64_MOVW_PREL_G1_NC 290
41 #define R_AARCH64_MOVW_PREL_G2    291
42 #define R_AARCH64_MOVW_PREL_G2_NC 292
43 #define R_AARCH64_MOVW_PREL_G3    293
44 
45 #define R_AARCH64_LD_PREL_LO19        273
46 #define R_AARCH64_ADR_PREL_LO21       274
47 #define R_AARCH64_ADR_PREL_PG_HI21    275
48 #define R_AARCH64_ADR_PREL_PG_HI21_NC 276
49 #define R_AARCH64_ADD_ABS_LO12_NC     277
50 #define R_AARCH64_LDST8_ABS_LO12_NC   278
51 #define R_AARCH64_TSTBR14             279
52 #define R_AARCH64_CONDBR19            280
53 #define R_AARCH64_JUMP26              282
54 #define R_AARCH64_CALL26              283
55 #define R_AARCH64_LDST16_ABS_LO12_NC  284
56 #define R_AARCH64_LDST32_ABS_LO12_NC  285
57 #define R_AARCH64_LDST64_ABS_LO12_NC  286
58 #define R_AARCH64_LDST128_ABS_LO12_NC 299
59 
60 /* Masks for immediate values */
61 #define AARCH64_MASK_IMM12 BIT_MASK(12)
62 #define AARCH64_MASK_IMM14 BIT_MASK(14)
63 #define AARCH64_MASK_IMM16 BIT_MASK(16)
64 #define AARCH64_MASK_IMM19 BIT_MASK(19)
65 #define AARCH64_MASK_IMM26 BIT_MASK(26)
66 
67 /* MOV instruction helper symbols */
68 #define AARCH64_MASK_MOV_OPCODE  BIT_MASK(8)
69 #define AARCH64_SHIFT_MOV_OPCODE (23)
70 #define AARCH64_SHIFT_MOV_IMM16  (5)
71 #define AARCH64_OPCODE_MOVN      (0b00100101)
72 #define AARCH64_OPCODE_MOVZ      (0b10100101)
73 
74 /* ADR instruction helper symbols */
75 #define AARCH64_MASK_ADR_IMMLO  BIT_MASK(2)
76 #define AARCH64_MASK_ADR_IMMHI  BIT_MASK(19)
77 #define AARCH64_SHIFT_ADR_IMMLO (29)
78 #define AARCH64_SHIFT_ADR_IMMHI (5)
79 #define AARCH64_ADR_IMMLO_BITS  (2)
80 
81 #define AARCH64_PAGE(expr) ((expr) & ~0xFFF)
82 
83 enum aarch64_reloc_type {
84 	AARCH64_RELOC_TYPE_NONE,
85 	AARCH64_RELOC_TYPE_ABS,
86 	AARCH64_RELOC_TYPE_PREL,
87 	AARCH64_RELOC_TYPE_PAGE,
88 };
89 
90 /**
91  * @brief Function computing a relocation (X in AArch64 ELF).
92  *
93  * @param[in] reloc_type Type of relocation operation.
94  * @param[in] loc Address of an opcode to rewrite (P in AArch64 ELF).
95  * @param[in] sym_base_addr Address of the symbol referenced by relocation (S in AArch64 ELF).
96  * @param[in] addend Addend from RELA relocation.
97  *
98  * @return Result of the relocation operation (X in AArch64 ELF)
99  */
reloc(enum aarch64_reloc_type reloc_type,uintptr_t loc,uintptr_t sym_base_addr,int64_t addend)100 static uint64_t reloc(enum aarch64_reloc_type reloc_type, uintptr_t loc, uintptr_t sym_base_addr,
101 		      int64_t addend)
102 {
103 	switch (reloc_type) {
104 	case AARCH64_RELOC_TYPE_ABS:
105 		return sym_base_addr + addend;
106 	case AARCH64_RELOC_TYPE_PREL:
107 		return sym_base_addr + addend - loc;
108 	case AARCH64_RELOC_TYPE_PAGE:
109 		return AARCH64_PAGE(sym_base_addr + addend) - AARCH64_PAGE(loc);
110 	case AARCH64_RELOC_TYPE_NONE:
111 		return 0;
112 	}
113 
114 	CODE_UNREACHABLE;
115 }
116 
117 /**
118  * @brief Handler for static data relocations.
119  *
120  * @param[in] rel Relocation data provided by ELF
121  * @param[in] reloc_type Type of relocation operation.
122  * @param[in] loc Address of an opcode to rewrite (P in AArch64 ELF).
123  * @param[in] sym_base_addr Address of the symbol referenced by relocation (S in AArch64 ELF).
124  *
125  * @retval -ERANGE Relocation value overflow
126  * @retval 0 Successful relocation
127  */
data_reloc_handler(elf_rela_t * rel,elf_word reloc_type,uintptr_t loc,uintptr_t sym_base_addr)128 static int data_reloc_handler(elf_rela_t *rel, elf_word reloc_type, uintptr_t loc,
129 			      uintptr_t sym_base_addr)
130 {
131 	int64_t x;
132 
133 	switch (reloc_type) {
134 	case R_AARCH64_ABS64:
135 		*(int64_t *)loc = reloc(AARCH64_RELOC_TYPE_ABS, loc, sym_base_addr, rel->r_addend);
136 		break;
137 
138 	case R_AARCH64_ABS32:
139 		x = reloc(AARCH64_RELOC_TYPE_ABS, loc, sym_base_addr, rel->r_addend);
140 		if (x < 0 || x > UINT32_MAX) {
141 			return -ERANGE;
142 		}
143 		*(uint32_t *)loc = (uint32_t)x;
144 		break;
145 
146 	case R_AARCH64_ABS16:
147 		x = reloc(AARCH64_RELOC_TYPE_ABS, loc, sym_base_addr, rel->r_addend);
148 		if (x < 0 || x > UINT16_MAX) {
149 			return -ERANGE;
150 		}
151 		*(uint16_t *)loc = (uint16_t)x;
152 		break;
153 
154 	case R_AARCH64_PREL64:
155 		*(int64_t *)loc = reloc(AARCH64_RELOC_TYPE_PREL, loc, sym_base_addr, rel->r_addend);
156 		break;
157 
158 	case R_AARCH64_PREL32:
159 		x = reloc(AARCH64_RELOC_TYPE_PREL, loc, sym_base_addr, rel->r_addend);
160 		if (x < INT32_MIN || x > INT32_MAX) {
161 			return -ERANGE;
162 		}
163 		*(int32_t *)loc = (int32_t)x;
164 		break;
165 
166 	case R_AARCH64_PREL16:
167 		x = reloc(AARCH64_RELOC_TYPE_PREL, loc, sym_base_addr, rel->r_addend);
168 		if (x < INT16_MIN || x > INT16_MAX) {
169 			return -ERANGE;
170 		}
171 		*(int16_t *)loc = (int16_t)x;
172 		break;
173 
174 	default:
175 		CODE_UNREACHABLE;
176 	}
177 
178 	return 0;
179 }
180 
181 /**
182  * @brief Handler for relocations using MOV* instructions.
183  *
184  * @param[in] rel Relocation data provided by ELF
185  * @param[in] reloc_type Type of relocation operation.
186  * @param[in] loc Address of an opcode to rewrite (P in AArch64 ELF).
187  * @param[in] sym_base_addr Address of the symbol referenced by relocation (S in AArch64 ELF).
188  *
189  * @retval -ERANGE Relocation value overflow
190  * @retval 0 Successful relocation
191  */
movw_reloc_handler(elf_rela_t * rel,elf_word reloc_type,uintptr_t loc,uintptr_t sym_base_addr)192 static int movw_reloc_handler(elf_rela_t *rel, elf_word reloc_type, uintptr_t loc,
193 			      uintptr_t sym_base_addr)
194 {
195 	int64_t x;
196 	uint32_t imm;
197 	int lsb = 0; /* LSB of X to be used */
198 	bool is_movnz = false;
199 	enum aarch64_reloc_type type = AARCH64_RELOC_TYPE_ABS;
200 	uint32_t opcode = sys_le32_to_cpu(*(uint32_t *)loc);
201 
202 	switch (reloc_type) {
203 	case R_AARCH64_MOVW_SABS_G0:
204 		is_movnz = true;
205 	case R_AARCH64_MOVW_UABS_G0_NC:
206 	case R_AARCH64_MOVW_UABS_G0:
207 		break;
208 
209 	case R_AARCH64_MOVW_SABS_G1:
210 		is_movnz = true;
211 	case R_AARCH64_MOVW_UABS_G1_NC:
212 	case R_AARCH64_MOVW_UABS_G1:
213 		lsb = 16;
214 		break;
215 
216 	case R_AARCH64_MOVW_SABS_G2:
217 		is_movnz = true;
218 	case R_AARCH64_MOVW_UABS_G2_NC:
219 	case R_AARCH64_MOVW_UABS_G2:
220 		lsb = 32;
221 		break;
222 
223 	case R_AARCH64_MOVW_UABS_G3:
224 		lsb = 48;
225 		break;
226 
227 	case R_AARCH64_MOVW_PREL_G0:
228 		is_movnz = true;
229 	case R_AARCH64_MOVW_PREL_G0_NC:
230 		type = AARCH64_RELOC_TYPE_PREL;
231 		break;
232 
233 	case R_AARCH64_MOVW_PREL_G1:
234 		is_movnz = true;
235 	case R_AARCH64_MOVW_PREL_G1_NC:
236 		type = AARCH64_RELOC_TYPE_PREL;
237 		lsb = 16;
238 		break;
239 
240 	case R_AARCH64_MOVW_PREL_G2:
241 		is_movnz = true;
242 	case R_AARCH64_MOVW_PREL_G2_NC:
243 		type = AARCH64_RELOC_TYPE_PREL;
244 		lsb = 32;
245 		break;
246 
247 	case R_AARCH64_MOVW_PREL_G3:
248 		is_movnz = true;
249 		type = AARCH64_RELOC_TYPE_PREL;
250 		lsb = 48;
251 		break;
252 
253 	default:
254 		CODE_UNREACHABLE;
255 	}
256 
257 	x = reloc(type, loc, sym_base_addr, rel->r_addend);
258 	imm = x >> lsb;
259 
260 	/* Manipulate opcode for signed relocations. Result depends on sign of immediate value. */
261 	if (is_movnz) {
262 		opcode &= ~(AARCH64_MASK_MOV_OPCODE << AARCH64_SHIFT_MOV_OPCODE);
263 
264 		if (x >= 0) {
265 			opcode |= (AARCH64_OPCODE_MOVN << AARCH64_SHIFT_MOV_OPCODE);
266 		} else {
267 			opcode |= (AARCH64_OPCODE_MOVZ << AARCH64_SHIFT_MOV_OPCODE);
268 			/* Need to invert immediate value for MOVZ. */
269 			imm = ~imm;
270 		}
271 	}
272 
273 	opcode &= ~(AARCH64_MASK_IMM16 << AARCH64_SHIFT_MOV_IMM16);
274 	opcode |= (imm & AARCH64_MASK_IMM16) << AARCH64_SHIFT_MOV_IMM16;
275 
276 	*(uint32_t *)loc = sys_cpu_to_le32(opcode);
277 
278 	if (imm > UINT16_MAX) {
279 		return -ERANGE;
280 	}
281 
282 	return 0;
283 }
284 
285 /**
286  * @brief Handler for static relocations except these related to MOV* instructions.
287  *
288  * @param[in] rel Relocation data provided by ELF
289  * @param[in] reloc_type Type of relocation operation.
290  * @param[in] loc Address of an opcode to rewrite (P in AArch64 ELF).
291  * @param[in] sym_base_addr Address of the symbol referenced by relocation (S in AArch64 ELF).
292  *
293  * @retval -ERANGE Relocation value overflow
294  * @retval 0 Successful relocation
295  */
imm_reloc_handler(elf_rela_t * rel,elf_word reloc_type,uintptr_t loc,uintptr_t sym_base_addr)296 static int imm_reloc_handler(elf_rela_t *rel, elf_word reloc_type, uintptr_t loc,
297 			     uintptr_t sym_base_addr)
298 {
299 	int lsb = 2;    /* LSB of X to be used */
300 	int len;        /* bit length of immediate value */
301 	int shift = 10; /* shift of the immediate in instruction encoding */
302 	uint64_t imm;
303 	uint32_t bitmask = AARCH64_MASK_IMM12;
304 	int64_t x;
305 	bool is_adr = false;
306 	enum aarch64_reloc_type type = AARCH64_RELOC_TYPE_ABS;
307 	uint32_t opcode = sys_le32_to_cpu(*(uint32_t *)loc);
308 
309 	switch (reloc_type) {
310 	case R_AARCH64_ADD_ABS_LO12_NC:
311 	case R_AARCH64_LDST8_ABS_LO12_NC:
312 		lsb = 0;
313 		len = 12;
314 		break;
315 
316 	case R_AARCH64_LDST16_ABS_LO12_NC:
317 		lsb = 1;
318 		len = 11;
319 		break;
320 
321 	case R_AARCH64_LDST32_ABS_LO12_NC:
322 		len = 10;
323 		break;
324 
325 	case R_AARCH64_LDST64_ABS_LO12_NC:
326 		lsb = 3;
327 		len = 9;
328 		break;
329 
330 	case R_AARCH64_LDST128_ABS_LO12_NC:
331 		lsb = 4;
332 		len = 8;
333 		break;
334 
335 	case R_AARCH64_LD_PREL_LO19:
336 	case R_AARCH64_CONDBR19:
337 		type = AARCH64_RELOC_TYPE_PREL;
338 		bitmask = AARCH64_MASK_IMM19;
339 		shift = 5;
340 		len = 19;
341 		break;
342 
343 	case R_AARCH64_ADR_PREL_LO21:
344 		type = AARCH64_RELOC_TYPE_PREL;
345 		is_adr = true;
346 		lsb = 0;
347 		len = 21;
348 		break;
349 
350 	case R_AARCH64_TSTBR14:
351 		type = AARCH64_RELOC_TYPE_PREL;
352 		bitmask = AARCH64_MASK_IMM14;
353 		shift = 5;
354 		len = 14;
355 		break;
356 
357 	case R_AARCH64_ADR_PREL_PG_HI21_NC:
358 	case R_AARCH64_ADR_PREL_PG_HI21:
359 		type = AARCH64_RELOC_TYPE_PAGE;
360 		is_adr = true;
361 		lsb = 12;
362 		len = 21;
363 		break;
364 
365 	case R_AARCH64_CALL26:
366 	case R_AARCH64_JUMP26:
367 		type = AARCH64_RELOC_TYPE_PREL;
368 		bitmask = AARCH64_MASK_IMM26;
369 		shift = 0;
370 		len = 26;
371 		break;
372 
373 	default:
374 		CODE_UNREACHABLE;
375 	}
376 
377 	x = reloc(type, loc, sym_base_addr, rel->r_addend);
378 	x >>= lsb;
379 
380 	imm = x & BIT_MASK(len);
381 
382 	/* ADR instruction has immediate value split into two fields. */
383 	if (is_adr) {
384 		uint32_t immlo, immhi;
385 
386 		immlo = (imm & AARCH64_MASK_ADR_IMMLO) << AARCH64_SHIFT_ADR_IMMLO;
387 		imm >>= AARCH64_ADR_IMMLO_BITS;
388 		immhi = (imm & AARCH64_MASK_ADR_IMMHI) << AARCH64_SHIFT_ADR_IMMHI;
389 		imm = immlo | immhi;
390 
391 		shift = 0;
392 		bitmask = ((AARCH64_MASK_ADR_IMMLO << AARCH64_SHIFT_ADR_IMMLO) |
393 			   (AARCH64_MASK_ADR_IMMHI << AARCH64_SHIFT_ADR_IMMHI));
394 	}
395 
396 	opcode &= ~(bitmask << shift);
397 	opcode |= (imm & bitmask) << shift;
398 
399 	*(uint32_t *)loc = sys_cpu_to_le32(opcode);
400 
401 	/* Mask X sign bit and upper bits. */
402 	x = (int64_t)(x & ~BIT_MASK(len - 1)) >> (len - 1);
403 
404 	/* Incrementing X will either overflow and set it to 0 or
405 	 * set it 1. Any other case indicates that there was an overflow in relocation.
406 	 */
407 	if ((int64_t)x++ > 1) {
408 		return -ERANGE;
409 	}
410 
411 	return 0;
412 }
413 
414 /**
415  * @brief Architecture specific function for relocating partially linked (static) elf
416  *
417  * Elf files contain a series of relocations described in a section. These relocation
418  * instructions are architecture specific and each architecture supporting extensions
419  * must implement this.
420  *
421  * The relocation codes for arm64 are well documented
422  * https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst#relocation
423  *
424  * @param[in] rel Relocation data provided by ELF
425  * @param[in] loc Address of an opcode to rewrite (P in AArch64 ELF)
426  * @param[in] sym_base_addr Address of the symbol referenced by relocation (S in AArch64 ELF)
427  * @param[in] sym_name Name of symbol referenced by relocation
428  * @param[in] load_bias `.text` load address
429  * @retval 0 Success
430  * @retval -ENOTSUP Unsupported relocation
431  * @retval -ENOEXEC Invalid relocation
432  */
arch_elf_relocate(elf_rela_t * rel,uintptr_t loc,uintptr_t sym_base_addr,const char * sym_name,uintptr_t load_bias)433 int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc, uintptr_t sym_base_addr, const char *sym_name,
434 		      uintptr_t load_bias)
435 {
436 	int ret = 0;
437 	bool overflow_check = true;
438 	elf_word reloc_type = ELF_R_TYPE(rel->r_info);
439 
440 	switch (reloc_type) {
441 	case R_ARM_NONE:
442 	case R_AARCH64_NONE:
443 		overflow_check = false;
444 		break;
445 
446 	case R_AARCH64_ABS64:
447 	case R_AARCH64_PREL64:
448 		overflow_check = false;
449 	case R_AARCH64_ABS16:
450 	case R_AARCH64_ABS32:
451 	case R_AARCH64_PREL16:
452 	case R_AARCH64_PREL32:
453 		ret = data_reloc_handler(rel, reloc_type, loc, sym_base_addr);
454 		break;
455 
456 	case R_AARCH64_MOVW_UABS_G0_NC:
457 	case R_AARCH64_MOVW_UABS_G1_NC:
458 	case R_AARCH64_MOVW_UABS_G2_NC:
459 	case R_AARCH64_MOVW_UABS_G3:
460 	case R_AARCH64_MOVW_PREL_G0_NC:
461 	case R_AARCH64_MOVW_PREL_G1_NC:
462 	case R_AARCH64_MOVW_PREL_G2_NC:
463 	case R_AARCH64_MOVW_PREL_G3:
464 		overflow_check = false;
465 	case R_AARCH64_MOVW_UABS_G0:
466 	case R_AARCH64_MOVW_UABS_G1:
467 	case R_AARCH64_MOVW_UABS_G2:
468 	case R_AARCH64_MOVW_SABS_G0:
469 	case R_AARCH64_MOVW_SABS_G1:
470 	case R_AARCH64_MOVW_SABS_G2:
471 	case R_AARCH64_MOVW_PREL_G0:
472 	case R_AARCH64_MOVW_PREL_G1:
473 	case R_AARCH64_MOVW_PREL_G2:
474 		ret = movw_reloc_handler(rel, reloc_type, loc, sym_base_addr);
475 		break;
476 
477 	case R_AARCH64_ADD_ABS_LO12_NC:
478 	case R_AARCH64_LDST8_ABS_LO12_NC:
479 	case R_AARCH64_LDST16_ABS_LO12_NC:
480 	case R_AARCH64_LDST32_ABS_LO12_NC:
481 	case R_AARCH64_LDST64_ABS_LO12_NC:
482 	case R_AARCH64_LDST128_ABS_LO12_NC:
483 		overflow_check = false;
484 	case R_AARCH64_LD_PREL_LO19:
485 	case R_AARCH64_ADR_PREL_LO21:
486 	case R_AARCH64_TSTBR14:
487 	case R_AARCH64_CONDBR19:
488 		ret = imm_reloc_handler(rel, reloc_type, loc, sym_base_addr);
489 		break;
490 
491 	case R_AARCH64_ADR_PREL_PG_HI21_NC:
492 		overflow_check = false;
493 	case R_AARCH64_ADR_PREL_PG_HI21:
494 		ret = imm_reloc_handler(rel, reloc_type, loc, sym_base_addr);
495 		break;
496 
497 	case R_AARCH64_CALL26:
498 	case R_AARCH64_JUMP26:
499 		ret = imm_reloc_handler(rel, reloc_type, loc, sym_base_addr);
500 		/* TODO Handle case when address exceeds +/- 128MB */
501 		break;
502 
503 	default:
504 		LOG_ERR("unknown relocation: %llu\n", reloc_type);
505 		return -ENOEXEC;
506 	}
507 
508 	if (overflow_check && ret == -ERANGE) {
509 		LOG_ERR("sym '%s': relocation out of range (%#lx -> %#lx)\n", sym_name, loc,
510 			sym_base_addr);
511 		return -ENOEXEC;
512 	}
513 
514 	return 0;
515 }
516