我正在尝试用 Rust 完全编写一个 x86 引导加载程序和操作系统(没有单独的汇编文件,只有内联汇编withinRust)。
我的引导加载程序在 QEMU 模拟器中完全按照预期工作,我已经深入开发内核,并且我的引导加载程序从未失败过。我只是担心我的引导加载程序是否有任何未定义的行为,而这种行为恰好对我有用。
我需要做的第一件事“引导加载程序”是将堆栈指针设置为有效的内存区域,以充当引导加载程序的堆栈。我的引导加载程序使用局部变量,并且还使用内联汇编设置堆栈指针。
// extreme oversimplification of what I have as my bootloader
#![no_std]
#![no_main]
#[no_mangle]
fn entry() -> !
{
// place the stack area just before the boot sector
unsafe { core::arch::asm!
(
"mov sp, 0x7c00"
)}
let bootloader_variable_1 = ...;
let bootloader_variable_2 = ...;
// do things with bootloader variables
}
我主要担心的是,编译器在运行entry
函数中的任何内容之前,为堆栈上的局部变量分配了一些空间,并且编译器期望这些变量位于特定的偏移量,但随后我手动更改堆栈指针,使所有偏移量无效。
push bx
sub sp, 12
mov sp, 0x7c00
...
生成的程序集在我的函数之前运行两个命令,第二个命令是我最担心的,
sub sp, 12
。编译器正在堆栈上为“某事”分配一些空间。我对 x86 汇编不是最熟练的,但似乎局部变量的所有实例都在远离堆栈的情况下进行了优化,并在处理器寄存器中使用,这是我对引导加载程序现在工作的原因的最佳猜测。
这值得担忧吗?我可以在no_std
环境中(在程序一开始)安全地设置堆栈指针并且不会损坏任何局部变量吗?这个方案适用于所有 Rust 兼容的编译器吗?如果没有,有没有办法在没有外部汇编文件的情况下做到这一点?
我排除了完整的引导加载程序第 1 阶段代码和完整的反汇编,但如果有人认为它有帮助,我可以添加它。谢谢!
是的,这是一个问题。函数调用可能包括序言,序言中可能包含任何指令,包括操作堆栈的指令。
(实际上,它只是
global_asm!()
裸函数,保证没有序言或尾声的函数。不幸的是,这意味着编译器不能依赖于实现 Rust 代码所需的属性,这意味着它们所能拥有的只是一个巨大的
asm!()
块。 但这并不意味着您需要用汇编语言编写代码。您可以创建一个包含您的代码的
extern "C"
函数。由于它是 extern "C"
,因此它将具有已知的调用约定,这意味着您可以在完成所需的设置后从程序集中调用它。但由于 Rust 编译器允许插入序言/尾声,因此您可以在那里编写正常的 Rust 代码。但它只会在您根据需要进行设置后运行。
执行此操作的方法是使
_entry
,编译器将不会生成超出您指定的任何附加汇编代码。
裸函数目前是一个实验性功能,需要夜间编译器。启用#![feature(naked_functions)]
,然后像这样编写你的入口函数:
#[naked]
pub extern "C" fn entry() -> ! {
unsafe {
asm!(
"mov sp, 0x7c00",
"call _main",
"hlt",
options(noreturn)
);
}
}
_main
函数作为真正的入口点;当堆栈已经建立时,main 就可以被写成一个常规的 Rust 函数。如果您期望 _main 返回,它就会发出 HLT 来停止 CPU。