Winapi C程序集PUSH不适用于WriteFile函数

Winapi C程序集PUSH不适用于WriteFile函数,winapi,gcc,assembly,x86,inline-assembly,Winapi,Gcc,Assembly,X86,Inline Assembly,我用C-assembly调用(_GetStdHandle@4)函数获取(输出)句柄,然后使用(_WriteFile@20)函数使用我从中获得的句柄在控制台上写入字符串(_GetStdHandle@4). 我在我的源代码中为每个函数使用了(pushl)来传递参数,但有些地方出错了,因为(WriteFile))函数返回错误(6),这是无效的句柄,但句柄是有效的。。。所以,传递论点有点不对劲。。。对我的问题是使用(pushl)将参数传递给(_WriteFile)函数。。。在这段代码中,我对每个参数使用

我用C-assembly调用(_GetStdHandle@4)函数获取(输出)句柄,然后使用(_WriteFile@20)函数使用我从中获得的句柄在控制台上写入字符串(_GetStdHandle@4). 我在我的源代码中为每个函数使用了(pushl)来传递参数,但有些地方出错了,因为(WriteFile))函数返回错误(6),这是无效的句柄,但句柄是有效的。。。所以,传递论点有点不对劲。。。对我的问题是使用(pushl)将参数传递给(_WriteFile)函数。。。在这段代码中,我对每个参数使用(g),因为没有理由将参数移动到寄存器,然后推动寄存器。。。所以我没有使用(r),但是如果我使用(r),程序工作没有任何问题(首先将参数移动到寄存器,然后推动寄存器(我希望推动参数而不将其移动到寄存器中) 这段代码没有显示任何内容,问题出在(WriteFile)函数上,如果我对(WriteFile)参数使用(r),打印就会完成,但为什么我不能使用“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  $0\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\0"
    .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    $16, %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  $0
    pushl  %eax
    pushl  $5
    pushl  $LC0
    pushl  12(%esp)
    call   _WriteFile@20
 # 0 "" 2
/NO_APP
    movl    %eax, 8(%esp)
L2:
    movl    $0, %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,而不是直接调用它们。您可以使用
\uuuuu属性(stdcall))

如果您不需要使用内联程序集,则不应该使用。GCC的内联程序集很难纠正。如果出错,代码可能会一直运行到某一天,尤其是在启用了优化的情况下。David Wohlferd有一篇很好的文章介绍了如果您不需要,为什么要这样做


在生成的代码的这一部分中可以看到主要问题:

pushl  $0
pushl  %eax
pushl  $5
pushl  $LC0
pushl  12(%esp)
call   _WriteFile@20
GCC已将第一个参数的内存操作数(句柄)计算为
12(%esp)
。问题是您已在以前的推送中更改了esp,现在偏移量
12(%esp)
不再位于
handle
所在的位置

要解决此问题,您可以通过寄存器或直接(如果可能)传递内存地址,而不是使用包含
m
g
约束(内存约束),只需对寄存器和立即数使用
ri
。这可防止生成内存操作数。如果通过寄存器传递指针,则还需要添加
“内存”
clobber

允许函数销毁EAX、ECX和EDX(也称为易失性寄存器)。可能
GetStdHandle
WriteFile
会对ECX和EDX进行缓冲,并在EAX中返回一个值。您需要确保ECX和EDX也被列为缓冲(或具有将其标记为输出的约束),否则编译器可能会假定这些寄存器中的值在内联汇编块完成之前和之后是相同的。如果它们不同,则可能会导致细微的错误

通过这些更改,您的代码可能看起来像:

#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  $0\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
    内联程序集标记为
    \uuuu volatile
    ,这样,如果优化器认为没有使用
    result
    ,就无法删除整个内联程序集。编译器不知道函数的副作用是显示被更新。请将函数标记为volatile以防止内联程序集从被完全移除

  • GetStdHandle
    对潜在内存操作数没有问题,因为在初始的
    push%1
    之后没有进一步使用约束。您遇到的问题只是修改ESP时的问题(通过push/POP或直接更改为ESP)之后,在内联程序集中可能会使用内存约束


请尝试将您的问题巧妙地格式化。现在它只是一堵文本墙。请使用段落和其他间距。请参阅。另外,请花一些时间阅读。您好……很清楚……(为什么此代码不起作用!)我还建议您首先了解目标平台的ABI和调用约定。可能在不使用内联汇编的情况下使用该函数,然后检查生成的代码,看看编译器在做什么。如果您坚持使用内联汇编,则可以轻松复制该代码(为什么?)。如果其中一个参数可能在堆栈上,则在使用所有参数之前不应更改堆栈指针,因为编译器可能会生成相对于堆栈指针的地址。正如您所看到的,
pushl 12(%esp)正是这样的情况
。由于您更改了
esp
,因此不再引用正确的位置,因此句柄无效。不清楚为什么要这样做。如果要汇编,请使用一个单独的汇编模块,在该模块中您可以完全控制,以便根据需要访问参数。如果坚持使用内联asm,则不允许使用内存操作数(但是你已经知道了)。有一些原始版本的更新,因为我有几个变体,并且发布了一个错误的版本(有一个bug)。为什么不使用
\uuuu属性(stdcall))声明这些函数呢
并正常调用它们?@fuz:因为在他的评论中,他说我只是想了解调用WINAPI函数时WINAPI函数会发生什么情况,我问了他这个问题,他给出了回答。出于某种原因,他希望知道它在引擎盖下是如何工作的。我个人会在GCC之外的汇编中编写它,但他没有这样做他打算使用内联汇编来解决这个问题。我不确定你的评论是针对我提供的解决方案还是针对Jason(你没有标记他)。我的序言已经暗示我质疑使用内联汇编的动机。@PeterCordes:我应该指出,当我说我只使用了那个约定时,我的意思是说我只使用了那个约定,因为