如何使用程序集来获得返回float的__stdcall函数的结果

我有一个汇编程序,以通用的方式调用已知的使用stdcall约定并返回一个float的函数。 编组框架使用此函数将stdcall函数暴露给脚本语言。

背景

下面是使用GNU内联程序集编译MinGW 4.3,Win32的函数:

 inline uint64_t stdcall_invoke_return_float(int args_size_bytes, const char * args_ptr, void * func_ptr) { uint64_t result; assert( 0 == args_size_bytes % 4 || !"argument size must be a multiple of 4 bytes"); #if defined(__GNUC__) asm ( /* INPUT PARAMS: %0 is the address where top of FP stack to be stored * %1 is the number of BYTES to push onto the stack, */ /* and during the copy loop it is the address of */ /* the next word to push */ /* %2 is the base address of the array */ /* %3 is the address of the function to call */ "testl %1, %1 # If zero argument bytes given, skip \n\t" "je 2f # right to the function call. \n\t" "addl %2, %1\n" "1:\n\t" "subl $4, %1 # Push arguments onto the stack in \n\t" "pushl (%1) # reverse order. Keep looping while \n\t" "cmp %2, %1 # addr to push (%1) > base addr (%2) \n\t" "jg 1b # Callee cleans up b/c __stdcall. \n" "2:\n\t" "call * %3 # Callee will leave result in ST0 \n\t" "fsts %0 # Copy 32-bit float from ST0->result" : "=m" (result) : "r" (args_size_bytes), "r" (args_ptr), "mr" (func_ptr) : "%eax", "%edx", "%ecx" /* eax, ecx, edx are caller-save */, "cc" ); #else #pragma error "Replacement for inline assembler required" #endif return result; } 

这只是一个简单的写作testing用例:

 template<typename FuncPtr, typename ArgType> float float_invoke(FuncPtr f, int nargs, ArgType * args) { uint64_t result = stdcall_invoke_return_float( nargs * sizeof(ArgType), reinterpret_cast<const char *>(args), reinterpret_cast<void *>(f) ); return *reinterpret_cast<float *>(&result); } 

现在我有一些testing用例调用这个函数:

 __stdcall float TestReturn1_0Float() { return 1.0f; } __stdcall float TestFloat(float a) { return a; } __stdcall float TestSum2Floats(float a, float b) { return a + b; } static const float args[2] = { 10.0f, -1.0f }; assert_equals(1.0f, float_invoke(TestReturn1_0Float, 0, args)); // test 1 assert_equals(10.0f, float_invoke(TestFloat, 1, args)); // test 2 assert_equals(-1.0f, float_invoke(TestFloat, 1, args + 1)); // test 3 assert_equals(9.0f, float_invoke(TestSumTwoFloats, 2, args)); // test 4 

问题

随机地,testing3给我垃圾输出,而不是返回-1.0。

我想知道我是不是

  • call指令之前没有保留某些状态?
  • fsts指令搞乱了一些状态?
  • 从根本上误解如何从返回floatstdcall函数获取float值?

所有帮助非常感谢。

缺乏一台Windows机器,我不能完全测试这个; 在Linux上,以下代码获得了float函数的返回码:

 extern float something(int); #include #include int main(int argc, char **argv) { int val = atoi(argv[1]); float ret; asm("pushl %1\n\t" "call * %2\n\t" "addl $4, %%esp" : "=t"(ret) : "r"(val), "r"(something) : "%eax", "%ecx", "%edx", "memory", "cc"); printf("something(%d) == %f\n", val, ret); return 0; } 

关键是使用"=t"(ret)约束 – 获取浮点堆栈顶部 ,参见机器约束 (来自gcc手册)。 如果Windows stdcall也在ST(0)返回float结果,那么应该可以工作,不需要fld / fst因为如果需要的话编译器可以为你做这些。

在内联程序集中调用函数时,还需要指定memorycc clobbers。

您正在允许函数指针的内存引用,GCC可能会在不正确的假定内联程序集不改变它的情况下构造一个相对于堆栈指针的引用。