一、C++ 源代码,本文所有汇编、函数堆栈数据情况都是根据以下源代码得到的
int Add(int x,int y)
{
int sum;
sum = x+y;
return sum;
}
int main(int argc, char * argv[])
{
int z;
z = Add(1,2);
}
二、需要知道的基础知识:
1、X86 寄存器基础
(1)ESP:栈顶指针,X86中的栈是向下增长,所以入站push时 esp--,出栈pop时,esp++
(2)EBP:函数的参数和局部变量都是存储在程序栈中,所以当一个函数想要获取它自己的参数或者局部变量时,想到的第一个方案就是使用(ESP寄存器的值+栈偏移量)推算出参数和局部变量的地址。但是栈顶指针的值会随着程序入栈和出栈操作不断变化。所以为了计算方便,可以将该值保存到另一个寄存器---EBP(extended base pointer,扩展基址寄存器)。这样获取参数可以用:EBP+偏移量,获取局部变量就可以用 EBP-偏移量了。(下图中的数据是根据第一部分的代码得到的)
(3)EBX ,基址寄存器,在内存中寻址时使用。
(4)ESI/EDI,源/目的地址寄存器,暂时不清楚有什么用
(5)ECX,(extended counter )计数器寄存器,和rep和loop指令搭配使用。主要用来进行循环计数
2、汇编指令基础
(1)lea指令,格式:lea + 目的寄存器+源操作数,作用:将源操作数的地址偏移量保存到目的寄存器中。学习lea指令可以和mov指令一起来记,他们格式相同,但mov指令是将操作数指向的内存中的数据保存到目的寄存器。
(2)call 指令,格式 :call + 目标地址,作用:将程序调转到目标地址处执行。
call 指令使用的是相对寻址,所谓的相对寻址就是:基址+偏移量 = 最终地址。在call指令中,基址就是call指令的下一条指令的起始地址。偏移量就是call指令中后4字节的内容。
call指令返回地址会在指令执行过程中被压到程序栈中。
等价指令:push EIP+5 ,jmp 目标地址
(3)ret指令,作用:将栈顶保存的地址弹入EIP指令寄存器,这个过程ESP要增大(因为执行了一次出栈操作)
(4)rep 指令,格式 rep+其他指令,作用:重复rep后面的其他指令,重复次数记录在ECX寄存器中,每次循环ECX寄存器执行减减操作。
(5)stos指令,格式 stos+目的地址,将寄存器EAX中的内容保存到目的地址处。目的地址格式 ES:[EDI] ,ES保存了段选择符,EDI保存了段偏移量。如果设置了direction flag, 那么EDI会在该指令执行后减小, 如果没有设置direction flag, 那么EDI的值会增加, 为下一次的存储做准备
三、正文,使用汇编分析函数调用并返回过程中的原理
1、main函数反汇编:
int main(int argc, char * argv[])
{
001D1A70 55 push ebp
001D1A71 8B EC mov ebp,esp
001D1A73 81 EC CC 00 00 00 sub esp,0CCh
001D1A79 53 push ebx
001D1A7A 56 push esi
001D1A7B 57 push edi
001D1A7C 8D BD 34 FF FF FF lea edi,[ebp+FFFFFF34h]
001D1A82 B9 33 00 00 00 mov ecx,33h
001D1A87 B8 CC CC CC CC mov eax,0CCCCCCCCh
001D1A8C F3