You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86 32位反汇编中栈对齐与函数参数访问的疑问

重要说明

本问题与ARM汇编无关,添加Android标签仅为环境说明。

我了解汇编基础,但刚接触反汇编,尤其对汇编中函数参数的访问存在疑惑。汇编中通常使用EBP(帧指针)访问局部变量,因其在函数栈帧执行期间保持静态,而ESP会不断递减,因此通过EBP加固定偏移访问函数参数更简便。

环境信息

使用Android Termux环境

安装proot-distro运行Linux(Ubuntu)ELF文件

使用交叉编译器i686-linux-gnu-gcc编译x86 32位ELF文件

使用x86_64-linux-gnu-objdump进行反汇编

疑惑的指令

在反汇编结果中,我注意到以下指令:

endbr32
LEA  ECX, [ESP+ 0x4]
AND  ESP, 0xfffffff0
PUSH DWORD PTR [ECX- 0x4]

我理解AND ESP, 0xFFFFFFF0指令:通过将ESP与掩码0xFFFFFFF0进行按位与,清零ESP的低四位,将栈对齐到16字节边界。这是因为movaps等SSE指令要求XMM寄存器中的打包数据类型需16字节对齐,非对齐操作(如movups)会降低性能;同理AVX-2要求YMM寄存器256位(32字节)对齐,AVX-512要求ZMM寄存器512位(64字节)对齐。

但我对栈对齐指令前后的两条指令的作用存在疑惑:

LEA  ECX, [esp + 0x4]
PUSH DWORD PTR [ECX- 0x4]

我的解读

LEA ecx, [esp + 0x4]指令将ESP上方4字节的地址加载到ECX中。

PUSH DWORD PTR [ecx - 0x4]指令将调用者的返回地址压入对齐后的栈中,确保调用指令前栈帧对齐。

观察结论

参数访问偏移受影响

由于对齐空间的存在,函数参数相对于EBP的偏移不再固定。例如常规汇编中可通过EBP+8等固定偏移访问参数,但16字节对齐后,偏移量取决于返回地址的栈地址,导致无法再使用固定偏移访问参数。

例如根据返回地址的不同,对齐可能占用2个或3个DWORD,无法固定。


示例程序1

#include <stdio.h>
int main(int argc, char **argv) {
    printf("Count: %d\n", argc);
    return 0;
}

main函数反汇编结果

0000118d <main>:
    118d:       f3 0f 1e fb             endbr32
    1191:       8d 4c 24 04             lea    ecx, [esp + 0x4]
    1195:       83 e4 f0                and    esp, 0xfffffff0
    1198:       ff 71 fc                push   DWORD PTR [ecx - 0x4]
    119b:       55                      push   ebp
    119c:       89 e5                   mov    ebp, esp
    119e:       53                      push   ebx
    119f:       51                      push   ecx
    11a0:       e8 2c 00 00 00          call   11d1 <__x86.get_pc_thunk.ax>
    11a5:       05 33 2e 00 00          add    eax, 0x2e33
    11aa:       89 ca                   mov    edx, ecx
    11ac:       83 ec 08                sub    esp, 0x8
    11af:       ff 32                   push   DWORD PTR [edx]
    11b1:       8d 90 30 e0 ff ff       lea    edx, [eax - 0x1fd0]
    11b7:       52                      push   edx
    11b8:       89 c3                   mov    ebx, eax
    11ba:       e8 81 fe ff ff          call   1040 <printf@plt>
    11bf:       83 c4 10                add    esp, 0x10
    11c2:       b8 00 00 00 00          mov    eax, 0x0
    11c7:       8d 65 f8                lea    esp, [ebp - 0x8]
    11ca:       59                      pop    ecx
    11cb:       5b                      pop    ebx
    11cc:       5d                      pop    ebp
    11cd:       8d 61 fc                lea    esp, [ecx - 0x4]
    11d0:       c3                      ret

各指令执行后的栈布局

初始栈布局

Argument 2 (Top): [ESP + 8] (0xbfff1230)
       Argument 1:       [ESP + 4] (0xbfff122c)
       Return Address:   [ESP]     (0xbfff1228)

LEA ECX, [ESP + 4]执行后栈布局

Argument 2 (Top): [ESP + 8] (0xbfff1230)
       Argument 1:       [ESP + 4] (0xbfff122c)
       Return Address:   [ESP]     (0xbfff1228)

ECX -> 第一个参数的地址

AND ESP, 0xFFFFFFF0执行后栈布局

Argument 2 (Top):  [OLD ESP + 8]  (0xbfff1230)
       Argument 1:        [OLD ESP + 4]  (0xbfff122c)
       Return Address:    [OLD ESP]      (0xbfff1228)
       Alignment DWORD 1: (0xbfff1224)
       Alignment DWORD 2: (0xbfff1220) <- ESP

ESP -> 0xbfff1220

PUSH DWORD PTR [ECX - 4]执行后栈布局

Argument 2 (Top):  [OLD ESP + 8]  (0xbfff1230)
       Argument 1:        [OLD ESP + 4]  (0xbfff122c)
       Return Address:    [OLD ESP]      (0xbfff1228)
       Alignment DWORD 1: (0xbfff1224)
       Alignment DWORD 2: (0xbfff1220) 
       Return Address:    <- ESP (0xbfff121C)

ESP -> 0xbfff121C

结合上述栈布局,我认为ECX被加载了[ESP + 4]的地址(即第一个参数的地址),由于对齐导致无法用EBP访问参数,ECX被用于存储函数参数的起始地址。请问我的理解是否正确?


示例程序2

#include <stdio.h>
int main(int argc, char **argv) {
    printf("Count: %d\n", argc);
    printf("Filename: %s\n", *argv);
    return 0;
}

反汇编结果

0000118d <main>:
    118d:       f3 0f 1e fb             endbr32
    1191:       8d 4c 24 04             lea    ecx, [esp + 0x4]
    1195:       83 e4 f0                and    esp, 0xfffffff0
    1198:       ff 71 fc                push   DWORD PTR [ecx - 0x4]
    119b:       55                      push   ebp
    119c:       89 e5                   mov    ebp, esp
    119e:       56                      push   esi
    119f:       53                      push   ebx
    11a0:       51                      push   ecx
    11a1:       83 ec 0c                sub    esp, 0xc
    11a4:       e8 e7 fe ff ff          call   1090 <__x86.get_pc_thunk.bx>
    11a9:       81 c3 2f 2e 00 00       add    ebx, 0x2e2f
    11af:       89 ce                   mov    esi, ecx
    11b1:       83 ec 08                sub    esp, 0x8
    11b4:       ff 36                   push   DWORD PTR [esi]
    11b6:       8d 83 30 e0 ff ff       lea    eax, [ebx - 0x1fd0]
    11bc:       50                      push   eax
    11bd:       e8 7e fe ff ff          call   1040 <printf@plt>
    11c2:       83 c4 10                add    esp, 0x10
    11c5:       8b 46 04                mov    eax, DWORD PTR [esi + 0x4]
    11c8:       8b 00                   mov    eax, DWORD PTR [eax]
    11ca:       83 ec 08                sub    esp, 0x8
    11cd:       50                      push   eax
    11ce:       8d 83 41 e0 ff ff       lea    eax, [ebx - 0x1fbf]
    11d4:       50                      push   eax
    11d5:       e8 66 fe ff ff          call   1040 <printf@plt>
    11da:       83 c4 10                add    esp, 0x10
    11dd:       b8 00 00 00 00          mov    eax, 0x0
    11e2:       8d 65 f4                lea    esp, [ebp - 0xc]
    11e5:       59                      pop    ecx
    11e6:       5b                      pop    ebx
    11e7:       5e                      pop    esi
    11e8:       5d                      pop    ebp
    11e9:       8d 61 fc                lea    esp, [ecx - 0x4]
    11ec:       c3                      ret

从反汇编结果可见:

第二个参数的偏移量基于ECX指向的基地址计算。此处使用了ESI + 4,而ESI的值来自ECX,随后将ESI + 4的值移入EAX并压栈。

因此我认为LEA ecx, [esp + 0x4]指令用于保存函数参数的起始地址(即第一个参数的地址),以便在函数内部访问参数。请指正该理解是否正确。


内容的提问来源于stack exchange,提问作者Nalan PandiKumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:20:54