堆栈红色区域的实际大小是多少? [复制]

Posted

技术标签:

【中文标题】堆栈红色区域的实际大小是多少? [复制]【英文标题】:What is the actual size of stack red zone? [duplicate] 【发布时间】:2019-11-16 19:41:09 【问题描述】:

在x86-64 System V ABI 中指定$rsp - 128 后面的空间是所谓的红色区域,没有被任何信号处理程序触及。在我的机器上

$ ulimit -s
8192

我预计堆栈中只有 2 页。所以我写了下面的程序来测试红色区域可以扩大到多大:

PAGE_SIZE equ 0x1000
SYS_exit equ 0x3C

section .text
global _start 

_start:
    lea rcx, [rsp - 0x1f * PAGE_SIZE]
    mov rax, rsp
loop:
    sub rax, PAGE_SIZE
    mov qword [rax], -1
    cmp rax, rcx
    jne loop

    mov rax, SYS_exit
    mov rdi, 0x20

所以我预计程序总是会失败。但该程序有时会因SEGV 而失败,有时完成得很好

行为与MAP_GROWSDOWN 记录的完全一样:

此标志用于堆栈。它向内核表示虚拟 映射应该在内存中向下扩展的内存系统。这 返回地址比实际的内存区域低一页 在进程的虚拟地址空间中创建。触摸地址 映射下方的“守卫”页面将导致映射增长 页面。这种增长可以重复,直到映射增长到 在下一个较低映射的高端的一页内,其中 点触“守卫”页面将产生SIGSEGV 信号。

正如所讨论的,使用MAP_GROWSDOWNPROT_GROWSDOWN 创建的in this question 映射不会以这种方式增长:

volatile char *mapped_ptr = mmap(NULL, 4096,
                        PROT_READ | PROT_WRITE | PROT_GROWSDOWN,
                        MAP_GROWSDOWN | MAP_ANONYMOUS | MAP_PRIVATE,
                        -1, 0); 

mapped_ptr[4095] = 'a';  //OK!
mapped_ptr[0]    = 'b';  //OK!
mapped_ptr[-1]   = 'c';  //SEGV

问题:结合上面的推理,唯一使用MAP_GROWSDOWN的映射是主线程的[stack]映射吗?

【问题讨论】:

红色区域始终是 RSP 当前值下方的 128 个字节。它根本不基于堆栈的大小。 @MichaelPetch The red zone is always the 128 bytes - 是不是同一个红区 Raymond Chen discussed? 请注意,ulimit 内置以 1024 字节为增量打印大小。所以当ulimit -s 打印 8192 时,就意味着你的堆栈限制是 8MiB -- 2048 页。 由于没有标记操作系统,需要注意的是Windows没有红色区域。 sub rax, 4096相比,为什么循环如此复杂?我认为你通过每次重做乘法来做同样的事情,但它更难遵循。 【参考方案1】:

这些都与红色区域无关,因为您没有移动 RSP。内存保护适用于页面粒度,但红色区域始终仅比 RSP 低 128 字节,这对于读/写是安全的以及,并且不受异步破坏。


不,除非您手动使用,否则不会使用 MAP_GROWSDOWN。主线程的栈使用了一个不可破坏的机制,不会让其他mmap 调用随机窃取它的增长空间。在Analyzing memory mapping of a process with pmap. [stack]上查看我的回答

您的 asm 代码的有时成功与 Why does this code crash with address randomization on? - 您在 RSP 下最多触及 124 kiB 的内存,因此 132 kiB 的初始分配有时恰好足够,具体取决于 ASLR 以及 args + env 在堆栈上占用的空间。

Why is MAP_GROWSDOWN mapping does not grow? 是有趣的部分:MAP_GROWSDOWN 可能不适用于 1 页映射。但同样,这与堆栈无关。手册页显示“此标志用于堆栈”。是 100% 错误的。这是添加功能时的意图,但设计实际上并不可用,因此即使与文档相比,实现也可能存在错误。

【讨论】:

总结一下。只要保留ulimit -s,内核就可以增加堆栈,但是如果我们触摸初始映射的132KiB之外的页面并且正常的#PF处理机制将SIGSEGV发送到进程,因为我们尝试过访问未被进程映射的内存。我检查了sub rsp, 0x10000 * PAGE_SIZE\n mov qword [rsp], -1ulimit -s unlimited,即使在没有调整rsp 结果SIGSEGV 的情况下触摸页面时没有触摸中间页面,它也能正常工作。 @St.Antario:是的,只要红色区域(或 RSP)的底部低于或在出错的页面内。否则它只是一个无效的#PF -> SIGSEGV。 (您评论的第一句话省略了有关 RSP 必须移动的部分,所以这不是一个很好的总结。)【参考方案2】:

您混淆了2个不同的概念,除了它们都涉及堆栈,红色区域和堆栈内存区域的扩展无关。如果调用了信号处理程序并且未指定替代信号处理程序堆栈,则会更改红色区域下方但位于堆栈内的内存位置。

我怀疑mmap 分配的MAP_GROWSDOWN 区域未能增长是另一个区域在下方,mmap 通常会连续向下分配虚拟地址。

【讨论】:

感谢红区区域的解释。根据MAP_GROWSDON失败,我检查了进程的内存映射,发现mmap调用返回的指针等于0x7fb361abc000,但是它下面最近的映射区域是7fb361aa2000所以这不太可能就是这样。我还尝试将特定地址指定为mmap 的参数,但结果相同。 @St.Antario: 主线程的栈是not MAP_GROWSDOWN。它可以一步增长到最大堆栈大小,而无需接触保护页面的“堆栈探测器”(但only if RSP is decremented first),并且保留潜在的堆栈增长区域,因此其他分配不会' t不小心偷了它。 (对于MAP_GROWSDOWN,这些都不正确,这就是为什么它对线程堆栈不安全。pthreads 分配完整大小的线程堆栈,因为 Linux 无论如何都会对物理页面进行惰性分配。请参阅this。

以上是关于堆栈红色区域的实际大小是多少? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

当我们有一个红色区域时,为啥我们需要堆栈分配?

Java VM 对堆栈红色区域的无效访问

Grails:堆栈红色区域的无效访问

画布对齐问题

根据子视图大小调整 XIB 中的 UIView 大小

2019前端面试系列——CSS面试题