在裸金属 Rust 上设置堆栈指针

问题描述 投票:0回答:2

我正在尝试用 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函数中的任何内容之前,为堆栈上的局部变量分配了一些空间,并且编译器期望这些变量位于特定的偏移量,但随后我手动更改堆栈指针,使所有偏移量无效。

查看在发布模式下构建时生成的二进制文件的反汇编(x86 Intex 语法),我明白了......

push bx sub sp, 12 mov sp, 0x7c00 ...

生成的程序集在我的函数之前运行两个命令,第二个命令是我最担心的,
sub sp, 12

。编译器正在堆栈上为“某事”分配一些空间。我对 x86 汇编不是最熟练的,但似乎局部变量的所有实例都在远离堆栈的情况下进行了优化,并在处理器寄存器中使用,这是我对引导加载程序现在工作的原因的最佳猜测。

这值得担忧吗?我可以在 no_std 环境中(在程序一开始)安全地设置堆栈指针并且不会损坏任何局部变量吗?这个方案适用于所有 Rust 兼容的编译器吗?如果没有,有没有办法在没有外部汇编文件的情况下做到这一点?

我排除了完整的引导加载程序第 1 阶段代码和完整的反汇编,但如果有人认为它有帮助,我可以添加它。谢谢!

    

是的,这是一个问题。函数调用可能包括序言,序言中可能包含任何指令,包括操作堆栈的指令。

assembly rust x86 bootloader stack-pointer
2个回答
0
投票
有一个包涵盖了 99% 的用例,包括您的

(实际上,它只是

global_asm!()

的包装)。 解决方案是

裸函数
,保证没有序言或尾声的函数。不幸的是,这意味着编译器不能依赖于实现 Rust 代码所需的属性,这意味着它们所能拥有的只是一个巨大的

asm!()

块。 但这并不意味着您需要用汇编语言编写代码。您可以创建一个包含您的代码的

extern "C"
函数。由于它是

extern "C"

,因此它将具有已知的调用约定,这意味着您可以在完成所需的设置后从程序集中调用它。但由于 Rust 编译器允许插入序言/尾声,因此您可以在那里编写正常的 Rust 代码。但它只会在您根据需要进行设置后运行。

    
执行此操作的方法是使

_entry

0
投票
裸函数

,编译器将不会生成超出您指定的任何附加汇编代码。

裸函数目前是一个实验性功能,需要夜间编译器。启用 #![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。


© www.soinside.com 2019 - 2024. All rights reserved.