Back

/ 4 min read

windows-x64汇编开发

开发环境配置

; 动态链接:
; 动态链接CRT
includelib ucrt.lib
includelib vcruntime.lib
includelib msvcrt.lib
; 兼容stdio符号
includelib legacy_stdio_definitions.lib
extern printf :proc
.const
szText db "Hello World", 0
.code
main proc
sub rsp, 28h
mov rcx, offset szText
call printf
add rsp, 28h
xor eax,eax
ret
main endp
end

ucrt.libvcruntime.lib构成完整的现代VS CRT。使用legacy_stdio_definitions.lib添加兼容的stdio符号。

x64寄存器

image-20250218211214165

注意

mov rax, -1
xor eax, eax ; 使用32位寄存器,会将rax的高32位扩展为0
; 此时rax的高32位变为了0
xor al, al; 使用非32位寄存器,不会拓展

x64 ABI约定

栈对齐

image-20250218210740648

每一个函数都保证自身的栈对齐到16。那么当函数内部使用call指令后,为了保存返回地址8字节,此时进入被调用函数时,被调用函数的栈未对齐到16

image-20250218211030313

调用约定

windows x64仅使用__vectorcall一种调用约定,前四个参数使用rcxrdxr8r9进行传递,多余参数使用栈传递。

如果传递的是浮点类型,那么将会使用XMM0-XMM3四个寄存器进行传递

整数参数在寄存器 RCX、RDX、R8 和 R9 中传递。 浮点数参数在 XMM0L、XMM1L、XMM2L 和 XMM3L 中传递。

隐藏的 this 指针被视为第一个整数类型参数。 当前四个自变量之一中的 HVA 自变量无法传入可用寄存器时,对调用方分配的内存的引用将传入相应的整数类型寄存器。 第四参数位置之后的整数类型参数在堆栈上传递。

如果混合,则按照固定顺序传递

func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e pushed on stack

上面的例子中,rdxXMM2寄存器未使用。

返回值

整形使用RAX,浮点类型使用XMM0

可以适应 64 位的标量返回值(包括 __m64 类型)是通过 RAX 返回的。 非标量类型(包括浮点类型、双精度类型和向量类型,例如 __m128__m128i__m128d)以 XMM0 的形式返回。 返回到 RAX 或 XMM0 中的值的未使用位数的状态未定义。

影子存储

如果我们的函数中存在调用其他函数,那么就必须分配20h(32)字节的大小空间来供系统使用影子存储。

为了兼容变参传递,所以变参函数的内部从影子存储读取参数后,再进行栈的遍历。

image-20250218215122624

便捷的结构

main proc
sub rsp, 20h ; 影子空间抬栈
sub rsp, 50h ; 为传参预留10个qword 第一个栈中参数为 [rsp+20h]
sub rsp, 0F8h ; 为局部变量预留空间,第一个局部变量为 [rsp+78h]开始
add rsp, 20h
add rsp, 50h
add rsp, 0F8h
ret
main endp