Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support raw-dylib link kind on ELF #135695

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Noratrieb
Copy link
Member

raw-dylib is a link kind that allows rustc to link against a library without having any library files present.
This currently only exists on Windows. rustc will take all the symbols from raw-dylib link blocks and put them in an import library, where they can then be resolved by the linker.

While import libraries don't exist on ELF, it would still be convenient to have this same functionality. Not having the libraries present at build-time can be convenient for several reasons, especially cross-compilation. With raw-dylib, code linking against a library can be cross-compiled without needing to have these libraries available on the build machine. If the libc crate makes use of this, it would allow cross-compilation without having any libc available on the build machine. This is not yet possible with this implementation, at least against libc's like glibc that use symbol versioning. The raw-dylib kind could be extended with support for symbol versioning in the future.

This implementation is very experimental and I have not tested it very well. I have tested it for a toy example and the lz4-sys crate, where it was able to successfully link a binary despite not having a corresponding library at build-time.

I was inspired by Björn's comments in https://internals.rust-lang.org/t/bundle-zig-cc-in-rustup-by-default/22096/27
Tracking issue: #135694

r? bjorn3

@rustbot rustbot added A-compiletest Area: The compiletest test runner A-testsuite Area: The testsuite used to check the correctness of rustc S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jan 18, 2025
@rustbot
Copy link
Collaborator

rustbot commented Jan 18, 2025

These commits modify compiler targets.
(See the Target Tier Policy.)

Some changes occurred in src/tools/compiletest

cc @jieyouxu

@rust-log-analyzer

This comment has been minimized.

@@ -2346,19 +2413,34 @@ fn linker_with_args(
.native_libraries
.iter()
.filter_map(|(&cnum, libraries)| {
(dependency_linkage[cnum] != Linkage::Static).then_some(libraries)
if sess.target.is_like_windows {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine as is for now, but once Apple targets support raw-dylib, this static deps skipping behavior should likely apply there too if you are allowed to put .tbd files in static libraries.

let ext = if sess.target.is_like_windows {
if lib.verbatim { "" } else { ".dll" }
} else {
".so"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be possible to handle verbatim by emitting a SONAME in the stub shared library with the actual name of the shared library and then have the stub have a name starting with lib and ending with .so like usual. We will need to support verbatim for linking against shared libraries like libc.so.6 (glibc) or basically any shared library that is part of a distro.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need to set SONAME for verbatim? Couldn't we just name the stub library after the verbatim name and get it working directly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because the linker requires the filename of every shared library to start with lib and end with .so, but with versioned shared libraries the name ends with .so.<version>. The way this normally works is by having libfoo.so.1 contains a SONAME of libfoo.so.1 and then have a symlink from libfoo.so to libfoo.so.1 in the -dev package. The linker will see libfoo.so and then encode libfoo.so.1 in the executable because of the SONAME.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand. You mentioned verbatim, but with verbatim we can just pass the -l:libfoo.so.1 to the linker and it will pick that up for the NEEDED so name. So there isn't anything we need to do to make this work. Since we do need to know the library version from the source code, verbatim is needed anyways for all these cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but with verbatim we can just pass the -l:libfoo.so.1 to the linker and it will pick that up for the NEEDED so name.

What if the user passes an absolute path as verbatim name? NEEDED entries accept absolute paths just fine, but -l: would look at the wrong location. SONAME can encode arbitrary values to set as NEEDED without messing up the search logic of the linker. Also using SONAME + random filename for the stub library should still allow #[link(name = "foo")] to still look for the system libfoo.so even if there is a #[link_name = "foo", kind = "raw-dylib")] only encodes a subset of the functions available in libfoo.so (as the crate in question didn't need the rest). If the stub library is called libfoo.so it would take precedence over the system install and thus every raw-dylib block would need to exhaustively list all functions, including those not needed by the crate in question to avoid breaking someones build.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, that makes sense. I have changed the strategy to accommodate this by always generating it with a random file name and setting the SONAME.

@rust-log-analyzer

This comment has been minimized.

@Noratrieb Noratrieb force-pushed the elf-raw-dylib branch 2 times, most recently from 7b85a6a to f339acd Compare January 18, 2025 21:30
@rustbot

This comment was marked as outdated.

@rustbot rustbot added the A-run-make Area: port run-make Makefiles to rmake.rs label Jan 18, 2025
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

Copy link
Member

@jieyouxu jieyouxu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compiletest changes LGTM, would be great if you could add the two new directives to the directives listing in r-d-g

compiler/rustc_target/src/spec/mod.rs Show resolved Hide resolved
src/tools/compiletest/src/directive-list.rs Show resolved Hide resolved
@rustbot
Copy link
Collaborator

rustbot commented Jan 19, 2025

The rustc-dev-guide subtree was changed. If this PR only touches the dev guide consider submitting a PR directly to rust-lang/rustc-dev-guide otherwise thank you for updating the dev guide with your changes.

cc @BoxyUwU, @jieyouxu, @Kobzol

@rustbot rustbot added the A-rustc-dev-guide Area: rustc-dev-guide label Jan 19, 2025
raw-dylib is a link kind that allows rustc to link against a library
without having any library files present.
This currently only exists on Windows. rustc will take all the symbols
from raw-dylib link blocks and put them in an import library, where they
can then be resolved by the linker.

While import libraries don't exist on ELF, it would still be convenient
to have this same functionality. Not having the libraries present at
build-time can be convenient for several reasons, especially
cross-compilation. With raw-dylib, code linking against a library can be
cross-compiled without needing to have these libraries available on the
build machine. If the libc crate makes use of this, it would allow
cross-compilation without having any libc available on the build
machine. This is not yet possible with this implementation, at least
against libc's like glibc that use symbol versioning.
The raw-dylib kind could be extended with support for symbol versioning
in the future.

This implementation is very experimental and I have not tested it very
well. I have tested it for a toy example and the lz4-sys crate, where it
was able to successfully link a binary despite not having a
corresponding library at build-time.
@rustbot
Copy link
Collaborator

rustbot commented Jan 21, 2025

These commits modify the Cargo.lock file. Unintentional changes to Cargo.lock can be introduced when switching branches and rebasing PRs.

If this was unintentional then you should revert the changes before this PR is merged.
Otherwise, you can ignore this comment.

@bors
Copy link
Contributor

bors commented Jan 23, 2025

☔ The latest upstream changes (presumably #135164) made this pull request unmergeable. Please resolve the merge conflicts.

let shared_object = create_elf_raw_dylib_stub(&load_filename, &raw_dylib_imports, sess);

let random_number = rand::thread_rng().next_u32();
let temporary_lib_name = format!("{random_number}{}", sess.target.dll_suffix);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead use a hash of load_filename and raw_dylib_imports? I'm afraid this non-determinism will leak into debuginfo.

(Architecture::Sparc, None) => elf::EM_SPARC,
(Architecture::Sparc32Plus, None) => elf::EM_SPARC32PLUS,
(Architecture::Sparc64, None) => elf::EM_SPARCV9,
(Architecture::Xtensa, None) => elf::EM_XTENSA,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would have been nice if the object crate exposed a method for this.

// When using the low-level object::write::elf, the order of the reservations
// needs to match the order of the writing.

let mut stub = write::Writer::new(object::Endianness::Little, true, &mut stub_buf);
Copy link
Member

@bjorn3 bjorn3 Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let mut stub = write::Writer::new(object::Endianness::Little, true, &mut stub_buf);
let endianness = match sess.target.options.endian {
Endian::Little => object::Endianness::Little,
Endian::Big => object::Endianness::Big,
};
let mut stub = write::Writer::new(endianness, sess.target.pointer_width == 64, &mut stub_buf);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-compiletest Area: The compiletest test runner A-run-make Area: port run-make Makefiles to rmake.rs A-rustc-dev-guide Area: rustc-dev-guide A-testsuite Area: The testsuite used to check the correctness of rustc S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants