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 在这方面的帮助!
直到现在,我一直在 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 在这方面的帮助!