c++反汇编(x86)
本帖最后由 vstk 于 2023-2-12 17:04 编辑## 1.类型大小
### 1. 基本数据类型
```cpp
printf("%d\n", sizeof(char)); //1
printf("%d\n", sizeof(short int)); //2
printf("%d\n", sizeof(int)); //4
printf("%d\n", sizeof(long int)); //4
printf("%d\n", sizeof(__int64)); //8
printf("%d\n", sizeof(float)); //4
printf("%d\n", sizeof(double)); //8
```
### 2. 数组类型
```cpp
char arr1 = { 0 };
short arr2 = { 0 };
int arr3 = { 0 };
printf("%d\n", sizeof(arr1)); //10
printf("%d\n", sizeof(arr2)); //20
printf("%d\n", sizeof(arr3)); //40
printf("%d\n", sizeof(arr1));//sizeof(char) 1
printf("%d\n", sizeof(arr2));//sizeof(short) 2
printf("%d\n", sizeof(arr3));//sizeof(int) 4
```
### 3. 数据对齐
1. 为什么要有数据对齐?
本质:效率还是空间,二选一的结果;
`#pragma pack`的基本用法为:
```md
#pragma pack( n )
结构体...
#pragma pack()
```
对齐参数:n为字节对齐数,其取值为1、2、4、8,默认是8。如果这个值比结构体成员的sizeof值小,那么该成员的偏移量应该以此值为准,
即是说,结构体成员的偏移量应该取二者的最小值.
2. 结构对齐大小
如果这个值比结构体成员的sizeof值小,那么该成员的偏移量应该以此值为准,即是说,结构体成员的偏移量应该取二者的最小值;
对齐原则:
* 原则一:数据成员对齐规则:结构的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储).
* 原则二:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
* 原则三:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。
> (struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储.)
原则四:对齐参数如果比结构体成员的sizeof值小,该成员的偏移量应该以此值为准.也就是说,结构体成员的偏移量应该取二者的最小值
3. 建议:
按照数据类型由小到大的顺序进行书写
4. 实例
```cpp
//16
struct S1
{
char c; //8
double i; //8
};
//40
struct S2
{
char c1;//8
S1 s; //16 => char c; double i;
char c2;//8
double c3;//8
};
//32
struct S3
{
char c1;//8
S1 s; //16 => char c; double i;
char c2;// => sizeof(c2) + sizeof(c3) = 2 + 6 = 8
char c3;
};
//12
struct S4
{
int c1; //sizeof(int) + sizeof(char) * 5 =》 4 + 4 + 1 + 3(填充) = 12
char c2;
};
//16
struct S5
{
int c1; //sizeof(int) + sizeof(char) * 10 =》 4 + (4 * 2 + 2 + 2(填充)) = 16
char c2;
};
int main() {
int a = sizeof(S1);
int b = sizeof(S2);
int c = sizeof(S3);
int d = sizeof(S4);
int e = sizeof(S5);
return 0;
}
```
## 2. 面向对象
### 2.1 对象
1. 绕过编译器,修改对象的私有成员:
```cpp
#include <iostream>
class Test {
public:
Test(int x, int y) {
this->x = x;
this->y = y;
}
void pri() {
std::cout << "x : " << x << "\ny : " << y << std::endl;;
}
private:
int x;
int y;
};
int main() {
Test t(1, 2);
int* p = (int*)&t;
int n = *p;
int m = *(p + 1);
*p = 3;
*(p + 1) = 4;
std::cout << "n : " << n << "\nm : " << m << std::endl;;
t.pri();
system("pause");
return 0;
}
```
2. 绕过编译器,调用对象的私有成员函数:
```cpp
#include <iostream>
struct Base
{
private:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n");
}
};
int main() {
Base b;
Base* ptr = &b;
void(*f1)(void) = (void(*)(void))(*((int*)(*(int*)&b + 0)));
void(*f2)(void) = (void(*)(void))(*((int*)(*(int*)&b + 4)));
void(*f3)(void) = (void(*)(void))(*((int*)(*(int*)&b + 8)));
/*
0. 函数指针:(void(*)(void))
1. 指向虚表的地址:(*(int *)&b)
2. 虚函数的地址(或第一个虚函数的地址):(*((int*)(*(int *)&b + 0)))
*/
f1();
f2();
f3();
system("pause");
return 0;
}
```
总结:
* private修饰的成员与普通的成员没有区别 只是编译器会检测。
* private修饰的成员只有自己的其他成员才能访问;
3. struct 与 class
* 编译器默认class中的成员为private,而struct中的成员为public
* class 默认private继承,struct 默认public继承
4. 指针与引用
```cpp
#include <iostream>
int main() {
int a = 12;
int* ptr = &a;
int& ref = a;
system("pause");
return 0;
}
```
```cpp
int a = 12;
008F1728mov dword ptr ,0Ch
int* ptr = &a;
008F172Flea eax,
008F1732mov dword ptr ,eax
int& ref = a;
008F1735lea eax,
008F1738mov dword ptr ,eax
```
总结:指针与引用在汇编层次,没有任何区别;
### 2.2 虚表
继承
* 父类中的私有成员是会被继承的
* 只是编译器不允许直接进行访问
1. 当类中有虚函数时,会多一个属性,4个字节(单一继承)
2. 多出的属性是一个地址,指向一张表,里面存储了所有虚函数的地址
> 在x86中,指针类型大小都为4个字节
#### 2.2.1 非继承虚函数表
```cpp
//普通继承,非覆盖
struct Base
{
public:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n");
}
};
int main() {
Base base;
Base* ptr = &base;
//断点到此处
ptr->Function_1();
ptr->Function_2();
ptr->Function_3();
system("pause");
return 0;
}
```
汇编如下:
```cpp
Base base;
009D51D8lea ecx,
009D51DBcall 009D103C
Base* ptr = &base;
009D51E0lea eax, ;获得base的地址
009D51E3mov dword ptr ,eax
//断点到此处
ptr->Function_1();
009D51E6mov eax,dword ptr ;&base
009D51E9mov edx,dword ptr ;base的虚表地址
009D51EBmov esi,esp
009D51EDmov ecx,dword ptr
009D51F0mov eax,dword ptr ;第一个函数的地址
009D51F2call eax
009D51F4cmp esi,esp
009D51F6call 009D1140
ptr->Function_2();
009D51FBmov eax,dword ptr
009D51FEmov edx,dword ptr
009D5200mov esi,esp
009D5202mov ecx,dword ptr
009D5205mov eax,dword ptr ;第二个函数的地址,偏移4个字节
009D5208call eax
009D520Acmp esi,esp
009D520Ccall 009D1140
ptr->Function_3();
009D5211mov eax,dword ptr
009D5214mov edx,dword ptr
009D5216mov esi,esp
009D5218mov ecx,dword ptr
009D521Bmov eax,dword ptr ;第三个函数的地址,偏移8个字节
009D521Ecall eax
009D5220cmp esi,esp
009D5222call 009D1140
```
验证:
```cpp
void TestMethod()
{
//查看 Sub 的虚函数表
Base base;
Base* ptr = &base;//包含一个虚表指针
printf("base 的地址为:%x\n", &base);
//对象的前四个字节就是虚函数表
printf("base 的虚函数表地址为:%x\n", *(int*)&base);
for (int i = 0; i < 3; i++) {
printf("虚函数%d的地址:%x\n", i + 1, *((int*)(*(int*)&base) + i));
}
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
pFunction pFn;
for (int i = 0; i < 3; i++)
{
int temp = *((int*)(*(int*)&base) + i);
pFn = (pFunction)temp;
pFn();
}
}
```
#### 2.2.2 继承非覆盖虚函数表
父类的虚函数表和子类不相同,**每个类都独有自己的一份虚函数表;**
通过调试不难发现,Sub子类的的虚表就是:Function_1、Function_2、Function_3、Function_4、Function_5、Function_6 这个6个函数的地址;Base基类的虚表就是:Function_1、Function_2、Function_3 这个3个函数地址;
```cpp
#include <iostream>
//普通继承,非覆盖
struct Base
{
public:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n");
}
};
struct Sub :Base
{
public:
virtual void Function_4()
{
printf("Sub:Function_4...\n");
}
virtual void Function_5()
{
printf("Sub:Function_5...\n");
}
virtual void Function_6()
{
printf("Sub:Function_6...\n");
}
};
void TestMethod() {
//查看 Sub, Base 的虚函数表
Sub sub;
Base base;
Base* ptr1 = ⊂//包含一个虚表指针
Base* ptr2 = &base;
printf("base 的地址为:%x\n", &base);
//对象的前四个字节就是虚函数表
printf("base 的虚函数表地址为:%x\n", *(int*)&base);
for (int i = 0; i < 3; i++) {
printf("Base虚函数%d的地址:%x\n", i + 1, *((int*)(*(int*)&base) + i));
}
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
pFunction p1Fn;
for (int i = 0; i < 3; i++)
{
int temp = *((int*)(*(int*)&base) + i);
p1Fn = (pFunction)temp;
p1Fn();
}
std::cout << std::endl;
printf("Sub 的地址为:%x\n", &sub);
//对象的前四个字节就是虚函数表
printf("Sub 的虚函数表地址为:%x\n", *(int*)&sub);
for (int i = 0; i < 6; i++) {
printf("Sub虚函数%d的地址:%x\n", i + 1, *((int*)(*(int*)&sub) + i));
}
//通过函数指针调用函数,验证正确性
pFunction p2Fn;
for (int i = 0; i < 6; i++)
{
int temp = *((int*)(*(int*)&sub) + i);
p2Fn = (pFunction)temp;
p2Fn();
}
}
int main() {
TestMethod();
system("pause");
return 0;
}
```
#### 2.2.3 继承覆盖虚函数表
子类优先按照基类声明的顺序添加虚函数;
```cpp
#include <iostream>
//普通继承,覆盖
struct Base
{
public:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n");
}
};
struct Sub :Base
{
public:
virtual void Function_1()
{
printf("Sub:Function_1...\n");
}
virtual void Function_5()
{
printf("Sub:Function_5...\n");
}
virtual void Function_3()
{
printf("Sub:Function_3...\n");
}
};
//查看 Sub, Base 的虚函数表
void TestMethod()
{
//查看 Sub, Base 的虚函数表
Sub sub;
Base base;
Base* ptr1 = ⊂//包含一个虚表指针
Base* ptr2 = &base;
printf("base 的地址为:%x\n", &base);
//对象的前四个字节就是虚函数表
printf("base 的虚函数表地址为:%x\n", *(int*)&base);
for (int i = 0; i < 3; i++) {
printf("Base虚函数%d的地址:%x\n", i + 1, *((int*)(*(int*)&base) + i));
}
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
pFunction p1Fn;
for (int i = 0; i < 3; i++)
{
int temp = *((int*)(*(int*)&base) + i);
p1Fn = (pFunction)temp;
p1Fn();
}
std::cout << std::endl;
printf("Sub 的地址为:%x\n", &sub);
//对象的前四个字节就是虚函数表
printf("Sub 的虚函数表地址为:%x\n", *(int*)&sub);
for (int i = 0; i < 4; i++) {
printf("Sub虚函数%d的地址:%x\n", i + 1, *((int*)(*(int*)&sub) + i));
}
//通过函数指针调用函数,验证正确性
pFunction p2Fn;
for (int i = 0; i < 4; i++)
{
int temp = *((int*)(*(int*)&sub) + i);
p2Fn = (pFunction)temp;
p2Fn();
}
}
int main() {
TestMethod();
system("pause");
return 0;
}
```
#### 2.2.4 多继承非覆盖虚函数表
```cpp
#include <iostream>
struct Base1
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base2
{
public:
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
virtual void Fn_4()
{
printf("Base2:Fn_4...\n");
}
};
struct Sub :Base1, Base2
{
public:
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
virtual void Fn_6()
{
printf("Sub:Fn_6...\n");
}
};
int main() {
//查看 Sub 的虚函数表
Sub sub;
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
//对象的前四个字节是第一个虚表: 经过编译器优化:Base1 + Sub 的虚函数组成的表
printf("Sub的第一个虚表的虚函数表地址为:%x\n", *(int*)&sub);
pFunction pFn;
for (int i = 0; i < 4; i++)
{
int temp = *((int*)(*(int*)&sub) + i);
if (temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
//对象的第二个四字节是第二个虚表, 根据编译器的优化 =》 Base2的虚表
printf("Sub的第二个虚表的虚函数表地址为:%x\n", *(int*)((int*)&sub + 4));
pFunction pFn1;
for (int k = 0; k < 2; k++)
{
int temp = *((int*)(*(int*)((int)&sub + 4)) + k);
pFn1 = (pFunction)temp;
pFn1();
}
system("pause");
return 0;
}
```
结论:Sub的虚表由两个虚表组成:
1. Base1 + Sub 的虚函数组成的虚表;
2. Base2组成的虚表
>猜测:之后的继承的每个类有单独的虚表,如Base2一样;
#### 2.2.5 多继承覆盖虚函数表
```cpp
#include <iostream>
struct Base1
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base2
{
public:
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
virtual void Fn_4()
{
printf("Base2:Fn_4...\n");
}
};
struct Base3
{
public:
virtual void Fn_6()
{
printf("Base3:Fn_6...\n");
}
virtual void Fn_7()
{
printf("Base3:Fn_7...\n");
}
};
struct Sub :Base1, Base2, Base3
{
public:
virtual void Fn_1()
{
printf("Sub:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Sub:Fn_3...\n");
}
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
virtual void Fn_6()
{
printf("Sub:Fn_6...\n");
}
virtual void Fn_8()
{
printf("Sub:Fn_8...\n");
}
};
int main() {
//查看 Sub 的虚函数表
Sub sub;
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
//对象的前四个字节是第一个虚表: 经过编译器优化:Base1 + Sub 的虚函数组成的表
printf("第一个虚表 的虚函数表地址为:%x\n", *(int*)&sub);
pFunction pFn;
for (int i = 0; i < 4; i++)
{
int temp = *((int*)(*(int*)&sub) + i);
if (temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
//对象的第二个四字节是第二个虚表, 根据编译器的优化 =》 Base2的虚表
printf("第二个虚表 的虚函数表地址为:%x\n", *(int*)((int)&sub + 4));
pFunction pFn1;
for (int k = 0; k < 2; k++)
{
int temp = *((int*)(*(int*)((int)&sub + 4)) + k);
pFn1 = (pFunction)temp;
pFn1();
}
//对象的第三个四字节是第三个虚表, 根据编译器的优化 =》 Base3的虚表
printf("第三个虚表 的虚函数表地址为:%x\n", *(int*)((int)&sub + 8));
for (int k = 0; k < 2; k++)
{
int temp = *((int*)(*(int*)((int)&sub + 8)) + k);
pFn1 = (pFunction)temp;
pFn1();
}
system("pause");
return 0;
}
```
结论:Sub的虚表由三个虚表组成,按照继承顺序组合:
1. Base1 + Sub 的虚函数组成的虚表:Sub::Fn_1(), Base1::Fn_2(), Sub::Fn_5(), Sub::Fn_8();
2. Base2 + Sub 的虚函数组成的虚表:Sub::Fn_3(), Base2::Fn_4();
3. Base3 + Sub 的虚函数组成的虚表:Sub::Fn_6(), Base3::Fn_7();
#### 2.2.6 单一连续继承,无函数覆盖
```cpp
#include <iostream>
struct Base1
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base2 :Base1
{
public:
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
virtual void Fn_4()
{
printf("Base2:Fn_4...\n");
}
};
struct Sub :Base2
{
public:
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
virtual void Fn_6()
{
printf("Sub:Fn_6...\n");
}
};
int main(int argc, char* argv[])
{
//查看 Sub 的虚函数表
Sub sub;
//观察大小:虚函数表只有一个
printf("%x\n", sizeof(sub));
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
//对象的前四个字节是就是虚函数表
printf("Sub 的虚函数表地址为:%x\n", *(int*)&sub);
pFunction pFn;
for (int i = 0; i < 6; i++)
{
int temp = *((int*)(*(int*)&sub) + i);
if (temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
return 0;
}
```
结论:单一继承,虚表只有一个;
#### 2.2.7 单一连续继承,有函数覆盖
```cpp
#include <iostream>
struct Base1
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base2 :Base1
{
public:
virtual void Fn_1()
{
printf("Base2:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
};
struct Sub :Base2
{
public:
virtual void Fn_1()
{
printf("Sub:Fn_1...\n");
}
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
};
int main(int argc, char* argv[])
{
//查看 Sub 的虚函数表
Sub sub;
//观察大小:虚函数表只有一个
printf("%x\n", sizeof(sub));
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
//对象的前四个字节是就是虚函数表
printf("Sub 的虚函数表地址为:%x\n", *(int*)&sub);
pFunction pFn;
for (int i = 0; i < 6; i++)
{
int temp = *((int*)(*(int*)&sub) + i);
if (temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
return 0;
}
```
结论:单一继承,虚表只有一个;
#### 2.2.8 例子
1. 定义一个父类:Base 有两个成员X,Y 有一个函数Print(非virtul)
能够打印X,Y的值。
```cpp
#include <iostream>
class Base {
public:
Base(int x, int y) : x(x), y(y) {
};
private:
int x, y;
};
void prt(void* ptr) {
printf("X: %d Y: %d\n", *((int*)ptr), *((int*)ptr + 1));
}
int main() {
Base base(1, 2);
prt(&base);
system("pause");
return 0;
}
```
2. 定义3个子类:Sub1有一个成员A, Sub2有一个成员B, Sub3有一个成员C; 每个子类有一个函数Print(非virtul),打印所有成员。
* Sub1:打印X Y A
* Sub2:打印X Y B
* Sub3:打印X Y C
```cpp
#include <iostream>
class Base {
public:
Base(int x, int y) : x(x), y(y) {
};
private:
int x, y;
};
classSub1 : public Base {
public:
Sub1(int x, int y, int n) : Base(x, y), a(n) {
}
void prt(void* ptr) {
printf("X: %d Y: %d #: %d\n", *((int*)ptr), *((int*)ptr + 1), *((int*)ptr + 2));
}
private:
int a;
};
classSub2 : public Base {
public:
Sub2(int x, int y, int n) : Base(x, y), b(n) {
}
void prt(void* ptr) {
printf("X: %d Y: %d #: %d\n", *((int*)ptr), *((int*)ptr + 1), *((int*)ptr + 2));
}
private:
int b;
};
classSub3 : public Base {
public:
Sub3(int x, int y, int n) : Base(x, y), c(n) {
}
void prt(void* ptr) {
printf("X: %d Y: %d #: %d\n", *((int*)ptr), *((int*)ptr + 1), *((int*)ptr + 2));
}
private:
int c;
};
int main() {
Sub1 sub1(1, 2, 3);
Sub1 sub2(4, 5, 6);
Sub1 sub3(7, 8, 9);
sub1.prt(&sub1);
sub2.prt(&sub2);
sub3.prt(&sub3);
system("pause");
return 0;
}
```
总结:子类的成员 = 基类的成员 + 子类的成员
3. 将上面所有的Print函数改成virtul 继续观察效果.
```cpp
#include <iostream>
class Base {
public:
Base(int x, int y) : x(x), y(y) {
};
private:
int x, y;
};
classSub1 : public Base {
public:
Sub1(int x, int y, int n) : Base(x, y), a(n) {
}
virtual void prt(void* ptr) {
printf("X: %d Y: %d #: %d\n", *((int*)ptr + 1), *((int*)ptr + 2), *((int*)ptr + 3));
}
private:
int a;
};
classSub2 : public Base {
public:
Sub2(int x, int y, int n) : Base(x, y), b(n) {
}
virtual void prt(void* ptr) {
printf("X: %d Y: %d #: %d\n", *((int*)ptr + 1), *((int*)ptr + 2), *((int*)ptr + 3));
}
private:
int b;
};
classSub3 : public Base {
public:
Sub3(int x, int y, int n) : Base(x, y), c(n) {
}
virtual void prt(void* ptr) {
printf("X: %d Y: %d #: %d\n", *((int*)ptr + 1), *((int*)ptr + 2), *((int*)ptr + 3));
}
private:
int c;
};
int main() {
Sub1 sub1(1, 2, 3);
Sub1 sub2(4, 5, 6);
Sub1 sub3(7, 8, 9);
sub1.prt(&sub1);
sub2.prt(&sub2);
sub3.prt(&sub3);
system("pause");
return 0;
}
```
总结:子类成员在对象的前4字节(虚表地址)之后,子类的成员 = 基类的成员 + 子类的成员 C++中还存在虚基类。对于微软的编译器,可能有`vbtable`。可以使用`/d1reportSingleClassLayoutClassName`确定布局,其中`ClassName`为特定类名称。 周易 发表于 2023-2-12 19:32
C++中还存在虚基类。对于微软的编译器,可能有`vbtable`。可以使用`/d1reportSingleClassLayoutClassNa ...
虚函数表(即vtable)在类中定义了虚函数即会生成,由vptr指向,在虚函数的多继承场景下,可能还会存在多个虚函数表,可使用vs 在反汇编界面逐指令调试查看具体vptr指向与虚函数载运行时动态绑定情况 如果有可能的话,加几行带注释的反汇编就更好了 楼主的帖子难度好大,不像入门的{:1_918:}支持一下 讲得很好啊 收藏,谢谢楼主。 收藏,谢谢楼主。 感谢分享