计算机系统课程 笔记总结 CSAPP第三章 程序的机器级表示(3.8-3.10)

3.8.1 基本原则

数据类型T和整形常数N,声明:

  • T A[N]
  • 在内存中分配 L×N 字节的连续区域(L是数据类型T的大小)
  • 起始位置表示为 Xa ,标识符 A 作为数组开头的指针
  • 数组元素 i 会被存放在地址为 Xa+L×i的地方

例如,假设E是一个int型的数组,而我们想计算E[i],E的地址存放在寄存器%rdx中,而i存放在寄存器%rcx中。然后,指令

  • movl (%rdx, %rcx, 4), %eax

会执行地址计算,读这个内存位置的值。

3.8.2 指针运算

3.8.3 嵌套的数组

二维数组声明:

 

3.8.4 定长数组

优化

最后一个元素存在 &B[N-1][k] 则 &B[N][k] 为终止地址

  • 移动到下一列,地址加4(int)
  • 移动到下一行,地址加4*N

3.8.5 变长数组

例如要访问n×n数组的元素i,j,我们可以写一个如下的函数:

参数n必须在参数A[n][n]之前,这样函数就可以在遇到这个数组的时候计算出数组的维度。


3.9 异质的数据结构

  • 结构 structure
    • 关键字:struct
  • 联合 union
    • 关键字:union

3.9.1 结构 struct

  • C语言的struct声明创建一个数据类型,将可能不同类型的对象聚合到一个对象中。
  • 类似于数组的实现,结构的所有组成部分都存放在内存中一段连续的区域内,而指向结构的指针就是结构第一个字节的地址
  • 编译器维护关于每个结构类型的信息,指示每个字段(field)的字节偏移
  • 它以这些偏移作为内存引用指令中的位移,从而产生对结构元素的引用。

3.9.2 联合 union

  • 对于类型 union U3 * 的指针p, p->c, p->i[0], p->v 引用的都是数据结构的起始位置
  • 一个联合总的大小等于它最大字段的大小

即:指针/数据 二选一

优化空间

  • 访问不同数据类型的位模式
  • 强行转换类型
  • 字节顺序问题很重要
  • 小端法与大端法得出的 double d 不同

3.9.3 数据对齐

  • 许多计算机系统要求某种类型对象的地址必须是K(通常是248)的倍数
  • 这种对齐限制简化了形成处理器和内存系统之间接口的硬件设计。
  • 无论数据是否对齐,x86-64硬件都能正确工作。
  • 编译器在结构体中插入空白,以确保字段的正确对齐

结构体数组:

7字节留白

优化空间


3.10 在机器级程序中将控制与数据结合起来

3.10.1 理解指针

特点/原则

  1. 每个指针都对应一个类型
  2. 每个指针都有一个
  3. 指针用‘&’运算符创建
  4. *’操作符用于间接引用指针
  5. 数组与指针紧密联系
  1. 将指针从一种类型强制转换成另一种类型,只改变它的类型,而不改变它的值
  1. 指针也可以指向函数(函数指针???)

3.10.2 应用:使用GDB调试器

  • quit 退出
  • run 运行(在此行给出命令行参数)
  • kill 停止程序
  • break multstore 在函数multstore入口处设置断点
  • break * 0x400540 在地址0x400540处设置断点
  • delete 1 删除断点1
  • delete 删除所有断点
  • stepi 执行1条指令
  • stepi 4 执行4条指令
  • continue 运行到当前函数返回
  • disas 反汇编当前函数
  • print $rax 以十进制输出%rax内容
  • info frame
  • info register
  • help

3.10.3 内存越界引用和缓冲区溢出

  • gets()函数无法确定是否为保存整个字符串分配了足够的空间
  • strcpy, strcat: 任意长度字符串的拷贝
  • scanf, fscanf, sscanf, 使用 %s 转换符时
  • 缓冲区溢出,返回地址被破坏,程序看起来能工作
  • 缓冲区溢出使程序执行它本来不愿意执行的函数,这是最常见的通过计算机网络攻击系统安全的方法
  • 输入字符串包含可执行代码的字节序列
  • 将返回地址 A用缓冲区B的地址替换
  • 当Q执行ret后,将跳转到B,执行漏洞利用程序(exploit code)

3.10.4 对抗缓冲区溢出攻击

针对缓冲区溢出攻击:

  • 避免溢出漏洞(函数、代码细节)
    • fgets代替gets、strncpy代替strcpy
    • scanf函数中别用%s(或用%ns代替%s,其中n是一个合适的整数)
  • 使用系统级的防护
    • 3.10.4.1 栈随机化(随机的栈偏移)
      • 程序启动后,在栈中分配随机数量的空间

  • 将移动整个程序使用的栈空间地址
  • 黑客很难预测插入代码的起始地址
  • 例如:执行5次内存申请代码
    • 每次程序执行,栈都重新定位
  • 3.10.4.3 限制可执行代码区域(非可执行代码段)
    • 目的:消除攻击者向系统中插入可执行代码的能力
    • 在传统的x86中,可以标记存储区为“只读”或“可写的”
    • x86-64添加显式“执行”权限
    • 将stack标记为不可执行(漏洞利用程序exploit code

  • 3.10.4.2 栈破坏检测(栈金丝雀)
    • 编译器使用“栈金丝雀”(stack canaries)
      • 在栈中缓冲区(buffer)之后的位置放置特殊的值——金丝雀(canary)
      • 退出函数之前,检查是否被破坏
    • 用GCC实现
      • -fstack-protector
      • 该选项现在是默认开启的(早期默认关闭)

 

另:

3.10.5 支持变长栈帧

有些函数需要的局部存储是变长的(例如alloca函数)

%rbp 帧指针/基指针

base pointer --> bp

发布了36 篇原创文章 · 获赞 11 · 访问量 3515

猜你喜欢

转载自blog.csdn.net/gzn00417/article/details/104236220
今日推荐