如何将程序移动到自己的平台上执行
程序执行的 目标平台三元组(CPU,操作系统,运行时库)
bash
$ rustc --version --verbose
rustc 1.61.0-nightly (68369a041 2022-02-22)
binary: rustc
commit-hash: 68369a041cea809a87e5bd80701da90e0e0a4799
commit-date: 2022-02-22
host: x86_64-unknown-linux-gnu
release: 1.61.0-nightly
LLVM version: 14.0.0
键入如上指令,CPU:X86_64,操作系统:linux,运行时库:gnu
接下来,我们希望把 Hello, world! 移植到 RICV 目标平台 riscv64gc-unknown-none-elf 上运行。
- riscv64gc-unknown-none-elf 指无操作系统,无运行时库,但能生成elf文件
- rust基本上只有core库不依赖gnu libc。
移除标准库依赖(使cargo去新的平台编译)
rust
# os/.cargo/config
[build]
target = "riscv64gc-unknown-none-elf"
- 创建config后,此时在os文件夹下编译即使用如上参数。
- 此时pirntln!无法使用
bash
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: cannot find macro `println` in this scope
--> src/main.rs:4:5
|
4 | println!("Hello, world!");
| ^^^^^^^
- panic处理无法使用。
bash
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: `#[panic_handler]` function required, but not found
构建裸机执行环境
panic处理
rust
// os/src/lang_items.rs
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
- 标记panic_handler属性,告知编译器使用该函数作为panic处理。
告知函数执行入口(去除main函数)
- 此时有报错
bash
$ cargo build
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: requires `start` lang_item
缺少#[lang = "start"]
一个rust程序正常的执行流(by deepseek)
- 以下是标准库std的处理
rust
// 在 libstd 中大致是这样的(简化版)
#[lang = "start"]
fn start<T: Termination>(
main: fn() -> T,
argc: isize,
argv: *const *const u8,
_sigpipe: u8,
) -> isize {
// 1. 初始化Rust运行时
init_runtime();
// 2. 设置堆栈保护
setup_stack_guard();
// 3. 初始化线程局部存储(TLS)
init_thread_local_storage();
// 4. 设置panic处理
setup_panic_handler();
// 5. 处理命令行参数
let args = parse_args(argc, argv);
// 6. 调用main函数
let result = main();
// 7. 处理退出码
result.report()
}
- lang是language items的缩写,#[lang = "start"]意为将接下来的处理作为start语义项来处理。
最小化用户态执行环境
无操作系统程序执行流(引导加载程序bootloader)
- Bootloader → 你的 _start 函数 → 代码------退出机制
- 注:这是最小的执行环境
_start(),声明函数入口位置
rust
// os/src/main.rs
#[no_mangle]
extern "C" fn _start() {
loop{};
}
- extern c可以保证在链接后函数名为原函数名,而不做任何重命名(rust常规会做一些重命名)。
退出机制:第一个系统调用
rust
// os/src/main.rs
const SYSCALL_EXIT: usize = 93;
fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret;
unsafe {
core::arch::asm!(
"ecall",
inlateout("x10") args[0] => ret,
in("x11") args[1],
in("x12") args[2],
in("x17") id,
);
}
ret
}
pub fn sys_exit(xstate: i32) -> isize {
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}
#[no_mangle]
extern "C" fn _start() {
sys_exit(9);
}
第二个系统调用:输出字符串
- 以下是AI生成的注释和system_wirte
rust
// 执行 write 系统调用
fn sys_write(fd: i32, buffer: &[u8]) -> isize {
let ptr = buffer.as_ptr();
let len = buffer.len();
let result: isize;
unsafe {
asm!(
"syscall", // 系统调用指令
in("rax") 1, // 系统调用号:write = 1
in("rdi") fd, // 第一个参数:文件描述符
in("rsi") ptr, // 第二个参数:缓冲区指针
in("rdx") len, // 第三个参数:缓冲区长度
out("rcx") _, // 被 syscall 修改的寄存器
out("r11") _, // 被 syscall 修改的寄存器
lateout("rax") result, // 返回值(系统调用完成后)
options(nostack, preserves_flags)
);
}
result
}
- rCore手册中对其进一步使用宏进行了封装
构建裸机执行环境(操作系统执行环境)
使用ecall退出(ecall可以让用户态切换到内核态)
加载
ld
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80200000;
SECTIONS
{
. = BASE_ADDRESS; // . 为临时变量
skernel = .;
stext = .;
.text : {
*(.text.entry)
*(.text .text.*)
}
. = ALIGN(4K);
/*含义: . = ALIGN(4K)把当前位置指针.向上对齐到最近的4KB边界(4096字节)。
数值公式: 对齐后的.等于(. + 4096 - 1) & ~(4096 - 1),也就是把当前值四舍五入到下一个4096的倍数。
具体值: 取决于前面已放入段的大小。比如
若 .text 结束时.为0x80203456,对齐后变为0x80204000。
若刚好在边界,如.为0x80204000,对齐结果仍是0x80204000。*/
etext = .;
srodata = .;
.rodata : {
*(.rodata .rodata.*)
*(.srodata .srodata.*)
} // 将文件中所有该段合并到.rodata,并将rodata置于此。
//写下.rodata证明.rodata数据放在这里。
. = ALIGN(4K);
erodata = .;
sdata = .;
.data : {
*(.data .data.*)
*(.sdata .sdata.*)
}
. = ALIGN(4K);
edata = .;
.bss : {
*(.bss.stack)
sbss = .;
*(.bss .bss.*)
*(.sbss .sbss.*)
}
. = ALIGN(4K);
ebss = .;
ekernel = .;
/DISCARD/ : {
*(.eh_frame)
}
}