栈 Stack

栈是一种数据结构,遵循Last in, First out (LIFO)规则,栈在中文语境中有时也被叫做堆栈。在实际中,是一片内存空间。

IMG

在栈中,有两个重要的寄存器

SP (Stack Pointer)

BP (Base Pointer)

在16位系统中是这样的,在32位系统中分别叫做esp ebp,在64位中

栈是从高地址向低地址搭起来的,因此栈顶位于低地址,栈底位于高地址。一般来说是这样,当然你的程序也可以不这样做,主打一个叛逆。

ESP 函数调用

要理解栈和那两个有关栈的寄存器,可以通过函数调用来理解。

以下是x86 32bit的汇编程序。

push 1
push 2
call 002110D
add esp,8
nop
; this func will add up a and b
mov eax,dword ptr [esp+4]
add eax,dword ptr [esp+8]
ret

其中push操作码不仅会将参数压入栈中,还会使esp寄存器的值减4,即sub esp,#4。以确保esp始终指向栈顶。

call首先会用push来把下一个操作码的地址压入栈中,这个地址叫做返回地址,然后跳转到函数所在的地址。

在进入到func后,此时esp指向之前call所压入栈内的地址。就可以直接通过exp+4向下拿到参数。

执行完函数后,就需要考虑栈平衡了。要让调用函数后的栈和调用函数前的保存一致。

ret 跳转到返回地址,执行add esp,8 清除栈内的函数参数。

EBP 函数调用

但分析C语言的函数调用,会发现与上文的esp寻址有些不同,一般是用ebp来进行参数的寻址。

int add(int a, int b) {
    return a + b;
}

int main() {
    add(1, 2);
    return 0;
}

这是一段简单的C代码,用gcc将其编译成32位可执行文件

gcc -m32 -o main main.c

对于Windows 64 bit要想编译32位程序,如果MinGW的exception model是SEH,那么可以用i686-w64-mingw32-gcc来编译32位程序

对main.exe进行反汇编

AEM1

在上图中可以看到,在调用call前,先处理两个参数,但这里因为编译器等等问题使用了mov而不是push,实现的效果也是一样的。这里没有用push的区别就是esp没有始终指向栈顶,,所以也不用考虑栈平衡了。

AEM2

这里就是通过ebp来使用两个形参,分别赋到了EDX, EAX。然后EAX储存函数返回值。


Refs

https://blog.csdn.net/song_lee/article/details/105297902