新坑,准备开博记录学习历程,进而鞭策自己~

0x00 第一章 计算机系统漫游

编译系统

hello.c

1
2
3
4
5
6
7
#include<stdio.h>

int main()
{
printf("hello,world\n");
return 0;
}

hello.c->预处理器(cpp)->hello.i->编译器(ccl)->hello.s->汇编器(as)->hello.o、printf.o->链接器(ld)->hello

编译分为4个阶段:预处理,编译,汇编,链接
经过ccl的翻译,hello.i被翻译成hello.s
hello.s

1
2
3
4
5
6
7
main:
subq $8,%rsp
movl $.LC0,%edi
call puts
movl $0,%eax
addq $8,%rsp
ret

虚拟内存

虚拟是一个抽象概念,它为每一个进程提供了一个假象,每个进程都在独占地使用主存,每个进程看到的内存都是一致的

内核虚拟内存 用户代码不可见的内存
用户栈(运行时候创建)
共享库的内存(映射区域) 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<stdio.h>

typedef unsigned char *byte_pointer;

void show_bytes(byte_pointer start,size_t len)
{
size_t i;
for (i=0 ; i < len; i++ )
printf("%.2x",start[i]);//两个数字的16进制格式输出
printf("\n");
}

void show_int(int x)
{
show_bytes((byte_pointer) &x,sizeof(int));
}

void show_float(float x)
{
show_bytes((byte_pointer) &x,sizeof(float));
}

void show_pointer(void *x)
{
show_bytes((byte_pointer) &x,sizeof(void *));
}
//打印程序对象的字节表示

getpeername的安全漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* Illustation of code vulnerability similar to that found in
* FreeBSD's implementation of getpeername()
*/

/* Declaration of library function memcpy */
void *memcpy(void * dest ,void *src , size_t n);

/* Kernel memory region holding user-accessible data*/
#define KSIZE 1024;
char kbuf[KSIZE];

/*Copy at most maxlen bytes from kernel region to user buffer */
int copy_from_kernel(void *user_dest, int maxlen)
{
/* Byte count len is minimum of buffer size and maxlen */
int len= KSIZE< maxlen ? KSIZE : maxlen;
memcpy(user_dest , kbuf , len);
return len;
}

size_t 在32位机器中是unsigned int,64位机器中是unsigned long,传入参数int数据类型不匹配导致能够访问到大于可见区域的内核内存

0x02 第三章 程序的机器级表示

程序编码

1
linux> gcc -Og -o p1.c p2.c

代码实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>

long mult2(long,long);

void multstore(long x,long y,long *dest)
{
long t = mult2(x,y);
*dest = t;
}

int main()
{
long d;
multstore(2,3,&d);
printf("2 * 3 --> %d\n",d);
return 0;
}
long mult2(long a,long b)
{
long s = a * b;
return s;
}

汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
00000000004005b6 <mult2>:
4005b6: 48 89 f8 mov %rdi,%rax
4005b9: 48 0f af c6 imul %rsi,%rax
4005bd: c3 retq

00000000004005be <multstore>:
4005be: 53 push %rbx # save %rbx
4005bf: 48 89 d3 mov %rdx,%rbx # Copy dest to %rbx
4005c2: e8 ef ff ff ff callq 4005b6 <mult2> # Call mult2(x,y)
4005c7: 48 89 03 mov %rax,(%rbx) # Store result at *dest
4005ca: 5b pop %rbx # Restore %rbx
4005cb: c3 retq # Return

数据格式

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
2
3
4
5
movl $0x4050,%eax    #Immediate--Register, 4 Bytes
movw %bp,%sp #Register--Register, 2 Bytes
movb (%rdi,%rcx),%al #Memory--Register, 1 Bytes
movv $-17,(%rsp) #Immediate--Memory, 1 Bytes
movq %rax,-12(%rbp) #Register--Memory, 8 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
2
3
4
5
#以下指令
pushq %rbp
#等价于
subq $8,%rsp # Decrement stack pointer
movq %rbp,(%rsp) # Store %rbp on stack

pop指令

1
2
3
4
5
#以下指令
popq %rax
#等价于
movq (%rsp),%rax # Read %rax from stack
addq $8,%rsp # Increment stack pointer

算数和逻辑操作

操作分为四组:加载有效地址、一元操作、二元操作和移位

指令 效果 描述
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
2
3
4
5
long scale(long x, long y, long z)
{
long t = x + 4 * y + 12 * z;
return t;
}

编译时,该函数的算数运算以三条leaq指令实现

1
2
3
4
5
scale:
leaq (%rdi,%rsi,4),%rax # x + 4 * y
leaq (%rdx,%rdx,2),%rdx # z + 2 * z = 3 * z
leaq (%rax,%rdx,4),%rax # (x + 4 * y) + 4 * (3 * z)
ret

一元和二元操作

第二组是一元操作,第三组是二元操作

移位操作

算数移位:填上符号位;
逻辑移位:填上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$ CF&ZF 超过(无符号>)
setae D setnb D $\leftarrow$ ~CF 超过或相等(无符号>=)
setb D setnae D $\leftarrow$ CF 低于(无符号<)
setbe D setna D $\leftarrow$ CF|ZF 低于或相等(无符号<=)
1
2
3
4
5
6
7
# int comp(data_t a,data_t b)
# a in %rdi, b in %rsi
comp:
cmpq %rsi,%rdi # Compare a:b
setl %al # Set low-order byte of %eax to 0 or 1
movzbl %al,%eax # Clear rest of %eax (and rest of %rax)
ret

跳转指令

1
2
3
4
5
    movq $0,%rax
jmp .L1
movq (%rax),%rdx
.L1:
popq %rdx

jmp无条件跳转

1
2
jmp *%rax   # 间接跳转
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 CF&ZF 超过(无符号>)
jae $label$ jnb ~CF 超过或相等(无符号>=)
jb $label$ jnae CF 低于(无符号<)
jbe $label$ jna CF|ZF 低于或相等(无符号<=)

跳转指令的编码

  • 地址差编码
  • 绝对地址

用条件控制来实现实现条件分支

To Be Continue