0

After looking at some basic examples of how C++ classes are compiled by MSVC, I tried to apply the knowledge to a DLL I'm working on. While searching for a class to start with, I came across CRefCountable. Its constructors are straightforward and it appears to be a base class that almost every other class extends from.

Looking at one of the ctors, it seems like a CRefCountable is 8 bytes long, containing the vftable and a DWORD. I then got the impression that there are some "hidden" fields not initialized, as it is called with a pointer to 16 bytes of memory. Scrolling through the references to this ctor, I was surprised to see that it is also called with a pointer to 572, 500 and/or 20 bytes of memory, never with 8 bytes as I suspected.

How can the size of this class be variable and bigger than the ctor implies? Or could this be a compiler optimization, where memory for a child class or something else is allocated together with the memory for the object, saving a call to malloc/new?

I've decided not to include the decompilation or disassembly of the code in question, as it's either trivial or included in a way bigger function of a child class I haven't touched yet. If needed, I can provide some examples though.

EDIT

I completely forgot that the term "base class" was a thing, sorry about that. I'm very sure that this is a base class from the name and from looking around.

This is the constructor that I mentioned above.

// mangled name: ??0CRefCountable@@QAE@XZ
// demangled name: public: __thiscall CRefCountable::CRefCountable(void)

mov     eax, ecx
mov     dword ptr [eax], offset ??_7CRefCountable@@6B@ ; const CRefCountable::`vftable'
mov     dword ptr [eax+4], 0
retn

The values for the size of the object are in constructs such as this:

...
push    10h
call    new_or_malloc
mov     esi, eax
add     esp, 4
mov     [esp+18h+var_10], esi
test    esi, esi
mov     [esp+18h+var_4], 0
jz      short loc_1002C12E
mov     ecx, esi        ; this
call    ??0CRefCountable@@QAE@XZ ; CRefCountable::CRefCountable(void)
mov     dword ptr [esi], offset off_100443DC
mov     eax, [edi+8]
mov     [esi+8], eax
mov     [esi+0Ch], edi
jmp     short loc_1002C130
loc_1002C12E:
xor     esi, esi
loc_1002C130:
...

Ghidra's decompiler turns that into constructs like this (which I used to find these more quickly, as it's more concise).

  crc = (CRefCountable *)new_or_malloc(0x10);
  local_4 = 0;
  if (crc == (CRefCountable *)0x0) {
    crc = (CRefCountable *)0x0;
  }
  else {
    CRefCountable::CRefCountable(crc );
    crc ->vftable = &PTR_AddReference_100443dc;
    crc[1].vftable = *(undefined ***)(param_1 + 8);
    crc[1].field_0x04 = param_1;
  }

The value I suppose is the size of the object is the arg to new_or_malloc. This function wasn't recognized by IDA Free or ghidra, but it made sense in the context and the function does call HeapAlloc in the end.

Something that I overlooked yesterday evening was that param_1 (or edi in the disassembly) sometimes is the this pointer to a object. In this particular case it's a plugin manager class that doesn't seem to extend CRefCountable.

2
  • I think you better post some fragments, e.g. how exactly you determined the size of memory passed to the constructor.
    – Igor Skochinsky
    Dec 20, 2021 at 21:32
  • Name of that class sounds like name of base class. In case of inheritance instance of the derived class will contain members of the base class. Memory will be allocated before call to constructor to derived class, then from the inside of derived class constructor, call to base class constructor will be made. That's probably why you observed different sizes.
    – malikcjm
    Dec 20, 2021 at 22:22

1 Answer 1

1

It looks like CRefCountable is either a base class or the first member of the bigger class/structure being initialized. By itself, it is indeed only 8 bytes (vtable pointer and a data member, most likely the reference count).

1
  • Thank you for confirming what I have so far! This got me on track. After analyzing a test program, it seems like my initial assumption (malloc for entire child class, call base class constructor) was correct, as this produced a structure much like the one I saw in the DLL. Dec 21, 2021 at 17:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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