Skip to content

Commit cdca384

Browse files
authored
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 #135694
2 parents 2a8bb6e + e31876c commit cdca384

File tree

8 files changed

+267
-29
lines changed

8 files changed

+267
-29
lines changed

compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs

Lines changed: 106 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
44

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

219219
let mut stub_buf = Vec::new();
220220

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

229+
let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features)
230+
else {
231+
sess.dcx().fatal(format!(
232+
"raw-dylib is not supported for the architecture `{}`",
233+
sess.target.arch
234+
));
235+
};
236+
229237
let endianness = match sess.target.options.endian {
230238
Endian::Little => object::Endianness::Little,
231239
Endian::Big => object::Endianness::Big,
232240
};
233-
let mut stub = write::Writer::new(endianness, true, &mut stub_buf);
241+
242+
let is_64 = match arch.address_size() {
243+
Some(AddressSize::U8 | AddressSize::U16 | AddressSize::U32) => false,
244+
Some(AddressSize::U64) => true,
245+
_ => sess.dcx().fatal(format!(
246+
"raw-dylib is not supported for the architecture `{}`",
247+
sess.target.arch
248+
)),
249+
};
250+
251+
let mut stub = write::Writer::new(endianness, is_64, &mut stub_buf);
252+
253+
let mut vers = Vec::new();
254+
let mut vers_map = FxHashMap::default();
255+
let mut syms = Vec::new();
256+
257+
for symbol in symbols {
258+
let symbol_name = symbol.name.as_str();
259+
if let Some((name, version_name)) = symbol_name.split_once('@') {
260+
assert!(!version_name.contains('@'));
261+
let dynstr = stub.add_dynamic_string(name.as_bytes());
262+
let ver = if let Some(&ver_id) = vers_map.get(version_name) {
263+
ver_id
264+
} else {
265+
let id = vers.len();
266+
vers_map.insert(version_name, id);
267+
let dynstr = stub.add_dynamic_string(version_name.as_bytes());
268+
vers.push((version_name, dynstr));
269+
id
270+
};
271+
syms.push((name, dynstr, Some(ver)));
272+
} else {
273+
let dynstr = stub.add_dynamic_string(symbol_name.as_bytes());
274+
syms.push((symbol_name, dynstr, None));
275+
}
276+
}
277+
278+
let soname = stub.add_dynamic_string(soname.as_bytes());
234279

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

238-
// First, we crate the dynamic symbol table. It starts with a null symbol
283+
// First, we create the dynamic symbol table. It starts with a null symbol
239284
// and then all the symbols and their dynamic strings.
240285
stub.reserve_null_dynamic_symbol_index();
241286

242-
let dynstrs = symbols
243-
.iter()
244-
.map(|sym| {
245-
stub.reserve_dynamic_symbol_index();
246-
(sym, stub.add_dynamic_string(sym.name.as_str().as_bytes()))
247-
})
248-
.collect::<Vec<_>>();
249-
250-
let soname = stub.add_dynamic_string(soname.as_bytes());
287+
for _ in syms.iter() {
288+
stub.reserve_dynamic_symbol_index();
289+
}
251290

252291
// Reserve the sections.
253292
// We have the minimal sections for a dynamic SO and .text where we point our dummy symbols to.
254293
stub.reserve_shstrtab_section_index();
255294
let text_section_name = stub.add_section_name(".text".as_bytes());
256295
let text_section = stub.reserve_section_index();
257-
stub.reserve_dynstr_section_index();
258296
stub.reserve_dynsym_section_index();
297+
stub.reserve_dynstr_section_index();
298+
if !vers.is_empty() {
299+
stub.reserve_gnu_versym_section_index();
300+
stub.reserve_gnu_verdef_section_index();
301+
}
259302
stub.reserve_dynamic_section_index();
260303

261304
// These reservations now determine the actual layout order of the object file.
262305
stub.reserve_file_header();
263306
stub.reserve_shstrtab();
264307
stub.reserve_section_headers();
265-
stub.reserve_dynstr();
266308
stub.reserve_dynsym();
309+
stub.reserve_dynstr();
310+
if !vers.is_empty() {
311+
stub.reserve_gnu_versym();
312+
stub.reserve_gnu_verdef(1 + vers.len(), 1 + vers.len());
313+
}
267314
stub.reserve_dynamic(2); // DT_SONAME, DT_NULL
268315

269316
// First write the ELF header with the arch information.
270-
let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features)
271-
else {
272-
sess.dcx().fatal(format!(
273-
"raw-dylib is not supported for the architecture `{}`",
274-
sess.target.arch
275-
));
276-
};
277317
let e_machine = match (arch, sub_arch) {
278318
(Architecture::Aarch64, None) => elf::EM_AARCH64,
279319
(Architecture::Aarch64_Ilp32, None) => elf::EM_AARCH64,
@@ -342,18 +382,19 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
342382
sh_addralign: 1,
343383
sh_entsize: 0,
344384
});
345-
stub.write_dynstr_section_header(0);
346385
stub.write_dynsym_section_header(0, 1);
386+
stub.write_dynstr_section_header(0);
387+
if !vers.is_empty() {
388+
stub.write_gnu_versym_section_header(0);
389+
stub.write_gnu_verdef_section_header(0);
390+
}
347391
stub.write_dynamic_section_header(0);
348392

349-
// .dynstr
350-
stub.write_dynstr();
351-
352393
// .dynsym
353394
stub.write_null_dynamic_symbol();
354-
for (_, name) in dynstrs {
395+
for (_name, dynstr, _ver) in syms.iter().copied() {
355396
stub.write_dynamic_symbol(&write::Sym {
356-
name: Some(name),
397+
name: Some(dynstr),
357398
st_info: (elf::STB_GLOBAL << 4) | elf::STT_NOTYPE,
358399
st_other: elf::STV_DEFAULT,
359400
section: Some(text_section),
@@ -363,10 +404,47 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
363404
});
364405
}
365406

407+
// .dynstr
408+
stub.write_dynstr();
409+
410+
// ld.bfd is unhappy if these sections exist without any symbols, so we only generate them when necessary.
411+
if !vers.is_empty() {
412+
// .gnu_version
413+
stub.write_null_gnu_versym();
414+
for (_name, _dynstr, ver) in syms.iter().copied() {
415+
stub.write_gnu_versym(if let Some(ver) = ver {
416+
assert!((2 + ver as u16) < elf::VERSYM_HIDDEN);
417+
elf::VERSYM_HIDDEN | (2 + ver as u16)
418+
} else {
419+
1
420+
});
421+
}
422+
423+
// .gnu_version_d
424+
stub.write_align_gnu_verdef();
425+
stub.write_gnu_verdef(&write::Verdef {
426+
version: elf::VER_DEF_CURRENT,
427+
flags: elf::VER_FLG_BASE,
428+
index: 1,
429+
aux_count: 1,
430+
name: soname,
431+
});
432+
for (ver, (_name, dynstr)) in vers.into_iter().enumerate() {
433+
stub.write_gnu_verdef(&write::Verdef {
434+
version: elf::VER_DEF_CURRENT,
435+
flags: 0,
436+
index: 2 + ver as u16,
437+
aux_count: 1,
438+
name: dynstr,
439+
});
440+
}
441+
}
442+
366443
// .dynamic
367444
// the DT_SONAME will be used by the linker to populate DT_NEEDED
368445
// which the loader uses to find the library.
369446
// DT_NULL terminates the .dynamic table.
447+
stub.write_align_dynamic();
370448
stub.write_dynamic_string(elf::DT_SONAME, soname);
371449
stub.write_dynamic(elf::DT_NULL, 0);
372450

compiler/rustc_metadata/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,6 @@ metadata_wasm_import_form =
330330
331331
metadata_whole_archive_needs_static =
332332
linking modifier `whole-archive` is only compatible with `static` linking kind
333+
334+
metadata_raw_dylib_malformed =
335+
link name must be well-formed if link kind is `raw-dylib`

compiler/rustc_metadata/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,3 +815,10 @@ pub struct AsyncDropTypesInDependency {
815815
pub extern_crate: Symbol,
816816
pub local_crate: Symbol,
817817
}
818+
819+
#[derive(Diagnostic)]
820+
#[diag(metadata_raw_dylib_malformed)]
821+
pub struct RawDylibMalformed {
822+
#[primary_span]
823+
pub span: Span,
824+
}

compiler/rustc_metadata/src/native_libs.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,8 +700,21 @@ impl<'tcx> Collector<'tcx> {
700700
.link_ordinal
701701
.map_or(import_name_type, |ord| Some(PeImportNameType::Ordinal(ord)));
702702

703+
let name = codegen_fn_attrs.link_name.unwrap_or_else(|| self.tcx.item_name(item));
704+
705+
if self.tcx.sess.target.binary_format == BinaryFormat::Elf {
706+
let name = name.as_str();
707+
if name.contains('\0') {
708+
self.tcx.dcx().emit_err(errors::RawDylibMalformed { span });
709+
} else if let Some((left, right)) = name.split_once('@')
710+
&& (left.is_empty() || right.is_empty() || right.contains('@'))
711+
{
712+
self.tcx.dcx().emit_err(errors::RawDylibMalformed { span });
713+
}
714+
}
715+
703716
DllImport {
704-
name: codegen_fn_attrs.link_name.unwrap_or_else(|| self.tcx.item_name(item)),
717+
name,
705718
import_name_type,
706719
calling_convention,
707720
span,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//@ only-x86_64-unknown-linux-gnu
2+
//@ needs-dynamic-linking
3+
//@ build-pass
4+
5+
#![allow(incomplete_features)]
6+
#![feature(raw_dylib_elf)]
7+
8+
#[link(name = "hack", kind = "raw-dylib")]
9+
unsafe extern "C" {}
10+
11+
fn main() {}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//@ only-x86_64-unknown-linux-gnu
2+
//@ needs-dynamic-linking
3+
//@ run-pass
4+
//@ compile-flags: -Cpanic=abort
5+
//@ edition: 2024
6+
7+
#![allow(incomplete_features)]
8+
#![feature(raw_dylib_elf)]
9+
#![no_std]
10+
#![no_main]
11+
12+
use core::ffi::{c_char, c_int};
13+
14+
extern "C" fn callback(
15+
_fpath: *const c_char,
16+
_sb: *const (),
17+
_tflag: c_int,
18+
_ftwbuf: *const (),
19+
) -> c_int {
20+
0
21+
}
22+
23+
// `libc.so` is a linker script that provides the paths to `libc.so.6` and `libc_nonshared.a`.
24+
// In earlier versions of glibc, `libc_nonshared.a` provides the symbols `__libc_csu_init` and
25+
// `__libc_csu_fini` required by `Scrt1.o`.
26+
#[link(name = "c_nonshared", kind = "static")]
27+
unsafe extern "C" {}
28+
29+
#[link(name = "libc.so.6", kind = "raw-dylib", modifiers = "+verbatim")]
30+
unsafe extern "C" {
31+
#[link_name = "nftw@GLIBC_2.2.5"]
32+
unsafe fn nftw_2_2_5(
33+
dirpath: *const c_char,
34+
f: extern "C" fn(*const c_char, *const (), c_int, *const ()) -> c_int,
35+
nopenfd: c_int,
36+
flags: c_int,
37+
) -> c_int;
38+
#[link_name = "nftw@GLIBC_2.3.3"]
39+
unsafe fn nftw_2_3_3(
40+
dirpath: *const c_char,
41+
f: extern "C" fn(*const c_char, *const (), c_int, *const ()) -> c_int,
42+
nopenfd: c_int,
43+
flags: c_int,
44+
) -> c_int;
45+
#[link_name = "exit@GLIBC_2.2.5"]
46+
safe fn exit(status: i32) -> !;
47+
unsafe fn __libc_start_main() -> c_int;
48+
}
49+
50+
#[unsafe(no_mangle)]
51+
extern "C" fn main() -> ! {
52+
unsafe {
53+
// The old `nftw` does not check whether unknown flags are set.
54+
let res = nftw_2_2_5(c".".as_ptr(), callback, 20, 1 << 30);
55+
assert_eq!(res, 0);
56+
}
57+
unsafe {
58+
// The new `nftw` does.
59+
let res = nftw_2_3_3(c".".as_ptr(), callback, 20, 1 << 30);
60+
assert_eq!(res, -1);
61+
}
62+
exit(0);
63+
}
64+
65+
#[cfg(not(test))]
66+
#[panic_handler]
67+
fn panic_handler(_: &core::panic::PanicInfo<'_>) -> ! {
68+
exit(1);
69+
}
70+
71+
#[unsafe(no_mangle)]
72+
extern "C" fn rust_eh_personality(
73+
_version: i32,
74+
_actions: i32,
75+
_exception_class: u64,
76+
_exception_object: *mut (),
77+
_context: *mut (),
78+
) -> i32 {
79+
exit(1);
80+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ only-elf
2+
//@ needs-dynamic-linking
3+
//@ check-fail
4+
5+
#![feature(raw_dylib_elf)]
6+
#![allow(incomplete_features)]
7+
8+
#[link(name = "libc.so.6", kind = "raw-dylib", modifiers = "+verbatim")]
9+
unsafe extern "C" {
10+
#[link_name = "exit@"]
11+
pub safe fn exit_0(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib`
12+
#[link_name = "@GLIBC_2.2.5"]
13+
pub safe fn exit_1(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib`
14+
#[link_name = "ex\0it@GLIBC_2.2.5"]
15+
pub safe fn exit_2(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib`
16+
#[link_name = "exit@@GLIBC_2.2.5"]
17+
pub safe fn exit_3(status: i32) -> !; //~ ERROR link name must be well-formed if link kind is `raw-dylib`
18+
}
19+
20+
fn main() {}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: link name must be well-formed if link kind is `raw-dylib`
2+
--> $DIR/malformed-link-name.rs:11:5
3+
|
4+
LL | pub safe fn exit_0(status: i32) -> !;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: link name must be well-formed if link kind is `raw-dylib`
8+
--> $DIR/malformed-link-name.rs:13:5
9+
|
10+
LL | pub safe fn exit_1(status: i32) -> !;
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: link name must be well-formed if link kind is `raw-dylib`
14+
--> $DIR/malformed-link-name.rs:15:5
15+
|
16+
LL | pub safe fn exit_2(status: i32) -> !;
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
19+
error: link name must be well-formed if link kind is `raw-dylib`
20+
--> $DIR/malformed-link-name.rs:17:5
21+
|
22+
LL | pub safe fn exit_3(status: i32) -> !;
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24+
25+
error: aborting due to 4 previous errors
26+

0 commit comments

Comments
 (0)