I'm decompiling a MFC 4.0 application, and now loaded the MFCS42.PDB from the MFC 4.2 source into IDA 7.0 (not having the MFC 4.0 source) to make it create the appropriate structs representing the many MFC classes and virtual function tables.

However, IDA seems to only create *Vtbl structs for the very base classes, like CObject, not for child classes like CCmdTarget, which looks as follows:

struct CObjectVtbl // totally correct
{
    CRuntimeClass *(__thiscall *GetRuntimeClass)(CObject *this);
    void (__thiscall *~CObject)(CObject *this);
    void (__thiscall *Serialize)(CObject *this, CArchive *);
    void (__thiscall *AssertValid)(CObject *this);
    void (__thiscall *Dump)(CObject *this, CDumpContext *);
};
struct __cppobj CObject // totally correct
{
    CObjectVtbl *vfptr;
};

struct __cppobj CCmdTarget : CObject // wrong, makes it have only a CObject vftable
{
    int m_dwRef;
    IUnknown *m_pOuterUnknown;
    unsigned int m_xInnerUnknown;
    CCmdTarget::XDispatch m_xDispatch;
    int m_bResultExpected;
    CCmdTarget::XConnPtContainer m_xConnPtContainer;
    AFX_MODULE_STATE *m_pModuleState;
};

In effect, this results in missing new virtual functions of CCmdTarget, as it only references the CObjectVtbl by inheriting from CObject, but CCmdTarget has 7 more methods.

I previously hand-crafted these structures (which is tedious as you can guess), and it should actually look more like this:

// CObject and CObjectVtbl same as above

struct CCmdTargetVtbl : CObjectVtbl // inherit to keep base methods
{
    BOOL (__thiscall *OnCmdMsg)(CCmdTarget *this, UINT nID, int nCode, void *pExtra, void *pHandlerInfo);
    void (__thiscall *OnFinalRelease)(CCmdTarget *this);
    AFX_MSGMAP *(__thiscall *GetMessageMap)(CCmdTarget *this);
    int field_20; // Don't know names yet
    int field_24;
    int field_28;
    int field_2C;
};
struct CCmdTargetMembers // member struct to reuse it in child classes
{
    int m_dwRef;
    IUnknown *m_pOuterUnknown;
    unsigned int m_xInnerUnknown;
    CCmdTarget::XDispatch m_xDispatch;
    int m_bResultExpected;
    CCmdTarget::XConnPtContainer m_xConnPtContainer;
    AFX_MODULE_STATE *m_pModuleState;
};
struct CCmdTarget
{
    CCmdTargetVtbl *vfptr;
    CCmdTargetMembers members;
};

Only this way, accessing virtual functions on child classes makes sense as their vftables are known. A sample hexrays decompilation shows that it does not make much sense with only the base vftables available:

Decompilation with child vftables:

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    int nReturnCode; // esi MAPDST
    CWinApp *pWinApp; // edi
    CWinThreadVtbl *pThread; // ebx

    nReturnCode = -1;
    pWinApp = (CWinApp *)CBumperApp::instance;
    if ( AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nShowCmd) )
    {
        pThread = &pWinApp->vftable->CWinThread;
        if ( pWinApp->vftable->InitApplication(pWinApp) )
        {
            if ( pThread->InitInstance((CWinThread *)pWinApp) )
            {
                nReturnCode = pThread->Run((CWinThread *)pWinApp);
            }
[...]

Decompilation without child vftables:

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    int nReturnCode; // esi MAPDST
    CWinApp *pWinApp; // edi
    CObjectVtbl *pThread; // ebx

    nReturnCode = -1;
    pWinApp = CBumperApp::instance;
    if ( AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nShowCmd) )
    {
        pThread = pWinApp->vfptr;
        if ( pWinApp->vfptr[5].GetRuntimeClass(pWinApp) ) // nonsense
        {
            if ( (pThread[2].Serialize)(pWinApp) ) // nonsense
            {
                nReturnCode = (pThread[2].AssertValid)(pWinApp); // nonsense
            }
[...]

Is there any way to get IDA to load the vftables for child classes from the PDB and create all required structs? Or is this not yet possible in IDA 7.0?

To my knowledge, the PDB should have that info. Is there a tool to look into the PDB file to see if it indeed has this info?

up vote 4 down vote accepted

As far as I know, everything you've said is 100% correct. I've experienced the same issues. In fact, I was working on this exact problem in the background when a friend sent me a link to this post.

There are actually three related issues here. First, prior to IDA 7.2, IDA's type system did not have a concept of a virtual function table per se. Meaning, although you can create a structure with a bunch of function pointers in it, IDA did not have a mechanism for changing the type of the VTable pointer in a derived object. "Derivation" meant simply that everything from the base class was included literally at offset 0. Fortunately, IDA 7.2 does understand the concept of VTables in inheritance. There is a bit of documentation on Hex-Rays' website. The end of that page summarizes the rules:

  • VFT pointer must have the "__vftable" name
  • VFT type must follow the "CLASSNAME_vtbl" pattern
  • For multiple inheritance use "CLASSNAME_%04X_vtbl" as the VFT type name where %04X corresponds to the offset of the vft pointer in CLASSNAME in the case if the offset is not zero

The second issue is that the PDB plugin currently does not take advantage of these recent changes to the type system. I.e., it does not generate VTable type/class VTable member names according to the rules above.

The third issue is that the PDB file format is, frankly, a nightmare. You can view the contents of a PDB with the "dia2dump" sample that comes with Visual Studio (in the "DIA SDK" directory), however, be forewarned that the output is often wrong, misleading, or missing important information. The handling of virtual function information is especially terrible. I haven't completed my investigations, but one thing I've learned so far is that only base classes have SymTagVTable symbols. I.e., for a class B that derives from another class A with a VTable, only A will have a SymTagVTable symbol, not B. This is even true if B defines additional virtual functions that were not defined in A. And currently, the PDB plugin only creates VTables if the class has a SymTagVTable symbol, hence the behavior that you are seeing. Instead, you have to iterate through the member functions of B and check whether they are virtual using the get_virtual method -- which, by the way, is something that dia2dump does not do. I'm still investigating how all of this works for multiple inheritance.

In short, the solution to your problem -- and my problem -- lies in modifying the IDA 7.2 PDB plugin (which comes with the SDK) to:

  1. Recover VTable information for derived classes, including the multiple inheritance scenario
  2. Name the VTable structure types in a suitable way based on the class names (and displacement offset in the case of multiple inheritance)
  3. Use the new features in typeinf.hpp to mark the VTable pointer with attribute TAFLD_VFTABLE, and the VTable structure itself with attribute TAUDT_VFTABLE.

Again, this is what I am also currently working on.

  • This is a great coverage of what is new in IDA 7.2 on CPP / OOP things. I already quickly read about it, but did not realize it already starts here. I'm not eligible to update to 7.2 at this time, but I'm looking forward to do so in the future, and try to at least name my structures and fields in a way to match their new pattern for now, maybe via a script. – Ray Koopa Dec 6 at 2:01

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.