开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 4047|回复: 10
收起左侧

[分享] 浅谈X64汇编中关于16字节对齐的有趣表现

[复制链接]
发表于 2021-3-1 23:18:12 | 显示全部楼层 |阅读模式   浙江省绍兴市
本帖最后由 树上的鱼儿 于 2021-3-2 08:46 编辑

X64汇编:浅谈关于16字节对齐的有趣表现
本编文章也作为个人学习过程中的心得记录,同时作为帖子发表个人的浅薄见解,如有错误,忘超神们嘴下留情,并客观指出问题,在此谢过。
本文可能不适合无x64汇编基础的朋友,因为这里不阐述汇编基础,也不详细阐述x64调用约定,只谈谈个人对x64汇编中16字节对齐的看法,有过学习汇编的朋友都不陌生对齐的说法,尤其是涉及x64汇编,基本都听过这个感念,那么到底什么是对齐,x64汇编为什么要对齐,并且为什么都说是16字节对齐,有的朋友可能光听说或看过一下网络文章,那么自己有没有佐证什么呢????
这里我先上一张图,朋友们仔细观察,也许你看过这张图,如柳暗花明般已扫清阴霾。
堆栈.png

  上图为一张调试器下断点后,堆栈空间显示的内容,我这大概给编辑了一下图片,方便观察。
  我们知道 rsp表示当前rip位置的堆栈指针,从图中我们很明显的看到,所有call压入的返回地址所存储的rsp指针结尾数值都是8,对都是8,你没看错,十六进制的8,那么这个8跟16字节对齐有关系吗?
  由于x64调用约定的关系,是由调用者平栈,我们在汇编函数体内部基本都能看到

调用函数前减堆栈  sub rsp,0x多少

调用函数后加堆栈  add rap,0x多少

也就说在call前由调用者申请栈空间,调用call以后由调用者平栈空间。

很多网络上的文章五花八门,对这个x64汇编16字节对齐的说话五花八门,有的说在call前的抬起的栈大小值要16字节对齐,每次0x10称为16字节对齐,甚至更有说者把call返回地址也算进去,4个参数的话sub rsp,0x28,可4个参数明明是0x20呀,等等众说纷纭,看的能把我这个小白搞蒙逼,搞得不知谁对谁错,一头雾水。

我们用实践观察来检验分析一下,不评论网络文章,我们只要搞清楚,到底怎么样才算对齐,我们需要注意什么?
我们可以观察到的情况是,所有call压入的返回地址所存储的rsp指针结尾数值都是8,我们知道call这个指令执行后会压入这个返回地址,然后rsp-8,进入函数体以后由被调用方尾部ret返回,然后rsp+8,那么倒推一下,就是在调用call指令前,rsp的值=返回地址所存储的rsp指针+8
那么也就是说在调用这条call指令前当时的 rsp栈指针其实是0x?????????????0
在调用这个call指令后进入了函数体后       rsp栈指针其实是0x?????????????8
函数体执行完整个代码尾部ret返回后        rsp栈指针其实是0x?????????????0
以上完成整个 栈指针16字节对齐过程,调用者在调用call前与调用call返回后栈指针无变化。

总结而言就是所谓堆栈16字节对齐就是在 代码走到call这条指令的时候 rsp栈指针其实是0x?????????????0
对,rsp的最后一位数值,最后一位数值是十六进制0,每条call指令的经过都是这个情况,那么所谓的call前
sub rsp,0x多少 这个抬起多少其实是根据函数体内部其他栈操作指令的影响来决定的,总是当运行到call这条指令的时候rsp最后一位值必须是0
所有的栈指针8进位,结尾不是0就是8,因为十进制8=十六进制8,而十进制8+8=16=十六进制10
那么在所有函数体的第一句代码未执行的时候,rsp最后一位值必须是8

以下用两个列子说明情况,先给上x64 参数的调用规律

  1. ' ----------调用call时参数的赋值规律
  2. ' mov [rsp+28],参数六
  3. ' mov [rsp+20],参数五
  4. ' mov r9,参数四
  5. ' mov r8,参数三
  6. ' mov rdx,参数二
  7. ' mov rcx,参数一
  8. ' CALL 函数入口
  9. ' ----------call内部参数入栈规律
  10. ' call以后函数内部rsp的指向返回地址,实际在函数里各参数的地址是
  11. ' mov 参数六,[rsp+30]
  12. ' mov 参数五,[rsp+28]
  13. ' mov [rsp+20],r9 参数四
  14. ' mov [rsp+18],r8 参数三
  15. ' mov [rsp+10],rdx 参数二
  16. ' mov [rsp+8],rcx 参数一
复制代码


比如进入一个函数体后会在调用一个1个参数的call

进入一个新函数体由于返回地址的压入,rsp不对齐,假如现在的rsp=0x00000000000028
注意观察每句指令结束后rsp的值
  1. Push rbp    // rsp=0x000000000000020
  2. Push rdi    // rsp=0x00000000000018
  3. Push r14    // rsp=0x0000000000010
  4. sub rsp,0x10   // rsp=0x00000000000000 //一个参数应该是0×8rsp-0x8,但是为了对齐必须抬0×10
  5. mov rcx,0   // rsp=0x00000000000000  //参数一
  6. call 0xfffffffffffffff // rsp=0x00000000000000 返回后依然不变
  7. add rsp,0x10   // rsp=0x000000000000010 //平栈
  8. pop r14    // rsp=0x00000000000018
  9. pop rdi    // rsp=0x000000000000020
  10. pop rbp    // rsp=0x000000000000028
复制代码

比如在调用一个2个参数的call

假如现在的rsp=0x00000000000030是对齐状态
注意观察每句指令结束后rsp的值
  1. Push rbp    // rsp=0x00000000000028
  2. Push rdi    // rsp=0x00000000000020
  3. Push r14    // rsp=0x00000000000018
  4. sub rsp,0x18   // rsp=0x00000000000000  //两个参数其实0x10就够了,这里0x18就是为了16字节对齐
  5. mov rcx,0   // rsp=0x00000000000000//参数一
  6. mov rdx,0   // rsp=0x00000000000000//参数二
  7. call 0xfffffffffffffff   // rsp=0x00000000000000  //返回后依然不变
  8. add rsp,0x18   // rsp=0x00000000000018//平栈
  9. pop r14    // rsp=0x00000000000020
  10. pop rdi    // rsp=0x00000000000028
  11. pop rbp    // rsp=0x00000000000030
复制代码



以上列子充分说明 sub rsp,0x值,这个值是由当时函数体情况而定的,目的只有一个在走到call这个指令前rsp最后一位数值必须为0
有的朋友取尝试用调试器下个断点,在观察堆栈可能会疑惑有的返回值地址栈指针最后一位是8,那么这个指针指向的仅仅是一个其他需要而存储的一个值而已,只是调试器把他解释为调用的返回地址而已,这个指针肯定不会被用来返回的,不信的话你可以单步调试,走到每句call指令时rsp最后一位数值必为0
这就是x64汇编中比较有意思的事情,今天的趣谈就到这里吧,欢迎指正。











评分

参与人数 3好评 +3 精币 +8 收起 理由
qq84500313 + 1 + 2 大神,请收下我的膝盖
猪滴寳貝哝 + 1 + 2 此处应该有鼓励~
福仔 + 1 + 4 感谢分享,很给力!~

查看全部评分

本帖被以下淘专辑推荐:

结帖率:33% (2/6)

签到天数: 17 天

发表于 2024-6-1 10:19:19 | 显示全部楼层   广东省湛江市
感谢分享,很给力!~
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)

签到天数: 1 天

发表于 2022-7-22 19:08:16 | 显示全部楼层   广东省深圳市
曲高和寡。。这篇帖子是为数不多真正说清了x64栈帧内rsp传参,以及x64堆栈平衡的好帖子,膜拜,让人豁然开朗。
回复 支持 反对

使用道具 举报

发表于 2021-3-26 19:51:52 | 显示全部楼层   广东省肇庆市
所得到的多多多多多多多多多多付00
回复 支持 反对

使用道具 举报

发表于 2021-3-15 21:37:58 | 显示全部楼层   广东省肇庆市
顶下大神!
回复 支持 反对

使用道具 举报

发表于 2021-3-6 18:59:29 | 显示全部楼层   福建省龙岩市
本帖最后由 冰封夕阳 于 2021-3-6 19:16 编辑

回复错帖子了
回复 支持 反对

使用道具 举报

签到天数: 8 天

 楼主| 发表于 2021-3-2 20:05:28 | 显示全部楼层   浙江省绍兴市
福仔 发表于 2021-3-2 08:50
我到现在都没了解x64是怎么完成一次call传递参数的, x86都是push, 看x64好像并不是使用push.... ...

是的 x64汇编调用函数 参数不用push,而是 前4个用寄存器,其他用 已经申请好的栈空间赋值,所以在调用call前需要由调用者来控制栈指针的抬起和平栈,帖子中已经附了调用规则,朋友可以仔细看看
回复 支持 反对

使用道具 举报

签到天数: 8 天

发表于 2021-3-2 10:25:31 | 显示全部楼层   江西省宜春市
谢谢分享!
回复 支持 反对

使用道具 举报

结帖率:100% (9/9)

签到天数: 18 天

发表于 2021-3-2 08:50:01 | 显示全部楼层   广东省揭阳市
我到现在都没了解x64是怎么完成一次call传递参数的, x86都是push, 看x64好像并不是使用push....
回复 支持 反对

使用道具 举报

签到天数: 8 天

 楼主| 发表于 2021-3-2 08:30:35 高大上手机用户 | 显示全部楼层   浙江省杭州市
我的小拇指啊 发表于 2021-3-2 07:47
有些sse指令需要地址16字节对齐才能访问,不然会异常
新的编译器都开始用sse了 ...

对的,所以有些函数不对也不异常
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表