-
Notifications
You must be signed in to change notification settings - Fork 6.6k
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: more advanced detached section support #80626
Conversation
Move a variable calculation inside the block, where it is actually used. Signed-off-by: Guennadi Liakhovetski <[email protected]>
Use the same logging level as in the rest of LLEXT. Signed-off-by: Guennadi Liakhovetski <[email protected]>
Detached sections are used in situ without being copied to or referenced from ext->mem[] LLEXT region arrays. Their caches must be synchronised accordingly. Signed-off-by: Guennadi Liakhovetski <[email protected]>
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, mostly LGTM and I love the -fPIC
option implementation. A few comments below.
subsys/llext/llext_link.c
Outdated
* In some cases | ||
* text + got_offset | ||
* is the same as | ||
* (uintptr_t)llext_loaded_sect_ptr(ldr, ext, shdr->sh_info) + rel->r_offset | ||
* but not in all. Eventually we could figure out how to generalize the latter to | ||
* cover all cases and to possibly simplify the above calculation |
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.
If this is the case, I think it deserves a dedicated LLEXT GH issue and some investigation. I believe it should absolutely be possible to only use the latter.
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.
@pillo79 hm, I did see them being different during development, but I re-added a check now and it never triggered... Looks like they are indeed equal, maybe I had a bug in that version still.
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.
If this is still a concern, I propose to replace this comment with a check, so we get useful debugging info for this issue.
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.
@pillo79 how about "TODO: A should be the same as B, consider simplifying?"
) | ||
|
||
if(CONFIG_LLEXT_BUILD_PIC) | ||
set(LLEXT_REMOVE_FLAGS ${LLEXT_REMOVE_FLAGS} | ||
-fno-pic | ||
-fno-pie | ||
) | ||
set(LLEXT_APPEND_FLAGS ${LLEXT_APPEND_FLAGS} | ||
-fPIC | ||
) | ||
else() | ||
set(LLEXT_APPEND_FLAGS ${LLEXT_APPEND_FLAGS} | ||
-ffreestanding |
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.
Interesting - never saw -ffreestanding
before, so I have read about it... IIUC it makes the llext use the core's definition of the standard functions. Shouldn't this be the default for both PIC and non-PIC? (... and on other arches as well?)
Also, should we add -fno-pic
here? Not sure what the Zephyr default is 🤷♂️
[whatever the decision, also update gcc/target_xtensa.cmake
to match]
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 said "mostly"... 😇 but I disagree with the last commit in the series, and I really wish to find a better solution which works for everybody.
subsys/llext/llext_load.c
Outdated
ldr->sect_hdrs = NULL; | ||
} | ||
ldr->sect_hdrs = NULL; | ||
|
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 tiny change leaks the contents of an internal and purposefully undocumented field, only when you use a buf_loader
, for no apparent Zephyr reason. Do we have to support this peculiar field and functionality from now on and forevermore?
We went through this already with SOF taking "ownership" of the similarly private ldr->sects[]
.
Please do not access private fields and expect them to be stable. Instead, a properly documented API to access the information you need would improve LLEXT for everyone.
Only as examples, either of these would work for me - but feel free to propose your own:
- the
sect_hdrs
pointer could be moved to a documented field ofstruct llext
and disposed later with the extension, maybe gated with aload_param
so it doesn't waste memory for those not interested in this feature; - a separate "cleanup" function could be added to dispose of all
llext_loader
fields (in this case samples and tests should be updated as well).
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.
@pillo79 yep, agree, I don't like that either and I expected objections against it. In your first option - you mean llext_unload()
, right? But currently it has no access to the loader. So we'd need to either modify the function's signature by adding it as the second argument, or to add a pointer to the loader to struct llext
?
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 loader struct can be dropped as soon as llext_load
completes, so I would not create such a constraint. I was instead thinking about fully moving the field from struct llext_loader
to struct llext
. That way it is unambiguous that it's a permanent feature.
The pointer could be freed at the end of llext_load
unless a load_param field like keep_section_headers
(or whatever) was set, in which case it would be kept until llext_unload
.
What do you think?
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.
@pillo79 in principle that sounds good, would work fine for me, but with that header array you anyway then have to scan it, you probably don't have section index to jump directly to it. And we do already have an API function llext_find_section()
that scans that array. Its usefulness at least for SOF is currently limited by it only returning an offset, not a complete header. Ideally I would modify that function to also return an entire section header in a user-supplied parameter, but it's an official API (LLEXT is still experimental). We could add a new function for that and potentially deprecate / migrate over time.
Also, if we do move the pointer to struct llext
maybe just keep it until freeing without a parameter - it isn't that much memory
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.
Great idea! Something along the lines of:
int llext_get_section_info(void **start, size_t *length, struct elf_shdr **shdr)
where each parameter is optional?
Or even better, I guess we can have a struct llext_section_info
with all those fields. It will make it easier to extend in the future without breaking compatibility.
WDYT?
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 like 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.
Sorry, I had a different use case in mind - for loaded sections. LGTM as well!
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 like it
@teburd hm, but I've just realised, that it cannot work (without large-scale changes to LLEXT). All access to section header tables in LLEXT is currently performed via the loader. IIUC originally the loader was assumed to be used only for initial linking / relocation, only while llext_load()
is running. Then we've relaxed that limitation by allowing the user to call llext_seek()
, llext_peek()
, llext_read()
and potentially llext_find_section()
- all with the loader as the first argument. But still we free all the dynamically allocated loader assets when exiting llext_load()
. Maybe we indeed should extend the loader life-cycle and make an explicit loader_free()
?
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 gets a bit awkward at some point as we're allocating in llext_load() but then I guess we're needing information past the load call. What lifetime are you looking for in the loader? Why?
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.
we've relaxed that limitation by allowing the user to call
llext_seek()
,llext_peek()
,llext_read()
@lyakh These are used by LLEXT for ELF loading and they are explicitly undocumented. TBH I don't think an end user should use them 🙂
we free all the dynamically allocated loader assets when exiting
llext_load()
That should be fine, if we move sect_hdrs
to struct llext
as discussed. This is definitely a big commit, but should be automated with a sed
command 🙂 What field that is needed after load completes are you referring to?
2914e9c
to
2bde66a
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.
Makes sense to me
subsys/llext/llext.c
Outdated
@@ -23,12 +23,32 @@ static sys_slist_t _llext_list = SYS_SLIST_STATIC_INIT(&_llext_list); | |||
|
|||
static struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock); | |||
|
|||
ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name) | |||
ssize_t llext_section_offset(struct llext_loader *ldr, struct llext *ext, const char *search_name) |
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 find_section
API was IMO terribly broken, since it does not tell you the size associated with the section; it is unclear to me how to safely use it. 🤷♂️
Can we take this opportunity to provide a replacement where you get the full section header information instead? SOF can just refer to the sh_offset
field in the returned struct.
ssize_t llext_section_offset(struct llext_loader *ldr, struct llext *ext, const char *search_name) | |
const elf_shdr_t *llext_get_section_header(struct llext_loader *ldr, struct llext *ext, const char *search_name) |
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.
@pillo79 yep, can do that, but I'd rather not return a pointer, but take a pointer as argument and copy the header into it. For a user, not familiar with LLEXT internals, returning a pointer to an object raises questions about object life-cycle, which might be a bit confusing?
subsys/llext/llext_link.c
Outdated
* In some cases | ||
* text + got_offset | ||
* is the same as | ||
* (uintptr_t)llext_loaded_sect_ptr(ldr, ext, shdr->sh_info) + rel->r_offset | ||
* but not in all. Eventually we could figure out how to generalize the latter to | ||
* cover all cases and to possibly simplify the above calculation |
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.
If this is still a concern, I propose to replace this comment with a check, so we get useful debugging info for this issue.
@@ -415,7 +415,7 @@ ZTEST(llext, test_find_section) | |||
res = llext_load(loader, "find_section", &ext, &ldr_parm); | |||
zassert_ok(res, "load should succeed"); | |||
|
|||
section_ofs = llext_find_section(loader, ".data"); | |||
section_ofs = llext_section_offset(loader, ext, ".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.
Since find_section
is not deprecated, both should be tested here.
EDIT: Wow didn't notice it actually was deprecated! 👍 to that, though this deprecation broke a test in SOF. 🙁 Maybe the deprecation should be delayed after SOF switches to the new API...
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.
we plan to deprecate it - once we've updated SOF to not call it to avoid twister failures
When detached sections are used, STB_GLOBAL relocations also have to be processed depending on the relocation type. This commit unified STB_GLOBAL and STB_LOCAL flows by making them use the same relocation type parser, adds support for R_XTENSA_GLOB_DAT and R_XTENSA_JMP_SLOT type relocations on Xtensa and fixes STT_SECTION address calculation for such sections. Signed-off-by: Guennadi Liakhovetski <[email protected]>
Currently when building LLEXT for Xtensa we use the -fPIC compiler option, but this cannot be used when using detached sections in extensions. Add a Kconfig option to switch between the two compilation modes and switch -fPIC off when building relocatable (partially linked) ELF binaries. Signed-off-by: Guennadi Liakhovetski <[email protected]>
Move cached section headers to struct llext from struct llext_loader to preserve them after llext_load() returns. Signed-off-by: Guennadi Liakhovetski <[email protected]>
Now that section header cache is persistent, it can be used for finding sections. Add a new function, similar to llext_find_section() to use it and deprecate llext_find_section(). Signed-off-by: Guennadi Liakhovetski <[email protected]>
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.
LGTM!
With this PR I have been able to move complex functions in extensions into detached sections with all the cross-referencing still working. I'll push a sample SOF PR, using this feature, later.