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