Rollup merge of #144221 - usamoi:versym, r=bjorn3

generate elf symbol version in raw-dylib

For link names like `aaa@bbb`, it generates a symbol named `aaa` and a version named `bbb`.

For link names like `aaa\0bbb`, `aaa@`@bbb`` or `aa@bb@cc`, it emits errors.

It adds a test that the executable is linked with glibc using raw-dylib.

cc rust-lang/rust#135694
This commit is contained in:
León Orell Valerian Liehr
2025-07-24 15:08:23 +02:00
committed by GitHub
8 changed files with 288 additions and 50 deletions

View File

@@ -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;
@@ -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();
@@ -226,47 +226,6 @@ 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 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);
// 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
// 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());
// 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_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_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!(
@@ -274,6 +233,87 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
sess.target.arch
));
};
let endianness = match sess.target.options.endian {
Endian::Little => object::Endianness::Little,
Endian::Big => object::Endianness::Big,
};
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 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();
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_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_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 e_machine = match (arch, sub_arch) {
(Architecture::Aarch64, None) => elf::EM_AARCH64,
(Architecture::Aarch64_Ilp32, None) => elf::EM_AARCH64,
@@ -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),
@@ -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);

View File

@@ -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`

View File

@@ -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,
}

View File

@@ -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,

View File

@@ -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() {}

View File

@@ -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);
}

View File

@@ -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() {}

View File

@@ -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