二进制拆弹专家实验(lab3 phase_2)

phase_2:

  1. 汇编码分析:

0000000000400efc <phase_2>:
  400efc:    55                       push   %rbp
  400efd:    53                       push   %rbx
  400efe:    48 83 ec 28              sub    $0x28,%rsp //开栈分配4*6 40字节
  400f02:    48 89 e6                 mov    %rsp,%rsi //栈顶地址存入%rsi
  400f05:    e8 52 05 00 00           callq  40145c <read_six_numbers> //调用函数读取6个数
  400f0a:    83 3c 24 01              cmpl   $0x1,(%rsp) //比较1与rsp中的数是否一样,一样就继续
  400f0e:    74 20                    je     400f30 <phase_2+0x34> //一样
  400f10:    e8 25 05 00 00           callq  40143a <explode_bomb>//不一样 bomb!
  400f15:    eb 19                    jmp    400f30 <phase_2+0x34>
  400f17:    8b 43 fc                 mov    -0x4(%rbx),%eax // 第一个数存进%eax
  400f1a:    01 c0                    add    %eax,%eax //左移一位,*2
  400f1c:    39 03                    cmp    %eax,(%rbx) //比较前一个数是否为%rbx中后一个数的2倍 
  400f1e:    74 05                    je     400f25 <phase_2+0x29> //是 跳转到0x400f25
  400f20:    e8 15 05 00 00           callq  40143a <explode_bomb> //不是 bomb!
  400f25:    48 83 c3 04              add    $0x4,%rbx //%rbx后移4字节 变成下一个数
  400f29:    48 39 eb                 cmp    %rbp,%rbx //比较最后一个数和%rbp中的数是否相等
  400f2c:    75 e9                    jne    400f17 <phase_2+0x1b> //是 循环一遍之前的
  400f2e:    eb 0c                    jmp    400f3c <phase_2+0x40> //不是 跳到0x400f3c 结束循环
  400f30:    48 8d 5c 24 04           lea    0x4(%rsp),%rbx //%rsp中偏移4字节 将第二个数字存入%rbx
  400f35:    48 8d 6c 24 18           lea    0x18(%rsp),%rbp //%rsp再偏移24个字节,第7个数存进%rsp
  400f3a:    eb db                    jmp    400f17 <phase_2+0x1b> //跳转进循环
  400f3c:    48 83 c4 28              add    $0x28,%rsp
  400f40:    5b                       pop    %rbx
  400f41:    5d                       pop    %rbp
  400f42:    c3                       retq   

答案:1 2 4 8 16 32

等效代码:


void phase_2() {
    int numbers[6];
    read_six_numbers(numbers);  // 读取6个数
    
    int* end = &numbers[6];  // 计算“第七个数”的地址(实际未存储数据)
    for (int* ptr = &numbers[1]; ptr < end; ptr++) {
        // 验证每个数是否为前一个数的两倍
    }
}

问题思考

  1. 为什么看起来像 “存了第七个数”?

 “第七个数” 实际是一个内存地址标记,而非真正存储了第七个数值。

关键代码分析
400f35: 48 8d 6c 24 18        lea    0x18(%rsp),%rbp  ; %rbp = &numbers[6](第七个数的位置)
...
400f29: 48 39 eb              cmp    %rbp,%rbx        ; 比较当前位置是否到达%rbp
400f2c: 75 e9                 jne    400f17           ; 若未到达,继续循环
为什么需要这个地址?
  1. 循环终止条件
    函数使用 %rbp 作为循环的终止标记。虽然用户只输入 6 个数(numbers[0]~numbers[5]),但通过计算 0x18(%rsp)(即 &numbers[6]),函数创建了一个边界地址。当 %rbx 递增到这个地址时,表示所有 6 个数都已验证完毕。

  2. 内存布局
    栈上分配的空间为 24 字节(6 个整数),但函数通过 lea 计算了一个超出该区域的地址(%rsp + 24)。这个地址本身未被写入数据,但作为指针比较的终点。

  3. 安全设计
    这种方式避免了硬编码循环次数(如 cmp $6, %ecx),而是通过指针比较动态确定循环终止点,使代码更健壮。

这是一种常见的编程技巧,称为哨兵值(Sentinel)边界标记: 

  • 函数并未真正存储第七个数,而是计算了一个超出有效数据范围的地址
  • 这个地址仅用于比较,不参与数据验证,因此不会导致越界访问。

为什么40 字节的栈空间看似可容纳 10 个整数(每个整数 4 字节),但实际只需存储 6 个整数(24 字节)。剩余 16 字节可能用于对齐或临时变量? 

内存对齐 和 临时变量存储 的设计考量:

1. 栈空间分配的基本规则

在 x86-64 架构中,栈操作通常遵循以下原则:

  • 对齐要求:栈指针必须始终保持 16 字节对齐(部分指令如 SSE 浮点运算强制要求)。
  • 函数调用约定:调用函数前,栈指针需调整为 16 的倍数,以满足被调用函数的对齐需求。
2. 具体案例分析
400efe: 48 83 ec 28           sub    $0x28,%rsp  ; 分配40字节栈空间(0x28 = 40)
  • 用户输入:6 个整数需要 24 字节(6 × 4 = 24)。
  • 实际分配:40 字节(超过需求 16 字节)。
3. 为什么多分配 16 字节?
① 内存对齐需求
  • 初始状态:假设调用phase_2前栈指针%rsp为 16 的倍数(如0x7fffffffe000)。
  • 函数调用开销
    callq指令会将返回地址压栈(8 字节),导致%rsp变为 非 16 字节对齐(如0x7fffffffdff8)。
  • 调整栈指针
    为满足 16 字节对齐,需分配 40 字节(而非 24 字节),使%rsp调整为:

    plaintext

    0x7fffffffdff8 - 40 = 0x7fffffffdfe0  →  16的倍数(0x7fffffffdfe0 ÷ 16 = 整数)
    
② 临时变量存储
  • 函数可能需要额外空间存储中间值,例如:
    400f05: e8 52 05 00 00        callq  40145c <read_six_numbers>
    

    read_six_numbers可能将输入解析到栈上的临时缓冲区,再复制到最终位置。
4. 内存布局示例
栈地址(低到高)                用途
0x7fffffffdfe0 ──────────────┐
                            │
...                         │  40字节栈空间
                            │
0x7fffffffe008 ──────────────┘
 ↑                             ↑
 |- %rsp(调整后)           |- %rsp(调用前)
                            ↑
                            |- 返回地址(8字节)
5. 为什么不精确分配 24 字节?

如果只分配 24 字节:

sub $0x18,%rsp  ; 24字节(0x18 = 24)
  • 调整后的%rsp为:
    0x7fffffffdff8 - 24 = 0x7fffffffdfe8  →  非16的倍数!
    
  • 这会导致后续指令(如调用其他函数)因对齐错误而性能下降或崩溃。
总结
  • 多分配的 16 字节主要用于:
    1. 满足 16 字节对齐要求,确保栈指针在函数执行期间保持对齐。
    2. 存储临时变量或中间结果(如函数调用时的参数传递)。
  • 这种设计虽然浪费了部分栈空间,但提高了内存访问效率,避免了潜在的对齐错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值