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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
11
tests/ui/linkage-attr/raw-dylib/elf/empty.rs
Normal file
11
tests/ui/linkage-attr/raw-dylib/elf/empty.rs
Normal 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() {}
|
||||
80
tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs
Normal file
80
tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs
Normal 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);
|
||||
}
|
||||
20
tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs
Normal file
20
tests/ui/linkage-attr/raw-dylib/elf/malformed-link-name.rs
Normal 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() {}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user