x86-64 寄存器只有6个寄存器来存参数,那 C 函数为什么还能超过6个参数

简介: x86-64 寄存器只有6个寄存器来存参数,那 C 函数为什么还能超过6个参数
#include <stdio.h>

int test(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10) {
    return arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10;
}

int main() {
    printf("%d", test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    return 0;
}
000000000040052d <test>:
  40052d:    55                       push   %rbp
  40052e:    48 89 e5                 mov    %rsp,%rbp
  400531:    89 7d fc                 mov    %edi,-0x4(%rbp)
  400534:    89 75 f8                 mov    %esi,-0x8(%rbp)
  400537:    89 55 f4                 mov    %edx,-0xc(%rbp)
  40053a:    89 4d f0                 mov    %ecx,-0x10(%rbp)
  40053d:    44 89 45 ec              mov    %r8d,-0x14(%rbp)
  400541:    44 89 4d e8              mov    %r9d,-0x18(%rbp)
  400545:    8b 45 f8                 mov    -0x8(%rbp),%eax
  400548:    8b 55 fc                 mov    -0x4(%rbp),%edx
  40054b:    01 c2                    add    %eax,%edx
  40054d:    8b 45 f4                 mov    -0xc(%rbp),%eax
  400550:    01 c2                    add    %eax,%edx
  400552:    8b 45 f0                 mov    -0x10(%rbp),%eax
  400555:    01 c2                    add    %eax,%edx
  400557:    8b 45 ec                 mov    -0x14(%rbp),%eax
  40055a:    01 c2                    add    %eax,%edx
  40055c:    8b 45 e8                 mov    -0x18(%rbp),%eax
  40055f:    01 c2                    add    %eax,%edx
  400561:    8b 45 10                 mov    0x10(%rbp),%eax
  400564:    01 c2                    add    %eax,%edx
  400566:    8b 45 18                 mov    0x18(%rbp),%eax
  400569:    01 c2                    add    %eax,%edx
  40056b:    8b 45 20                 mov    0x20(%rbp),%eax
  40056e:    01 c2                    add    %eax,%edx
  400570:    8b 45 28                 mov    0x28(%rbp),%eax
  400573:    01 d0                    add    %edx,%eax
  400575:    5d                       pop    %rbp
  400576:    c3                       retq

0000000000400577 <main>:
  400577:    55                       push   %rbp
  400578:    48 89 e5                 mov    %rsp,%rbp
  40057b:    48 83 ec 20              sub    $0x20,%rsp
  40057f:    c7 44 24 18 0a 00 00     movl   $0xa,0x18(%rsp)
  400586:    00
  400587:    c7 44 24 10 09 00 00     movl   $0x9,0x10(%rsp)
  40058e:    00
  40058f:    c7 44 24 08 08 00 00     movl   $0x8,0x8(%rsp)
  400596:    00
  400597:    c7 04 24 07 00 00 00     movl   $0x7,(%rsp)
  40059e:    41 b9 06 00 00 00        mov    $0x6,%r9d
  4005a4:    41 b8 05 00 00 00        mov    $0x5,%r8d
  4005aa:    b9 04 00 00 00           mov    $0x4,%ecx
  4005af:    ba 03 00 00 00           mov    $0x3,%edx
  4005b4:    be 02 00 00 00           mov    $0x2,%esi
  4005b9:    bf 01 00 00 00           mov    $0x1,%edi
  4005be:    e8 6a ff ff ff           callq  40052d <test>
  4005c3:    89 c6                    mov    %eax,%esi
  4005c5:    bf 70 06 40 00           mov    $0x400670,%edi
  4005ca:    b8 00 00 00 00           mov    $0x0,%eax
  4005cf:    e8 3c fe ff ff           callq  400410 <printf@plt>
  4005d4:    b8 00 00 00 00           mov    $0x0,%eax
  4005d9:    c9                       leaveq
  4005da:    c3                       retq
  4005db:    0f 1f 44 00 00           nopl   0x0(%rax,%rax,1)

关于函数的调用栈,https://www.cnblogs.com/clover-toeic/p/3755401.html 这篇文章写得挺好。

push %rbp

把 caller(相对于当前函数的调用函数)的函数栈的栈底的内存地址(存在 rbp 寄存器中)压栈(写入到栈上),注意会占用8字节的内存长度。
因为C程序的入口函数是_start,然后再经过一系列初始化函数,才到main,所以这里main里面也需要执行push %rbp,又比如test函数里面的push %rbp就是main函数的%rbp里记录的地址。
栈是由高位向低位生长,所以此时是在调用函数的rsp 基础上向低位移动,又因为x86-64机器上,地址占8字节,所以push %rbp的背后还有一个隐含的操作就是sub $0x8 %rsp(如果是32机器就是向下移动4字节)。

mov    %rsp,%rbp

把 caller 的栈顶的内存地址(存在 rsp 寄存器中)赋值到 rbp 寄存器。

sub    $0x20,%rsp

rsp再减去0x20,也就是地址再向下移动32个单元(字节),用于main函数局部变量和参数的存储。这里预留少空间,在编译期就计算好了。当前实验是10个参数,如果我们改为13个参数,则会是sub $0x50,%rsp所以生成的汇编代码,在运行之前就已经确定了rsp偏移量

movl   $0xa,0x18(%rsp)
movl   $0x9,0x10(%rsp)
movl   $0x8,0x8(%rsp)
movl   $0x7,(%rsp)

参数的压栈是从右向左的,最后面的参数最先处理,我们知道寄存器里存储函数参数的只有6个,所以还需要使用栈上的空间来存储其他参数。把0xa放在rsp+0x18的地址上,然后把0x9放在rsp+0x10的地址上,以此类推。

mov    $0x6,%r9d
mov    $0x5,%r8d
mov    $0x4,%ecx
mov    $0x3,%edx
mov    $0x2,%esi
mov    $0x1,%edi

把参数从右向左进行处理,剩余的6个则有专门的寄存器来存储。

callq  40052d <test>

调用test函数,背后隐含的一个操作是把调用test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)下一条指令mov %eax,%esi的地址压栈,这样rsp的地址会向下偏移8字节。

mov    %eax,%esi

test的返回值,放入esi作为printf的第二个参数

mov    $0x400670,%edi

0x400670 作为第一个参数传入printf0x400670 则是%d字符串常量,在只读取的地址

具体的之前在 https://mengkang.net/1383.html 的实验中做过说明

leaveq

这个指令是函数开头的push %rbpmov %rsp,%rbp的逆操作。把rbp赋值给rsp,然后把rsp+0x8

retq

retq指令,它是callq指令的逆操作。在上面执行leaveq之后,再把0x8(rsp)里保存的调用函数的下一行代码的地址取出来赋值给riprip程序计数器,负责从哪里开始执行代码),然后再次rsp+0x8,就从test恢复到main里面了。

test函数的操作就比较简单了,就是把寄存器的值压栈,然后相加计算,返回。

WX20190914-071635@2x.png

目录
相关文章
|
Java 数据处理
Java Scanner 类详解
`Scanner` 类是 Java 中 `java.util` 包提供的强大工具,用于从多种输入源(如键盘、文件、字符串)读取数据。本文详细介绍如何创建 `Scanner` 对象并使用其常用方法(如 `next()`, `nextInt()`, `nextLine()` 等)。通过示例代码展示如何从标准输入、字符串及文件中读取数据,并进行输入验证。使用时需注意关闭 `Scanner` 以释放资源,并确保输入类型匹配,避免异常。掌握 `Scanner` 可显著提升程序的数据处理能力。
508 1
|
11月前
|
IDE 编译器 开发工具
【C语言】全面系统讲解 `#pragma` 指令:从基本用法到高级应用
在本文中,我们系统地讲解了常见的 `#pragma` 指令,包括其基本用法、编译器支持情况、示例代码以及与传统方法的对比。`#pragma` 指令是一个强大的工具,可以帮助开发者精细控制编译器的行为,优化代码性能,避免错误,并确保跨平台兼容性。然而,使用这些指令时需要特别注意编译器的支持情况,因为并非所有的 `#pragma` 指令都能在所有编译器中得到支持。
960 41
【C语言】全面系统讲解 `#pragma` 指令:从基本用法到高级应用
|
11月前
|
监控 搜索推荐 API
京东按图搜索京东商品(拍立淘)API接口的开发、应用与收益
京东通过开放商品详情API接口,尤其是按图搜索(拍立淘)API,为开发者、企业和商家提供了创新空间和数据支持。该API基于图像识别技术,允许用户上传图片搜索相似商品,提升购物体验和平台竞争力。开发流程包括注册账号、获取密钥、准备图片、调用API并解析结果。应用场景涵盖电商平台优化、竞品分析、个性化推荐等,为企业带来显著收益,如增加销售额、提高利润空间和优化用户体验。未来,随着数字化转型的深入,该API的应用前景将更加广阔。
496 1
|
物联网 网络性能优化 网络架构
关闭 2.4GHz 会让 5GHz 更快吗?
【5月更文挑战第10天】
1957 4
关闭 2.4GHz 会让 5GHz 更快吗?
|
11月前
|
IDE JavaScript 开发工具
一、鸿蒙应用开发快速体验
本文介绍了鸿蒙应用开发的快速体验流程,涵盖从开发环境的准备到项目的实际运行。首先,需安装并配置华为DevEco Studio IDE,该IDE基于IntelliJ IDEA Community构建,旨在为鸿蒙应用开发提供一站式解决方案。接着,通过创建新项目、选择合适的模板及配置项目信息,可以迅速搭建起应用的基本框架。最后,本文还详细描述了如何利用Preview预览、模拟器运行以及真机调试三种方式来测试和优化应用,确保其在不同场景下的兼容性和性能表现。整个过程旨在帮助开发者快速入门鸿蒙应用开发,提升开发效率。
365 0
一、鸿蒙应用开发快速体验
|
Linux UED iOS开发
|
存储 算法 编译器
【C++ 引用 】C++深度解析:引用成员变量的初始化及其在模板编程中的应用(一)
【C++ 引用 】C++深度解析:引用成员变量的初始化及其在模板编程中的应用
1781 0
|
SQL 关系型数据库 数据库
postgresql数据库修改参数的方式
在PostgreSQL数据库中,你可以通过多种方式修改数据库参数,以更改其行为。以下是一些常见的修改数据库参数的方式: 1. **通过配置文件修改(postgresql.conf):** PostgreSQL的配置文件是 `postgresql.conf`。你可以直接编辑该文件,找到要修改的参数,修改其值,然后重新启动PostgreSQL服务以使更改生效。 通常,`postgresql.conf` 文件位于 PostgreSQL 数据目录下。修改完毕后,确保重新启动 PostgreSQL 服务。 2. **使用 ALTER SYSTEM 命令:** PostgreSQL
916 2
|
SQL 分布式计算 关系型数据库
dump
【7月更文挑战第20天】
391 2
|
安全 Java 测试技术
谈谈springboot的单例模式
【4月更文挑战第13天】在 Spring Boot 和更广泛的 Spring 框架中,单例模式扮演着核心的角色,特别是在 Spring 的 Bean 生命周期和管理中。这里我们详细探讨一下 Spring Boot 中单例模式的运作原理、优势及其潜在问题。
539 7

热门文章

最新文章