Skip to content

Commit 996d4ec

Browse files
authored
Rollup merge of rust-lang#128362 - folkertdev:naked-function-symbol-visibility, r=bjorn3
add test for symbol visibility of `#[naked]` functions tracking issue: rust-lang#90957 This test is extracted from rust-lang#128004 That PR attempts to generated naked functions as an extern function declaration, combined with a global asm block that provides the implementation for that declaration. In order to link declaration and definition together, some flavor of external linking must be used: LLVM will error for other linkage types. Specifically the allowed options are `#[linkage = "external"]` and `#[linkage = "extern_weak"]`. That is kind of an implementation detail though: to the user, a naked function should just behave like a normal function. Hence it should be visible to the linker under the same circumstances as a normal, vanilla function and have the same attributes (Weak, External). Getting this behavior right will require some care, so I think it's a good idea to lock it in now, before making any changes, to make sure we don't regress. Are there any interesting cases that I missed here? E.g. is checking on different architectures worth it? I don't think the other binary types (rlib etc) are relevant here, but may be missing something. r? `@bjorn3`
2 parents e98f00b + 940a109 commit 996d4ec

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#![feature(naked_functions, asm_const, linkage)]
2+
#![crate_type = "dylib"]
3+
4+
use std::arch::asm;
5+
6+
pub trait TraitWithConst {
7+
const COUNT: u32;
8+
}
9+
10+
struct Test;
11+
12+
impl TraitWithConst for Test {
13+
const COUNT: u32 = 1;
14+
}
15+
16+
#[no_mangle]
17+
fn entry() {
18+
private_vanilla_rust_function_from_rust_dylib();
19+
private_naked_rust_function_from_rust_dylib();
20+
21+
public_vanilla_generic_function_from_rust_dylib::<Test>();
22+
public_naked_generic_function_from_rust_dylib::<Test>();
23+
}
24+
25+
extern "C" fn private_vanilla_rust_function_from_rust_dylib() -> u32 {
26+
42
27+
}
28+
29+
#[no_mangle]
30+
pub extern "C" fn public_vanilla_rust_function_from_rust_dylib() -> u32 {
31+
42
32+
}
33+
34+
pub extern "C" fn public_vanilla_generic_function_from_rust_dylib<T: TraitWithConst>() -> u32 {
35+
T::COUNT
36+
}
37+
38+
#[linkage = "weak"]
39+
extern "C" fn vanilla_weak_linkage() -> u32 {
40+
42
41+
}
42+
43+
#[linkage = "external"]
44+
extern "C" fn vanilla_external_linkage() -> u32 {
45+
42
46+
}
47+
48+
#[naked]
49+
extern "C" fn private_naked_rust_function_from_rust_dylib() -> u32 {
50+
unsafe { asm!("mov rax, 42", "ret", options(noreturn)) }
51+
}
52+
53+
#[naked]
54+
#[no_mangle]
55+
pub extern "C" fn public_naked_rust_function_from_rust_dylib() -> u32 {
56+
unsafe { asm!("mov rax, 42", "ret", options(noreturn)) }
57+
}
58+
59+
#[naked]
60+
pub extern "C" fn public_naked_generic_function_from_rust_dylib<T: TraitWithConst>() -> u32 {
61+
unsafe { asm!("mov rax, {}", "ret", const T::COUNT, options(noreturn)) }
62+
}
63+
64+
#[naked]
65+
#[linkage = "weak"]
66+
extern "C" fn naked_weak_linkage() -> u32 {
67+
unsafe { asm!("mov rax, 42", "ret", options(noreturn)) }
68+
}
69+
70+
#[naked]
71+
#[linkage = "external"]
72+
extern "C" fn naked_external_linkage() -> u32 {
73+
unsafe { asm!("mov rax, 42", "ret", options(noreturn)) }
74+
}
75+
76+
// functions that are declared in an `extern "C"` block are currently not exported
77+
// this maybe should change in the future, this is just tracking the current behavior
78+
// reported in https://github.com/rust-lang/rust/issues/128071
79+
std::arch::global_asm! {
80+
".globl function_defined_in_global_asm",
81+
"function_defined_in_global_asm:",
82+
"ret",
83+
}
84+
85+
extern "C" {
86+
pub fn function_defined_in_global_asm();
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// @only-x86_64
2+
use run_make_support::object::read::{File, Object, Symbol};
3+
use run_make_support::object::ObjectSymbol;
4+
use run_make_support::{dynamic_lib_name, rfs, rustc};
5+
6+
fn main() {
7+
let rdylib_name = dynamic_lib_name("a_rust_dylib");
8+
rustc().arg("-Zshare-generics=no").input("a_rust_dylib.rs").run();
9+
10+
let binary_data = rfs::read(&rdylib_name);
11+
let rdylib = File::parse(&*binary_data).unwrap();
12+
13+
// check vanilla symbols
14+
not_exported(&rdylib, "private_vanilla_rust_function_from_rust_dylib");
15+
global_function(&rdylib, "public_vanilla_rust_function_from_rust_dylib");
16+
not_exported(&rdylib, "public_vanilla_generic_function_from_rust_dylib");
17+
18+
weak_function(&rdylib, "vanilla_weak_linkage");
19+
global_function(&rdylib, "vanilla_external_linkage");
20+
21+
// naked should mirror vanilla
22+
not_exported(&rdylib, "private_naked_rust_function_from_rust_dylib");
23+
global_function(&rdylib, "public_naked_rust_function_from_rust_dylib");
24+
not_exported(&rdylib, "public_naked_generic_function_from_rust_dylib");
25+
26+
weak_function(&rdylib, "naked_weak_linkage");
27+
global_function(&rdylib, "naked_external_linkage");
28+
29+
// functions that are declared in an `extern "C"` block are currently not exported
30+
// this maybe should change in the future, this is just tracking the current behavior
31+
// reported in https://github.com/rust-lang/rust/issues/128071
32+
not_exported(&rdylib, "function_defined_in_global_asm");
33+
34+
// share generics should expose the generic functions
35+
rustc().arg("-Zshare-generics=yes").input("a_rust_dylib.rs").run();
36+
let binary_data = rfs::read(&rdylib_name);
37+
let rdylib = File::parse(&*binary_data).unwrap();
38+
39+
global_function(&rdylib, "public_vanilla_generic_function_from_rust_dylib");
40+
global_function(&rdylib, "public_naked_generic_function_from_rust_dylib");
41+
}
42+
43+
#[track_caller]
44+
fn global_function(file: &File, symbol_name: &str) {
45+
let symbols = find_dynamic_symbol(file, symbol_name);
46+
let [symbol] = symbols.as_slice() else {
47+
panic!("symbol {symbol_name} occurs {} times", symbols.len())
48+
};
49+
50+
assert!(symbol.is_definition(), "`{symbol_name}` is not a function");
51+
assert!(symbol.is_global(), "`{symbol_name}` is not marked as global");
52+
}
53+
54+
#[track_caller]
55+
fn weak_function(file: &File, symbol_name: &str) {
56+
let symbols = find_dynamic_symbol(file, symbol_name);
57+
let [symbol] = symbols.as_slice() else {
58+
panic!("symbol {symbol_name} occurs {} times", symbols.len())
59+
};
60+
61+
assert!(symbol.is_definition(), "`{symbol_name}` is not a function");
62+
assert!(symbol.is_weak(), "`{symbol_name}` is not marked as weak");
63+
}
64+
65+
#[track_caller]
66+
fn not_exported(file: &File, symbol_name: &str) {
67+
assert_eq!(find_dynamic_symbol(file, symbol_name).len(), 0)
68+
}
69+
70+
fn find_dynamic_symbol<'file, 'data>(
71+
file: &'file File<'data>,
72+
expected: &str,
73+
) -> Vec<Symbol<'data, 'file>> {
74+
file.dynamic_symbols()
75+
.filter(|symbol| {
76+
let name = symbol.name().unwrap();
77+
!name.contains("__imp_") && name.contains(expected)
78+
})
79+
.collect()
80+
}

0 commit comments

Comments
 (0)