我有时会看到反汇编程序,其中包含以下指令:
mov %eax, -4(%esp)
将eax存储在esp-4的堆栈中,而不改变esp。
我想知道一般来说,你是否可以将数据放入堆栈指针之外的堆栈中,并保留这些数据(除非我特别注意,否则不会改变)。
此外,这取决于我使用的操作系统吗?
答案 0 :(得分:2)
使用哪个操作系统很重要,因为不同的操作系统有不同的ABI。 (如果您不知道这意味着什么,请参阅x86标签维基。)
我可以通过两种方式看到mov %eax, -4(%esp)
可以理智:
在Linux x32 ABI(具有32位指针的长模式)中,正常的x86-64 ABI中有128B red zone。编译器经常使用地址大小前缀生成代码,因为它们无法证明例如4(%rdi)
在每种情况下都与4(%edi)
相同(例如环绕式)。不幸的是,gcc 5.3仍然对堆栈上的本地使用32位寻址,只能在%rsp == 0
时包装(因为ABI要求它是16B对齐的)。
无论如何,void foo(void) { volatile int x = 10; }
编译为
movl $10, -4(%esp)
/ ret
gcc 5.3 -O3 -mx32
on the Godbolt Compiler Explorer。
在禁用中断的情况下运行的(内核)代码。由于除DMA之外不会发生任何异步,因此没有任何东西可以破坏你的堆栈内存。 (虽然x86有NMI:不可屏蔽的中断。根据NMI的处理程序,以及它们是否可以被阻塞,我认为NMI可能会破坏堆栈指针下面的内存。)
在用户空间中,您的信号处理程序不是唯一可以在堆栈指针下面异步破坏内存的东西:
正如Jester在对dwelch的回答的评论中指出的那样,堆栈指针下面的页面可以被丢弃(当然是异步的),因此临时使用大量堆栈的进程不会永远浪费所有这些页面。如果%esp
碰巧位于页面边界,则-4(%esp)
位于不同的页面中。而不是在新分配的堆栈内存页面中出错,访问堆栈指针下面的未映射页面会变成Linux上的段错误。
除非你有其他保证(例如红色区域),否则你必须假设%esp
以下的所有内容都在每条指令之间被潦草地写下来。没有一个标准的32位ABI有红色区域,而Windows 64bit ABI也没有红色区域。堆栈的异步使用(通常是Linux中的信号处理程序)是一个完整的程序,而不是编译器只能从当前编译单元确定的东西(即使在编译器可以证明-4(%esp)
所在的情况下与(%esp)
)相同的页面。
请注意,Linux x32 ABI是AMD64 aka x86-64的64位ABI,而不是i386,即IA32 aka x86-32。它更像是通常的AMD64 ABI,因为它是在之后设计的。
答案 1 :(得分:0)
修改
不确定你的意思是上面和下面的,因为有些人"看"地址增加或增加。
但这没关系。如果堆栈在地址X初始化并且当前为Y,则必须保留X和Y之间的数据(一端不包括在内)。任何一方的记忆都是合理的游戏。
编译器而不是操作系统会使这种情况发生,它会移动堆栈指针以覆盖该函数所需的任何内容。并在完成后将其移回。每个嵌套函数都会消耗越来越多的堆栈,每次返回都会返回一些。