NASM中的Hello world,带有LINK.EXE和WinAPI

NASM中的Hello world,带有LINK.EXE和WinAPI,winapi,assembly,x86,x86-64,nasm,Winapi,Assembly,X86,X86 64,Nasm,我正在尝试在NASM中运行一个简单的Hello world程序。 我希望打印到控制台而不使用C库,直接与WinAPI接口 我正在使用Visual Studio提供的LINK.EXE进行链接 以下是我目前的代码: section.data 消息:db“你好,世界!”,10 ; '你好,世界加上换行字符 messageLen:db$-消息;“你好,世界!”的长度一串 全球启动 外行 外部写操作 外部退出过程 第节.案文 _开始: ; 双字字节; mov rbp,rsp 副rsp,字节8 ; h

我正在尝试在NASM中运行一个简单的Hello world程序。 我希望打印到控制台而不使用C库,直接与WinAPI接口

我正在使用Visual Studio提供的LINK.EXE进行链接

以下是我目前的代码:

section.data
消息:db“你好,世界!”,10    ; '你好,世界加上换行字符
messageLen:db$-消息;“你好,世界!”的长度一串
全球启动
外行
外部写操作
外部退出过程
第节.案文
_开始:
; 双字字节;
mov rbp,rsp
副rsp,字节8
; hStdOut=GetStdHandle(标准输出句柄)
mov-ecx,-11
调用GetStdHandle
; WriteFile(hstdOut,消息,长度(消息),&字节,0);
mov-rcx,rax
mov-rdx,消息
mov r8,messageLen
lea r9[rsp-4]
推0
调用WriteConsoleW
; 退出进程(0)
mov-rcx,0
呼叫退出过程
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"
messageLen equ $-message             ; Length of the 'Hello world!' string
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
但是,当我运行生成的.exe文件时,我什么也得不到

到目前为止,我试过的一些东西是

  • 使用修饰的名称(如_GetStdHandle@4),这导致链接器抱怨未解析的引用
  • 不尝试打印任何内容并调用Sleep,这导致进程无限期休眠
  • 使用不同的返回码退出,但再次无效
我做错了什么


编辑:固定呼叫约定

修改后的代码有三个问题。第一个是:

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
这将把label
messageLen
的地址移动到r8。您真正应该做的是将
messageLen
定义为装配时间常数,如下所示:

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"
messageLen equ $-message             ; Length of the 'Hello world!' string
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
第二个问题是将字符串定义为单字节字符序列:

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字节对齐的值。这些要求可在中找到

考虑到这一点的代码如下所示:

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"
messageLen equ $-message             ; Length of the 'Hello world!' string
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

修改后的代码有三个问题。第一个是:

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
这将把label
messageLen
的地址移动到r8。您真正应该做的是将
messageLen
定义为装配时间常数,如下所示:

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"
messageLen equ $-message             ; Length of the 'Hello world!' string
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
第二个问题是将字符串定义为单字节字符序列:

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字节对齐的值。这些要求可在中找到

考虑到这一点的代码如下所示:

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"
messageLen equ $-message             ; Length of the 'Hello world!' string
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

您的代码是x64,而不是x86。您首先需要了解x64调用约定我已经解决了调用约定问题,但最终结果仍然是一样的。请参阅问题的编辑您仍然没有完全修复调用约定:您没有保留32字节的阴影空间,因此您调用的函数可能会占用堆栈空间。您的代码是x64,而不是x86。您首先需要了解x64调用约定我已经解决了调用约定问题,但最终结果仍然是一样的。请参阅问题的编辑您仍然没有完全修复调用约定:您没有保留32字节的卷影空间,因此您调用的函数可能会占用堆栈空间。第三个问题:没有保留卷影空间。顺便说一句,当你说“unicode”时,在这种情况下是指UTF-16吗?因为消息是有效的UTF-8 unicode。@彼得科德斯:还有堆栈对齐的问题(不仅仅是阴影空间)。至于Unicode,在NT 4和更早版本的Windows操作系统上是UCS-2,在Windows 2000和更高版本上是UTF-16。NASM有一个
\uuuuuuuuuf16\uuuuu
命令来定义UTF-16字符串,因此编码不是问题。第三个问题:没有保留阴影空间。顺便说一句,当你说“unicode”时,在这种情况下是指UTF-16吗?因为消息是有效的UTF-8 unicode。@彼得科德斯:还有堆栈对齐的问题(不仅仅是阴影空间)。至于Unicode,在NT 4和更早版本的Windows操作系统上是UCS-2,在Windows 2000和更高版本上是UTF-16。NASM有一个定义UTF-16字符串的
\uuuuuuuuu
命令,因此编码不是问题。