Reputation: 151
I am writing a Rust CLI tool which uses a C library that I'm linking statically. I followed the bindgen tutorial to get started, and can currently create a binary that works as expected with cargo build
.
However, when I try to build the project with cargo build --release
, I am met with build errors indicating that the external C functions that I'm using are undefined, for example:
stumpless-logger/target/release/deps/libstumpless-292541b9e494f811.rlib(stumpless-292541b9e494f811.stumpless.7e987498-cgu.0.rcgu.o): In function `stumpless::FileTarget::new':
stumpless.7e987498-cgu.0:(.text._ZN9stumpless10FileTarget3new17hef1370275727c33dE+0x82): undefined reference to `stumpless_open_file_target'
nm
shows the expected exports for both the debug and release library builds in their respective target directories:
# first with the debug
stumpless-logger$ nm --defined-only target/debug/build/stumpless-sys-ca96d217a4e3d9db/out/lib/libstumpless.a | grep file
0000000000000000 T raise_file_open_failure
0000000000000000 T raise_file_write_failure
file.c.o:
0000000000000000 T destroy_file_target
0000000000000000 T file_open_default_target
0000000000000000 T new_file_target
0000000000000000 T sendto_file_target
0000000000000000 T stumpless_close_file_target
0000000000000000 T stumpless_open_file_target
# and now with the release
stumpless-logger$ nm --defined-only target/release/build/stumpless-sys-6f81199b96080c30/out/lib/libstumpless.a | grep file
00000000 T raise_file_open_failure
00000000 T raise_file_write_failure
file.c.o:
00000000 T destroy_file_target
00000000 T file_open_default_target
00000000 T new_file_target
00000000 T sendto_file_target
00000000 T stumpless_close_file_target
00000000 T stumpless_open_file_target
I can also build and run a usage example from the C library which uses one of the allegedly undefined functions, which works as expected with both the debug and release libraries:
# first with the debug version
stumpless/docs/examples/file$ gcc file_example.c -L "stumpless-logger/target/debug/build/stumpless-sys-ca96d217a4e3d9db/out/lib" -l:libstumpless.a -I "stumpless-logger/target/debug/build/stumpless-sys-ca96d217a4e3d9db/out/include" -o example-with-debug
stumpless/docs/examples/file$ ./example-with-debug && cat example.log
<14>1 2022-03-06T16:18:11.649456Z Angus example-app-name - example-msgid [basic-element basic-param-name="basic-param-value"] This is an example message.
# and then with the release
rm example.log
stumpless/docs/examples/file$ gcc file_example.c -L "stumpless-logger/target/release/build/stumpless-sys-6f81199b96080c30/out/lib" -l:libstumpless.a -I "stumpless-logger/target/release/build/stumpless-sys-6f81199b96080c30/out/include" -o example-with-release
stumpless/docs/examples/file$ ./example-with-release && cat example.log
<14>1 2022-03-06T16:21:16.255854Z Angus example-app-name - example-msgid [basic-element basic-param-name="basic-param-value"] This is an example message.
At first I thought this was something that would be resolved by splitting the FFI bindings into a separate -sys crate which seems to be standard practice. However the problem persists after doing this (but at least that's done now, silver linings I suppose).
I can make the problem go away by modifying the release
build profile to have an optimization level of 0. That is, adding this to my Cargo.toml
manifest resolves the problem:
[profile.release]
opt-level = 0
But this will bring it back:
[profile.release]
opt-level = 1
I tried explicitly disabling other optimizations listed in the profiles documentation, particularly LTO, to no avail. I thought perhaps my functions had mistakenly been removed during an LTO pass, but it is disabled by default (at least according to the docs), so this was unlikely to be the culprit anyway. And indeed, it still fails even with lto = false
in the manifest. Predictably, it also fails with lto = true
(it was a longshot, but I tried).
I'd really like my release builds to be optimized, as I suspect the end binary's performance would be better that way. But until I understand why this issue is happening, I'm unfortunately at a loss about how to achieve that. Anyone who can explain why this is happening and/or how to optimize my release builds would be highly appreciated.
I'm new to Rust development, so it's quite possible that I've missed something obvious. I've certainly struggled with other parts of the toolchain. However, I haven't been able to turn anything up on this issue after a day or so of research and fiddling, so I'm turning here for help. At the very least some future soul that encounters the same problem will have an easier time fixing their issue.
A side note, if you're tracing things to the C build itself note that I'm using a non-default branch as I tweak some things, the release-tune
branch of stumpless is what's relevant here.
EDIT 1 I forgot to include my Rust environmental information: I'm using rustup
to manage my install, with my active toolchain as such:
stable-x86_64-unknown-linux-gnu (default)
rustc 1.59.0 (9d1b2106e 2022-02-23)
EDIT 2 Well, I managed to resolve the issue, but I'm still perplexed as to why this resolves it. I would really appreciate any explanations that could be offered.
In my build.rs
file, the code that handles the static library linking contained this line:
println!("cargo:rustc-link-lib=static=stumpless");
While comparing my code to the tutorial I remembered that I added the =static
portion while looking at other examples and decided it was better to be more explicit. I took it out, and lo and behold, release builds now work correctly. That is, that line becomes:
println!("cargo:rustc-link-lib=stumpless");
It's still static, at least to my knowledge: I disable shared library building in the CMake build step, so there's no dynamic library for it to find. Does anyone know why specifying the type of library would have such a catastrophic effect on release builds? I did check to make sure that I haven't accidentally installed the dynamic library, and it is not there (that is, ldconfig -p | grep stumpless
yields no output.
Upvotes: 7
Views: 2427
Reputation: 120
I just ran into this issue as well while trying to link with lz4
via the lz4
crate.
cargo test
compiled fine and tests were green, so the code was run. However, cargo test --release
fails with undefined reference to LZ4_decompress_safe_partial
I personally "fixed" it by simply using a different linker. I put the following in my ~/.cargo/config
file and then it worked without problems.
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
Upvotes: 1