mmastrac / rust-ctor

Module initialization/global constructor functions for Rust
Apache License 2.0
749 stars 49 forks source link

ctor not running for statically linked C library #280

Open vsbogd opened 1 year ago

vsbogd commented 1 year ago

Very similar to #27. I am opening it to ask for help in understanding what exactly doesn't work here.

I have a project with the following structure:

When C API library is built in release mode and linked to the executable binary then ctor is successfully called on start. If library is build in debug mode then ctor function is not included into the final binary by the linker while it is still present in a static library itself.

I use absolutely same minimal gcc options to try both debug and release:

gcc ./main.c -o ./debug -I. -L./c/target/debug -l:libhyperonc.a -lm

I have tried to repeat the issue building a minimal example from scratch but in minimal example it works correctly.

Platform: Linux x86_64 ctor version 0.2.0 Rust version 1.69.0 GCC version 12.2.1 LD version 2.40

mmastrac commented 1 year ago

@vsbogd Try with the new 0.2.1 and cargo +nightly --features=used_linker. It's possible this may help. If so, we just need to wait for that feature to stabilize.

vsbogd commented 1 year ago

I receive an error when try using used_linker feature:

$ cat Cargo.toml | grep ctor
ctor = "0.2.1"
$ cargo +nightly build --features=used_linker
error: Package `hyperon v0.1.2` does not have the feature `used_linker`

Do I miss something?

mmastrac commented 1 year ago

You'll need to add a feature to hyperon in its Cargo.toml that depends on the ctor feature:

[features]
used_linker = ["ctor/used_linker"]
vsbogd commented 1 year ago

Thanks @mmastrac . I also needed to add used_linker into both my libraries: Rust and C API in order to compile C API.

Unfortunately it doesn't help. I still can see on_load which is marked as ctor in both static libraries:

$ objdump -t ./c/target/debug/libhyperonc.a | grep on_load 
0000000000000000 l    d  .text._ZN7hyperon7on_load17hdc39ff8fb07a0999E  0000000000000000 .text._ZN7hyperon7on_load17hdc39ff8fb07a0999E
0000000000000000 l     F .text._ZN7hyperon7on_load17hdc39ff8fb07a0999E  000000000000000b _ZN7hyperon7on_load17hdc39ff8fb07a0999E
0000000000000000 g     F .text.startup  0000000000000008 _ZN7hyperon26on_load___rust_ctor___ctor26on_load___rust_ctor___ctor17hc8a7af7b1d321ffcE
0000000000000000 g     O .init_array    0000000000000008 on_load___rust_ctor___ctor

$ objdump -t ./c/target/release/libhyperonc.a | grep on_load 
0000000000000000 l    d  .gcc_except_table._ZN7hyperon26on_load___rust_ctor___ctor26on_load___rust_ctor___ctor17hf96ede0c9e76c82eE  0000000000000000 .gcc_except_table._ZN7hyperon26on_load___rust_ctor___ctor26on_load___rust_ctor___ctor17hf96ede0c9e76c82eE
0000000000000000 l       .gcc_except_table._ZN7hyperon26on_load___rust_ctor___ctor26on_load___rust_ctor___ctor17hf96ede0c9e76c82eE  0000000000000000 GCC_except_table107
0000000000000000 g     F .text.startup  000000000000005c _ZN7hyperon26on_load___rust_ctor___ctor26on_load___rust_ctor___ctor17hf96ede0c9e76c82eE
0000000000000000 g     O .init_array    0000000000000008 on_load___rust_ctor___ctor

and cannot see it in executable linked with debug library:

$ objdump -t debug | grep on_load

$ objdump -t release | grep on_load
0000000000399500 g     O .init_array    0000000000000008              on_load___rust_ctor___ctor
0000000000066450 g     F .text  000000000000005c              _ZN7hyperon26on_load___rust_ctor___ctor26on_load___rust_ctor___ctor17hf96ede0c9e76c82eE
paukala commented 6 months ago

This issue is still present today. At least in my case, the issue seems to be related to the codegen-units setting, which defaults to 256 for debug and 16 for release builds. Lowering this value for debug builds (i.e. for the dev profile) solves the issue, although leading to increased build times:

# Cargo.toml
# ...

[profile.dev]
codegen-units = 16
dbartussek commented 6 months ago

Messing with codegen-units unfortunately doesn't seem to be a surefire way of solving this problem. I've built a simple test project to reproduce this issue because I encountered it in my own code: https://github.com/dbartussek/Rust-CRT-XCU-issue

The only way I've found to keep the initializer around was to have a static variable that Rust thinks might be touched by the code run from main.

mmastrac commented 6 months ago

The only way I've found to keep the initializer around was to have a static variable that Rust thinks might be touched by the code run from main.

What pattern ended up working for you?