新坑,准备开博记录学习历程,进而鞭策自己~
0x00 第一章 计算机系统漫游
编译系统
hello.c
1 |
|
hello.c->预处理器(cpp)->hello.i->编译器(ccl)->hello.s->汇编器(as)->hello.o、printf.o->链接器(ld)->hello
编译分为4个阶段:预处理,编译,汇编,链接
经过ccl的翻译,hello.i被翻译成hello.s
hello.s
1 | main: |
虚拟内存
虚拟是一个抽象概念,它为每一个进程提供了一个假象,每个进程都在独占地使用主存,每个进程看到的内存都是一致的
| 内核虚拟内存 | 用户代码不可见的内存 |
|---|---|
| 用户栈(运行时候创建) | |
| 共享库的内存(映射区域) | printf函数 |
| 运行时堆(在运行时由malloc创建的) | |
| 读/写数据 | 从hello可执行文件加载进来的 |
| 只读的代码和数据 | |
Amdahl定律
当我们对系统的某个部分加速的时候,对其系统整体性能的影响取决于该部分的重要性和加速程度。
系统执行某应用程序所需要的时间是$T_{old}$,假设系统某部分所需要的执行时间是与该时间的比为$\alpha$,该部分性能提升比为$k$
即该部分初始化所需要的时间是$\alpha T_{old}$,现在所需的时间是$(\alpha T_{old})/k$
所以总得执行时间是
$$
T_{new}=(1-\alpha)T_{old}+(\alpha T_{old})/k=T_{old}[(1-\alpha)+\alpha /k]
$$
可以计算加速比$S=T_{old}/T_{new}$为
$$
S=\frac{1}{(1-\alpha)+\alpha /k}
$$
Amdahl定律一个有趣的特殊情况是考虑$k$趋向$\infty$时的效果,意味着,我们可以取系统的某一部分将其加速到一个点,在这个点上,这部分花费的时间可以忽略不计
$$
S=\frac{1}{1-\alpha}
$$
0x01 第二章 信息的表示和处理
信息存储
- 大多数计算机使用8位的块(byte)作为最小的可寻址的内存单位
字数据大小
每台计算机都以一个字长,指明指针数据的标称大小。对于一个字长为$w$的机器而言,虚拟地址的范围为$0$~$2^w-1$,程序最多访问$2^w$个字节
32位字长限制虚拟地址空间为4千兆字节(4GB)刚刚超过$5\times 10^9$,扩展到64位字长是的虚拟地址空间为16EB,大约是$1.84\times 10^{19}$字节
基本C数据类型的典型大小(以字节为单位)
| 有符号 | 无符号 | 32位 | 64位 |
|---|---|---|---|
| [signed] char | unsigned char | 1 | 1 |
| short | unsigned short | 2 | 2 |
| int | unsigned | 4 | 4 |
| long | unsigned long | 4 | 8 |
| int32_t | uint32_t | 4 | 4 |
| int64_t | uint64_t | 8 | 8 |
| char * | 4 | 8 | |
| float | 4 | 4 | |
| double | 8 | 8 |
1 |
|
getpeername的安全漏洞
1 | /* |
size_t 在32位机器中是unsigned int,64位机器中是unsigned long,传入参数int数据类型不匹配导致能够访问到大于可见区域的内核内存
0x02 第三章 程序的机器级表示
程序编码
1 | linux> gcc -Og -o p1.c p2.c |
代码实例
1 |
|
汇编代码
1 | 00000000004005b6 <mult2>: |
数据格式
| C声明 | Intel数据类型 | 汇编代码后缀 | 大小(字节) |
|---|---|---|---|
| char | 字节 | b | 1 |
| short | 字 | w | 2 |
| int | 双字 | l | 4 |
| long | 四字 | q | 8 |
| char* | 四字 | q | 8 |
| float | 单精度 | s | 4 |
| double | 双精度 | l | 8 |
访问信息
假设下面的值放在指明的内存地址和寄存器中
| 地址 | 值 |
|---|---|
| 0x100 | 0xFF |
| 0x104 | 0xAB |
| 0x108 | 0x13 |
| 0x10C | 0x11 |
| 寄存器 | 值 |
|---|---|
| %rax | 0x100 |
| %rcx | 0x1 |
| %rdx | 0x3 |
那么下表中操作数的值是:
| 操作数 | 值 |
|---|---|
| %rax | 0x100 |
| 0x104 | 0xAB |
| $0x108 | 0x108 |
| (%rax) | 0xFF |
| 4(%rax) | 0xAB |
| 9(%rax,%rdx) | 0x11 |
| 260(%rcx,%rdx) | 0x13 |
| 0xFC(,%rcx,4) | 0xFF |
| (%rax,%rdx,4) | 0x11 |
数据传送指令
简单的数据传送指令
| 指令 | 效果 | 描述 |
|---|---|---|
| MOV S,D | D$\leftarrow$S | 传送 |
| movb | 传送字节 | |
| movw | 传送字 | |
| movl | 传送双字 | |
| movq | 传送四字 | |
| movabsq I,R | R$\leftarrow$I | 传送绝对的四字 |
注意:传送指令的两个操作数不能都指向内存位置,将一个值从一个内存位置复制到另一个内存位置需要两条指令——第一条指令将源值加载到寄存器中,第二条将该寄存器值写入目的位置
以下是源和目的类型的五种可能组合
1 | movl $0x4050,%eax #Immediate--Register, 4 Bytes |
零扩展数据传送指令,注意:不存在movzlq,可以用movl来实现
| 指令 | 效果 | 描述 |
|---|---|---|
| MOVZ S,R | R$\leftarrow$零扩展(S) | 以零扩展进行传送 |
| movzbw | 将做了零扩展的字节传送到字 | |
| movzbl | 将做了零扩展的字节传送到双字 | |
| movzwl | 将做了零扩展的字传送到双字 | |
| movzbq | 将做了零扩展的字节传送到四字 | |
| movzwq | 将做了零扩展的字传送到四字 |
符号扩展数据传送指令,cltq指令只作用于寄存器%eax和%rax
| 指令 | 效果 | 描述 |
|---|---|---|
| MOVS S,R | R$\leftarrow$符号扩展(S) | 传送符号扩展的字节 |
| movsbw | 将做了符号扩展的字节传送到字 | |
| movsbl | 将做了符号扩展的字节传送到双字 | |
| movswl | 将做了符号扩展的字传送到双字 | |
| movsbq | 将做了符号扩展的字节传送到四字 | |
| movswq | 将做了符号扩展的字传送到四字 | |
| movslq | 将做了符号扩展的双字传送到四字 | |
| cltq | %rax$\leftarrow$符号扩展(%eax) |
把%eax符号扩展到%rax |
压入和弹出栈数据
在x86-64中,程序栈存放在内存中的某个区域,栈向下增长,栈顶元素的地址是所有栈中元素地址最低的,栈指针%rsp保存着栈顶元素的地址。
| 指令 | 效果 | 描述 |
|---|---|---|
| pushq S | R[%rsp]$\leftarrow$R[%rsp]-8; M[R[%rsp]]$\leftarrow$S; |
将四字压入栈 |
| popq D | D$\leftarrow$M[R[%rsp]]; R[%rsp]$\leftarrow$R[%rsp]+8; |
将四字弹出栈 |
push指令
1 | #以下指令 |
pop指令
1 | #以下指令 |
算数和逻辑操作
操作分为四组:加载有效地址、一元操作、二元操作和移位
| 指令 | 效果 | 描述 |
|---|---|---|
| leaq S,D | D $\leftarrow$ &S | 加载有效地址 |
| INC D | D $\leftarrow$ D + 1 | 加1 |
| DEC D | D $\leftarrow$ D - 1 | 减1 |
| NEG D | D $\leftarrow$ -D | 取负 |
| NOT D | D $\leftarrow$ ~D | 取反 |
| ADD S,D | D $\leftarrow$ D + S | 加 |
| SUB S,D | D $\leftarrow$ D - S | 减 |
| IMUL S,D | D $\leftarrow$ D * S | 乘 |
| XOR S,D | D $\leftarrow$ D ^ S | 异或 |
| OR S,D | D $\leftarrow$ D|S | 或 |
| AND S,D | D $\leftarrow$ D & S | 与 |
| SAL k,D | D $\leftarrow$ D << k | 左移 |
| SHL k,D | D $\leftarrow$ D << k | 左移(等同于SAL) |
| SAR k,D | D $\leftarrow$ D >> $_{A}$k | 算数右移 |
| SHR k,D | D $\leftarrow$ D >> $_{L}$k | 逻辑右移 |
加载有效地址
加载有效地址指令leaq实际上是movq指令的变形
举例:
1 | leaq 7(%rdx,%rdx,4),%rax #%rdx的值为x,%rax的值为5x+7 |
从C程序看
1 | long scale(long x, long y, long z) |
编译时,该函数的算数运算以三条leaq指令实现
1 | scale: |
一元和二元操作
第二组是一元操作,第三组是二元操作
移位操作
算数移位:填上符号位;
逻辑移位:填上0;
特殊的算数操作
| 指令 | 效果 | 描述 |
|---|---|---|
| imulq S | R[%rdx]:R[%rax] $\leftarrow$ S×R[%rax] | 有符号乘法 |
| mulq S | R[%rdx]:R[%rax] $\leftarrow$ S×R[%rax] | 无符号乘法 |
| clto | R[%rdx]:R[%rax] $\leftarrow$ 符号扩展(R[%rax]) | 转换为八字 |
| idivq S | R[%rdx] $\leftarrow$ R[%rdx]:R[%rax] mod S R[%rdx] $\leftarrow$ R[%rdx]:R[%rax] ÷ S |
有符号除法 |
| divq | R[%rdx] $\leftarrow$ R[%rdx]:R[%rax] mod S R[%rdx] $\leftarrow$ R[%rdx]:R[%rax] ÷ S |
无符号除法 |
一对寄存器%rdx和%rax组成一个128位的八字
控制
机器代码提供两种基本的低级机制来实现现有的条件的行为:
- 测试数据值
- 根据测试结果来改变控制流或者数据流
条件码
条件码寄存器
- CF:进位标志。最近的操作使最高位产生了进位。可以用来检查无符号操作的溢出。
- ZF:零标志。最近的操作得到的结果为0。
- SF:符号标志。最近的操作得到的结果为负数。
- OF:溢出标志。最近的操作导致一个补码溢出——正溢出或者负溢出。
比较和测试指令
| 指令 | 基于 | 描述 |
|---|---|---|
| CMP $S_{1}$,$S_{2}$ | $S_{1}-S_{2}$ | 比较 |
| cmpb | 比较字节 | |
| cmpw | 比较字 | |
| cmpl | 比较双字 | |
| cmpq | 比较四字 | |
| TEST $S_{1}$,$S_{2}$ | $S_{1} & S_{2}$ | 测试 |
| testb | 测试字节 | |
| testw | 测试字 | |
| testl | 测试双字 | |
| testq | 测试四字 |
访问条件码
条件码一般不能直接读取,常用方法有三种:
- 根据条件码的某种组合,将一个字节设置为0或者1,2
- 条件跳转到程序的某个其他部分
- 有条件的传送数据
set指令
| 指令 | 同义名 | 效果 | 设置条件 |
|---|---|---|---|
| sete D | setz | D $\leftarrow$ ZF | 相等/零 |
| setne D | setnz | D $\leftarrow$ ~ZF | 不等/非零 |
| sets D | D $\leftarrow$ SF | 负数 | |
| setns D | D $\leftarrow$ ~SF | 非负数 | |
| setg D | setnle | D $\leftarrow$ ~(SF^OF) & ~ZF | 大于(有符号>) |
| setge D | setnl | D $\leftarrow$ ~(SF^OF) | 大于等于(有符号>=) |
| setl D | setnge | D $\leftarrow$ SF^OF | 小于(有符号<) |
| setle D | setng | D $\leftarrow$ (SF^OF)|ZF | 小于等于(有符号<=) |
| seta D | setnbe | D $\leftarrow$ |
超过(无符号>) |
| setae D | setnb | D $\leftarrow$ ~CF | 超过或相等(无符号>=) |
| setb D | setnae | D $\leftarrow$ CF | 低于(无符号<) |
| setbe D | setna | D $\leftarrow$ CF|ZF | 低于或相等(无符号<=) |
1 | # int comp(data_t a,data_t b) |
跳转指令
1 | movq $0,%rax |
jmp无条件跳转
1 | jmp *%rax # 间接跳转 |
jmp指令
| 指令 | 同义名 | 跳转条件 | 描述 |
|---|---|---|---|
| jmp $label$ | 1 | 直接跳转 | |
| jmp *$Operand$ | 1 | 间接跳转 | |
| je $label$ | jz | ZF | 相等/零 |
| jne $label$ | jnz | ~ZF | 不相等/非零 |
| js $label$ | SF | 负数 | |
| jns $label$ | ~SF | 非负数 | |
| jg $label$ | jnle | ~(SF^OF) & ~ZF | 大于(有符号>) |
| jge $label$ | jnl | ~(SF^OF) | 大于等于(有符号>=) |
| jl $label$ | jnge | SF^OF | 小于(有符号<) |
| jle $label$ | jng | (SF^OF)|ZF | 小于等于(有符号<=) |
| ja $label$ | jnbe | 超过(无符号>) | |
| jae $label$ | jnb | ~CF | 超过或相等(无符号>=) |
| jb $label$ | jnae | CF | 低于(无符号<) |
| jbe $label$ | jna | CF|ZF | 低于或相等(无符号<=) |
跳转指令的编码
- 地址差编码
- 绝对地址
用条件控制来实现实现条件分支
To Be Continue