-
Notifications
You must be signed in to change notification settings - Fork 7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
llext: add support for init arrays - revised #77641
Conversation
bd0f582
to
d3d9141
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall.
A few thoughts and some minor change suggestions/requests:
subsys/llext/llext.c
Outdated
} | ||
|
||
/* Start extension main function, if provided */ | ||
if (entry_fn) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it could be done by the caller, unclear what benefit passing entry_fn in and calling it here does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My reasoning was to allow llext_bootstrap
to be called directly by k_thread_create
, to minimize boilerplate code. See the changes in test_llext_simple.c
and the above linked doc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh that makes sense, it’s user mode friendly then I take it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely. The LLEXT test suite in this PR already runs llext_bootstrap
both in user and kernel mode 🙂
d3d9141
to
9623405
Compare
v2:
|
subsys/llext/llext.c
Outdated
typedef void (*elf_init_fn_t)(void); | ||
|
||
int init_fn_count = ret / sizeof(elf_init_fn_t); | ||
elf_init_fn_t init_fns[init_fn_count]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we check ret
for a maximum value before allocating an arbitrary size array on stack?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The syscall returns sizes of arrays already in memory, so this number is "sane", but of course the size of the stack is unknown. Not sure how I could add overflow checks - suggestions?
subsys/llext/llext_handlers.c
Outdated
|
||
if (buf) { | ||
char *byte_ptr = buf; | ||
const char **fn_ptrs = buf; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const void **
? I had to think why char
, thought there would be some particular purpose in it. Same for text_start
and test_end
below. In fact personally I prefer uint8_t *
over char *
in such cases for the 2 reasons: (1) these aren't characters or strings, and (2) uint8_t
has a well defined signedness unlike char
, but I do realise, that char
is used a lot in such cases...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am only comparing the pointer values in a sanity check here, so the different types have no actual effect; choosing a byte-sized type allows to easily calculate text_end
and compare pointers without ugly extra casts, and char *
is used by memcpy()
a few lines above.
}; | ||
static const void *const init_fn_ptrs[] __used Z_GENERIC_SECTION(".init_array") = { | ||
init_fn | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we actually use C++ tests? Might be even nicer to actually have a couple of constructors as a test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... that would require proper C++ support for LLEXT (cmake, toolchains, etc). That's a different league - definitely not going that far in one PR. This is portable and has the same effect.
C++ tests will come in due time! We as Arduino kinda depend on that 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review! Will post an update soon.
subsys/llext/llext.c
Outdated
typedef void (*elf_init_fn_t)(void); | ||
|
||
int init_fn_count = ret / sizeof(elf_init_fn_t); | ||
elf_init_fn_t init_fns[init_fn_count]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The syscall returns sizes of arrays already in memory, so this number is "sane", but of course the size of the stack is unknown. Not sure how I could add overflow checks - suggestions?
subsys/llext/llext_handlers.c
Outdated
|
||
if (buf) { | ||
char *byte_ptr = buf; | ||
const char **fn_ptrs = buf; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am only comparing the pointer values in a sanity check here, so the different types have no actual effect; choosing a byte-sized type allows to easily calculate text_end
and compare pointers without ugly extra casts, and char *
is used by memcpy()
a few lines above.
}; | ||
static const void *const init_fn_ptrs[] __used Z_GENERIC_SECTION(".init_array") = { | ||
init_fn | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... that would require proper C++ support for LLEXT (cmake, toolchains, etc). That's a different league - definitely not going that far in one PR. This is portable and has the same effect.
C++ tests will come in due time! We as Arduino kinda depend on that 🙂
Load the .preinit_array, .init_array and .fini_array sections in ELF files. These sections are arrays of function pointers that are filled by the compiler with the addresses of functions that need to be called at startup or termination by the loader, such as C++ constructors and destructors. Signed-off-by: Luca Burelli <[email protected]>
llext_bringup() and llext_teardown() are intended to be used to call the extension's own initialization and cleanup functions, respectively. They are meant to be called by the developer after loading an extension and before unloading it. The list of function pointers to be called is obtained via the new llext_get_fn_table() syscall, so that they are compatible with user mode. llext_bootstrap() is intended to be used as the entry point for a thread created to run an extension, in either user or kernel contexts. It will call the extension's own initialization functions and then an additional entry point in the same context (if desired). The same function can also be called directly in the main thread, if only initialization is required. Signed-off-by: Luca Burelli <[email protected]>
Add documentation for the new LLEXT APIs that allow to call the initialization and cleanup functions of an extension. Signed-off-by: Luca Burelli <[email protected]>
9623405
to
1fb4040
Compare
1fb4040
to
035b4ed
Compare
Rebased onto current
|
* referenced at most once in the ELF file. | ||
*/ | ||
LOG_ERR("Region %d redefined", mem_idx); | ||
return -ENOEXEC; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't this be caught in line 211?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, line 211 checks that the ELF section (ldr->sect_map[shndx]
) was assigned a region before entering llext_map_sections
- I added that log so the whole function prints a full ELF section table, including reserved sections found by llext_find_tables
.
This is instead checking if the region has been already defined (ie. ldr->sects[mem_idx]
- the array that really should be renamed ldr->regions
...).
|
||
/* Start extension main function */ | ||
LOG_DBG("calling entry function %p(%p)", entry_fn, user_data); | ||
entry_fn(user_data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you wrote "if desired" in the commit description. Should you check for entry_fn == NULL
then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, commit message is a leftover from the previous iteration, when llext_bootstrap
was the only way to call init functions. I have since changed code, comments and documentation to make it clear it's not optional anymore; if you only want to init an extension, you now have llext_bringup
.
Will fix if I have to do any code change 👍
This replaces #76724, and includes all feedback received there.
Add support for the
.preinit_array
,.init_array
and.fini_array
sections in ELF files. These sections are arrays of function pointers that are filled by the compiler with the addresses of functions, such as C++ constructors, that need to be called at app startup or cleanup by the loader.This is now achieved separately from
llext_load
, in whatever execution context is deemed secure by the application writer, by either calling directlyllext_bringup
/llext_teardown
or starting a new user or kernel thread with thellext_bootstrap
function. The list of function pointers is obtained in all cases via a syscall. (link to related doc)The PR also adds aEDIT: Moved to #77941.init_fini
test that creates an ELF file with the required sections and verifies the execution order is correct.Supporting the.fini_array
section requires more changes and is not currently implemented.