Windows 上的 GNU 汇编程序:WriteFile returns ERROR_INVALID_HANDLE

GNU assembler on Windows: WriteFile returns ERROR_INVALID_HANDLE

直到现在,我一直在 Linux 上用 GAS 语法编写汇编代码,所以我想试试在 Windows 上会怎样。第一个目标是将单个字符打印到 stdout - 但没有成功

这是我的代码:

.intel_syntax noprefix

.extern GetStdHandle
.extern GetLastError
.extern WriteFile
.extern ExitProcess

.section .rodata
        .Lchar: .ascii "F"

.section .data
    .Lbytes_written: .long 0

.section .text

.global main

main:
        mov rcx, -11 //-11 = stdout
        call GetStdHandle
        mov rcx,  rax
        lea rdx, [rip + .Lchar]
        mov  r8, 1
        lea r9, [rip + .Lbytes_written]
        push 0
        call WriteFile
        call GetLastError //After this call, rax=0x6

        xor rcx, rcx
        call ExitProcess

使用gcc -g -o example.exe ./example.S编译后(我用的是mingw64),没有打印出来。当使用调试器单步执行代码时,我注意到 GetStdHandle 没有失败(在它 returned 0 之后直接调用 GetLastError),但是 WriteFile 失败并返回 0x6,即 ERROR_INVALID_HANDLE.

所以我的问题是:这可能是什么问题?这个问题对某些人来说可能很愚蠢,所以我提前道歉。 谢谢!

编辑:这比我想象的还要奇怪。拿这两块代码

.section .rodata
.Lchar: .ascii "F"

.section .data
.Lbytes_written: .long 0

.section .text

.global main

main:
    sub rsp, 8
    mov rcx, -11
    call GetStdHandle
    mov rcx, rax
    lea rdx, [rip + .Lchar]
    mov r8, 1
    lea r9, [rip + .Lbytes_written]
    push 0
    push 0
    call WriteFile
    call GetLastError
    add rsp, 16
    add rsp, 8
    ret
.section .rodata
.Lchar: .ascii "F"

.section .data
.Lbytes_written: .long 0

.section .text

.global main

main:
    sub rsp, 24
    mov rcx, -11
    call GetStdHandle
    mov rcx, rax
    lea rdx, [rip + .Lchar]
    mov r8, 1
    lea r9, [rip + .Lbytes_written]
    push 0
    push 0
    call WriteFile
    call GetLastError
    add rsp, 16
    add rsp, 24
    ret

唯一的区别是第二个代码块在堆栈上多分配了 16B,这应该不是问题,因为所有调用仍然是 16B 对齐的。然而对于第二个代码块,对 WriteFile 的调用不起作用。有趣的是,对 GetStdHandle 的两个函数调用都成功并且 return 的值相同(在我的例子中是 84) 这可能是什么来源?

main:
    sub rsp, 8
    mov rcx, -11
    call GetStdHandle

此时内存已损坏。堆栈中的前四个槽(32 字节)需要空闲。正确的行似乎是

    sub rsp, 40

向右移动:

    call WriteFile
    call GetLastError

这里是错误的。检查 rax 以获取错误代码。如果没有错误,GetLastError returns 之前的错误不管是什么。

如果某些东西看起来绝对疯狂,请进一步检查代码。据我从尝试使这些东西工作时可以确定的那样,Windows 在某些地方反汇编了您的代码,如果堆栈未对齐,未到达的代码仍然会崩溃。我有一个案例,崩溃是由于不注释代码和无条件跳转的注释代码之间的区别。

我的代码的主要问题是我违反了 Windows' ABI,其中包括

  • 函数调用前未正确对齐堆栈
  • 没有在堆栈上添加 32B 填充(阴影 space),这意味着第 5 个参数提供不正确

这是此代码的工作版本:

.intel_syntax noprefix

.extern GetStdHandle
.extern GetLastError
.extern WriteFile

.section .rodata
.Lchar: .ascii "F"

.section .data
.Lbytes_written: .long 0      # surprisingly just needs to be a dword, not qword in win64

.section .text

.global main

main:
    sub   rsp, 40                      # allocate shadow space + re-align the stack to RSP%16 == 0
    mov   rcx, -11                     # Magic number for stdout
    call  GetStdHandle
    mov   rcx, rax                     # hFile = return value
    lea   rdx, [rip + .Lchar]          # lpBuffer
    mov   r8, 1                        # 1 byte to write
    lea   r9, [rip + .Lbytes_written]  # output arg pointer
    mov   QWORD PTR [rsp + 32], 0      # lpOverlapped=NULL  in the stack space that was padding for the first call
    call  WriteFile
    add   rsp, 40
    ret

感谢 Peter Cordes、Joshua 和 RbMm 在这方面的帮助!