Rust 自定义裸机编译目标:链接器需要“_start”符号并丢弃未使用的符号:How can I specify a custom entry symbol?
Rust custom bare metal compile target: linker expects "_start" symbol and discards unused ones: How can I specify a custom entry symbol?
我正在使用 Rust 为 x86 交叉编译裸机 32 位代码,我遇到了一个问题,如果没有准确调用入口函数,最终目标文件是空的 _start
;链接器丢弃所有代码,因为它认为它已经死了。我知道 _start
是众所周知的入口点名称,但问题仍然是:
Rust、LLVM 或 Linker 中的哪一部分强制执行此操作? extern "C" fn ...
、#[no_mangle]
或 #[export_name = "foobar"]
等属性也不起作用(被链接器丢弃)。我的猜测是,它不是 Rust 编译器而是链接器。如您所见,在我的例子中,我使用 rust-lld
作为链接器,使用 ld.lld
作为链接器风格(见下文)。
- 所需的
_start
- 来自哪里?为什么链接器会丢弃我的其他代码?
- 将我的自定义入口点指定到链接器的最佳选项是什么?
x86-未知-bare_metal.json
{
"llvm-target": "i686-unknown-none",
"data-layout": "e-m:e-i32:32-f80:128-n8:16:32-S128-p:32:32",
"arch": "x86",
"target-endian": "little",
"target-pointer-width": "32",
"target-c-int-width": "32",
"os": "none",
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "+soft-float,+sse"
}
我每晚都在使用 Rust 1.54.0 并将其构建在 Linux 5.8.0 系统上。
我花了一些时间在互联网上搜索并发现讨论,Rust 最终应该得到一个 #[entrypoint="foobar
注释或类似的东西,但不幸的是我没有找到可用的解决方案。
我的尝试是追加
"pre-link-args": {
"ld.lld": [
"-e,foobar"
]
}
到目标定义(函数也称为 foobar),但目标文件仍然是空的。另一种尝试是保留所有死代码。这行得通,但这个解决方案很脏。
最小代码示例:
// disable rust standard library
#![no_std]
// disables Rust runtime init,
#![no_main]
// see https://docs.rust-embedded.org/embedonomicon/smallest-no-std.html
#![feature(lang_items)]
// see https://docs.rust-embedded.org/embedonomicon/smallest-no-std.html
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
use core::panic::PanicInfo;
use core::sync::atomic;
use core::sync::atomic::Ordering;
#[no_mangle]
/// The name **must be** `_start`, otherwise the compiler doesn't output anything
/// to the object file. I don't know why it is like this.
/// Also `pub` or `pub extern "C"` doesn't work
fn _start() -> ! {
loop {}
}
#[inline(never)]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {
atomic::compiler_fence(Ordering::SeqCst);
}
}
新答案[解]
真正的解决方案非常简单但很难找到,因为很难在这个相对未记录的领域中挖掘可能的选项和解决方案。我发现,llvm-ld
使用与 GNU ld
相同的选项。所以我检查了 GNU ld
link 选项并找到了解决方案。必须是
"pre-link-args": {
"ld.lld": [
"--entry=entry_32_bit"
]
}
值是文件中函数的名称。函数 必须 注释为 #[no_mangle]
。
另见:https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html
旧答案:
一个快速但非常肮脏的解决方案是
#[no_mangle]
/// The name **must be** `_start`, otherwise the compiler doesn't output anything
/// to the object file. I don't know it is like this.
fn _start() -> ! {
entry_32_bit();
}
#[no_mangle]
#[inline(never)]
fn entry_32_bit() -> ! {
loop {}
}
这样就可以直接从汇编跳转到符号entry_32_bit
了。但这个“解决方案”远非理想!特别是当你想link多个bin在一起时,会出现名字冲突。
我正在使用 Rust 为 x86 交叉编译裸机 32 位代码,我遇到了一个问题,如果没有准确调用入口函数,最终目标文件是空的 _start
;链接器丢弃所有代码,因为它认为它已经死了。我知道 _start
是众所周知的入口点名称,但问题仍然是:
Rust、LLVM 或 Linker 中的哪一部分强制执行此操作? extern "C" fn ...
、#[no_mangle]
或 #[export_name = "foobar"]
等属性也不起作用(被链接器丢弃)。我的猜测是,它不是 Rust 编译器而是链接器。如您所见,在我的例子中,我使用 rust-lld
作为链接器,使用 ld.lld
作为链接器风格(见下文)。
- 所需的
_start
- 来自哪里?为什么链接器会丢弃我的其他代码? - 将我的自定义入口点指定到链接器的最佳选项是什么?
x86-未知-bare_metal.json
{
"llvm-target": "i686-unknown-none",
"data-layout": "e-m:e-i32:32-f80:128-n8:16:32-S128-p:32:32",
"arch": "x86",
"target-endian": "little",
"target-pointer-width": "32",
"target-c-int-width": "32",
"os": "none",
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "+soft-float,+sse"
}
我每晚都在使用 Rust 1.54.0 并将其构建在 Linux 5.8.0 系统上。
我花了一些时间在互联网上搜索并发现讨论,Rust 最终应该得到一个 #[entrypoint="foobar
注释或类似的东西,但不幸的是我没有找到可用的解决方案。
我的尝试是追加
"pre-link-args": {
"ld.lld": [
"-e,foobar"
]
}
到目标定义(函数也称为 foobar),但目标文件仍然是空的。另一种尝试是保留所有死代码。这行得通,但这个解决方案很脏。
最小代码示例:
// disable rust standard library
#![no_std]
// disables Rust runtime init,
#![no_main]
// see https://docs.rust-embedded.org/embedonomicon/smallest-no-std.html
#![feature(lang_items)]
// see https://docs.rust-embedded.org/embedonomicon/smallest-no-std.html
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
use core::panic::PanicInfo;
use core::sync::atomic;
use core::sync::atomic::Ordering;
#[no_mangle]
/// The name **must be** `_start`, otherwise the compiler doesn't output anything
/// to the object file. I don't know why it is like this.
/// Also `pub` or `pub extern "C"` doesn't work
fn _start() -> ! {
loop {}
}
#[inline(never)]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {
atomic::compiler_fence(Ordering::SeqCst);
}
}
新答案[解]
真正的解决方案非常简单但很难找到,因为很难在这个相对未记录的领域中挖掘可能的选项和解决方案。我发现,llvm-ld
使用与 GNU ld
相同的选项。所以我检查了 GNU ld
link 选项并找到了解决方案。必须是
"pre-link-args": {
"ld.lld": [
"--entry=entry_32_bit"
]
}
值是文件中函数的名称。函数 必须 注释为 #[no_mangle]
。
另见:https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html
旧答案:
一个快速但非常肮脏的解决方案是
#[no_mangle]
/// The name **must be** `_start`, otherwise the compiler doesn't output anything
/// to the object file. I don't know it is like this.
fn _start() -> ! {
entry_32_bit();
}
#[no_mangle]
#[inline(never)]
fn entry_32_bit() -> ! {
loop {}
}
这样就可以直接从汇编跳转到符号entry_32_bit
了。但这个“解决方案”远非理想!特别是当你想link多个bin在一起时,会出现名字冲突。