intel cpu的程序,关于函数调用压栈的过程,到底如何实现的? ESP,EBP,EIP都负责什么任务?
我们现在要调用函数A, 有三个参数,a,b,c.。执行A函数,需要两部,第一步, 在进入函数之前,要将c,b,a依次压入栈中,然后进入到函数A中。第二步,开始执行程序。
第一步具体的细节为:
mov c 0xC(esp)
mov b 0x8(esp)
mov a 0x4(esp)
call A
(esp)代表堆栈寄存器esp所指向的内存地址。oxc(esp)代表了(esp所指向的内存地址+0xC)的地址。
执行call A指令时,会将EIP(下一步要执行的指令的地址)存入堆栈中,即好比执行了mov EIP (esp).
第二步的细节为:
push ebp
mov esp ebp
..........
pop ebp
ret
第二步中,push ebp和mov esp ebp不是A函数中要处理的逻辑,是为了函数调用而实现的,任何一个函数调用都会有这两步。 ,.....代表函数A中的一些处理分为几种情况:
(1) 如果要使用函数A的参数,则使用ebp+offset来获得;
(2) 如果要进行函数调用,则像第一步一样;
(3) 如果要使用栈stack, 则使用esp堆栈指针。但是在第二步的ret之前,要将esp恢复到原值。
intel中关于call操作的解释为,先将esp的值改变,然后压入栈中:
ELSE IF StackAddrSize = 32
THEN
IF OperandSize = 32
THEN
ESP ← (ESP − 4);
IF (SRC is FS or GS)
THEN
TEMP = ZeroExtend32(SR
ELSE IF (SRC is IMMEDIATE)
TEMP = SignExtend32(SRC
ELSE
TEMP = SRC;
FI;
SS:ESP ← TEMP; (* Push doublewor
ELSE (* OperandSize = 16*)
ESP ← (ESP − 2);
SS:ESP ← SRC; (* Push word *)
可以看到,第二步完成之后,ebp和esp的值都没有改变。
在进行第二步时,刚进入第二步,执行了push ebp和mov esp ebp, 但是还没有执行A自己代码时,(esp)的值为执行完A后要执行的代码的地址EIP的值。0x4(esp)的值为参数a, 0x8(esp)为参数b,0xc(esp)为参数c.
总结:
我写这篇文章所用的编译器为GCC, Intel x86平台。希望对于esp, ebp,eip作用和函数调用如何实现比较困惑的朋友有所帮助。