assembly 在x64 64位Windows上使用NASM(和GoLink)的Hello World

bvk5enib  于 2023-10-19  发布在  Windows
关注(0)|答案(1)|浏览(144)

我目前正在为一个自定义编译器项目学习汇编语言。然而,我不能做一个单一的工作汇编程序。我有一个64位操作系统的AMD CPU和一个基于x64的处理器。我正在使用Windows 11。
我不想使用c库,因为它破坏了尽可能低层次编码的目的,这是我的目标。
我已经尝试了很多不同的程序和链接器。
Assembly Syscalls in 64-bit Windows这不包含任何工作代码。
Hello world在windows assembly中使用nasm我认为这是32位系统,但我不知道我需要改变什么。我把“eax”改成了“rax”,这个程序编译并运行了,但是没有打印任何东西。我还可以在代码中添加随机字母,没有任何错误,退出代码仍然为0。(仅一个GoLink警告)
Setting Exit Status in NASM for Windows同样的问题。我总是得到一个退出代码,编译器不关心任何语法错误。
我也试过使用gcc和ld,但没有成功。
我做错了什么?

编辑我试过这个:如何在Windows下用汇编语言编写Hello World?但是link.exe似乎只适用于visual studio(我正在使用vscode)。当我使用GoLink时,我得到以下错误:

Error!
The following symbol was not defined in the object file or files:-
MessageBoxA
Output file not made

Edit 2当我使用gcc时,我得到以下错误:

C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64- 
mingw32/13.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: 
out.obj:out.asm:(.text+0x1e): undefined reference to `MessageBoxA'
collect2.exe: error: ld returned 1 exit status

如果我删除消息打印代码,它工作正常,我终于得到了我选择的退出代码!MessageBoxA函数是在另一个库中还是什么?我该怎么解决这个问题?

编辑3我发现了这段代码并复制了它,但同样,它不工作。(编者按:它是32位调用约定的32位代码。)

global _main
    extern  GetStdHandle
    extern  WriteFile
    extern  ExitProcess

    section .text
    _main:
        ; DWORD  bytes;    
        mov     ebp, esp
        sub     esp, 4

        ; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
        push    -11
        call    GetStdHandle
        mov     ebx, eax    

        ; WriteFile( hstdOut, message, length(message), &bytes, 0);
        push    0
        lea     eax, [ebp-4]
        push    rax
        push    (message_end - message)
        push    message ; ERROR
        push    rbx
        call    WriteFile

        ; ExitProcess(0)
        xor rcx, rcx
        call    ExitProcess

        ; never here
        hlt
    message: db      "Hello, World", 8
    message_end:

当我运行以下命令时:

nasm -f win64 out.asm && gcc -o  out.exe out.obj -nostdlib -lkernel32

.我得到一个截断错误(idk这意味着什么)

out.obj:out.asm:(.text+0x18): relocation truncated to fit: 
IMAGE_REL_AMD64_ADDR32 against `.text'
collect2.exe: error: ld returned 1 exit status

我有点恼火,为什么这么难执行这样一个简单的程序。我只需要一个nasm的hello world程序,x64_64 windows,带有visual studio代码,没有c库。

6vl6ewon

6vl6ewon1#

经过一番努力,我终于让它工作了。这里是这个答案的第一个版本中的代码的清理版本(由@PeterCordes)。(同样,我使用的是AMD基于x64的处理器的Windows。我在Visual Studio Code中编写代码。

default rel

extern GetStdHandle
extern WriteFile
extern ExitProcess

section .text
global main
main:
    sub     rsp, 40          ; reserve shadow space and align stack by 16

    mov     rcx, -11         
    call    GetStdHandle

    mov     rcx, rax         ; HANDLE is a 64-byte type on x64
    lea     rdx, [msg]       ; lpBuffer = RIP-relative LEA (default rel)
    mov     r8d, msg.len     ; DWORD nNumberOfBytesToWrite = 32-bit immediate constant length
    lea     r9, [rsp+48]     ; lpNumberOfBytesWritten pointing into main's shadow space
    mov     qword [rsp + 32], 0   ; lpOverlapped = NULL; This is a pointer, needs to be qword.
    call    WriteFile        ; WriteFile(handle, msg, len, &our_shadow_space, NULL)

;;; BOOL return value in EAX (BOOL is a 4-byte type, unlike bool).
;;; NumberOfBytesWritten in dword [rsp+48]

    xor     ecx, ecx
    call ExitProcess
  ; add     rsp, 40      ; alternative to calling a noreturn function like ExitProcess
  ; ret

section .data         ; or section .rdata to put it in a read-only page
    msg:  db "Hello, World!", 13, 10    ; including a CR LF newline is a good idea for a line of output text
    .len equ  $-msg    ; let the assembler calculate the string length
                       ; .len is a local label that appends to the most recent non-dot label, so this is msg.len

用nasm和gcc组装链接:

nasm -f win64 out.asm   &&  gcc -o  out.exe out.obj -nostdlib -lkernel32

在这个例子中省略:stack-unwind metadata用于更改RSP的指令(sub rsp,40)。只要没有SEH或C++异常发生,没有它也能正常运行,但是调试器从WriteFile内部通过main回溯到它的调用者是不起作用的。* Under what conditions do I need to set up SEH unwind info for an x86-64 assembly function? * 查看更多。如果你是asm的初学者,只是想了解“正常”的东西是如何工作的,并且不打算在生产代码中实际使用手写的asm,那么你根本不需要担心SEH和堆栈展开信息。
关于这与 * How To Properly call 64 Bit Windows API In Assembly * 中的代码之间的差异的评论,它最初基于:
静态存储(.data/.bss/.rdata)中唯一需要的数据是字符串本身的ASCII字节。输出的字节数可以指向堆栈空间,我们传递给WriteFile的常量可以只是机器码中的立即数,而不是从静态存储(dq 14)加载的。一般来说,have the assembler calculate a length instead of hard-coding it也不错。
WriteFile需要存储实际写入的字节数(因为返回值只是BOOL,不幸的是,不像POSIX ssize_t write(),它返回负数的错误)。因为main是一个函数,所以它有自己的阴影空间。
(The从 * How To Properly call 64 Bit Windows API In Assembly * 复制的示例代码使用了像NtlpNBytesWritten这样的奇怪名称作为BSS中的变量,以指向WriteFilelpNumberOfBytesWritten输出arg。这是令人困惑的,因为这些不是低级NT函数,它们是Win32 API。变量本身只是一个双字,所以如果你使用的是静态存储,它的名字中不应该有lp
注解中的arg名称来自MS的WriteFile文档:https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
还有其他的Hello World示例,例如如何在Windows下的程序集中编写Hello World?- 使用MessageBoxA,它在user32.dll中,而GetStdHandleWriteFilekernel32.dll

相关问题