什么是多态
定义一个虚基类ISpeaker
class ISpeaker{
protected:
size_t b;
public:
ISpeaker( size_t _v )
: b(_v)
{}
virtual void speak() = 0;
};
有两个子类,都实现了虚函数speak():
class Dog : public ISpeaker {
public:
Dog()
: ISpeaker(0)
{}
//
virtual void speak() override {
printf("woof! %llu\n", b);
}
};
class Human : public ISpeaker {
private:
size_t c;
public:
Human()
: ISpeaker(1)
, c(2)
{}
virtual void speak() override {
printf("hello! %llu\n", c);
}
};
main方法中把Dog*和Human*类型的指针,退化(强转)成ISpeaker*指针,并调用speak()方法:
int main( int argc, char** _argv ) {
Human* pHuman = new Human();
Dog* pDog = new Dog();
//
ISpeaker* speaker1 = (ISpeaker*)pHuman;
ISpeaker* speaker2 = (ISpeaker*)pDog;
//
speaker2->speak();
speaker1->speak();
//
return 0;
}
输出:
woof! 0
hello! 2
同样类型的指针(都是ISpeaker*),其函数表现出不同的行为(输出不同),称为多态
编译器如何实现多态
以MSVC编译器为例

编译器处理虚类ISpeaker时,生成内存模型(结构体)__ispeaker,其成员有虚表指针SpeakerTable* vt。
Dog继承ISpeaker,所以对应的内存模型__dog会包含__ispeaker中的成员;
Human继承ISpeaker,所以对应的内存模型__human会包含__ispeaker中的所有成员,此外还有自己的成员size_t c

而后,编译器为每个类生成唯一的虚函数表(虚表)对象
__dogSpeakerTable和__humanSpeakerTable是虚表对象
虚表实例中的函数指针指向对应的函数

所以Dog和Human,对应的 虚表对象 中的函数指针指向的函数不同。
这就是为什么speaker2->speak();和speaker1->speak();的输出不同。
在内存中,__dog和__humen的布局如下:
struct __dog {
const SpeakerTable* vt;
size_t b;
};
struct __human {
const SpeakerTable* vt;
size_t b;
size_t c;
};
因此,想访问b或者c成员时,是按虚表对象之后的偏移量来访问的。
调用__dog_speak和__human_speak时会传递对象__dog和__human作为参数:
int main( int _argc, char** _argv ) {
__dog* dog = createDog();
__human* human = createHuman();
//
__ispeaker* speaker1 = (__ispeaker*)dog;
__ispeaker* speaker2 = (__ispeaker*)human;
//
speaker1->vt->speak(speaker1);
speaker2->vt->speak(speaker2);
return 0;
}
访问b或c对象时,基于偏移量:
void __dog_speak( void* _ptr ) {
uint8_t* p = (uint8_t*)_ptr;
p+=sizeof(SpeakerTable*);
size_t b = *((size_t*)p);
printf("woof! %llu\n", b);
}
void __human_speak( void* _ptr ) {
uint8_t* p = (uint8_t*)_ptr;
p+=sizeof(SpeakerTable*);
p+=sizeof(size_t);
size_t b = *((size_t*)p);
printf("hello! %llu\n", b);
}
例如 uint8_t* p = (uint8_t*)_ptr;拿到__dog对象的起始偏移,
p+=sizeof(SpeakerTable*);是越过虚表对象,
size_t b = *((size_t*)p);就找到b对象了。
反编译so时虚函数调用长什么样

比如这里面的*(*a1+32)(a1,a2,a3)
a1是类对应的结构体__dog的起始地址,也就是__dog中const SpeakerTable* vt的地址
*a1是const SpeakerTable* vt的值,也就是虚表__dogSpeakerTable的地址
*a1+32是虚表中某个虚表项的地址
*(*a1+32)是某个虚表项的内容,也就是某个虚函数的地址
参考:
http://showlinkroom.me/2017/08/21/C-%E9%80%86%E5%90%91%E5%88%86%E6%9E%90/
https://www.bilibili.com/video/BV15g4y1a7F3


文章解释了C++中多态的概念,通过一个ISpeaker虚基类和Dog、Human两个子类的例子展示了多态性。当Dog和Human对象通过ISpeaker*指针调用speak()方法时,表现出不同的行为。编译器通过生成虚表(vtable)来实现多态,每个派生类都有自己的虚表实例。
逆向 | 安卓so虚函数逆向&spm=1001.2101.3001.5002&articleId=128681856&d=1&t=3&u=00bf5320a8534d578e4edca48c56ff9d)
386

被折叠的 条评论
为什么被折叠?



