C 程序集 pushl 不适用于 WriteFile 函数

C assembly pushl not working for WriteFile function

我使用 C 程序集调用 (_GetStdHandle@4) 函数来获取(输出)句柄,然后使用 (_WriteFile@20) 函数使用我从 (_GetStdHandle@4) 获得的句柄在控制台上写入我的字符串. 我在我的每个函数的源代码中使用 (pushl) 来传递参数,但有些地方是错误的,因为 (WriteFile)) function return error (6) 这是无效的句柄但句柄是有效的......所以有些地方不对劲传递参数......是的......我的问题是使用(pushl)将参数传递给(_WriteFile)函数......在这段代码中,我对每个参数使用(g)因为没有理由将参数移动到注册然后推送寄存器...所以我没有使用(r)但是如果我使用(r),程序工作没有任何问题(首先将参数移动到寄存器然后推送寄存器(我想推送参数而不将它们移动到寄存器中) 此代码未显示任何内容,问题出在 (WriteFile) 函数中,如果我将 (r) 用于 (WriteFile) 参数,打印将完成,但为什么我不能使用 "g" 不将参数移动到寄存器 ?

    typedef void * HANDLE;

#define GetStdHandle(result, handle)                                    \
    __asm (                                                             \
        "pushl  %1\n\t"                                                 \
        "call   _GetStdHandle@4"                                        \
            : "=a" (result)                                             \
            : "g" (handle))

#define WriteFile(result, handle, buf, buf_size, written_bytes)         \
    __asm (                                                             \
        "pushl  [=10=]\n\t"                                                 \
        "pushl  %1\n\t"                                                 \
        "pushl  %2\n\t"                                                 \
        "pushl  %3\n\t"                                                 \
        "pushl  %4\n\t"                                                 \
        "call   _WriteFile@20"                                          \
            : "=a" (result)                                             \
            : "g" (written_bytes), "g" (buf_size), "g" (buf), "g" (handle))

int main()
{
    HANDLE handle;
    int write_result;
    unsigned long written_bytes;

    GetStdHandle(handle, -11);
    if(handle != INVALID_HANDLE_VALUE)
    {
        WriteFile(write_result, handle, "Hello", 5, & written_bytes);
    }

    return 0;
}

这个程序的汇编代码是:

.file   "main.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "Hello[=11=]"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB25:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    , %esp
    call    ___main
/APP
    pushl  $-11
    call   _GetStdHandle@4
 # 0 "" 2
/NO_APP
    movl    %eax, 12(%esp)
    cmpl    $-1, 12(%esp)
    je  L2
    leal    4(%esp), %eax
/APP
    pushl  [=11=]
    pushl  %eax
    pushl  
    pushl  $LC0
    pushl  12(%esp)
    call   _WriteFile@20
 # 0 "" 2
/NO_APP
    movl    %eax, 8(%esp)
L2:
    movl    [=11=], %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE25:
    .ident  "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"

有什么问题?

我质疑是否需要像这样通过包装器调用 WINAPI 而不是直接调用它们。您可以使用
声明 stdcall 调用约定的原型 __attribute__((stdcall))

如果您不需要使用内联汇编,则不应该。 GCC 的内联汇编很难正确。弄错会使代码看起来可以工作,直到有一天它不能工作,尤其是在启用了优化的情况下。 David Wohlferd 有一篇很好的文章,解释了为什么 shouldn't use inline assembly 如果你不需要。


主要问题可见这段生成代码:

pushl  [=10=]
pushl  %eax
pushl  
pushl  $LC0
pushl  12(%esp)
call   _WriteFile@20

GCC 已将第一个参数的内存操作数(句柄)计算为 12(%esp)。问题是您已经用之前的推送更改了 ESP,现在偏移量 12(%esp) 不再位于 handle 所在的位置。

要解决此问题,您可以通过寄存器或立即数(如果可能)传递内存地址。与其使用包含 m(内存约束)的 g 约束,不如对寄存器和立即数使用 ri。这可以防止生成内存操作数。如果您通过寄存器传递指针,您还需要添加 "memory" 破坏符。

STDCALL(WINAPI) calling convention允许函数销毁EAXECXEDX(又名易失性寄存器)。 GetStdHandleWriteFile 可能会破坏 ECXEDX 以及 return 一个值在 EAX 中。您需要确保 ECXEDX 也被列为破坏者(或具有将其标记为输出的约束),否则编译器可能假设这些寄存器中的值在内联汇编块完成之前和之后是相同的。如果它们不同,可能会导致细微的错误。

通过这些更改,您的代码可能类似于:

#define INVALID_HANDLE_VALUE (void *)-1    
typedef void *HANDLE;

#define GetStdHandle(result, handle)                                    \
    __asm (                                                             \
        "pushl  %1\n\t"                                                 \
        "call   _GetStdHandle@4"                                        \
            : "=a" (result)                                             \
            : "g" (handle)                                              \
            : "ecx", "edx")

#define WriteFile(result, handle, buf, buf_size, written_bytes)         \
    __asm __volatile (                                                  \
        "pushl  [=11=]\n\t"                                                 \
        "pushl  %1\n\t"                                                 \
        "pushl  %2\n\t"                                                 \
        "pushl  %3\n\t"                                                 \
        "pushl  %4\n\t"                                                 \
        "call   _WriteFile@20"                                          \
            : "=a" (result)                                             \
            : "ri" (written_bytes), "ri" (buf_size), "ri" (buf), "ri" (handle) \
            : "memory", "ecx", "edx")

int main()
{
    HANDLE handle;
    int write_result;
    unsigned long written_bytes;

    GetStdHandle(handle, -11);
    if(handle != INVALID_HANDLE_VALUE)
    {
        WriteFile(write_result, handle, "Hello", 5, &written_bytes);
    }

    return 0;
}

备注

  • 我将 WriteFile 内联程序集标记为 __volatile 以便优化器在认为 result 不存在时无法删除整个内联程序集用过的。编译器不知道该函数的副作用是显示已更新。将函数标记为 volatile 以防止内联程序集被完全删除。

  • GetStdHandle 对潜在的内存操作数没有问题,因为在初始 push %1 之后没有进一步使用约束。您遇到的问题仅在 ESP 已被修改(通过 PUSH/POP 或直接更改为 ESP 时出现之后可能会在内联汇编中使用内存约束。