关于CSAPP lab3中压栈问题引发的思考


之前有个问题也没特别注意,今天回来看邮件发现有同学和我讨论关于函数调用压栈的问题。



废话少说,直接上对比测试图:

图一:CSAPP lab3的getbuf反汇编结果截图



图二: 我测试,节选了部分的getbuf实现,然后很简单的去测试getbuf的反汇编结果,反汇编结果如下图


我究竟是怎么测试的:

unsigned long long getbuf()
{
  char buf[36];
  volatile char* variable_length;
  int i;
  unsigned long long val;

  return val % 40;
}

int main()
{
    getbuf();
    return 0;
}

个人还是觉得测试代码没问题的。主要就是观察buf数组到底怎么被压栈的。保留其他的局部变量就可以了,理论上不会影响esp自减开辟新栈的大小。


但是但是。。。

Q1:

童鞋们能很明显的看出这里前后两个反汇编的结果不同。

前者开辟了0x30的大小的stack frame

后者开辟了0x40的大小的stack frame。


Q2:

利用CSAPP的可执行程序,gdb调试,然后查看反汇编程序。

观察寄存器rbp和buf的地址。


也就是说 buf数组距离栈基地址(rbp寄存器指向处),相差了0x30的大小。

而这段区域内应该只存在buf数组元素,无其他变量或者寄存器被压栈。于是我就怀疑这个buf数组(本来只有36byte)被对齐到了48byte(0x30), 但是这又无法解释, 因为如果按照64位操作系统的话,8byte对齐,只要对齐到

0x28就可以了,不用0x30. 这里相差的8byte就是个疑问。



再次深入讨论:(强调!可以抛开上面的问题不看,直接看下面的讨论分析)

测试环境: Linux/gcc-4.8.2 64bits


特别感谢@Black.(Runshen) ,@andygordo 提出的宝贵意见


gcc对于栈内对齐是有规则且有参数选项的。

http://stackoverflow.com/questions/1061818/stack-allocation-padding-and-alignment


这个世界上也有以其他人也曾遇到过这个问题的。。。所以嘛~就是要死磕...


关于参数 -mpreferred-stack-boundary=

图3



当SSE保护被关闭的时候,边界对齐是可以被调整到 8 byte对齐的。但是,一般默认都是16byte栈内对齐。


我们可以尝试修改这个编译参数选项去观察压栈时栈的大小变化。

我们最新的测试代码:

#include <stdio.h>

unsigned long long getbuf()
{
  char buf[36];
  volatile char* variable_length = &buf;
  int i = 0;
  unsigned long long val = buf;

  printf("%x %x %x\n", variable_length, i, val);

  return val % 40;
}

int main()
{
    getbuf();
    return 0;
}

你能看到我这里添加了printf,而且还给各种变量赋初值。不为别的。。。就是为了不让编译器优化掉。。

如果你不给i赋值,并且也不用这个变量。你亲自调试的时候会发现,i变量被 optimized out。。。

只有保证变量不被优化,我们才能更具体的讨论压栈的时候发生了什么




图4



图5 (对于上述内存布局的解释)


我特意把对齐条件增加到了 2^4 == 16byte对齐。

于是。要开辟 char buf[36];的时候36byte的数组会向上对齐到16byte的整数倍,即此处buf会对齐到48byte == 0x30.

但是!这里却给buf预留了0x40 byte的空间(&buf和%rbp之间的空间),这就有问题,多了0x10byte的空间。


这是为什么呢? 看反汇编代码吧。。。




我遇到过很多次,push %rbx 这个8byte的寄存器被压栈,由于16byte对齐,于是就变成下面这个样子了



然后你还要注意一下那两句

mov %fs:0x28, %rax

mov %rax , -0x18(%rbp)

把fs段偏移0x28处的一个数据储存到了 rbp下面0x18距离处。这是为了防止缓冲区溢出的手段,金丝雀数 canary number.




有同学肯定要问,为什么要push %rbx,这个寄存器是干嘛的?保存它的值干嘛?

http://stackoverflow.com/questions/12736437/why-does-gcc-push-rbx-at-the-beginning-of-main

The main function is like any other function in this context. gcc decided to use ebx for intermediate calculations, so it preserves its value.





1.栈内的对齐是编译器参数可变的,当前版本的gcc(4.8.2)默认的是16byte对齐

2.你会发现我都把gcc的编译参数选项很细致的截图了,而且加上了 -O0, 尽量的不要让gcc去优化代码。亲测,你不开启这货,gcc就会默认-O2,然后有些局部变量压根就不会给你分配栈上空间,而是直接用寄存器。。。,当然,这个问题只是影响栈的大小而已,不会影响到我们讨论的buf对齐的空间问题  

3. 注意下那个金丝雀数,当前的gcc是为了防止缓冲区溢出设置的(并不在实验范围内,因为CSAPP的实验材料是老师故意准备好的,不会有防止缓冲区溢出的手段存在,这里是我们实测的,一般通用的gcc所生成的安全代码反汇编的探究分析结果)


如有问题,欢迎进一步的交流讨论 : )


2015.05.30 下午5时~6时30分有多次update改动,见谅~




  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值