为什么ESP被屏蔽为0xFFFFFFF0?

Bob*_*421 4 x86 assembly elf

我已经拆解了一个程序.我在开始时看到一个ANDESP和的指令0xFFFFFFF0.

这个面具是什么意思?这是对齐问题吗?

它是32位x86的ELF可执行文件.

Pet*_*des 6

gcc for i386 Linux默认为-mpreferred-stack-boundary=4(意味着2 4 = 16字节对齐).(其他非Linux系统也使用ELF可执行文件,但它们也具有相同的堆栈对齐默认值和SysV ABI.)

与clang不同,gcc不会假设堆栈在入口处对齐main,因此它使用AND指令来屏蔽堆栈指针的低位.这是在堆栈上保留足够的填充以到达下一个对齐边界的最便宜的方法.

堆栈对齐与您将看到调用另一个函数的函数保留堆栈上的某些空间的原因相同,它不会用于任何事情:

extern int bar(void);
int foo(int x) { return x+bar(); }

  gcc 5.3 -O3
    sub     esp, 12                   # align the stack for another call
    call    bar
    add     eax, DWORD PTR [esp+16]   # add our arg (from the stack) to bar()'s return value (in eax)
    add     esp, 12
    ret
Run Code Online (Sandbox Code Playgroud)

在Godbolt编译器资源管理器中查看此内容,您可以在其中尝试不同的编译器和选项.

-mincoming-stack-boundary=3(或更少)导致堆栈对齐样板被添加到每个函数(不仅仅是main). -mstackrealign-mno-stackrealign有没有影响foo()main()有或无小-mincoming-stack-boundary.基于gcc手册中的文档,我认为它将启用或禁用除main之外的函数或main的对齐函数.


32位的x86的SysV ABI仅用于保障4个字节的栈对齐,但调用约定保证现在的16字节对齐%esp一个之前call指令.

第2.2.2节堆栈框架

...换句话说,%esp + 4当控制转移到函数入口点时,value()总是16的倍数.(当32位ymm向量按值传递时为32)

所以gcc -mpreferred-stack-boundary=4不仅仅是一个好主意,而是法律(在像Linux这样的系统上,包含这种更强保证的更新ABI版本是官方标准).这使得除了main在使用对齐的SSE存储/加载到堆栈之前省略该对齐步骤之外的功能是安全的.这些指令(如movaps)将在未对齐的地址上出错.因此,正确性需要对齐,而不仅仅是性能.


AND没有实际需要,这些天指令

32位的当前版本ABI 保证一个freshly- execve版32位进程将开始%esp对齐16字节.(第2.3.1节"初始堆栈和寄存器状态",请参阅有关其%esp自身初始状态的项目符号,而不是堆栈内容.)

这意味着gcc在main的开头对齐堆栈​​的行为现在已经过时,假设调用的CRT启动代码main没有错位堆栈.

clang确实假设堆栈在main的开头对齐,就像64位gcc那样.

16B-alignment从一开始就是x86-64 SysV ABI的一部分,后来没有添加,因此它始终是一个安全的假设,并且没有旧的内核在进程启动时不提供.

标签wiki有链接到其他的ABI,以及更多.

  • 不幸的是,ABI的改变无偿地向后兼容地打破了ABI.它也是"法律",只是你认为该文件的GNU版本正式取代了实际的System V ABI文档. (2认同)
  • 即使在Linux上,也不清楚是什么让它成为"官方".Linux内核本身特别不使用16字节堆栈对齐,并且每个Linux发行版都是免费的,他们自己决定他们认为是官方的.当他们升级用于构建用户域的编译器时,大多数人会默默地,并且可能无知地采用更新后的ABI. (2认同)