no_std 环境下的可执行文件

作者: 吴翱翔@pymongo / 后期编辑: 张汉东

原文: no_std binary(static link)

由于作者身边只有 linux 操作系统的设备,所以本文内容仅探讨 Rust/C/C++ 在 linux 操作系统下 no_std 的可执行文件

本文更多探讨的是编译生成纯静态链接没有动态链接的 no_std 可执行文件,不仅连 Rust 的标准库也不用,连操作系统自带的 C 标准库也不用的环境

推荐这个 Making our own executable packer(linux) 系列文章:

在介绍Rust如何编译运行 no_std 的可执行文件之前,先看看汇编和 C/C++ 是如何编译 no_std 的可执行文件

汇编语言编译可执行文件

x86 汇编主要有两种语法,一是 Unix 的 AT&T syntax,另一个则是 windows 的 Intel syntax

由于 AT&T 有贝尔实验室,而 Unix 操作系统和 C 语言都是贝尔实验室发明的,所以 linux 的 gcc 和 as 都用 AT&T 汇编语法

如果想用 Intel 汇编语法可以用 llvm 或 nasm 工具

rustc 生成的汇编默认是 Intel 语法,可以传入 llvm 参数让 rustc 生成 AT&T 语法的汇编代码

rustc --emit asm -C llvm-args=-x86-asm-syntax=att main.rs

以这个网站GNU Assembler Examples 介绍的第一段汇编代码为准

编译运行这段代码有两个方法:

gcc -c s.s && ld s.o && ./a.out

或者用as工具(GNU assembler (GNU Binutils))

as s.s && ld s.o && ./a.out

可以用 ldd 工具校验编译生成的可执行文件是不是 statically linked (没有引入任何动态链接库)

汇编的劣势在于代码跟硬件架构绑定,gcc 编译这段汇编代码时加上-m32参数指定生成32位的可执行文件时就会报错

C 编译 no_std 可执行文件

用 gcc 或 clang 的 -nostdlib参数很容易生成无动态链接库的可执行文件

[w@w-manjaro temp]$ echo "int main(){return 0;}" > main.c && gcc -nostdlib main.c && ldd ./a.out
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000
        statically linked

C 在 no_std 的环境下程序的入口函数名字不能是 main,要改成 _start

[w@w-manjaro temp]$ echo "int _start(){return 0;}" > main.c && gcc -nostdlib main.c && ldd ./a.out
        statically linked

当然也可以让 gcc 加上-m32参数生成32位的可执行文件

注意在 mac 或 windows 上用gcc 或 clang 的 -nostdlib参数可能会报错

$ clang -nostdlib c.c
ld: dynamic main executables must link with libSystem.dylib for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

根据苹果开发者文档,Apple does not support statically linked binaries on Mac OS X

可能 macOS 要用特殊的 ld 工具或稍微复杂点的方法才能编译纯静态链接的可执行文件,不过这不在本文的探讨范围内了

Rust 编译 no_std 可执行文件


#![allow(unused)]
#![no_std]
#![no_main]
#![feature(lang_items,asm)]

fn main() {
/// entry_point/start_address of process, since the linker looks for a function named `_start` by default
#[no_mangle]
extern "C" fn _start() -> ! {
    exit(0); // macOS: illegal hardware instruction
}

fn exit(code: isize) -> ! {
    unsafe {
        asm!(
            "syscall",
            in("rax") 60, // exit
            in("rdi") code,
            options(noreturn)
        );
    }
}

#[lang = "eh_personality"]
extern "C" fn eh_personality() {}

#[panic_handler]
fn my_panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}
}

源码在我这个仓库,linux 下的编译方法:

rustc -C link-arg=-nostartfiles main.rs

或者将以下两行写到.cargo/config.toml

[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-nostartfiles"]

如果只是编译 no_std 环境下的 动态链接库(cdylib),则不需要加上述 rustc 参数