这些是使用 Windows API 调用的 Win32 和 Win64 示例。它们适用于 MASM 而不是 NASM,但请查看它们。您可以在this 文章中找到更多详细信息。
这使用 MessageBox 而不是打印到标准输出。
Win32 MASM
;---ASM Hello World Win32 MessageBox
.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
.data
title db 'Win32', 0
msg db 'Hello World', 0
.code
Main:
push 0 ; uType = MB_OK
push offset title ; LPCSTR lpCaption
push offset msg ; LPCSTR lpText
push 0 ; hWnd = HWND_DESKTOP
call MessageBoxA
push eax ; uExitCode = MessageBox(...)
call ExitProcess
End Main
Win64 MASM
;---ASM Hello World Win64 MessageBox
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
.data
title db 'Win64', 0
msg db 'Hello World!', 0
.code
main proc
sub rsp, 28h
mov rcx, 0 ; hWnd = HWND_DESKTOP
lea rdx, msg ; LPCSTR lpText
lea r8, title ; LPCSTR lpCaption
mov r9d, 0 ; uType = MB_OK
call MessageBoxA
add rsp, 28h
mov ecx, eax ; uExitCode = MessageBox(...)
call ExitProcess
main endp
End
要使用 MASM 组装和链接这些,请将其用于 32 位可执行文件:
ml.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main
或者这个对于 64 位可执行文件:
ml64.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
为什么 x64 Windows 需要在 call 之前保留 28h 字节的堆栈空间?这是调用约定所要求的 32 字节 (0x20) 的影子空间(也称为主空间)。另外 8 个字节将堆栈重新对齐 16,因为调用约定要求 RSP 在call 之前 对齐 16 字节。 (我们的main 的调用者(在 CRT 启动代码中)这样做了。8 字节的返回地址意味着 RSP 距离函数入口处的 16 字节边界有 8 个字节。)
Shadow space 可以被函数用来将其寄存器参数转储到任何堆栈参数(如果有)所在的位置旁边。除了前面提到的 4 个寄存器之外,system call 还需要 30h(48 个字节)来为 r10 和 r11 保留空间。但 DLL 调用只是函数调用,即使它们是 syscall 指令的包装器。
有趣的事实:非 Windows,即 x86-64 System V 调用约定(例如在 Linux 上)根本不使用影子空间,并且最多使用 6 个整数/指针寄存器参数,和 em> XMM 寄存器中最多 8 个 FP args。
使用 MASM 的 invoke 指令(它知道调用约定),您可以使用一个 ifdef 来制作一个可以构建为 32 位或 64 位的版本。
ifdef rax
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
else
.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
endif
.data
caption db 'WinAPI', 0
text db 'Hello World', 0
.code
main proc
invoke MessageBoxA, 0, offset text, offset caption, 0
invoke ExitProcess, eax
main endp
end
两者的宏变体相同,但您不会通过这种方式学习汇编。您将改为学习 C 风格的 asm。 invoke 用于stdcall 或fastcall,而cinvoke 用于cdecl 或变量参数fastcall。汇编器知道使用哪个。
你可以反汇编输出看看invoke是如何扩展的。