x86代码移植到x64下,如何访问虚表和虚函数指针,使用地址调用虚函数
来源:互联网 发布:msp430单片机与stm32 编辑:程序博客网 时间:2024/05/21 10:47
最近有一个老项目需要从x86下移植到x64环境下, 遇到了如下一个问题需要解决.
其中一个RPC模块通过client向server直接发送callable对象的指针的地址和其内部虚函数成员的index来调用服务(实际上, rpc里直接发送可执行代码地址非常tricky, 需要考虑各种安全问题, 这部分不在本文讨论范围内). 简化后的核心代码如下:
#include <iostream>#include <stdlib.h>using namespace std;class service_base{public: service_base() :_m(0) {} int _m; virtual void fun1() { cout << _m << "service_base::fun1()" << '\n'; } virtual void fun2() { cout << _m << "service_base::fun2()" << '\n'; }};class service_derived : public service_base{public: service_derived() :_m(1) {} int _m; virtual void fun1() { cout << _m << "service_derived::fun1()" << '\n'; } virtual void fun2() { cout << _m << "service_derived::fun2()" << '\n'; }};template <typename T>void call(T *_ptr, size_t i){ typedef void(*FunPtr)(); size_t *vptr = *reinterpret_cast<size_t**>(_ptr); FunPtr callable = reinterpret_cast<FunPtr>(vptr[i]); __asm { mov ecx, DWORD PTR _ptr } callable();}service_base* base = new service_base();service_derived* derv = new service_derived();int main(){ service_base* base = new service_base(); service_derived* derv = new service_derived(); call(base, 0); call(base, 1); call(derv, 0); call(derv, 1); system("pause");}
以上代码工作在x86环境下(windows).
对于包含(及继承了)虚函数成员的类对象来说,其开头总是保存着一个4个字节的指针指向虚函数表,虚函数表又是一个简单的线性表其每个元素又都是一个4字节的指针指向对象自己实现的虚函数. C++程序员应该对此很熟悉. 除去其中的inline asm, 其余部分并不难理解.
call函数直接取虚表地址并且获取对应index的虚函数指针, 尝试直接调用.
这里不能忘记this指针, 调用成员函数时this指针是必须被指定的, 在win32 abi里这个this指针必须放在ECX寄存器中. 这个就是inline asm做的工作.
执行上述代码,输出
0service_base::fun1()0service_base::fun2()1service_derived::fun1()1service_derived::fun2()
一切很美好, 但是当移植代码到x64的时候, 麻烦来了.
Visual Studio报告
error C4235: nonstandard extension used: '__asm' keyword not supported on this architecture
google后发现msvc x64下已经不支持inline asm.
使用替代的办法 ,下载masm for x64.
将inline asm抽出来单独写一个asm文件, 把 ECX改为RCX.
用masm编译成obj链接到C++代码中.
测试执行, 代码输出
```11276778service_base::fun1()11276778service_base::fun2()0service_derived::fun1()0service_derived::fun2()
明显出问题, this指针不正确.
反汇编看到RCX的确被设置为this. 那么应该是win64 abi改变了this指针的寄存器,
再写一个常规的C++对象调用的测试程序, 反汇编发现 this现在被放到了RDI中.
再次尝试把this放到RDI中, 执行代码, 运行正常.
为了多了解一下x64下abi的变化, 搜了一些资料
https://en.wikipedia.org/wiki/X86_calling_conventions
https://msdn.microsoft.com/en-us/library/9b372w95.aspx
https://software.intel.com/sites/default/files/m/d/4/1/d/8/Introduction_to_x64_Assembly.pdf
大概浏览一下后发现一个很有意思的事情, 在system v 64 abi里, RDI也是常规函数第一个int或者pointer参数. 那么上面的代码只需要一个小改动就可以省去必须使用汇编代码的麻烦了.
修改call函数如下:
void call(T *_ptr, size_t i){ typedef void(*FunPtr)(T *_ptr); size_t *vptr = *reinterpret_cast<size_t**>(_ptr); FunPtr callable = reinterpret_cast<FunPtr>(vptr[i]); /* __asm { mov ecx, DWORD PTR _ptr } */ callable(_ptr);}
把this指针作为call的第一个参数传入, 编译器会自动把this放入RDI.
编译到x64,运行测试, 结果正确.
- x86代码移植到x64下,如何访问虚表和虚函数指针,使用地址调用虚函数
- x86 和 x64 汇编调用C 函数参数传递规则(GCC)
- x86 和 x64 汇编调用C 函数参数传递规则(GCC)
- C++中访问虚函数表获取虚函数地址
- X86&&X64 汇编学习——调用汇编函数
- 地址指针调用函数1
- 地址指针调用函数2
- 如何通过函数指针调用函数(实现代码)
- 通过函数指针使用虚函数表中的函数
- C++利用地址访问虚函数
- Q13.3 关于虚表指针、虚函数表、虚函数表中虚函数地址
- C++虚函数:虚指针、虚表、虚函数入口地址
- 虚函数调用 汇编代码
- x86下的C函数调用惯例
- x86下的C函数调用惯例
- x86下的C函数调用惯例
- C++虚函数表学习心得之由类实例地址到虚函数表再到虚函数地址中各种地址解析
- 构造函数内如何调用虚函数
- 反编译apk linux mac 反编译自动化脚本
- 云安全之Neutron 是如何实现负载均衡器虚拟化的 [LBaaS V1 in Juno]
- 约束优化问题(拉格朗日乘子法求解)
- Linux/Windows/Mac OS中的回车与换行
- Android-sqlite的命令
- x86代码移植到x64下,如何访问虚表和虚函数指针,使用地址调用虚函数
- Android SDK Manager国内无法更新的解决方案
- 华为OJ训练之0034-170112-密码强度等级
- mvn不是内部或外部命令,也不是可运行的程序或批处理文件
- 工具打包
- android手机返回桌面后,点击app图标,app重启
- mycat ER关系 取模分片试验
- ios
- CF Round #413( Div.1/2) Field expansion