簡體   English   中英

Windows 的 x64 程序集中的“Hello world” - 陰影空間/堆棧 alignment

[英]"Hello world" in x64 assembly for Windows - Shadow space / Stack alignment

這是https://codereview.stackexchange.com/questions/278940/hello-world-in-x64-assembly-for-windows-shadow-space-stack-alignment/的轉貼,有人建議我 stackoverflow 可能有更合適的答案/評論。

我目前正在嘗試使用 NASM 深入研究 windows 下的 x64 程序集,並創建了一個簡約的“Hello World”應用程序。

它主要是作為我和可能其他人的教育資源,因此是繁重的文檔風格。

帶有構建說明和代碼的完整倉庫位於hello_kernel32 ,這是相關的源文件:

;; Resources:
;; https://sonictk.github.io/asm_tutorial/
;; https://gist.github.com/mcandre/b3664ffbeb4f5764b36a397fafb04f1c
;; https://retroscience.net/x64-assembly.html


;; Make clear this file contains 64bit assembly
bits 64

;; Use rip-relative addressing
default rel

;; Export entry symbol (this is specified in the call to link.exe)
global _start

;; Import external symbols
;; (all of them exist in kernel32.lib, which gets passed to link.exe in addition to the programs object file hello.obj)
;;
;; Why import symbols/functions from kernel32.lib?
;;
;; In windows, the "low level API stack" is: Kernel < Syscalls < ntdll.dll < kernel32.dll (and others like user32.dll)
;;
;; * The kernel itself cannot be accessed by user programs for obvious reasons (CPU ring protection modes)
;; * Syscalls could be performed, but are undocumented and evidently unstable between different versions of windows
;; * ntdll.dll is only partially documented and not intended for external use
;; * kernel32.dll (and friends) are the "official" low-level entry points to the windows API
extern GetStdHandle
extern WriteFile
extern ExitProcess


;; This section contains read-only data
section .rodata

;; Store the output string followed by CRLF as a sequence of bytes, at address 'msg'
msg db "Hello World!", 0x0d, 0x0a

;; The length will be needed by the output function, and can be statically calculated at assembly time by using 'equ'
;; It is actually a nifty trick that calculates the offset between the current address '$', and the address of 'msg'
;; See https://nasm.us/doc/nasmdoc3.html#section-3.2.4
msg_len equ $ - msg


;; This section contains the code
section .text

_start:
    ;; For being able to print text, we first need to acquire a HANDLE to STDOUT
    ;; This HANDLE is a required parameter for the call to WriteFile

    ;; HANDLE = GetStdHandle(-11)
    ;;
    ;; See https://docs.microsoft.com/en-us/windows/console/getstdhandle
    ;;
    ;; Parameter 1 (rcx): requests the type of HANDLE, -11 is the constant for STDOUT
    ;; Return value (rax): HANDLE (an address with some type of meaning) is stored in rax, as per calling conventions
    mov rcx, -11
    sub rsp, 40 ;; Allocate 32 bytes of shadow space and 8 bytes to keep the stack 16-byte aligned
    call GetStdHandle
    add rsp, 40 ;; Undo shadow space + alignment padding

    ;; code = WriteFile(HANDLE, msg, msg_len, NULL, NULL)
    ;;
    ;; See https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
    ;;
    ;; Parameter 1 (rcx): HANDLE to write to
    ;; Parameter 2 (rdx): Address of message to print
    ;; Parameter 3 (r8): Length of message
    ;; Parameter 4 (r9): Write amount of written bytes to this address, null pointer
    ;;                   (Required according to docs when parameter 5 is null, but passing null seems to work just fine)
    ;; Parameter 5 (on stack): Unused optional parameter, null pointer
    ;; Return value (rax): Nonzero on success
    mov rcx, rax
    mov rdx, msg
    mov r8, msg_len
    mov r9, 0
    push qword 0
    sub rsp, 32 ;; The previous push aligned us to 16 bytes, only allocate shadow space
    call WriteFile
    add rsp, 40 ;; Undo shadow space + push

    ;; ExitProcess(code)
    ;;
    ;; See https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitprocess
    ;;
    ;; Parameter 1 (rcx): Exit code
    mov rcx, rax
    sub rsp, 40 ;; Shadow space + 8 byte alignment
    call ExitProcess
    ;; add rsp, 40 ;; Not necessary because the previous function call will end the program anyway

我的主要問題是:

  • 陰影空間 + 堆棧 alignment

在執行簡單的“hello world”程序時,我發現的大多數資源似乎完全忽略了這一點。 刪除所有sub rsp / add rsp語句時,我的程序似乎也運行良好。

所以我想知道不遵循這些約定對代碼正確性的真正含義是什么。

我目前的理解是GetStdHandle / WriteFile / ExitProcess根本不使用影子空間,也不執行任何需要在當前實現中對齊堆棧的操作,如我的機器上存在的那樣

但是,對kernel32.dll的任何更新都可以以依賴於存在影子空間和/或對齊堆棧的方式自由更改這些實現。

因此,對於外部調用忽略影子空間/堆棧 alignment 的代碼在與外部 API 不正確接口的一般意義上是不正確的,即使許多 API 可能以它們可以容忍稍微不正確的訪問的方式構建(但這種容忍度是一個實現細節可能隨時改變)。

->這個定義/理解正確嗎? / 有什么要補充/澄清的嗎?

  • 一般問題

代碼或注釋中是否還有其他明顯的錯誤/誤解?

不,這些函數可能正在寫入它們的影子空間,您的程序只是不依賴於 [rsp+0..31] 在call中未被修改

call ExitProcess而不是ret (因為 _start 甚至可能在堆棧上沒有返回地址?IDK 如果 Windows 有)。

而且您不會在自己的堆棧空間中保留任何本地變量,這些變量會成為被調用者的影子空間,因此它們不會踩到您的本地變量。

但是,是的,如果您的_start被稱為 function,即使用RSP%16 == 8 ,它們可以容忍錯位。 與 Linux 不同,其中RSP%16 == 0_start入口點,其中 RSP 指向argcargv[]envp[] ,而不是返回地址。

Alignment is usually only a correctness problem in code that uses movaps / movdqa to copy 16 bytes at a time, like glibc scanf/printf being common examples on Linux ( glibc scanf Segmentation faults when called from a function that doesn't align RSP ). 在 Windows 上,也許他們的 DLL 函數不使用 SSE 來復制 16 字節塊,或者他們使用movups / movdqu (MSVC 通常不發出對齊要求的mov指令,即使對於像_mm_store_si128這樣的對齊要求的內在_mm_storeu_si128 ,在 K10 或 Core2 等古老 CPU 上運行速度較慢的代碼)。


通常,您不會在每個 function 周圍添加/添加 RSP 您可以在 function 的頂部添加 rsp sub rsp, 40以保留陰影空間 + alignment。 並在最后add rsp, 40一次,或者如果您通過調用 noreturn function 而不是使用ret結束。 除了 function 序言/尾聲之外,根本不移動 RSP 是正常的,尤其是在具有陰影空間的調用約定中。

要存儲堆棧參數,您可以將push 0替換為mov qword [rsp+32], 0 或者你實際上用push 0 / sub rsp, 32啟動你的 function 以便陰影空間上方的0一直存在,並且可以作為第一個 function 調用以獲取堆棧 arg 的 arg。 沒有堆棧參數的函數必須不要觸摸它,因為它不是它們的參數,只是調用者堆棧框架的一部分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM