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 				    &reg_hdr, (const void **)&reg_addr, &reg_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 				     &sect_hdr, &sect_region, &sect_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