I am working to add support to Zephyr to make it easy for users of Zephyr to write their apps using Rust. I am running into a rather weird issue with #[global_allocator] and Zephyr's "USERSPACE" feature.
I am implementing a #[global_allocator] that uses the malloc/free provided within Zephyr. This seems to work well.
The problem comes when CONFIG_USERSPACE is enabled in Zephyr. In this mode, global variables are not accessible from tasks that are marked as userspace, and they have to be put in explicit linker sections to make sure they are available.
The issue is that the #[global_allocator] creates a u8, __rust_no_alloc_shim_is_unstable. The alloc implementation does a non-volatile read from this variable before calling the defined alloc function. This read faults in this configuration of Zephyr, because the data segment is not readable. The symbol comes from the compiler itself, so I don't see any obvious way to influence what linker segment it is placed in.
Any thoughts on how to deal with this? Is there another way to provide an allocator I could use from Nightly. Although, I'd prefer to have as many features available to Zephyr with the stable tools.
Who defines the #[global_allocator]? The app itself or the kernel? If it is the app itself, why do you need to use this special section? Surely you don't have to change literally every crate to use #[link_section] on each of their statics? If it is the kernel, isn't it expected that userspace apps can't access it? Also even if you somehow can access kernel symbols directly from userspace, linking libcore and such into the kernel itself would mean that you need to compile the kernel code and the userspace using the exact same rustc version. Is that intended?
So Zephyr's notion of "userspace" is very different than something like Linux. The Zephyr image is still a single image linked together, with the application linked to the kernel. Userspace adds additional protection to some threads that are run in user-mode on the CPU. But, they have to be granted access to any segments they wish to access (the processors don't have MMUs, but generally just an MPU, which is like the protection part of a memory manager, but without the remapping).
The consequence is that a userspace thread doesn't have access to any global variables that aren't placed into special sections it is supposed to have access to.
In a typical usage, it would be granted access to a shared memory pool, which is what its implementation of malloc/free use.
But, when trying to add a global allocator, which is defined in a library linked to this large single image, the alloc function from the alloc crate does a volatile read from __rust_no_alloc_shim_is_unstable before calling the provided allocate. The allocate would work, as the allocator is given access. But, I don't have control over what linker section this variable gets put into, and the read faults, because it doesn't have the ability to read from .bss.
It's kind of ironic, since Rust is normally quite good about not requiring reads from globals, except that there is one here, that seems to only be to address linking issues. I'm not sure if there is some other way to do this with having to actually read from the location.
The best I've thought of is to try and add something to the linker script to force this symbol into its own section.
I see. __rust_no_alloc_shim_is_unstable exists for the sole purpose of ensuring that the "allocator shim" is linked in when using liballoc as we don't want to guarantee that linking will succeed without it. Using a linker script to put it in the right section is likely the easiest option. The alternative would be to patch rustc itself.
It actually seems to work fine by just having a C file declare const unsigned char __rust_no_alloc_shim_is_unstable = 0;, which sticks the value in rodata, and seems to override the linker coming in from the later linked in Rust code.