So the latest bug I ran into at work involved a missing vtable in Clang. No, I’m not able to produce a reduced case, so evasion is the only route.

In the meantime, let’s document how to look at vtables.

So, the layout of vtables in memory is simple: the first 8 bytes of every class with virtual methods is a pointer to a table of function pointers. There’s exactly one vtable for every class, and if the information for generating this is missing, you get the familiar linker error.

class EliminateConditionals
{
  class Predicate
  {
    virtual bool shouldEliminate() const { return true; }
    virtual ~Predicate() {}
    static void queryPredicate() {}
  };
};

An object of the EliminateConditionals class will be 1 byte long (any two 0 byte objects will be equal to each other, hence 1 byte). An object of the class Predicate will be 8 bytes long (the vtable entry), and the vtable will contain one 8 byte pointer each to shouldEliminate() and ~Predicate(). The reason they ask you to make destructors virtual is simple: if you’re using a derived object cast as a base object, and then destructing it doesn’t call the destructors on the members unique to the derived class.

(lldb) im loo -r -v -s "vtable for EliminateConditionals
2 symbols match the regular expression 'vtable for EliminateConditionals' \
in /home/artagnon/tmp/class-size:
        Address: class-size[0x0000000100001160] (class-size.__DATA.__data + 16)
        Summary: vtable for EliminateConditionals::Predicate
         Module: file = "/home/artagnon/tmp/class-size", arch = "x86_64"
         Symbol: id = {0x00000048}, range =
         [0x0000000100001160-0x0000000100001188), \
          ^~~~~~~~~~~~~~~~~~

         name="vtable for EliminateConditionals::Predicate", \
         mangled="_ZTVN2CG9transform21EliminateConditionals9PredicateE"
        Address: class-size[0x0000000100001110] (class-size.__DATA.__const + 0)
        Summary: class-size`vtable for EliminateConditionals
         Module: file = "/home/artagnon/tmp/class-size", arch = "x86_64"
         Symbol: id = {0x0000005e}, range =
         [0x0000000100001110-0x0000000100001130), \
          ^~~~~~~~~~~~~~~~~~
          We can `mem r` this

         name="vtable for EliminateConditionals", \
         mangled="_ZTVN2CG9transform21EliminateConditionalsE"

That’s an image lookup, and it works as long as everything is loaded; nothing needs to run.