A Close Look at a Visual Studio 2015 Generated V-Table (C++)

As I mentioned in an earlier blog post on analyzing disassembly, it's sometimes really helpful to know what exactly is happening at the machine level of C++ code I write. This includes knowing how the vtable works.

Here's a really simple class structure for the sake of investigation. We have an interface named Vehicle with a few pure virtual methods and then a couple of concrete implementations of the base class:

Vehicle UML

The code for the classes:

    class Vehicle
{
    public:
        virtual int GetWheelCount() = 0;
        virtual int GetSeatingCapacity() = 0;
        virtual int GetHorsepower() = 0;
};

class ToyotaCorolla : public Vehicle
{
    public:
        int GetWheelCount() override { return 4; }
        int GetSeatingCapacity() override { return 5; }
        int GetHorsepower() override { return 132; }
};

class Chevy3500HD : public Vehicle
{
    public:
        int GetWheelCount() override { return 6; }
        int GetSeatingCapacity() override { return 3; }
        int GetHorsepower() override { return 360; }
}; 

I then randomly instantiate one of the two concrete classes and display the attributes as follows:

    Vehicle* CreateNewVehicle()
{
    std::srand(static_cast(std::time(0)));
    if(std::rand() % 2)
        return new ToyotaCorolla();
    else
        return new Chevy3500HD();
}

int main()
{
    auto vehicle{CreateNewVehicle()};

    auto wheelCount{vehicle->GetWheelCount()};
    auto seatingCapacity{vehicle->GetSeatingCapacity()};
    auto horsepower{vehicle->GetHorsepower()};

    std::cout << "Wheel count: " << wheelCount << std::endl;
    std::cout << "Seating capacity: " << seatingCapacity << std::endl;
    std::cout << "Horsepower: " << horsepower << std::endl;

    return 0;
}

 
Visual Studio 2015 No Optimization

When compiling with Visual Studio 2015 in debug mode with optimizations disabled (/Od) we see the following assembly generated for the three virtual function calls:

VS2015DebugAssembly

When setting a breakpoint after vehicle is defined and executing, we can look at the contents of vehicle as shown in the image below. Note the __vfptr member of vehicle. This member variable is a pointer to the virtual function table (aka vtable) for vehicle.

VS2015DebugVehicleContents

In the image above, we see the address of vehicle's vtable is 0x7FF7504EBD30. We also see it has three entries - one for each of the virtual functions in our vehicle class. We see this as well when looking directly at the memory address locations and their contents:

VS2015DebugVtableMemory

Each of these point to an address of a method in the Chevy3500HD class. This shows us that the instantiated object "vehicle" points to is a "Chevy3500HD". Viewing the code at each vtable entry address, we see the following:

VS2015DebugMethodLocations

...these point to the memory addresses of the actual Chevy3500HD methods. For example, at memory location 0x7FF7504E6870 is the GetSeatingCapacity method of Chevy3500HD:

VS2015DebugGetSeatingCapacity

So, let's make sure we know exactly what's happening when a virtual method is called. First, recall the disassembly of the GetHorsepower virtual call:

    00007FF7504E27E7  mov         rax,qword ptr [vehicle]  
00007FF7504E27EB  mov         rax,qword ptr [rax]  
00007FF7504E27EE  mov         rcx,qword ptr [vehicle]  
00007FF7504E27F2  call        qword ptr [rax+10h]  
00007FF7504E27F5  mov         dword ptr [horsepower],eax 

The first two move ("mov") instructions load the address of vehicle's vtable into the RAX register. They do this by first loading vehicle's address (0x143B3A815F0) into the RAX register and then taking the address of it (0x7FF7504EBD30). The third move instruction loads the address of vehicle (0x143B3A815F0) into the RCX register. This is likely the "this" pointer being passed to the GetHorsepower function.

The call instruction finds the location of the function to call by dereferencing the memory address at RAX + 0x10. From the first two instructions, we know that RAX currently has a value of 0x7FF7504EBD30. Adding 0x10 to it results in 0x7FF7504EBD40. Recall from the explanation above, that the value contained at this memory address is 0x7FF7504E151E. Recall that this is the address of the Chevy3500HD::GetHorsepower method.

The result of the function is stored in the EAX register. The final move instruction moves the value from EAX into the horsepower variable. The other two virtual function calls work similarly.

Visual Studio 2015 Full Optimization

Compiling the same code in Visual Studio 2015 with full optimization (/Ox) doesn't change the overall process but does, unsurprisingly, optimize it. Below is the optimized assembly generated for the three virtual function calls. When comparing it to the debug version, we have two less instructions for the first line of code and one less instruction on each of the following two lines of code:

VS2015ReleaseAssembly

The optimized version relies more heavily on the use of registers. Investigating the contents of the vehicle variable after it's initialized we see the __vfptr member is not initialized at all:

VS2015ReleaseLocals

To understand why, let's first look at the assembly of the CreateNewVehicle function:

VS2015ReleaseCreateNewVehicle

Note how the appropriate vtable (aka "vftable", aka "virtual function table") memory address is loaded into the RAX register. Looking at the data at this address in memory we see the following:

VS2015ReleaseVTableMemory

The entry addresses in the vtable match the addresses of the methods for the concrete implementation of Vehicle (Chevy3500HD) as shown below:

VS2015ReleaseMethodLocations

Looking back at the original lines of assembly, it's clear how the proper methods are called:

VS2015ReleaseAssembly

Note also how the return values from each method are stored in registers. Each "Get" function populates the EAX register. We see this value gets saved off into another register before calling the next method. Looking at the registers after all three virtual methods are called, we see the following:

VS2015ReleaseRegisters

Date: January 17th, 2017 at 3:44pm
Author: Terence Darwen
Tags: C++, virtual function table, vtable, Visual Studio 2015, Sabbatical

Previous Next