(Visual) C++ 中动态创建函数的调用约定

Posted

技术标签:

【中文标题】(Visual) C++ 中动态创建函数的调用约定【英文标题】:Calling convention for dynamically created function in (Visual) C++ 【发布时间】:2011-11-26 16:09:44 【问题描述】:

我使用以下类型在运行时创建新函数:

typedef int (*pfunc)(int);

union funcptr 
  pfunc x;
  byte* y;
;

这使我能够在y 中编写指令,然后像这样调用函数:

byte* p = (byte*)VirtualAllocEx(GetCurrentProcess(), 0, 1<<16, MEM_COMMIT, PAGE_EXECUTE_READWRITE );

// Write some instructions to p

funcptr func;
func.y = p;

int ret = func.x(arg1); // Call the generated function

了解 C++ 如何准备参数(调用约定)至关重要,因此我查看了项目属性 (Visual C++),发现它使用了__cdecl。它应该根据:http://msdn.microsoft.com/en-us/library/aa271989(v=vs.60).aspx 和http://en.wikipedia.org/wiki/X86_calling_conventions#cdecl 将参数放在堆栈上,但是当我查看生成的程序集时,参数被移动到 EAX 寄存器。

我想绝对确定论点是如何准备的。那么我是否忽略了 cdecl 的某些内容,或者 Visual C++ 是否优化了调用,如果是,我如何确保它不会发生?

最好的问候,Lasse Espeholt

【问题讨论】:

我很好奇你用这种技术解决了什么问题。 我不是MSVC,但你不能在函数指针类型中嵌入调用约定吗? @KerrekSB:比起联合技巧,我更担心使用一些动态填充的二进制代码运行函数。前者更加“未定义”。 ;) @JohnZwinck 和其他人。我正在开发一个迷你 JIT 编译器,有没有更好的方法呢? @lasseespeholt:是的,当然:声明函数指针:pfunc f;,然后获取指向其内存的字符指针:char * pf = reinterpret_cast&lt;char*&gt;(&amp;f);,然后用 that 填充正确的值。 (将指针转换为 char 指针显然是 not 类型的双关语。)这并不意味着函数调用本身就有意义;只是你的建设是宽恕的。 【参考方案1】:

EAX 寄存器是used for the return value of the function。您在 cmets 中声明您正在使用 /Gd 进行编译,因此该函数将使用 __cdecl。尽管如此,在我看来,用明确的__cdecl 标记函数类型pfunc 的声明是有意义的,这样就不会有混淆和不匹配的余地。

当然,没有什么可以阻止您使用编译器支持的其他调用约定之一。最重要的一点是,无论你选择哪种调用约定,都应该明确指定函数指针的调用约定,因为编译器只负责接口的一半。

【讨论】:

根据我正在使用的项目属性__cdecl (/Gd)? @lasseespeholt,无论如何都试试__cdecl(例如typedef int (__cdecl *pfunc)(int);),让我们知道发生了什么。 对不起,我搞混了。 EAX 用于函数的返回值。答案已更新,现在是正确的(我相信!!) @avakar 成功了。但同样,项目属性说它是cdecl。我会尝试创建一个新项目。谢谢大家:) @lasseespeholt 请查看我的更新答案。我的原始版本让你感到困惑。对不起。所有调用约定都使用 EAX 寄存器作为返回值。【参考方案2】:

至少在Linux上,你可能想使用libffi(外部函数接口),甚至已经移植到其他系统(包括Windows)。

如果你想在运行时生成机器码,可以考虑使用GNU lightning、DotGnu 的libjit、LLVM。 LuaJit 的dynasm 等你也可以生成C 代码到foo.c,通过fork 一个gcc -fPIC -shared foo.c -o foo.so 命令和dlopen("./foo.so", RTLD_GLOBAL) 来编译它(和Windows 有同等的能力)

【讨论】:

GNU 闪电看起来很有希望。但是对于这个宠物项目,我想自己做这一切——包括 x86 汇编。但我想我可以看看源代码,看看它们是如何生成和调用函数的。 libjit 目前由Aleksey Demakov 维护;您可以从git.savannah.gnu.org 获取最新版本; libffi 的替代方案是dyncall,它支持 MSVC,而 Windows 版本的 libffi 需要使用 MinGW 或 Cygwin 构建

以上是关于(Visual) C++ 中动态创建函数的调用约定的主要内容,如果未能解决你的问题,请参考以下文章

带你玩转Visual Studio——调用约定与(动态)库

C++ Visual Studio 运行时错误

C++函数调用方式 stdcall pascal cdecl 总结

在 C++ 中调用 Visual Basic DLL

C++ lambda 函数的默认调用约定是啥?

__stdcall的名字修饰约定