Repository navigation
riscv: userspace: undefined symbol: z_stack_space_get #98491
Description
Describe the bug
While building qemu_riscv64 with lld, and code model large, I get undefined symbol z_stack_space_get.
Taking a look this actually seems to be true.
Since:
Line 869 in 0589958
| #if defined(CONFIG_INIT_STACKS) && defined(CONFIG_THREAD_STACK_INFO) |
introduced in #22520 guards the definition of
z_stack_space_get, but not the usage in arch_thread_priv_stack_space_getzephyr/arch/riscv/core/thread.c
Line 216 in f0da1d7
| return z_stack_space_get((void *)thread->arch.priv_stack_start, *stack_size, unused_ptr); |
which is guarded by
zephyr/arch/riscv/core/thread.c
Line 127 in f0da1d7
| #ifdef CONFIG_USERSPACE |
there is also CONFIG_ARCH_HAS_THREAD_PRIV_STACK_SPACE_GET introduced in #77425 guarding the definition of arch_thread_priv_stack_space_get in the headers, but not the compilation unit itself, which is unconditionally compiled.
I have a workaround currently, but I get the feeling that someone more knowledgable of this should take a look and decide how it SHOULD work. There are multiple paths towards a solution
- a separate function definition
- making the Kconfig variable actually conditionally compile this properly
- syncing up whether to check for USERSPACE and/or INIT_STACKS
diff --git a/arch/riscv/core/thread.c b/arch/riscv/core/thread.c
index 773bf51479a..0da917191ab 100644
--- a/arch/riscv/core/thread.c
+++ b/arch/riscv/core/thread.c
@@ -207,6 +207,9 @@ FUNC_NORETURN void arch_user_mode_enter(k_thread_entry_t user_entry,
int arch_thread_priv_stack_space_get(const struct k_thread *thread, size_t *stack_size,
size_t *unused_ptr)
{
+#if !defined(CONFIG_INIT_STACKS) || !defined(CONFIG_THREAD_STACK_INFO)
+ return -EINVAL;
+#endif
if ((thread->base.user_options & K_USER) != K_USER) {
return -EINVAL;
}
Regression
- This is a regression.
Steps to reproduce
LLVM_TOOLCHAIN_PATH=/workdir/llvm/build/ ZEPHYR_TOOLCHAIN_VARIANT=llvm west build -p -b qemu_riscv64 samples/userspace/hello_world_user/ -DCONFIG_LLVM_USE_LLD=y -DCONFIG_COMPILER_RT_RTLIB=y
Relevant log output
ld.lld: error: undefined symbol: z_stack_space_get
>>> referenced by thread.c
>>> thread.c.obj:(.data.rel.ro+0x58) in archive zephyr/arch/arch/riscv/core/libarch__riscv__core.a
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.Impact
Major – Severely degrades functionality; workaround is difficult or unavailable.
Environment
Additional Context
Why is this only being discovered in some special cases?
I guess because linker will cull symbols not being used, is there some way to detect all undefined symbols?
Activity
[-]riscv: userspace: `z_stack_space_get`[/-][+]riscv: userspace: undefined symbol: `z_stack_space_get` [/+]cfriedt commentedon Nov 12, 2025
Looking at this currently. I think that RISC-V follows the same approximate implementation for
arch_thread_priv_stack_space_get()compared to xtensa and x86, although riscv and xtensa only compile that whenCONFIG_USERSPACE=y.My immediate take on this, as pointed out in the description, and also just from some experience working with
lldas well, is that the link strategy used by GCC and LLVM differ slightly. GCC may be a bit more aggressive in stripping dead code. LLVM's linker is single-pass, faster (at least the last time I checked), and might leave some symbols unresolved as a result.Of course, we should strive to make both work.
z_stack_space_get()itself is guarded byCONFIG_INIT_STACKSandCONFIG_THREAD_STACK_INFOas mentioned, so in my opinion, the workaround seems legitimate, and probably should also be applied to other places wherez_stack_space_get()is called and compilation is not conditional.The call-sites for
z_stack_space_get()are here:INIT_STACKSandTHREAD_STACK_INFOvia Kconfig, so is safeINIT_STACKSandTHREAD_STACK_INFOvia Kconfig, so is safeCONFIG_INIT_STACKSandCONFIG_THREAD_STACK_INFO, along with the implementation, so is safe@RobinKastberg - are you OK if I were to put up a PR using your workaround? I would be happy to apply your sign-off to the commit as well, and will pull you into the PR for review.
RobinKastberg commentedon Nov 14, 2025
@cfriedt please do whatever you'd like to help solve the problem! Good thing someone knowledgeable is looking at it.
RobinKastberg commentedon Nov 14, 2025
In the large code model in RISC-V, the addresses of functions are placed in a separate section to be able to load them on demand. Unneeded functions are not pruned from there, and I think this is a triggering factor why it shows up in this case.
It would be good if we could catch these in the gcc case also. Is there some flag that can be turned on?
cfriedt commentedon Nov 14, 2025
Oh wow - i'm not sure about how to fix all of the symbols mentioned yet, but there is a PR that could be merged soon.