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