C语言中函数执行过程中堆栈的变化

一个最简易的C函数:执行一个加法

int add(int x, int y) {
    return x + y;
}

void main() {
    
    __asm {
        // 下断点
        mov eax, eax;
    }

    add(1, 2);

    return;
}

VS2019中,创建项目。F5执行,右键到反汇编窗口。

这是main()的对应汇编代码:  

  

 此时的堆栈信息如图:

  

1 .F10(F11)逐步执行了PUSH 1,PUSH 2(函数执行前传入参数)后,ESP的值减少了2*4=8个字节

对应堆栈信息如下:

  

寄存器内容:

  

  可以看到ESP由原来的A88变成了A80,正好减少了8个字节。

 2.F11单步进入函数执行,这里需要注意的是VS执行函数前会先通过JMP指令跳到函数对应的地址,如图:

  

 此时继续F11执行即可进入函数内查看分步信息。

3.这是add函数执行前的堆栈及寄存器信息:

  

   

4.函数的执行使用了EBP寻址的方式保证了函数执行前后的堆栈平衡:

EBP寻址以前也有记录,这里介绍add函数对应汇编指令的含义:

  

(1)把EBP放入堆栈,同时让栈顶栈底指向同一处
PUSH EBP
MOV EBP,ESP

此时ESP与EBP的状态如图:

  

(2)开辟当前函数用到的内存,开辟了C0个这么大的内存:
SUB ESP,0C0h

C0/4=30,即开辟了30个4字节的内存(30格——3*16=48格格<10进制>),此时的堆栈信息如图:

  

一格为4字节,sub esp,c0就是开辟了c0个字节的内存,即48格。

且此时的ESP减少了C0,变成了009EF9B8,而EBP保存了原来的ESP的值。

(3) 因为函数中可能用到寄存器,但是这个寄存器的内容,在当前函数执行结束以后,后面的程序可能用到,所以需要先将用到的寄存器的内容先push入栈保存起来:
PUSH EBX
PUSH ESI
PUSH EDI

 对应堆栈信息:

  

(4)向缓冲区(函数开辟的那块内存)循环填入CC程序断点,防止缓冲区溢出.
LEA EDI,[EBP-0C0h]
MOV ECX,030h
MOV EAX,0CCCCCCCCh
REP SOTS DWORD PTR ES:[EDI]

MOV ECX,030h

MOV EAX,0CCCCCCCCh

REP SOTS DWORD PTR ES:[EDI]

  这三行指令的作用是循环执行把EAX的值放到EDI的位置,循环次数为ECX内保存的值(30h<48>次),正好是当前函数开辟的内存大小。

  执行结束后堆栈信息如下:

  

且这几行指令执行结束后ESP与EBP的值并没有改变。只是把缓冲区的内容全部填充成了CCCCCCCC断点。以便在缓冲区溢出时及时停止

(5) 函数执行1+2:把传入的参数x,y相加并保存到eax中
mov         eax,dword ptr [x]
add         eax,dword ptr [y]

 此时EAX:

  

(6)接下来就是恢复堆栈:

①还原EDI,ESI,EBX

POP EDI
POP ESI
POP EBX

 ②还原ESP

ADD ESP,0C0h

此时堆栈应该ESP=EBP=009EFA78

  

③还原EBP:把堆栈中存在栈顶的EBP的值POP到EBP中

POP EBP

 堆栈信息如图:

  

④return:表示add函数执行结束,这里的堆栈已经回到了进入add()函数之前的状态。

RET

 堆栈如图:

  

 (6)因为还有PUSH两个参数造成的两段已经使用过的内存A84和A88,所以在main()函数中还要进一步恢复:
ADD ESP,8

 此时的堆栈如图:

  

这样:ESP和EBP就回到了这个add函数执行之前的样子

原文地址:https://www.cnblogs.com/codexlx/p/13333831.html