我指的是之前问过的这个问题: 虚函数调用 asm
我想知道如何才能知道此 ASM 列表是否代表 Virtual Functions 调用?上面链接的问题中提到的 ASM 中有什么内容表明这是虚拟函数调用?
我指的是之前问过的这个问题: 虚函数调用 asm
我想知道如何才能知道此 ASM 列表是否代表 Virtual Functions 调用?上面链接的问题中提到的 ASM 中有什么内容表明这是虚拟函数调用?
间接调用,使用 ecx 作为 this 指针等表明它是一个虚函数调用
让我们以您在查询中引用的示例稍微修改一下并查看反汇编
目录预编译内容
D:\virt>dir /b
virt.cpp
来自示例的源代码经过适当修改
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 }
在 x86 中使用 vs2017 社区编译和执行为 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
调用函数的第 26、27、28 行的反汇编注意指针算术
和间接调用,用 <<<<<<<<<<<<<<<
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)
编辑 :
函数指针数组将生成间接调用,如 call qword ptr [reg + offset] 、call qword ptr [mem+offset] 等
如果您正在寻找一种理论方法来区分间接调用的性质,我的回答更具有实用性,下面显示的是函数指针数组的源代码和反汇编
源代码编译和执行
:\>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
主要拆解
:\>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:
没有 100% 确定的方法来区分虚拟调用和函数指针调用,但有一些强有力的提示。
虚函数表 (vftable)通常位于对象的最开始处,因此假设对象的地址存储在 中objectreg,您应该会看到类似
mov vftreg, [objectreg]
根据使用的 ABI,对象地址(this指针)被传递给虚方法,通常作为第一个参数或在单独的寄存器中。在 Microsoft x86 中,ecx寄存器用于此目的,因此常见模式是:
mov ecx, objectreg
调用是使用 vftable 中的一个槽来执行的。它可以直接使用来自保存 vftable 的寄存器的位移来完成:
call [vftreg+slotoff]
或者通过一个中间寄存器:
mov callreg, [vftreg+slotoff]
call callreg
slotoff 应该是指针大小的倍数,可能为零。
如果方法有参数,它们也会被加载(通常this参数最后初始化,就在调用之前)。