摘要:主要谈谈vc里面函数调用汇编成汇编代码的情形,首先针对之前的一个小程序,说说vc编译器的优化。
例子程序:
#include <iostream> //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm
{
mov dword ptr [ebp-4], 20h
}
int b = i;
cout << "i=" << b << endl;
return 0;
}
这段代码很简洁,但汇编代码可不简洁,首先看看release模式下的汇编代码概况:
……
; 14 :
; 15 : //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
; 16 : __asm
; 17 : {
; 18 : mov dword ptr [ebp-4], 20h
mov DWORD PTR [ebp-4], 32 ; 00000020H
; 19 : }
; 20 : int b = i;
; 21 : cout << "i=" << b << endl;
push 10 ; 0000000aH
push OFFSET FLAT:??_C@_02HDOK@i?$DN?$AA@ ; `string'
push OFFSET FLAT:?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A ; std::cout
call ??6std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<
add esp, 8
mov ecx, eax
call ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z ; std::basic_ostream<char,std::char_traits<char> >::operator<<
mov esi, eax
push 10 ; 0000000aH
mov ecx, esi
call ?put@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV12@D@Z ; std::basic_ostream<char,std::char_traits<char> >::put
mov ecx, DWORD PTR [esi]
xor edi, edi
……
调用cout前面,直接一个push 10,这是函数调用前压参数的过程,压了个常数在里面,呵呵,其实i已经被修改了,但是编译器不知道,以为i仍然是10,顾做了优化,压参压了常量在里面。
再看看debug模式下的汇编代码情况:
16: __asm
17: {
18: mov dword ptr [ebp-4], 20h
004017DE mov dword ptr [ebp-4],20h
19: }
20: int b = i;
004017E5 mov edx,dword ptr [ebp-4]
004017E8 mov dword ptr [ebp-0Ch],edx
21: cout << "i=" << b << endl;
004017EB push offset @ILT+195(std::endl) (004010c8)
004017F0 mov eax,dword ptr [ebp-0Ch]
004017F3 push eax
004017F4 push offset string "i=" (0046c01c)
004017F9 push offset std::cout (00477a10)
004017FE call @ILT+640(std::operator<<) (00401285)
00401803 add esp,8
00401806 mov ecx,eax
b = i,赋值这句成了从i的地址去取值送往b了,
mov edx,dword ptr [ebp-4]
mov dword ptr [ebp-0Ch],edx
注意release版本是没有这两句的,所以现在b取值就是肯定正确的了,后面压参的时候,也把b地址指向的值压入了堆栈,呵呵,这也是之前说的为什么两个版本下运行结果不同的原因。
把这个程序稍加修改,开始我们下面的函数调用汇编浅析:
#include <iostream>
using namespace std;
int ChangNum(int, int);
int main(int argc, char* argv[])
{
int i=10;
int a = i;
cout << "i=" << a << endl;
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm
{
mov dword ptr [ebp-4], 20h
}
int b = i;
cout << "i=" << b << endl;
ChangNum(50, 100);
return 0;
}
int ChangNum(int nParam, int nW)
{
int i = 10;
int a = i;
cout << "i=" << a << endl;
__asm
{
mov dword ptr [ebp - 4], 20h
mov dword ptr [ebp + 12], 0h
}
int b = i;
cout << "i=" << b << endl;
return 0;
}
主要看看函数调用那段的汇编代码:
1、函数调用点汇编代码:
23: ChangNum(50, 100);
00401824 push 64h
00401826 push 32h
00401828 call @ILT+590(ChangNum) (00401253)
0040182D add esp,8
分别是两个参数入栈,call这句有两个作用,下一行地址入栈,同时进行函数调用,最后一句是恢复栈空间,两个整型参数每个四字节,所以esp堆栈指针要加上8字节。
2、函数体中的汇编代码:
004018C0 push ebp
004018C1 mov ebp,esp
004018C3 sub esp,4Ch
004018C6 push ebx
004018C7 push esi
004018C8 push edi
004018C9 lea edi,[ebp-4Ch]
004018CC mov ecx,13h
004018D1 mov eax,0CCCCCCCCh
004018D6 rep stos dword ptr [edi]
30: int i = 10;
004018D8 mov dword ptr [ebp-4],0Ah
31: int a = i;
004018DF mov eax,dword ptr [ebp-4]
004018E2 mov dword ptr [ebp-8],eax
32: cout << "i=" << a << endl;
004018E5 push offset @ILT+195(std::endl) (004010c8)
004018EA mov ecx,dword ptr [ebp-8]
004018ED push ecx
004018EE push offset string "i=" (0046c01c)
004018F3 push offset std::cout (00477a10)
004018F8 call @ILT+645(std::operator<<) (0040128a)
004018FD add esp,8
00401900 mov ecx,eax
00401902 call @ILT+250(std::basic_ostream<char,std::char_traits<char> >::operator<<) (004010ff)
00401907 mov ecx,eax
00401909 call @ILT+475(std::basic_ostream<char,std::char_traits<char> >::operator<<) (004011e0)
33:
34: __asm
35: {
36: mov dword ptr [ebp - 4], 20h
0040190E mov dword ptr [ebp-4],20h
37: mov dword ptr [ebp + 12], 0h
00401915 mov dword ptr [ebp+0Ch],0
38: }
39: int b = i;
0040191C mov edx,dword ptr [ebp-4]
0040191F mov dword ptr [ebp-0Ch],edx
40: cout << "i=" << b << endl;
00401922 push offset @ILT+195(std::endl) (004010c8)
00401927 mov eax,dword ptr [ebp-0Ch]
0040192A push eax
0040192B push offset string "i=" (0046c01c)
00401930 push offset std::cout (00477a10)
00401935 call @ILT+645(std::operator<<) (0040128a)
0040193A add esp,8
0040193D mov ecx,eax
0040193F call @ILT+250(std::basic_ostream<char,std::char_traits<char> >::operator<<) (004010ff)
00401944 mov ecx,eax
00401946 call @ILT+475(std::basic_ostream<char,std::char_traits<char> >::operator<<) (004011e0)
41:
42: return 0;
0040194B xor eax,eax
43: }
0040194D pop edi
0040194E pop esi
0040194F pop ebx
00401950 add esp,4Ch
00401953 cmp ebp,esp
00401955 call __chkesp (00406df0)
0040195A mov esp,ebp
0040195C pop ebp
0040195D ret
ebp入栈,然后将esp的值传给ebp,现在ebp是指向此时的堆栈了,注意后面除了函数返回前,ebp的值一直是固定的,通过这种机制来访问参数和局部变量,esp减少了一个比较大的值,留给局部变量使用的,然后是通用寄存器入栈,接着就是实际的工作的代码了,这里就不说了,到那个return后面再看,是通用寄存器出栈,esp恢复,ebp出栈,ret回到函数调用的下一条指令。
【End】