汇编语言
==学习路径==:
[汇编语言](https://www.bilibili.com/video/BV1Rs411c7HG)
==测试点练习路径==:
基础知识
概念
汇编语言是机器指令便于记忆的的书写格式
组成
- 汇编指令(机器码的助记符)
- 伪指令(由编译器执行)
- 其他符号(由编译器识别):+-*÷ 由编译器转换为逻辑运算符(与或非)传给计算机执行
基础知识
存储单元的地址(地址信息)
器件的选择,读或写命令(控制信息)
读或写的数据(数据信息)
==总线==:连接CPU与其他芯片的导线
地址总线
- 通过地址总线来指定存储单元
- 一个cpu有N根地址总线,则可以说这个cpu地址总线的宽度位N,最多可以寻找2^n^个存储单元
- 64位的速度由64位CPU、64位操作系统、64位软件共同决定
数据总线
- 数据总线的宽度决定cpu与外界的数据传送速度
控制总线
- 有多少根控制总线,就意味着cpu提供了对外部器件的多少种控制
==内存地址空间==:
假如一个cpu的地址总线宽度为10,那么可以寻址1024个内存单元,这些内存单元就构成了这个cpu的内存地址空间
==主板==:
一个PC有一个主板,包含核心器件和主要器件,这些器件通过总线相连
==接口卡==:
cpu对外部设备不能直接控制,直接控制这些设备进行互作的是插在扩展插槽的接口卡
==存储器芯片==:
从读写属性上看分为:随机存储器(RAM)和只读存储器(ROM)
从功能和连接上分类:
随机存储器RAM
装有BIOS的ROM
- BIOS(基本输入输出系统):由主板和各类接口卡厂商提供的软件系统,可以通过它利用该硬件设备进行最基础的输入输出。cpu与显示器通过显卡;网卡。
接口卡上的RAM
主板逻辑连接:
寄存器(CPU工作原理)
CPU概述
由运算器、控制器、寄存器等器件组成
寄存器概述
8086CPU有14个寄存器
==通用寄存器==:
通用寄存器:AX,BX,CX,DX:存放一般数据
8086CPU所有的寄存器都是16位,可以存放两个字节

为保证兼容性,会将这四个16位的寄存器都分别分为两个独立的8位的寄存器(可独立使用),以兼容以前的8位寄存器
AX可以分为AH(高位)和AL(低位),BX、CX、DX同理

==字==:
一个字=2字节(2B),字高位和低位分别对应寄存器的高位和低位
汇编指令
汇编指令不区分大小写


00C5H+93H=0058H(不是0158H,因为高低8位是独立的,不共用,进制位会被单独存放在特点的寄存器)
物理地址
==16位结构的CPU==:
- 运算器一次最多可以处理16位的数据
- 寄存器的最大宽度位16位
- 寄存器和运算器之间的通路是16位
8086cpu给出物理地址的方法
地址加法器工作原理:
合成物理地址的方法:
物理地址=段地址*16+偏移地址

段的概念
内存并没有分段,段的划分来自于CPU
CPU访问内单元时,必须向内存提供内存单元的物理地址
8086CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址
- CPU可以用不同的段地址+偏移地址形成同一个物理地址

段寄存器
8086CPU有四个段寄存器:
- CS(代码段寄存器,Code Segment)
- DS(数据段寄存器,Data Segment)
- SS(堆栈段寄存器,Stack Segment)
- ES(附加段寄存器,Extra Segment)
当8086CPU需要访问内存时,由这4个寄存器提供内存单元的段地址
==CS和IP(指令指针寄存器)==

工作流程:
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲区
- IP=IP+所读取指令的长度,从而指向下一条指令
- 执行指令,转到步骤1
在8086CPU加电启动时,CS=FFFFH,IP=0000H,即CPU从内存FFFF0H单元中读取指令执行
==修改CS:IP的指令==
mov不能用于设置CS、IP的值
同时修改CS、IP:
而是用转移指令:jmp
jmp 段地址:偏移地址
jmp 2AE3:3 对应物理地址2AE33
jmp 3:0B16 对应物理地址0003*16=00030,00030+00B16=00B46
- 仅修改IP的内容
jmp 某一合法寄存器
jmp ax (类似于mov IP,ax)//用寄存器的值修改IP
练习:
依次执行:
mov ax,6622H ax=6622
jmp 1000:3
mov ax,0000 ax=0000
mov bx,ax bx=0000
jmp bx
mov ax,0123H ax=0123
执行mov ax,0000 不断循环
代码段
对于8086PC机,可以根据需求将一组内存单元定义为一个段
可以将长度为N的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段
CS:IP指向代码段,则代码段就被执行,CPU只认CS:IP所指代码段
实验
查看CPU和内存,用机器指令和汇编指令编程
使用工具:DOSBox
R:查看、改变CPU寄存器内容

T:执行指令

D:查看内存内容

(1)

- 通过-a,将指令输入
- 用过-d,来查看内存

- 用u来将指令转换为汇编语言

- 改变CS:IP指向刚刚执行的指令

- 执行当前指令,发现ax被覆盖,并且IP的值也指向了下一个指令

(2)将下面3条指令写入从2000:0开始的内存单元中,利用这3条指令计算2的8次方
- mov ax,1
- add ax,ax
- jmp 2000:0003
- -a写入指令
- -r改变CS:IP指向指令内存地址
- -t依次执行

(3)

- -d查看,其中30(十六进制)对应0(ASCII),2F(十六进制)对应/(ASCII),依次对应

- -e更改,发现无法更改,因为写在ROM中的,为只读

(4)


观察右上角出现图案,是因为在往显存中写数据,所以出现了图案
寄存器(内存访问)
内存中字的存储
- 任何两个地址连续的内存单元,N号单元和N+1号单元可以看成两个内存单元,也可以看成一个地址为N的字单元中的高位字节单元和低位字节单元
1地址字单元的中存放的字型数据是124EH
2地址字单元的中存放的字型数据是0012H
DS和[address]
mov指令格式:
- [ ]:表示一个内存单元
- [ ]中的0:表示内存单元中的偏移地址

注意:8086CPU不支持将数据直接送入段寄存器(DS)(硬件设计的问题)
所以mov ds,1000H是不合法的
数据–>通用寄存器–>段寄存器
如何将数据从寄存器送入内存单元?也就是从内存单元读数据的逆过程,但是注意通过通用寄存器作为中间人

字的传送
8086CPU是16位结构,有16根数据线,可以一次性传送16位数据(一个字)

- 先写入数据

- 查看寄存器信息,以及CS:IP所指内存地址的信息

- 写入指令

- 执行指令,mov ax,[0]:将DS为1000,偏移量[0],也就是地址为10000H的值,以及高一个的地址的值传入ax,此处也就是10000H以及10001H的值,因此ax=1123,以此类推

add bx,[1]:将bx的值(6622)加上DS为1000,偏移地址为[1](也就是10001H的值)及高一个的地址(10002H的值),因此bx=6622H+2211H=8833H,以此类推

注意:如何来看是吧[0]、[1]……当作字节(1字节)来看,还是当作字(2字节)来看
如果为ax则当作字来看,则取当前及上一个的地址
如果为al或者ah,则当成字节来看,取当前的地址

写入数据
写入指令(11316为十进制,对应十六进制为2C34;sub指令就是相减)
执行指令

sub bx,[2] 就为bx=bx-[2],所以bx=2c34-1122=1b12,查看内存信息,1b12已经存入内存

mov、add、sub指令
mov:

add、sub:
数据段
8位为一个字节,16位(2字节)为一个字,32位(4字节)为双字


逻辑地址等于段地址左移4位后加上偏移地址,即逻辑地址 = 段地址 x 16 + 偏移地址

- 0001:0000 = 0001 *16 +0000 = 0010
栈
push指令:入栈
pop指令:出栈
8086CPU的入栈和出栈都是以字为单位进行
- push ax:将寄存器ax中的数据送入栈中
- pop ax:从栈顶取出数据送入ax
入栈:
出栈:
段寄存器SS:存放栈顶的段地址
寄存器SP:存放栈顶的偏移地址
SS:SP指向栈顶元素
push指令执行过程:
pop是push的逆过程
push:先SP=SP-2,再放入数据
pop:先取出数据,再SP=SP+2
注意:pop之后,数据其实依旧在,只是下一次push的时候,会将其覆盖
当栈为空时,SP指向最高地址单元的下一个单元,这样当有数据进来,sp先减2,指向1000EH再将数据写入
换个角度看:
栈顶越界的问题
要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据过多而导致超界
执行出栈时也要注意,以防栈空的时候出栈导致超界
push、pop指令
push ds:将一个段寄存器中的数据入栈
pop ds:出栈,用一个段寄存器接收出栈的数据



- mov ax,001AH
- mov bx,001BH
- mov cx,1000H
- mov ss,cx
- mov sp,0010H
- push ax
- push bx
- mov ax,0
- mov bx,0
- pop bx
- pop ax

- mov cx,1000H //段寄存器过渡
- mov ss,cx
- mov sp,0010H //栈顶指针
- mov ax,002AH //赋值
- mov bx,002BH
- push ax //入栈
- push bx
- mov ax,0 //清零
- mov bx,0
- pop ax //用ax来接收刚刚bx的数据
- pop bx

- mov ax,1000H
- mov ss,ax
- mov sp,2 //因为push之前先执行SP=SP-2,所以将栈顶指针指到10002H
- mov ax,2266H
- push ax
栈段
将一段内存当作栈段是我们自己安排的,CPU不会在执行push、pop时自动将我们定义的栈段当作栈空间来使用


汇编语言程序
源程序
汇编指令:有对应的机器码的指令,可以被编译为机器指令,最终被CPU执行
伪指令:没有对应的机器码的指令,最终不被CPU所执行,它是由编译器来执行的指令
- 伪指令:
segment和ends是一对成对使用的伪指令
功能是定义一个段,segment说明一个段开始,ends说明一个段结束
- 伪指令使用格式:
段名 segment
段名 ends
一个汇编指令由多个段组成,这些段用来存放代码、数据 、当作栈空间来使用
一个有意义的汇编程序中至少有一个段,这个段用来存放代码
end是一个汇编程序的结束标记,编译器在编译过程中碰到end就结束对源程序的编译
- assume:
假设某一段寄存器和程序中的某一个segment……ends定义的段相关联
源程序:源程序文件中的所有内容
程序:源程序中最终由计算机执行处理的指令或数据
程序最先以汇编指令的形式存在源程序中,经编译连接后变为机器码,存在可执行文件中
- 标号:一个标号指代了一个地址
- codesg放在segment前面,作为一个段的名称,他最终会被编译连接程序处理为一个段的段地址
DOS是一个单任务操作系统