【问题标题】:x86-64 assembler for polymorphic call用于多态调用的 x86-64 汇编器
【发布时间】:2014-03-07 02:45:33
【问题描述】:

我有 C++ 代码:

int main(){
    M* m;

    O* o = new IO();
    H* h = new H("A");

    if(__rdtsc() % 5 == 0){
        m = new Y(o, h);
    }
    else{
        m = new Z(o, h);
    }

    m->my_virtual();

    return 1;
}

虚拟调用由这个 asm 表示:

mov         rax,qword ptr [x]  
mov         rax,qword ptr [rax]  
mov         rcx,qword ptr [x]  
call        qword ptr [rax]

这比我对 vtable 方法调用的预期多一行。所有四个 ASM 行都特定于多态调用吗?

以上四行如何伪读?

这是完整的 ASM 和 C++(虚拟调用在最后进行):

int main(){
 add         byte ptr [rax-33333334h],bh  
 rep stos    dword ptr [rdi]  
 mov         qword ptr [rsp+0A8h],0FFFFFFFFFFFFFFFEh  
    M* x;

    o* o = new IO();
 mov         ecx,70h  
 call        operator new (013F6B7A70h) 
 mov         qword ptr [rsp+40h],rax  
 cmp         qword ptr [rsp+40h],0  
 je          main+4Fh (013F69687Fh)  
 mov         rcx,qword ptr [rsp+40h]  
 call        IO::IO (013F6814F6h)  
 mov         qword ptr [rsp+0B0h],rax  
 jmp         main+5Bh (013F69688Bh)  
 mov         qword ptr [rsp+0B0h],0  
 mov         rax,qword ptr [rsp+0B0h]  
 mov         qword ptr [rsp+38h],rax  
 mov         rax,qword ptr [rsp+38h]  
 mov         qword ptr [o],rax  
    H* h = new H("A");
 mov         ecx,150h  
 call        operator new (013F6B7A70h)  
 mov         qword ptr [rsp+50h],rax  
 cmp         qword ptr [rsp+50h],0  
 je          main+0CEh (013F6968FEh)  
 lea         rax,[rsp+58h]  
 mov         qword ptr [rsp+80h],rax  
 lea         rdx,[ec_table+11Ch (013F7C073Ch)]  
 mov         rcx,qword ptr [rsp+80h]  
 call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> > (013F681104h)  
 mov         qword ptr [rsp+0B8h],rax  
 mov         rdx,qword ptr [rsp+0B8h]  
 mov         rcx,qword ptr [rsp+50h]  
 call        H::H (013F6826A3h)  
 mov         qword ptr [rsp+0C0h],rax  
 jmp         main+0DAh (013F69690Ah)  
 mov         qword ptr [rsp+0C0h],0  
 mov         rax,qword ptr [rsp+0C0h]  
 mov         qword ptr [rsp+48h],rax  
 mov         rax,qword ptr [rsp+48h]  
 mov         qword ptr [h],rax  

    if(__rdtsc() % 5 == 0){
 rdtsc  
 shl         rdx,20h  
 or          rax,rdx  
 xor         edx,edx  
 mov         ecx,5  
 div         rax,rcx  
 mov         rax,rdx  
 test        rax,rax  
 jne         main+175h (013F6969A5h)  
        x = new Y(o, h);
 mov         ecx,18h  
 call        operator new (013F6B7A70h)  
 mov         qword ptr [rsp+90h],rax  
 cmp         qword ptr [rsp+90h],0  
 je          main+14Ah (013F69697Ah)  
 mov         r8,qword ptr [h]  
 mov         rdx,qword ptr [o]  
 mov         rcx,qword ptr [rsp+90h]  
 call        Y::Y (013F681B4Fh)  
 mov         qword ptr [rsp+0C8h],rax  
 jmp         main+156h (013F696986h)  
 mov         qword ptr [rsp+0C8h],0  
 mov         rax,qword ptr [rsp+0C8h]  
 mov         qword ptr [rsp+88h],rax  
 mov         rax,qword ptr [rsp+88h]  
 mov         qword ptr [x],rax  
    }
    else{
 jmp         main+1DCh (013F696A0Ch)  
        x = new Z(o, h);
 mov         ecx,18h  
 call        operator new (013F6B7A70h)  
 mov         qword ptr [rsp+0A0h],rax  
 cmp         qword ptr [rsp+0A0h],0  
 je          main+1B3h (013F6969E3h)  
 mov         r8,qword ptr [h]  
 mov         rdx,qword ptr [o]  
 mov         rcx,qword ptr [rsp+0A0h]  
 call        Z::Z (013F68160Eh)  
 mov         qword ptr [rsp+0D0h],rax  
 jmp         main+1BFh (013F6969EFh)  
 mov         qword ptr [rsp+0D0h],0  
 mov         rax,qword ptr [rsp+0D0h]  
 mov         qword ptr [rsp+98h],rax  
 mov         rax,qword ptr [rsp+98h]  
 mov         qword ptr [x],rax  
    }

    x->my_virtual();
 mov         rax,qword ptr [x]  
 mov         rax,qword ptr [rax]  
 mov         rcx,qword ptr [x]  
 call        qword ptr [rax]  

    return 1;
 mov         eax,1  
}

【问题讨论】:

  • 第 3 行更新 rcx,而不是 rax
  • @xxbbcc 对不起 :) 太晚了

标签: c++ assembly x86 x86-64


【解决方案1】:

您可能正在查看未优化的代码:

mov         rax,qword ptr [x]       ; load rax with object pointer
mov         rax,qword ptr [rax]     ; load rax with the vtable pointer
mov         rcx,qword ptr [x]       ; load rcx with the object pointer (the 'this' pointer)
call        qword ptr [rax]         ; call through the vtable slot for the virtual function

【讨论】:

  • +1。我想知道rcx 的作用——我忘了​​this 指针。
  • 只是想知道您为什么说它的代码未优化?多态调用的优化版本是什么?
  • @user997112:例如,我希望优化器能够识别出rax 已经拥有指向x 的指针,因此它不需要重新加载它。优化后的序列可能类似于mov rcx, rax; mov rax,[rax], call qword ptr [rax]。但是,如果您打开优化器,您可能会得到一些不同的东西,因为导致虚拟调用的代码生成可能会发生很大变化,并且寄存器使用选择可能会有所不同(尽管调用约定需要rcx在进行虚拟调用时获得对象指针)。
  • @MichaelBurr 就像 Ben Voight 在他的回答的第一部分一样?
  • @user997112:我想的更像是 Ben 的第二个示例,没有第一行,因为优化器会知道 rax 已经包含来自执行 new 操作的代码序列之一的对象指针.然而,由于优化器也会修改这些序列,虚拟调用如何结束的细节也会改变。所以谁知道呢——它实际上可能会像 Ben 的第一个例子一样结束。
【解决方案2】:
mov         rax,qword ptr [x]  

获取x指向的地址

mov         rax,qword ptr [rax]  

获取 x 类的 vtable 地址(使用我们刚刚计算出的 rax)。放在rax里

mov         rcx,qword ptr [x]  

获取指针x并放入rcx,这样就可以在被调用函数中作为“this”指针使用。

call        qword ptr [rax]

使用我们之前找到的 vtable 中的地址调用函数(没有偏移,因为它是第一个虚函数)。

肯定有更短的方法可以做到这一点,如果您打开优化,编译器可能会使用这些方法(例如,只获取 [x] 一次)。

更新了来自 Ben Voigt 的更多信息

【讨论】:

  • 第二阶段我不太明白。该命令究竟是如何访问 vtable 的?
  • @user997112:显然 vtable 指针存储在特定布局中的偏移量为零处。
  • @TheDark:我觉得第二行得到的是vtable的地址,直到调用指令才得到虚函数的地址……
  • 我认为你是对的 Ben - 它必须获取 vtable 的地址,而不是直接从 [x] 的地址读取 vtable。否则每个对象都会有一个 vTable,而不是指向一个公共的 vtable。我会更新我的答案。
  • 是表中的第一个虚函数,还是表中的第一个条目?我只是想知道类型信息是否在虚拟函数之前/之后的表中?我正在使用 MSVC
【解决方案3】:

在伪代码中:

(*(*m->__vtbl)[0])(m)

优化版(rcx可以用于索引吗?):

mov         rcx,qword ptr [x]       ; load rcx with object pointer
mov         rax,qword ptr [rcx]     ; load rax with the vtable pointer
call        qword ptr [rax]         ; call through the vtable slot for the virtual function

mov         rax,qword ptr [x]       ; load rax with object pointer
mov         rcx,rax                 ; copy object pointer to rcx (the 'this' pointer)
mov         rax,qword ptr [rax]     ; load rax with the vtable pointer
call        qword ptr [rax]         ; call through the vtable slot for the virtual function

【讨论】:

  • 和 (*(*m->__vtbl)[0])(m) 实际上是 void 参数的函数指针,对吧?只是指针来自通过取消引用第二个指针计算的偏移量?
  • 不,不是 void 参数。成员函数需要一个 this 参数。
  • 忘记了!是 1 个参数。
  • 那么在 asm 中 - 'this' 参数的加载是如何发生的?我在 asm cmets 中看不到任何涉及参数传递的内容。
  • 正如其他人所说,rcx 寄存器包含this 参数。
猜你喜欢
  • 2022-10-15
  • 2012-02-25
  • 1970-01-01
  • 2018-10-13
  • 2018-08-11
  • 1970-01-01
  • 2014-04-30
  • 2012-11-06
相关资源
最近更新 更多