前言
跟前面的顺序有点不一样 因为在补笔记233
一、基础知识
(一)Intel处理器体系结构的历史
1.Intel x86系列处理器
- 理论上CISC的性能很难与精简指令集计算机(RISC)相匹敌,但是Intel采用了CISC(CISC之前提过 指令多且格式复杂)
- 向后兼容至8086处理器(诞生于1978年)
- 进化的里程碑如下
- 新特性
- 多媒体操作的指令支持
提供了更加有效率的条件操作指令
机器字长从32位变为64位
多核
- 多媒体操作的指令支持
2.摩尔定律
- 单位面积上可以容纳的晶体管数量
几乎每两年增加一倍
3.Core i7 Broadwell 2015
下面是CPU芯片
粉色的是内存
内存右边的是串形显卡
中间蓝色的BDW Core是八核
蓝色的LLC是高速缓存
左边浅蓝色的是网卡
网卡右边的DMA 和PCU是控制器
网卡下边的SATA是硬盘
左下角是时针
时针旁边是接口
右下角是集成I/O
4.X86兼容处理器:AMD
- 历史上
AMD仅仅跟随着Intel
性能稍差,价格更便宜 - 随后
从DEC和其他业绩下降的公司招聘顶级电路设计师 - 提出了Opteron架构:成为Pentium 4的有力竞争对手
引入了x86-64架构,自主的扩展到64位体系结构 - 近年来
Intel重新迎头赶上,重新占据了半导体技术的世界主导地位
AMD稍有落后,依赖外部的半导体代工厂
5.Intel 64位体系结构发展的历史
-
2001年:Intel试图从IA32彻底转变为IA64
完全不同的体系结构(安腾)
◼ 以legacy(传统)模式执行IA32的指令
◼ 性能令人失望 -
2003年:AMD提出了体系结构进化的解决方案
◼ x86-64位体系结构(现称为AMD64) -
Intel觉得有义务专注于IA64
◼ 难以承认技术路线的错误以及AMD方案更优
Intel 64位体系结构发展的历史 -
2004年:Intel提出了EM64T体系结构实现对IA32的64
位扩展◼ 扩展实现了64位内存寻址技术
◼ 几乎与x86-64相同 -
2019年:英特尔宣布放弃IA64架构
-
除低端x86处理器外,其他处理器均支持x86-64
◼ 但是目前许多程序仍然在32位模式下运行
(二)C语言,汇编语言和机器语言
1.定义
-
体系结构:(指令集体系结构,ISA)编写汇编代码时需要理解的处理器设计部分。
◼ 例如:指令集规范、寄存器组织 -
微体系结构:体系结构的具体实现
◼ 例如:高速缓存大小、核心频率 -
代码格式
◼ 机器语言:处理器可以直接执行的字节级的程序
◼ 汇编语言:文本形式的机器语言 -
举例:常见的指令集体系结构
◼ Intel: x86, IA32, Itanium, x86-64
◼ ARM:几乎所有的移动电话中都使用
2.汇编语言/机器语言视角下的计算机
- PC:程序计数器
◼ 存储下一条要执行指令的地址
◼ 在x86-64中的名称为 RIP - 寄存器文件
◼ 程序的数据会频繁地使用它来存储 - 条件码
◼ 存储最近一次算术逻辑运算的状态
信息
◼ 用于条件分支 - 存储器
◼ 基于字节寻址的阵列
◼ 存储程序和用户数据
◼ 存储栈数据(以实现过程的支持)
3.将C语言代码转换为机器语言
- 代码文件:p1.c p2.c
- 编译命令:gcc –Og p1.c p2.c -o p
◼ 使用基本的编译优化选项 –Og (最新版本GCC支持) - 将编译结果写入文件 p
下面是例子
这是C语言
这个是汇编语言
- 使用下面的命令生成汇编语言
◼ 生成文件:sum.s - 警告:由于编译选项的不同和gcc版本的不同可能会得到不同的编译结果
4.汇编语言的特征
(1)数据类型
- 整数:1、2、4或8字节的
◼ 数据的值
◼ 地址 (无类型的指针) - 浮点数:4、8或10字节
- 代码:指令的字节序列编码
- 没有聚合类型,例如:数组或结构体
◼ 这些在汇编语言中都都表现为在内存中连续分配的字节
(2)操作
- 对寄存器或存储器数据执行算术/逻辑运算
- 在寄存器和存储器间传输数据
◼ 将数据从存储器加载至寄存器
◼ 将寄存器的数据存储至存储器 - 转移控制
◼ 无条件跳转 至/从 过程
◼ 条件分支
5.条件码(重点)
- 汇编器
◼ 将 .s 翻译为 .o
◼ 对每条指令进行二进制编码
◼ 几乎是完整的可执行代码
◼ 缺少了不同文件的链接信息
6.反汇编目标码
objdump –d sum
- 反汇编器
◼ 将 t 的值存储至 dest 指向的地址
◼ 探索目标码的一个十分有用的工具
◼ 可以分析指令的编码序列
◼ 根据目标码重新生成汇编代码
◼ 可以对任何可执行程序文件和.o文件进行反汇编 - 另一种反汇编方法: 使用gdb调试器
◼ 反汇编过程(函数)
◼ 反汇编从sumstore开始的的14个字节目标码
反汇编后的汇编指令如图
目标码如图
7.反汇编的条件
- 任何可以解释为可执行代码的文件
- 反汇编程序分析字节并重构为汇编代码
下面是一个机器指令的例子
这个是C代码
这个是汇编语言
这个是机器指令
二、基本操作
这节比较重要 会考
(一)x86寄存器
- x86-64 寄存器
◼每个寄存器的低4/2/1字节
都有唯一的标识(r开头的是64位 e开头的是32位 可以看到64位比32位多八个寄存器)
- IA32 (x86-32) 寄存器
让我们来逐个分析一下这些寄存器 - eax 累加
- ecx 计数器
- edx 数据
- ebx 基地址
- esi 源操作数
- edi 目标操作数
- esp 栈顶指针
- ebp 栈底指针
(二)数据移动指令(move)
1.汇编语言格式
- [label :] [opcode] [operand 1] [, operand 2]
[标号:] [操作码] [操作数1 ] [,操作数2 ] - 汇编例子: l1: movq $5 , %rax
2.移动数据(重点)
movq Source, Dest
◼ 操作数类型
- 立即数:整数常量
◼ 例如: $0x400, $-533
Example: $0x400, $-533
◼ 和C语言中的常数类似,但需要加前缀 $
◼ 被编码为1、2、4或8个字节 - 寄存器:十六个整数寄存器之一
◼ 例如: %rax, %r13
%rsp有特殊用途,通常不使用
◼ 其他寄存器在一些特殊的指令中也会有特殊用途 - 存储器:指向的内存中8个连续字节,由寄存器给出地址(指针)
◼ 一个简单的例子:(%rax)
◼ 有很多其他的“寻址模式”
3.move指令操作数几种组合
前面几个都好理解
最后一行的操作是
- 先在寄存器组里面找到rax
- 然后根据rax里面的地址找到相应的存储单元 取出操作数的地址
- 再放到rdx寄存器中
4.数据格式(很重要)
记住后缀以及各类型的大小
5.几种简单的存储器寻址模式
- 间接寻址 ® Mem[Reg[R]]
Normal
(R ) Mem[Reg[R]]
◼寄存器 R 指向了存储器的地址
◼和C语言中的指针作用相同 - 基地址+偏移量寻址 D( R ) Mem[Reg[R]+D]
D( R ) Mem[Reg[R]+D]
◼寄存器 R 指定了存储器区域的开始位置
◼常数 D 是偏移量
下面是例子 swap 函数分析
这是C代码
这是汇编代码
可以分析出来各寄存器里的变量:
一开始是这样的
然后执行第一条指令movq (%rdi), %rax # t0 = *xp
接着执行第二条指令movq (%rsi), %rdx # t1 = *yp
movq %rdx, (%rdi) # *xp = t1
movq %rax, (%rsi) # *yp = t0
6.完整的存储器寻址模式(重要)
-
最通用的形式D(Rb, Ri, S)Mem[Reg[Rb] + S * Reg[Ri] + D]
◼D:常数偏移量,可以为 1,2,4或8字节整数
◼Rb:基地址寄存器16个寄存器之一
◼Ri:变址寄存器,除%rsp外的其他寄存器
◼S:比例因子:可以为1,2,4或8 (为什么是这些数字?)(因为地址是跳跃的 与数据类型有关) -
一些特殊形式
Special Cases
(Rb, Ri) Mem[Reg[Rb] + Reg[Ri]]
D(Rb, Ri) Mem[Reg[Rb] + Reg[Ri] + D]
(Rb, Ri, S) Mem[Reg[Rb] + S * Reg[Ri]]
(三)算术、逻辑运算指令
1.地址计算指令
leaq Src, Dst
- Src是寻址模式表达式
◼ 将表达式计算的地址写入Dst - 用途
◼ 计算地址(计算过程中不需要引用存储器)
◼ p = &x[i]
◼ 计算模式为 x + k*y的表达式
◼ k = 1, 2, 4, or 8
下面是例子
这是汇编代码(所以地址计算也可以用于计算x + k*y的表达式)
2.一些算术运算指令
下面是例子
这是C代码
这是汇编代码
其中leaq: 地址计算
salq: 左移
imulq: 乘法
各寄存器中的情况
不同的颜色对应不同的代码
我们可以发现
- 指令顺序和C语言语句顺序不同
- 一些表达式需要由多条指令组合实现
- 一些指令可以实现多个表达式的功能
- (x+y+z)(x+4+48y)可以得到相同的汇编代码