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

时间:2019-07-02 11:41:08

标签: winapi gcc assembly x86 inline-assembly

我使用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"

出什么问题了?

1 个答案:

答案 0 :(得分:3)

我会问是否需要通过这样的包装而不是直接调用WINAPI来调用WINAPI。您可以使用
声明stdcall调用约定的原型 __attribute__((stdcall))

如果不需要使用内联汇编,则不需要。 GCC的内联汇编很难正确处理。弄错了它可能会使代码似乎直到一天都无法工作,尤其是在启用优化的情况下。 David Wohlferd撰写了一篇很好的文章,介绍了为什么shouldn't use inline assembly(如果不需要)。


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

pushl  $0
pushl  %eax
pushl  $5
pushl  $LC0
pushl  12(%esp)
call   _WriteFile@20

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

要解决此问题,可以通过寄存器或立即数(如果可能)传递内存地址。与其使用包含g的{​​{1}}约束(内存约束),不如将m用于寄存器和立即数。这样可以防止生成内存操作数。如果通过寄存器传递指针,则还需要添加ri Clobber。

STDCALL(WINAPI) calling convention允许函数销毁 EAX ECX EDX (又称​​易失性寄存器< / em>)。 "memory"GetStdHandle可能会破坏 ECX EDX 并在 EAX 中返回一个值。您需要确保同时将 ECX EDX 列为Clobbers(或具有将其标记为输出的约束),否则编译器可能会假定这些寄存器中的值内联装配块完成之前和之后的步骤相同。如果它们不同,则可能导致细微的错误。

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

WriteFile

注释

  • 我将#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,以便优化程序认为未使用__volatile时无法删除整个内联程序集。编译器不知道该功能的副作用是显示已更新。将功能标记为易失性,以防止完全删除嵌入式程序集。

  • result对潜在的内存操作数没有问题,因为在初始GetStdHandle之后不再使用约束。您遇到的问题仅是修改了 ESP (通过PUSH / POP或直接更改为 ESP )并且仅在可能的情况下使用内存限制的问题然后在该内联汇编中。