1 /*
2 * Copyright (c) 2023 Intel Corporation
3 * Copyright (c) 2024 Arduino SA
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/sys/util.h>
9 #include <zephyr/llext/elf.h>
10 #include <zephyr/llext/loader.h>
11 #include <zephyr/llext/llext.h>
12 #include <zephyr/llext/llext_internal.h>
13 #include <zephyr/kernel.h>
14 #include <zephyr/cache.h>
15
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL);
18
19 #include <string.h>
20
21 #include "llext_priv.h"
22
23 #ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID
24 #define SYM_NAME_OR_SLID(name, slid) ((const char *) slid)
25 #else
26 #define SYM_NAME_OR_SLID(name, slid) name
27 #endif
28
arch_elf_relocate(struct llext_loader * ldr,struct llext * ext,elf_rela_t * rel,const elf_shdr_t * shdr)29 __weak int arch_elf_relocate(struct llext_loader *ldr, struct llext *ext, elf_rela_t *rel,
30 const elf_shdr_t *shdr)
31 {
32 return -ENOTSUP;
33 }
34
arch_elf_relocate_local(struct llext_loader * ldr,struct llext * ext,const elf_rela_t * rel,const elf_sym_t * sym,uint8_t * rel_addr,const struct llext_load_param * ldr_parm)35 __weak void arch_elf_relocate_local(struct llext_loader *ldr, struct llext *ext,
36 const elf_rela_t *rel, const elf_sym_t *sym, uint8_t *rel_addr,
37 const struct llext_load_param *ldr_parm)
38 {
39 }
40
arch_elf_relocate_global(struct llext_loader * ldr,struct llext * ext,const elf_rela_t * rel,const elf_sym_t * sym,uint8_t * rel_addr,const void * link_addr)41 __weak void arch_elf_relocate_global(struct llext_loader *ldr, struct llext *ext,
42 const elf_rela_t *rel, const elf_sym_t *sym, uint8_t *rel_addr,
43 const void *link_addr)
44 {
45 }
46
47 /*
48 * Find the memory region containing the supplied offset and return the
49 * corresponding file offset
50 */
llext_file_offset(struct llext_loader * ldr,uintptr_t offset)51 ssize_t llext_file_offset(struct llext_loader *ldr, uintptr_t offset)
52 {
53 unsigned int i;
54
55 for (i = 0; i < LLEXT_MEM_COUNT; i++) {
56 if (ldr->sects[i].sh_addr <= offset &&
57 ldr->sects[i].sh_addr + ldr->sects[i].sh_size > offset) {
58 return offset - ldr->sects[i].sh_addr + ldr->sects[i].sh_offset;
59 }
60 }
61
62 return -ENOEXEC;
63 }
64
65 /*
66 * We increment use-count every time a new dependent is added, and have to
67 * decrement it again, when one is removed. Ideally we should be able to add
68 * arbitrary numbers of dependencies, but using lists for this doesn't work,
69 * because multiple extensions can have common dependencies. Dynamically
70 * allocating dependency entries would be too wasteful. In this initial
71 * implementation we use an array of dependencies, if at some point we run out
72 * of array entries, we'll implement re-allocation.
73 * We add dependencies incrementally as we discover them, but we only ever
74 * expect them to be removed all at once, when their user is removed. So the
75 * dependency array is always "dense" - it cannot have NULL entries between
76 * valid ones.
77 */
llext_dependency_add(struct llext * ext,struct llext * dependency)78 static int llext_dependency_add(struct llext *ext, struct llext *dependency)
79 {
80 unsigned int i;
81
82 for (i = 0; i < ARRAY_SIZE(ext->dependency); i++) {
83 if (ext->dependency[i] == dependency) {
84 return 0;
85 }
86
87 if (!ext->dependency[i]) {
88 ext->dependency[i] = dependency;
89 dependency->use_count++;
90
91 return 0;
92 }
93 }
94
95 return -ENOENT;
96 }
97
llext_dependency_remove_all(struct llext * ext)98 void llext_dependency_remove_all(struct llext *ext)
99 {
100 unsigned int i;
101
102 for (i = 0; i < ARRAY_SIZE(ext->dependency) && ext->dependency[i]; i++) {
103 /*
104 * The use-count of dependencies is tightly bound to dependent's
105 * life cycle, so it shouldn't underrun.
106 */
107 ext->dependency[i]->use_count--;
108 __ASSERT(ext->dependency[i]->use_count, "LLEXT dependency use-count underrun!");
109 /* No need to NULL-ify the pointer - ext is freed after this */
110 }
111 }
112
113 struct llext_extension_sym {
114 struct llext *ext;
115 const char *sym;
116 const void *addr;
117 };
118
llext_find_extension_sym_iterate(struct llext * ext,void * arg)119 static int llext_find_extension_sym_iterate(struct llext *ext, void *arg)
120 {
121 struct llext_extension_sym *se = arg;
122 const void *addr = llext_find_sym(&ext->exp_tab, se->sym);
123
124 if (addr) {
125 se->addr = addr;
126 se->ext = ext;
127 return 1;
128 }
129
130 return 0;
131 }
132
llext_find_extension_sym(const char * sym_name,struct llext ** ext)133 static const void *llext_find_extension_sym(const char *sym_name, struct llext **ext)
134 {
135 struct llext_extension_sym se = {.sym = sym_name};
136
137 llext_iterate(llext_find_extension_sym_iterate, &se);
138 if (ext) {
139 *ext = se.ext;
140 }
141
142 return se.addr;
143 }
144
145 /*
146 * Read the symbol entry corresponding to a relocation from the binary.
147 */
llext_read_symbol(struct llext_loader * ldr,struct llext * ext,const elf_rela_t * rel,elf_sym_t * sym)148 int llext_read_symbol(struct llext_loader *ldr, struct llext *ext, const elf_rela_t *rel,
149 elf_sym_t *sym)
150 {
151 int ret;
152
153 ret = llext_seek(ldr, ldr->sects[LLEXT_MEM_SYMTAB].sh_offset
154 + ELF_R_SYM(rel->r_info) * sizeof(elf_sym_t));
155 if (ret != 0) {
156 return ret;
157 }
158
159 ret = llext_read(ldr, sym, sizeof(elf_sym_t));
160
161 return ret;
162 }
163
164 /*
165 * Determine address of a symbol.
166 */
llext_lookup_symbol(struct llext_loader * ldr,struct llext * ext,uintptr_t * link_addr,const elf_rela_t * rel,const elf_sym_t * sym,const char * name,const elf_shdr_t * shdr)167 int llext_lookup_symbol(struct llext_loader *ldr, struct llext *ext, uintptr_t *link_addr,
168 const elf_rela_t *rel, const elf_sym_t *sym, const char *name,
169 const elf_shdr_t *shdr)
170 {
171 if (ELF_R_SYM(rel->r_info) == 0) {
172 /*
173 * no symbol
174 * example: R_ARM_V4BX relocation, R_ARM_RELATIVE
175 */
176 *link_addr = 0;
177 } else if (sym->st_shndx == SHN_UNDEF) {
178 /* If symbol is undefined, then we need to look it up */
179 *link_addr = (uintptr_t)llext_find_sym(NULL, SYM_NAME_OR_SLID(name, sym->st_value));
180
181 if (*link_addr == 0) {
182 /* Try loaded tables */
183 struct llext *dep;
184
185 *link_addr = (uintptr_t)llext_find_extension_sym(name, &dep);
186 if (*link_addr) {
187 llext_dependency_add(ext, dep);
188 }
189 }
190
191 if (*link_addr == 0) {
192 LOG_ERR("Undefined symbol with no entry in "
193 "symbol table %s, offset %zd, link section %d",
194 name, (size_t)rel->r_offset, shdr->sh_link);
195 return -ENODATA;
196 }
197
198 LOG_INF("found symbol %s at %#lx", name, *link_addr);
199 } else if (sym->st_shndx == SHN_ABS) {
200 /* Absolute symbol */
201 *link_addr = sym->st_value;
202 } else if ((sym->st_shndx < ldr->hdr.e_shnum) &&
203 !IN_RANGE(sym->st_shndx, SHN_LORESERVE, SHN_HIRESERVE)) {
204 /* This check rejects all relocations whose target symbol has a section index higher
205 * than the maximum possible in this ELF file, or belongs in the reserved range:
206 * they will be caught by the `else` below and cause an error to be returned. This
207 * aborts the LLEXT's loading and prevents execution of improperly relocated code,
208 * which is dangerous.
209 *
210 * Note that the unsupported SHN_COMMON section is rejected as part of this check.
211 * Also note that SHN_ABS would be rejected as well, but we want to handle it
212 * properly: for this reason, this check must come AFTER handling the case where the
213 * symbol's section index is SHN_ABS!
214 *
215 *
216 * For regular symbols, the link address is obtained by adding st_value to the start
217 * address of the section in which the target symbol resides.
218 */
219 *link_addr =
220 (uintptr_t)llext_loaded_sect_ptr(ldr, ext, sym->st_shndx) + sym->st_value;
221 } else {
222 LOG_ERR("cannot apply relocation: "
223 "target symbol has unexpected section index %d (%#x)",
224 sym->st_shndx, sym->st_shndx);
225 return -ENOEXEC;
226 }
227
228 return 0;
229 }
230
llext_link_plt(struct llext_loader * ldr,struct llext * ext,elf_shdr_t * shdr,const struct llext_load_param * ldr_parm,elf_shdr_t * tgt)231 static void llext_link_plt(struct llext_loader *ldr, struct llext *ext, elf_shdr_t *shdr,
232 const struct llext_load_param *ldr_parm, elf_shdr_t *tgt)
233 {
234 unsigned int sh_cnt = shdr->sh_size / shdr->sh_entsize;
235 /*
236 * CPU address where the .text section is stored, we use .text just as a
237 * reference point
238 */
239 uint8_t *text = ext->mem[LLEXT_MEM_TEXT];
240
241 LOG_DBG("Found %p in PLT %u size %zu cnt %u text %p",
242 (void *)llext_section_name(ldr, ext, shdr),
243 shdr->sh_type, (size_t)shdr->sh_entsize, sh_cnt, (void *)text);
244
245 const elf_shdr_t *sym_shdr = ldr->sects + LLEXT_MEM_SYMTAB;
246 unsigned int sym_cnt = sym_shdr->sh_size / sym_shdr->sh_entsize;
247
248 for (unsigned int i = 0; i < sh_cnt; i++) {
249 elf_rela_t rela;
250
251 int ret = llext_seek(ldr, shdr->sh_offset + i * shdr->sh_entsize);
252
253 if (!ret) {
254 ret = llext_read(ldr, &rela, sizeof(rela));
255 }
256
257 if (ret != 0) {
258 LOG_ERR("PLT: failed to read RELA #%u, trying to continue", i);
259 continue;
260 }
261
262 /* Index in the symbol table */
263 unsigned int j = ELF_R_SYM(rela.r_info);
264
265 if (j >= sym_cnt) {
266 LOG_WRN("PLT: idx %u >= %u", j, sym_cnt);
267 continue;
268 }
269
270 elf_sym_t sym;
271
272 ret = llext_seek(ldr, sym_shdr->sh_offset + j * sizeof(elf_sym_t));
273 if (!ret) {
274 ret = llext_read(ldr, &sym, sizeof(sym));
275 }
276
277 if (ret != 0) {
278 LOG_ERR("PLT: failed to read symbol table #%u RELA #%u, trying to continue",
279 j, i);
280 continue;
281 }
282
283 uint32_t stt = ELF_ST_TYPE(sym.st_info);
284
285 if (stt != STT_FUNC &&
286 stt != STT_SECTION &&
287 stt != STT_OBJECT &&
288 (stt != STT_NOTYPE || sym.st_shndx != SHN_UNDEF)) {
289 continue;
290 }
291
292 const char *name = llext_symbol_name(ldr, ext, &sym);
293
294 /*
295 * Both r_offset and sh_addr are addresses for which the extension
296 * has been built.
297 *
298 * NOTE: The calculations below assumes offsets from the
299 * beginning of the .text section in the ELF file can be
300 * applied to the memory location of mem[LLEXT_MEM_TEXT].
301 *
302 * This is valid only when CONFIG_LLEXT_STORAGE_WRITABLE=y
303 * and peek() is usable on the source ELF file.
304 */
305 uint8_t *rel_addr = (uint8_t *)ext->mem[LLEXT_MEM_TEXT] -
306 ldr->sects[LLEXT_MEM_TEXT].sh_offset;
307
308 if (tgt) {
309 /* Relocatable / partially linked ELF. */
310 rel_addr += rela.r_offset + tgt->sh_offset;
311 } else {
312 /* Shared / dynamically linked ELF */
313 ssize_t offset = llext_file_offset(ldr, rela.r_offset);
314
315 if (offset < 0) {
316 LOG_ERR("Offset %#zx not found in ELF, trying to continue",
317 (size_t)rela.r_offset);
318 continue;
319 }
320
321 rel_addr += offset;
322 }
323
324 uint32_t stb = ELF_ST_BIND(sym.st_info);
325 const void *link_addr;
326
327 switch (stb) {
328 case STB_GLOBAL:
329 /* First try the global symbol table */
330 link_addr = llext_find_sym(NULL,
331 SYM_NAME_OR_SLID(name, sym.st_value));
332
333 if (!link_addr) {
334 /* Next try internal tables */
335 link_addr = llext_find_sym(&ext->sym_tab, name);
336 }
337
338 if (!link_addr) {
339 /* Finally try any loaded tables */
340 struct llext *dep;
341
342 link_addr = llext_find_extension_sym(name, &dep);
343 if (link_addr) {
344 llext_dependency_add(ext, dep);
345 }
346 }
347
348 if (!link_addr) {
349 LOG_WRN("PLT: cannot find idx %u name %s", j, name);
350 continue;
351 }
352
353 /* Resolve the symbol */
354 arch_elf_relocate_global(ldr, ext, &rela, &sym, rel_addr, link_addr);
355 break;
356 case STB_LOCAL:
357 arch_elf_relocate_local(ldr, ext, &rela, &sym, rel_addr, ldr_parm);
358 }
359
360 LOG_DBG("symbol %s relocation @%p r-offset %#zx .text offset %#zx stb %u",
361 name, (void *)rel_addr,
362 (size_t)rela.r_offset, (size_t)ldr->sects[LLEXT_MEM_TEXT].sh_offset, stb);
363 }
364 }
365
llext_link(struct llext_loader * ldr,struct llext * ext,const struct llext_load_param * ldr_parm)366 int llext_link(struct llext_loader *ldr, struct llext *ext, const struct llext_load_param *ldr_parm)
367 {
368 uintptr_t sect_base = 0;
369 elf_rela_t rel;
370 elf_word rel_cnt = 0;
371 const char *name;
372 int i, ret;
373
374 for (i = 0; i < ext->sect_cnt; ++i) {
375 elf_shdr_t *shdr = ext->sect_hdrs + i;
376
377 /* find proper relocation sections */
378 switch (shdr->sh_type) {
379 case SHT_REL:
380 if (shdr->sh_entsize != sizeof(elf_rel_t)) {
381 LOG_ERR("Invalid entry size %zd for SHT_REL section %d",
382 (size_t)shdr->sh_entsize, i);
383 return -ENOEXEC;
384 }
385 break;
386 case SHT_RELA:
387 if (IS_ENABLED(CONFIG_ARM)) {
388 LOG_ERR("Found unsupported SHT_RELA section %d", i);
389 return -ENOTSUP;
390 }
391 if (shdr->sh_entsize != sizeof(elf_rela_t)) {
392 LOG_ERR("Invalid entry size %zd for SHT_RELA section %d",
393 (size_t)shdr->sh_entsize, i);
394 return -ENOEXEC;
395 }
396 break;
397 default:
398 /* ignore this section */
399 continue;
400 }
401
402 if (shdr->sh_info >= ext->sect_cnt ||
403 shdr->sh_size % shdr->sh_entsize != 0) {
404 LOG_ERR("Sanity checks failed for section %d "
405 "(info %zd, size %zd, entsize %zd)", i,
406 (size_t)shdr->sh_info,
407 (size_t)shdr->sh_size,
408 (size_t)shdr->sh_entsize);
409 return -ENOEXEC;
410 }
411
412 rel_cnt = shdr->sh_size / shdr->sh_entsize;
413
414 name = llext_section_name(ldr, ext, shdr);
415
416 /*
417 * FIXME: The Xtensa port is currently using a different way of
418 * handling relocations that ultimately results in separate
419 * arch-specific code paths. This code should be merged with
420 * the logic below once the differences are resolved.
421 */
422 if (IS_ENABLED(CONFIG_XTENSA)) {
423 elf_shdr_t *tgt;
424
425 if (strcmp(name, ".rela.plt") == 0 ||
426 strcmp(name, ".rela.dyn") == 0) {
427 tgt = NULL;
428 } else {
429 /*
430 * Entries in .rel.X and .rela.X sections describe references in
431 * section .X to local or global symbols. They point to entries
432 * in the symbol table, describing respective symbols
433 */
434 tgt = ext->sect_hdrs + shdr->sh_info;
435 }
436
437 llext_link_plt(ldr, ext, shdr, ldr_parm, tgt);
438 continue;
439 }
440
441 if (!(ext->sect_hdrs[shdr->sh_info].sh_flags & SHF_ALLOC)) {
442 /* ignore relocations acting on volatile (debug) sections */
443 continue;
444 }
445
446 LOG_DBG("relocation section %s (%d) acting on section %d has %zd relocations",
447 name, i, shdr->sh_info, (size_t)rel_cnt);
448
449 enum llext_mem mem_idx = ldr->sect_map[shdr->sh_info].mem_idx;
450
451 if (mem_idx == LLEXT_MEM_COUNT) {
452 LOG_ERR("Section %d not loaded in any memory region", shdr->sh_info);
453 return -ENOEXEC;
454 }
455
456 sect_base = (uintptr_t) llext_loaded_sect_ptr(ldr, ext, shdr->sh_info);
457
458 for (int j = 0; j < rel_cnt; j++) {
459 /* get each relocation entry */
460 ret = llext_seek(ldr, shdr->sh_offset + j * shdr->sh_entsize);
461 if (ret != 0) {
462 return ret;
463 }
464
465 ret = llext_read(ldr, &rel, shdr->sh_entsize);
466 if (ret != 0) {
467 return ret;
468 }
469
470 #ifdef CONFIG_LLEXT_LOG_LEVEL
471 if (CONFIG_LLEXT_LOG_LEVEL >= LOG_LEVEL_INF) {
472 uintptr_t link_addr;
473 uintptr_t op_loc =
474 llext_get_reloc_instruction_location(ldr, ext,
475 shdr->sh_info,
476 &rel);
477 elf_sym_t sym;
478
479 ret = llext_read_symbol(ldr, ext, &rel, &sym);
480
481 if (ret != 0) {
482 return ret;
483 }
484
485 name = llext_symbol_name(ldr, ext, &sym);
486
487 ret = llext_lookup_symbol(ldr, ext, &link_addr, &rel, &sym, name,
488 shdr);
489
490 if (ret != 0) {
491 LOG_ERR("Could not find symbol %s!", name);
492 return ret;
493 }
494
495 LOG_DBG("relocation %d:%d info %#zx (type %zd, sym %zd) offset %zd"
496 " sym_name %s sym_type %d sym_bind %d sym_ndx %d",
497 i, j, (size_t)rel.r_info, (size_t)ELF_R_TYPE(rel.r_info),
498 (size_t)ELF_R_SYM(rel.r_info), (size_t)rel.r_offset,
499 name, ELF_ST_TYPE(sym.st_info),
500 ELF_ST_BIND(sym.st_info), sym.st_shndx);
501
502 LOG_INF("writing relocation type %d at %#lx with symbol %s (%#lx)",
503 (int)ELF_R_TYPE(rel.r_info), op_loc, name, link_addr);
504 }
505 #endif /* CONFIG_LLEXT_LOG_LEVEL */
506
507
508 /* relocation */
509 ret = arch_elf_relocate(ldr, ext, &rel, shdr);
510 if (ret != 0) {
511 return ret;
512 }
513 }
514 }
515
516 #ifdef CONFIG_CACHE_MANAGEMENT
517 /* Make sure changes to memory regions are flushed to RAM */
518 for (i = 0; i < LLEXT_MEM_COUNT; ++i) {
519 if (ext->mem[i]) {
520 sys_cache_data_flush_range(ext->mem[i], ext->mem_size[i]);
521 sys_cache_instr_invd_range(ext->mem[i], ext->mem_size[i]);
522 }
523 }
524
525 /* Detached section caches should be synchronized in place */
526 if (ldr_parm->section_detached) {
527 for (i = 0; i < ext->sect_cnt; ++i) {
528 elf_shdr_t *shdr = ext->sect_hdrs + i;
529
530 if (ldr_parm->section_detached(shdr)) {
531 void *base = llext_peek(ldr, shdr->sh_offset);
532
533 sys_cache_data_flush_range(base, shdr->sh_size);
534 sys_cache_instr_invd_range(base, shdr->sh_size);
535 }
536 }
537 }
538 #endif
539
540 return 0;
541 }
542