CSAPP-过程调用,数据存储,缓冲区溢出
程序编译:
1.预处理阶段:
1.文件包含:将#include扩展成文件正文
2.条件编译:根据#if和#ifdef将程序的某部分排除或者包含
3.宏展开:将出现宏引用的地方展开成相应的宏
2.编译阶段:
检查代码的规范性、是否有语法错误等,然后将其转换成低级机器语言。(C会转换成汇编语言)
3.汇编阶段:
将汇编代码转换成二进制目标代码
4.链接阶段:
”printf”的函数并没有在代码中实现,且在预编译中包含进的”stdio.h”中也只有该函数的声明
系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函 数”printf”了,而这也就是链接的作用。
函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件 了。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以 节省系统的开销。
过程调用
………………………………………………………………………………………………………………………………………
在过程调用中主要涉及三个重要的方面:
1.传递控制:包括如何开始执行过程代码,以及如何返回到开始的地方
当进入过程Q的时候,程序计数器必需设为Q代码的起始位置。在返回的时候,程序技术的要设置为
调用Q那条指令的地址。
2.传递数据:包括过程需要的参数以及过程的返回值
3.内存管理:在过程执行的时候分配内存,以及在返回之后释放内存
………………………………………………………………………………………………………………………………………
在过程调用的时候,会把退出Q返回P的地址放入栈帧中,还包括被保存的 寄存器,局部变量,参数构造区。
所以递归本身就是一个隐式的栈实现,但是系统一般对于栈的深度有限制(每次一都需要保存当前栈帧的各种数据),
所以一般来说会把递归转换成显式栈来进行处理以防溢出。
数据存储:
局部数据必须放在内存中:
1.寄存器不足存放所有本地数据
2.对一个局部变量使用地址运算符&
3.某些局部变量是数组或结构,因此必须通过引用访问
………………………………………………
struct的内存对齐:
对于结构体内部成员,通常会有这样的规定:各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。
避免缓冲区的溢出:
1.避免缓冲区溢出,我们用更安全的方法,如:fgets, strncpy 等等。
2.栈随机化:栈的位置不确定,让缓冲区溢出没办法影响到,并且每次位置都不一样,就不怕被暴力破解。并且也可以把一段内存标记为只读,那么就避免因为缓冲区溢出而导致的重写。
3.破坏检测:使用认证机制(Stack Canaries)。简单来说,就是在超出缓冲区的位置加一个特殊的值,如果发现这个值变化了,那么就知道出问题了。
4.限制可执行:限制插入执行代码的能力,限制哪些内存区域能存放可执行代码。
参考资料:
不周山之读薄CSAPP
《深入理解计算机系统 》