为什么 vtable 会多次引用同一个函数?

逆向工程 艾达 虚表 微信
2021-07-04 00:11:18

我在 IDA 7 中反编译了一个 VC++ 应用程序,我经常发现 vftables 多次引用同一个函数,就像我在这里称为“Object__pure”的那个,它是Object应用程序中几乎所有其他类都继承的“重基”类的一部分

seg002:008F4748     const Object::`vftable' dd offset Object__free ; DATA XREF: Object__ctor+12↑o Object__dtor+A↑o Object__copy+16↑o
seg002:008F474C         dd offset Object__vsub_7D0990
seg002:008F4750         dd offset Object__vsub_7D09A0
seg002:008F4754         dd offset Object__pure
seg002:008F4758         dd offset Object__pure
seg002:008F475C         dd offset Object__pure
seg002:008F4760         dd offset Object__pure
seg002:008F4764         dd offset Object__vsub_47B660

我的可执行文件中继承自的子类Object通常具有自己的自定义函数,而不是它们的 vtable 中的那些“纯”(我如何称呼它们)函数。

不知道那可能是什么,我给它起了个名字“纯”,把它想象成一个虚拟或纯虚拟调用。该函数本身除了调用一个完全空的之外什么都不做Object__vsub_80CD50

seg000:007E4580     Object__pure proc near ; CODE XREF: <lots!>
seg000:007E4580
seg000:007E4580     a1  = dword ptr -4
seg000:007E4580     arg_0= dword ptr  8
seg000:007E4580
seg000:007E4580 000     push    ebp
seg000:007E4581 004     mov     ebp, esp
seg000:007E4583 004     push    ecx
seg000:007E4584 008     mov     [ebp+a1], ecx
seg000:007E4587 008     mov     eax, [ebp+arg_0]
seg000:007E458A 008     push    eax
seg000:007E458B 00C     mov     ecx, [ebp+a1] ; this
seg000:007E458E 00C     call    Object__vsub_80CD50 ; Call Procedure
seg000:007E4593 00C     mov     esp, ebp
seg000:007E4595 004     pop     ebp
seg000:007E4596 000     retn    4 ; Return Near from Procedure
seg000:007E4596     Object__pure endp

...

seg000:0080CD50     Object__vsub_80CD50 proc near ; CODE XREF: <lots again!>
seg000:0080CD50
seg000:0080CD50     var_4= dword ptr -4
seg000:0080CD50
seg000:0080CD50 000     push    ebp
seg000:0080CD51 004     mov     ebp, esp
seg000:0080CD53 004     push    ecx
seg000:0080CD54 008     mov     [ebp+var_4], ecx
seg000:0080CD57 008     mov     esp, ebp
seg000:0080CD59 004     pop     ebp
seg000:0080CD5A 000     retn    4 ; Return Near from Procedure
seg000:0080CD5A     Object__vsub_80CD50 endp

为什么这样的函数会被多次引用?是不是因为优化,统一了什么都不做的功能?这些功能通常是虚拟的/纯虚拟的吗?

1个回答

正如我猜测并在评论中得到证实的那样,这显然是一些编译器优化重用执行相同逻辑的方法。

当我反转一个更具体的对象的方法时,这对我来说很清楚,比如这里的数据流读取器/写入器:

seg002:008F4FC4     const DataStream::`vftable' dd offset DataStream__readByte ; DATA XREF: DataStream__ctor+12↑o
seg002:008F4FC8         dd offset DataStream__readWord
seg002:008F4FCC         dd offset DataStream__readDword
seg002:008F4FD0         dd offset DataStream__readBytes
seg002:008F4FD4         dd offset DataStream__canReadWrite
seg002:008F4FD8         dd offset DataStream__writeByte
seg002:008F4FDC         dd offset DataStream__writeWord
seg002:008F4FE0         dd offset DataStream__writeDword
seg002:008F4FE4         dd offset DataStream__writeBytes
seg002:008F4FE8         dd offset DataStream__canReadWrite

您可以看到这些方法canReadcanWrite被简单地优化为一种方法(我命名为canReadWrite),因为两者的逻辑是相同的(hexrays 输出):

bool __thiscall DataStream::canReadWrite(DataStream *this, int lengthRequired)
{
    return this->members.pData <= this->members.pDataEnd
        && this->members.pDataEnd - this->members.pData >= lengthRequired;
}

可能是一些其他的数据流类(如只读或只写的一个)将实现每个方法不同(简单地返回的情况下false对所述例),但不是在这个类。

因此,对于像Object上面这样的更通用的基类,许多方法没有特别做任何特定的事情,并且被优化为一个。