c++ - 如何使用程序集获取返回 float 的 __stdcall 函数的结果

标签 c++ windows x86 inline-assembly 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;
}

这只是一点点粘合剂,可以让编写测试用例变得更容易:

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 指令搞乱某些状态?
  • 从根本上误解了如何从返回 floatstdcall 函数获取 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 因为编译器可以在必要时为您执行这些操作。

当您在内联汇编中调用函数时,您还需要指定 memorycc 破坏者。

关于c++ - 如何使用程序集获取返回 float 的 __stdcall 函数的结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18138632/

相关文章:

c++ - constexpr 未定义行为

java - Artifactory 启动时抛出异常 : "No content to map due to end-of-input"

x86 - (写内核)如何修改中断描述符表?

compiler-construction - 给定一个指令地址,能否确定包含它的函数的起始地址?

java - 从C++到Java的转换(在java中返回多个值)

c++ - 如何将 C 类型 "char *"存储到 C++ vector ?

windows - 将 Unix ImageMagick 脚本转换为 Windows 批处理文件

windows - 如何为 Windows toast 注册协议(protocol)?

c++ - 从 IA-32 中的程序集访问 C++ 中的函数参数

C++ IDE 在特定命名空间内搜索