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(elf_rela_t * rel,uintptr_t loc,uintptr_t sym_base_addr,const char * sym_name,uintptr_t load_bias)29 __weak int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc,
30 uintptr_t sym_base_addr, const char *sym_name, uintptr_t load_bias)
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,size_t offset)51 static size_t llext_file_offset(struct llext_loader *ldr, size_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 offset;
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
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)145 static void llext_link_plt(struct llext_loader *ldr, struct llext *ext, elf_shdr_t *shdr,
146 const struct llext_load_param *ldr_parm, elf_shdr_t *tgt)
147 {
148 unsigned int sh_cnt = shdr->sh_size / shdr->sh_entsize;
149 /*
150 * CPU address where the .text section is stored, we use .text just as a
151 * reference point
152 */
153 uint8_t *text = ext->mem[LLEXT_MEM_TEXT];
154
155 LOG_DBG("Found %p in PLT %u size %zu cnt %u text %p",
156 (void *)llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr->sh_name),
157 shdr->sh_type, (size_t)shdr->sh_entsize, sh_cnt, (void *)text);
158
159 const elf_shdr_t *sym_shdr = ldr->sects + LLEXT_MEM_SYMTAB;
160 unsigned int sym_cnt = sym_shdr->sh_size / sym_shdr->sh_entsize;
161
162 for (unsigned int i = 0; i < sh_cnt; i++) {
163 elf_rela_t rela;
164
165 int ret = llext_seek(ldr, shdr->sh_offset + i * shdr->sh_entsize);
166
167 if (!ret) {
168 ret = llext_read(ldr, &rela, sizeof(rela));
169 }
170
171 if (ret != 0) {
172 LOG_ERR("PLT: failed to read RELA #%u, trying to continue", i);
173 continue;
174 }
175
176 /* Index in the symbol table */
177 unsigned int j = ELF_R_SYM(rela.r_info);
178
179 if (j >= sym_cnt) {
180 LOG_WRN("PLT: idx %u >= %u", j, sym_cnt);
181 continue;
182 }
183
184 elf_sym_t sym;
185
186 ret = llext_seek(ldr, sym_shdr->sh_offset + j * sizeof(elf_sym_t));
187 if (!ret) {
188 ret = llext_read(ldr, &sym, sizeof(sym));
189 }
190
191 if (ret < 0) {
192 LOG_ERR("PLT: failed to read symbol table #%u RELA #%u, trying to continue",
193 j, i);
194 continue;
195 }
196
197 uint32_t stt = ELF_ST_TYPE(sym.st_info);
198
199 if (stt != STT_FUNC &&
200 stt != STT_SECTION &&
201 stt != STT_OBJECT &&
202 (stt != STT_NOTYPE || sym.st_shndx != SHN_UNDEF)) {
203 continue;
204 }
205
206 const char *name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name);
207
208 /*
209 * Both r_offset and sh_addr are addresses for which the extension
210 * has been built.
211 *
212 * NOTE: The calculations below assumes offsets from the
213 * beginning of the .text section in the ELF file can be
214 * applied to the memory location of mem[LLEXT_MEM_TEXT].
215 *
216 * This is valid only when CONFIG_LLEXT_STORAGE_WRITABLE=y
217 * and peek() is usable on the source ELF file.
218 */
219 uint8_t *rel_addr = (uint8_t *)ext->mem[LLEXT_MEM_TEXT] -
220 ldr->sects[LLEXT_MEM_TEXT].sh_offset;
221
222 if (tgt) {
223 /* Relocatable / partially linked ELF. */
224 rel_addr += rela.r_offset + tgt->sh_offset;
225 } else {
226 /* Shared / dynamically linked ELF */
227 rel_addr += llext_file_offset(ldr, rela.r_offset);
228 }
229
230 uint32_t stb = ELF_ST_BIND(sym.st_info);
231 const void *link_addr;
232
233 switch (stb) {
234 case STB_GLOBAL:
235 /* First try the global symbol table */
236 link_addr = llext_find_sym(NULL,
237 SYM_NAME_OR_SLID(name, sym.st_value));
238
239 if (!link_addr) {
240 /* Next try internal tables */
241 link_addr = llext_find_sym(&ext->sym_tab, name);
242 }
243
244 if (!link_addr) {
245 /* Finally try any loaded tables */
246 struct llext *dep;
247
248 link_addr = llext_find_extension_sym(name, &dep);
249 if (link_addr) {
250 llext_dependency_add(ext, dep);
251 }
252 }
253
254 if (!link_addr) {
255 LOG_WRN("PLT: cannot find idx %u name %s", j, name);
256 continue;
257 }
258
259 /* Resolve the symbol */
260 arch_elf_relocate_global(ldr, ext, &rela, &sym, rel_addr, link_addr);
261 break;
262 case STB_LOCAL:
263 arch_elf_relocate_local(ldr, ext, &rela, &sym, rel_addr, ldr_parm);
264 }
265
266 LOG_DBG("symbol %s relocation @%p r-offset %#zx .text offset %#zx stb %u",
267 name, (void *)rel_addr,
268 (size_t)rela.r_offset, (size_t)ldr->sects[LLEXT_MEM_TEXT].sh_offset, stb);
269 }
270 }
271
llext_link(struct llext_loader * ldr,struct llext * ext,const struct llext_load_param * ldr_parm)272 int llext_link(struct llext_loader *ldr, struct llext *ext, const struct llext_load_param *ldr_parm)
273 {
274 uintptr_t sect_base = 0;
275 elf_rela_t rel;
276 elf_sym_t sym;
277 elf_word rel_cnt = 0;
278 const char *name;
279 int i, ret;
280
281 for (i = 0; i < ext->sect_cnt; ++i) {
282 elf_shdr_t *shdr = ext->sect_hdrs + i;
283
284 /* find proper relocation sections */
285 switch (shdr->sh_type) {
286 case SHT_REL:
287 if (shdr->sh_entsize != sizeof(elf_rel_t)) {
288 LOG_ERR("Invalid entry size %zd for SHT_REL section %d",
289 (size_t)shdr->sh_entsize, i);
290 return -ENOEXEC;
291 }
292 break;
293 case SHT_RELA:
294 if (IS_ENABLED(CONFIG_ARM)) {
295 LOG_ERR("Found unsupported SHT_RELA section %d", i);
296 return -ENOTSUP;
297 }
298 if (shdr->sh_entsize != sizeof(elf_rela_t)) {
299 LOG_ERR("Invalid entry size %zd for SHT_RELA section %d",
300 (size_t)shdr->sh_entsize, i);
301 return -ENOEXEC;
302 }
303 break;
304 default:
305 /* ignore this section */
306 continue;
307 }
308
309 if (shdr->sh_info >= ext->sect_cnt ||
310 shdr->sh_size % shdr->sh_entsize != 0) {
311 LOG_ERR("Sanity checks failed for section %d "
312 "(info %zd, size %zd, entsize %zd)", i,
313 (size_t)shdr->sh_info,
314 (size_t)shdr->sh_size,
315 (size_t)shdr->sh_entsize);
316 return -ENOEXEC;
317 }
318
319 rel_cnt = shdr->sh_size / shdr->sh_entsize;
320
321 name = llext_string(ldr, ext, LLEXT_MEM_SHSTRTAB, shdr->sh_name);
322
323 /*
324 * FIXME: The Xtensa port is currently using a different way of
325 * handling relocations that ultimately results in separate
326 * arch-specific code paths. This code should be merged with
327 * the logic below once the differences are resolved.
328 */
329 if (IS_ENABLED(CONFIG_XTENSA)) {
330 elf_shdr_t *tgt;
331
332 if (strcmp(name, ".rela.plt") == 0 ||
333 strcmp(name, ".rela.dyn") == 0) {
334 tgt = NULL;
335 } else {
336 /*
337 * Entries in .rel.X and .rela.X sections describe references in
338 * section .X to local or global symbols. They point to entries
339 * in the symbol table, describing respective symbols
340 */
341 tgt = ext->sect_hdrs + shdr->sh_info;
342 }
343
344 llext_link_plt(ldr, ext, shdr, ldr_parm, tgt);
345 continue;
346 }
347
348 LOG_DBG("relocation section %s (%d) acting on section %d has %zd relocations",
349 name, i, shdr->sh_info, (size_t)rel_cnt);
350
351 enum llext_mem mem_idx = ldr->sect_map[shdr->sh_info].mem_idx;
352
353 if (mem_idx == LLEXT_MEM_COUNT) {
354 LOG_ERR("Section %d not loaded in any memory region", shdr->sh_info);
355 return -ENOEXEC;
356 }
357
358 sect_base = (uintptr_t)llext_loaded_sect_ptr(ldr, ext, shdr->sh_info);
359
360 for (int j = 0; j < rel_cnt; j++) {
361 /* get each relocation entry */
362 ret = llext_seek(ldr, shdr->sh_offset + j * shdr->sh_entsize);
363 if (ret != 0) {
364 return ret;
365 }
366
367 ret = llext_read(ldr, &rel, shdr->sh_entsize);
368 if (ret != 0) {
369 return ret;
370 }
371
372 /* get corresponding symbol */
373 ret = llext_seek(ldr, ldr->sects[LLEXT_MEM_SYMTAB].sh_offset
374 + ELF_R_SYM(rel.r_info) * sizeof(elf_sym_t));
375 if (ret != 0) {
376 return ret;
377 }
378
379 ret = llext_read(ldr, &sym, sizeof(elf_sym_t));
380 if (ret != 0) {
381 return ret;
382 }
383
384 name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name);
385
386 LOG_DBG("relocation %d:%d info 0x%zx (type %zd, sym %zd) offset %zd "
387 "sym_name %s sym_type %d sym_bind %d sym_ndx %d",
388 i, j, (size_t)rel.r_info, (size_t)ELF_R_TYPE(rel.r_info),
389 (size_t)ELF_R_SYM(rel.r_info), (size_t)rel.r_offset,
390 name, ELF_ST_TYPE(sym.st_info),
391 ELF_ST_BIND(sym.st_info), sym.st_shndx);
392
393 uintptr_t link_addr, op_loc;
394
395 op_loc = sect_base + rel.r_offset;
396
397 if (ELF_R_SYM(rel.r_info) == 0) {
398 /* no symbol ex: R_ARM_V4BX relocation, R_ARM_RELATIVE */
399 link_addr = 0;
400 } else if (sym.st_shndx == SHN_UNDEF) {
401 /* If symbol is undefined, then we need to look it up */
402 link_addr = (uintptr_t)llext_find_sym(NULL,
403 SYM_NAME_OR_SLID(name, sym.st_value));
404
405 if (link_addr == 0) {
406 /* Try loaded tables */
407 struct llext *dep;
408
409 link_addr = (uintptr_t)llext_find_extension_sym(name, &dep);
410 if (link_addr) {
411 llext_dependency_add(ext, dep);
412 }
413 }
414
415 if (link_addr == 0) {
416 LOG_ERR("Undefined symbol with no entry in "
417 "symbol table %s, offset %zd, link section %d",
418 name, (size_t)rel.r_offset, shdr->sh_link);
419 return -ENODATA;
420 }
421
422 LOG_INF("found symbol %s at 0x%lx", name, link_addr);
423 } else if (sym.st_shndx == SHN_ABS) {
424 /* Absolute symbol */
425 link_addr = sym.st_value;
426 } else if ((sym.st_shndx < ldr->hdr.e_shnum) &&
427 !IN_RANGE(sym.st_shndx, SHN_LORESERVE, SHN_HIRESERVE)) {
428 /* This check rejects all relocations whose target symbol
429 * has a section index higher than the maximum possible
430 * in this ELF file, or belongs in the reserved range:
431 * they will be caught by the `else` below and cause an
432 * error to be returned. This aborts the LLEXT's loading
433 * and prevents execution of improperly relocated code,
434 * which is dangerous.
435 *
436 * Note that the unsupported SHN_COMMON section is rejected
437 * as part of this check. Also note that SHN_ABS would be
438 * rejected as well, but we want to handle it properly:
439 * for this reason, this check must come AFTER handling
440 * the case where the symbol's section index is SHN_ABS!
441 *
442 *
443 * For regular symbols, the link address is obtained by
444 * adding st_value to the start address of the section
445 * in which the target symbol resides.
446 */
447 link_addr = (uintptr_t)llext_loaded_sect_ptr(ldr, ext,
448 sym.st_shndx)
449 + sym.st_value;
450 } else {
451 LOG_ERR("rela section %d, entry %d: cannot apply relocation: "
452 "target symbol has unexpected section index %d (0x%X)",
453 i, j, sym.st_shndx, sym.st_shndx);
454 return -ENOEXEC;
455 }
456
457 LOG_INF("writing relocation symbol %s type %zd sym %zd at addr 0x%lx "
458 "addr 0x%lx",
459 name, (size_t)ELF_R_TYPE(rel.r_info), (size_t)ELF_R_SYM(rel.r_info),
460 op_loc, link_addr);
461
462 /* relocation */
463 ret = arch_elf_relocate(&rel, op_loc, link_addr, name,
464 (uintptr_t)ext->mem[LLEXT_MEM_TEXT]);
465 if (ret != 0) {
466 return ret;
467 }
468 }
469 }
470
471 #ifdef CONFIG_CACHE_MANAGEMENT
472 /* Make sure changes to memory regions are flushed to RAM */
473 for (i = 0; i < LLEXT_MEM_COUNT; ++i) {
474 if (ext->mem[i]) {
475 sys_cache_data_flush_range(ext->mem[i], ext->mem_size[i]);
476 sys_cache_instr_invd_range(ext->mem[i], ext->mem_size[i]);
477 }
478 }
479
480 /* Detached section caches should be synchronized in place */
481 if (ldr_parm->section_detached) {
482 for (i = 0; i < ext->sect_cnt; ++i) {
483 elf_shdr_t *shdr = ext->sect_hdrs + i;
484
485 if (ldr_parm->section_detached(shdr)) {
486 void *base = llext_peek(ldr, shdr->sh_offset);
487
488 sys_cache_data_flush_range(base, shdr->sh_size);
489 sys_cache_instr_invd_range(base, shdr->sh_size);
490 }
491 }
492 }
493 #endif
494
495 return 0;
496 }
497