但是现在我看到了VFT索引为13(!!!!),即offset = 0x34。 (我写“ index”是因为调用Qt函数
o.metaObject()
的代码是((func***)o)[13][0](o)
)。天哪,这是怎么回事?为什么VFT地址位于...何处?编辑(在抱怨问题不清楚后):表。通常,这是对象二进制表示形式中的第一个32位值(可以通过
((void**)objAddr)[0]
进行访问)。但是在下面的示例中,VMT指针的偏移量不为0! (函数名称可能会被c++filt
分解;为了便于阅读,类名已缩短为Abc
和Xyz
):.text:02EF171C _ZN3XyzC2EP7QObject ; constructor Xyz::Xyz(QObject*), r0 = objAddr, r1 = QObject addr
.text:02EF171C PUSH.W {R4-R8,LR}
.text:02EF1720 MOV R4, R0
.text:02EF1722 LDR R5, =(_GLOBAL_OFFSET_TABLE_ - 0x02EF1730)
.text:02EF1724 MOV R7, R1
.text:02EF1726 BL.W _ZN4AbcdC2EP7QObject ; superclass_constructor(objAddr)
.text:02EF172A ; ---------------------------------------------------------------------------
.text:02EF172A LDR R3, =(_ZTVN3XyzE_ptr - 0x27E4BE0) ; vtable for Xyz
.text:02EF172C ADD R5, PC ; _GLOBAL_OFFSET_TABLE_
.text:02EF172E MOV R6, R4
.text:02EF1730 MOV R1, R7
.text:02EF1732 LDR R3, [R5,R3] ; _ZTVN3XyzE_ptr ; pointer to vtable for Xyz
.text:02EF1734 ADDS R3, #8 ; *_ptr points to the (-2)nd element of VMT
.text:02EF1736 STR.W R3, [R6],#0x34 ; OOPS! the offset is 0x34 !!!
我希望能够找到指针到任何对象的VMT,但是如上例所示,指向VMT的指针不一定是
((void**)objAddr)[0]
。所以问题是:
1)为什么VMT指针是在对象的二进制表示中间?
2)该如何确定VMT指针实际上在哪里? (理想情况下,在运行时给定了对象地址。我有代码来告知无效地址中的有效地址。我对Android / ARM的GCC感兴趣,尽管事实证明适用于不同平台的技术也适用。)
PS在Android上检测有效地址的代码是:
#include <unistd.h>
#include <fcntl.h>
int isValidPtr(const void*p, int len) {
if (!p) { return 0; }
int ret = 1;
int nullfd = open("/dev/random", O_WRONLY); // does not work with /dev/null !!!
if (write(nullfd, p, len) < 0) {
ret = 0; /* Not OK */
}
close(nullfd);
return ret;
}
UPDATE
在以下示例中,VMT偏移为0:
class Base {
public:
int x,y;
};
class Derived: public Base {
public:
int z;
Derived();
virtual int func();
virtual int func2();
};
从
Base*
到Derived*
的强制转换为:SUBS R0, #4
int test3(Base*b) {
Derived*d = (Derived*)b;
int r = addDerived(*d);
return r;
}
; test3(Base *)
_Z5test3P4Base
CBZ R0, loc_1C7A
SUBS R0, #4
B.W _Z10addDerivedR7Derived ;
UPDATE2
我尝试过
struct Cls2 {
unsigned x[13];
Derived d;
Cls2();
};
这是反汇编:
.text:00001CE2 _ZN4Cls2C2Ev ; Cls2::Cls2(void)
.text:00001CE2 PUSH {R4,LR}
.text:00001CE4 MOV R4, R0
.text:00001CE6 ADD.W R0, R0, #0x34
.text:00001CEA BL _ZN7DerivedC2Ev ; Derived::Derived(void)
.text:00001CEE MOV R0, R4
.text:00001CF0 POP {R4,PC}
也就是说,
Cls2::d
的VFT指针确实会在偏移量0x34处,但是没有STR.W R3,[R6],#0x34
,因此它不是Willem Hengeveld建议的#2。 > struct Cls2 {
unsigned x[13];
Derived d;
// Cls2();
};
在
int testCls2() {
Cls2 c;
return c.d.func2();
}
我们得到
.text:00001C9E _Z8testCls2v
.text:00001C9E var_18 = -0x18
.text:00001C9E PUSH {LR}
.text:00001CA0 SUB SP, SP, #0x4C
.text:00001CA2 ADD R0, SP, #0x50+var_18
.text:00001CA4 BL _ZN7DerivedC2Ev ; Derived::Derived(void)
.text:00001CA8 ADD R0, SP, #0x50+var_18
.text:00001CAA BL _ZN7Derived5func2Ev ; Derived::func2(void)
.text:00001CAE ADD SP, SP, #0x4C
.text:00001CB0 POP {PC}
与原始代码非常相似
但在我的情况下,VMT
vtable for Xyz
是从Xyz::Xyz()
而不是从封闭函数编写的。#1 楼
我可以想到2种情况,其中VMT不在对象的第一个单词中:使用多重继承
当对象的成员变量具有虚拟方法时
多重继承
struct base1 {
uint32_t x[12];
virtual void m1() { }
};
struct base2 {
virtual void m2() { }
};
struct cls : base1, base2 {
};
base2的VMT偏移量为0x34
虚拟成员
struct cls2 {
uint32_t x[13];
base2 b;
};
现在base2的VMT也位于偏移量0x34
#2 楼
用于检测和打印虚拟功能表指针的代码为:int isIdentifier(const char* s) { // true if points to [0-9a-zA-Z_]*\x00
if(!isValidPtr(s,0x10)) { return 0; }
if(!s[0]) { return 0; }
int i;
for (i=0; s[i] && i<512; i++) {
if( i/0x10 && i%0x10 == 0 && !isValidPtr(s,0x10)) { return 0; }
unsigned char c = s[i];
if ('0'<=c && c<='9' || 'a'<=c && c <= 'z' || 'A'<=c && c <= 'Z' || '_' == c) {
} else {
return 0;
}
}
return !s[i];
}
char* isVftPtr(void*addr) { // returns addr of mangled class name (prefix it with _Z to demangle with c++filt)
unsigned int* vmtaddr = isValidPtr(addr,4)
&& 0 == (3 & *(int*)addr)
&& isValidPtr(*(int**)addr,4)
? *(unsigned int**)addr
: (void*)0;
if (vmtaddr
&&isValidPtr(vmtaddr-2,0x20)
) {
char**ptypeinfo = ((char***)vmtaddr)[-1];
if (isValidPtr(ptypeinfo,4)
&&isValidPtr((char***)ptypeinfo[0]-1,8)
&&isValidPtr(((char***)ptypeinfo[0])[-1],8)
&&isValidPtr(((char***)ptypeinfo[0])[-1][1],0x20)
&&isIdentifier(ptypeinfo[1])
) {
return !strncmp(((char***)ptypeinfo[0])[-1][1], "N10__cxxabiv",12) ? ptypeinfo[1] : 0;
}
}
return 0;
}
// Usage example: printVfts("pThis", pThis, -8, 0x400)
void printVfts(const char*tag, void* addr, int from, int upto) {
void** start = addr+from;
void** end = addr+upto;
DLOG("{ %s ====== printVfts %p (%p..%p)", tag, addr,start,end);
void**p;
char*n = 0;
for(p=addr;p<end;p++) {
if (n = isVftPtr(p)) {
DLOG("vft at %p [off=0x%x] _Z%s",p,(unsigned)p - (unsigned)addr, n);
}
}
DLOG("} %s ====== printVfts %p", tag, addr);
}
可在Android / ARM上运行的代码。
给出了功能
isValidPtr()
在问题中,下面给出了日志记录宏:
评论
显示代码/ asm /任何内容。目前尚不清楚“索引”的含义。对Derived :: Derived(void)的调用本来可以插入正确的优化标志。
根据您继续编辑问题的方式,您可能需要通读Inside The C ++ Object Model。它包含的信息比单个答案中提供的信息要多得多。