C 语言编译过程
- 源文本———-预处理,处理 define,include 等
- 预处理文本————编译
- 汇编文本—————汇编得到二进制文件 .o
- 目标格式—————链接
- 可执行文件
二进制补码原理和 C 语言处理类型转换
- 二进制补码最高位本质是一个 $-2^x$
- 同时处理有符号和无符号整数比较时或者进行其它二元运算时,C 语言会默认无符号且均为正数。
- 编译器处理一个
-x
的表达式时,会先读x
然后取反,所以-2147483648
是不合法的,应该写为-2147483647-1
简单的汇编语言
操作系统将物理内存抽象为了一个一维数组,所有汇编层面的操作均在一维数组内进行
每一条汇编指令都可以被描述为两个部分,指令和操作对象,这两部分的整体可以被一个或多个字节描述,此规则本质上是一颗哈夫曼树。
一个程序的汇编指令会被保存在主存上,有一个程序计数器
epi
指出当前执行到的地方。常用寄存器名有
eax,ecx,edx,ebx,esi,edi,esp,ebp
。前三者的数据由当前程序保存在栈中,后三者的值由下级程序保存,最后二者为帧指针和栈指针,一般不使用。前四个寄存器可以通过ah,al,ax
等形式访问低二字节和低一字,但都可以用di
形式访问低一字。传送指令分为三种
movb movw movl
分别表示字节,字和双字。传送对应类型时,应该使用对应的寄存器位置。push,pop
指令为压栈和弹栈,本质上是操作了主存和栈指针。lea
指令可以快速计算a+b*(1,2,4,8)+c
,a
必须为常量,b,c
必须为寄存器中的数。操作数分为三类,一类是立即数,一类是寄存器,一类是主存数据,访问格式如下。
1
2
3
4
5
6
7
8
9
10$imm //立即数 imm
eg. $0x3f
E //寄存器 E
eg. eax
Imm(e1,e2,s) //主存上 Imm+e1+e2*s 位置上的数据,Imm 可以缺省,后二者可以缺省,e1 缺省时不能省略 ','
eg. 0x3f(eax,ebx,4)
eg. (eax)
用
test
和cmp
指令来控指条件,本质上,它们改变了条件寄存器内的值,test x x
等价于cmp 0 x
。执行cmp
后,jl
等条件跳转语句会在cmp
成立时执行,jl
表示小于时跳转,cmp x y
可以看作是y<x
时执行jl
call
指令会将返回地址 (call) 语句后那条语句的地址入栈后跳转到函数所在位置。ret
指令利用返回地址跳转回去,一般来说,用eax
保存返回内容。由于程序寄存器中的某些值会被缓存到主存中,所以使用
gets
等不安全函数读取时,如果读取的内容过长超出了为此缓存区分别的字节后,会污染一些先前被存储在主存中的寄存器值,因为超出分配的内存后,会写在外面,通过这样的操作,我们可以改变一些系统寄存器的值,让程序跳转执行一些我们希望让它执行的代码(这样的代码通常被写在我们的输入里),这就是缓冲区攻击,通常操作是改变ebp
的值使得ret
操作跳到我们希望的地方。