导引

零基础入门学习汇编语言 作者:小甲鱼

小甲鱼汇编视频+配套教材:王爽汇编第三版pdf+答案

推荐教材汇编语言

第一章 基础知识

1.1 机器语言

  • 机器语言是机器指令的集合。
  • 机器指令展开讲就是一台机器可以正确执行的命令。
    指令:01010000 (PUSH AX)

1.2 汇编语言的产生

  • 汇编语言的主体是汇编指令。
  • 汇编指令和机器指令的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式。
  • 汇编指令是机器指令的助记符。
    机器指令:1000100111011000
    操作:寄存器BX的内容送到AX中
    汇编指令:MOV AX, BX
    这样的写法与人类语言接近,便于阅读和记忆。
  • 寄存器:简单的讲是CPU中可以存储数据的器件,一个CPU中有多个寄存器。
    AX是其中一个寄存器的代号;
    BX是另一个寄存器的代号。

1.3 汇编语言的组成

  • 汇编语言由以下3类组成:
  1. 汇编指令(机器码的助记符)
  2. 伪指令(由编译器执行)
  3. 其它符号(由编译器识别)
  • 汇编语言的核心是汇编指令,它决定了汇编语言的特性。

1.4 存储器

1.5 指令和数据

1.6 存储单元

1.7 CPU对存储器的读写

1.8 地址总线

1.9 数据总线

1.10 控制总线

小结

  1. 汇编指令是机器指令的助记符,同机器指令一一对应。
  2. 每一种CPU都有自己的汇编指令集。
  3. CPU可以直接使用的信息在存储器中存放。
  4. 在存储器中指令和数据没有任何区别,都是二进制信息。
  5. 存储单元从零开始顺序编号。
  6. 一个存储单元可以存储8个bit(用作单位写成“b”),即8位二进制数。
  7. 1B = 8b; 1KB = 1024B; 1MB = 1024KB; 1GB = 1024MB
  8. 每个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出三种总线,总线的宽度标志这个CPU的不同方面的性能:
  • 地址总线的宽度决定了CPU的寻址能力;
  • 数据总线的宽度决定了CPU与其它器件进行数据传送时的一次数据传送量;
  • 控制总线宽度决定了CPU对系统中其它器件的控制能力。

1.11 内存地址空间(概述)

1.12 主板

1.13 接口卡

1.14 各类存储器芯片

  • 从读写属性上看分为两类:
  • 随机存储器(RAM)
  • 只读存储器(ROM)
  • 从功能和连接上分类:
  • 随机存储器RAM
  • 装有BIOS的ROM
    BIOS:Basic Input/Output System,基本输入输出系统。
    BIOS是由主板和各类接口卡(如:显卡、网卡等)厂商提供的软件系统,可以通过它利用该硬件设备进行最基本的输入输出。在主板和某些接口卡上插有存储相应BIOS的ROM。
  • 接口卡上的RAM

1.15 内存地址空间

PC机中各类存储器的逻辑连接情况:

汇编 8086 TEST 汇编语言入门教程_IP

假设上图中的内存空间地址段分配如下:

  • 地址0~7FFFH的32KB空间为主随机存储器的地址空间(7FFF - 0 + 1 = 8000H = 1000 0000 0000 0000B = \(2^{15}B\)
  • 地址8000H~9FFFH的8KB空间为显存地址空间(9FFF - 8000 + 1 = 2000H = 0010 0000 0000 0000B = \(2^{13}B\)
  • 地址A000H~FFFFH的24KB空间为各个ROM的地址空间(FFFF - A000 + 1 = 5FFF + 1 = 6000H = 0110 0000 0000 0000B = \(2^{14} + 2^{13} = (2^4 + 2^3) * 2^{10} = 24 * 2^{10}B = 24KB\))。

附一张 Intel 8086PC机的内存地址图:

汇编 8086 TEST 汇编语言入门教程_汇编 8086 TEST_02

第二章 寄存器(CPU工作原理)

2.1 通用寄存器

CPU概述

寄存器概述

  • 8086 CPU有14个寄存器,它们的名称为:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。
    这些寄存器我们以后会陆续介绍,因为“以后用到的知识以后再讲——减负”
  • 8086 CPU所有的寄存器都是16位的,可以存放两个字节。
  • AX、BX、CX、DX 通常用来存放一般性数据被称为通用寄存器。
    下面以AX为例,我们看一下寄存器的逻辑结构:
  • 一个16位寄存器可以存储一个16位的数据。
    例如:
  • 数据:18
  • 二进制表示:10010
  • 在寄存器AX中的存储:

例如:

  • 数据:20000
  • 二进制表示:0100111000100000
  • 在寄存器AX中的存储:
  • 一个16位寄存器所能存储的数据的最大值为多少?
    答案:\(2^{16}-1\)
  • 8086上一代CPU中的寄存器都是8位的。为了保证兼容性,这四个寄存器都可以分为两个独立的8位寄存器使用。
  • AX可以分为AH和AL;
  • BX可以分为BH和BL;
  • CX可以分为CH和CL;
  • DX可以分为DH和DL。

汇编 8086 TEST 汇编语言入门教程_段地址_03

  • AX的低8位(0~7位)构成了AL寄存器,高8位(8~15位)构成了AH寄存器。
  • AH和AL寄存器是可以独立使用的8位寄存器。
  • 一个8位寄存器所能存储的数据的最大值是多少?
    答案:\(2^8-1\)

2.2 字在寄存器中的存储

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

2.3 几条汇编指令

汇编 8086 TEST 汇编语言入门教程_寄存器_04

汇编语言不区分大小写。CPU执行下表中的程序段的每条指令后,对寄存器中的数据进行的改变。

汇编 8086 TEST 汇编语言入门教程_汇编 8086 TEST_05

答案:ax:044CH

因为8226H+8226H=1044CH,但是,寄存器AX只有32位,即4个十六进制数,所以高位1存不下。难道要把进位1丢掉吗?no,计算机自有办法,后边会讲。

再看一道:

汇编 8086 TEST 汇编语言入门教程_汇编 8086 TEST_06

注:ax高8位ah,低8位al。bx同理。al:0058H。注意,ah和al是相互独立的8位寄存器,C5H + 93H = 0158H 不要认为进位1会存到高位ah寄存器中,只会截断(丢弃)。若最后一条指令是 add ax, 93H,则 ax = 0158H。

这里的丢失,指的是进位制不能在8位寄存器中保存,但是CPU并不是真的丢掉这个进位值,这个问题会在后面的课程中讨论(CPU会有一个专门的寄存器来存放进制位)。

  • 检测

汇编 8086 TEST 汇编语言入门教程_段地址_07

AX = F4A3H

AX = 31A3H

AX = 3123H

AX = 6246H

BX = 826CH

CX = 6246H

AX = 826CH

AX = 04D8H

AX = 0482H

AX = 6C82H

AX = D882H

AX = D888H

AX = D810H

AX = 6246H

(2)只能使用目前学过的汇编指令,最多使用四条指令,编程计算2的4次方。

mov ax, 2

add ax, ax

add ax, ax

add ax, ax

2.4 物理地址

2.5 16位结构的CPU

概括的讲,16位结构描述了一个CPU具有以下几个方面特征:

  1. 运算器一次最多可以处理16位的数据。
  2. 寄存器的最大宽度为16位。
  3. 寄存器和运算器之间的通路是16位。

决定一个CPU是多少位的,就是看这三方面的特点。

2.6 8086CPU给出物理地址的方法

  • 8086外部有20位地址总线,可传送20位地址,寻址能力1MB。
  • 8086内部为16位结构,它只能传送16位的地址,表现出的寻址能力却只有64KB。

那么,问题来了。8086 CPU如何用内部16位的数据转换成20位的地址呢?

8086 CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。

汇编 8086 TEST 汇编语言入门教程_寄存器_08

  • 8086 CPU读写内存时,发生了这么一些事:
  1. CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
  2. 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
  3. 地址加法器将两个16位地址合并成一个20位的地址;
  4. ······

地址加法器工作原理

  • 地址加法器合成物理地址的方法
    物理地址 = 段地址 * 16 + 偏移地址

注:1230为十六进制的段地址;00C8为十六进制偏移地址。段地址 * 16 相当于十六进制数1230H向左移一位(也就是其对应二进制向左移四位),成为12300H。这样得到的物理地址123C8H就是20位了。

由段地址 * 16 引发的血案······

  • “段地址 * 16”有一个更为常用的说法就是数据左移4位。(二进制)

移位位数

二进制

十六进制

十进制

0

10B

2H

2

1

100B

4H

4

2

1000B

8H

8

3

10000B

10H

16

4

100000B

20H

32

注:

  • 一个数据(十进制)的二进制形式左移1位,相当于该数据乘以2;
  • 一个数据的二进制形式左移N位,相当于该数据乘以2的N次方;
  • 地址加法器如何完成段地址 * 16的运算?
    以二进制形式存放的段地址左移4位。

一个馒头引发的分析······

  • 一个数据的十六进制形式左移1位,相当于乘以16;
  • 一个数据的十进制形式左移1位,相当于乘以10;
  • 一个数据的X进制形式左移1位,相当于乘以X。

2.7 “段地址 * 16 + 偏移地址 = 物理地址”的本质含义

两个比喻说明:

  • 说明“基础地址 + 偏移地址 = 物理地址”的思想:第一个比喻

比如说,学校、体育馆同在一条笔直的单行路上(学校位于路的起点0米处)。读者在学校,要去图书馆,问我那里的地址,我可以用几种方式描述这个地址?

  • 从学校走2826m到图书馆。这2826可以认为是图书馆的物理地址。
  • 从学校走2000m到体育馆,从体育馆再走826m到图书馆。
    第一个距离2000m是相对于起点的基础地址;
    第二个距离826m是相对于基础地址的偏移地址。
  • 说明“段地址 * 16 + 偏移地址 = 物理地址”的思想:第二个比喻
    比如我们只能通过纸条来通信,读者问我图书馆的地址,我只能将它写在纸上告诉读者。显然我必须有一张可以容纳4位数据的纸条才能写下2826这个数据:

不巧的是,没有能容纳4位数据的纸条,仅有两张可以容纳3位数据的纸条。这样我只能以这种方式告诉读者2826这个数据:

汇编 8086 TEST 汇编语言入门教程_寄存器_09

让读者把第一个纸条的3位数200乘以10,然后加上第二个纸条的3位数826。这样得到图书馆地址。

8086 CPU就是这样一个只能提供两张3位数据纸条的CPU。

2.8 段的概念

  • 错误认识:
    内存被划分成了一个一个的段,每一个段有一个段地址。
  • 其实:
    内存并没有分段,段的划分来自于CPU,由于8086 CPU用“(段地址 * 16) + 偏移地址 = 物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。

左边和右边是相同的内存地址区间,我们可以人为的认为它们是一个个段。我们认为有多少个段看需要而定。

两点需要注意:

  1. 段地址 * 16必然是16的倍数,所以一个段的起始地址也一定是16的倍数;
  2. 偏移地址为16位,16位地址的寻址能力为64K(\(2^{16}\)),所以一个段的长度最大为64K。

以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地址 * 16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。

内存单元地址小结

  • CPU访问内存单元时,必须向内存提供内存单元的物理地址。
  • 8086 CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。
  1. 观察下面的地址,读者有什么发现?

物理地址

段地址

偏移地址

21F60

2000H

1F60H

2100H

0F60H

21F0H

0060H

21F6H

0000H

1F00H

2F60H

结论:CPU可以用不同的段地址和偏移地址形成同一个物理地址。

  1. 如果给定一个段地址,仅通过变化偏移地址来进行寻址,最多可以定位多少内存单元?
    结论:偏移地址16位,变化范围为0~FFFFH,仅用偏移地址来寻址最多可寻64K个内存单元。
    比如:给定段地址1000H,用偏移地址寻址,CPU的寻址范围为:10000H~1FFFFH。(因为段地址 * 16,相当于段地址1000H,左移1位)

没有小结的小结

  • 在8086PC机中,存储单元的地址用两个元素来描述。即段地址和偏移地址。
  • “数据在21F60H内存单元中。”对于8086PC机的两种描述:
  1. 数据在内存2000:1F60单元中;
  2. 数据存在内存的2000段中的1F60H单元中。
  • 可根据需要,将起始地址为16的倍数的一组连续内存单元定义为一个段。

检测点2.2

没有通过检测点请不要向下学习!

  1. 给定段地址为 0001H,仅通过变化偏移地址寻址,CPU的寻址范围为____到____。
  2. 有一数据存放在内存 20000H 单元中,现给定段地址为 SA,若想用偏移地址寻到此单元,则 SA 应满足的条件是:最小为____,最大为____。
    提示:反过来思考一下,当段地址给定为多少,CPU无论怎么变化偏移地址都无法寻到20000H单元?

参考答案:

  1. 0010H 1000FH
  2. 1001H 2000H

2.9 段寄存器

  • 段寄存器就是提供段地址的。
    8086 CPU有4个段寄存器:CS、DS、SS、ES
    CS:code segment 代码段寄存器 :提供代码段地址
    DS:data segment 数据段寄存器 :提供数据段地址
    SS:stack segment 堆栈段寄存器 :提供堆栈段地址
    ES:extra segment 附加段寄存器 :前面3个不够,存放到附加段
    当8086 CPU要访问内存时,由这4个段寄存器提供内存单元的地址。

2.10 CS和IP

  • CS和IP是8086 CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。
    CS为代码段寄存器
    IP为指令指针寄存器

8086 PC读取和执行指令相关部件

8086 PC工作过程的简要描述

  1. 从 CS:IP 指向内存单元读取指令,读取的指令进入指令缓冲区;
  2. IP = IP + 所读取指令的长度,从而指向下一条指令;
  3. 执行指令。转到步骤1,重复这个过程。

在8086 CPU加电启动或复位后(即CPU刚开始工作时)CS和IP被设置为CS = FFFFH,IP = 0000H。即在8086 PC机刚启动时,CPU从内存FFFF0H单元中读取指令执行。FFFF0H单元中的指令是8086PC机开机后执行的第一条指令。

在任何时候,CPU将CS和IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,执行。如果说,内存中的一段信息曾被CPU执行过得话,那么,它所在的内存单元必然被 CS:IP 指向过。

2.11 修改CS、IP的指令

在CPU中,程序员能够使用的指令读写部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU的控制。CPU从何处执行指令是由CS、IP中的内容决定的,程序员可以通过改变CS、IP中的内容来控制CPU执行目标指令。我们如何改变CS、IP的值呢?

8086 CPU必须提供相应的指令。先回想我们如何修改寄存器AX中的值?

如何修改AX(通用寄存器)的值

  • mov 指令
  • 如:mov ax, 123
    就是把 123 这个数据放到 ax 中。
    mov指令可以改变8086 CPU大部分寄存器的值,被称为传送指令。
    能够通过mov指令改变CS、IP的值吗?
    mov指令不能用于设置CS、IP的值,8086 CPU没有提供这样的功能。8086 CPU为CS、IP提供了另外的指令来改变它们的值:转移指令
    同时修改CS、IP的内容,如下:
    jmp 段地址:偏移地址
    如:
  • jmp 2AE3:3 告诉CPU跳转到的物理地址为2AE33
  • jmp 3:0B16 告诉CPU跳转到的物理地址为00B46

功能:用指令中给出的段地址修改CS,偏移地址修改IP。

仅修改IP的内容,如下:

jmp 某一合法寄存器

如:

  • jmp ax (类似于 mov IP, ax)
  • jmp bx

功能:用寄存器中的值修改IP。

问题分析:CPU运行的流程

内存中存放的机器码和对应汇编指令情况(初始:CS = 2000H,IP = 0000H):

汇编 8086 TEST 汇编语言入门教程_IP_10

问题分析结果:

  1. mov ax, 6622
  2. jmp 1000:3
  3. mov ax, 0000
  4. mov bx, ax
  5. jmp bx
    这条跳转指令等价于:
    mov IP, bx
    就是说把寄存器bx的值赋给IP寄存器
  6. mov ax, 0123H
  7. 转到第3步执行

我们可以发现这是一个死循环。

2.12 代码段

  • 对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。
  • 可以将长度为N(N<=64KB)的一组代码,存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段。
  • 也就是说,我们的偏移地址不能超过16位(即,一个段的索引长度不能超过64KB,也就是一个段最大能存放64KB)。那如果一个段放不下怎么办?连续存下去即可,从CPU角度看内存是连续的存储单元,是我们人为的把内存分段来索引。所以,起始地址是16的倍数,段地址都是16的倍数。
    例如:

汇编指令

机器码

mov ax, 0000

(B8 00 00)

add ax, 0123

(05 23 01)

mov bx, ax

(8B D8)

jmp bx

(FF E3)

这段长度为10字节的指令,存在从123B0H123B9H的一组内存单元中,我们就可以认为,123B0H123B9H这段内存单元是用来存放代码的,是一个代码段,它的段地址为123BH,长度为10字节。

  • 如何使得代码段中的指令被执行呢?
    将一段内存当做代码段,仅仅是我们在编程时的一种安排,CPU并不会由于这种安排,就自动地将我们定义的代码段中的指令当作指令来执行。CPU只认被 CS:IP 指向的内存单元中的内容为指令。所以要将 CS:IP 指向所定义的代码段中的第一条指令的首地址。

2.9节~2.12节 小结

  • 段地址在8086 CPU的寄存器中存放。当8086 CPU要访问内存时,由段寄存器提供内存单元的段地址。8086 CPU有4个段寄存器,其中CS用来存放指令的段地址。
  • CS存放指令的段地址,IP存放指令的偏移地址。
    8086机中,任意时刻,CPU将 CS:IP 指向的内容当作指令执行。
  • 8086 CPU的工作过程:
  1. 从 CS:IP 指向内存单元读取指令,读取的指令进入指令缓冲器;
  2. IP指向下一条指令;
  3. 执行指令。转到步骤1,重复这个过程。
  • 8086 CPU提供转移指令(jmp)修改CS、IP的内容。

检测点2.3

没有通过检测点请不要向下学习!

下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?

mov ax, bx

sub ax, ax

jmp ax

答:一共修改四次

第一次:读取 mov ax, bx 之后

第二次:读取 sub ax, ax 之后

第三次: 读取 jmp ax 之后

第四次:执行 jmp ax 修改 IP

最后 IP 的值为 0000H,因为最后 ax 中的值为 0000H,所以 IP 中的值也为 0000H。

实验一

  • 查看CPU和内存,用机器指令和汇编指令编程

DEBUG

  • R命令查看、改变CPU寄存器的内容;
  • D命令查看内存中的内容;
  • E命令改写内存中的内容;
  • U命令将内存中的机器指令翻译成汇编指令;
  • T命令执行一条机器指令;
  • A命令以汇编指令的格式在内存中写入一条机器指令。
  • P命令结束循环或者结束程序
  • G命令跳到指定偏移地址处