Skip to content

generate elf symbol version in raw-dylib #144221

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

Merged
merged 1 commit into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 106 additions & 28 deletions compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};

use rustc_abi::Endian;
use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN};
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_data_structures::stable_hasher::StableHasher;
use rustc_hashes::Hash128;
use rustc_session::Session;
Expand Down Expand Up @@ -214,7 +214,7 @@ pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>(
/// It exports all the provided symbols, but is otherwise empty.
fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]) -> Vec<u8> {
use object::write::elf as write;
use object::{Architecture, elf};
use object::{AddressSize, Architecture, elf};

let mut stub_buf = Vec::new();

Expand All @@ -226,54 +226,94 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
// It is important that the order of reservation matches the order of writing.
// The object crate contains many debug asserts that fire if you get this wrong.

let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features)
else {
sess.dcx().fatal(format!(
"raw-dylib is not supported for the architecture `{}`",
sess.target.arch
));
};

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

let is_64 = match arch.address_size() {
Some(AddressSize::U8 | AddressSize::U16 | AddressSize::U32) => false,
Some(AddressSize::U64) => true,
_ => sess.dcx().fatal(format!(
"raw-dylib is not supported for the architecture `{}`",
sess.target.arch
)),
};

let mut stub = write::Writer::new(endianness, is_64, &mut stub_buf);

let mut vers = Vec::new();
let mut vers_map = FxHashMap::default();
let mut syms = Vec::new();

for symbol in symbols {
let symbol_name = symbol.name.as_str();
if let Some((name, version_name)) = symbol_name.split_once('@') {
assert!(!version_name.contains('@'));
let dynstr = stub.add_dynamic_string(name.as_bytes());
let ver = if let Some(&ver_id) = vers_map.get(version_name) {
ver_id
} else {
let id = vers.len();
vers_map.insert(version_name, id);
let dynstr = stub.add_dynamic_string(version_name.as_bytes());
vers.push((version_name, dynstr));
id
};
syms.push((name, dynstr, Some(ver)));
} else {
let dynstr = stub.add_dynamic_string(symbol_name.as_bytes());
syms.push((symbol_name, dynstr, None));
}
}

let soname = stub.add_dynamic_string(soname.as_bytes());

// These initial reservations don't reserve any bytes in the binary yet,
// they just allocate in the internal data structures.

// First, we crate the dynamic symbol table. It starts with a null symbol
// First, we create the dynamic symbol table. It starts with a null symbol
// and then all the symbols and their dynamic strings.
stub.reserve_null_dynamic_symbol_index();

let dynstrs = symbols
.iter()
.map(|sym| {
stub.reserve_dynamic_symbol_index();
(sym, stub.add_dynamic_string(sym.name.as_str().as_bytes()))
})
.collect::<Vec<_>>();

let soname = stub.add_dynamic_string(soname.as_bytes());
for _ in syms.iter() {
stub.reserve_dynamic_symbol_index();
}

// Reserve the sections.
// We have the minimal sections for a dynamic SO and .text where we point our dummy symbols to.
stub.reserve_shstrtab_section_index();
let text_section_name = stub.add_section_name(".text".as_bytes());
let text_section = stub.reserve_section_index();
stub.reserve_dynstr_section_index();
stub.reserve_dynsym_section_index();
stub.reserve_dynstr_section_index();
if !vers.is_empty() {
stub.reserve_gnu_versym_section_index();
stub.reserve_gnu_verdef_section_index();
}
stub.reserve_dynamic_section_index();

// These reservations now determine the actual layout order of the object file.
stub.reserve_file_header();
stub.reserve_shstrtab();
stub.reserve_section_headers();
stub.reserve_dynstr();
stub.reserve_dynsym();
stub.reserve_dynstr();
if !vers.is_empty() {
stub.reserve_gnu_versym();
stub.reserve_gnu_verdef(1 + vers.len(), 1 + vers.len());
}
stub.reserve_dynamic(2); // DT_SONAME, DT_NULL

// First write the ELF header with the arch information.
let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features)
else {
sess.dcx().fatal(format!(
"raw-dylib is not supported for the architecture `{}`",
sess.target.arch
));
};
let e_machine = match (arch, sub_arch) {
(Architecture::Aarch64, None) => elf::EM_AARCH64,
(Architecture::Aarch64_Ilp32, None) => elf::EM_AARCH64,
Expand Down Expand Up @@ -342,18 +382,19 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
sh_addralign: 1,
sh_entsize: 0,
});
stub.write_dynstr_section_header(0);
stub.write_dynsym_section_header(0, 1);
stub.write_dynstr_section_header(0);
if !vers.is_empty() {
stub.write_gnu_versym_section_header(0);
stub.write_gnu_verdef_section_header(0);
}
stub.write_dynamic_section_header(0);

// .dynstr
stub.write_dynstr();

// .dynsym
stub.write_null_dynamic_symbol();
for (_, name) in dynstrs {
for (_name, dynstr, _ver) in syms.iter().copied() {
stub.write_dynamic_symbol(&write::Sym {
name: Some(name),
name: Some(dynstr),
st_info: (elf::STB_GLOBAL << 4) | elf::STT_NOTYPE,
st_other: elf::STV_DEFAULT,
section: Some(text_section),
Expand All @@ -363,10 +404,47 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
});
}

// .dynstr
stub.write_dynstr();

// ld.bfd is unhappy if these sections exist without any symbols, so we only generate them when necessary.
if !vers.is_empty() {
// .gnu_version
stub.write_null_gnu_versym();
for (_name, _dynstr, ver) in syms.iter().copied() {
stub.write_gnu_versym(if let Some(ver) = ver {
assert!((2 + ver as u16) < elf::VERSYM_HIDDEN);
elf::VERSYM_HIDDEN | (2 + ver as u16)
} else {
1
});
}

// .gnu_version_d
stub.write_align_gnu_verdef();
stub.write_gnu_verdef(&write::Verdef {
version: elf::VER_DEF_CURRENT,
flags: elf::VER_FLG_BASE,
index: 1,
aux_count: 1,
name: soname,
});
for (ver, (_name, dynstr)) in vers.into_iter().enumerate() {
stub.write_gnu_verdef(&write::Verdef {
version: elf::VER_DEF_CURRENT,
flags: 0,
index: 2 + ver as u16,
aux_count: 1,
name: dynstr,
});
}
}

// .dynamic
// the DT_SONAME will be used by the linker to populate DT_NEEDED
// which the loader uses to find the library.
// DT_NULL terminates the .dynamic table.
stub.write_align_dynamic();
stub.write_dynamic_string(elf::DT_SONAME, soname);
stub.write_dynamic(elf::DT_NULL, 0);

Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_metadata/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,6 @@ metadata_wasm_import_form =

metadata_whole_archive_needs_static =
linking modifier `whole-archive` is only compatible with `static` linking kind

metadata_raw_dylib_malformed =
link name must be well-formed if link kind is `raw-dylib`
7 changes: 7 additions & 0 deletions compiler/rustc_metadata/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -815,3 +815,10 @@ pub struct AsyncDropTypesInDependency {
pub extern_crate: Symbol,
pub local_crate: Symbol,
}

#[derive(Diagnostic)]
#[diag(metadata_raw_dylib_malformed)]
pub struct RawDylibMalformed {
#[primary_span]
pub span: Span,
}
15 changes: 14 additions & 1 deletion compiler/rustc_metadata/src/native_libs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,8 +700,21 @@ impl<'tcx> Collector<'tcx> {
.link_ordinal
.map_or(import_name_type, |ord| Some(PeImportNameType::Ordinal(ord)));

let name = codegen_fn_attrs.link_name.unwrap_or_else(|| self.tcx.item_name(item));

if self.tcx.sess.target.binary_format == BinaryFormat::Elf {
let name = name.as_str();
if name.contains('\0') {
self.tcx.dcx().emit_err(errors::RawDylibMalformed { span });
} else if let Some((left, right)) = name.split_once('@')
&& (left.is_empty() || right.is_empty() || right.contains('@'))
{
self.tcx.dcx().emit_err(errors::RawDylibMalformed { span });
}
}

DllImport {
name: codegen_fn_attrs.link_name.unwrap_or_else(|| self.tcx.item_name(item)),
name,
import_name_type,
calling_convention,
span,
Expand Down
11 changes: 11 additions & 0 deletions tests/ui/linkage-attr/raw-dylib/elf/empty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//@ only-x86_64-unknown-linux-gnu
//@ needs-dynamic-linking
//@ build-pass

#![allow(incomplete_features)]
#![feature(raw_dylib_elf)]

#[link(name = "hack", kind = "raw-dylib")]
unsafe extern "C" {}

fn main() {}
80 changes: 80 additions & 0 deletions tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//@ only-x86_64-unknown-linux-gnu
//@ needs-dynamic-linking
//@ run-pass
//@ compile-flags: -Cpanic=abort
//@ edition: 2024

#![allow(incomplete_features)]
#![feature(raw_dylib_elf)]
#![no_std]
#![no_main]

use core::ffi::{c_char, c_int};

extern "C" fn callback(
_fpath: *const c_char,
_sb: *const (),
_tflag: c_int,
_ftwbuf: *const (),
) -> c_int {
0
}

// `libc.so` is a linker script that provides the paths to `libc.so.6` and `libc_nonshared.a`.
// In earlier versions of glibc, `libc_nonshared.a` provides the symbols `__libc_csu_init` and
// `__libc_csu_fini` required by `Scrt1.o`.
#[link(name = "c_nonshared", kind = "static")]
unsafe extern "C" {}

#[link(name = "libc.so.6", kind = "raw-dylib", modifiers = "+verbatim")]
unsafe extern "C" {
#[link_name = "nftw@GLIBC_2.2.5"]
unsafe fn nftw_2_2_5(
dirpath: *const c_char,
f: extern "C" fn(*const c_char, *const (), c_int, *const ()) -> c_int,
nopenfd: c_int,
flags: c_int,
) -> c_int;
#[link_name = "nftw@GLIBC_2.3.3"]
unsafe fn nftw_2_3_3(
dirpath: *const c_char,
f: extern "C" fn(*const c_char, *const (), c_int, *const ()) -> c_int,
nopenfd: c_int,
flags: c_int,
) -> c_int;
#[link_name = "exit@GLIBC_2.2.5"]
safe fn exit(status: i32) -> !;
unsafe fn __libc_start_main() -> c_int;
}

#[unsafe(no_mangle)]
extern "C" fn main() -> ! {
unsafe {
// The old `nftw` does not check whether unknown flags are set.
let res = nftw_2_2_5(c".".as_ptr(), callback, 20, 1 << 30);
assert_eq!(res, 0);
}
unsafe {
// The new `nftw` does.
let res = nftw_2_3_3(c".".as_ptr(), callback, 20, 1 << 30);
assert_eq!(res, -1);
}
exit(0);
}

#[cfg(not(test))]
#[panic_handler]
fn panic_handler(_: &core::panic::PanicInfo<'_>) -> ! {
exit(1);
}

#[unsafe(no_mangle)]
extern "C" fn rust_eh_personality(
_version: i32,
_actions: i32,
_exception_class: u64,
_exception_object: *mut (),
_context: *mut (),
) -> i32 {
exit(1);
}
20 changes: 20 additions & 0 deletions tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//@ only-elf
//@ needs-dynamic-linking
//@ check-fail

#![feature(raw_dylib_elf)]
#![allow(incomplete_features)]

#[link(name = "libc.so.6", kind = "raw-dylib", modifiers = "+verbatim")]
unsafe extern "C" {
#[link_name = "exit@"]
pub safe fn exit_0(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib`
#[link_name = "@GLIBC_2.2.5"]
pub safe fn exit_1(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib`
#[link_name = "ex\0it@GLIBC_2.2.5"]
pub safe fn exit_2(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib`
#[link_name = "exit@@GLIBC_2.2.5"]
pub safe fn exit_3(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib`
}

fn main() {}
26 changes: 26 additions & 0 deletions tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
error: link name must be well-formed if link kind is `raw-dylib`
--> $DIR/malformed-link-name.rs:11:5
|
LL | pub safe fn exit_0(status: i32) -> !;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: link name must be well-formed if link kind is `raw-dylib`
--> $DIR/malformed-link-name.rs:13:5
|
LL | pub safe fn exit_1(status: i32) -> !;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: link name must be well-formed if link kind is `raw-dylib`
--> $DIR/malformed-link-name.rs:15:5
|
LL | pub safe fn exit_2(status: i32) -> !;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: link name must be well-formed if link kind is `raw-dylib`
--> $DIR/malformed-link-name.rs:17:5
|
LL | pub safe fn exit_3(status: i32) -> !;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 4 previous errors

Loading