代码:
#include"stdio.h"
int main()
{
printf("I love sin!");
return 0;
}
注:在printf处下断点,按F5执行,右键点击进入反汇编,按F10逐句调试,F11逐过程调试(需要进入call内部,需要按F11)
反汇编(Release版):
printf("I love sin!");
EAX = 0102F6D0 EBX = 00879000 ECX = 00000000 EDX = 00000000 ESI = 75F36314 EDI = 75F36308 EIP = 00E21040 ESP = 00AFFB0C EBP = 00AFFB50 EFL = 00000200
00E21040 push offset string "I love sin!" (0E220F8h) (参数入栈)
00E21045 call printf (0E21010h)
ESP = 00AFFB04 EBP = 00AFFB50
=>只进行了一次跳转,esp却上移了4字节
00E21010 push ebp =>保存栈顶,用于还原现场
00E21011 mov ebp,esp =>栈底上移到栈顶的位置,保护数据
00E21013 push esi
int _Result;
va_list _ArgList;
__crt_va_start(_ArgList, _Format);
_Result = _vfprintf_l(stdout, _Format, NULL, _ArgList);
00E21014 mov esi,dword ptr [_Format]
00E21017 push 1
ESP = 00AFFAF8 EBP = 00AFFB00
00E21019 call dword ptr [__imp____acrt_iob_func (0E220B0h)]
ESP = 00AFFAF4 EBP = 00AFFB00
75E9CF70 mov edi,edi
ESP = 00AFFAF4 EBP = 00AFFB00
75E9CF72 push ebp ==>存入ebp地址
ESP = 00AFFAF0 EBP = 00AFFB00
75E9CF73 mov ebp,esp =>ebp上移到esp位置
ESP = 00AFFAF0 EBP = 00AFFAF0
75E9CF75 imul eax,dword ptr [ebp+8],38h
75E9CF79 add eax,75F352E0h
ESP = 00AFFAF0 EBP = 00AFFAF0
75E9CF7E pop ebp
ESP = 00AFFAF4 EBP = 00AFFB00
75E9CF7F ret
ESP = 00AFFAF8 EBP = 00AFFB00
00E2101F add esp,4
00E21022 lea ecx,[ebp+0Ch]
00E21025 push ecx
00E21026 push 0
00E21028 push esi
00E21029 push eax
ESP = 00AFFAF8 EBP = 00AFFB00
00E2102A call __local_stdio_printf_options (0E21000h)
ESP = 00AFFAF8 EBP = 00AFFB00
00E2102F push dword ptr [eax+4]
00E21032 push dword ptr [eax]
00E21034 call dword ptr [__imp____stdio_common_vfprintf (0E220ACh)]
00E2103A add esp,18h
00E2103D pop esi
__crt_va_end(_ArgList);
return _Result;
}
00E2103E pop ebp
00E2103F ret
00E2104A add esp,4
return 0;
00E2104D xor eax,eax
}
00E2104F ret
注:本程序我并没有全部的逐过程调试(太多跳转了!!)
总结
===>可以发现,完成这个call指令之后,ebp的地址与进入call指令之前是相同的,也就是说,在执行完这个指令后,ebp回到了原现场,可以继续进行原来的操作。
===>当进行跳转后的另一函数体的执行时,都会出现这样的操作其目的是为了保护当前进行的程序所用到的数据,然后再跳至另一内存地址执行跳转之后的指令,即发生下图所示动作(当新函数有数据进入时,esp会继续上游,出栈时esp向ebp靠拢,当esp等于ebp时(即栈底等于栈顶),栈清空):
===>每次指令call指令跳转后,esp都会莫名的多出4个字节(一个地址),但是我们并未对其进行操作,为什么会多出来?而执行完call指令返回后,这4个字节又消失了,这又是为什么?4个字节刚好是一个地址,而其中保存的正是当前执行到的地址,为了能够在执行完call指令之内的程序时,可以回到原来的路,编译器自动为我们保存了该地址,在ret的时候自动返回。
===>关于参数入栈:
在开始的时候,程序即对参数进行了push的操作,说明我们的程序的参数同样也是保存在栈中进行使用的