Memory 堆栈是否仅保留在堆栈指针上方?

Memory 堆栈是否仅保留在堆栈指针上方?,memory,assembly,stack,x86,Memory,Assembly,Stack,X86,我有时会看到反汇编程序的指令如下: mov %eax, -4(%esp) 在不改变esp的情况下,将eax存储到esp-4的堆栈中 我想知道,通常情况下,您是否可以将数据放在堆栈指针之外的堆栈中,并保留这些数据(除非我专门这样做,否则不会更改) 此外,这是否取决于我使用的操作系统?编辑 不确定你所说的“上”和“下”是什么意思,因为有些人“看到”地址是递增还是递减 但这并不重要。如果堆栈在地址X处初始化,并且当前在Y处,则必须保留X和Y之间的数据(一端不包括在内)。任何一方的记忆都是公平的 编译

我有时会看到反汇编程序的指令如下:

mov %eax, -4(%esp)
在不改变esp的情况下,将eax存储到esp-4的堆栈中

我想知道,通常情况下,您是否可以将数据放在堆栈指针之外的堆栈中,并保留这些数据(除非我专门这样做,否则不会更改)

此外,这是否取决于我使用的操作系统?

编辑

不确定你所说的“上”和“下”是什么意思,因为有些人“看到”地址是递增还是递减

但这并不重要。如果堆栈在地址X处初始化,并且当前在Y处,则必须保留X和Y之间的数据(一端不包括在内)。任何一方的记忆都是公平的


编译器而不是操作系统实现了这一点,它移动堆栈指针以覆盖该函数所需的任何内容。完成后将其向后移动。每个嵌套函数消耗越来越多的堆栈,每个返回都有一点返回。

使用哪个操作系统很重要,因为不同的操作系统有不同的ABI。(如果您不知道这意味着什么,请参阅标记wiki)

我可以通过两种方式看出
mov%eax,-4(%esp)
可能是正常的:

  • 在(32位指针的长模式)中,与普通x86-64 ABI中的128B类似。当编译器无法证明例如
    4(%rdi)
    在每种情况下(例如,环绕)都与
    4(%edi)
    相同时,它们通常使用地址大小前缀生成代码。不幸的是,gcc 5.3仍然对堆栈上的局部变量使用32位寻址,这只能在
    %rsp==0
    时进行包装(因为ABI要求它与16B对齐)

    无论如何,
    void foo(void){volatile int x=10;}
    编译为
    movl$10,-4(%esp)
    /
    ret
    with

  • 在禁用中断的情况下运行的(内核)代码。因为除了DMA之外,没有什么异步的事情可以发生,所以没有什么可以破坏堆栈内存。(尽管x86有NMIs:不可屏蔽中断。我认为,取决于NMIs的处理程序以及它们是否可以被阻止,NMIs可能会阻塞堆栈指针下的内存。)

    在用户空间中,您的信号处理程序不是唯一可以异步关闭堆栈指针下内存的东西:

    正如Jester在对dwelch答案的评论中指出的那样,堆栈指针下的页面可以被丢弃(当然是异步的),因此临时使用大量堆栈的进程不会永远浪费所有这些页面。如果
    %esp
    恰好位于页面边界,则
    -4(%esp)
    位于不同的页面中。在Linux上,访问堆栈指针下未映射的页面会变成SEGFULTS,而不是在新分配的堆栈内存页中出错


除非您另有保证(例如红色区域),否则您必须假设
%esp
下面的所有内容都是在每个指令之间涂鸦的。标准32位ABI都没有红色区域,Windows 64位ABI也没有红色区域。堆栈的异步使用(通常由Linux中的信号处理程序)是整个程序的事情,而不仅仅是编译器可以从当前编译单元确定的事情(即使在编译器可以证明
-4(%esp)
(%esp)
在同一页的情况下)


请注意,Linux x32 ABI是AMD64 aka x86-64的64位ABI,而不是i386 aka IA32 aka x86-32。它更像通常的AMD64 ABI,因为它是在之后设计的。

取决于操作系统、调用惯例和运气。在64位模式下,如果您使用的是sysv abi,那么您将得到一个128字节的红色区域,因此您可以在没有显式分配的情况下访问该区域。在其他系统上,通常所有下注都是关闭的,您不应该在堆栈指针下访问。它可能会出现故障(如果您碰巧进入了未映射的内存),或者可能会被信号之类的东西撞坏。感谢您的快速响应!因此,如果我只是将堆栈指针减去一个很大的量,那么会有一个很大的堆栈空间(其中甚至可能没有有用的数据),操作系统不会触及这个空间?在32位Linux代码中,您从ESP中减去函数所需的本地堆栈数据量-是的。在64位Linux代码中,只要您的函数不调用其他函数,就可以在不移动RSP的情况下使用低于RSP当前值128字节的数据,而不用担心数据被破坏。正如Jester指出的,这被称为红色区域,正如在中所讨论的,您确定它是-4(esp)而不是-4(ebp)吗?请参阅@MichaelPetch:有一种情况是,存在一个红色区域,取消引用
%esp
是有意义的:Linux x32 ABI。超出Y(我想你的意思是Y是堆栈指针)肯定取决于操作系统。诸如信号和中断之类的异步内容可能会假定该区域是自由的。交换可能不会保留它。内存可能还没有被分配,操作系统可能会考虑到一个错误(Linux确实如此)。那么这个目标的编译器会知道,而不是尝试使用它。不管怎样,当您有多个使用这些寄存器的执行路径(isr或更复杂的(操作系统))时,这样做是不明智的。也许我误解了您的意思,但Linux内核本身并不尊重红色区域(内核内).GCC被告知在编译内核本身时不要使用它。因此,在任何情况下,您都不应该期望Linux内核中的红色区域受到尊重。@MichaelPetch:禁用中断后,在重新启用它们之前,任何异步(DMA除外…)都不会发生。我称之为“禁用中断”一个单独的例子,与红色区域不同。作为一个历史脚注,在某些情况下,ESP在GCC 2.96上出现了一个错误。一个错误让许多软件项目措手不及。我相信在官方3.0发布时,这个讨厌的错误已经修复。