Weak symbols in compiler_builtins

Hi! Apologies if this is a silly question or if I've got anything wrong. This is definitely not my aread of expertise:

I'm currently trying to some work with Rust using an ARM7v embedded device. The device in question is a bit odd, in that you send a binary over the network, where it's then dynamically linked and loaded — so we need to target bare metal, but produce a dynamically linked-executable!

This has worked fine, until I tried to update to Rust nightly-2025-02-20 (yay, Rust 2024!), where the binary now starts exporting some weak symbols from compiler-builtins, like __aeabi_uidivmod. The binary loader on the device doesn't currently cope with this and fails.

I've pinned this down to a change between nightly-2024-12-12 and nightly-2024-12-13, but I'm not quite sure what's causing it, or where to go for here.

Cargo.toml:

[package]
name = "example"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["staticlib"]

[profile.release]
codegen-units = 1
opt-level = 2
lto = true

src/lib.rs

#![no_std]
#![no_main]

use core::panic::PanicInfo;

#[unsafe(no_mangle)]
extern "C" fn div_euclid(x: i64) -> i64 {
    x.div_euclid(123)
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

I'm then compiling the whole thing with the following command (well, not IRL, but for this example!):

env RUSTFLAGS=-Crelocation-model=pic cargo +nightly-2024-12-13 build --target=armv7a-none-eabihf -Zbuild-std=core --release
ld.lld -shared  target/armv7a-none-eabihf/release/libexample.a --entry div_euclid -x -o out.elf

On 2024-12-12, the binary exports the following symbols:

$ nm -g out.elf
00114f8 T div_euclid

But on 2024-12-13, it exports:

$ nm -g out.elf
00011668 W __aeabi_idivmod
00011680 W __aeabi_ldivmod
00011628 W __aeabi_uidivmod
00011644 W __aeabi_uldivmod
000115e8 T div_euclid

If I compare the original libexample.a file, this appears to be because the visibility of these symbols has changed from HIDDEN to DEFAULT, but it's not clear why to me — the version of compiler-builtins is the same.

Does anyone know what might have changed in Rust to cause this, and if there's any good ways to prevent this? Ideally I'd like to strip the weak marker from all symbols (either in the Rust lib, or in the final link step), but most existing tooling (e.g. objcopy) is only interested in making symbols more weak, and don't think there's any linker flags to ignore the weak marker.

My current workaround is to mark symbols as hidden (llvm-objcopy --localize-symbols=(llvm-readelf -s libexample.a | grep "WEAK DEFAULT" | awk '{ print $8 }' | psub) libexample.a), but this does not feel like a good long-term solution.

Maybe passing a version script with the exact symbols you need to export works? Also why are you using ld.lld -shared? That produces a dynamically linked library, which is expected to export all symbols that are defined. You can use ld.lld -pie to create a position independent executable instead, which will only export the main function by default unless you explicitly tell it to also export other symbols.

Oh, that works perfectly! Thank you so much.

Ahh, sorry! Me calling it an executable was a bit of a misnomer — the device very much just loads the binary as a shared library. It exports several functions for interacting with the underlying hardware, that we then call from our own Rust code (including some of the __aeabi, but not all of them) — so we do actually need the dynamic linking here. It makes sense for the problem it's trying to solve, but definitely not typical for embedded :).