论坛首页 安全工具分享区 阅读主题

[原创]从GDB中观察x86-64函数调用约定的完整过程

209 浏览 0 回复
#1 楼主 2026-06-01 21:08:47
测试代码:
#include <stdio.h>

// 测试参数传递:6个参数(刚好全用寄存器)
int six_args(int a, int b, int c, int d, int e, int f) {
    return a + b + c + d + e + f;

// 测试参数传递:8个参数(超出6个,后两个走栈)
int eight_args(int a, int b, int c, int d, int e, int f, int g, int h) {
    return a + b + c + d + e + f + g + h;

// 测试callee-saved寄存器
int use_callee_saved(int x) {
    return arr[0] + arr[1] + arr[2] + x;

int main() {
    printf("six: %d\n", six_args(1, 2, 3, 4, 5, 6));
    printf("eight: %d\n", eight_args(1, 2, 3, 4, 5, 6, 7, 8));
    printf("saved: %d\n", use_callee_saved(42));
    return 0;
}运行环境:
    Ubuntu 20.04.6 LTS编译指令:
    gcc -g -O0 -o main main.cgcc版本    gcc version 9.4.0调试工具:    GDB

开始执行:进入main 函数,查看反汇编代码: endbr64  push   %rbp mov    %rsp,%rbp mov    $0x6,%r9d mov    $0x5,%r8d mov    $0x4,%ecx mov    $0x3,%edx mov    $0x2,%esi mov    $0x1,%edi callq  1169 <six_args> mov    %eax,%esi lea    0xd8c(%rip),%rdi mov    $0x0,%eax callq  1070 <printf@plt> pushq  $0x8 pushq  $0x7 mov    $0x6,%r9d mov    $0x5,%r8d mov    $0x4,%ecx mov    $0x3,%edx mov    $0x2,%esi mov    $0x1,%edi callq  11a3 <eight_args> add    $0x10,%rsp mov    %eax,%esi lea    0xd55(%rip),%rdi mov    $0x0,%eax callq  1070 <printf@plt> mov    $0x2a,%edi callq  11e7 <use_callee_saved> mov    %eax,%esi lea    0xd43(%rip),%rdi mov    $0x0,%eax callq  1070 <printf@plt> mov    $0x0,%eax leaveq  retq   验证函数在小于等于6个参数时,函数传参的方式是按照 rdi(函数的第一个参数),rsi,rdx,rcx,r8,r9(函数的第六个参数)顺序进行传参的反汇编代码如下: mov    $0x6,%r9d mov    $0x5,%r8d mov    $0x4,%ecx mov    $0x3,%edx mov    $0x2,%esi mov    $0x1,%edi进入six_args函数: 在执行call的过程中观察栈中的信息发现,在执行call命令后会自动的将call命令的下一跳指令的rip压入栈中(return address),保证函数在返回后可以回到当前执行流中继续执行  callq  1169 <six_args>  ; gdb查看rsp中的值  x /10x $rsp
反汇编代码如下; endbr64  push   %rbp mov    %rsp,%rbp mov    %edi,-0x4(%rbp) mov    %esi,-0x8(%rbp) mov    %edx,-0xc(%rbp) mov    %ecx,-0x10(%rbp) mov    %r8d,-0x14(%rbp) mov    %r9d,-0x18(%rbp) mov    -0x4(%rbp),%edx mov    -0x8(%rbp),%eax add    %eax,%edx mov    -0xc(%rbp),%eax add    %eax,%edx mov    -0x10(%rbp),%eax add    %eax,%edx mov    -0x14(%rbp),%eax add    %eax,%edx mov    -0x18(%rbp),%eax add    %edx,%eax pop    %rbp retq   
在函数调用时,会保存调用方的栈帧,为了防止被调用函数污染了调用方的栈帧,也是为了保证堆栈平衡即: push   %rbp mov    %rsp,%rbp继续向下执行: mov    %edi,-0x4(%rbp) mov    %esi,-0x8(%rbp) mov    %edx,-0xc(%rbp) mov    %ecx,-0x10(%rbp) mov    %r8d,-0x14(%rbp) mov    %r9d,-0x18(%rbp)发现函数在保存调用方的传参时,并没有为当前函数开辟一块栈空间来储存这些参数的值,而是直接使用了rbp向下增长来保存参数经过查阅发现 x86-64的System V ABI(Linux/macOS使用的调用约定)定义了一个叫做Red Zone红区的概念:大概就是在函数的调用过程中,rsp-128字节的空间被编译器都认为是安全的,可以直接使用,并不需要sub rsp, x
继续向下执行: mov    -0x4(%rbp),%edx mov    -0x8(%rbp),%eax add    %eax,%edx mov    -0xc(%rbp),%eax add    %eax,%edx mov    -0x10(%rbp),%eax add    %eax,%edx mov    -0x14(%rbp),%eax add    %eax,%edx mov    -0x18(%rbp),%eax add    %edx,%eax计算6个参数的值,最后结果保存在eax中作为函数的返回值,在x86-64 System V ABI规则中,函数的返回值:整数/指针的返回式是保存在rax(小的值可能保存在eax,ax, al)中的,而浮点数是保存在xmm0中
继续向下执行: 恢复栈帧,将栈帧恢复调用前的状态,retq后会取出压入的return address,修改rip的值,恢复调用方执行流 pop   

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291273.htm

暂无回复,快来抢沙发吧!

请登录后参与讨论

立即登录 注册账号