1 /*
2 * Copyright (c) 2023 Intel Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/ztest.h>
8 #include <zephyr/device.h>
9 #include <zephyr/kernel.h>
10 #include <zephyr/fs/fs.h>
11 #if defined(CONFIG_FILE_SYSTEM_LITTLEFS)
12 #include <zephyr/fs/littlefs.h>
13 #endif
14 #include <zephyr/llext/elf.h>
15 #include <zephyr/llext/llext.h>
16 #include <zephyr/llext/inspect.h>
17 #include <zephyr/llext/symbol.h>
18 #include <zephyr/llext/buf_loader.h>
19 #include <zephyr/llext/fs_loader.h>
20 #include <zephyr/logging/log.h>
21 #include <zephyr/storage/flash_map.h>
22 #include <zephyr/sys/libc-hooks.h>
23 #include "syscalls_ext.h"
24 #include "threads_kernel_objects_ext.h"
25
26
27 LOG_MODULE_REGISTER(test_llext);
28
29
30 #ifdef CONFIG_LLEXT_STORAGE_WRITABLE
31 #define LLEXT_CONST
32 #else
33 #define LLEXT_CONST const
34 #endif
35
36 #ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID
37 #define LLEXT_FIND_BUILTIN_SYM(symbol_name) llext_find_sym(NULL, symbol_name ## _SLID)
38
39 #ifdef CONFIG_64BIT
40 #define printk_SLID ((const char *)0x87B3105268827052ull)
41 #define z_impl_ext_syscall_fail_SLID ((const char *)0xD58BC0E7C64CD965ull)
42 #else
43 #define printk_SLID ((const char *)0x87B31052ull)
44 #define z_impl_ext_syscall_fail_SLID ((const char *)0xD58BC0E7ull)
45 #endif
46 #else
47 #define LLEXT_FIND_BUILTIN_SYM(symbol_name) llext_find_sym(NULL, # symbol_name)
48 #endif
49
50 struct llext_test {
51 const char *name;
52
53 LLEXT_CONST uint8_t *buf;
54 size_t buf_len;
55
56 bool kernel_only;
57
58 /*
59 * Optional callbacks
60 */
61
62 /* Called in kernel context before each test starts */
63 void (*test_setup)(struct llext *ext, struct k_thread *llext_thread);
64
65 /* Called in kernel context after each test completes */
66 void (*test_cleanup)(struct llext *ext);
67 };
68
69
70 K_THREAD_STACK_DEFINE(llext_stack, 1024);
71 struct k_thread llext_thread;
72
73
74 /* syscalls test */
75
z_impl_ext_syscall_ok(int a)76 int z_impl_ext_syscall_ok(int a)
77 {
78 return a + 1;
79 }
80
81 #ifdef CONFIG_USERSPACE
z_vrfy_ext_syscall_ok(int a)82 static inline int z_vrfy_ext_syscall_ok(int a)
83 {
84 return z_impl_ext_syscall_ok(a);
85 }
86 #include <zephyr/syscalls/ext_syscall_ok_mrsh.c>
87 #endif /* CONFIG_USERSPACE */
88
89
90 /* threads kernel objects test */
91
92 /* For these to be accessible from user space, they must be top-level globals
93 * in the Zephyr image. Also, macros that add objects to special linker sections,
94 * such as K_THREAD_STACK_DEFINE, do not work properly from extensions code.
95 */
96 K_SEM_DEFINE(my_sem, 1, 1);
97 EXPORT_SYMBOL(my_sem);
98 struct k_thread my_thread;
99 EXPORT_SYMBOL(my_thread);
100 K_THREAD_STACK_DEFINE(my_thread_stack, MY_THREAD_STACK_SIZE);
101 EXPORT_SYMBOL(my_thread_stack);
102
103 #ifdef CONFIG_USERSPACE
104 /* Allow the test threads to access global objects.
105 * Note: Permissions on objects used in the test by this thread are initialized
106 * even in supervisor mode, so that user mode descendant threads can inherit
107 * these permissions.
108 */
threads_objects_test_setup(struct llext *,struct k_thread * llext_thread)109 static void threads_objects_test_setup(struct llext *, struct k_thread *llext_thread)
110 {
111 k_object_access_grant(&my_sem, llext_thread);
112 k_object_access_grant(&my_thread, llext_thread);
113 k_object_access_grant(&my_thread_stack, llext_thread);
114 #if DT_HAS_CHOSEN(zephyr_console) && DT_NODE_HAS_STATUS_OKAY(DT_CHOSEN(zephyr_console))
115 k_object_access_grant(DEVICE_DT_GET(DT_CHOSEN(zephyr_console)), llext_thread);
116 #endif
117 }
118 #else
119 /* No need to set up permissions for supervisor mode */
120 #define threads_objects_test_setup NULL
121 #endif /* CONFIG_USERSPACE */
122
load_call_unload(const struct llext_test * test_case)123 void load_call_unload(const struct llext_test *test_case)
124 {
125 struct llext_buf_loader buf_loader =
126 LLEXT_BUF_LOADER(test_case->buf, test_case->buf_len);
127 struct llext_loader *loader = &buf_loader.loader;
128 struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
129 struct llext *ext = NULL;
130
131 int res = llext_load(loader, test_case->name, &ext, &ldr_parm);
132
133 zassert_ok(res, "load should succeed");
134
135 void (*test_entry_fn)() = llext_find_sym(&ext->exp_tab, "test_entry");
136
137 zassert_not_null(test_entry_fn, "test_entry should be an exported symbol");
138
139 #ifdef CONFIG_USERSPACE
140 /*
141 * Due to the number of MPU regions on some parts with MPU (USERSPACE)
142 * enabled we need to always call into the extension from a new dedicated
143 * thread to avoid running out of MPU regions on some parts.
144 *
145 * This is part dependent behavior and certainly on MMU capable parts
146 * this should not be needed! This test however is here to be generic
147 * across as many parts as possible.
148 */
149 struct k_mem_domain domain;
150
151 k_mem_domain_init(&domain, 0, NULL);
152
153 #ifdef Z_LIBC_PARTITION_EXISTS
154 k_mem_domain_add_partition(&domain, &z_libc_partition);
155 #endif
156
157 res = llext_add_domain(ext, &domain);
158 if (res == -ENOSPC) {
159 TC_PRINT("Too many memory partitions for this particular hardware\n");
160 ztest_test_skip();
161 return;
162 }
163 zassert_ok(res, "adding partitions to domain should succeed");
164
165 /* Should be runnable from newly created thread */
166 k_thread_create(&llext_thread, llext_stack,
167 K_THREAD_STACK_SIZEOF(llext_stack),
168 (k_thread_entry_t) &llext_bootstrap,
169 ext, test_entry_fn, NULL,
170 1, 0, K_FOREVER);
171
172 k_mem_domain_add_thread(&domain, &llext_thread);
173
174 if (test_case->test_setup) {
175 test_case->test_setup(ext, &llext_thread);
176 }
177
178 k_thread_start(&llext_thread);
179 k_thread_join(&llext_thread, K_FOREVER);
180
181 if (test_case->test_cleanup) {
182 test_case->test_cleanup(ext);
183 }
184
185 /* Some extensions may wish to be tried from the context
186 * of a userspace thread along with the usual supervisor context
187 * tried above.
188 */
189 if (!test_case->kernel_only) {
190 k_thread_create(&llext_thread, llext_stack,
191 K_THREAD_STACK_SIZEOF(llext_stack),
192 (k_thread_entry_t) &llext_bootstrap,
193 ext, test_entry_fn, NULL,
194 1, K_USER, K_FOREVER);
195
196 k_mem_domain_add_thread(&domain, &llext_thread);
197
198 if (test_case->test_setup) {
199 test_case->test_setup(ext, &llext_thread);
200 }
201
202 k_thread_start(&llext_thread);
203 k_thread_join(&llext_thread, K_FOREVER);
204
205 if (test_case->test_cleanup) {
206 test_case->test_cleanup(ext);
207 }
208 }
209
210 #else /* CONFIG_USERSPACE */
211 /* No userspace support: run the test only in supervisor mode, without
212 * creating a new thread.
213 */
214 if (test_case->test_setup) {
215 test_case->test_setup(ext, NULL);
216 }
217
218 #ifdef CONFIG_LLEXT_TYPE_ELF_SHAREDLIB
219 /* The ELF specification forbids shared libraries from defining init
220 * entries, so calling llext_bootstrap here would be redundant. Use
221 * this opportunity to test llext_call_fn, even though llext_bootstrap
222 * would have behaved simlarly.
223 */
224 zassert_ok(llext_call_fn(ext, "test_entry"),
225 "test_entry call should succeed");
226 #else /* !USERSPACE && !SHAREDLIB */
227 llext_bootstrap(ext, test_entry_fn, NULL);
228 #endif
229
230 if (test_case->test_cleanup) {
231 test_case->test_cleanup(ext);
232 }
233 #endif /* CONFIG_USERSPACE */
234
235 llext_unload(&ext);
236 }
237
238 /*
239 * Attempt to load, list, list symbols, call a fn, and unload each
240 * extension in the test table. This excercises loading, calling into, and
241 * unloading each extension which may itself excercise various APIs provided by
242 * Zephyr.
243 */
244 #define LLEXT_LOAD_UNLOAD(_name, extra_args...) \
245 ZTEST(llext, test_load_unload_##_name) \
246 { \
247 const struct llext_test test_case = { \
248 .name = STRINGIFY(_name), \
249 .buf = _name ## _ext, \
250 .buf_len = sizeof(_name ## _ext), \
251 extra_args \
252 }; \
253 load_call_unload(&test_case); \
254 }
255
256 /*
257 * ELF file should be aligned to at least sizeof(elf_word) to avoid issues. A
258 * larger value eases debugging, since it reduces the differences in addresses
259 * between similar runs.
260 */
261 #define ELF_ALIGN __aligned(4096)
262
263 static LLEXT_CONST uint8_t hello_world_ext[] ELF_ALIGN = {
264 #include "hello_world.inc"
265 };
266 LLEXT_LOAD_UNLOAD(hello_world,
267 .kernel_only = true
268 )
269
270 #ifndef CONFIG_LLEXT_TYPE_ELF_SHAREDLIB
271 static LLEXT_CONST uint8_t init_fini_ext[] ELF_ALIGN = {
272 #include "init_fini.inc"
273 };
274
init_fini_test_cleanup(struct llext * ext)275 static void init_fini_test_cleanup(struct llext *ext)
276 {
277 /* Make sure fini_fn() was called during teardown.
278 * (see init_fini_ext.c for more details).
279 */
280 const int *number = llext_find_sym(&ext->exp_tab, "number");
281 const int expected = (((((1 << 4) | 2) << 4) | 3) << 4) | 4; /* 0x1234 */
282
283 zassert_not_null(number, "number should be an exported symbol");
284 zassert_equal(*number, expected, "got 0x%x instead of 0x%x during cleanup",
285 *number, expected);
286 }
287
288 LLEXT_LOAD_UNLOAD(init_fini,
289 .test_cleanup = init_fini_test_cleanup
290 )
291 #endif
292
293 static LLEXT_CONST uint8_t logging_ext[] ELF_ALIGN = {
294 #include "logging.inc"
295 };
296 LLEXT_LOAD_UNLOAD(logging)
297
298 static LLEXT_CONST uint8_t relative_jump_ext[] ELF_ALIGN = {
299 #include "relative_jump.inc"
300 };
301 LLEXT_LOAD_UNLOAD(relative_jump)
302
303 static LLEXT_CONST uint8_t object_ext[] ELF_ALIGN = {
304 #include "object.inc"
305 };
306 LLEXT_LOAD_UNLOAD(object)
307
308 static LLEXT_CONST uint8_t syscalls_ext[] ELF_ALIGN = {
309 #include "syscalls.inc"
310 };
311 LLEXT_LOAD_UNLOAD(syscalls)
312
313 static LLEXT_CONST uint8_t threads_kernel_objects_ext[] ELF_ALIGN = {
314 #include "threads_kernel_objects.inc"
315 };
316 LLEXT_LOAD_UNLOAD(threads_kernel_objects,
317 .test_setup = threads_objects_test_setup,
318 )
319
320 static LLEXT_CONST uint8_t align_ext[] ELF_ALIGN = {
321 #include "align.inc"
322 };
323 LLEXT_LOAD_UNLOAD(align)
324
325 static LLEXT_CONST uint8_t inspect_ext[] ELF_ALIGN = {
326 #include "inspect.inc"
327 };
328
do_inspect_checks(struct llext_loader * ldr,struct llext * ext,enum llext_mem reg_idx,const char * sect_name,const char * sym_name)329 void do_inspect_checks(struct llext_loader *ldr, struct llext *ext, enum llext_mem reg_idx,
330 const char *sect_name, const char *sym_name)
331 {
332 const elf_shdr_t *sect_hdr = NULL, *reg_hdr = NULL;
333 enum llext_mem sect_region = LLEXT_MEM_COUNT;
334 uintptr_t reg_addr = 0, sym_addr = 0;
335 size_t reg_size = 0, sect_offset = 0;
336 int sect_shndx, res;
337
338 res = llext_get_region_info(ldr, ext, reg_idx,
339 ®_hdr, (const void **)®_addr, ®_size);
340 zassert_ok(res, "get_region_info() should succeed");
341 sect_shndx = llext_section_shndx(ldr, ext, sect_name);
342 zassert_true(sect_shndx > 0, "section %s should be found", sect_name);
343 res = llext_get_section_info(ldr, ext, sect_shndx,
344 §_hdr, §_region, §_offset);
345 zassert_ok(res, "get_section_info() should succeed");
346 sym_addr = (uintptr_t)llext_find_sym(&ext->exp_tab, sym_name);
347 zassert_true(sym_addr, "symbol %s must be exported", sym_name);
348
349 zassert_equal(reg_idx, sect_region, "region mismatch (expected %d, got %d)",
350 reg_idx, sect_region);
351 zassert_true(sect_hdr->sh_offset >= reg_hdr->sh_offset &&
352 (sect_hdr->sh_offset + sect_hdr->sh_size <=
353 reg_hdr->sh_offset + reg_hdr->sh_size),
354 "section %s overflows its region %d", sect_name, reg_idx);
355 zassert_true(sect_offset < reg_size, "section offset outside region");
356 zassert_true(sym_addr >= reg_addr && sym_addr < reg_addr + reg_size,
357 "symbol %s mapped outside region %d", sym_name, reg_idx);
358 zassert_true(sym_addr >= reg_addr + sect_offset &&
359 sym_addr < reg_addr + sect_offset + sect_hdr->sh_size,
360 "symbol %s mapped outside section %s", sym_name, sect_name);
361 }
362
ZTEST(llext,test_inspect)363 ZTEST(llext, test_inspect)
364 {
365 int res;
366
367 struct llext_buf_loader buf_loader =
368 LLEXT_BUF_LOADER(inspect_ext, sizeof(inspect_ext));
369 struct llext_loader *ldr = &buf_loader.loader;
370 struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
371 struct llext *ext = NULL;
372 size_t max_alloc_bytes;
373
374 ldr_parm.keep_section_info = true;
375 res = llext_load(ldr, "inspect", &ext, &ldr_parm);
376 zassert_ok(res, "load should succeed");
377
378 do_inspect_checks(ldr, ext, LLEXT_MEM_BSS, ".bss", "number_in_bss");
379 do_inspect_checks(ldr, ext, LLEXT_MEM_DATA, ".data", "number_in_data");
380 do_inspect_checks(ldr, ext, LLEXT_MEM_RODATA, ".rodata", "number_in_rodata");
381 do_inspect_checks(ldr, ext, LLEXT_MEM_RODATA, ".my_rodata", "number_in_my_rodata");
382 do_inspect_checks(ldr, ext, LLEXT_MEM_TEXT, ".text", "function_in_text");
383
384 max_alloc_bytes = ext->alloc_size;
385 llext_free_inspection_data(ldr, ext);
386 zassert_true(ext->alloc_size < max_alloc_bytes, "inspection data should be freed");
387
388 llext_unload(&ext);
389 }
390
391 #ifndef CONFIG_LLEXT_TYPE_ELF_OBJECT
392 static LLEXT_CONST uint8_t multi_file_ext[] ELF_ALIGN = {
393 #include "multi_file.inc"
394 };
395 LLEXT_LOAD_UNLOAD(multi_file)
396
397 #if defined(CONFIG_RISCV) && defined(CONFIG_RISCV_ISA_EXT_C)
398 static LLEXT_CONST uint8_t riscv_edge_case_cb_type_ext[] ELF_ALIGN = {
399 #include "riscv_edge_case_cb_type.inc"
400 };
401 LLEXT_LOAD_UNLOAD(riscv_edge_case_cb_type)
402 #endif /* CONFIG_RISCV && CONFIG_RISCV_ISA_EXT_C */
403
404 #if defined(CONFIG_RISCV)
405 static LLEXT_CONST uint8_t riscv_edge_case_non_paired_hi20_lo12_ext[] ELF_ALIGN = {
406 #include "riscv_edge_case_non_paired_hi20_lo12.inc"
407 };
408 LLEXT_LOAD_UNLOAD(riscv_edge_case_non_paired_hi20_lo12)
409 #endif /* CONFIG_RISCV */
410
411 #endif /* !CONFIG_LLEXT_TYPE_ELF_OBJECT */
412
413 #ifndef CONFIG_USERSPACE
414 static LLEXT_CONST uint8_t export_dependent_ext[] ELF_ALIGN = {
415 #include "export_dependent.inc"
416 };
417
418 static LLEXT_CONST uint8_t export_dependency_ext[] ELF_ALIGN = {
419 #include "export_dependency.inc"
420 };
421
ZTEST(llext,test_inter_ext)422 ZTEST(llext, test_inter_ext)
423 {
424 const void *dependency_buf = export_dependency_ext;
425 const void *dependent_buf = export_dependent_ext;
426 struct llext_buf_loader buf_loader_dependency =
427 LLEXT_BUF_LOADER(dependency_buf, sizeof(hello_world_ext));
428 struct llext_buf_loader buf_loader_dependent =
429 LLEXT_BUF_LOADER(dependent_buf, sizeof(export_dependent_ext));
430 struct llext_loader *loader_dependency = &buf_loader_dependency.loader;
431 struct llext_loader *loader_dependent = &buf_loader_dependent.loader;
432 const struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
433 struct llext *ext_dependency = NULL, *ext_dependent = NULL;
434 int ret = llext_load(loader_dependency, "inter_ext_dependency", &ext_dependency, &ldr_parm);
435
436 zassert_ok(ret, "dependency load should succeed");
437
438 ret = llext_load(loader_dependent, "export_dependent", &ext_dependent, &ldr_parm);
439
440 zassert_ok(ret, "dependent load should succeed");
441
442 int (*test_entry_fn)() = llext_find_sym(&ext_dependent->exp_tab, "test_entry");
443
444 zassert_not_null(test_entry_fn, "test_entry should be an exported symbol");
445 test_entry_fn();
446
447 llext_unload(&ext_dependent);
448 llext_unload(&ext_dependency);
449 }
450 #endif
451
452 #if defined(CONFIG_LLEXT_TYPE_ELF_RELOCATABLE) && defined(CONFIG_XTENSA)
453 static LLEXT_CONST uint8_t pre_located_ext[] ELF_ALIGN = {
454 #include "pre_located.inc"
455 };
456
ZTEST(llext,test_pre_located)457 ZTEST(llext, test_pre_located)
458 {
459 struct llext_buf_loader buf_loader =
460 LLEXT_BUF_LOADER(pre_located_ext, sizeof(pre_located_ext));
461 struct llext_loader *loader = &buf_loader.loader;
462 struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
463 struct llext *ext = NULL;
464 const void *test_entry_fn;
465 int res;
466
467 /* load the extension trying to respect the addresses in the ELF */
468 ldr_parm.pre_located = true;
469 res = llext_load(loader, "pre_located", &ext, &ldr_parm);
470 zassert_ok(res, "load should succeed");
471
472 /* check the function address is the expected one */
473 test_entry_fn = llext_find_sym(&ext->exp_tab, "test_entry");
474 zassert_equal(test_entry_fn, (void *)0xbada110c, "test_entry should be at 0xbada110c");
475
476 llext_unload(&ext);
477 }
478 #endif
479
480 #if defined(CONFIG_LLEXT_STORAGE_WRITABLE)
481 static LLEXT_CONST uint8_t find_section_ext[] ELF_ALIGN = {
482 #include "find_section.inc"
483 };
484
ZTEST(llext,test_find_section)485 ZTEST(llext, test_find_section)
486 {
487 /* This test exploits the fact that in the STORAGE_WRITABLE cases, the
488 * symbol addresses calculated by llext will be directly inside the ELF
489 * file buffer, so the two methods can be easily compared.
490 */
491
492 int res;
493 ssize_t section_ofs;
494
495 struct llext_buf_loader buf_loader =
496 LLEXT_BUF_LOADER(find_section_ext, sizeof(find_section_ext));
497 struct llext_loader *loader = &buf_loader.loader;
498 struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
499 struct llext *ext = NULL;
500 elf_shdr_t shdr;
501
502 res = llext_load(loader, "find_section", &ext, &ldr_parm);
503 zassert_ok(res, "load should succeed");
504
505 section_ofs = llext_find_section(loader, ".data");
506 zassert_true(section_ofs > 0, "find_section returned %zd", section_ofs);
507
508 res = llext_get_section_header(loader, ext, ".data", &shdr);
509 zassert_ok(res, "get_section_header() should succeed");
510 zassert_equal(shdr.sh_offset, section_ofs,
511 "different section offset %zd from get_section_header", shdr.sh_offset);
512
513 uintptr_t symbol_ptr = (uintptr_t)llext_find_sym(&ext->exp_tab, "number");
514 uintptr_t section_ptr = (uintptr_t)find_section_ext + section_ofs;
515
516 /*
517 * FIXME on RISC-V, at least for GCC, the symbols aren't always at the beginning
518 * of the section when CONFIG_LLEXT_TYPE_ELF_OBJECT is used, breaking this assertion.
519 * Currently, CONFIG_LLEXT_TYPE_ELF_OBJECT is not supported on RISC-V.
520 */
521
522 zassert_equal(symbol_ptr, section_ptr,
523 "symbol at %p != .data section at %p (%zd bytes in the ELF)",
524 symbol_ptr, section_ptr, section_ofs);
525
526 llext_unload(&ext);
527 }
528
529 static LLEXT_CONST uint8_t test_detached_ext[] ELF_ALIGN = {
530 #include "detached_fn.inc"
531 };
532
533 static struct llext_loader *detached_loader;
534 static struct llext *detached_llext;
535 static elf_shdr_t detached_shdr;
536
test_section_detached(const elf_shdr_t * shdr)537 static bool test_section_detached(const elf_shdr_t *shdr)
538 {
539 if (!detached_shdr.sh_addr) {
540 int res = llext_get_section_header(detached_loader, detached_llext,
541 ".detach", &detached_shdr);
542
543 zassert_ok(res, "get_section_header should succeed");
544 }
545
546 return shdr->sh_name == detached_shdr.sh_name;
547 }
548
ZTEST(llext,test_detached)549 ZTEST(llext, test_detached)
550 {
551 struct llext_buf_loader buf_loader =
552 LLEXT_BUF_LOADER(test_detached_ext, sizeof(test_detached_ext));
553 struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
554 int res;
555
556 ldr_parm.section_detached = test_section_detached;
557 detached_loader = &buf_loader.loader;
558
559 res = llext_load(detached_loader, "test_detached", &detached_llext, &ldr_parm);
560 zassert_ok(res, "load should succeed");
561
562 /*
563 * Verify that the detached section is outside of the .text region.
564 * This only works with the shared ELF type, because with other types
565 * section addresses aren't "real," e.g. they can be 0.
566 */
567 elf_shdr_t *text_region = detached_loader->sects + LLEXT_MEM_TEXT;
568
569 zassert_true(text_region->sh_offset >= detached_shdr.sh_offset + detached_shdr.sh_size ||
570 detached_shdr.sh_offset >= text_region->sh_offset + text_region->sh_size);
571
572 void (*test_entry_fn)() = llext_find_sym(&detached_llext->exp_tab, "test_entry");
573
574 zassert_not_null(test_entry_fn, "test_entry should be an exported symbol");
575 test_entry_fn();
576
577 test_entry_fn = llext_find_sym(&detached_llext->exp_tab, "detached_entry");
578
579 zassert_not_null(test_entry_fn, "detached_entry should be an exported symbol");
580 test_entry_fn();
581
582 llext_unload(&detached_llext);
583 }
584 #endif
585
586 #if defined(CONFIG_FILE_SYSTEM)
587 #define LLEXT_FILE "hello_world.llext"
588
589 FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(storage);
590 static struct fs_mount_t mp = {
591 .type = FS_LITTLEFS,
592 .fs_data = &storage,
593 .storage_dev = (void *)FIXED_PARTITION_ID(storage_partition),
594 .mnt_point = "/lfs",
595 };
596
ZTEST(llext,test_fs_loader)597 ZTEST(llext, test_fs_loader)
598 {
599 int res;
600 char path[UINT8_MAX];
601 struct fs_file_t fd;
602
603 /* File system should be mounted before the testcase. If not mount it now. */
604 if (!(mp.flags & FS_MOUNT_FLAG_AUTOMOUNT)) {
605 zassert_ok(fs_mount(&mp), "Filesystem should be mounted");
606 }
607
608 snprintf(path, sizeof(path), "%s/%s", mp.mnt_point, LLEXT_FILE);
609 fs_file_t_init(&fd);
610
611 zassert_ok(fs_open(&fd, path, FS_O_CREATE | FS_O_TRUNC | FS_O_WRITE),
612 "Failed opening file");
613
614 zassert_equal(fs_write(&fd, hello_world_ext, ARRAY_SIZE(hello_world_ext)),
615 ARRAY_SIZE(hello_world_ext),
616 "Full content of the buffer holding ext should be written");
617
618 zassert_ok(fs_close(&fd), "Failed closing file");
619
620 struct llext_fs_loader fs_loader = LLEXT_FS_LOADER(path);
621 struct llext_loader *loader = &fs_loader.loader;
622 struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
623 struct llext *ext = NULL;
624
625 res = llext_load(loader, "hello_world", &ext, &ldr_parm);
626 zassert_ok(res, "load should succeed");
627
628 void (*test_entry_fn)() = llext_find_sym(&ext->exp_tab, "test_entry");
629
630 zassert_not_null(test_entry_fn, "test_entry should be an exported symbol");
631
632 llext_unload(&ext);
633 fs_unmount(&mp);
634 }
635 #endif
636
637 /*
638 * Ensure that EXPORT_SYMBOL does indeed provide a symbol and a valid address
639 * to it.
640 */
ZTEST(llext,test_printk_exported)641 ZTEST(llext, test_printk_exported)
642 {
643 const void * const printk_fn = LLEXT_FIND_BUILTIN_SYM(printk);
644
645 zassert_equal(printk_fn, printk, "printk should be an exported symbol");
646 }
647
648 /*
649 * The syscalls test above verifies that custom syscalls defined by extensions
650 * are properly exported. Since `ext_syscalls.h` declares ext_syscall_fail, we
651 * know it is picked up by the syscall build machinery, but the implementation
652 * for it is missing. Make sure the exported symbol for it is NULL.
653 */
ZTEST(llext,test_ext_syscall_fail)654 ZTEST(llext, test_ext_syscall_fail)
655 {
656 const void * const esf_fn = LLEXT_FIND_BUILTIN_SYM(z_impl_ext_syscall_fail);
657
658 zassert_is_null(esf_fn, "est_fn should be NULL");
659 }
660
661 ZTEST_SUITE(llext, NULL, NULL, NULL, NULL, NULL);
662