Skip to content
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: dependency test #77738

Merged
merged 3 commits into from
Sep 12, 2024
Merged

LLEXT: dependency test #77738

merged 3 commits into from
Sep 12, 2024

Conversation

lyakh
Copy link
Collaborator

@lyakh lyakh commented Aug 29, 2024

Add a twister test to the new LLEXT dependency infrastructure. It contains #77473 but the goal is to merge them separately

@lyakh lyakh force-pushed the depend-test branch 2 times, most recently from c04e284 to 21f1a03 Compare August 29, 2024 14:23
Copy link
Collaborator

@pillo79 pillo79 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this! 🙇‍♂️ 🚀

One small issue I see with this setup is that it does not reliably test the actual relocation; there is no failure in the dependent if calling the function has no effect.

Instead of reusing the hello_world extension, what do you think about two tiny dep_parent_ext/dep_child_ext so that some easy calculation in child depends on the result of a function exported from parent? This way you can use z_assert in child to ensure the path was taken correctly.

Comment on lines 277 to 298
void *dependency_buf = k_heap_alloc(&tllext_heap, sizeof(hello_world_ext), K_NO_WAIT);

zassert_not_null(dependency_buf);

void *dependent_buf = k_heap_alloc(&tllext_heap, sizeof(export_dependent_ext), K_NO_WAIT);

zassert_not_null(dependent_buf);

memcpy(dependency_buf, hello_world_ext, sizeof(hello_world_ext));
memcpy(dependent_buf, export_dependent_ext, sizeof(export_dependent_ext));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to copy the _ext buffers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a comment in the respective commit - because I reuse "hello world" and I thought it could be done by others from time to time too. And if we relocate extensions in place, that breaks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

@pillo79 pillo79 Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it was a quick review and I did not check the commits. 🤦‍♂️
However there are two things I am not fond of in that commit:

  1. it is being applied even when STORAGE_WRITABLE is not enabled. This makes the LLEXT code always access ELF data in RAM, and thus will no longer catch bugs that cause LLEXT to write something there.
  2. even if applied only on STORAGE_WRITABLE, this means that ELF buffers are never written to - they should be made fully const (not LLEXT_CONST).


void (*test_entry_fn)() = llext_find_sym(&ext_dependent->exp_tab, "test_dependent");

zassert_not_null(test_entry_fn, "test_entry should be an exported symbol");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_entry in the message is really test_dependent

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, there's one common entry point, so I thought it shouldn't matter

@lyakh
Copy link
Collaborator Author

lyakh commented Aug 29, 2024

One small issue I see with this setup is that it does not reliably test the actual relocation; there is no failure in the dependent if calling the function has no effect.

@pillo79 thanks for reviewing! I first have done two extensions, but then I decided that I can reuse "hello world" for the dependency. As for no effect - I thought the effect would be an exception - if we somehow linked wrongly? So, yes, if there's a strong opinion, that we need a more explicit test, we can do that too

@pillo79
Copy link
Collaborator

pillo79 commented Aug 29, 2024

One small issue I see with this setup is that it does not reliably test the actual relocation; there is no failure in the dependent if calling the function has no effect.

[...] As for no effect - I thought the effect would be an exception - if we somehow linked wrongly?

Yes, most of the time a bad byte sequence will cause a CPU exception. However, I am sure I have seen jumps being silently ignored in some early LLEXT debugging. Maybe they were relative and "jumped 0 bytes" without a valid relocation?
Luckily we are past that time now 🥳 but I would still err on the safe side... 😇

@lyakh
Copy link
Collaborator Author

lyakh commented Aug 29, 2024

One small issue I see with this setup is that it does not reliably test the actual relocation; there is no failure in the dependent if calling the function has no effect.

[...] As for no effect - I thought the effect would be an exception - if we somehow linked wrongly?

Yes, most of the time a bad byte sequence will cause a CPU exception. However, I am sure I have seen jumps being silently ignored in some early LLEXT debugging. Maybe they were relative and "jumped 0 bytes" without a valid relocation? Luckily we are past that time now 🥳 but I would still err on the safe side... 😇

yeah, we could make the extension cryptographically sign our (pseudo-) random challenge... or we can just make it return 42 :-)

Copy link
Collaborator

@pillo79 pillo79 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with modifying the test_entry() signature in all extensions, when the return value is only ever useful for the new custom test.

Instead of the last 2 commits, I suggest to add 2 extensions:

  • one exporting an int get_value() { return 42; },
  • the other a standard void test_entry() { zassert_equal(get_value(), 42); }.

@@ -15,7 +15,9 @@ target_include_directories(app PRIVATE
${ZEPHYR_BASE}/arch/${ARCH}/include
)

set(ext_names hello_world logging relative_jump object syscalls threads_kernel_objects)
set(ext_names
hello_world logging relative_jump object syscalls threads_kernel_objects export_dependent
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please convert this so the names are one per line, I think we are going to need this going forward 🙂

@@ -38,6 +38,10 @@ LOG_MODULE_REGISTER(test_llext_simple);
#define LLEXT_FIND_BUILTIN_SYM(symbol_name) llext_find_sym(NULL, # symbol_name)
#endif

#ifdef CONFIG_LLEXT_STORAGE_WRITABLE
K_HEAP_DEFINE(tllext_heap, 32 * 1024);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
K_HEAP_DEFINE(tllext_heap, 32 * 1024);
K_HEAP_DEFINE(scratch_heap, CONFIG_LLEXT_HEAP_SIZE * 1024);

(name is a suggestion but please make it more descriptive, and size it automatically)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to verify - CONFIG_LLEXT_HEAP_SIZE is used in llext_mem.c as the heap size for LLEXT itself, you want to use the same here for the test?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's overkill, but it was the simplest solution I could think of - you can also add a CONFIG_MAX_ELF_SIZE in the test Kconfig, for example.
IMHO the hardcoded number will bite us often - AArch64 already needs a bigger buffer in the initial PR 🙂
But these comments are not blockers, sorry. Don't know how to explain what I needed to the Github UI, apparently 🤦‍♂️

@lyakh
Copy link
Collaborator Author

lyakh commented Sep 6, 2024

I disagree with modifying the test_entry() signature in all extensions, when the return value is only ever useful for the new custom test.

Instead of the last 2 commits, I suggest to add 2 extensions:

* one exporting an `int get_value() { return 42; }`,

* the other a standard `void test_entry() { zassert_equal(get_value(), 42); }`.

@pillo79 I actually think it's good for extensions to return a value, also demonstrates and tests the API better IMHO. @teburd what do you think?

@pillo79
Copy link
Collaborator

pillo79 commented Sep 6, 2024

I disagree with modifying the test_entry() signature in all extensions, when the return value is only ever useful for the new custom test.

@pillo79 I actually think it's good for extensions to return a value, also demonstrates and tests the API better IMHO. @teburd what do you think?

@lyakh I am totally in favor of doing that; I would also go the extra step of passing a pointer in, TBH.
But the way it was implemented gave no meaning or definition to the value in any test except the new special-case "dependency", where it was compared to a known one. That's what I want to avoid 🙂

So I would gladly approve either:

  • a standard way to define expected return values and verify them in llext_load_call_unload(), refactoring current tests to match, plus the new dependency one, or
  • the simpler solution of doing the check inside the specific _ext, as every other basic test does.

@lyakh
Copy link
Collaborator Author

lyakh commented Sep 6, 2024

  • a standard way to define expected return values and verify them in llext_load_call_unload(), refactoring current tests to match, plus the new dependency one, or

  • the simpler solution of doing the check inside the specific _ext, as every other basic test does.

@pillo79 ok, for option 1 - would it be enough if I extended LLEXT_LOAD_UNLOAD(_name, _userspace, _perm_setup) with an additional _ret parameter and pass it to threads as the third argument? I was also thinking about a void * argument, but can you pass such pointers to user-space threads?
For option 2 - you mean while making all test functions return an integer, or making a single new one to be called in this test?

Copy link
Collaborator

@pillo79 pillo79 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks much better to me, thanks! 👍 2 minor comments below.

Comment on lines 18 to 19
short a = mask & (uintptr_t)test_dependent;
short b = mask & ((uintptr_t)test_dependent >> half_ptr_bits);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think short is correct on 64 bit arches given the values involved (although the assert will be fine).
IMHO a simpler calculation with constant values would make the logic clearer and still test proper linking and argument passing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pillo79 yeah, I was thinking about it too - if addresses are 64-bit, then IIRC int can still be 32 bits. So I wanted to replace int with long and short with int, would that be enough?

#include <zephyr/sys/util.h>
extern int test_dependency(short a, short b);

void test_dependent(void)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think this should be test_entry, for consistency with all other test suites.

LLEXT loader parameters are input-only, make them "const."

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
When identifying the size of an extension image we simply need its
size in bytes, not the number of array elements, that contain it.
Both happen to return the same value, because we use arrays of bytes,
but using sizeof() is clearer.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
This test uses two LLEXT objects: one of them exports a symbol and
the other one calls the exported function.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
Copy link
Collaborator

@pillo79 pillo79 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@nashif nashif merged commit 96852bc into zephyrproject-rtos:main Sep 12, 2024
23 checks passed
@lyakh lyakh deleted the depend-test branch September 13, 2024 06:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: llext Linkable Loadable Extensions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants