前言
逆向水的一批,顶多反汇编看看有啥过程
正文
x64虽然快点,但是老的还是从x86开始
类的函数调用约定this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | #include<iostream>
  class Box{ 	int hp; public: 	int Add(int a, int b){ 		return hp + a + b; 	} };
  int main(){
  	Box b1; 	b1.Add(100, 200);
  	return 0; }
   | 
 
- 模式切换成release,选择x86
 
- 关闭优化
 
- 禁用安全检查
 
安全检查会增加不少的汇编代码,不利于常规分析。
然后随便打断点反汇编查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | int main(){ 003A1020 55                   push        ebp   003A1021 8B EC                mov         ebp,esp   003A1023 51                   push        ecx  
  	Box b1; 	b1.Add(100, 200); 003A1024 68 C8 00 00 00       push        0C8h   003A1029 6A 64                push        64h   003A102B 8D 4D FC             lea         ecx,[ebp-4]   003A102E E8 CD FF FF FF       call        003A1000  
  	return 0; 003A1033 33 C0                xor         eax,eax   } 003A1035 8B E5                mov         esp,ebp   003A1037 5D                   pop         ebp   003A1038 C3                   ret  
  | 
 
可以看到精简了很多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | class Box{ 	int hp; public: 	int Add(int a, int b){ 003A1002 EC                   in          al,dx   003A1003 51                   push        ecx   003A1004 89 4D FC             mov         dword ptr [ebp-4],ecx   		return hp + a + b; 003A1007 8B 45 FC             mov         eax,dword ptr [ebp-4]   003A100A 8B 00                mov         eax,dword ptr [eax]   003A100C 03 45 08             add         eax,dword ptr [ebp+8]   003A100F 03 45 0C             add         eax,dword ptr [ebp+0Ch]   	} 003A1012 8B E5                mov         esp,ebp   003A1014 5D                   pop         ebp   003A1015 C2 08 00             ret         8 
  | 
 
至于显示符号名这个可开可不开,都是便于理解的东西
1 2 3 4 5 6
   | 	Box b1; 	b1.Add(100, 200); 003A1024 68 C8 00 00 00       push        0C8h   003A1029 6A 64                push        64h   003A102B 8D 4D FC             lea         ecx,[ebp-4]   003A102E E8 CD FF FF FF       call        003A1000 
   | 
 
调用b1.Add的时候也是从右至左把参数压入栈中。
lea指令就是把地址传给寄存器,那么这里ebp-4自然就是实例化对象b1的内存地址。
call 003a1000 就是调用函数,这里也就调用了.Add函数。
直接将地址copy进来,跳转,自然就是Box::Add的位置了。
反汇编里,看起来成员函数的调用传参都和正常函数没差。
因为要看this所以把符号名打开了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | class Box{ 	int hp; public: 	int Add(int a, int b){ 003A1000 55                   push        ebp   003A1001 8B EC                mov         ebp,esp   003A1003 51                   push        ecx   003A1004 89 4D FC             mov         dword ptr [this],ecx   		return hp + a + b; 003A1007 8B 45 FC             mov         eax,dword ptr [this]   003A100A 8B 00                mov         eax,dword ptr [eax]   003A100C 03 45 08             add         eax,dword ptr [a]   003A100F 03 45 0C             add         eax,dword ptr [b]   	} 003A1012 8B E5                mov         esp,ebp   003A1014 5D                   pop         ebp   003A1015 C2 08 00             ret         8
  | 
 
函数入栈ebp跟随上来,然后esp跟ebp持平保持栈平衡
然后调用了一个ecx寄存器,又将ecx的值传给this指针,其实就是ebp-4 实例化 b1的地址,它的内存上也就一个hp。
然后将this指针的值传给eax寄存器,后面这个把eax的值又赋给eax其实有点迷惑操作。
可能先前是为了获取这个对象的地址,然后再取出hp的值,如果多个成员估计就能看出区别了。
按照这种道理,其实就是this->hp+a+b。先通过实例化对象,再去找它需要的成员。
后面的两个add就没啥问题,就是hp+a+b顺序执行。
不过按照这个反汇编,倒是看到,在类中调用自身成员其实都是通过this->获取
即使代码上不加,反汇编的时候还是遵循这个过程。
所以之前加上倒是便于理解了。
_thiscall
_thiscall是c++类中的成员函数访问时定义的函数调用约定
- 寄存器ecx用来存放类的指针
 
- 参数依旧是从右往左入栈
 
- 堆栈平衡由被调用者负责恢复
 
类中的非静态成员函数都可以调用this指针,this指针本身就是实例化对象的地址通过ecx传给成员函数。
所以当类中成员函数访问其成员变量的时候,都是指针+偏移的形式访问。
而且不管你是否明确使用this->,编译器默认都是按照这个逻辑。
不过如果这个ecx被人逮到了,那里面的东西其实都可以通过便宜访问到了。
静态成员函数没有this指针
_cdecl
类的静态成员函数调用约定_cdecl
- 参数从右往左入栈
 
- 由调用者恢复堆栈平衡 – 就是这个函数结束的时候会add esp,8 当然数值不固定由内存决定
 
前面也说了,静态成员函数本质上就是一个普通的函数,他不会随着对象实例化而传递。
因此他也不会有this指针这种东西,没有了this指针也就注定它不能访问类中的成员变量
而静态成员变量能访问是因为静态变量本质上也是一个全局的变量,内存地址固定,与类实例化后互不影响。
所以静态成员可以通过作用域方式访问到:类::静态成员变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | #include<iostream>
  class Box{ 	int hp; public: 	int Add(int a, int b){ 		return hp + a + b; 	}
  	static int test(int a,int b){ 		return a + b; 	} };
  int main(){
  	Box b1; 	
  	b1.test(100,200);
  	return 0; }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | int main(){ 00EA1010 55                   push        ebp   00EA1011 8B EC                mov         ebp,esp  
  	Box b1; 	
  	b1.test(100,200); 00EA1013 68 C8 00 00 00       push        0C8h   00EA1018 6A 64                push        64h   00EA101A E8 E1 FF FF FF       call        Box::test (0EA1000h)   00EA101F 83 C4 08             add         esp,8  
  	return 0; 00EA1022 33 C0                xor         eax,eax   } 00EA1024 5D                   pop         ebp   00EA1025 C3                   ret 
  | 
 
参数入栈没啥毛病,都是从右往左,call的时候也是正常跳转函数
然后函数结束完后主动恢复栈平衡add esp,8
但是会注意到明明是b1调用的,但是却没有出现ecx。
1 2 3 4 5 6 7 8
   | 	static int test(int a,int b){ 00EA1002 EC                   in          al,dx   		return a + b; 00EA1003 8B 45 08             mov         eax,dword ptr [a]   00EA1006 03 45 0C             add         eax,dword ptr [b]   	} 00EA1009 5D                   pop         ebp   00EA100A C3                   ret
  | 
 
至于这个函数本身是没调用类的成员,也没有出现ecx。
所以说当你用b1调用静态成员函数和直接作用域调用是一样的。它没有this指针。
所以静态成员函数不能访问类的非静态成员变量
类是否一定有构造函数
刚学类的时候就说了,类默认都会有一个构造函数和析构函数,如果没定义,那就是自动生成一个空的构造函数和析构函数。
至于在编译器编译的时候,为什么没看到。
1 2 3 4 5 6 7 8
   | 	Box b1; 	
  	b1.test(100,200); 00EA1013 68 C8 00 00 00       push        0C8h   00EA1018 6A 64                push        64h   00EA101A E8 E1 FF FF FF       call        Box::test (0EA1000h)   00EA101F 83 C4 08             add         esp,8
   | 
 
就拿这个来说,实例化对象b1,没有自动调用构造,首先是因为没定义,其次就是本身就是空的构造。那么有没有意义都不大,就被编译器主动删掉了。
结语
还是有点糙,就是简单的分析