x86-64 寄存器只有6个寄存器来存参数,那 C 函数为什么还能超过6个参数-阿里云开发者社区

开发者社区> 周梦康> 正文

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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【C/C++】cctype中的字符函数
c++中头文件为 ,c中则是。 这些函数以一个数值或者字符作为参数并返回布尔值true或flase,或者是字符,具体因函数不同 这里面的函数可以分为两类: 判断函数 它们检查输入参数是否属于某个类别: isalnum...
673 0
函数式编程初探
函数式编程初探  (原文地址:http://blog.jobbole.com/17228/) 04月 11, 2012 at 9:50 am by 齐哲 Tags: Erlang, 函数式编程, 编程语言   诞生50多年之后,函数式编程(functional programming)开始获得越来越多的关注。   不仅最古老的函数式语言Li
1140 0
7.2 函数的参数
1、给 b 变量设定一个默认的值 如果实参传入的时候,指定了 b 的值,那 b 优先选择传入的实参,当 b 没有值时,才会用默认值 def funcA(a,b=0):     print(a)     print(b) funcA(1)        # b 变量选择默认实参...
469 0
Linux系统中使用GCC CPU参数优化代码编译
Linux系统中使用GCC CPU参数优化代码编译 使用特定的GCC参数可以使编译出的程序执行效率有较大提升。具体如下: 1、优化原理: 在编译程序时,借助参数传递的方法,使用与系统CPU相匹配的gcc参数,编译出的程序就是为系统CPU而进行特定优化过的,因而执行速度和效率都会是最好。
731 0
C++函数对象
  原文:http://blog.csdn.net/ggggqqqqihc/article/details/1727020   标准库里的count_if可以统计容器中满足特定条件的元素的个数。例如要统计一个整数vector——ivec中正数的个数,可以先写一个返回类型为bool,含有一个int参数的条件函数: bool pred(int val){ return val>0; }    之后可以用count_if(ivec.begin(),ivec.end(),pred)计算出正整数的个数。
465 0
可变参数函数模板
16.53 编写你自己版本的print函数,并打印一个、两个及五个实参来测试它,要打印的每个实参都应有不同的类型。 #include #include using namespace std; template ostream& print(ostream &os,const T...
508 0
+关注
周梦康
十年前从 LNMP 开始个人站长 mengkang.net 生涯。 分享各种线上故障复盘笔记,关注我,防止采坑。
115
文章
70
问答
来源圈子
更多
PHP学习资料大全
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载