函数调用的汇编码分析

函数调用的汇编码分析

这几天学习汇编,分析了一下 c++ 中函数调用(cdecl 和 fastcall 方式)过程的汇编码,记录如下:

 

程序例子

struct tagTest 
{
 int n1;
 long n2;
 DWORD n3;
};

 

long funtest1(tagTest p1,int p2,LPCTSTR lpszP3) 

{  // 普通函数
 p1.n1 = 3;
 LPCTSTR lpszxx = lpszP3;
 p1.n3 = p2;

 return 300;

}

 

long __stdcall funtest2(tagTest p1,int p2,LPCTSTR lpszP3)

{ // stdcall 函数
 p1.n1 = 3;
 LPCTSTR lpszxx = lpszP3;
 p1.n3 = p2;

 return 300;

}

1、普通调用(cdecl)。

    调用方 C++ 代码:

 long ixx = 0;
 tagTest tag1={34,6,87};
 ixx = funtest1(tag1,i2,"asdffffffdddddd");

    生成的汇编码:

 long ixx = 0;
0104171E  mov         dword ptr [ixx],0

 tagTest tag1={34,6,87};
01041738  mov         dword ptr [tag1],22h     ; 成员赋值
0104173F  mov         dword ptr [ebp-10h],6    ; 成员赋值
01041746  mov         dword ptr [ebp-0Ch],57h  ; 成员赋值
 ixx = funtest1(tag1,i2,"asdffffffdddddd");
0104174D  push        offset CAnonymousAsmTestApp::`vftable'+0F4h (11E60B0h) ; 入栈参数

                          ; "asdffffffdddddd" 的地址。这里显示似乎有问题(实际地址是对的)
01041752  mov         edx,dword ptr [i2] 
01041755  push        edx                 ; 入栈参数 i2
01041756  sub         esp,0Ch             ; 在栈中分配参数 tag1 的空间
01041759  mov         eax,esp
0104175B  mov         ecx,dword ptr [tag1]
0104175E  mov         dword ptr [eax],ecx  ; 入栈 tag1.n1
01041760  mov         edx,dword ptr [ebp-10h]
01041763  mov         dword ptr [eax+4],edx  ; 入栈 tag1.n2
01041766  mov         ecx,dword ptr [ebp-0Ch]
01041769  mov         dword ptr [eax+8],ecx  ; 入栈 tag1.n3
0104176C  call        funtest1 (1041680h)   ; 调用函数。

                       ;注意:这里同时将返回地址(下条指令的地址) 也入栈(这里是4字节);

                       ;   所以,函数中取得参数时,需要从当前 ESP 中加上 4 字节!
01041771  add         esp,14h              ; 由调用者清参数栈
01041774  mov         dword ptr [ixx],eax  ; 获取返回值

 

注意:已经关闭了编译器的某些优化选项,使汇编码更易读!

    调用方代码总结:

  • 参数从右向左依次入栈。
  • 栈指针(ESP)从底部向顶部依次减小。也就是说,栈顶地址小;栈底地址大。
  • 如果参数是结构(struct),则直接移动栈顶指针,预留出结构的大小;然后用 mov 指令逐步将成员拷贝到预留出来的栈空间!
  • call 指令调用目标地址
  • 调用方清栈
  • 获得返回值

    函数中生成的汇编码

long funtest1(tagTest p1,int p2,LPCTSTR lpszP3)
{

 

00351680  push        ebp    ; 基址指针入栈
00351681  mov         ebp,esp  ; 将函数入口点的栈指针作为当前基址指针。

                               ;顾名思义,基址指针就是进入函数时的栈顶指针!
00351683  push        ecx     ; 在栈中分配局部变量空间。

                              ;ecx 中的值无关紧要,只是预留一个 4 字节空间而已
 p1.n1 = 3;
00351684  mov         dword ptr [p1],3  ; 赋值
 LPCTSTR lpszxx = lpszP3;
0035168B  mov         eax,dword ptr [lpszP3] ; [lpszP3] 地址应该就是 ebp - 4
0035168E  mov         dword ptr [lpszxx],eax ; 赋值(通过 eax)
 p1.n3 = p2;
00351691  mov         ecx,dword ptr [p2]
00351694  mov         dword ptr [ebp+10h],ecx ; 赋值(通过 ecx)
 return 300;
00351697  mov         eax,12Ch    ; 返回值放在 eax
}
0035169C  mov         esp,ebp  ; 恢复栈:清除局部变量
0035169E  pop         ebp      ; 恢复基址指针
0035169F  ret                  ; 返回(从栈中弹出返回地址,然后 jmp )

 

 

2、_stdcall调用。

    调用方 C++ 代码:

 int i2=3;
 long ixx = 0;

 tagTest tag1={34,6,87};

 ixx = funtest2(tag1,i2,"asdffffffdddddd");

    生成的汇编码:

 int i2=3;
010A1759  mov         dword ptr [i2],3
 long ixx = 0;
010A1760  mov         dword ptr [ixx],0

 tagTest tag1={34,6,87};
010A1767  mov         dword ptr [tag1],22h
010A176E  mov         dword ptr [ebp-0Ch],6
010A1775  mov         dword ptr [ebp-8],57h

 ixx = funtest2(tag1,i2,"asdffffffdddddd");
010A177C  push        offset CAnonymousAsmTestApp::`vftable'+104h (12460C0h) ; 入栈参数

                          ; "asdffffffdddddd" 的地址。这里显示似乎有问题(实际地址是对的)

                          ; 和 cdecl 一样,参数从右边开始入栈


010A1781  mov         eax,dword ptr [i2]
010A1784  push        eax 
010A1785  sub         esp,0Ch
010A1788  mov         ecx,esp
010A178A  mov         edx,dword ptr [tag1]
010A178D  mov         dword ptr [ecx],edx
010A178F  mov         eax,dword ptr [ebp-0Ch]
010A1792  mov         dword ptr [ecx+4],eax
010A1795  mov         edx,dword ptr [ebp-8]
010A1798  mov         dword ptr [ecx+8],edx
010A179B  call        funtest2 (10A16B0h)
010A17A0  mov         dword ptr [ixx],eax  ; 取得返回值。

                                           ; 注意:这里没有清栈的代码,由函数自己清栈!

 

注意:已经关闭了编译器的某些优化选项,使汇编码更易读!

    调用方代码总结:

  • 参数从右向左依次入栈。
  • 栈指针(ESP)从底部向顶部依次减小。也就是说,栈顶地址小;栈底地址大。
  • 如果参数是结构(struct),则直接移动栈顶指针,预留出结构的大小;然后用 mov 指令逐步将成员拷贝到预留出来的栈空间!
  • call 指令调用目标地址
  • 返回时,由函数自己清栈(通过 ret 指令)
  • 获得返回值

    函数中生成的汇编码

long __stdcall funtest2(tagTest p1,int p2,LPCTSTR lpszP3)
{
010A16B0  push        ebp 
010A16B1  mov         ebp,esp
010A16B3  push        ecx  
 p1.n1 = 3;
010A16BD  mov         dword ptr [p1],3
 LPCTSTR lpszxx = lpszP3;
010A16C4  mov         eax,dword ptr [lpszP3]
010A16C7  mov         dword ptr [lpszxx],eax
 p1.n3 = p2;
010A16CA  mov         ecx,dword ptr [p2]
010A16CD  mov         dword ptr [ebp+10h],ecx

 return 300;
010A16D0  mov         eax,12Ch
}
010A16D5  mov         esp,ebp
010A16D7  pop         ebp 
010A16D8  ret         14h    ; 返回并清栈。

                            ;(从栈中弹出返回地址,返回;然后ESP增加14H,14H为参数栈的字节数)

你可能感兴趣的:(函数调用的汇编码分析)