汇编总结
汇编的发展史
机械语言
- 由0和1组成的机器指令(如:0101 0001 1101 0110)
汇编语言(Assembly Language)
- 使用符号代替机器语言,也称符号语言(如:mov,ax,bx)
高级语言
- C\C++\Java\OC\Swift, 更加接近人类的自然语言(如:int a = b;)
我们的代码在终端设备上是这样的过程:
- 汇编语言和机器语言一一对应,每一条机器指令都有与之对应的汇编指令
- 汇编语言可以通过编译得到机器语言,机器语言可以通过反汇编得到汇编语言
- 高级语言可以通过编译得到汇编语言\机器语言,但是汇编语言\机器语言几乎不可能还原成高级语言
汇编语言的特点
- 可以直接访问、控制各种硬件设备,比如存储器、CPU等,能最大限度地发挥硬件的功能
- 能改不受编译器的限制,对生成的二进制代码进行完全的控制
- 目标代码简短,占用内存少,执行速度快
- 汇编指令是机器指令的助记符,同机器指令一一对应。每一种CPU都有自己的机器指令集、汇编指令集,所以汇编语言不具备可移植性
- 知识点过多,开发者需要对CPU等硬件结构有所了解,不易编写、调试、维护
- 不区分大小写,比如mov 和MOV 是一样的
用途
- 软件安全
- 病毒分析
- 逆向、加壳、脱壳、破解、外挂、免杀、加密解密、漏洞、黑客
- 理解整个计算机系统的最佳起点和最有效途径
- 弄清代码的本质
- 函数的本质究竟是什么
- sizeof(其实是一条指令)
- ++a + ++a + ++a 底层如何执行的?
- 编译器到底帮我们做了什么?
- debug 模式和 release 模式有什么关键的地方被我们忽略了
汇编种类
- 目前讨论的比较多的有
- 8086 汇编(8086 处理器16bit 的CPU)
- Win 32汇编
- Win 64汇编
- ARM 汇编(嵌入式、Mac、iOS)
- 我们iPhone 里用到的都是ARM 汇编,但是不同的设备也是有差异的,因CPU的架构不同。
架构 设备 armv6 iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch armv7 iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4 armv7s iPhone5, iPhone5C, iPad4(iPad with Retina Display) arm64 iPhone6s , iphone6s plus,iPhone6, iPhone6 plus,iPhone5S ,iPad Air, iPad mini2
- 汇编的原理是相通的
几个必要常识
- 要学好汇编,首先要了解CPU等硬件的结构
App程序执行过程
- 硬件相关最为关键的是CPU、内存
- 在汇编中大部分指令都是和CPU和内存相关的
总线
- 每一个CPU芯片都是多管脚的,这些管脚和总线相连,CPU通过总线跟外部器件进行交互
- 总线:一根根导线的集合
- 总线分类
- 地址总线
- 数据总线
- 控制总线
例子
- 地址总线
- 它的宽度决定了CPU的寻址能力
- 8086 的地址总线为20,所以寻址能力为1M(2^20)
- 数据总线
- 它的宽度决定了CPU的单次数据传达量,也就是数据传送速度
- 8086 的数据总线宽度为16,所以单次最大传递2个字节的数据
- 控制总线
- 它的宽度决定了CPU对其它器件的控制能力、能有多少中控制
寄存器
内部部件之间由总线连接
- 对于程序员来说,CPU中最主要的部件就是寄存器,可以通过改变寄存器内容来实现对CPU的控制
- 不同的CPU,寄存器个数、结构是不相同的(8086是16位结构的CPU)
- 8086 有14个寄存器
- 都是16位寄存器
可以存放2个字节
通用寄存器
- AX、BX、CX、DX 这4个寄存器通常用来存放一般性的数据,称为通用寄存器(有时有特定的用途)
- 都是16位
- 为了保证兼容性可分位2个独立的8位来寄存器来使用
- H代表高位寄存器
- L代表地位寄存器
- 通常CPU会先将内存中的数据存储到寄存器中,然后在对寄存器中数据进行运算
例:假设内存中有一块红色区域为3,现在想把它的值加1,并将结果存到蓝色内的内存空间 mov ax,红色内存空间 add ax,1 mov 蓝色内存空间,ax
字和字节
- 在汇编的数据存储中,有2个比较常用的单位
- 字节:byte,一个字节由8bit 组成,可以存储8位寄存器中
- 字:word, 一个字由2个字节组成,这2个字节分别称为字的高字节和低字节
- 比如数据2000(4e20h, 01001110001000000B),高字节是78,低字节是32
- 一个字存在1个16位寄存器中,这个字的高字节、低字节分别存储在这个寄存器的高8位寄存器、低8位寄存器中
8086的寻址方式
- CPU访问内存单元时,要给出内存单元的地址,所有的内存单元都有唯一的地址,叫做物理地址
- 8086有20位地址总线,可以传送20位的地址,1M的寻址能力
- 但它又是16位结构的CPU,它内部能够一次性处理、传输、暂时存储的地址为16位。如果将地址从内部简单地发出,那么它只能送出16位的地址,表现出来的寻址能力只有64KB
8086采用一种在内部用2个16位地址合成的方法来生成1个20位的物理地址
结论:CPU可以用不同的段地址和偏移地址形成同一个物理地址
比如:CPU要访问21F60H 单元,则它给出的段地址SA 和偏移地址EA 满足SA * 16 + EA = 21F60H 即可。
内存分段管理
- 8086是用“基础地址(段地址×16) + 偏移地址 = 物理地址”的方式给出物理地址
为了开发方便,我们可以采取分段的方法来管理内存,比如:
- 地址10000H~100FFH的内存单元组成一个段,该段的起始地址(基础地址)为10000H,段地址为1000H,大小为100H
- 地址10000H1007FH、10080H100FFH的内存单元组成2个段,它们的起始地址(基础地址)为:10000H和10080H,段地址为1000H和1008H,大小都为80H
- 在编程时可以根据需要,将若干连续地址的内存单元看做一个段,用短地址*16定位段地址的起始地址(基础地址),用偏移地址定位段中的单元
段寄存器
- 8086在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址
- 是什么部件提供段地址?段地址在8086的段寄存器中存放
- 8086有4个段寄存器:CS、DS、SS、ES,当CPU需要访问内存时由这4个段寄存器提供内存单元的段地址
- CS(Code Segment):代码段寄存器
- DS(Data Segment):数据段寄存器
- SS(Stack Segment):堆栈段寄存器
- ES(Extra Segment):附加段寄存器
CS和IP
- CS 为代码段寄存器,IP为指令指针寄存器,他们表示了CPU当前读取指令的地址
任意时刻,8086CPU都会将CS:IP指向的指令作为下一条需要取出执行的指令
指令和数据
- 在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息
CPU在工作的时候把有的信息看做指令,有的信息看做数据,为同样的信息赋予了不同的意义
- CPU 根据什么将内存中的信息看做指令?
- CPU将CS:IP 指向的内存单元的内容看做指令
- 如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元比如被CS:IP指向过
jmp指令
- CPU从何处执行指令是由CS、IP中的内容决定的,我们可以通过改变CS、IP的内存来控制CPU的执行目标指令
- 8086 提供了一个mov指令可以用来修改大部分寄存器的值
- mov ax,10、mov bx,20、
- 但是mov指令不能用于设置CS、IP的值
通过转移指令来修改CS、IP的值最简单的就是jmp 指令
DS 和 [arrdeess]
- CPU要读写一个内存单元时,必须要先给出这个内存单元的地址,内存单元地址在8086中由段地址和偏移地址组成
- 8086 中有一个DS寄存器,通常用来存放访问数据的段地址
mov bx,1000H mov ds,bx mov al,[0]
- 上面3条指令的作用将10000H(1000:0)中的内存数据赋值到al寄存器中
- mov al,[address]的意思将DS:address中的内存数据赋值到al寄存器中
- 由于al是8位寄存器,所以是将一个字节的数据赋值给al寄存器
- 8086不支持将数据直接送入段寄存器中,mov ds,1000H是错误的
写几条指令,将al中的数据送入内存单元1000H中
mov bx,1000H mov ds,bx mov [0],al
字型数据的传递(2个字节)
写出下面指令执行后寄存器ax,bx,cx中的值
mov ax,1000H ; ax = 1000H mov ds,ax ; ds = ax mov ax,[0] ; ax = 1123H mov bx,[2] ; bx = 6622H mov cx,[1] ; cx = 2211H add bx,[1] ; bx = 6622H + 2211H = 8833H add cx,[2] ; cx = 8833H
大小端
- 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中(高低\低高)(Big Endian)
- 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中(高高\低低) (Little Endian)
汇编指令
- 汇编指令:mov、add、sub...
- 伪指令:assume、Segment、ends、end...等
- 注释以分号开头
; 代码段 assume cs:code code segment mov ax,1122h mov bx,3344h add ax,bx ; 退出指令 mov ah,4ch int 21h code ends end
- segment和ends的作用是定义一个段,segment代表一个段的开始,ends代表一个段的结束,使用格式为
- 一个有意义的汇编程序中,至少要有一个段作为代码段存放代码
- assume
- 将用作代码段的code段和CPU中的cs寄存器关联起来
- end
- 编译器遇到end时,就结束对源程序的编译
- 下面2句代码的作用是退出程序
mov ah,4ch int 21h
或者
mov ax,4c00h int 21
栈
栈:是一种具有特殊处理方式的存储空间(先进后出)
- 8086会将CS作为代码段的段地址,将CS:IP指向的指令作为下一条需要取出执行的指令
- 8086会将DS作为数据段的段地址,mov ax,[address]就是取出DS:address的内存数据放到ax寄存器中
- 8086会将SS作为栈段的段地址,任意时刻,SS:SP指向栈顶元素
- 8086提供了PUSH(入栈)和POP(出栈)指令来操作栈段的数据
- 比如push ax是将ax的数据入栈,pop ax是将栈顶的数据送入ax
push ax
push ax 的执行由2个步骤完成
- SP = SP - 2,SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
将ax中的内容送入 SS:SP 指向的内存单元处,SS:SP此时指向新栈顶
pop ax
pop ax的执行过程和push ax刚好相反,由以下两步完成
- 将SS:SP指向的内存单元处的数据送入ax中;
- SP=SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶
简书连接
Loop 指令
- loop 指令和cx 寄存器配合使用,用于操作类似于高级语言的for、while
- 使用格式
mov cx,循环次数 标号: 循环执行的程序代码 loop 标号
loop 指令流程
步骤1:先将cx 寄存器的值-1,cx = cx - 1
步骤2:判断cx 的值
- 如果不为零执行标号代码,又执行步骤1
- 如果为零执行loop 后面的代码
补充:
获取数据,除了通过ds段来获取,还可以利用其它段地址来获取
mov ax,ds:[0]
8086 伪指令
- db(define byte)自定义字节
- dw(define word)自定义字
Call 和 ret 指令
Call指令
- call 标号
- 将下一条指令的偏移地址入栈!
- 跳转到定位的地址执行指令!
ret 指令
- ret 指令就是将栈顶的值pop 给IP