2

I'm referring to this question that was asked previously: Virtual functions call asm

I'm wondering how is it possible to know whether this ASM listing represents Virtual Functions calls? What's in the ASM mentioned in the question linked above that tells that this is Virtual Function call?

2
  • an indirect call like call eax, call[ecx+4] , call [reg+offset] calls indicate that
    – blabb
    Feb 14, 2021 at 11:24
  • Thanks for your reply! Wouldn't a normal class method call result in similar indirect register calls as well? As well as Struct functions and function pointers? Feb 14, 2021 at 13:06

2 Answers 2

2

an indirect call , use of ecx as the this pointer etc indicates it is a virtual function call

lets take the example you quoted in your query modify it a little and see the disassembly

contents of directory pre compilation

D:\virt>dir /b
virt.cpp

source from example duly modified a bit

D:\virt>type virt.cpp
01 #include <iostream>
02 class Animal {
03 public:
04     Animal( char *name,char *color) {_name =name; _color=color;};
05     virtual char *getname(){return _name;}
06     virtual char *makeSound() = 0;
07     virtual char *getColor() {return _color;}
08     virtual ~Animal() {};
09 private:
10     char *_name;
11     char *_color;
12 };
13 class Cat : public Animal{
14 public:
15     Cat(char *name,char* color) : Animal(name,color) {};
16     char *makeSound() { return "meow"; }
17     ~Cat() {};
18 };
19 Animal* animals[] = {
20     new Cat("fluffy","  red  "), new Cat("gruffy"," green "),
22     new Cat("mooody","magenta")
23 };
24 int main(){
25     for (int i = 0; i < 3; i++)
26     if (animals[i]){
27         std::cout << animals[i]->getname()      << " is ";
28         std::cout << animals[i]->getColor()     << " and sounds ";
29         std::cout << animals[i]->makeSound()    << std::endl;
30     }
31     for (int i = 0; i < 3; i++)
32     delete animals[i];
33     return 0;
34 }

compiled and executed using vs2017 community in x86 as x86

D:\virt>cl /Zi /W4 /analyze /EHsc /nologo /Od virt.cpp /link /release
virt.cpp

D:\virt>virt.exe
fluffy is   red   and sounds meow
gruffy is  green  and sounds meow
mooody is magenta and sounds meow

disassembly of line 26,27,28 which calls the functions notice the pointer arithmetic
and indirect calls highlighted with <<<<<<<<<<<<<<<

D:\virt>cdb -lines -c "g virt!main;uf .;q" virt.exe | grep -iE " 26| 27| 28"
virt!main+0x33 [d:\virt\virt.cpp @ 26]:
   26 00251223 6800022c00      push    offset virt!__xt_z+0x34 (002c0200)
   26 00251228 8b55fc          mov     edx,dword ptr [ebp-4]
   26 0025122b 8b049528892d00  mov     eax,dword ptr virt!animals (002d8928)[edx*4]
   26 00251232 8b4dfc          mov     ecx,dword ptr [ebp-4]
   26 00251235 8b10            mov     edx,dword ptr [eax]
   26 00251237 8b0c8d28892d00  mov     ecx,dword ptr virt!animals (002d8928)[ecx*4]
   26 0025123e 8b02            mov     eax,dword ptr [edx] <<<<<<<<<
   26 00251240 ffd0            call    eax <<<<<<<<<<<<<<<
   26 00251242 50              push    eax
   26 00251243 68688a2d00      push    offset virt!std::cout (002d8a68)
   26 00251248 e803020000      call    virt!std::operator<<<std::char_traits<char> 
   26 0025124d 83c408          add     esp,8
   26 00251250 50              push    eax
   26 00251251 e8fa010000      call    virt!std::operator<<<std::char_traits<char>
   26 00251256 83c408          add     esp,8
   27 00251259 6808022c00      push    offset virt!__xt_z+0x3c (002c0208)
   27 0025125e 8b4dfc          mov     ecx,dword ptr [ebp-4]
   27 00251261 8b148d28892d00  mov     edx,dword ptr virt!animals (002d8928)[ecx*4]
   27 00251268 8b45fc          mov     eax,dword ptr [ebp-4]
   27 0025126b 8b12            mov     edx,dword ptr [edx]
   27 0025126d 8b0c8528892d00  mov     ecx,dword ptr virt!animals (002d8928)[eax*4]
   27 00251274 8b4208          mov     eax,dword ptr [edx+8] <<<<<<<
   27 00251277 ffd0            call    eax <<<<<<<<<
   27 00251279 50              push    eax
   27 0025127a 68688a2d00      push    offset virt!std::cout (002d8a68)
   27 0025127f e8cc010000      call    virt!std::operator<<<std::char_traits<char>
   27 00251284 83c408          add     esp,8
   27 00251287 50              push    eax
   27 00251288 e8c3010000      call    virt!std::operator<<<std::char_traits<char>
   27 0025128d 83c408          add     esp,8
   28 00251290 68f01b2500      push    offset virt!std::endl<char,std::char_traits<char>
   28 00251295 8b4dfc          mov     ecx,dword ptr [ebp-4]
   28 00251298 8b148d28892d00  mov     edx,dword ptr virt!animals (002d8928)[ecx*4]
   28 0025129f 8b45fc          mov     eax,dword ptr [ebp-4]
   28 002512a2 8b12            mov     edx,dword ptr [edx]
   28 002512a4 8b0c8528892d00  mov     ecx,dword ptr virt!animals (002d8928)[eax*4]
   28 002512ab 8b4204          mov     eax,dword ptr [edx+4] <<<<<<<<<
   28 002512ae ffd0            call    eax <<<<<<<<<
   28 002512b0 50              push    eax
   28 002512b1 68688a2d00      push    offset virt!std::cout (002d8a68)
   28 002512b6 e895010000      call    virt!std::operator<<<std::char_traits<char>
   28 002512bb 83c408          add     esp,8
   28 002512be 8bc8            mov     ecx,eax
   28 002512c0 e8db1a0000      call    ::operator<< (00252da0)

edit :

an array of function pointers will generate indirect calls like call qword ptr [reg + offset] ,call qword ptr [mem+offset] etc

my answer is more of practical nature if you are looking for a theoretical way to distinguish the nature indirect calls i cant be of much help there shown below is source and disassembly of an array of function pointers

source compiled and executed

:\>type funarr.cpp
#include <stdio.h>
#include <stdlib.h>
int sum(int a, int b){ return a + b;}
int sub(int a, int b){ return a - b;}
int mul(int a, int b){ return a * b;}
int (*p[3]) (int x, int y) {sum,sub,mul};

int main(int argc,char *argv[])
{
        if(argc == 3)
        {
                int i = atoi(argv[1]);
                int j = atoi(argv[2]);
                int k = (*p[0]) (i, j);
                int l = (*p[1]) (i, j);
                int m = (*p[2]) (i, j);
                printf("%d %d %d", k,l,m );
        }
}
:\>cl /Zi /W4 /analyze /O1 /nologo funarr.cpp /link /release
funarr.cpp

:\>funarr.exe 2 3
5 -1 6

disassembly of main

:\>cdb -c "uf funarr!main;q" funarr.exe | awk "/Reading/,/quit/"
0:000> cdb: Reading initial command 'uf funarr!main;q'                                                                  funarr!main:                                                                                                            00007ff7`95f2106c 48895c2408      mov     qword ptr [rsp+8],rbx
00007ff7`95f21071 48896c2410      mov     qword ptr [rsp+10h],rbp
00007ff7`95f21076 4889742418      mov     qword ptr [rsp+18h],rsi
00007ff7`95f2107b 57              push    rdi
00007ff7`95f2107c 4883ec20        sub     rsp,20h
00007ff7`95f21080 488bda          mov     rbx,rdx
00007ff7`95f21083 83f903          cmp     ecx,3
00007ff7`95f21086 754c            jne     funarr!main+0x68 (00007ff7`95f210d4)

funarr!main+0x1c:
00007ff7`95f21088 488b4a08        mov     rcx,qword ptr [rdx+8]
00007ff7`95f2108c e803810200      call    funarr!atoi (00007ff7`95f49194)
00007ff7`95f21091 488b4b10        mov     rcx,qword ptr [rbx+10h]
00007ff7`95f21095 8be8            mov     ebp,eax
00007ff7`95f21097 e8f8800200      call    funarr!atoi (00007ff7`95f49194)
00007ff7`95f2109c 8bd0            mov     edx,eax
00007ff7`95f2109e 8bcd            mov     ecx,ebp
00007ff7`95f210a0 8bf8            mov     edi,eax
00007ff7`95f210a2 ff1558cf0500    call    qword ptr [funarr!p (00007ff7`95f7e000)]
00007ff7`95f210a8 8bd7            mov     edx,edi
00007ff7`95f210aa 8bcd            mov     ecx,ebp
00007ff7`95f210ac 8bf0            mov     esi,eax
00007ff7`95f210ae ff1554cf0500    call    qword ptr [funarr!p+0x8 (00007ff7`95f7e008)]
00007ff7`95f210b4 8bd7            mov     edx,edi
00007ff7`95f210b6 8bcd            mov     ecx,ebp
00007ff7`95f210b8 8bd8            mov     ebx,eax
00007ff7`95f210ba ff1550cf0500    call    qword ptr [funarr!p+0x10 (00007ff7`95f7e010)]
00007ff7`95f210c0 448bc3          mov     r8d,ebx
00007ff7`95f210c3 488d0d76c20400  lea     rcx,[funarr!`string' (00007ff7`95f6d340)]
00007ff7`95f210ca 448bc8          mov     r9d,eax
00007ff7`95f210cd 8bd6            mov     edx,esi
00007ff7`95f210cf e818000000      call    funarr!printf (00007ff7`95f210ec)

funarr!main+0x68:
00007ff7`95f210d4 488b5c2430      mov     rbx,qword ptr [rsp+30h]
00007ff7`95f210d9 33c0            xor     eax,eax
00007ff7`95f210db 488b6c2438      mov     rbp,qword ptr [rsp+38h]
00007ff7`95f210e0 488b742440      mov     rsi,qword ptr [rsp+40h]
00007ff7`95f210e5 4883c420        add     rsp,20h
00007ff7`95f210e9 5f              pop     rdi
00007ff7`95f210ea c3              ret
quit:
2
  • Thank you for your answer and comments. However, that still didn't answer my specific answer (sorry again if I'm missing something obvious, so please bear with me), isn't that disassembly and the access of indirect function calls through registers could as well mean class/structure methods or even array of function pointers? Feb 15, 2021 at 12:03
  • Your edit helped a lot clarifying the difference between the different assembly results! Thanks a lot! :) Feb 16, 2021 at 6:17
1

There’s no 100% sure way to distinguish a virtual call from a function pointer call but there are some strong hints.

  1. The virtual function table (vftable) is usually at the very start of the object so assuming the object’s address is stored in objectreg, you should see something like

    mov vftreg, [objectreg]
    
  2. The object address (the this pointer) is passed to the virtual method, commonly as the first argument or in a separate register, according to the ABI in use. In Microsoft x86 the ecx register is used for this purpose so the common pattern is:

    mov ecx, objectreg
    
  3. The call is performed using a slot in the vftable. It can be done either directly using a displacement from the register holding the vftable:

    call [vftreg+slotoff]
    

Or via an intermediate register:

  mov callreg, [vftreg+slotoff]
  call callreg

slotoff should be a multiple of a pointer size and may be zero.

In case the method has arguments, they will be loaded as well (usually the this argument is initialized last, just before the call).

You may also find informative my old article on the topic.

1
  • That even clarified the issue more, thanks for taking the time to answer, as well as linking your article! Feb 18, 2021 at 8:15

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.