I have 3 classes:
class Base { virtual foo() {...} }
class A : Base { virtual foo() override {...} }
class B : A { virtual bar() {...} /* introduce bar, but no foo override */ }
and I want to understand how this virtual function calls works under the hood.
If I call A a; a.foo() - the compiler generates Call instruction FF 10 which leads to near-jump instruction E9 01 02 03 04. The last 4 bytes - relative offset from vtable-record to real function. It is pretty obvious and clear. But if I call B b; b.foo() - the Call instruction leads to another kind of Jump FF 25. Visual Studio disassemblers displays it like this:
00007FFA2CAB1C85 FF 25 AD 76 21 01 jmp qword ptr [A::foo (07FFA2DCC9338h)]
I expected to get jump to 07FFA2DCC9338h address, but instead, it jumps to 07FFA4AD05885h(I suppose VS is lying with predicted address):
00007FFA4AD05885 E9 96 5D 01 00 jmp A::foo (07FFA4AD1B620h)
And this jump instuction (E9) forwards me to the real function.
But I do not understand how FF 25 works. Unfortunately, my assembly knowledge is very poor, I read some documentation, it says about some "gates", and I do not understand it.
Is it possible to predict the jump location for far-jump(FF) by having current instruction address(RIP) and jump-arguments?