Intel处理器系列
IA32 (Intel 32位体系结构)
Intel 64,IA32的64位扩展,称为x86-64,简称x86
GCC C编译器是Linux上默认的编译器,通过命令gcc启动
—Og 、—O1、—O2表示编译器生成机器代码时的优化等级,越高级别的优化会使代码严重变形,以至于产生的机器代码和初始源代码之间的关系难以理解
机器级编程的两种重要抽象:
1、指令集体系结构/指令集架构(Instruction Set Architecture,ISA),定义处理器状态、指令格式,以及每条指令对状态的影响
2、机器级程序使用的内存地址是虚拟地址,由操作系统进行管理以及翻译成实际处理器内存中的物理地址
X86-64的汇编指令学习
以 . 开头的都是指导汇编器和链接器工作的伪指令
在C程序中插入汇编代码的方法
方法一:编写完整的函数,放进一个独立的汇编代码文件.s中,让汇编器和链接器将其余c语言书写的代码合并起来
方法二:使用GCC的内联汇编特性,用asm伪指令在c程序中包含简短的汇编代码
X86-64的CPU包含一组16个存储64位值的通用目的寄存器,名字以%r开头
操作数的类型
—— 立即数,表示常数值
—— 寄存器,表示某个寄存器的内容
—— 内存引用,根据计算出来的地址访问某个内存位置
多种寻址方式
数据传送指令(两个操作数不能都指向内存位置,必须先从内存加载到寄存器,再从寄存器写入到内存)
1、MOV类:简单地将数据从源位置复制到目的位置,不做任何改变
2、进栈出栈操作:Push 和 Pop,进栈将栈顶地址减少一个字节的长度,并存放数据到栈顶指针的内存位置处
出栈则是栈顶指针增加一个字节的长度,原来的数据仍存在直至被覆盖
栈是向低地址方向增长的
3、加载有效地址(load effective address)leaq:从内存读取数据到寄存器
4、一元操作:INC(+1)、DEC(-1)、NEG(取负)、NOT(取补)
5、二元操作:ADD、SUB、IMUL、XOR、OR、AND
6、移位操作:SAL/SHL(左移)、SAR(算术右移)、SHR(逻辑右移)
机器代码实现有条件行为的机制:测试数据值,根据测试值来改变控制流或者数据流
条件码寄存器
CF —— 进位标志,可用来检查无符号操作的溢出
ZF —— 零标志
SF —— 符号标志,检测最近的操作得到的结果为负数
OF —— 溢出标志,补码溢出(正溢出或负溢出)
CMP和TEST指令只改变条件码,而不改变目标寄存器的值
实现条件操作的方法:
1、使用控制的条件转移
2、使用数据的条件转移
基于数据的条件转移代码性能要高于基于控制的条件转移代码,因为在基于控制的条件转移中,分支预测的正确与否主导者函数的性能。
汇编实现循环的方式: 利用条件测试和跳转组合实现
过程
过程是一种抽象,提供了一种代码封装的方式,用一组指定的参数和一个可选的返回值实现了某种功能
过程在运行时要处理的操作
1、传递控制 —— 程序开始和返回时PC的跳转
2、传递数据 —— 参数,绝大部分通过寄存器实现,若大于6个参数则超出部分通过栈传递
3、分配和释放内存 —— 局部变量存储以及返回时需要释放的空间
call 指令 —— 指明被调用过程起始的指令地址(赋值PC),并且将当前程序的地址压入栈作为返回地址
异质的数据结构 —— struct 和 union
struct结构的各个字段的选取完全是在编译时处理,机器代码不包含关于字段声明或字段名字的信息
union用不同的字段引用想用的内存块,给节省内存空间带来便利
数据对齐
对齐原则:任何K字节的基本对象的地址必须是K的倍数
汇编代码中的对齐 —— .align 8指明全局数据所需的对齐
struct中的对齐 —— 在中间或者尾部插入间隙来满足对齐
指针详解
void *类型代表通用指针,应用举例:malloc函数返回一个通用指针,然后通过显示强制类型转化或者赋值操作隐式强制类型转换将其转换成一个有类型的指针;
将指针从一种类型强制转换成另一种类型,只改变它的类型,而不改变它的值;
函数指针是该函数机器代码表示中第一条指令的地址
函数指针的解读举例:
int (*f) (int x,int *p) —— f 是一个指向函数的指针,该函数有两个形参 x和*p,且有一个int类型的返回值
内存越界引用和缓冲区溢出
1、缓冲区溢出
在调用函数时不指定需要用到的数组的最大大小,使得函数无法确定是否为保存所有数据分配了足够的空间;当数组大小超出函数分配的可用的栈空间时有可能将存储函数返回值的空间覆盖导致错误。
漏洞利用:攻击代码,即输入给程序的字符串中包含一些可执行代码的字节编码
蠕虫和病毒
共同点:都试图在计算机中传播自己的代码段
不同点:蠕虫可以自己运行,并且能够将自己的等效副本传播到其他机器;而病毒必须将自己添加到包括操作系统在内的其他程序中才能运行。
防御缓冲区溢出的机制
栈随机化 —— 即栈的位置在程序每次运行时都有变化。即便许多机器都运行同样的代码,但其栈地址都是不同的,使得攻击者不容易获取栈位置来存放攻击代码的指针字符。栈随机化在Linux中属于“地址空间布局随机化”技术(ASLR)的一种。
栈破坏检测 —— 当超越局部缓冲区的边界时尝试检测到它,在栈帧中任何局部缓冲区与栈状态之间存储一个特殊的“金丝雀”值,由程序随机产生(或设置为只读)。在函数返回之前检测该值是都被函数的某个操作改变了如果是则程序异常中止。
限制可执行代码区域 —— 只有保存编译器产生的代码的那部分内存才是可执行区域,其他部分可设置为只允许读和写。
变长栈帧
x86-64使用寄存器%rbp作为帧指针(base pointer)
浮点比较操作
浮点比较指令会设置三个条件码:零标志位ZF、进位标志位CF和奇偶标志位PF