当前位置: 首页 > news >正文

64位x86的函数调用栈布局

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net    linuxfocus.blog.chinaunix.net

在看本文之前,如果不了解x86的32位机的函数布局的话,建议先阅读一下前一篇文章《如何手工展开函数栈定位问题》—— http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=3029639

为啥还要就64位的情况单开一篇文章呢,难道64位与32位不一样吗?

还是先看测试代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>


  3. static void test(void *p1, void *p2, int p3)
  4. {
  5.     p1 = p1;
  6.     p2 = p2;
  7.     p3 = p3;
  8. }

  9. int main()
  10. {
  11.     void *p1 = (void*)1;
  12.     void *p2 = (void*)2;
  13.     int p3 = 3;

  14.     test(p1, p2, p3);

  15.     return 0;
  16. }
编译gcc -g -Wall test.c,调试进入test
  1. (gdb) bt
  2. #0 test (p1=0x1, p2=0x2, p3=3) at test.c:10
  3. #1 0x0000000000400488 in main () at test.c:18

查看寄存器bp
  1. (gdb) info registers rbp
  2. rbp 0x7fffab620d00 0x7fffab620d00

那么检查栈的内容
  1. (gdb) x /16xg 0x7fffab620d00
  2. 0x7fffab620d00: 0x00007fffab620d30 0x0000000000400488
  3. 0x7fffab620d10: 0x00000000004004a0 0x0000000000000002
  4. 0x7fffab620d20: 0x0000000000000001 0x0000000300000000
  5. 0x7fffab620d30: 0x0000000000000000 0x00007f93bbaa11c4
  6. 0x7fffab620d40: 0x0000000000400390 0x00007fffab620e18
  7. 0x7fffab620d50: 0x0000000100000000 0x0000000000400459
  8. 0x7fffab620d60: 0x00007f93bc002c00 0x85b4aff07d2e87c7
  9. 0x7fffab620d70: 0x0000000000000000 0x00007fffab620e10

开始分析栈的内容:
1. 0x00007fffab620d30:为test调用者main的BP内容,没有问题;
2. 0x0000000000400488:为test的返回地址,与前面的bt输出相符,没有问题;
3. 0x00000000004004a0:——这个是什么东东??!!
4. 0x0000000000000002, 0x0000000000000001, 0x0000000300000000:这里也有不少疑问啊?!
1.  这个0x00000003是第3个参数?因为是整数所以在64位的机器上,只使用栈的一个单元的一半空间?
2. 参数的顺序为什么是3,1,2呢?难道是因为前两个参数为指针,第三个参数为int有关?

我在工作中遇到了类似的问题,所以才特意写了上面的测试代码,就为了测试相同参数原型的函数调用栈的问题。看到这里,感觉很奇怪,对于上面两个问题很困惑啊。上网也没有找到64位的x86函数调用栈的特别的资料。

难道64位机与32位机有这么大的不同?!大家先想一下,答案马上揭晓。

当遇到疑难杂症时,汇编则是王道:
  1. (gdb) disassemble main
  2. Dump of assembler code for function main:
  3. 0x0000000000400459 : push %rbp+0>
  4. 0x000000000040045a : mov %rsp,%rbp+1>
  5. 0x000000000040045d : sub $0x20,%rsp+4>
  6. 0x0000000000400461 : movq $0x1,-0x10(%rbp)+8>
  7. 0x0000000000400469 : movq $0x2,-0x18(%rbp)+16>
  8. 0x0000000000400471 : movl $0x3,-0x4(%rbp)+24>
  9. 0x0000000000400478 : mov -0x4(%rbp),%edx+31>
  10. 0x000000000040047b : mov -0x18(%rbp),%rsi+34>
  11. 0x000000000040047f : mov -0x10(%rbp),%rdi+38>
  12. 0x0000000000400483 : callq 0x400448+42>
  13. 0x0000000000400488 : mov $0x0,%eax+47>
  14. 0x000000000040048d : leaveq+52>
  15. 0x000000000040048e : retq+53>
  16. End of assembler dump.
看红色部分的汇编代码,为调用test时的处理,原来64位机器上,调用test时,根本没有对参数进行压栈,所以上面对于栈内容的分析有误。后面的内存中存放的根本不是test的参数。看到汇编代码,我突然想起,由于64位cpu的寄存器比32位cpu的寄存器要多,所以gcc会尽量使用寄存器来传递参数来提高效率。

让我们重新运行程序,再次在test下查看寄存器内容:
  1. (gdb) info registers
  2. rax 0x7f141fea1a60 139724411509344
  3. rbx 0x7f14200c2c00 139724413742080
  4. rcx 0x4004a0 4195488
  5. rdx 0x3 3
  6. rsi 0x2 2
  7. rdi 0x1 1
  8. rbp 0x7fff9c08d380 0x7fff9c08d380
  9. rsp 0x7fff9c08d380 0x7fff9c08d380
这里rdx,rsi和rdi清晰的显示了三个参数的值,分别为3,2,1与前面的反汇编代码相符。

而前面被当做参数的0x0000000000000002, 0x0000000000000001和0x00000003,其实为main中的局部变量p2, p1和p3的定义。如前面反汇编代码中的蓝色代码,这三个局部变量在栈上的定义顺序为p3, p1和p2,与栈的内容相符。


我写本文的目的,主要是为了与大家分享一下64位机器上调试时需要注意的一个问题:函数调用时,编译器会尽量使用寄存器来传递参数,这点与32位机有很大不同。在我们的调试中,要特别注意这点。


注:关于压栈顺序,参数的传递方式等等,都可以通过编译选项来指定或者禁止的。本文的情况为GCC的默认行为。

相关文章:

  • 纯文本配置还是注册表
  • “你的优势是什么?
  • 记录项目代码迁移后,UI测试框架的搭建(配置文件的修改、测试脚本试运行)...
  • QComboBox 树形视图选择
  • 用户28万、营收超1亿,《生化危机》给VR游戏做了个好榜样
  • 验证数据过程中碰到的问题记录
  • Python--多进程
  • IE安全系列之:中流砥柱(I)—Jscript 5处理浅析
  • python一条语句分析几个常用函数和概念
  • 使用strtok_s函数从一个字符串中分离出单词
  • Android Studio_更新Gradle
  • MyBatis源码解读之SqlSession
  • 【小松教你手游开发】【系统模块开发】根据上一个GameObject坐标生成的tips界面...
  • 观察者模式在One Order回调函数中的应用
  • grep sed awk 练习题
  • “大数据应用场景”之隔壁老王(连载四)
  • 3.7、@ResponseBody 和 @RestController
  • Android交互
  • Bytom交易说明(账户管理模式)
  • eclipse的离线汉化
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • React-flux杂记
  • Spring Boot快速入门(一):Hello Spring Boot
  • spring security oauth2 password授权模式
  • windows下如何用phpstorm同步测试服务器
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 类orAPI - 收藏集 - 掘金
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 如何进阶一名有竞争力的程序员?
  • 深度解析利用ES6进行Promise封装总结
  • 跳前端坑前,先看看这个!!
  • 携程小程序初体验
  • #AngularJS#$sce.trustAsResourceUrl
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (5)STL算法之复制
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (十六)Flask之蓝图
  • (未解决)macOS matplotlib 中文是方框
  • (转)socket Aio demo
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (转载)(官方)UE4--图像编程----着色器开发
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • ./configure,make,make install的作用
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .gitignore文件---让git自动忽略指定文件
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • .NET简谈设计模式之(单件模式)
  • .net快速开发框架源码分享
  • .NET微信公众号开发-2.0创建自定义菜单
  • ?php echo $logosrc[0];?,如何在一行中显示logo和标题?