《CSAPP》阅读笔记(持续更新)


第一章 计算机系统漫游

信息就是位+上下文

源程序:由值为0和1组成的位(比特)序列

.c文件:以ASCII字符组成的文本文件,以字节序列的方式储存在文件中,每个文本行以\n结尾,对应数字10

字节八个位被组成的一组称为字节

二进制文件:除了只由ASCII码组成的文本文件,其他都是二进制文件,系统中的所有信息都是由一串比特表示

区分不同数据对象的唯一方法:数据对象的上下文

相关组织ANSI 美国国家标准学会 ISO 国际标准化组织

程序被其他程序翻译成不同的格式

二进制磁盘文件机器语言指令按照可执行目标程序的格式打包好之后以二进制磁盘文件的形式存放

目标程序:也称为可执行目标文件,或可执行文件,可以被加载到内存中,由系统执行

编译器驱动程序:用于将源文件转化为目标文件

编译系统:预处理器 + 编译器 + 汇编器 + 链接器

将源程序转化为目标文件的四个阶段

阶段 工具 结果 扩展名 文件性质
预处理阶段 预处理器 cpp 根据以#开头的命令进行预处理,读取系统头文件中的内容并且直接插入到程序文本中 .i 文本文件
编译阶段 编译器 ccl .i文件翻译成汇编语言程序 .s 文本文件
汇编阶段 汇编器 as .s文件翻译成机器语言指令,打包成可重定位目标程序格式 .o 二进制文件
链接阶段 连接器 ld 将程序中调用的函数的单独编译好的.o目标文件和汇编得到的.o文件合并成可执行文件,存在磁盘 \ 二进制文件

GNU项目:完整的类Unix系统,源代码能不受限制的被修改和传播,其环境包括**EMACS编辑器,GCC编译器,GDB调试器,汇编器,链接器,处理二进制文件的工具**等

处理器读并解释储存在内存中的指令

shell:命令行解释器

系统的硬件组成

总线

作用携带、传递定长的字节块

补充:字节块 = 字, 属性: 字节数 = 字长, 机器字长: 32位中字长为4字节,64位中字长为8字节

I/O设备

功能:通过控制器适配器I/O总线相连,在I/O主总线和I/O设备之间传递信息

封装方式的区别控制器是I/O设备本身或系统主印制电路板(即主板)上的芯片组适配器是插在主板插槽上的卡

例子:磁盘、键盘、显示器、网络

主存

作用:用来存放程序和程序处理的数据临时存储设备

组成:由一组动态随机存取存储器(DRAM)芯片组成的线性的字节数组

处理器

别称:中央处理单元、CPU、中央处理器

功能:解释或执行存储在主存中的指令的引擎,不断执行PC指向的指令,解释指令中的位,执行该指令指示的简单操作,然后更新PC指向下一条指令(不一定相邻

简单指令:围绕着主存、寄存器文件、算术/逻辑单元ALU进行,例如:加载、存储、操作、跳转

寄存器文件:是一个小的存储设备,由一些单个字长的寄存器组成

指令集架构:描述每条机器代码指令的效果

微体系结构:描述处理器实际上是如何实现

高速缓存和存储设备形成层次结构

高速缓存cache

作用存放可能经常访问的数据,大部分的内存操作都能快速在高速缓存中完成,节省了内存、I/O设备喝CPU寄存器之间的复制数据

存储设备形成层次结构

主要思想:上一层的存储器作为低一层存储器的高速缓存,了解不同的高速缓存和对整个存储器层次结构可以提高程序性能

操作系统管理硬件

操作系统

功能

  • 防止硬件被失控的应用程序滥用
  • 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备,所有应用程序对硬件的操作都必须通过操作系统

实现:通过进程、虚拟内存、文件实现

进程

进程:操作系统对正在运行的程序的一种抽象

并发运行:一个进程的指令和另一个进程的指令是交错执行

上下文操作系统保持根据进程运行所需的所有状态信息,这种状态成为上下文

上下文切换:操作系统实现交错执行的机制,执行的是内核代码

单处理器系统:只能执行一个进程代码,系统绝对把控制权从当前进程转移到新进程就会上下文切换,控制权传递到新进程

内核kernel:从一个进程到另一个进程的转换是由操作系统内核管理的,内核时操作系统代码常驻主存的部分,不是一个独立的进程,而是系统管理全部进程所用代码和数据结构的集合。

系统调用:应用程序需要系统的某些操作时,执行系统调用将控制权传递给操作系统内核,内核执行被请求的操作并返回应用程序

线程

线程: 组成进程的执行单元,运行在进程的上下文中,每个线程共享同样的代码和全局数据

多线程的优点:容易共享数据,高效,运行的快

虚拟内存

虚拟内存:是一个抽象概念,为每个进程提供了独占的使用主存的假象,其运作需要硬件和操作系统软件的交互

虚拟地址空间:每个进程看到的一致的内存

虚拟地址空间的区域(由低地址到高地址)

区域名称 内容
程序代码和数据 代码从固定地址开始,接着是C全局变量对应的数据位置
动态扩展和收缩
共享库 存放例如C标准库、数学库这样的共享库的代码和数据的区域
用户虚拟地址空间顶部的是用户栈,用于实现函数调用,动态扩展和收缩,调用函数时栈增长,反之收缩
内核虚拟内存 地址空间顶部,不允许应用程序读写或直接调用内核代码定义的函数,必须调用内核来执行

基本思想:把一个进程虚拟内存的内容存储在磁盘上,然后用主存作为磁盘的高速缓存

第二章 信息的表示和处理

信息存储

十六进制

特点:每个数字占4位

十六进制转二进制:直接将每个数字的二进制依次排列即可

二进制转十六进制:则从后往前每四位分一部分最前面不足补0

字节顺序

小端法和大端法:大端法是正序,从最低有效字节到最高有效字节,小端法相反。大多机器使用小端法

字符串

特点:以null(值为0)结尾,数字xASCII码为0x3x,不受大端小端的影响在任何系统结果相同

位移运算

左移:末尾补k0,取后m

逻辑右移:前补k0,取前m位(无符号数)

算数右移:前补k个首位的数,取前m位(常用)

特殊情况k > m,则位移 k mod m

整数表示

有符号数与无符号数的数值范围

符号 数值
UMaxw 2^w - 1
UMinw 0
TMaxw 2^(w-1) - 1
TMinw -2^(w - 1)
#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX - 1)

不对称性|TMinw| = |TMaxw| + 1 UMaxw = 2TMaxw + ,因此不是所有负数都存在相反数

补码two’s complement

特点:将最高有效位解释为负权,也称符号位,权重为-2^w-1(其二进制共w位),正数补码为原码,负数补码为其二进制取反后加一

转换为无符号数的规则:位值不变,只是改变了最高位的解释位的方式,即是否为符号位

转换计算方法:-1 和UMaxw位表示相同,全1的串,0 ~ TMaxw之内转换数值不变,范围之外转换加上或减去2^w

隐式类型转换

  • 相互赋值

  • printf输出时使用不对应的格式化字符串

  • 算术运算或比较一边存在无符号数则另外一边强制转为无符号数

扩展和截断

无符号数转换为一个更大的数据类型:零扩展,即二进制前导补0

补码转换为一个更大的数据类型:符号扩展,即二进制前导补符号扩展位的值

长度不同的类型之间的转换:长度转一致之后再类型转换,如shortunsigned要先转为int再转unsigned

截断规则:舍弃高位,补码将最高位转换为符号位

关于符号数与无符号数的建议

尽量避免无符号/有符号类型的混用,因为这样可能会进行隐式类型转换,造成非预期的错误

#define KSIZE 1024
char kbuf[KSIZE];

void *memcpy(void *dest, void*src, size_t n);
int copy_from_kernel(void* user_dest, int maxlen)
{
  int len = KSIZE < maxlen ? KSIZE : maxlen;
  memcpy(user_dest, kbuf, len);
  return len;
}

例中,memcpy函数的定义中n类型为size_t,若len输入负数则会导致n为一个很大的数,使程序读取到它没有被被授权的内核内存区域

IEEE浮点表示

公式: V = (-1)^s * M * 2^E

位名称 释义和值
s 符号位,一个单独的位编码符号位
M 尾数,一个二进制小数,编码为n位小数字段frac
E 阶码,用于对浮点加权,编码为k为的阶码字段exp,被解释为以偏置形式表示的有符号整数
f frac / 2 ^ n
Bias 偏置值,2^(k-1) - 1
类别 特点 E M
规格化的值 阶码域有0有1 e - Bias 1 + f
非规格化的值 阶码域全为0 1 - Bias f
特殊值 阶码域全为1,s = 0为正无穷,s=1为负无穷,能够表示溢出的结果,小数域非0称为NaN \ \

注意:浮点数可以表示的值范围比整数大但只是近似表示,数字越大精度越低,其运算不具有结合性,且单精度与双精度值进行比较可能会发生错误,当超过最大的规格化的值时会发生溢出

第三章 程序的机器级表示

条件分支

  • 用条件转移实现条件分支

    在现代处理器上会非常低效,因为处理器通过使用流水线来获得高性能,要求能够实现确定执行的指令序列。当机器遇到条件跳转时只有当分支条件求值完成才能决定走哪条分支,处理器会采用分支预测逻辑来猜测每条跳转指令是否执行,当预测错误时会严重降低性能

  • 用条件传送来实现条件分支

    计算一个条件操作的两种结果,然后根据条件是否满足从中选取一个

    //计算两个数的差的绝对值
    //使用条件转移
    long cmowdiff (long x, long y){
    	long result;
    	if(x < y)
    		result = y - x;
    	else 
    		result = x - y;
    	return result;
    }
    
    //使用条件传送
    long cmowdiff (long x, long y){
        long rval = y - x;
        long eval = x - y;
        long ntest = x >= y;
        if(ntest)	rval = eval;
        return rval;
    }

浮点代码

浮点代码使用的寄存器是ymm0~ymm15,可以保存32字节,其低16字节是xmm寄存器,浮点函数的

xmm寄存器用来向函数传递和返回浮点数,函数使用xmm0返回浮点值

数据格式

数据类型 术语 汇编代码 大小
字节 byte b 1字节 8位
word w 2字节 16位
双字 double words l 4字节 32位
四字 quad words q 8字节 64位

汇编指令

lea		加载有效地址
inc 	加1
dec		减1
neg		取负
sal		左移
sar		算数右移
shr		逻辑右移
mul		乘
cqto	转换为8字
div		除
cmp		比较
test	测试
set		将一个字节设置为0或1
CF		进位标志,检查无符号操作的溢出
ZF		零标志
SF		符号标志
OF		溢出标志,补码的正/负溢出

过程

形式包括:函数,方法,子例程,处理函数

栈帧:存储空间超出寄存器能够存放的大小时就会在栈上分配空间,这个部分成为过程的栈帧,栈指针是rsp,帧指针是rbp

GDB

kill			    停止程序
delete  		    删除断点
info frame		    有关当前栈帧的信息
info registers		所有寄存器值

对抗缓冲区溢出攻击

ASLR:通过在程序开始时分配一段随机大小的空间而不使用,使得后续栈位置发生变化。可以通过在攻击代码前插入nop滑过随机大小的序列

NX:限制可执行代码区域,堆栈不可执行

canary:检测缓冲区越界,可以使用-fno-stack-protector关闭canary保护


  目录