我有一个汇编例程,它以通用方式调用已知使用 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;
}
这只是一点点粘合剂,可以让编写测试用例变得更容易:
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);
}
现在我有一些调用此函数的测试用例:
__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
问题
随机地,测试 3 给我垃圾输出而不是返回 -1.0。
我想知道我是不是
- 未能在
call
指令之前保留某些状态? - 使用
fsts
指令搞乱某些状态? - 从根本上误解了如何从返回
float
的stdcall
函数获取float
值???
非常感谢所有帮助。
最佳答案
由于缺乏Windows机器,我无法完全测试这个;在 Linux 上,以下内容获取浮点函数的返回代码:
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)
约束 - 获取浮点堆栈的顶部,请参阅 Machine Constraints (来自海湾合作委员会手册)。如果 Windows stdcall
返回 float
结果也是 ST(0)
,那么应该可以工作,不需要 fld
/fst
因为编译器可以在必要时为您执行这些操作。
当您在内联汇编中调用函数时,您还需要指定 memory
和 cc
破坏者。
关于c++ - 如何使用程序集获取返回 float 的 __stdcall 函数的结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18138632/