Problem with external global static linker region

Hi. I'm having some trouble with a new change that came as part of 1.71. In 1.71 there is a new static variable static __rust_no_alloc_shim_is_unstable: u8; added as part of some update to alloc. See here: https://github.com/rust-lang/rust/blob/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library/alloc/src/alloc.rs#L42

My use case is a bit specific, my target platform is a cortex-m arm that runs Zephyr RTOS (no_std + alloc). I statically link to the rust static app to the Zepher C code.

The issue is because I am using the MPU to restrict access to certain partitions of memory. The rust app runs in a user thread. In order to access global variables I need to link them to tell the linker where to place them or I get an MPU fault, something like this:

#[link_section = "some_allowed_region"]
static mut SOME_GLOBAL: u32;

This works well, the problem is I cannot add the link section decorator to the external global static. So my solution right now is to use rustc version 1.68.2 (1.70 should work too). Otherwise I would have to maintain a fork of rustc with the decorator applied which is not something I would like to do.

My question is: Is there a third option where I could make some change to the build.rs to tell the linker where to place the external static variable so that I can keep using the latest version of rust?

Thanks!

I'm not personally familiar with these problems, but this sounds like a use case for a linker script.

that symbol is not defined in liballoc (nor libstd for that matter), but actually required by liballoc to exist when linking (it's in an extern block) and typically generated by rustc. if you use rustc to drive the final linking stage, the symbol will be correctly linked. if you drive the linking stage using a C/C++ linker, you'll have to define the symbol yourself in a C or C++ object (together with the allocator functions like __rust_alloc, __rust_dealloc etc required by liballoc, if you are not linking an #[global_allocator] rust crate)

no_std + alloc is special for embedded platform. alloc crate doesn't actually contains allocator implementation, but defines the allocator api. when a no_std crate uses alloc, it assumes their will be an allocator implementation being linked to the final binary, this can either be an #[global_allocator] crate or libstd, or even an C library that implements the rust allocator api. the symbole __rust_no_alloc_shim_is_unstable is part of the api. as I said, if you drive your final linking step with C/C++ toolchain, you'll have to define it yourself, even if you use an #[global_allocator] crate for embedded, as #[global_allocator] only provides functions of the allocator api, but the placeholder symbol __rust_no_alloc_shim_is_unstable is generated by rustc for the final binary target that needs linking.

also see the comments for an detailed explanation here:

that being said, I don't think the __rust_no_alloc_shim_is_unstable is ever accessed at runtime, so it shouldn't trigger any protection error even if you put it in a "protected" section.

2 Likes

It is accessed using read_volatile because that is the only way I know of to reliably ensure any attempt to allocate will result in a linker error if that symbol is not defined. Anything else risks getting optimized out and thus no longer referencing it at link time.

2 Likes

Thank you so much for this explanation :smiley: In the end the only thing that seems to work is to create a dedicated memory partition in Zephyr and mapping the address of __rust_no_alloc_shim_is_unstable to that at runtime before anything runs in userspace.

I tried to use the weak link to __rust_no_alloc_shim_is_unstable since that seems like the best way but the way the zephyr build scripts work this does not work.

thanks for the correction. I didn't read the code. sorry for the misleading information.

I don't know about the zephyr build system, but if you are linking the final executable using C/C++ toolchain, that symbol doesn't need to be weak.

I played with it a bit, and it seems direct linking a rlib with a C toolchain is still not supported. on Windows using nightly-msvc toolchain, I got an unresolved reference to alloc::alloc::handle_alloc_error using the following test code. but I assume this is not the case for embedded target such as cortex-m, since you need build liballoc anyway.

// lib.rs
#![no_std]

extern crate alloc;

use alloc::boxed::Box;
use core::panic::PanicInfo;

#[no_mangle]
pub unsafe extern "C" fn new_box(x: u32) -> *mut u32 {
	Box::into_raw(Box::new(x))
}

#[no_mangle]
pub unsafe extern "C" fn delete_box(ptr: *mut u32) {
	drop(Box::from_raw(ptr));
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
	unsafe { libc::abort() }
}
// main.c
#include <stdlib.h>
#include <stdio.h>

extern int *new_box(int x);
extern void delete_box(int *p);

int main(int argc, char **argv) {
	int *boxed = new_box(42);
	printf("ptr is 0x%p, value is %d\n", boxed, *boxed);
	delete_box(boxed);
	return 0;
}

extern int __rust_no_alloc_shim_is_unstable = 0;

void * __rust_alloc(size_t len, size_t _align) {
	// todo: proper align the pointer
	return malloc(len);
}

void *__rust_alloc_zeroed(size_t len, size_t _align) {
	return malloc(len);
}

void __rust_dealloc(void *ptr, size_t _size, size_t _align) {
	free(ptr);
}

void __rust_alloc_error_handler(size_t size, size_t align) {
	abort();
}

Must be the way I am compiling/linking because it seems the Rust definition always takes precedence. If I do not flag it as weak I get a compilation error due to multiple definitions. Probably something wrong with the linking order or something.... Got it in a workable state now and do not have time to investigate further. Think both the C and Rust are compiled independently and then linked afterwards, that must be the issue.