我不了解 x64 调用约定Microsoft也不了解 FASM 的实现



我用FASM编写了一个简短的PE32+程序;你好,世界"退出。

format PE64 console
include 'win64wx.inc'
.code
start:
invoke WriteFile,<invoke GetStdHandle,STD_OUTPUT_HANDLE>,hello,hello.length,dummy,0
invoke ExitProcess,0
.end start
.data
dummy rd 1  
hello db "Hello world!",13,10,0
hello.length = $ - hello

我看过生成的机器代码,但我不明白为什么RSP会被这样操作

sub rsp,byte +0x08         ;Allocate 8 bytes on the stack. 
sub rsp,byte +0x30         ;Allocate shadow space for WriteFile (48 bytes)
sub rsp,byte +0x20         ;Allocate shadow space for GetStdHandle
mov rcx,0xfffffffffffffff5 ;Set the constant for stdout
call [rel 0x1060]          ;Call GetStdHandle. The handle for stdout is now in RAX
add rsp,byte +0x20         ;Deallocate shadow space for GetStdHandle
mov rcx,rax                ;Set stdout handle: hFile
mov rdx,0x403004           ;Set the pointer to string "Hello World!rn": lpBuffer
mov r8,0xf                 ;Set the length of the string: nNumberOfBytesToWrite
mov r9,0x403000            ;Set the pointer for lpNumberOfBytesWritten
mov qword [rsp+0x20],0x0   ;Push a 64 bit NULL pointer onto the stack: lpOverlapped
call [rel 0x1068]          ;Call WriteFile
add rsp,byte +0x30         ;Deallocate shadow space for WriteFile
sub rsp,byte +0x20         ;Allocate shadow space for ExitProcess
mov rcx,0x0                ;Set the return value
call [rel 0x1058]          ;Call ExitProcess
add rsp,byte +0x20         ;Deallocate shadow space for ExitProcess

我知道WriteFile的空间提前分配并不重要,但为什么是sub rsp,byte +0x30而不是sub rsp,byte +0x28?为什么会出现第一个sub rsp,byte +0x08?是FASM的特殊性,还是我从根本上误解了Microsoft x64堆栈管理规则?

注释已经解决了16字节的堆栈对齐问题,这会导致在call指令中推送返回地址后堆栈变得不对齐,这意味着您必须在函数prolog中将其与sub rsp, 8重新对齐,请参阅https://learn.microsoft.com/en-us/cpp/build/stack-usage.您还提到了32(0x20(字节的影子存储,它可用于溢出在寄存器中传递的前4个参数,请参阅https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention.
虽然调用者负责分配影子存储,但这当然可以组合用于多个函数调用。不过,FASM实际上可以使用frame/endf宏来执行此操作,请参阅https://flatassembler.net/docs.php?article=win32#1.4.现在,如果显式调用GetStdHandle,删除嵌套(不确定为什么需要这样做(,则可以组合分配:

format PE64 console
include 'win64wx.inc'
.code
start:
frame
invoke GetStdHandle,STD_OUTPUT_HANDLE
invoke WriteFile,rax,hello,hello.length,dummy,0
invoke ExitProcess,0
endf
.end start
.data
dummy rd 1  
hello db "Hello world!",13,10,0
hello.length = $ - hello

组装到:

sub rsp,0x8
sub rsp,0x30
mov rcx,0xFFFFFFFFFFFFFFF5
call qword ptr ds:[<&GetStdHandle>]
mov rcx,rax
mov rdx,test.403004
mov r8,0xF
mov r9,test.403000
mov qword ptr ss:[rsp+0x20],0x0
call qword ptr ds:[<&WriteFile>]
mov rcx,0x0
call qword ptr ds:[<&FatalExit>]
add rsp,0x30

sub rsp, 0x20现在不见了。不幸的是,sub rsp, 8仍然在这里(它是由.code宏插入的(,但它要干净得多。如果使用proc宏,那么push rbp将已经对齐堆栈,因此不需要额外的sub rsp, 8

当然,这是FASM,所以如果不使用proc,您可以添加/重新定义一些宏来组合所有分配,甚至可以使用影子存储来溢出您使用的非易失性寄存器,并在不调用函数时保持堆栈不对齐。这就是我在https://github.com/stevenwdv/asmsequent/blob/main/proc_mod.inc(配frame/save/rest(矿石(/。。。宏(,它可能有点难看,我不知道(现在(它的大部分是如何工作的,但它完成了任务。例如,函数求解组装到:

mov qword ptr ss:[rsp+0x8],r12
mov qword ptr ss:[rsp+0x10],r13
mov qword ptr ss:[rsp+0x18],r14
mov qword ptr ss:[rsp+0x20],r15
push rsi
push rbx
sub rsp,0x28
...
call ...
...
call ...
...
add rsp,0x28
pop rbx
pop rsi
mov r15,qword ptr ss:[rsp+0x20]
mov r14,qword ptr ss:[rsp+0x18]
mov r13,qword ptr ss:[rsp+0x10]
mov r12,qword ptr ss:[rsp+0x8]
ret

请注意组合分配(以及在可能的情况下使用影子存储(。

最新更新