winapi - 带有 LINK.EXE 和 WinAPI 的 NASM 中的 Hello world
问题描述
我正在尝试在 NASM 中运行一个简单的 Hello world 程序。我想在不使用 C-Libraries 的情况下打印到控制台,直接与 WinAPI 交互。
我正在使用 Visual Studio 提供的 LINK.EXE 进行链接。
到目前为止,这是我的代码:
section .data
message: db 'Hello world!',10 ; 'Hello world!' plus a linefeed character
messageLen: db $-message ; Length of the 'Hello world!' string
global _start
extern GetStdHandle
extern WriteConsoleW
extern ExitProcess
section .text
_start:
; DWORD bytes;
mov rbp, rsp
sub rsp, byte 8
; hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)
mov ecx, -11
call GetStdHandle
; WriteFile(hstdOut, message, length(message), &bytes, 0);
mov rcx, rax
mov rdx, message
mov r8, messageLen
lea r9, [rsp-4]
push 0
call WriteConsoleW
; ExitProcess(0)
mov rcx, 0
call ExitProcess
ret
我像这样组装和链接:
nasm -f win64 .\ASM.ASM
link /entry:_start /nodefaultlib /subsystem:console .\ASM.obj "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x64\kernel32.lib" "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x64\user32.lib"
但是,当我运行生成的 .exe 文件时,我什么也得不到。
到目前为止我尝试过的一些事情是
- 使用修饰名称(如 _GetStdHandle@4),导致链接器抱怨未解析的引用
- 不尝试打印任何内容并调用睡眠,导致进程无限期睡眠
- 以不同的返回码退出,这又一次什么也没做
我究竟做错了什么?
编辑:固定调用约定
解决方案
您修改后的代码存在三个问题。第一个是:
message: db 'Hello world!',10 ; 'Hello world!' plus a linefeed character
messageLen: db $-message ; Length of the 'Hello world!' string
您定义messageLen
为一个包含消息长度的字节,并将该值存储在messageLen
. 然后你这样做:
mov r8, messageLen
这会将标签的地址移动messageLen
到 r8。您真正应该做的是定义messageLen
为这样的装配时间常数:
messageLen equ $-message ; Length of the 'Hello world!' string
第二个问题是将字符串定义为单字节字符序列:
message: db 'Hello world!',10 ; 'Hello world!' plus a linefeed character
这没有什么问题,但是要打印出来,您需要使用函数的 Ansi 版本,WriteConsole
即WriteConsoleA
. 使用WriteConsoleW
将字符串打印为 Unicode(Windows 2000 和更高版本上的 UTF-16,NT4 和更早版本的 Windows 上的 UTS-2)。
第三个问题是关于在进行函数调用之前将基于堆栈的参数放置在堆栈上之前的强制性 32 字节影子空间。在进行函数调用时,您还需要确保堆栈 (RSP) 是 16 字节对齐的值。这些要求可以在Microsoft 64 位调用约定中找到。
考虑到这一点的代码如下所示:
section .data
message: db 'Hello world!',10 ; 'Hello world!' plus a linefeed character
messageLen equ $-message ; Length of the 'Hello world!' string
global _start
extern GetStdHandle
extern WriteConsoleA
extern ExitProcess
section .text
_start:
; At _start the stack is 8 bytes misaligned because there is a return
; address to the MSVCRT runtime library on the stack.
; 8 bytes of temporary storage for `bytes`.
; allocate 32 bytes of stack for shadow space.
; 8 bytes for the 5th parameter of WriteConsole.
; An additional 8 bytes for padding to make RSP 16 byte aligned.
sub rsp, 8+8+8+32
; At this point RSP is aligned on a 16 byte boundary and all necessary
; space has been allocated.
; hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)
mov ecx, -11
call GetStdHandle
; WriteFile(hstdOut, message, length(message), &bytes, 0);
mov rcx, rax
mov rdx, message
mov r8, messageLen
lea r9, [rsp-16] ; Address for `bytes`
; RSP-17 through RSP-48 are the 32 bytes of shadow space
mov qword [rsp-56], 0 ; First stack parameter of WriteConsoleA function
call WriteConsoleA
; ExitProcess(0)
; mov rcx, 0
; call ExitProcess
; alternatively you can exit by setting RAX to 0
; and doing a ret
add rsp, 8+8+32+8 ; Restore the stack pointer.
xor eax, eax ; RAX = return value = 0
ret
推荐阅读
- ajax - codeigniter 中的 Ajax 方法调用
- google-apps-script - 有没有办法限制谷歌表格单元格中的数据输入,只有一次?
- python - 如何使用 networkx 库为启用对角线的 A* 算法创建 8 个单元邻接图
- python - numpy.choice 与 numpy.randint 有何不同?
- jmeter - Jmeter中不同请求的相同响应
- c++ - C++ 编译器不支持 C++11(例如 std::unique_ptr)。构建 OpenWRT
- python - matplotlib 中的 set_xlim() 和 set_ylim() 是什么?
- php - PHP Command to Calculate Percentage of MySQL rows
- ios - UIPanGestureRecognizer 中的速度和平移开始状态
- exception - 我们可以抛出 CWin32Error 和带有文件名的附加参数吗