我用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
请注意组合分配(以及在可能的情况下使用影子存储(。