首页 > 解决方案 > 带有 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 文件时,我什么也得不到。

到目前为止我尝试过的一些事情是

我究竟做错了什么?

编辑:固定调用约定

标签: winapiassemblyx86x86-64nasm

解决方案


您修改后的代码存在三个问题。第一个是:

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 版本,WriteConsoleWriteConsoleA. 使用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

推荐阅读