Skip to content

riscv: userspace: undefined symbol: z_stack_space_get  #98491

Closed

Description

@RobinKastberg

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:

#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_get
return z_stack_space_get((void *)thread->arch.priv_stack_start, *stack_size, unused_ptr);

which is guarded by

#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

f39f9bd

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

  1. added
    bugThe issue is a bug, or the PR is fixing a bug
    on Oct 29, 2025
  2. changed the title [-]riscv: userspace: `z_stack_space_get`[/-] [+]riscv: userspace: undefined symbol: `z_stack_space_get` [/+] on Oct 29, 2025
  3. cfriedt commented on Nov 12, 2025

    @cfriedt
    Member

    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 when CONFIG_USERSPACE=y.

    My immediate take on this, as pointed out in the description, and also just from some experience working with lld as 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 by CONFIG_INIT_STACKS and CONFIG_THREAD_STACK_INFO as mentioned, so in my opinion, the workaround seems legitimate, and probably should also be applied to other places where z_stack_space_get() is called and compilation is not conditional.

    The call-sites for z_stack_space_get() are here:

    @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.

  4. RobinKastberg commented on Nov 14, 2025

    @RobinKastberg
    ContributorAuthor

    @cfriedt please do whatever you'd like to help solve the problem! Good thing someone knowledgeable is looking at it.

  5. RobinKastberg commented on Nov 14, 2025

    @RobinKastberg
    ContributorAuthor

    LLVM's linker is single-pass, faster (at least the last time I checked), and might leave some symbols unresolved as a result.

    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?

  6. cfriedt commented on Nov 14, 2025

    @cfriedt
    Member

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area: RISCVRISCV Architecture (32-bit & 64-bit)area: UserspaceUserspacebugThe issue is a bug, or the PR is fixing a bugpriority: highHigh impact/importance bug

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions