Linkable loadable extensions shell module
Overview
This example provides shell access to the Linkable Loadable Extensions (LLEXT) system and provides the ability to manage loadable code extensions in the shell.
Requirements
A board with a supported LLEXT architecture and a shell capable console. The example below uses an ARMv7 target for illustration; the workflow is the same on any LLEXT-supported architecture, only the toolchain prefix and the generated hex payload change.
Building
The following command will build the main shell application:
west build -b robokit1 samples/subsys/llext/shell_loader
Note
You may need to disable memory protection for the sample to work (e.g.
CONFIG_ARM_MPU=n on ARM, CONFIG_XTENSA_MMU=n /
CONFIG_XTENSA_MPU=n on Xtensa, CONFIG_RISCV_PMP=n on RISC-V). See
the full list of similar flags in
tests/subsys/llext/no_mem_protection.conf.
This sample also includes the source for a very basic extension, samples/subsys/llext/shell_loader/hello_world.c, which can be used to test the LLEXT features.
It can be compiled to build/hello_world.llext using the Zephyr build
system like this:
$ ninja -C build -vvv hello_world_ext
On a host machine with the Zephyr SDK and the matching toolchain in PATH,
the same object can also be produced directly. Pick the toolchain prefix that
matches your target, for example arm-zephyr-eabi- for ARM,
xtensa-<soc>_zephyr-elf- for Xtensa or riscv64-zephyr-elf- for
RISC-V:
$ <toolchain-prefix>gcc -mlong-calls -c -o build/hello_world.llext samples/subsys/llext/shell_loader/hello_world.c
Note
The extension must be rebuilt for each target architecture: an extension compiled for ARM cannot be loaded on Xtensa or RISC-V, and vice versa. The loader will accept the hex string but the CPU will trap when calling into the extension because the instruction stream is foreign to it.
Note
LLEXT by default only imports symbols that have been explicitly exported by
the extension via the EXPORT_SYMBOL macro. Compiling with this
macro requires using the full Zephyr build system, or at least the
LLEXT EDK.
To avoid this complexity, this sample configures Zephyr to use all global
symbols defined in the extension ELF file via the Kconfig option
CONFIG_LLEXT_IMPORT_ALL_GLOBALS. This is not recommended
for large extensions as the memory usage increases significantly.
The compiled extension can be inspected with the usual binutils utilities to
see symbols, sections, and relocations. Then, using additional tools, converted
to a hex string usable by the llext load_hex shell command. The example
output below was produced with the ARM toolchain; relocation types and the
instruction listing differ on other architectures, but the procedure is
identical:
$ <toolchain-prefix>objdump -r -d -x build/hello_world.llext
hello_world.elf: file format elf32-littlearm
hello_world.elf
architecture: armv4t, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000
private flags = 0x5000000: [Version5 EABI]
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000038 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 0000006c 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 0000006c 2**0
ALLOC
3 .rodata 00000025 00000000 00000000 0000006c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 00000021 00000000 00000000 00000091 2**0
CONTENTS, READONLY
5 .ARM.attributes 0000002a 00000000 00000000 000000b2 2**0
CONTENTS, READONLY
SYMBOL TABLE:
00000000 l df *ABS* 00000000 hello_world.c
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
00000000 l d .bss 00000000 .bss
00000000 l d .rodata 00000000 .rodata
00000000 l O .rodata 00000004 number
00000000 l d .comment 00000000 .comment
00000000 l d .ARM.attributes 00000000 .ARM.attributes
00000000 g F .text 00000034 hello_world
00000000 *UND* 00000000 printk
Disassembly of section .text:
00000000 <hello_world>:
0: b580 push {r7, lr}
2: af00 add r7, sp, #0
4: 4b08 ldr r3, [pc, #32] ; (28 <hello_world+0x28>)
6: 0018 movs r0, r3
8: 4b08 ldr r3, [pc, #32] ; (2c <hello_world+0x2c>)
a: f000 f813 bl 34 <hello_world+0x34>
e: 222a movs r2, #42 ; 0x2a
10: 4b07 ldr r3, [pc, #28] ; (30 <hello_world+0x30>)
12: 0011 movs r1, r2
14: 0018 movs r0, r3
16: 4b05 ldr r3, [pc, #20] ; (2c <hello_world+0x2c>)
18: f000 f80c bl 34 <hello_world+0x34>
1c: 46c0 nop ; (mov r8, r8)
1e: 46bd mov sp, r7
20: bc80 pop {r7}
22: bc01 pop {r0}
24: 4700 bx r0
26: 46c0 nop ; (mov r8, r8)
28: 00000004 .word 0x00000004
28: R_ARM_ABS32 .rodata
2c: 00000000 .word 0x00000000
2c: R_ARM_ABS32 printk
30: 00000014 .word 0x00000014
30: R_ARM_ABS32 .rodata
34: 4718 bx r3
36: 46c0 nop ; (mov r8, r8)
$ xxd -p -c 99999 build/hello_world.llext
7f454c4601010100000000000000000001002800010000000000000000000000680200000000000534000000000028000b000a0080b500af084b1800084b00f013f82a22074b11001800054b00f00cf8c046bd4680bc01bc0047c0460400000000000000140000001847c0462a00000068656c6c6f20776f726c640a0000000041206e756d62657220697320256c750a00004743433a20285a65706879722053444b20302e31362e31292031322e322e30004129000000616561626900011f000000053454000602080109011204140115011703180119011a011e06000000000000000000000000000000000100000000000000000000000400f1ff000000000000000000000000030001000000000000000000000000000300030000000000000000000000000003000400000000000000000000000000030005000f00000000000000000000000000050012000000000000000400000001000500190000000000000000000000000001000f0000002800000000000000000001001900000034000000000000000000010000000000000000000000000003000600000000000000000000000000030007001c000000010000003400000012000100280000000000000000000000100000000068656c6c6f5f776f726c642e63002464006e756d6265720024740068656c6c6f5f776f726c64007072696e746b000028000000020500002c000000020e00003000000002050000002e73796d746162002e737472746162002e7368737472746162002e72656c2e74657874002e64617461002e627373002e726f64617461002e636f6d6d656e74002e41524d2e6174747269627574657300000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f0000000100000006000000000000003400000038000000000000000000000004000000000000001b000000090000004000000000000000fc0100001800000008000000010000000400000008000000250000000100000003000000000000006c00000000000000000000000000000001000000000000002b0000000800000003000000000000006c0000000000000000000000000000000100000000000000300000000100000002000000000000006c00000025000000000000000000000004000000000000003800000001000000300000000000000091000000210000000000000000000000010000000100000041000000030000700000000000000000b20000002a0000000000000000000000010000000000000001000000020000000000000000000000dc000000f0000000090000000d000000040000001000000009000000030000000000000000000000cc0100002f0000000000000000000000010000000000000011000000030000000000000000000000140200005100000000000000000000000100000000000000
In this sample there are 3 absolute (R_ARM_ABS32) relocations, 2 of which
are meant to hold addresses into the .rodata sections where the strings are
located. A third is an address of where the printk function (symbol) can be
found. At load time LLEXT replaces the values in the .text section with
real memory addresses so that printk works as expected with the strings
included in the hello world sample.
Running
Once the board has booted, you will be presented with a shell prompt.
All the LLEXT system related commands are available as sub-commands of
llext, and can be seen with llext help:
uart:~$ llext help
llext - Loadable extension commands
Subcommands:
list :List loaded extensions and their size in memory
load_hex :Load an elf file encoded in hex directly from the shell input.
Syntax:
<ext_name> <ext_hex_string>
unload :Unload an extension by name. Syntax:
<ext_name>
list_symbols :List extension symbols. Syntax:
<ext_name>
call_fn :Call extension function with prototype void fn(void). Syntax:
<ext_name> <function_name>
The hex string generated above can be used to load the extension:
uart:~$ llext load_hex hello_world 7f454c4601010100000000000000000001002800010000000000000000000000680200000000000534000000000028000b000a0080b500af084b1800084b00f013f82a22074b11001800054b00f00cf8c046bd4680bc01bc0047c0460400000000000000140000001847c0462a00000068656c6c6f20776f726c640a0000000041206e756d62657220697320256c750a00004743433a20285a65706879722053444b20302e31362e31292031322e322e30004129000000616561626900011f000000053454000602080109011204140115011703180119011a011e06000000000000000000000000000000000100000000000000000000000400f1ff000000000000000000000000030001000000000000000000000000000300030000000000000000000000000003000400000000000000000000000000030005000f00000000000000000000000000050012000000000000000400000001000500190000000000000000000000000001000f0000002800000000000000000001001900000034000000000000000000010000000000000000000000000003000600000000000000000000000000030007001c000000010000003400000012000100280000000000000000000000100000000068656c6c6f5f776f726c642e63002464006e756d6265720024740068656c6c6f5f776f726c64007072696e746b000028000000020500002c000000020e00003000000002050000002e73796d746162002e737472746162002e7368737472746162002e72656c2e74657874002e64617461002e627373002e726f64617461002e636f6d6d656e74002e41524d2e6174747269627574657300000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f0000000100000006000000000000003400000038000000000000000000000004000000000000001b000000090000004000000000000000fc0100001800000008000000010000000400000008000000250000000100000003000000000000006c00000000000000000000000000000001000000000000002b0000000800000003000000000000006c0000000000000000000000000000000100000000000000300000000100000002000000000000006c00000025000000000000000000000004000000000000003800000001000000300000000000000091000000210000000000000000000000010000000100000041000000030000700000000000000000b20000002a0000000000000000000000010000000000000001000000020000000000000000000000dc000000f0000000090000000d000000040000001000000009000000030000000000000000000000cc0100002f0000000000000000000000010000000000000011000000030000000000000000000000140200005100000000000000000000000100000000000000
This extension can then be seen in the list of loaded extensions (list), its symbols printed
(list_symbols), and the hello_world function which the extension exports can be called and
run (call_fn).
uart:~$ llext call_fn hello_world hello_world
hello world
Loading multiple extensions
Each load_hex invocation allocates its own ELF buffer and tracks it under
the extension name. The buffer is freed when unload is called for that
name, so several extensions can stay loaded and be called independently:
uart:~$ llext load_hex hello_world <hex>
uart:~$ llext load_hex worker <hex>
uart:~$ llext list
| Name | Size |
| hello_world | ... |
| worker | ... |
uart:~$ llext call_fn hello_world hello_world
uart:~$ llext call_fn worker run
uart:~$ llext unload worker
The number of simultaneously loaded extensions is limited by the
LLEXT_SHELL_MAX_LOADED slot count in subsys/llext/shell.c
(four by default). Adjust it there if more concurrent extensions are needed.