分析平台:VC
编译工具:vs2012
类原型:
class CAddressChain
{
public:
CAddressChain();
BYTE* m_pbFeatureCode;
DWORD m_dwBaseAddress;
DWORD m_dwOffsetAddress[20];
};
构造函数定义:
CAddressChain:: CAddressChain()
{
m_pbFeatureCode = 0;
m_dwBaseAddress = 0;
for (int i = 0; i < 20; i++)
{
m_dwOffsetAddress[i] = -1;
}
}
创建方式之栈中创建:
void Test()
{
CAddressChain acw;
acw.m_dwBaseAddress = 1;
}
环境分析之准备环境:
void Test()
{
}
push ebp /*保存外层栈帧;栈帧,当前环境的栈空间的底部;栈底,空间最小值;栈帧指示了栈空间的底部,栈顶指示了栈空间的最大值;如图(在帖子最底下)
空间的顶部(esp),空间的底部(ebp) */
mov ebp,esp /*确定本层环境的栈空间底部;esp当前指示的是外层栈空间的顶部,本层需要拥有自己的栈空间,以方便数据的保存(局部变量),esp指示了栈空间当前的位置,顾把esp传递给ebp,用ebp来锁定本层栈空间的底部,有了底部(ebp),顶部(esp),就能确定本层栈空间的大小 */
/*这里为异常处理指令,没有深入研究,不做分析*/
push ebx /*保存外层寄存器,寄存器就那么几个,不够用,外层可能都用到了,所以这里先保存起来*/
push esi /*同上*/
push edi /*同上*/
sub esp,1C4h /*为本层栈空间分配了1C4h的空间;*/
lea edi,[ebp+FFFFFE30h] /* ebp,栈底;FFFFFE30h,这是补码形式,转换过来是-1C4h;即取到栈空间的顶部位置 */
mov ecx,71h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
/*上面五条指令为本层栈空间分配空间,并初始化为CC,即int 3指令,如果不小心运行到这,即产生异常*/
至此,环境就安排好了;总结为:保护外层栈空间、寄存器;申请本层栈空间并初始化;
环境现状:
栈底 ebp
栈顶 esp
栈空间可用来申请的大小 ebp - (ebp – 1C4h)
本层栈空间总容量 ebp - esp
环境分析之退出环境:
之前有sub esp,1C4
这里有 add esp,1C4
之前有 push esi
这里有 pop esi
之前有 mov ebp,esp
这里又 mov esp,ebp
/*用到的寄存器,全部还原回去*/
对象创建:
CAddressChain acw;
push 1
lea ecx,[ebp+FFFFFF78h] /* lea ecx,[acw]; ebp栈底; FFFFFF78h 补码方式,转换为-88h;此处选取了栈空间ebp – 88h 的位置,栈总空间是ebp – 1C4h */
call 016D1B64 /*call CAddressChain::__autoclassinit; 初始化对象的栈;详细代码如下:
push ebp
mov ebp,esp
sub esp,0CCh
push ebx
push esi
push edi
push ecx
lea edi,[ebp+FFFFFF34h]
mov ecx,33h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
pop ecx ////////////////////标记A
mov dword ptr [ebp-8],ecx
mov eax,dword ptr [ebp-8]
mov dword ptr [eax],0 ////////////////////结束标记A
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 4
上面的代码跟除了对环境进行了处理外,从A标记处这里需要注意,这是这段代码的意义所在 ; 之前有句lea ecx,[ebp+FFFFFF78h],这是在自己栈空间内找了一处地址,标记A处的指令对该地址处进行了填充,填充值为0,为什么要做这样的操作呢?继续往下看*/
lea ecx,[ebp+FFFFFF78h] /*再次取这个位置*/
call 016C355F /*call CAddressChain::CAddressChain;这里开始调用了构造函数;详细代码如下:
push ebp
mov ebp,esp
sub esp,0D8h
push ebx
push esi
push edi
push ecx
lea edi,[ebp+FFFFFF28h]
mov ecx,36h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
pop ecx /*上面为环境的操作,这里的ecx是由外层获得(详情可在上面查找)*/
mov dword ptr [ebp-8],ecx /*将外层acw对象(ebp+FFFFF28h)所在栈空间的具体位置放入本层自己申请的栈空间内(ebp-8处),从逻辑上,构造函数内的所有操作,都且只跟自己的栈空间打交道*/
mov eax,dword ptr [ebp-8] /* 空间ebp-8是本层栈空间,里面放的是ecx,ecx是外层栈空间的ebp+FFFFFF78h 的位置*/
mov dword ptr [eax],0 /*给外层acw对象所在的栈空间赋值*/
/*上面两句指令,为对象成员赋值,代码m_pbFeatureCode = 0; */
mov eax,dword ptr [ebp-8]
mov dword ptr [eax+4],0
/*上面两条指令,源码m_dwBaseAddress = 0; */
/*下面部分是for循环,具体不做分析,唯一需要注意的下面已经注释;源码如下:
for (int i = 0; i < 20; i++)
{
m_dwOffsetAddress[i] = -1;
}
*/
016E9E76 mov dword ptr [ebp-14h],0 /*此处为i变量,需要注意这里是在构造函数自己栈空间内分配的*/
016E9E7D jmp 016E9E88
016E9E7F mov eax,dword ptr [ebp-14h]
016E9E82 add eax,1
016E9E85 mov dword ptr [ebp-14h],eax
016E9E88 cmp dword ptr [ebp-14h],14h
016E9E8C jge 016E9E9E
016E9E8E mov eax,dword ptr [ebp-14h]
016E9E91 mov ecx,dword ptr [ebp-8] /*这里再次用到了对象的栈空间*/
016E9E94 mov dword ptr [ecx+eax*4+8],0FFFFFFFFh /*+8需理解,前面已经为pbFeatureCode和dwBaseAddress各分配了4字节 */
016E9E9C jmp 016E9E7F
}
*/
mov eax,dword ptr [ebp-8] /*把对象所在栈空间的位置给了eax,这里可以说明,构造函数是带返回值的,默认就是this指针,即对象所在栈空间的位置*/
/*下面为环境还原*/
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
对象创建完成,这里又回到Test函数
mov dword ptr [ebp+FFFFFF7Ch],1 /*这里需要注意,对象的栈位置在ebp+FFFFFF78h,这里FFFFFF7Ch,即对象的第二个参数;源码为acw.m_dwBaseAddress = 1*/
OK,一个在栈中对象的对象就搞定了,下一次深入下堆上创建对象;
下面做一下总结:
之前写了1/3停电,头大了….忍住心情,调整战略,重新在Micr Word上完成
1对象名是一种 ebp+XX的人性化表示
2对象的成员变量会在定义对象的时候被规划好,用不用都在那里,编译器始终为你保留,修饰符是在语法层,引用私有成员,编译器不为你提供汇编指令,让你通不过..
3在成员方法内对对象属性进行的操作和在外部采用的都是相同的方式,操作的位置也是一样的,即完全相同..选择在成员方法和在外部,取决于面向对象设计模式
4构造函数默认有返回值,即对象的this指针
5成员方法默认都带有this参数,你用不用,它都会用ecx寄存器带入进去,并放在自己的栈空间ebp-8处
6成员方法都拥有自己的栈空间,方法内的局部变量,申请在自己维护的栈空间内,对属性的操作也是在自己栈通过引用[ebp-8]来完成
7在调用构造函数之前,调用了一个名为__autoclassinit的函数,进行的操作只是把对象的第一个参数设置成0;
8 this指针是对象在栈空间的位置
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!