ctor not running for statically linked libraries
fredericvauchelles opened this issue · 15 comments
Hi,
I have an issue with the following setup:
- app crate defines the binary to produce
- lib crate defines a rust standard library
When using #[ctor]
attribute inside the crate lib, it is not called when running the binary built with app.
Using a rust library dependency statically link it so it should also include the ctor function, but it does not seems to be the case.
Am I missing something or is this an unsupported use case?
Could you confirm which version you are using (ie: paste your Cargo.toml here)?
I tried to have a minimal repro and investigated further, and I resulted in a behaviour I don't understand.
The repro is attached to this comment.
These are the main files:
app/src/main.rs
fn main() {
println!("Main execution");
#[cfg(feature = "assert")]
assert_eq!(1, unsafe {
lib::VALUE.load(std::sync::atomic::Ordering::Acquire)
});
}
app/Cargo.toml
[package]
name = "app"
version = "0.1.0"
edition = "2018"
[dependencies]
lib = { path = "../lib" }
lib/src/lib.rs
#[macro_use]
extern crate ctor;
#[macro_use]
extern crate libc_print;
use std::sync::atomic::{AtomicUsize, Ordering};
pub static mut VALUE: AtomicUsize = AtomicUsize::new(0);
#[ctor]
fn startup() {
unsafe {
VALUE.fetch_add(1, Ordering::AcqRel);
}
libc_print!("Startup lib\r\n");
}
#[dtor]
fn tear_down() {
libc_print!("Tear down lib\r\n");
}
fn unused() {
let _ = unsafe { VALUE.load(Ordering::Acquire) };
}
lib/Cargo.toml
[package]
name = "lib"
version = "0.1.0"
authors = ["Frédéric Vauchelles <fredpointzero@gmail.com>"]
edition = "2018"
[dependencies]
libc-print = "0.1.8"
ctor = "0.1.10"
This is the strange behaviour I get:
cargo run --package app --bin app
will show onlyMain execution
in the console.- Comment the
#[cfg(feature = "assert")]
feature in themain.rs
file cargo run --package app --bin app
will succeed
So this is weird, I can't tell if this is working when it does not access the static variable.
Also, I have my issue when using the crate inventory
in a bigger project, but I will try to have a minimal repro in that case.
cargo 1.39.0-nightly (22f7dd049 2019-08-27)
rustc 1.39.0-nightly (dfd43f0fd 2019-09-01) (nightly-x86_64-pc-windows-gnu)
rustup 1.19.0 (2af131cf9 2019-09-08)
and
cargo 1.37.0 (9edd08916 2019-08-02)
rustc 1.37.0 (eae3437df 2019-08-13) (stable-x86_64-pc-windows-msvc)
rustup 1.19.0 (2af131cf9 2019-09-08)
In my project using the inventory
crate, I run into the same behaviour.
The ctor and dtor of the library project are not executed until I access a static variable defined in the library from the main application.
Interesting. I wonder if this is an unused symbol getting pruned. Thanks for the repro - I'll poke around.
I confirmed this is definitely an issue. Looks like there's some sort of whole-module pruning going on.
Hi, do you have any updates on this?
Nothing yet. I was able to repro it with your steps, but I feel like this might be an LLVM/rustc bug.
Hi, do you have any updates on this? Is this an LLVM/rustc bug as you suggested?
I can reproduce this on macOS Mojave and Rust 1.39.0 as well. However, it seems that I am able to get the constructor to run at least with the following minimal application:
//! lib.rs
#[ctor::ctor]
fn on_startup() {
println!("Starting up!");
}
#[ctor::dtor]
unsafe fn on_shutdown() {
libc::printf("Shutting down!\n\0".as_ptr() as *const i8);
}
pub fn unused() {}
//! main.rs
use foo::unused;
fn main() {
unused();
println!("Running");
}
The output produced by this application is:
Starting up!
Running
If I comment out the unused()
call in main.rs
, the application now produces the following output instead:
Running
I was unable to get the destructor working with the #[dtor]
macro, but if you replace the definition of the foo::on_shutdown()
function with this instead:
extern "C" fn on_shutdown() {
unsafe { libc::printf("Shutting down!\n\0".as_ptr() as *const i8) };
}
And then add the following call to libc::atexit()
to the foo::on_startup()
constructor:
unsafe { libc::atexit(on_shutdown) };
The application now works as expected:
Starting up!
Running
Shutting down!
In short, it seems that a few tweaks to the way your application is written will get the constructor and destructor to run:
- Your
main.rs
must call at least one function or inherent struct method fromlib.rs
for#[ctor]
to register properly. Importing static and const values in themain.rs
doesn't seem to help. #[dtor]
doesn't seem to work at all. Register it manually withlibc::atexit()
in your#[ctor]
function.
I'm not sure what is going on, but I am also leaning towards the possibility of an issue with Rust or LLVM.
I thought the whole point of the #[used]
attribute (as used here was to ensure a symbol is always present in the final binary?
Just encountered this issue as well.
In a debug build all 233 #[ctor]
s are executed, while in a release build only 60 are being executed. Updating to Rust 1.43.1, and then 62 are executed in a release build.
Sadly, @ebkalderon solution did not fix it for me, however I'm on Windows.
It definitely feels like some sort of "whole-module pruning", as @mmastrac said, as in my case either none or all #[ctor]
s in a module is executed.
Would someone have some bandwidth to file an upstream bug? This definitely seems like a Rust core issue - #[used]
items should not be pruned.
This may be fixed now, however I'll need someone with the issue to attempt to repro.
Marking as fixed as upstream is fixed.