【问题标题】:Which exception can be generated when subtracting ESP or RSP register? (stack growing)减去 ESP 或 RSP 寄存器会产生哪个异常? (堆栈增长)
【发布时间】:2023-04-02 17:54:01
【问题描述】:

我试图了解堆栈的内存页面是如何分配/分配的。

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

#include <string.h>

int main()
{
    char a;

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

    return 0;
}

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

subq    $16, %rsp
leaq    -1(%rbp), %rax
subq    $4444444, %rax
movl    $3333333, %edx
movl    $0, %esi
movq    %rax, %rdi
call    memset

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

subq    $16, %rsp
leaq    -1(%rbp), %rax
subq    $4444444, %rax
movl    $3333333, %edx
movl    $0, %esi
movq    %rax, %rdi
subq    $5555555, %rsp /* added manually */
call    memset

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

我知道在这里调用memset 会导致“次要页面错误”异常。但这是另一回事(即分配物理内存页面)。

我的问题是:调用subq $5555555, %rsp 时产生了哪个异常?我建议这将是“堆栈错误”异常,但我没有找到确切的证据。

【问题讨论】:

  • x86 甚至没有这些寄存器......也许你使用了错误的架构标签?
  • @Ben Voigt 我有Intel® Core™ i3-2328M CPU @ 2.20GHz × 4,所以它是 x86 寄存器。
  • RSP 不是 x86 寄存器...它仅在长模式下可用,而不是 x86。
  • @MartinRosenau:是的,有一个标签x86_64
  • @glglgl 谢谢。我移动了“更新”部分来回答。

标签: linux assembly linux-kernel x86-64


【解决方案1】:

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

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

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

当调用subq $1500016, %rsp 时,堆栈地址范围不会改变。 但是当movb $44, -1500016(%rbp) 发生第一次写入时,堆栈地址范围会像我上面解释的那样扩展。

【讨论】:

    【解决方案2】:

    那一行也不例外。

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

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

    如果您的操作系统确实处理了在寄存器保存期间引起的访问冲突,它将提交所有介入的堆栈页面并重试操作(PUSH 指令)。然后这些中间页面将被memset内部的循环成功写入。

    当然,如果减法导致RSP 指向为堆栈增长保留的地址空间之外,那么所有的赌注都会被取消。你甚至可以使其他线程的堆栈增长。

    【讨论】:

    • 我更新了我的主题。请检查我对自己问题的回答。
    • 您的解释与实际行为不符。使用subq $555555, rsp 行,一切顺利。如果你的答案是正确的,那是行不通的……
    • @Ben:Linux 只对主(初始)线程进行堆栈扩展。辅助线程需要预先实际分配其完整堆栈映射,因为让它增长是不安全的。阻止其他分配(使用mmap(MAP_ANONYMOUS))窃取堆栈预计将增长到的页面的“魔法”仅适用于主线程。但是是的,如果您对线程堆栈使用不安全的MAP_GROWSDOWN,那么您可以扩展它们。
    • @glglgl:关于保护页的段落是 Windows 的工作原理。后面的段落描述了 Linux,您可以在其中扩展 ulimit -s 内的任何位置而无需堆栈探测。它的描述不是很明确,但看起来是正确的。
    猜你喜欢
    • 2015-02-03
    • 1970-01-01
    • 1970-01-01
    • 2012-07-30
    • 2021-12-08
    相关资源
    最近更新 更多