0x00
这一节我们要讨论对象的继承和虚函数的汇编实现。
0x01
我们先直接看汇编代码:
#include "com_example_ndkreverse6_Lesson6.h" #include <android/log.h> #define LOG_TAG "lesson6" #define ALOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) class Base { public: virtual void display() { //虚函数,virtual声明的函数,向上转型后的对象才能调用到子类同名的方法 ALOGD("Base:%d, BaseChar:%d", base_, baseChar_); } Base(int base) { base_ = base; baseChar_ = 8; ALOGD("Base ..."); } virtual ~Base() { //虚析构函数,只有声明成virtual,向上转型的对象先调用子类的析构函数,再调用父类的析构函数 ALOGD("~Base ..."); } private: int base_; char baseChar_; }; class Derived: public Base { public: virtual void display() { //覆盖父类的方法 ALOGD("Derived:%d, DerivedChar:%d", derived_, derivedChar_); Base::display(); //使用父类的方法,由于是覆盖,所以同名,要用这种方式来引用 } Derived(int derived) : Base(derived) { derived_ = derived; derivedChar_ = 10; ALOGD("Derived ..."); } ~Derived() { ALOGD("~Derived ..."); } private: int derived_; char derivedChar_; }; JNIEXPORT void JNICALL Java_com_example_ndkreverse6_Lesson6_main (JNIEnv * env, jobject jobject) { Base* d = new Derived(18); d->display(); delete d; }
那么,运行后执行的结果如下:
D/lesson6 (28959): Base ... D/lesson6 (28959): Derived ... D/lesson6 (28959): Derived:18, DerivedChar:10 D/lesson6 (28959): Base:18, BaseChar:8 D/lesson6 (28959): ~Derived ... D/lesson6 (28959): ~Base ...
0x02
下面我们使用ida来打开so,对汇编代码做出解释。该汇编代码使用的是调试状态下的汇编代码。
.text:00003088 EXPORT Java_com_example_ndkreverse6_Lesson6_main .text:00003088 Java_com_example_ndkreverse6_Lesson6_main .text:00003088 PUSH {R4-R6,LR} .text:0000308A MOVS R0, #0x14 ; unsigned int R0被分配为20,表示要分配20个字节的内存单元 .text:0000308C BL _Znwj ; operator new(uint) 分配20个字节大小的内存单元 .text:00003090 LDR R3, =(_ZTV4Base_ptr - 0x3098) ;直接调用Base的构造函数 .text:00003092 MOVS R6, #0x12 ;R6被初始化为18 .text:00003094 ADD R3, PC ; _ZTV4Base_ptr .text:00003096 LDR R3, [R3] ; `vtable for‘Base 取出Base的虚表指针,指向.data.rel.ro,即0x70428960 .text:00003098 MOVS R4, R0 ;R0为刚分配的20个字节大小的内存单元的首地址,现在赋值给R4 .text:0000309A ADDS R3, #8 ;R3+8再赋值给R3,R3指向了0x70428968 .text:0000309C STR R3, [R0] ;将0x70428968赋值给刚分配的内存单元的首地址(所指向的内存单元) .text:0000309E MOVS R3, #8 ;R3被赋值为8 .text:000030A0 LDR R5, =(aLesson6 - 0x30AA) .text:000030A2 LDR R2, =(aBase____0 - 0x30B0) .text:000030A4 STR R6, [R0,#4] ;把18赋值给刚分配的内存单元的首地址+4 .text:000030A6 ADD R5, PC ; "lesson6" R5指向了位于.rodata段中lesson6 .text:000030A8 MOVS R1, R5 ;把R5赋值给R1做为参数 .text:000030AA STRB R3, [R0,#8] ;把8赋值给刚分配的内存单元的首地址+8 .text:000030AC ADD R2, PC ; "Base ..." R2指向了位于.rodata段中Base ... .text:000030AE MOVS R0, #3 ;R0被初始化为3 .text:000030B0 BL j_j___android_log_print ;输出Base ... .text:000030B4 LDR R3, =(_ZTV7Derived_ptr - 0x30BE) ;开始调用Derived的构造函数 .text:000030B6 LDR R2, =(aDerived____0 - 0x30C2) .text:000030B8 MOVS R1, R5 ;R1被赋值为R5,指向位于.rodata段中的lesson6 .text:000030BA ADD R3, PC ; _ZTV7Derived_ptr .text:000030BC LDR R3, [R3] ; `vtable for‘Derived 取出Derived的虚表指针,指向.data.rel.ro,即0x70428978 .text:000030BE ADD R2, PC ; "Derived ..." R2指向位于.rodata段中Derived .text:000030C0 STR R6, [R4,#0xC] ;把18赋值给刚分配的内存单元的首地址+12 .text:000030C2 ADDS R3, #8 ;R3赋值为Derived的虚表指针+8,即0x70428980 .text:000030C4 STR R3, [R4] ;把0x70428980赋值给刚分配的内存单元的首地址 .text:000030C6 MOVS R3, #0xA ;把R3赋值给10 .text:000030C8 MOVS R0, #3 ;把R0赋值给3 .text:000030CA STRB R3, [R4,#0x10] ;把10赋值给刚分配的内存单元的首地址+16 .text:000030CC BL j_j___android_log_print ;目前R0,R1,R2都被初始化为正确的值了,开始调用j_j___android_log_print .text:000030D0 LDR R3, [R4] ;取出虚表指针,即位于.data.rel.ro段的地址0x70428980 .text:000030D2 MOVS R0, R4 ;把R0赋值为R4,作为第一个参数,R4为刚分配的内存单元的首地址,即this指针 .text:000030D4 LDR R3, [R3] ;取出虚表指针所指向的内容,即Derived的Display方法的地址 .text:000030D6 BLX R3 ;跳转到Derived的Display方法 .text:000030D8 LDR R3, [R4] ;取出虚表指针,即位于.data.rel.ro段的地址0x70428980 .text:000030DA MOVS R0, R4 ;把R0赋值为R4,作为第一个参数,R4为刚分配的内存单元的首地址,即this指针 .text:000030DC LDR R3, [R3,#8] ;取出虚表指针+8所指向的内容,即Derived的~Derived方法的地址 .text:000030DE BLX R3 ;跳转到Derived的~Derived方法 .text:000030E0 POP {R4-R6,PC}
.text:704220E4 off_704220E4 DCD _ZTV4Base_ptr - 0x70422098 .text:704220E4 ; DATA XREF: Java_com_example_ndkreverse6_Lesson6_main+8r .text:704220E8 off_704220E8 DCD aLesson6 - 0x704220AA .text:704220E8 ; DATA XREF: Java_com_example_ndkreverse6_Lesson6_main+18r .text:704220E8 ; "lesson6" .text:704220EC off_704220EC DCD aBase____0 - 0x704220B0 .text:704220EC ; DATA XREF: Java_com_example_ndkreverse6_Lesson6_main+1Ar .text:704220EC ; "Base ..." .text:704220F0 off_704220F0 DCD _ZTV7Derived_ptr - 0x704220BE .text:704220F0 ; DATA XREF: Java_com_example_ndkreverse6_Lesson6_main+2Cr .text:704220F4 off_704220F4 DCD aDerived____0 - 0x704220C2 .text:704220F4 ; DATA XREF: Java_com_example_ndkreverse6_Lesson6_main+2Er .text:704220F4 ; "Derived ..."
.got:70428F24 AREA .got, DATA .got:70428F24 ; ORG 0x70428F24 .got:70428F24 _ZTV4Base_ptr DCD _ZTV4Base ; DATA XREF: Base::~Base()+Ao .got:70428F24 ; Base::~Base()+Cr ... .got:70428F24 ; `vtable for‘Base .got:70428F28 _ZTV7Derived_ptr DCD _ZTV7Derived ; DATA XREF: Derived::~Derived()+Ao .got:70428F28 ; Derived::~Derived()+Cr ... .got:70428F28 ; `vtable for‘Derived
.data.rel.ro:70428960 ; `vtable for‘Base .data.rel.ro:70428960 _ZTV4Base DCB 0 ; DATA XREF: Base::~Base()+Co .data.rel.ro:70428960 ; Java_com_example_ndkreverse6_Lesson6_main+Eo ... .data.rel.ro:70428961 DCB 0 .data.rel.ro:70428962 DCB 0 .data.rel.ro:70428963 DCB 0 .data.rel.ro:70428964 DCD _ZTI4Base ; `typeinfo for‘Base .data.rel.ro:70428968 DCD _ZN4Base7displayEv+1 .data.rel.ro:7042896C DCD _ZN4BaseD2Ev+1 .data.rel.ro:70428970 DCD _ZN4BaseD0Ev+1 .data.rel.ro:70428974 ALIGN 8 .data.rel.ro:70428978 WEAK _ZTV7Derived .data.rel.ro:70428978 ; `vtable for‘Derived .data.rel.ro:70428978 _ZTV7Derived DCB 0 ; DATA XREF: Derived::~Derived()+Co .data.rel.ro:70428978 ; Java_com_example_ndkreverse6_Lesson6_main+34o ... .data.rel.ro:70428979 DCB 0 .data.rel.ro:7042897A DCB 0 .data.rel.ro:7042897B DCB 0 .data.rel.ro:7042897C DCD _ZTI7Derived ; `typeinfo for‘Derived .data.rel.ro:70428980 DCD _ZN7Derived7displayEv+1 .data.rel.ro:70428984 DCD _ZN7DerivedD2Ev+1 .data.rel.ro:70428988 DCD _ZN7DerivedD0Ev+1
.rodata:704267B0 ; `typeinfo name for‘Base .rodata:704267B0 _ZTS4Base DCB "4Base",0 ; DATA XREF: .data.rel.ro:_ZTI4Base+4o .rodata:704267B6 ALIGN 4 .rodata:704267B8 WEAK _ZTS7Derived .rodata:704267B8 ; `typeinfo name for‘Derived .rodata:704267B8 _ZTS7Derived DCB "7Derived",0 ; DATA XREF: .data.rel.ro:_ZTI7Derived+4o .rodata:704267C1 ALIGN 4 .rodata:704267C4 aLesson6 DCB "lesson6",0 ; DATA XREF: Base::display(void)+Eo .rodata:704267C4 ; .text:off_70421FD4o ... .rodata:704267CC aBaseDBasecharD DCB "Base:%d, BaseChar:%d",0 .rodata:704267CC ; DATA XREF: Base::display(void)+10o .rodata:704267CC ; .text:off_70421FD8o .rodata:704267E1 aDerivedDDerive DCB "Derived:%d, DerivedChar:%d",0 .rodata:704267E1 ; DATA XREF: Derived::display(void)+Eo .rodata:704267E1 ; .text:off_70422000o .rodata:704267FC aBase___ DCB "~Base ...",0 ; DATA XREF: Base::~Base()+10o .rodata:704267FC ; .text:off_7042202Co .rodata:70426806 aDerived___ DCB "~Derived ...",0 ; DATA XREF: Derived::~Derived()+10o .rodata:70426806 ; .text:off_70422060o .rodata:70426813 aBase____0 DCB "Base ...",0 ; DATA XREF: Java_com_example_ndkreverse6_Lesson6_main+24o .rodata:70426813 ; .text:off_704220ECo .rodata:7042681C aDerived____0 DCB "Derived ...",0 ; DATA XREF: Java_com_example_ndkreverse6_Lesson6_main+36o .rodata:7042681C ; .text:off_704220F4o
我们看到执行Display方法时,首先从对象的首地址取出虚表指针,然后再从虚表指针所指向的内存单元中取出具体的Display指令的地址,然后跳转到对应的指令执行。
.text:70421FDC ; _DWORD Derived::display(Derived *__hidden this) .text:70421FDC WEAK _ZN7Derived7displayEv .text:70421FDC _ZN7Derived7displayEv ; DATA XREF: .data.rel.ro:_ZTV7Derived+8o .text:70421FDC .text:70421FDC var_10 = -0x10 .text:70421FDC .text:70421FDC PUSH {R0,R1,R4,LR} .text:70421FDE MOVS R4, R0 ;R4被赋值给R0,即刚分配的内存单元的首地址 .text:70421FE0 LDRB R3, [R0,#0x10] ;从内存单元中取出子类的第二个变量,也就是DerivedChar的值 .text:70421FE2 LDR R1, =(aLesson6 - 0x70421FEC) .text:70421FE4 LDR R2, =(aDerivedDDerive - 0x70421FEE) .text:70421FE6 STR R3, [SP,#0x10+var_10] ;作为第五个参数存放在堆栈中传递 .text:70421FE8 ADD R1, PC ; "lesson6" .text:70421FEA ADD R2, PC ; "Derived:%d, DerivedChar:%d" .text:70421FEC LDR R3, [R4,#0xC] ; 第四个参数是子类的第一个变量,也就是Derived的值 .text:70421FEE MOVS R0, #3 .text:70421FF0 BL j_j___android_log_print .text:70421FF4 MOVS R0, R4 ; this ;返回this指针 .text:70421FF6 BL _ZN4Base7displayEv ; Base::display(void) .text:70421FFA POP {R0,R1,R4,PC}
接着,我们继续析构函数,思路和Display方法是一样的,首先从对象的首地址取出虚表指针,然后再从虚表指针+8所指向的内存单元中取出具体的Derived类析构函数的地址,然后跳转到对应的指令执行。
.text:70422064 ; _DWORD __fastcall Derived::~Derived(Derived *__hidden this) .text:70422064 WEAK _ZN7DerivedD0Ev .text:70422064 _ZN7DerivedD0Ev ; DATA XREF: .data.rel.ro:_ZTV7Derived+10o .text:70422064 PUSH {R4,LR} .text:70422066 MOVS R4, R0 ;R4被赋值为R0,即对象的首地址 .text:70422068 BL _ZN7DerivedD2Ev ; Derived::~Derived() .text:7042206C MOVS R0, R4 ; void * R0被赋值为对象的首地址 .text:7042206E BL _ZdlPv ; operator delete(void *) 真正的释放堆空间 .text:70422072 MOVS R0, R4
.text:70422030 ; _DWORD __fastcall Derived::~Derived(Derived *__hidden this) .text:70422030 WEAK _ZN7DerivedD2Ev .text:70422030 _ZN7DerivedD2Ev ; CODE XREF: Derived::~Derived()+4p .text:70422030 ; DATA XREF: .data.rel.ro:_ZTV7Derived+Co .text:70422030 PUSH {R4,LR} ; Alternative name is ‘Derived::~Derived()‘ .text:70422032 MOVS R4, R0 ;R4被赋值为R0,即对象的首地址 .text:70422034 LDR R3, =(_ZTV7Derived_ptr - 0x7042203E) .text:70422036 LDR R1, =(aLesson6 - 0x70422042) .text:70422038 LDR R2, =(aDerived___ - 0x70422044) .text:7042203A ADD R3, PC ; _ZTV7Derived_ptr .text:7042203C LDR R3, [R3] ; `vtable for‘Derived R3 取出Derived的虚表指针,指向.data.rel.ro,即0x70428978 .text:7042203E ADD R1, PC ; "lesson6" .text:70422040 ADD R2, PC ; "~Derived ..." .text:70422042 ADDS R3, #8 ;R3赋值为Derived的虚表指针+8,即0x70428980 .text:70422044 STR R3, [R0] ;把0x70428980赋值给刚分配的内存单元的首地址 .text:70422046 MOVS R0, #3 .text:70422048 BL j_j___android_log_print .text:7042204C MOVS R0, R4 ; this R0被赋值为对象的首地址 .text:7042204E BL _ZN4BaseD2Ev ; Base::~Base() 调用父类的析构函数 .text:70422052 MOVS R0, R4 ;返回对象的首地址 .text:70422054 POP {R4,PC}
.text:70422004 ; _DWORD __fastcall Base::~Base(Base *__hidden this) .text:70422004 WEAK _ZN4BaseD2Ev .text:70422004 _ZN4BaseD2Ev ; CODE XREF: Derived::~Derived()+1Ep .text:70422004 ; Base::~Base()+4p .text:70422004 ; DATA XREF: ... .text:70422004 PUSH {R4,LR} ; Alternative name is ‘Base::~Base()‘ .text:70422006 MOVS R4, R0 ;R4被赋值为对象的首地址 .text:70422008 LDR R3, =(_ZTV4Base_ptr - 0x70422012) .text:7042200A LDR R1, =(aLesson6 - 0x70422016) .text:7042200C LDR R2, =(aBase___ - 0x70422018) .text:7042200E ADD R3, PC ; _ZTV4Base_ptr .text:70422010 LDR R3, [R3] ; `vtable for‘Base 取出Base的虚表指针,指向.data.rel.ro,即0x70428960 .text:70422012 ADD R1, PC ; "lesson6" .text:70422014 ADD R2, PC ; "~Base ..." .text:70422016 ADDS R3, #8 ;R3+8再赋值给R3,R3指向了0x70428968 .text:70422018 STR R3, [R0] ;将0x70428968赋值给刚分配的内存单元的首地址(所指向的内存单元) .text:7042201A MOVS R0, #3 ;R0是第一个参数,赋值为3 .text:7042201C BL j_j___android_log_print .text:70422020 MOVS R0, R4 ;返回对象的首地址 .text:70422022 POP {R4,PC}
0x03
总结:父类的成员位于低地址,子类的成员位于高地址,虚表指针位于对象的首地址,构造对象时,先构造父类,虚表指针指向父类的虚表,再构造子类,虚表指针指向子类的虚表。析构对象时,先析构子类,虚表指针指向子类的虚表,再析构父类,虚表指针指向父类的虚表。
时间: 2024-10-07 15:10:29