大三暑假莫邪以内联汇编_4

大三暑假莫邪之内联汇编__4

      1-3篇,我大概的介绍了__cdecl __stdcall  堆栈与 函数 的关系与处理过程。发现现在****上,写C++文章的真少。今天的主题,我想带大家揭开__thiscall 调用约定以及类的 this指针的面貌。

     C++是一个庞大的东西,今天我们主要来看下 this与成员变量的关系(成员函数除外),C++的一个概貌。今天的演示程序如下:

 

#include<iostream>

class CExp
{
public:
	explicit CExp(size_t _value , size_t _value2 = 0);
			~CExp();

public:
	CExp(const CExp& _copy);
	CExp& operator = (const CExp& _copy);

public:
	size_t GetExp()const;

private:
	size_t m_value;
	size_t m_value2;
};

CExp::CExp(size_t _value, size_t _value2):m_value(_value)
                         ,m_value2(_value2)
{

}

CExp::~CExp()
{

}

CExp::CExp(const CExp& _copy)
{
	m_value = _copy.m_value;
}

CExp& CExp::operator = (const CExp& _copy)
{
	if(this != &_copy)
	{
		m_value = _copy.m_value;
	}

	return *this;
}

inline size_t CExp::GetExp()const
{
	return m_value2;
}

int main()
{
	try
	{
		CExp Gamer(99,1);//设计两个参数,主要是看入栈顺序
		size_t value = Gamer.GetExp();
		std::cout<<value<<std::endl;
		std::cout<<&Gamer<<std::endl;
		std::cout<<static_cast<int>(*(reinterpret_cast<unsigned char*>(&Gamer)))<<std::endl;
		//C风格为(int)(*((unsigned char*)&Gamer))
	}
	catch(...)
	{
		std::cout<<"Unknown Error"<<std::endl;
	}
	return 0;	
}


在 这里,我们的编译器是古老的 VC++6.0,写文章前我也搜过网上关于thiscall的文档,其中发现存放this指针的寄存器也是根据不同编译器的情况而不同。我们来看看VC上this的情况。 老规矩 我们启动反汇编过程。(假如,你不知道操作,请看内联__2)

 

图一

大三暑假莫邪以内联汇编_4

 

我们发现 prolog的工作 比较我们之前的文章,多出来点有趣的东西 比如__ehhandler&_main

这是什么呢?通过我自己查找相关资料,发现 这和 windows 上SEH 的 异常处理有关(我们知道C++是有它的异常机制的,)想深入了解,请大家搜索

Windows SEH 技术, 看来我们的prolog工作 和前几天 分析差不多 我们来看看 函数调用部分

 

图二

大三暑假莫邪以内联汇编_4

 

我们发现 这里的try 竟然会让main的第一个local对象初始化为0

之后是构造函数的调用,我们发现参数是从右到左入栈,那this指针呢?

我们看到 lea ecx,[Gamer], 看了this指针并没有push,而是把Gamer对象的地址存在了ecx里 (好家伙,没反汇编钱,我的第一篇文章就以后是push了,哎这里改过)

调试技巧提示:当运行到断点处的时候,并没有执行断点处的指令,如果此时去查看寄存器里的内容,这可不是一个明智的作为。

我们发现Call以后 右把ebp-4 初始化为1个字节 (注意:前面是dword 而这里是byte)

让我们看下构造函数是怎么处理的。

 

图三

大三暑假莫邪以内联汇编_4

 

看来thiscall 和 我们之前调试的__cdecl __stdcall 差不多

我们看到 mov dword ptr[ebp-4],ecx  pop后的ecx 就是我们原先存的 对象的地址, 这时候 我们发现 构造函数把它 当做了 第一个local对象, 之后看看

他把 第一个参数(非进栈顺序) [ebp+8]放到了 ecx处(等执行完构造函数,我们用memory去看看ecx的内存内容),之后的第二个参数[ebp+0ch]也放进了ecx+4处 

之后是  epilog 工作, 我们发现 他并没有 add esp 恢复栈平横

执行完构造函数,我们来看看 ecx的内存存储情况

 

图四

 

大三暑假莫邪以内联汇编_4

 

ecx 就是 Gamer的地址, 我们程序里最后会输出 Gamer的地址来比对下, 我们发现ecx的双字部分 存的 是0x00000063 正是我们的十进制99, 而ecx+4处便是我们的存的1,看来 我们类的this指针就是对象的地址,而它的成员变量,就是从this的地址开始存的。(这里我们先不分析虚函数表,其实图片上的红色信息表示寄存器的前后改动信息, 我们发现最后一行有个01 该怎么解释呢?:))

继续看看GetExp() 一个inline函数 是怎么做的

 

图五

 

大三暑假莫邪以内联汇编_4

 

既然我们知道了 this指针存在ecx 而 ecx 又作为成员函数的第一个local对象处理, 下面的问题就迎刃而解了。[ebp-4]此时是this地址 mov给了 eax,而eax+4 实际上就是this+4 结果把 值放到eax 然后返回。 epilog 依旧没有恢复堆栈平衡

 

不知道为什么?看了下析构也没有?在这里,请教下懂的人,为什么会这样呢?我很不解。

 

最后我写了一个 验证this指针 及存储成员数据的 代码

C++的风格 可能对于C程序员来说看的吃力, 下面有C风格的注释

指针也就那点好处呵呵,喜欢操作内存的感脚儿

 

贴下运行结果吧~

大三暑假莫邪以内联汇编_4

怎么样?ecx没骗你吧?:)

2楼template_cplus昨天 17:33
我私下把try catch 去了 汇编的结果 和 上面的不同, 主要还是ecx 的处理不同。具体情况大家可以自己试下
1楼template_cplus昨天 17:00
我傻掉了, 一个结构体有什么好分析的。有删掉的冲动~