反汇编研究C++虚函数表,调用类内函数,调用虚函数

环境:gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)

测试代码1:

#include <iostream>
#include <stdio.h>

using namespace std;

class A {
          
   
public:
	virtual void func1() {
          
   printf("A func1
");}
	virtual void func2() {
          
   printf("A func2
");}
};

class B :public A {
          
   
public:
	int i;
	virtual void func1() {
          
   printf("%d
",this->i); }
	virtual void func2() {
          
   printf("B func2
");}
};

using namespace std;

typedef void(*FUNC)();
int main() {
          
   
	B b; 
	b.i = 1;
	A* a=&b;
	
/*FUNC* table = *(FUNC**)(&b);
	(*table)();*/
	a->func1();

	return 0;
}
}

注:std::cout反汇编后不太看得懂,所以使用printf输出

%rbp-0x28处是a的地址所在,可以看到 之后的两个mov (%rax),rax分别将虚函数表的地址推进rax,将虚函数表中的第一个函数也就是B::func1函数的地址推进rax,最后callq *%rax来调用B::func1

记录下当前b的地址 继续运行直到call *%rax前 对%rax所指进行反汇编,可以发现此时rax的确指向B::func1,这也即虚函数的动态查找过程,实现了c++子类父类指针转换后仍然可以调用原本自己的虚函数 推测dynamic_cast进行转换时会更改类内虚函数表指针,而不是一成不变

另外,可以观察到B::fun1中将rdi压入了栈中,并且把rdi偏移地址为8处的4字节压入eax,rdi是64位操作系统&&程序传参时的第一个寄存器,从此可以得出,在调用类内函数时,c++会默认将this指针作为第一个参数传入,从前图的rdi的值也可以直到,rdi的确存着b的地址,也即a

测试代码2:

#include <iostream>
#include <stdio.h>

using namespace std;

class A {
          
   
public:
	virtual void func1() {
          
   printf("A func1
");}
	virtual void func2() {
          
   printf("A func2
");}
};

class B :public A {
          
   
public:
	int i;
	virtual void func1() {
          
   printf("%d
",this->i); }
	virtual void func2() {
          
   printf("B func2
");}
};

using namespace std;

typedef void(*FUNC)();
int main() {
          
   
	B b; 
	b.i = 1;
	A* a=&b;
	B b2;	

	FUNC* table = *(FUNC**)(&b);
	(*table)();
	/*a->func1();*/

	return 0;
}

指向虚函数表的指针被存放在虚类的最开头,这里通过强转直接取到B::func1的地址进行调用 注意:B b2的原因是,在我当前环境下,创建完b后,rdi直到调用(*table)()都未被更改,导致rdi仍然存储着b的地址来调用(*table)也即B::func1,可以输出正确结果1 这里B b2后,实际rdi在调用(*table)()时,实际存着的地址是b2的 可以看到c++先将b2即this指针保存进rdi,然后调用B:::B()默认构造函数,之后rdi都没有改变过

步进到callq *%rax rdi保存着b2的地址,所以程序继续执行,会输出一个垃圾值b2.i 前8子节为指向虚函数表的指针,预计输出1431652576,实际也是

over

经验分享 程序员 微信小程序 职场和发展