分类:N08_C与汇编
### 汇编语言课程设计课程内容
#### 1. 绪论
##### 1.1 我们要学汇编语言
> 汇编语言仍在发挥不可替代的作用:
>>
>> 效率
>>> 运行效率:开发软件的核心部件,快速执行和实时响应
>>> 开发效率:做适合的事,开发效率无敌
>>
>> 底层:计算机及外围设备的驱动程序
>>> 操作系统的内核
>>> 嵌入式系统:家用电器、仪器仪表、物联网等等
>
> 汇编语言在学习计算机中起到的独特作用-直击计算机系统的核心
>> 便于加深对计算机原理和操作的理解
>> 通过学习和使用汇编语言,能够感知,体会和理解及其的逻辑功能
>>> 向上为理解各种软件系统的原理,打下技术理论基础
>>> 向下为掌握硬件系统的原理,打下实践应用基础
>>
>> 学会底层的程序调试和错误分析方法
##### 1.2 由机器语言到汇编语言
###### 机器语言与机器指令
1. 机器语言是机器指令的集合
2. 机器指令是一台机器可以正确执行的命令
3. 机器指令是由一串二进制数表示,例:01010000
4. 电平脉冲:高低电平组成
5. 早期程序员门的工作状态
> 将0、1的数字编程的程序代码打在卡带或卡片上,1打孔,0不打孔,再将程序通过纸带机或卡片机输入计算机,进行运算。
###### 汇编语言与汇编指令
1. 汇编语言的主题是汇编指令
2. 汇编指令和机器指令的差别在于指令的表示方法上
> 汇编指令是机器指令便于记忆的书写格式
> 汇编指令是机器指令的助记符
```
机器指令:1000100111011000
操作:将寄存器BX的内容送到AX中
汇编指令:MOV AX BX
```
3. 寄存器:CPU中可以存储数据的器件,一个CPU中有多个寄存器
4. 用汇编语言编写程序的工作过程
程序员->编写->汇编指令->经过编译器->生成->机器码
```
;汇编语言程序实例
assume cs:codesg
codesg segment
start:
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00h
int 21h
codesg ends
end
```
> 以上代码说明:
>> 伪指令(由编译器执行)如:assume, codesg
>> 汇编指令(机器码的助记符)如:mov, add
>> 其他符号(由编译器识别)如:start, end
##### 1.3 计算机的组成
##### 1.4 内存的读写与地址空间
##### 1.5 汇编语言实践环境搭建
#### 2. 访问寄存器和内存
##### 2.0 阶段导学
##### 2.1 寄存器及数字存储
1. 8086CPU有14个寄存器
- 通用寄存器:AX、BX、CX、DX
- 变址寄存器:SI、DI
- 指针寄存器:SP、BP
- 指令指针寄存器:IP
- 段寄存器:CS、SS、DS、ES
- 标志寄存器:PSW
2. 共性
- 8086CPU所有的寄存器都是16位的,可以存放2个字节
##### 2.2 mov和add指令
1. 学习汇编指令 - 用中学
|汇编指令|控制CPU完成的操作|用高级语言的语法描述|
|---|---|---|
|mov ax, 18|将18送入AX|AX =18|
|mov ah, 78|将78送入AH|AH = 78|
|add ax, 8|将寄存器AX中的数值加上8|AX=AX+8|
|mov ax, bx|将寄存器BX中的数据送入寄存器AX|AX=BX|
|add ax, bx|将AX,BX中的内容相加,结果存到AX中|AX=AX+BX|
> 注:汇编指令不区分大小写
##### 2.3 确定物理地址的方法
1. 物理地址
- CPU访问内存单元时要给出内存单元的地址
- 所有的内存单元构成的存储空间室一个一维的线性空间
- 每一个聂村单元在这个空间中都有唯一的地址,这个唯一的地址称为物理地址
- 事实
* 8086有20位地址总线,可传递20位地址,寻址能力为1M
* 8086是16位结构的CPU
1. 运行器一次最多可以处理16位的数据,寄存器的最大宽度为16位
2. 在8086内部处理的、传输、暂存的地址也是16位,寻址能力也只有64KB
- 问题:8086如何处理在地址空间上的这个矛盾
- 8086CPU的解决办法:
* 用两个16位地址(段地址、偏移地址)合成一个20位的物理地址
- 地址加法器合成物理地址的方法
* 物理地址 = 段地址X16 + 偏移地址
例如:
```
段地址:1230
+ 偏移地址: 00C8
---------------
物理地址:123C8
```
- 本质含义:
* CPU在访问内存时,用一个基础地址(段地址X16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址
##### 2.4 内存的分段表示法
###### 用分段的方式管理内存
1. 8086CPU用“(段地址X16)+偏移地址= 物理地址”的方式给出内存单元的物理地址
2. 存层并没有分段,段的划分来自于CPU
###### 同一段内存,多种分段方案
1. 起始地址(基础地址)为10000H
2. 段地址位1000H,大小为100H
> 或者:
1. 起始地址(基础地址)为10000H和10080H,
2. 段地址为1000H和1008H,大小均为80H
###### 用不同的段地址和偏移地址形成同一个物理地址
|物理地址|段地址|偏移地址|
|-|-|-|
|21F60H|2000H|1F60H|
|21F60H|2100H|0F60H|
|21F60H|21F0H|0F60H|
|21F60H|21F6H|0000H|
|21F60H|1F00H|2F60H|
1. 在8086PC机种存储单元地址的表示方法
2. 例如:数据在21F60H内存单元中,段地址是2000H,说法
* (a)数据存在内存2000:1F60单元中;
* (b) 数据存在内存的2000H段中的1F60H单元中
3. 偏移地址16位,变化范围为0-FFFH,用偏移地址最多寻址64KB
4. 例:给定段地址2000H,用偏移地址寻址的范围:20000H-2FFFFFH,共64K
###### 段地址很重要! 用专门的寄存器存放段地址
> 4个段寄存器:
- CS-代码寄存器
- DS-数据段寄存器
- SS-栈段寄存器
- ES-附加段寄存器
> 偏移地址可以用多种方式提供- 8086丰富的取址方式
##### 2.5 Debug的使用
###### Debug是什么
1. Debug 是DOS系统中的著名的调试程序,也可以运行在windows系统模式下
2. 使用Debug程序,可以查看CPU各种寄存器中的内容、内存的情况,并且在机器指令级跟踪程序的运行
##### 2.6 CS、IP与代码段
###### 两个关键的寄存器
1. CS:代码段寄存器
2. IP:指令指针寄存器
3. CS:IP :CPU将内存中CS:IP 指向的内容当作指令执行
4. 示例:在CS和IP指示下代码的执行
* 8086CPU当前状态:CS中内容为2000H,IP中内容为0000H,通过地址加法器获得内存实际地址,并通过数据总线将指令输入到指令缓冲器,等待cup执行。
* 内存20000H-20009H处存放着可执行的机器代码
5. 8086PC工作过程的简要描述:
- 1.从CS:IP 指向内存单元读取指令,读取的指令进入指令缓冲器
- 2.IP = IP + 所读取指令的长度,从而指向下一条指令
- 3.执行指令,转到步骤1,重复这个过程
##### 2.7 jmp指令
###### 修改CS、IP的指令
1. 事实:执行何处的指令,取决于cs:ip
2. 应用:可以通过改变CS、IP中的内容,来控制CPU要执行的目标指令
3. 问题:如何修改CS、IP的值
4. 方法1:Debug中的R命令可以修改寄存器的值-rcs,rip
* DeBug 是调试手段,并非程序方法
5. 方法2:用指令修改(是禁止的,8086CPU不提供对CS和IP修改的指令)
* mov cs 2000H
* mov ip 0000H
6. 方法3:转移指令jmp
###### 转移指令jmp
1. 同时修改CS、IP的内容
* jmp 段地址:偏移地址
jmp 2AE#:3
jmp 3:0B16
* 功能:用指令中给出的段地址修改CS,偏移地址修改IP。
2. 仅修改IP的内容
* jmp某一合法寄存器
jmp ax (类似与 mov IP,ax)
jmp bx (类似与 mov IP,bx)
##### 3.1 内存中字的存储
###### 内存中字的存储
1. 事实:对8086CPU,16位作为一个字
2. 问题:16位的字存储在一个16位的寄存器中,如何存储
3. 回答:高8位放高字节,低8位放低字节
4. 问题:16位的字在内存中需要2个连续字节存储,怎么存放?
5. 回答:低位字节存在低地址单元,高位字节存在高地址单元
* 20000D(4E20H)存放0,1两个单元,18D(0012H)存放在2,3两个单元
###### 字单元
1. 字单元:由2个地址连续的内存单元组成,存放一个字形数据(16位)
2. 原理:在一个字单元中,低地址单元存放低位字节,高地址单元存放高位字节
* 在起始位置为0的单元中,存放的是4E20H
* 在起始位置为2的单元中,存放的是0012H
3. 问题:
1. 0地址单元中存放的字节型数据是(20H)
2. 0地址字单元中存放的字型数据是(4E20H)
##### 3.2 用DS和[address]实现字的传送
###### 要解决的问题:CPU从内存单元中要读取数据
1. 要求:CPU要读取的一个内存单元的时候,必须先给出这个内存单元的地址;
2. 原理:在8086PC中,内存地址由段地址和偏移地址组成(段地址:偏移地址)
3. 解决方案:DS和[address]配合
1. 用DS寄存器存放要访问的数据的段地址
2. 偏移地址用[...]形式直接给出
```
例1:
mov bx, 1000H
mov ds, bx
mov al, [0]
```
> 将10000H(1000:0)中的数据读到al中
```
例2:
mov bx, 1000H
mov ds, bx
mov [0], al
```
> 将al中的数据写到10000H(1000:0)中
4. 将段地址送入DS的两种地址方法
* 法1 mov ds, 1000H
* 法2 mov bx, 1000H
mov ds, bx
> 8086CPU 不支持将数据直接送入段寄存器(硬件设计的问题)
* 套路:数据->一般寄存器->段寄存器
```
内存:
10000H 23
10001H 11
10002H 22
10003H 66
指令
mov ax, 1000H
mov ds, ax
mov ax, [0]
mov bx, [2]
mov cx, [1]
add bx, [1]
add cx, [2]
```
##### 3.3 DS与数据段
###### 1.用mov 指令操作数据
|指令形式|示例|
|-|-|
|mov 寄存器,数据|mov ax,8|
|mov 寄存器,寄存器|mov ax,bx|
|mov 寄存器, 内存单元|mov ax, [0]|
|mov 内存单元,寄存器|mov [0], ax|
|mov 段寄存器,寄存器|mov ds, ax|
> 已知:mov 段寄存器, 寄存器
* 推测:mov 寄存器, 段寄存器
> 已知:mov 内存单元,寄存器
* 推测:mov 内存单元,段寄存器
* 推测:mov 段寄存器, 内存单元
> 已知:mov 寄存器, 数据
* 推测:mov 段寄存器, 数据(不行)
###### 2.加法add和减法sub指令
|add指令形式|示例|
|-|-|
|add 寄存器,数据|add ax, 8|
|add 寄存器,寄存器|add ax, bx|
|add 寄存器,内存单元|add ax, [0]|
|add 内存单元,寄存器|add [0], ax|
##### 3.4 栈与栈操作的实现
###### 1. 栈结构
1. 栈是一种只能在一端进行插入或者删除的数据结构
2. 栈有两个基本的操作:入栈和出栈
1. 入栈:将一个新的元素放到栈顶
2. 出栈:从栈顶取出一个元素
3. 栈顶的元素总是最最后入栈,需要出栈时,又最先被从栈中取出
4. 栈的操作规则:LIFO (Last In First Out, 后进先出)
5. CPU提供的栈机制
1. 现在的CPU中都有栈的设计
2. 8086CPU提供相关的指令,支持用栈的方式访问内存空间
3. 基于8086CPU的编程,可以将一端内存当作栈来使用
6. PUSH(入栈)和POP(出栈)指令
1. push ax: 将ax中的数据送入栈中
2. pop ax: 将栈顶取出数据送入ax(以字为单位对栈进行操作)
3. 例:将100000H-1000FH内存当作栈来使用
```
mov ax, 0123H
push ax
mov bx, 2266H
push bx
mov cx, 1122H
push cx
pop ax
pop bx
pop cx
```
7. 问题
1. CPU如何知道一段内存空间被当作栈使用?
2. 执行push和pop的时候,如何知道那个单元是栈顶单元
8. 回答:
> 8086 CPU中,有2个与栈相关的寄存器:
- 栈段寄存器:ss -存放栈顶的段地址
- 栈顶指针寄存器: sp -存放栈顶的偏移地址
> 任意时刻,SS:SP 指向栈顶元素
9. 栈的操作
```
mov ax, 1000H
mov ss, ax
mov sp, 0010H
mov ax, 001AH
mov bx, 001BH
push ax
push bx
pop ax
pop bx
```
> 说明:
1. push ax
- 1 SP = SP -1;
- 2 将ax中的内容送入ss:sp 指向的内存单元处,ss:sp此时指向新栈顶
2. pop ax
- 1 将 ss:sp 指向的内存单元处的数据送入ax中
- 2 SP = SP+2, ss:sp 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶
3. 栈顶超界问题
- 如何能够保证在入栈,出栈时,栈顶不会超出栈空间?
* 当栈满的时候再使用push指令入栈,将发生栈顶超界问题
* 当栈空的时候再使用pop指令出栈,将发生栈顶超界问题
4. 栈顶超界问题的解决
- 只能靠程序员自己解决
10. 栈的总结
1. push,pop实质上就是一种内存传送指令,可以在寄存器和内存直接传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的
2. 执行push和pop指令时,SP中的内存自动改变
3. 8086CPU提供的栈操作机制:
- 在SS,SP中存放栈顶的段地址和偏移地址,入栈和出栈指令根据SS:SP指示的地址,按照栈的方式访问内存单元
- push指令执行步骤:
1. sp=sp-2
2. 向ss:sp指向的字单元中送入数据
- pop指令的执行步骤
1. 从ss:sp指向的字单元中读取数据
2. sp=sp-2
##### 3.5 关于“段”的总结
###### 1. 各种段
1. 基础:物理地址 = 段地址X16+偏移地址
2. 做法
1. 编程时,可以根据需要将一组内存单元定义为一个段
2. 可以将起始地址为16的倍速,长度为N(N<=64K)的一组地址连续的内存单元,定义为一个段
3. 将一端内存定义为一个段,用一个段地址指示段,用偏移地址反问段内的单元,在程序中可以完全由程序员安排
3. 三种段
> 1.数据段
- 1. 将段地址放到DS中
- 2. 用mov,add,sub等访问单元的指令时,CPU将我们定义的数据段中的内容当作数据段来访问
> 2.代码段
- 1. 将段地址放在CS中,将段中第一条指令的偏移地址放在IP中
- 2. CPU将执行我们定义的代码段中的指令
> 3.栈段
- 1. 将段地址放在SS中,将栈顶单元的偏移地址放在SP中
- 2. CPU在需要进行栈操作(push,pop)时,就将我们定义的栈段当作栈空间来用
4. 综合示例:按要求设置段并执行代码
```
mov bx, 1000H
mov ds, bx
mov bx, 1001H
mov ss, bx
mov sp, 10H
mov ax, [0]
mov bx, [2]
push ax
push bx
pop ax
pop bx
mov [0], ax
mov [2], bx
```
#### 3. 汇编语言程序
##### 4.1 用汇编语言写的源程序
###### 1. 用汇编语言编写程序的工作过程
1. 汇编程序:包含汇编指令和伪指令的文本
2. 汇编指令:对应机器码的指令,可以被编译为机器指令,最终被CPU执行
3. 伪指令:没有对应的机器码的指令,最终不被CPU所执行
4. 谁来执行伪指令呢?
- 伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作
5. 程序返回(套路):程序结束运行后,将CPU的控制权交给使他运行的程序(常为DOS系统)
###### 2. 程序中的三种伪指令
1. 段定义
1. 一个汇编程序是由多个段组成的,这些段被用来存放代码,数据或当作栈空间来使用
2. 一个有意义的汇编程序中至少由一个段,这个段用来存放代码
3. 定义程序中的段:每一个段都需要有段名
* 段名 segment --段的开始
* 段名 ends --段的结束
2. end(不是ends)
> 汇编程序的结束标记。若程序结尾处不加end,编译器在编译程序时,无法知道程序在何处结束
3. assume(假设)
> 含义是假设每一段寄存器和程序中的某一个用segment...ends定义的段相关联--assume cs:codesg指CS寄存器与codesg关联,将定义的codesg当作程序的代码段使用
```
assume cs:codesg
codesg segment
start:
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00h
int 21h
codesg ends
end
```
###### 3. 源程序经编译链接后变为机器码
###### 4. 汇编程序的结构
1. 在Debug中直接写入指令编写的汇编程序
1. 适用于功能简单,短小精悍的程序
2. 只需要包含汇编指令即可
```
debug
-a
mov ax, 27a1
add ax, 892C
```
2. 单独编写成为源程序后再编译为可执行文件的程序
1. 使用与编写大程序
2. 需要包含汇编指令,还要有知道编译器工作的伪指令
3. 源程序有一些段构成,这些段存放代码、数据,或将某个段当做栈空间。
```
assume cs:code, ds:data, ss:stack
data segment
dw 0123H,0456H,0789H,0abcH,0defH
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
code segment
mov ax,stack
mov ss,ax
mov sp, 20h ;设置栈段
mov ax, data
mov ds, ax ;设置数据段
mov bx,0
mov cx,8
s:push [bx]
add bx,2
loop s
...
code ends
end
```
##### 4.2 由源程序到程序运行
##### 4.3 用Debug跟踪程序的执行
##### 5.1 [...]和(...)
##### 5.2 Loop指令
##### 5.3 Loop指令使用再例
##### 5.4 段前缀的使用
##### 6.1 在代码段中使用数据
##### 6.2 在代码段中使用栈
##### 6.3 将数据、代码、栈放入不同段
#### 4. 内存寻址方式
#### 5. 流程转移和子程序
#### 6. 中断及其应用
#### 7. 高级汇编语言技术