减去ESP或RSP寄存器会产生哪个异常? (堆栈增长)

Which exception can be generated when subtracting ESP or RSP register? (stack growing)

我正在尝试了解堆栈的内存页究竟是多少 allocated/assigned。

我编写了以下明显导致分段错误的概念验证 C 代码(在 x86_64 Linux 上):

#include <string.h>

int main()
{
    char a;

    memset( (&a - 4444444), 0, 3333333 );

    return 0;
}

以下汇编代码片段(AT&T 语法)由 gcc 从上述 C 程序生成:

subq    , %rsp
leaq    -1(%rbp), %rax
subq    44444, %rax
movl    33333, %edx
movl    [=11=], %esi
movq    %rax, %rdi
call    memset

如果我在调用 memset 之前手动添加 subq 55555, %rsp:

subq    , %rsp
leaq    -1(%rbp), %rax
subq    44444, %rax
movl    33333, %edx
movl    [=12=], %esi
movq    %rax, %rdi
subq    55555, %rsp /* added manually */
call    memset

然后分段错误消失了,因为在减去rsp寄存器后分配了堆栈的虚拟内存页面导致了一些硬件异常并调用了分配的异常处理程序(当然,在内核space中)。

我知道在这里调用memset会导致"minor page fault"异常。但这是另一回事(即分配物理内存页)。

我的问题是:调用subq 55555, %rsp时产生了哪个异常?我建议这将是 "stack fault" 例外,但我没有找到确切的证据。

那条线也没有例外。

但是,memset 的序言代码在尝试通过将寄存器保存到堆栈来保留寄存器时会导致访问冲突,因为堆栈指针无效。

在大多数环境中,只有一个保护页面会触发提交额外的堆栈页面。在那种情况下,访问冲突将不会通过增加堆栈来处理,程序只会崩溃。

如果您的 OS 实际上确实处理了寄存器保存期间导致的访问冲突,它将提交堆栈的所有中间页面并重试操作(PUSH 指令)。然后那些中间的页面将被 memset.

内的循环成功写入

当然,如果减法导致 RSP 指向为堆栈增长保留的地址 space 之外,则所有赌注都将取消。您甚至可以导致其他线程的堆栈增长。

我明白了。首先,减去 rsp 寄存器什么都不做。其次,当我们尝试写入非映射堆栈区域时 "minor page fault" 在内核 space 中调用异常处理程序。然后这个页面错误处理程序检查它是合法的还是非法的。我认为页面错误处理程序与线程的当前堆栈指针进行比较(在我们的例子中它是保存值 rsp 寄存器)。如果进程尝试写入的地址高于当前堆栈指针,则页面错误处理程序扩展进程的虚拟地址 space 并将此虚拟页面映射到物理页面,否则处理程序将 SIGSEGV 发送到进程。

我使用 GDB 和 /proc/[pid]/maps 检查了以下片段:

subq    00016, %rsp
movq    %fs:40, %rax
movq    %rax, -8(%rbp)
xorl    %eax, %eax
movb    , -1500016(%rbp)
movb    , -1100016(%rbp)
movb    , -600016(%rbp)

调用subq 00016, %rsp时,堆栈地址范围不会改变。 但是,当 movb , -1500016(%rbp) 第一次写入时,堆栈地址范围会扩大,正如我上面所解释的那样。