Skip to content

Commit 603f452

Browse files
committed
generate elf symbol version in raw-dylib
1 parent 12865ff commit 603f452

File tree

9 files changed

+229
-19
lines changed

9 files changed

+229
-19
lines changed

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

Lines changed: 77 additions & 18 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;
@@ -230,40 +230,66 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
230230
Endian::Little => object::Endianness::Little,
231231
Endian::Big => object::Endianness::Big,
232232
};
233+
233234
let mut stub = write::Writer::new(endianness, true, &mut stub_buf);
234235

236+
let mut vers = Vec::new();
237+
let mut vers_map = FxHashMap::default();
238+
let mut syms = Vec::new();
239+
240+
for symbol in symbols {
241+
let symbol_name = symbol.name.as_str();
242+
if let Some((name, version_name)) = symbol_name.split_once('@') {
243+
assert!(!version_name.contains('@'));
244+
let dynstr = stub.add_dynamic_string(name.as_bytes());
245+
let ver = if let Some(&ver_id) = vers_map.get(version_name) {
246+
ver_id
247+
} else {
248+
let id = vers.len();
249+
vers_map.insert(version_name, id);
250+
let dynstr = stub.add_dynamic_string(version_name.as_bytes());
251+
vers.push((version_name, dynstr));
252+
id
253+
};
254+
syms.push((name, dynstr, Some(ver)));
255+
} else {
256+
let dynstr = stub.add_dynamic_string(symbol_name.as_bytes());
257+
syms.push((symbol_name, dynstr, None));
258+
}
259+
}
260+
261+
let soname = stub.add_dynamic_string(soname.as_bytes());
262+
235263
// These initial reservations don't reserve any bytes in the binary yet,
236264
// they just allocate in the internal data structures.
237265

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

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());
270+
for _ in syms.iter() {
271+
stub.reserve_dynamic_symbol_index();
272+
}
251273

252274
// Reserve the sections.
253275
// We have the minimal sections for a dynamic SO and .text where we point our dummy symbols to.
254276
stub.reserve_shstrtab_section_index();
255277
let text_section_name = stub.add_section_name(".text".as_bytes());
256278
let text_section = stub.reserve_section_index();
257-
stub.reserve_dynstr_section_index();
258279
stub.reserve_dynsym_section_index();
280+
stub.reserve_dynstr_section_index();
281+
stub.reserve_gnu_versym_section_index();
282+
stub.reserve_gnu_verdef_section_index();
259283
stub.reserve_dynamic_section_index();
260284

261285
// These reservations now determine the actual layout order of the object file.
262286
stub.reserve_file_header();
263287
stub.reserve_shstrtab();
264288
stub.reserve_section_headers();
265-
stub.reserve_dynstr();
266289
stub.reserve_dynsym();
290+
stub.reserve_dynstr();
291+
stub.reserve_gnu_versym();
292+
stub.reserve_gnu_verdef(1 + vers.len(), 1 + vers.len());
267293
stub.reserve_dynamic(2); // DT_SONAME, DT_NULL
268294

269295
// First write the ELF header with the arch information.
@@ -342,18 +368,17 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
342368
sh_addralign: 1,
343369
sh_entsize: 0,
344370
});
345-
stub.write_dynstr_section_header(0);
346371
stub.write_dynsym_section_header(0, 1);
372+
stub.write_dynstr_section_header(0);
373+
stub.write_gnu_versym_section_header(0);
374+
stub.write_gnu_verdef_section_header(0);
347375
stub.write_dynamic_section_header(0);
348376

349-
// .dynstr
350-
stub.write_dynstr();
351-
352377
// .dynsym
353378
stub.write_null_dynamic_symbol();
354-
for (_, name) in dynstrs {
379+
for (_name, dynstr, _ver) in syms.iter().copied() {
355380
stub.write_dynamic_symbol(&write::Sym {
356-
name: Some(name),
381+
name: Some(dynstr),
357382
st_info: (elf::STB_GLOBAL << 4) | elf::STT_NOTYPE,
358383
st_other: elf::STV_DEFAULT,
359384
section: Some(text_section),
@@ -363,10 +388,44 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]
363388
});
364389
}
365390

391+
// .dynstr
392+
stub.write_dynstr();
393+
394+
// .gnu_version
395+
stub.write_null_gnu_versym();
396+
for (_name, _dynstr, ver) in syms.iter().copied() {
397+
stub.write_gnu_versym(if let Some(ver) = ver {
398+
assert!((2 + ver as u16) < elf::VERSYM_HIDDEN);
399+
elf::VERSYM_HIDDEN | (2 + ver as u16)
400+
} else {
401+
1
402+
});
403+
}
404+
405+
// .gnu_version_d
406+
stub.write_align_gnu_verdef();
407+
stub.write_gnu_verdef(&write::Verdef {
408+
version: elf::VER_DEF_CURRENT,
409+
flags: elf::VER_FLG_BASE,
410+
index: 1,
411+
aux_count: 1,
412+
name: soname,
413+
});
414+
for (ver, (_name, dynstr)) in vers.into_iter().enumerate() {
415+
stub.write_gnu_verdef(&write::Verdef {
416+
version: elf::VER_DEF_CURRENT,
417+
flags: 0,
418+
index: 2 + ver as u16,
419+
aux_count: 1,
420+
name: dynstr,
421+
});
422+
}
423+
366424
// .dynamic
367425
// the DT_SONAME will be used by the linker to populate DT_NEEDED
368426
// which the loader uses to find the library.
369427
// DT_NULL terminates the .dynamic table.
428+
stub.write_align_dynamic();
370429
stub.write_dynamic_string(elf::DT_SONAME, soname);
371430
stub.write_dynamic(elf::DT_NULL, 0);
372431

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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ only-x86_64-unknown-linux-gnu
2+
3+
use run_make_support::serde_json::{self, Value};
4+
use run_make_support::{cargo, llvm_nm, rfs, rustc};
5+
6+
fn main() {
7+
cargo()
8+
.args([
9+
"r",
10+
"--manifest-path",
11+
"t/Cargo.toml",
12+
"-Zbuild-std",
13+
"-Zbuild-std-features=compiler_builtins/mem",
14+
])
15+
.run();
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "t"
3+
version = "0.0.0"
4+
edition = "2024"
5+
6+
[profile.dev]
7+
panic = "abort"
8+
9+
[profile.release]
10+
panic = "abort"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#![allow(incomplete_features)]
2+
#![feature(raw_dylib_elf)]
3+
#![no_std]
4+
#![no_main]
5+
6+
use core::ffi::{c_char, c_int};
7+
8+
extern "C" fn callback(
9+
_fpath: *const c_char,
10+
_sb: *const (),
11+
_tflag: c_int,
12+
_ftwbuf: *const (),
13+
) -> c_int {
14+
0
15+
}
16+
17+
#[link(name = "libc.so.6", kind = "raw-dylib", modifiers = "+verbatim")]
18+
unsafe extern "C" {
19+
#[link_name = "nftw@GLIBC_2.2.5"]
20+
unsafe fn nftw_2_2_5(
21+
dirpath: *const c_char,
22+
f: extern "C" fn(*const c_char, *const (), c_int, *const ()) -> c_int,
23+
nopenfd: c_int,
24+
flags: c_int,
25+
) -> c_int;
26+
#[link_name = "nftw@GLIBC_2.3.3"]
27+
unsafe fn nftw_2_3_3(
28+
dirpath: *const c_char,
29+
f: extern "C" fn(*const c_char, *const (), c_int, *const ()) -> c_int,
30+
nopenfd: c_int,
31+
flags: c_int,
32+
) -> c_int;
33+
safe fn exit(status: i32) -> !;
34+
unsafe fn __libc_start_main() -> c_int;
35+
}
36+
37+
#[unsafe(no_mangle)]
38+
extern "C" fn main() -> ! {
39+
unsafe {
40+
// The old `nftw` does not check whether unknown flags are set.
41+
let res = nftw_2_2_5(c".".as_ptr(), callback, 20, 1 << 30);
42+
assert_eq!(res, 0);
43+
}
44+
unsafe {
45+
// The new `nftw` does.
46+
let res = nftw_2_3_3(c".".as_ptr(), callback, 20, 1 << 30);
47+
assert_eq!(res, -1);
48+
}
49+
exit(0);
50+
}
51+
52+
#[cfg(not(test))]
53+
#[panic_handler]
54+
fn panic_handler(_: &core::panic::PanicInfo<'_>) -> ! {
55+
exit(1);
56+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ only-x86_64-unknown-linux-gnu
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)