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