Assembly

一、基础知识

1.1.机器语言

  • 是机器指令的集合
  • 展开来讲就是一台机器可以正确执行的命令

1.2.汇编语言的产生

  • 主体是汇编指令
  • 汇编语言和机器语言的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式
  • 汇编指令是机器指令的助记符,与机器语言/指令是一一对应的

寄存器

简单来说是CPU中可以存储数据的器件,一个CPU中有多个寄存器。

1.3.汇编语言的组成

  • 汇编指令 – 机器码的助记符 [ 每一个CPU都有自己的汇编指令集 ]
  • 伪指令 – 由编译器执行
  • 其他符号 – 由编译器识别

1.4.存储器

  • CPU是计算机的核心部件,它控制整个计算机的运作并进行运算,要想让一个CPU工作,就必须向它提供指令和数据
  • 指令和数据在存储器中存放,也就是平时所说的内存
  • 在一台计算机中内存的作用仅次于CPU
  • 离开了内存,性能再好的CPU也无法工作
  • 磁盘不同于内存,磁盘上的数据或程序如果不读到内存,就无法被CPU使用

1.5.指令和数据

  • 指令和数据是应用上的概念
  • 在内存或磁盘上,指令和数据没有任何区别,都是二进制信息

1.6.存储单元

  • 存储器被划分为若干个存储单元,每个存储单元从零开始顺序编号

1.7.CPU对存储器的读写

必须和外部器件/芯片进行三类信息的交互

  • 存储单元的地址 – 地址信息
  • 器件的选择,读过些命令 – 控制信息
  • 读或写的数据 – 数据信息
  • 物理上:一根根导线的集合
  • 逻辑上:
    • 地址总线
    • 数据总线
    • 控制总线

1.8.地址总线

  • CPU是通过地址总线来指定存储单元
  • 地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址

1.9.数据总线

  • CPU与内存或其他器件之间的数据传送是通过数据总线来进行的
  • 数据总线的宽度决定了CPU和外界的数据传送速度

二、寄存器 – CPU工作原理

2.1.CPU概述

  • 一个典型的CPU运算器控制器寄存器等器件组成,这些器件靠内部总线相连。
  • 内部总线 – 实现CPU内部各个器件之间的联系
  • 外部总线 – 实现CPU和主板上其他器件的联系

寄存器

AXBXCXDXSIDISPBPIPCSSSDSESPSW

2.2.通用寄存器

8086CPU14个寄存器,所有寄存器都是16位

  • AXBXCXDX – 通常用来存放一般性数据,被称为通用寄存器

2.3.字在寄存器中的存储

一个字可以存在一个16位的寄存器中,这个字的高位字节和地位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中

2.4.几条汇编指令

不区分大小写

  • mov A, B – 将B的值赋给A
  • add A, B – 将AB相加后存放在A

ADD 会影响 CF 标志寄存器的值

2.5.物理地址

  • CPU访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间

2.6.16位结构的CPU

  • 运算器一次最多可以处理16位的数据
  • 寄存器的最大宽度为16位
  • 寄存器和运算器之间的通路是16位
  • 8086有20位的地址总线,可传送20位地址,寻址能力为1M
  • 8086内部为16位结构,他只能传送16位的地址,表现出的寻址能力却只有64K
  • 8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址
    • 物理地址 = 段地址 × 16 + 偏移地址

2.7.段的概念

  • 错误认识 – 内存被划分成了一个一个的段,每一个段有一个段地址
  • 其实 – 内存并没有分段,段的划分来自于CPU,由于8086CPU段地址 × 16 + 偏移地址 = 物理地址的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
  • 在编程时可以根据需求,将若干个地址连续的内存单元看作一个段,用段地址 × 16定位段的起始地址,用偏移地址定位段中内存单元。
    • 段地址 × 16必然是16的倍数,所以一个段的起始地址也一定是16的倍数
    • 偏移地址为16位16位地址的寻址能力为64K,所以一个段的长度最大为64K
  • CPU可以用不同的段地址和偏移地址形成同一个物理地址

2.8.段寄存器

  • 段寄存器就是提供段地址的
  • 8086CPU有4个段寄存器
    • CSCode-Segment
    • DSData-Segment
    • SSStack-Segment
    • ESExtra-Segment
  • 当8086CPU要访问内存时,由这4个段寄存器提供内存单元的段地址

2.9.CSIP寄存器

  • 是8086CPU中最关键的寄存器,他们指示了CPU当前要读取指令的地址
    • CS – 代码段寄存器
    • IP – 指令指针寄存器
  • 8086CPU工作过程的简要描述
    • CS:IP只想内存单元读取指令,读取的指令进入指令缓冲器
    • IP = IP + 所读取指令的长度,从而指向下一条指令
    • 执行指令
    • 转回第一步并重复过程
  • 在8086CPU加点启动或复位后,CSIP被设置为CS = FFFFHIP = 0000H
  • 即,在启动时,CPU从内存FFFF0H单元中读取指令执行
  • FFFF0H单元中的指令是8086PC机开机后执行的第一条指令

2.10.修改CSIP的指令

  • 转移指令
    • 同时修改 – jmp 段地址 : 偏移地址
    • 只改IPjmp 偏移地址

2.11.Debug的使用

  • -R – 查看寄存器的内容
  • -R 寄存器 – 查看指定寄存器的内容,并可进行修改
  • -D – 查看内存中的内容
  • -E – 改写内存中的内容
  • -U 段地址 : 偏移地址 – 将内存中的机器指令翻译成汇编指令
  • -T – 执行一条机器指令
  • -A –以汇编指令的格式在内存中写入机器指令

几个小操作

  • -D FFF0:0 FF
    • 可以查看到生产日期
  • -E B810:0 ASCII
    • B810 是显卡地址,通过输入ASCII码可以输出字符

三、寄存器 – 内存访问

3.1.DS[Address]

  • CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址
  • 在8086PC中,内存地址由段地址和偏移地址组成
  • 8086CPU中由一个DS寄存器,通常用来存放要访问的数据的段地址
  • DS不能直接赋值,需要经由另一个寄存器

3.2.数据段

对于8086PC机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段)

可以将一组长度为N(N<=64K)、地址连续且起始地址为16倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段

3.3.栈

8086CPU的入栈/出栈操作都是以字为单位进行的

两个基本操作:

  • Push – 入栈,将一个新的元素放到栈顶
  • Pop – 出栈,从栈顶取出一个元素

两个寄存器:

  • SS – 段寄存器,存放栈顶的段地址
  • SP – 存放栈顶的偏移地址

PushPop等有关栈的操作指令,修改的只是SP。也就是说,栈顶的变化最大范围为:0 ~ FFFFH

3.4.栈段

可将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。

将段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行PushPop指令等,就将我们定义的栈段当作栈空间来用

四、编译与连接

4.1.可执行文件

  • 程序 – 从源程序中的汇编指令翻译过来的机器码
  • 数据 – 源程序中定义的数据
  • 相关描述信息 – 如程序大小、所占内存空间等

4.2.程序的完成过程

  • 编写
  • 编译
  • 连接
  • 执行

4.3.几种指令

  • 汇编指令 – 有对应的机器码的指令,可以被编译为机器指令,最终被CPU执行
  • 伪指令 – 没有对应的机器码的指令,最终不被CPU执行。由编译器翻译成汇编指令

4.4.定义一个段

segmentcodes是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令

  • 一个汇编程序由多个段组成,这些段被用来存放代码、数据或当作栈空间来使用
  • 一个有意义的汇编程序中至少要有一个代码段

4.5.汇编代码的首尾

  • assume – 将某一段寄存器和程序中的某一个段关联起来
  • end – 是一个汇编程序结束的标记

4.6.标号

  • 一个标号指代了一个地址
  • 放在 segment 前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址

4.7.DOS中的程序运行

  • Dos是一个单任务操作系统

我们可以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行处理的指令或数据称为程序。程序最先以汇编指令的形式存在于源程序中,经编译、连接后转变为机器码,存储在可执行文件中。

4.8.程序的返回

1
2
MOV AX,4c00H
INT 21H

4.9.编译

将伪指令和汇编指令转换为机器码

4.10.连接

  • 当源程序很大时,可以将它分为多个源程序文件来进行编译,每个源程序编译成为目标文件后,再用连接将他们整合在一起,生成可执行文件
  • 程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件
  • 一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以在只有一个源程序文件,又不需要调用时也必须用连接程序对目标文件进行处理,生成可执行文件

4.11.exe文件在Dos中的运行

  • Commandexe文件载入内存
  • Command设置CPUCS:IP指向程序的第一条指令(即程序的入口),从而使程序得以运行
  • 程序运行结束后,返回Command中,CPU继续执行Command

Debug

  • 输入debug fileName.exe即可将文件载入,载入完成后cx中放的是程序的长度

  • 加载过程:

    • 找到一段起始地址为SA 0000的容量足够的空闲内存区
    • 在这段空间的前256个字节中创建一个称为程序的前缀(PSP)的数据区,Dos要利用PSP来和加载程序进行通信
    • 从这段内容区的256字节开始(PSP)后面,将程序装入,程序的地址被设为SA + 10H : 0
    • 将该内存区的段地址存入DS,初始化其他相关寄存器后,设置CS : IP指向程序的入口

五、[BX]loop

5.1.[BX]

  • 将偏移值放入BX
  • 通过 [BX] 获取偏移值的内容

5.2.Loop

  • CX中存放循环次数
  • loop指令中的标号所表示地址要在前面

六、包含多个段的程序

6.1.DW

Define Word – 定义字型数据,每个数据 2B

6.2.DB

Define Byte – 定义字节数据,1B

6.3.End

  • CPU会先去找程序中的End,根据End后面的标号找到程序的开头

6.4.多个段

8086CPU一个段 64 KB

  • 通过assume cs : code, ds: data, ss: stackcsdsss分别和codedatastack段相连后,CPU并不会指向这些段。
    • 因为 assume只是伪指令,是让编译器读的,要在汇编完成之后才真正能指向

七、更灵活的使用内存空间

7.1.AND

按位与指令

7.2.OR

按位或命令

7.3.ASCII

是美国的一种编码方式

7.4.SIDI寄存器

AxBxCxDx是一样的,只是不能拆成高低

一般,SI作为数据段的起始地址,DI作为目标地址

八、数据处理的两个基本问题

8.1.BXSIDIBP

8086 CPU 中,只有这四个可以放在中括号里,而且若是组合放在中括号,只能如下

  • [BX和SI]
  • [BX和DI]
  • [BP和SI]
  • [BP和DI]

以下组合是错误的

  • [BX和BP]
  • [DI和SI]

BP默认是在SS中的

BX默认是在DS中的

8.2.机器指令处理的数据所在所致

  • 读取
  • 写入
  • 运算

对机器指令来讲,不关心数据的值是多少,而是关心指令执行前一刻,它将要处理的数据所在位置

有三个概念用来表达数据的位置

  • 立即数 – 对于直接包含在机器指令中的数据,称为立即数,在汇编指令中直接给出,存在于CPU指令缓冲区
  • 寄存器 – 指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名
  • 段地址和偏移地址

8.3.寻址方式

  • 直接寻址
    • – [常数]
  • 寄存器间接寻址
    • – [寄存器]
  • 寄存器相对寻址
    • – [寄存器 + 常数]
  • 基址变址寻址
    • – [寄存器+寄存器
  • 相对基址变址寻址
    • – [寄存器+寄存器+常数]

8.4.处理的数据的长度

  • word ptr指明了指令访问的内存单元是一个字的单元 2B
  • byte ptr指明了指令访问的内存单元是一个字节的单位 1B

8.5.DIV指令

DX – 存放被除数的高 16位,除完后,存放商

AX – 存放被除数的低 16位,除完后,存放余数

AL – 除完后,存放商

AH – 除完后,存放余数

8.6.DD指令

double word双字,4字节

做除法运算时,应把前两个(高)字节放在AX,后两个(低)字节放在DX

8.7.DUP指令

伪指令

db 3 dup (2)db 0, 0, 0

db 3 dup (0, 1, 2)db 0, 1, 2, 0, 1, 2, 0, 1, 2

db 3 dup ('abc', 'ABC')db 'abcABCabcABCabcABC'

九、转移指令

  • 无条件转移指令
  • 条件转移指令
  • 循环指令
  • 过程
  • 中断指令

9.1.offset

取得标号得到偏移地址

1
2
start: mov ax, offset start ; 相当于 mov ax, 0
s: mov ax, offset s ; 相当于 mov ax, 3

9.2.jmp

需要给出以下某种信息

  • 转移的目的地址
  • 转移的距离(段间转移、段内短转移、段内进转移)
  • CPU 不需要目的地址就可以实现对 IP 的修改
  • jmp shortIP 的修改范围为 -128~127,即向前最多128个字节,向后最多127个字节
  • jmp 实现近转移,通过位移进行跳转

9.3.jmp far ptr

  • CS - 标号所在段的段地址
  • IP - 标号所在段中的偏移地址
  • far ptr 指明了指令用标号的段地址和偏移地址修改 CSIP

十、callret

10.1.call

10.2.ret

10.3.call far ptr

相当于

1
2
3
push CS
push IP
jmp far ptr

10.4.转移地址在寄存器中的 call

10.4.1 - call word ptr

1
2
push IP
jmp word ptr

10.4.2 - call dword ptr

十一、标志寄存器

8086CPU 的标志寄存器有 16 位,其中存储信息通常被称为程序状态字 PSW

15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
OF DF IF TF SF ZF AF PF CF

11.1.ZF

  • 第6位是ZF,零标志位。它记录相关指令执行后,其结果是否为0。如果(真),结果为0,那么ZF=1;如果(假),结果非0,那么ZF=0。

  • ZR – 值为 1

  • NZ – 值为 0

11.2.PF

奇偶标志位寄存器,结果中有奇数个 1 时 PF 为 0,偶数个 1 时 PF 为 1

  • PE – 值为 1
  • PO – 值为 0

11.3.SF

记录相关指令执行后,其结果是否为负,如果(真),结果为负,SF=1,如果(假),结果非负,SF=0

  • NG 值为 1
  • PL 值为 0

11.4.CF

  • 在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。

  • 对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位

  • CY` – 值为 1

  • NC – 值为 0

11.5.OF

  • 溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出,如果(真),发生了溢出,OF=1,如果(假),没有发生溢出,0F=0。

  • OV – 值为 1

  • NV – 值为 0

11.6.ADC

  • Adc 是带进位加法指令,它利用了CF位上记录的进位值。

11.7.SBB

  • Sbb 是带借位减法指令,它利用了CF位上记录的借位值。

11.8.CMP

  • Cmp 是比较指令,它的功能相当于 sub 指令,只是不保存结果。Cmp 指令执行后,将对标志寄存器产生影响,其它相关指令通过识别这些被影响的标志位来得知比较结果。
  • JE – 相等跳转
  • JNE – 不相等跳转
  • JB – 小于跳转
  • JNB – 不小于跳转
  • JA – 大于跳转
  • JNA – 不大于跳转

11.9.检测比较结果的条件转移指令

11.10.DF 标志和串传送指令

  • 方向标志位。在串处理指令中,控制每次操作后 SIDI 的增减。DF=0,每次操作后 SIDI 递增;DF=1,每次操作后 SIDI 递减。DF 标志位与串传送指令(movsb、movsw)有关,而串传送指令与游戏修改无关,所以就不讲解了。
  • MOVSB – 以字节为单位传送,将 DS:SI 指向的内存单元中的字节送入 ES:DI 中,然后根据标志寄存器DF 的值,将 SIDI 递增或者递减
  • MOVSW – 以字为单位传送,将 DS:SI 指向的内存字单元中 word 送入 es:di 中,然后根据标志寄存器 DF 位的值,将 sidi 递增 2 或递减 2
  • CLD – 将 DF 设置为 0
  • STD – 将 DF 设置为 1

11.11.PushfPopf

11.11.1.Pushf

  • 将标志寄存器的值压入栈

11.11.2.Popf

  • 从栈中弹出数据,送入标志寄存器

11.12.TF

  • 跟踪标志位。用于程序调试。如果 TF=1,则 CPU 处于单步执行指令的工作方式,此时,每执行完一条指令,就显示 CPU 各个寄存器的当前值及 CPU 将要执行的下一条指令。如果 TF=0,则处于连续工作模式。

11.13.AF

  • 辅助进位标志位。在下列情况下,AF的值被设置为1,否则其值为0。
    • 在字操作时,发生低字节向高字节进位或借位时。
    • 在字节操作时,发生低4位向高4位进位或借位时。

11.14.IF

  • 中断允许标志位。用来决定 CPU 是否响应 CPU 外部的可屏蔽中断发出的中断请求,当 IF=1,响应中断请求,当 IF=0,不响应中断请求。

11.15.LEANOP

11.15.1.LEA

  • Lea 为有效地址传送指令。
  • 将源操作数给出的有效地址传送到指定的寄存器中。
  • 格式:lea 操作对象1,操作对象2
    • LEA AX, [217A]

11.15.2.NOP

  • 本指令不产生任何结果,仅消耗几个时钟周期的时间,接着执行后续指令,常用于程序的延时等。

十二、内中断

12.1.内中断的产生

  • 中断是 CPU 处理外部突发事件的一个重要技术
  • 能够使 CPU 在运行过程中对外部事件发出的中断请求及时的处理,处理完成后又立即返回断点,继续进行原来的工作
  • 引起中断的原因或者说发出中断请求的来源叫中断源。根据中断源的不同,可以把中断分为硬件中断和软件中断两大类,而硬件中断又可以分为外中断和内中断两类
  • 中断优先级
    • 除法错误、溢出中断、软件中断
    • 不可屏蔽中断
    • 可屏蔽中断
    • 单步中断

12.2.内中断处理程序

  • CPU 的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得 CPU 根据中断信息可以找到要执行的处理程序
  • 中断信息中包含有标识中断源的类型码。根据 CPU 的设计,中断类型码的作用就是用来定位中断处理程序
  • 若要定位中断处理程序,需要知道他的段地址和偏移地址,而如何根据 8 位的中断类型码得到中断处理程序的段地址和偏移地址就要引入中断向量表

12.3.中断向量表

  • CPU 用 8 位的中断类型码,通过中断向量表找到相应的中断处理程序的入口地址
  • 是一个中断向量的索引
  • 中断向量表在内存中保存,其中存放着 256( 28 ) 个中断源所对应的中断处理程序的入口
  • 中断向量表指定存放在内存地址 0 处
  • 从内存 0000 : 0000 到 0000 : 03FF 的 1024 个单元中存放着中断向量表

12.4.中断过程

  • 找到这个入口地址的最终目的是用它设置 CSIP ,使 CPU 执行中断处理程序
  • 用中断类型码找到中断向量,并用它设置 CSIP ,这个工作是由 CPU 硬件自动完成的
  • CPU 硬件完成这个工作的过程被称为中断过程
    • 取得中断类型码
    • 标志寄存器的值入栈(保护标志位)
    • 设置标志寄存器的第 8 位 TF 和 第 9 位 IF 的值为 0
    • CS 的内容入栈
    • IP 的内容入栈
    • 从内存地址为中断类型码 * 4 和中断类型码 * 4 + 2 的两个字单元中读取中断处理程序的入口地址设置 IPCS

12.5.中断处理程序

  • 由于 CPU 随时都可能检测到中断信息,也就是说,CPU 随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中
  • 而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中
  • 中断处理程序的编写方法和子程序的比较相似
    • 保存用到的寄存器
    • 处理中断
    • 回复用到的寄存器
    • iret 指令返回

12.6.除法错误中断的处理

  • CPU 执行 DIV 等除法指令时,如果发生了除法溢出错误,将产生中断类型码为 0 的中断信息,CPU 将检测到这个信息,然后引发中断过程,转去执行 0 号中断所对应的中断处理程序

12.7.编程处理 0 号中断

  • 当发生除法溢出的时候,产生 0 号中断信息,从而引发中断过程
    • 取得中断类型码 0
    • 标志寄存器入栈,TFIF 设置为 0
    • CSIP 入栈
    • (IP)=(0*4)(CS)=(0*4+2)
  • 自己编写一段代码,自定义命名,这里为 do0,因为 CPU 随时可能将 CS:IP 指向这个的入口,所以这个程序应该写在内存中
    • 由于我们实在操作系统之上使用计算机,所有的硬件资源都在操作系统的管理之下,所以我们想要得到一块内存存放 do0,应该向操作系统申请,不过此处会直接将程序直接放入安全地址 0000:02000000:02FF
  • 把程序的入口放入中断向向量表
    • 因为除法溢出对应的中断类型码为 0,它的中断处理程序的入口地址应该从 04 地址单元开始存放,段地址存放在 0\4+2 字单元中,偏移地址存放在 0*4 字单元中

十三、INT 指令

  • 可以在程序中使用 int 指令调用任何一个中断的中断处理程序

13.1.BIOSDOS 中断的安装过程

  • 电脑加点启动,初始化 CS = 0FFFFH,IP = 0,自动从 FFFF:0 单元开始执行程序。FFFF:0 处有一条跳转指令,CPU 执行该指令后,转去执行 BIOS 中的硬件系统检测和初始化程序
  • 初始化程序将建立 BIOS 所支持的中断向量,即将 BIOS 提供的中断例程的入口地址登记在中断向量表中
  • 硬件系统检测和初始化完成后,调用 int 19H 进行操作系统的引导。从此将计算机交由操作系统控制
  • DOS 启动后,除完成其他工作外,还将它所提供的中断李晨装入内存,并建立相应的中断向量

十四、端口的读写

  • in
  • out

14.1.SHL

  • 逻辑左移
  • 将一个寄存器或者内存单元中的数据向左移位
  • 将最后移除的一位写入 CF

14.2.SHR

  • 逻辑右移

14.3.CMOS RAM 中存储的时间信息

  • 秒 – 00H
  • 分 – 02H
  • 时 – 04H
  • 日 – 07H
  • 月 – 08H
  • 年 – 09H
  • 使用 BCD 进行表示
  • CMOS 的端口是 70H,存放这 CMOS RAM 单元的地址
  • 71H 为数据端口,存放从选定的 CMOS RAM 单元中读取的数据,或要写入到其中的数据