寄存器
问题
- 寄存器在写汇编的时候是随便用的吗
- 寄存器的使用有严格的规则和约定,违反这些规则会导致程序崩溃或出现难以调试的
bug
- 寄存器的使用有严格的规则和约定,违反这些规则会导致程序崩溃或出现难以调试的
32位-常见调用约定对比
| 特性 | GCC cdecl |
MSVC __cdecl |
MSVC __stdcall |
MSVC/GCC __fastcall |
| 平台 | Linux | Windows | Windows | Windows/Linux |
参数1 |
栈 | 栈 | 栈 | ecx |
参数2 |
栈 | 栈 | 栈 | edx |
参数3+ |
栈 | 栈 | 栈 | 栈 |
| 参数顺序 | 从右到左 | 从右到左 | 从右到左 | 从右到左 |
| 栈清理 | 调用者 | 调用者 | 被调用者 | 被调用者 |
| 返回值 | eax |
eax |
eax |
eax |
| 名字修饰 | _func |
_func |
_func@bytes |
@func@bytes |
| 可变参数 | yes |
yes |
no |
no |
64位-常见调用约定对比
| 特性 | GCC (System V AMD64) |
MSVC (Microsoft x64) |
| 平台 | Linux/macOS |
Windows |
参数1 |
rdi |
rcx |
参数2 |
rsi |
rdx |
参数3 |
rdx |
r8 |
参数4 |
rcx |
r9 |
参数5 |
r8 |
栈 |
参数6 |
r9 |
栈 |
参数7+ |
栈 | 栈 |
浮点参数1-4 |
xmm0-xmm3 |
xmm0-xmm3 |
浮点参数5-8 |
xmm4-xmm7 |
栈 |
| 返回值 | rax |
rax |
| 栈清理 | 调用者 | 调用者 |
Shadow Space |
无 | 必须预留32字节 |
| 栈对齐 | 16字节 |
16字节 |
XMM寄存器 |
xmm0-xmm15 全部调用者保存 |
xmm0-xmm5 调用者,xmm6-xmm15 被调用者 |
32位Linux-完整调用约定
gcc
| 调用约定 | 参数传递 | 栈清理 | 使用场景 | 语法 |
cdecl |
全部栈 | 调用者 | 默认约定,C 函数 |
int func() |
stdcall |
全部栈 | 被调用者 | Windows API 兼容 |
__attribute__((stdcall)) |
fastcall |
ecx, edx, 其余栈 |
被调用者 | 性能优化 | __attribute__((fastcall)) |
regparm(n) |
前n个用寄存器 | 调用者 | GCC 特有,灵活优化 |
__attribute__((regparm(3))) |
thiscall |
this用ecx,其余栈 |
被调用者 | C++ 成员函数 |
自动处理 |
pascal |
全部栈(左到右) | 被调用者 | 老式约定,很少用 | __attribute__((pascal)) |
32位Windows -完整调用约定
msvc
| 调用约定 | 参数传递 | 栈清理 | 使用场景 | 语法 |
__cdecl |
全部栈 | 调用者 | 默认约定,C 函数 |
int __cdecl func() |
__stdcall |
全部栈 | 被调用者 | Windows API 标准 |
int __stdcall func() |
__fastcall |
ecx, edx, 其余栈 |
被调用者 | 性能优化 | int __fastcall func() |
__thiscall |
this用ecx,其余栈 |
被调用者 | C++ 成员函数 |
自动处理 |
__vectorcall |
向量寄存器优化 | 被调用者 | SIMD 优化 |
int __vectorcall func() |
__clrcall |
.NET CLR |
特殊 | .NET 互操作 |
int __clrcall func() |
64位Linux-调用约定
Linux/macOS (GCC) - 64位- 注意:
64位Linux只有一种调用约定
| 调用约定 | 参数传递 | 特点 | 使用场景 | 语法 |
System V AMD64 |
rdi, rsi, rdx, rcx, r8, r9 |
唯一标准 | 所有函数 | 默认 |
ms_abi |
rcx, rdx, r8, r9 |
Windows 兼容 |
调用 Windows DLL |
__attribute__((ms_abi)) |
64位Windows-完整调用约定
64位Windows也基本只有一种调用约定
| 调用约定 | 参数传递 | 特点 | 使用场景 | 语法 |
Microsoft x64 |
rcx, rdx, r8, r9 |
唯一标准 | 所有函数 | 默认 |
__vectorcall |
向量寄存器优化 | SIMD 增强 |
高性能计算 | int __vectorcall func() |
sysv_abi |
rdi, rsi, rdx, rcx, r8, r9 |
Linux 兼容 |
调用 Linux 库 |
GCC: __attribute__((sysv_abi)) |
寄存器保存规则
| 寄存器 | 64位 Linux |
64位 Windows |
32位 Linux |
32位 Windows |
rax/eax |
调用者 | 调用者 | 调用者 | 调用者 |
rcx/ecx |
调用者 | 调用者 | 调用者 | 调用者 |
rdx/edx |
调用者 | 调用者 | 调用者 | 调用者 |
rsi/esi |
调用者 | 被调用者 | 被调用者 | 被调用者 |
rdi/edi |
调用者 | 被调用者 | 被调用者 | 被调用者 |
rbx/ebx |
被调用者 | 被调用者 | 被调用者 | 被调用者 |
rbp/ebp |
被调用者 | 被调用者 | 被调用者 | 被调用者 |
rsp/esp |
特殊 | 特殊 | 特殊 | 特殊 |
r8-r11 |
调用者 | 调用者 | N/A |
N/A |
r12-r15 |
被调用者 | 被调用者 | N/A |
N/A |
特殊用途的寄存器
rsp(栈指针)- 绝对不能随便改
- 必须始终指向有效的栈顶
- 改变它必须小心,且要恢复
rbp(基址指针)- 通常用于建立栈帧
- 属于被调用者保存寄存器
- 使用前必须先
push rbp
rip(指令指针)- 不能直接修改
- 只能通过
jmp,call,ret等指令间接改变
参数传递规则
| 特性 | gcc-32 |
msvc-32 |
gcc-64 |
msvc-64 |
参数1 |
栈 | cdecl-栈fastcall-ecx |
rdi |
rcx |
参数2 |
栈 | cdecl-栈fastcall-edx |
rsi |
rdx |
参数3 |
栈 | 栈 | rdx |
r8 |
参数4 |
栈 | 栈 | rcx |
r9 |
参数5 |
栈 | 栈 | r8 |
栈 |
参数6 |
栈 | 栈 | r9 |
栈 |
参数7+ |
栈 | 栈 | 栈 | 栈 |
栈清理规则
| 编译器 | 调用约定 | 栈清理 |
gcc |
cdecl |
调用者 |
msvc |
cdecl |
调用者 |
msvc |
stdcall |
被调用者 |
msvc/gcc |
fastcall |
被调用者 |
其他
基础算术运算
|
1 2 3 4 5 6 7 8 9 10 11 12 |
; 简单加法 add_two_numbers: mov eax, 5 ; eax = 5 add eax, 3 ; eax = 5 + 3 = 8 ret ; 乘法 multiply: mov eax, 4 mov ebx, 6 imul eax, ebx ; eax = 4 * 6 = 24 ret |
条件判断(if-else)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
max_of_two: mov eax, DWORD PTR [rbp-4] ; 第一个数 mov edx, DWORD PTR [rbp-8] ; 第二个数 cmp eax, edx ; 比较两个数 jle .else ; 如果 eax <= edx,跳转 ; if 分支 mov eax, DWORD PTR [rbp-4] jmp .end .else: mov eax, DWORD PTR [rbp-8] .end: ret |
循环结构(for/while)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
; 计算 1 到 10 的和 sum_1_to_10: push rbp mov rbp, rsp mov DWORD PTR [rbp-4], 0 ; sum = 0 mov DWORD PTR [rbp-8], 1 ; i = 1 .loop: cmp DWORD PTR [rbp-8], 10 ; i <= 10? jg .end ; 如果 i > 10,退出 mov eax, DWORD PTR [rbp-4] add eax, DWORD PTR [rbp-8] ; sum += i mov DWORD PTR [rbp-4], eax add DWORD PTR [rbp-8], 1 ; i++ jmp .loop .end: mov eax, DWORD PTR [rbp-4] ; 返回 sum pop rbp ret |
数组访问
|
1 2 3 4 5 6 7 8 9 10 11 |
; 访问数组元素 arr[2] array_access: push rbp mov rbp, rsp sub rsp, 16 ; 分配数组空间 mov DWORD PTR [rbp-16], 10 ; arr[0] = 10 mov DWORD PTR [rbp-12], 20 ; arr[1] = 20 mov DWORD PTR [rbp-8], 30 ; arr[2] = 30 mov eax, DWORD PTR [rbp-8] ; 读取 arr[2] leave ret |
函数调用和参数传递
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
; 被调用函数:两数相加 add: push rbp mov rbp, rsp mov eax, edi ; 第一个参数 add eax, esi ; 加上第二个参数 pop rbp ret ; 调用函数 main: push rbp mov rbp, rsp mov edi, 5 ; 第一个参数 mov esi, 3 ; 第二个参数 call add ; 调用函数 ; eax 现在包含结果 8 pop rbp ret |
递归(阶乘)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
; 计算 n! factorial: push rbp mov rbp, rsp mov DWORD PTR [rbp-20], edi ; 保存参数 n cmp DWORD PTR [rbp-20], 1 ; if (n <= 1) jg .recursive mov eax, 1 ; return 1 jmp .end .recursive: mov eax, DWORD PTR [rbp-20] sub eax, 1 mov edi, eax call factorial ; factorial(n-1) imul eax, DWORD PTR [rbp-20] ; n * factorial(n-1) .end: pop rbp ret |
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 编译器扩展语法:一07/06
- ♥ X86_64汇编学习记述三08/08
- ♥ Windows小功能:汇总一07/18
- ♥ Visual Studio:内存泄露AddressSanitizer(跨平台)03/14
- ♥ 汇编记述一08/04
- ♥ 关于程序03/29
热评文章
- x86_64汇编学习记述一 0
- 汇编记述二 0
- 汇编记述三 0
- 标志寄存器 0
- x86_64汇编学习记述二 0
- 相关指令 0