由于工作中偶尔需要逆向过程或者逆向调试,这时就需要看汇编代码,才发现原来很多指令都有些遗忘了,同时看到很多汇编语句段也会比较陌生。其实工作时间越久,更体会到基础的重要性。很多时候,如果要要深刻理解高级语言的运行,通过调试分析生成的汇编指令可能效果更好,更不容易遗忘。
这里通过查看Visual Studio 2008生成的汇编语句,来一步步熟悉汇编指令。
首先是C++中的简单for循环。
for(int i=0;i<30;i++)
{
printf("kkk\r\n");
}
在Debug模式下编译调试:
for(int i=0;i<30;i++)
009F16FE mov dword ptr [i],0 ; 初始化 i = 0
009F1705 jmp main+30h (9F1710h) ; 无条件跳转 标记为label-1
009F1707 mov eax,dword ptr [i] ; label-3
009F170A add eax,1 ; i++
009F170D mov dword ptr [i],eax
009F1710 cmp dword ptr [i],1Eh ; label-1
009F1714 jge main+4Fh (9F172Fh) ; 大于或等于 30 跳转 标记为label-2
{
printf("kkk\r\n");
009F1716 mov esi,esp ;保存栈顶指针
009F1718 push offset string "kkk\r\n" (9F58E8h) ;字符串指针入栈
009F171D call dword ptr [__imp__printf (9F82F8h)] ;调用printf函数
009F1723 add esp,4 ;参数退栈
009F1726 cmp esi,esp ;比较栈顶指针,用于判断是否有堆栈溢出或破坏
009F1728 call @ILT+325(__RTC_CheckEsp) (9F114Ah)
}
009F172D jmp main+27h (9F1707h) ; 无条件跳转 标记为label-3
009F172F mov esi,esp ; label-2 已退出for循环
在Realease模式下编译调试:
for(int i=0;i<30;i++)
00121001 mov esi,dword ptr [__imp__printf (1220A0h)] ; printf 函数指针入栈
00121007 push edi
00121008 mov edi,1Eh ;初始化为30
0012100D lea ecx,[ecx]
{
printf("kkk\r\n");
00121010 push offset string "kkk\r\n" (122104h) ;字符串指针入栈 label-1
00121015 call esi
00121017 add esp,4 ;参数退栈
0012101A sub edi,1 ;edi 减1
0012101D jne main+10h (121010h) ;ZF=1,即edi为0,则退出循环,否则跳转 标记为label-1
}
printf("size = %d\n",sizeof(KDPC1));
0012101F push 20h
如上,通过分析debug和release模式下生成的汇编指令。可以知道:
(1)同样对应for语言。debug汇编指令基本是按照c、c++代码的顺序进行翻译,便于调试和单步跟踪。realease汇编指令则进行了很大程度上的优化,不仅仅是指令级别的优化,甚至于代码流程上的优化。
(2)C++中的默认函数调用方式,也是调用方负责清理堆栈的。对应于add esp,4 。
(3)Debug模式下,Vistual Studio可以配置执行相关的运行时刻检测,比如上面的 call @ILT+325(__RTC_CheckEsp) (9F114Ah) 。