在发布与调试模式下生成本地堆栈变量的代码

时间:2018-03-06 07:06:39

标签: assembly x86 rust code-generation osdev

作为我在Rust中的操作系统的一部分,我有以下系统调用入口点:

#[no_mangle]
#[naked]
#[inline(never)]
unsafe extern "C" fn syscall_handler() {

    // switch to the kernel stack dedicated for syscall handling, and save the user task's details
    asm!("swapgs; \
          mov gs:[0x8],  rsp; \
          mov gs:[0x10], rcx; \
          mov gs:[0x18], r11; \
          mov rsp, gs:[0x0];"
          : : : "memory" : "intel", "volatile");


    let (rax, rdi, rsi, rdx, r10, r8, r9): (u64, u64, u64, u64, u64, u64, u64); 
    asm!("" : "={rax}"(rax), "={rdi}"(rdi), "={rsi}"(rsi), "={rdx}"(rdx), "={r10}"(r10), "={r8}"(r8), "={r9}"(r9)  : : "memory" : "intel", "volatile");
    // do stuff with rax, rdi, rsi... 

这在调试模式下工作正常,并且在发布模式下(启用了调试信息),因为它会生成汇编代码,用于存储本地堆栈变量,如rdirsi等。基指针rbp。 例如,这里是生成的代码:

<syscall_handler>:
swapgs 
mov    %rsp,%gs:0x8
mov    %rcx,%gs:0x10
mov    %r11,%gs:0x18
mov    %gs:0x0,%rsp
mov    %rax,-0x1f0(%rbp)
mov    %rdi,-0x1e8(%rbp)
mov    %rsi,-0x1e0(%rbp)
mov    %rdx,-0x1d8(%rbp)
mov    %r10,-0x1d0(%rbp)
mov    %r8,-0x1c8(%rbp)
mov    %r9,-0x1c0(%rbp)
movb   $0x4,-0x1b1(%rbp)

该代码工作正常,因为我的系统调用处理程序使用指向当前内核堆栈顶部的堆栈指针运行(像往常一样),这意味着可以使用堆栈指针/库中的负偏移量指针(基指针rbp在此之前根据堆栈指针值设置)。

当我在没有调试信息的情况下构建发布模式时,它生成的代码使用堆栈指针本身(rsp而不是基本指针)的正偏移量作为本地堆栈变量的位置。这非常奇怪并且导致问题,因为当前堆栈指针rsp上方的内存超出了界限。

这里是在没有调试信息的纯发布模式下生成的代码:

<syscall_handler>:
swapgs 
mov    %rsp,%gs:0x8
mov    %rcx,%gs:0x10
mov    %r11,%gs:0x18
mov    %gs:0x0,%rsp
mov    %rax,0x1c0(%rsp)
mov    %rdi,0x1c8(%rsp)
 mov    %rsi,0x1d0(%rsp)
mov    %rdx,0x1d8(%rsp)
mov    %r10,0x1e0(%rsp)
mov    %r8,0x1e8(%rsp)
mov    %r9,0x1f0(%rsp)

为什么要生成此代码,使用堆栈指针的正偏移量的代码?这让我非常奇怪。

有什么方法可以避免这种情况或以某种方式更改代码生成?

1 个答案:

答案 0 :(得分:2)

堆栈增长。 RSP的正偏移是中断异步修改的安全部分,即&#34;保留&#34;。

RSP的负偏移量为the red zone, which you can't have on the kernel stack

使用<target name="app-name-debug"> <replaceregexp file="res/values/strings.xml" match='name="app_name"(.*)' replace='name="app_name"&gt;MyApp-DEBUG&lt;\/string&gt;'/> </target> <target name="app-name-release"> <replaceregexp file="res/values/strings.xml" match='name="app_name"(.*)' replace='name="app_name"&gt;MyApp&lt;\/string&gt;'/> </target> 或其他任何内容为裸功能的局部变量保留足够的空间。或者更好的是,在asm中写入整个入口点而不是乱用编译器 - 为此生成代码。

或者更好,只需自己使用sub rsp, 0x100,它就会更紧凑(代码大小)并且效率高push非常适合在堆栈上保存寄存器; Linux的系统调用入口点使用它。 (例如the entry point into an x86-64 kernel from syscall in 64-bit user-space使用push保存所有寄存器,从Linux 4.12开始(在Spectre / Meltdown缓解/解决方法补丁使入口点更复杂之前)。

它奇怪/令人困惑的原因是你要求push函数(因此没有函数序言来保留堆栈空间),但是无论如何你在其中使用了局部变量。否则编译器会naked自己为本地人保留足够的空间,然后再访问它们。

我认为在一些支持裸功能的C / C ++编译器中,不支持;只允许内联asm,因为允许整个函数体。但是,就内联asm和编译器之间的奇怪探索而言,Rust所说的IDK正式得到支持。就像我说的那样,如果你用纯粹的asm编写入口点,你就不会遇到这些问题。

您的调试模式版本似乎已损坏;您是否相对于RBP存储,但RBP尚未设置。您要求提供sub rsp, 0x...功能,因此您需要naked自己(从mov rbp, rsp加载RSP后),然后gs:0或其他什么来保留足够的空间在堆栈框架中,用于那些负偏移。

我认为你的调试模式版本是相对于用户空间的RBP进行存储的,如果用户空间进行系统调用,RBP指向不应该被破坏的任何东西,那么这将会非常可怕。如果RBP持有非指针值,那就更可怕了。

(如果您刚刚使用sub rsp, 0x20或其他内容,根据您的评论,您已将此部分删除,那么您使用的是RSP下方的空间,这是不安全的,没有红区。 )