Miten virtuaali funktio taulukon voi löytää disasm-ohjelmalla?
Tuo lienee kääntäjäsidonnaista. Sijainnillahan ei sinänsä ole minkäänlaisia vaatimuksia. Seuraa empiirinen tutkimus, jossa on siis yksinkertainen C++-ohjelma ja sitä vastaava GCC:n tuottama x86-assembly (g++-4.2 koe.cpp -Os -m32 -fno-rtti -fno-exceptions -S).
Kirjoitan havainnotkin jo tähän valmiiksi, jottei tarvitse alempaa selata: Luontifunktiosta nähdään, että muuttujattomalle luokalle varataan osoittimen verran tilaa. Tähän tilaan tallennetaan osoitin funktiotauluun. Funktiotaulu taas sisältää osoittimet virtuaalifunktioihin. En asiaa erikseen kokeillut, mutta järki sanoo, että taulun alussa olevat kaksi kohtaa viittaisivat normaalisti luonti- ja tuhoamisfunktioihin.
Luultavasti siis paras johtolanka disassemblerin kanssa on juuri tämä luominen. Etsi luontifunktio eli new-kutsu ja katso, mikä osoite varatun muistin alkuun sijoitetaan.
// Abstrakti luokka class PARENT { public: virtual int FUNK_A(int PARAMETRI) = 0; virtual int FUNK_B(int PARAMETRI) = 0; }; // Luokan toteutus class CHILD : public PARENT { public: virtual int FUNK_A(int PARAMETRI) {return PARAMETRI;} virtual int FUNK_B(int PARAMETRI) {return PARAMETRI + 1;} }; // Funktio, josta nähdään, kuinka objekti luodaan PARENT *PALAUTUS() { return new CHILD(); }
.file "koe.cpp" ;;; Perityn luokan funktio FUNK_A .section .text._ZN5CHILD6FUNK_AEi,"axG",@progbits,_ZN5CHILD6FUNK_AEi,comdat .align 2 .weak _ZN5CHILD6FUNK_AEi .type _ZN5CHILD6FUNK_AEi, @function _ZN5CHILD6FUNK_AEi: pushl %ebp movl %esp, %ebp movl 12(%ebp), %eax popl %ebp ret .size _ZN5CHILD6FUNK_AEi, .-_ZN5CHILD6FUNK_AEi ;;; Perityn luokan funktio FUNK_B .section .text._ZN5CHILD6FUNK_BEi,"axG",@progbits,_ZN5CHILD6FUNK_BEi,comdat .align 2 .weak _ZN5CHILD6FUNK_BEi .type _ZN5CHILD6FUNK_BEi, @function _ZN5CHILD6FUNK_BEi: pushl %ebp movl %esp, %ebp movl 12(%ebp), %eax popl %ebp incl %eax ret .size _ZN5CHILD6FUNK_BEi, .-_ZN5CHILD6FUNK_BEi ;;; Luontifunktio .text .align 2 .globl _Z8PALAUTUSv .type _Z8PALAUTUSv, @function _Z8PALAUTUSv: pushl %ebp movl %esp, %ebp subl $20, %esp pushl $4 call _Znwj movl $_ZTV5CHILD+8, (%eax) leave ret .size _Z8PALAUTUSv, .-_Z8PALAUTUSv ;;; Perityn luokan funktiotaulu .weak _ZTV5CHILD .section .rodata._ZTV5CHILD,"aG",@progbits,_ZTV5CHILD,comdat .align 8 .type _ZTV5CHILD, @object .size _ZTV5CHILD, 16 _ZTV5CHILD: .long 0 .long 0 .long _ZN5CHILD6FUNK_AEi .long _ZN5CHILD6FUNK_BEi ;;; Pääluokan funktiotaulu .weak _ZTV6PARENT .section .rodata._ZTV6PARENT,"aG",@progbits,_ZTV6PARENT,comdat .align 8 .type _ZTV6PARENT, @object .size _ZTV6PARENT, 16 _ZTV6PARENT: .long 0 .long 0 .long __cxa_pure_virtual .long __cxa_pure_virtual .ident "GCC: (GNU) 4.2.3 (Ubuntu 4.2.3-2ubuntu7)" .section .note.GNU-stack,"",@progbits
Kiitos vastauksesta!
Kokeilin tehdä oman 'empiirisen' tutkimuksen tuolla tavalla ja tässä on hahmottamani rakenne:
[this] | *points to* v [ADDRESS OF VTABLE] variable_1 | variable_2 | variable_3 | | .------* | V [ADDRESS OF FUNC_1] [ADDRESS OF FUNC_2] [ADDRESS OF FUNC_3]
Kääntäjä on vc++:n kääntäjä. Pääsinköhän nyt oikeille raiteille?
Jokseenkin noin. Varmista toki asia tulostelemalla oletettuja osoittimia ja vertaamalla niitä funktioiden oikeisiin osoitteisiin.
Itse ilmaisisin kaavion ennemmin suoraan koodina, jolla dataakin voi tarkastella. Jotenkin näin siis:
typedef int function(int p1, ...); struct functions { function *f_1; function *f_2; function *f_n; }; struct virtual_class { functions *vtable; int member_1, member_2, member_n; }; virtual_class *vclass = (virtual_class *) this; // Tulostetaan osoitteita printf("vtable @ %p\n", vclass->vtable); printf("f_1 @ %p\n", vclass->f_1);
Aihe on jo aika vanha, joten et voi enää vastata siihen.