C++反汇编-结构体和类
2013-12-03 11:54
211 查看
学无止尽,积土成山,积水成渊-《C++反汇编与逆向分析技术揭秘》 读书笔记
对象的内存布局
一般计算公式:
对象内存大小 = sizeof(数据成员1)+ sizeof(数据成员2) +. .. + sizeof(数据成员n)
若类中没有继承和虚函数的定义,还有三种特殊情况考虑:空类、内存对齐、静态成员函数。
空类:空类的长度为1字节,如果不占用字节的话,this指针会悬空。考虑到类可以仅有成员函数,没有数据成员。
内存对齐:在VC中,类和结构体中的数据成员是根据在类或结构中出现的顺序来依次申请空间的,由于内存对齐原因,可能不会连续的排列,数据成员之间可能有间隙。
静态变量:与静态全局变量类似,存在的位置和全局变量一致,只是编译器增加了作用域的检查,作用域之外不可见。
访问对象中的数据成员时,一般采用寄存器间接相对寻址,表达式esp±n±offset,或ebp±n±offset(其中esp±n或ebp±n是对象的首地址,offset 为数据成员相对对象首地址的偏移)。由于n和offset在编译阶段时属于常量,采用编译器优化时,表达式可能简化为 ebp±n 或 esp±n。
this指针
this指针是保存所属对象的首地址。在调用成员函数时,遵循默认的thiscall约定,利用寄存器ECX保存对象的首地址(即this指针),以寄存器传参的方式传递到成员函数中,即是this指针的约定。成员函数中访问成员数据既是通过this指针间接访问的。
thiscall并不属于关键字,是C++成员函数特有的调用方式。
thiscall的参数压栈顺序也是从右至左
thiscall的栈平衡方式与_stdcall相同,由被调用方平衡。
并不是所有的this指针的传递都是通过寄存器ECX,可以强制改用其他调用方式(_如stdcall)
class CTest
{
public:
void __stdcall SetNumber(int nNumber){
m_nInt = nNumber;
}
int nNumber;
}
使用thiscall调用方式的成员函数的要点分析
函数调用:
lea ecx, [mem] ;取对象地址
call FUN_ADDRESS ;调用成员函数
函数内部:
mov XXX, ecx ;发现函数内使用ecx中的数据,证明确是通过ecx来传递参数
mov [reg + 1], XXX
静态数据成员
静态数据成员和静态变量的原理相同,因此静态数据成员的初值会被写入编译链接后的执行文件中。当程序被加载时,操作系统将执行文件中的数据读到对应的内存单元中,静态数据成员便已经存在,而这时类并没有实例对象。静态数据成员不属于某一个对象,与对象之间是多对一的关系。静态数据成员仅仅和类相关,和对象无关。
所以在计算某个类的对象所占用内存的大小,静态数据成员是不计算在内的。
区别:
静态数据成员是常量地址,而普通数据成员一般存储在栈空间。
静态成员通过立即数间接寻址访问,而普通数据成员一般通过寄存器相对间接寻址访问。
静态成员访问时不需要this指针,而普通数据成员访问时需要使用this指针。
对象作为函数参数
对象作为函数参数,编译器会把对象视为由几个基本类型的数据的组合,和多个参数函数传参类似。类对象中的数据成员的传参顺序为:最先定义的数据成员最后压栈,最后定义的数据成员最先压栈。当类有构造函数和析构函数,过程会更复杂一些。由于对象在向函数传递过程中,由于复制了对象,等同于又定义了一个对象,会调用复制构造函数。在函数退出时,复制的对象作为函数内部的局部变量被销毁,会调用析构函数。
对象作为返回值
对象作为函数返回值,与基本类型不同。基本数据类型(双精度浮点数以及非标准的_int64类型除外)作为返回值时,通过寄存器EAX传递。对象作为返回值时,首先在调用函数中申请返回对象使用的栈空间,然后将返回对象的首地址作为参数,通过寄存器EAX传递给被调用函数。在退出被调用函数时,将返回对象中的数据复制到调用函数开辟的返回对象的栈空间,把返回对象的首地址通过寄存器EAX返回。返回的对象是临时存在的,也就是C++临时对象,作用域仅仅限于单条语句。
1 class CReturn{
2 public:
3 int m_nNumber;
4 in m_nArry[10];
5 };
6 CReturn GetCReturn()
7 {
8 CReturn RetObj;
9 RetObj.m_nNumber = 0;
for(int i=0; i< 10; i++)
{
RetObj.m_nArry[i] = i + 1;
}
return RetObj;
}
void main(int argc, char *argv[])
{
CReturn objA;
objA = GetCReturn();
printf("%d %d %d", objA.m_nNumber, objA.m_nArry[0], objA.m_nArry[9]);
}
对象的内存布局
一般计算公式:
对象内存大小 = sizeof(数据成员1)+ sizeof(数据成员2) +. .. + sizeof(数据成员n)
若类中没有继承和虚函数的定义,还有三种特殊情况考虑:空类、内存对齐、静态成员函数。
空类:空类的长度为1字节,如果不占用字节的话,this指针会悬空。考虑到类可以仅有成员函数,没有数据成员。
内存对齐:在VC中,类和结构体中的数据成员是根据在类或结构中出现的顺序来依次申请空间的,由于内存对齐原因,可能不会连续的排列,数据成员之间可能有间隙。
静态变量:与静态全局变量类似,存在的位置和全局变量一致,只是编译器增加了作用域的检查,作用域之外不可见。
访问对象中的数据成员时,一般采用寄存器间接相对寻址,表达式esp±n±offset,或ebp±n±offset(其中esp±n或ebp±n是对象的首地址,offset 为数据成员相对对象首地址的偏移)。由于n和offset在编译阶段时属于常量,采用编译器优化时,表达式可能简化为 ebp±n 或 esp±n。
this指针
this指针是保存所属对象的首地址。在调用成员函数时,遵循默认的thiscall约定,利用寄存器ECX保存对象的首地址(即this指针),以寄存器传参的方式传递到成员函数中,即是this指针的约定。成员函数中访问成员数据既是通过this指针间接访问的。
thiscall并不属于关键字,是C++成员函数特有的调用方式。
thiscall的参数压栈顺序也是从右至左
thiscall的栈平衡方式与_stdcall相同,由被调用方平衡。
并不是所有的this指针的传递都是通过寄存器ECX,可以强制改用其他调用方式(_如stdcall)
class CTest
{
public:
void __stdcall SetNumber(int nNumber){
m_nInt = nNumber;
}
int nNumber;
}
使用thiscall调用方式的成员函数的要点分析
函数调用:
lea ecx, [mem] ;取对象地址
call FUN_ADDRESS ;调用成员函数
函数内部:
mov XXX, ecx ;发现函数内使用ecx中的数据,证明确是通过ecx来传递参数
mov [reg + 1], XXX
静态数据成员
静态数据成员和静态变量的原理相同,因此静态数据成员的初值会被写入编译链接后的执行文件中。当程序被加载时,操作系统将执行文件中的数据读到对应的内存单元中,静态数据成员便已经存在,而这时类并没有实例对象。静态数据成员不属于某一个对象,与对象之间是多对一的关系。静态数据成员仅仅和类相关,和对象无关。
所以在计算某个类的对象所占用内存的大小,静态数据成员是不计算在内的。
区别:
静态数据成员是常量地址,而普通数据成员一般存储在栈空间。
静态成员通过立即数间接寻址访问,而普通数据成员一般通过寄存器相对间接寻址访问。
静态成员访问时不需要this指针,而普通数据成员访问时需要使用this指针。
对象作为函数参数
对象作为函数参数,编译器会把对象视为由几个基本类型的数据的组合,和多个参数函数传参类似。类对象中的数据成员的传参顺序为:最先定义的数据成员最后压栈,最后定义的数据成员最先压栈。当类有构造函数和析构函数,过程会更复杂一些。由于对象在向函数传递过程中,由于复制了对象,等同于又定义了一个对象,会调用复制构造函数。在函数退出时,复制的对象作为函数内部的局部变量被销毁,会调用析构函数。
对象作为返回值
对象作为函数返回值,与基本类型不同。基本数据类型(双精度浮点数以及非标准的_int64类型除外)作为返回值时,通过寄存器EAX传递。对象作为返回值时,首先在调用函数中申请返回对象使用的栈空间,然后将返回对象的首地址作为参数,通过寄存器EAX传递给被调用函数。在退出被调用函数时,将返回对象中的数据复制到调用函数开辟的返回对象的栈空间,把返回对象的首地址通过寄存器EAX返回。返回的对象是临时存在的,也就是C++临时对象,作用域仅仅限于单条语句。
1 class CReturn{
2 public:
3 int m_nNumber;
4 in m_nArry[10];
5 };
6 CReturn GetCReturn()
7 {
8 CReturn RetObj;
9 RetObj.m_nNumber = 0;
for(int i=0; i< 10; i++)
{
RetObj.m_nArry[i] = i + 1;
}
return RetObj;
}
void main(int argc, char *argv[])
{
CReturn objA;
objA = GetCReturn();
printf("%d %d %d", objA.m_nNumber, objA.m_nArry[0], objA.m_nArry[9]);
}
相关文章推荐
- 基于arm的C++反汇编 结构体和类
- C++反汇编->类,结构体,命名空间分析
- C++中结构体和类的区别
- C/C++语言宏中使用参数做结构体成员
- C++中结构体与类的区别(struct与class的区别)
- c&c++反汇编与逆向分析学习笔记(4)--启动函数和用户入口
- C\C++中结构体变量与结构体指针内存分配问题
- c&c++反汇编与逆向分析学习笔记(6)--减法表达式
- C++第2周项目1——有序的结构体数组
- C++反汇编代码分析--函数调用
- C/C++中的结构体对齐问题(内存对齐)
- c++函数的返回值存在哪?反汇编代码分析
- C++反汇编代码分析--函数调用
- c&c++反汇编与逆向分析学习笔记(10)--关系及逻辑运算
- C++反汇编十(数组)
- c/c++中typedef详解(此文对typedef用于结构体的定义说明得很清楚到位)
- C++ 结构体数组的使用
- C++成长历程 之 结构体
- C++学习总结(五)——结构体(struct),联合体(union)
- C++ 结构体嵌套、空结构体和空类