Remove default linker flags in Rust - rust

Is there a way to remove problematic linker flags in the cargo build command? I'm trying to cross-compile a binary on windows for x86_64-unknown-linux-gnu but linking fails with the message: ld unrecognized option: --eh-frame-hdr. Is there a way to remove this argument from the linking stage without having to create a custom target specification for linux builds?
I only found the rustflags key in config.toml, but as far as I know it only allows you to add additional flags and not to strip existing ones.

Start by making sure that you are passing the right linker-flavor. rustc might have misidentified what linker you are using, and specifying that might help. Also ensure you are using the right linker. But if you really want to remove that argument, it is possible using some unstable features.
There isn't a general way to remove individual linker arguments. The eh_frame_header target attribute indicates if the linker should insert a EH (exception handling) frame header. Unstable rustc does have support for changing individual target options, but it is a bit complicated and can only handle #![no_std] binaries on some platforms. You might want to consider just calling the linker manually.
First, dump the JSON target metadata (using a nightly compiler) for the target you are compiling to, replacing [target] with a target triple like thumbv7neon-unknown-linux-musleabihf:
$ rustc -Z unstable-options --print target-spec-json --target [target] > target.json
target.json now has target-specific compilation information. On some targets (such as WASM), EH frame headers are disabled by default. If you see a line like this in target.json, you are already done (and shouldn't be getting that error):
"eh-frame-header": false,
If you don't see that line, then add it to target.json right after opening brace, like
{
"eh-frame-header": false,
...
You can now specify the path to target.json wherever a target triple would normally be. However, you now need to recompile the standard libraries (such as core and std) with the new target information. Otherwise, the compiler will tell you that the `target-17676080659170949227` target may not be installed. On unstable Cargo, you can use -Z build-std
to do that:
$ cargo +nightly run -Z build-std=core --target target.json
It will take a bit longer than normal, since it needs to recompile the core standard library. Not all platforms support compiling the fuller std library this way, but you can change the build-std=core to just build-std if you want to try to build the whole std standard library as well.

Related

LLVM debug output through rust

I am looking for a way to read the LLVM debug output by invoking the rust compiler (through cargo). I am especially interested in output of LLVMs ASAN.
To run a build with ASAN I can do:
cargo clean && RUSTFLAGS="-Zsanitizer=address" cargo build
but I don't know the command to get the debug log ouput.
With clang I think one can add
-mllvm -debug-only=asan
as compiler-flag.
How can I supply something like this flag through rustc?
I am using the current rust source (https://github.com/rust-lang/rust/ commit 31f5d69) to build rustc myself.
The equivalent compiler argument for rustc is
-C llvm-args=-debug-only=<LLVM DEBUG_TYPE>
The -C part gives access to the options for code generation of rust.
The llvm-args part tells rustc to forward every subsequent flag to LLVM. If I understand correctly, you can specify every LLVM option that is evaluated with cl::opt() in LLVM source code.
So the full command to build a rust application with ASAN and see only the log output of ASAN would be:
cargo clean && RUSTFLAGS="-C llvm-args=-debug-only=asan -Zsanitizer=address" cargo build
Side note: I think debug mode for LLVM must be enabled in config.toml when building rust to see the respective log output.

rust, WebAssembly, and passing arguments for increased total memory

I have a rust project I am compiling to webasm per http://asquera.de/blog/2017-04-10/the-path-to-rust-on-the-web/
Project compiles. When I run it in Chrome Canary, it runs out of memory and gives me a very helpful error message:
abort("Cannot enlarge memory arrays. Either (1) compile with -s
TOTAL_MEMORY=X with X higher than the current value 16777216, (2) compile
with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime,
...
Problem is, its not clear how to pass those flags to rustc / the build tool chain.
Neither setting EMMAKEN_CFLAGS or the following work:
cargo rustc -v --target=wasm32-unknown-emscripten --release -- -Clink-args="-s TOTAL_MEMORY=33554432"
This blog post offers a solution that I think can be applied in your case too:
As best as I can tell there is no way to pass most linker arguments through cargo. Instead, hack around the limitation by specifying a custom linker that is actually a shell script wrapping the real linker.
Create a shell script like emcc_link that calls emscripten with the appropriate options:
emcc "-s" "TOTAL_MEMORY=33554432" $#
(You may need other options to make it work. Check the blog post for details.)
And specify to use it for your project by editing/creating .cargo/config:
[target.wasm32-unknown-emscripten]
linker = "/your/project/dir/emcc_sdl"
[target.asmjs-unknown-emscripten]
linker = "/your/project/dir/emcc_sdl"
I ruthlessly assumed the build environment is Linux or the like. On Windows the shell script should probably be a batch script and I'm not sure if there are any differences in .cargo/config.
Disclaimer: I have not tried any of this.

How to compile a static musl binary of a Rust project with native dependencies?

I have a project with dependencies on Hyper and Diesel, and because of that, on native libraries OpenSSL and libpq. The project builds on nightly Rust because it uses compiler plugins.
My current attempt is to build on a Docker container. I have the MUSL libc and the libraries make'd and installed with prefix /usr/local/musl. I run cargo with the following command: (Not sure if some of the options are redundant, I'm not too well-versed with the compiler chain, and not even sure if they end up to the linker, but I have to try, right.)
LDFLAGS="-static -L/usr/local/musl/lib" \
LD_LIBRARY_PATH=/usr/local/musl/lib:$LD_LIBRARY_PATH \
CFLAGS="-I/usr/local/musl/include" \
PKG_CONFIG_PATH=/usr/local/musl/lib/pkgconfig \
cargo build --release --target=x86_64-unknown-linux-musl
When I ldd the resulting file, it reveals this:
$ ldd server
linux-vdso.so.1 (0x00007fffb878e000)
libpq.so.5 => /usr/local/musl/lib/libpq.so.5 (0x00007f4d730e7000)
libssl.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007f4d72e82000)
libcrypto.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007f4d72a85000)
libc.so => /usr/local/musl/lib/libc.so (0x00007f4d727f6000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4d725f2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4d72246000)
/lib/ld64.so.1 => /lib64/ld-linux-x86-64.so.2 (0x000055e2124a2000)
There's all that dynamically linked stuff, and some even with the "x86_64-linux-gnu" chain! What went wrong?
I can make statically linked, simple pure-Rust projects without problems. ldd says that they are statically linked, and they run without problems, unlike the executable I have problems with.
When I used --verbose with Cargo, I got the following rustc command that actually builds the executable: http://pastebin.com/ywv0zNBK (Oops, that one had a custom outdir and -Z print-link-args, added by me)
Adding the print-link-args flag, I got the following linker command: http://pastebin.com/Aw43qd7h
How do I get cargo or rustc to believe that I want a static binary?
The problem was that for each crate providing a native dependency – say OpenSSL – there is the build.rs build script that is in charge of communicating the build and linking options to Cargo and to rustc. (For example: they print out something like cargo:rustc-link-lib=static=ssl which Cargo then reads and acts accordingly.)
So just setting the "standard" GCC environmental variables is hardly going to have any effect. You must check each and every build.rs separately to know how to coerce that exact crate to convey cargo its options. For OpenSSL, its env vars like OPENSSL_DIR, OPENSSL_STATIC etc.
Another hurdle is that if you use compiler plugins, they might be compiled with the target triplet too (at least docker_codegen). On the other hand, they are linked dynamically during the compiling process. This mean that not only must static libraries be linked correctly, you must also have dynamic libraries of the target host variety, like Musl libc.so in place, and correctly set (LD_LIBRARY_PATH etc.).
I made a thoroughly commented Dockerfile that builds my project statically with some native dependencies. It might be of help for others too.
https://gitlab.com/rust_musl_docker/image
If you want to statically link a Rust program without native dependencies, that is much easier:
$ rustup target add x86_64-unknown-linux-musl
$ cargo build --release --target=x86_64-unknown-linux-musl
I had the same problem with ldd and GCC. The musl target was generated in a different directory; not in target/release/... but in target/x86_64-unknown-linux-musl/release/....

Adding codegen flags to a Cargo build

On Macintosh, to allow some symbols to go unlinked, it is necessary to pass -C link-args='-Wl,-undefined,dynamic_lookup' to the Rust compiler. One needs to do this when building Postgres plugins, because some of the Postgres intrinsics are only compiled into the Postgres server, and not available for linking from shared libs.
At present, the project's process is as follows:
Build is run with cargo build -v.
Failing call to rustc is copied and -C link-args='-Wl,-undefined,dynamic_lookup' added to it.
Success!
This seems like a hard sell for automation. What options are available for adding codegen flags to Rust builds through cargo?
cargo provides rustc command which allows one to pass arbitrary compiler flags. The following should do it:
% cargo rustc -- -C link-args='-Wl,-undefined,dynamic_lookup'

How to I tell Rust where to look for a static library?

Is there any way to tell Rust where to look for my static library? Example code
#[link(name = "/this/is/the/path/libfoo.a", kind = "static")]
If not, what config change can I make or what folder do I place my library in so that I can use it?
rustc invokes the system linker which looks for all libraries specified in #[link(...)] in library directories. There are usually several default library directories (like /lib and /usr/lib on Linux), and more can be specified via linker flags (rustc accepts -L options which it then passes through to the linker).
If you invoke rustc directly, you can use the -L option to add additional library directories which will be then passed through to the linker. If you use Cargo, however, you've got a few more options:
Cargo adds the /target/<profile>/deps directory as a library source directory.
You can use cargo rustc
cargo rustc -- -L /path/to/library/directory
You can specify the RUSTFLAGS environment variable:
RUSTFLAGS='-L /path/to/library/directory' cargo build
You can use a build script to output more linker options
println!("cargo:rustc-link-lib=static=foo");
println!("cargo:rustc-link-search=native=/path/to/foo");
The easiest way for you, I think, is to add a custom build script which will copy or create a symlink to your library in the corresponding /target/<profile>/deps directory.
To add on to the accepted answer, what worked for me is as following:
under debug build, putting dependency files under target/debug/deps worked; but putting files under target/debug/native/* did not seem to work.
Cargo seems to only look under target/debug/deps by default.
You can run with cargo build --verbose to see the verbose rustc commands and the options used. -L option specifies additional link dependency directory.

Resources