汇编语言

基础知识

机器语言与汇编语言

  • 机器语言:一列二进制数,计算机将其变为一列高低电平,驱动计算机的电子器件。

  • 汇编语言:机器语言难以辨别和记忆,便产生了便于记忆的机器指令的书写格式

\(e.g.:\)

操作:把寄存器BX中的内容送到AX中

机器指令:1000100111011000

汇编指令:mov ax, bx

graph LR 汇编指令--编译器-->机器码

汇编语言的组成

  • 汇编指令:机器码的助记符,有对应得到机器码

  • 伪指令:没有对应的机器码,由编译器执行,计算机不执行

  • 其它符号:没有对应的机器码,有编译器识别,\(e.g.+、-、*、/\)

计算机原理补充

  • 指令和数据:都是二进制信息,在内存或磁盘上几乎没有区别,\(e.g.\)对于1000100111011000,既可以把它看作89D8H,也可以看作mov ax, bx指令来执行

  • CPU对存储器的读写:CPU要从内存中读数据,先要指定存储单元的地址,对哪个器件操作,执行什么操作...

    • 信息交互:存储单元的地址(地址信息)、器件的选择,读或写的命令(控制信息)、读或写的数据(数据信息)

    • 总线:连接CPU和其它芯片的导线,根据传送信息的不同,分为地址总线、控制总线、数据总线

      • 地址总线:CPU与通过地址总线指定存储器单元。例如一个具有10根地址总线的CPU可以传递10位二进制数据,表示\(2^{10}\)种不同的数据。

      • 数据总线:内存与其他器件之间通过数据总线进行数据传送,数据总线的宽度决定数据传送速度。

      • 控制总线:控制总线的宽度决定CPU对外部器件的控制能力。

CPU从内存中读取数据的过程如上图

  1. CPU通过地址总线将地址信息3发出;

  2. CPU通过控制总线发出内存读命令,选中存储器芯片,通知它将要从中读取数据;

  3. 存储器将3号单元中的数据8通过数据总线送入CPU中;

写入数据过程类似。

寄存器

CPU主要由运算器、控制器、寄存器构成,在CPU中:

  • 运算器进行信息处理

  • 寄存器进行信息存储

  • 控制器控制各种期间工作

  • 内部总线连接各器件并进行数据传送

  • \(8086CPU\)中有14个寄存器,分别为:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW

几条简单的汇编指令(不区分大小写):
mov ax, 18——将18送入AX
mov ah, 78——将78送入AH
add ax, 8——将寄存器AX中的数值加8 
mov ax, bx——将寄存器BX中的数据送入AX中
add ax, bx——将AX与BX中的数据相加并将结果存储在AX中

通用寄存器

\(8086CPU\)中寄存器为16位,存放两个字节。寄存器AX、BX、CX、DX通常用来存放一半性的数据,称为通用寄存器。而且它们都可以氛围两个独立使用的8位寄存器使用。

  • AX分为AH和AL

  • BX分为BH和BL

  • ……

    2.png

如上图,数据20000的二进制为100111000100000,存储在AX中,将16位的AX分为两个8位寄存器,即AX的低八位(0-7)构成AL,表示78;高八位(8-15)构成AH,表示32

字在寄存器中的存储:一个字有两个字节组成,它的高位字节和低位字节分别存储在寄存器的高八位和低八位寄存器中。

段寄存器

物理地址

所有的内存单元构成的存储空间是一个一维空间,每个内存单元在这个空间中有唯一的地址,这个地址称为物理地址

8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力。但是8086CPU是16位结构,在内部一次性处理、传输、暂时存储的地址为16位。如果只是将地址从内部简单发出,那么它只能送出16位的地址,表现出的寻址能力只有64KB。

如图,8086CPU在内部用两个16位地址合成一个20位的物理地址。

3
  1. CPU中的相关部件提供一个16位的地址,一个称为段地址,另一个称为偏移地址

  2. 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;

  3. 地址加法器将2个16位的地址合成为一个20位的地址;物理地址=段地址*16+偏移地址

  4. 地址加法器再通过内部总线将20位的物理地址送入输入输出控制电路

  5. 输入输出控制电路将20位的物理地址送上地址总线;

  6. 20位的物理地址被地址总线传送到存储器;

$e.g.段地址:0X1230,偏移地址:0X00C8,物理地址= 0X1230*16+0X00C8 = 123C8 $

解读物理地址的生成过程物理地址=段地址*16(基础地址)+偏移地址

  • 段的划分:段的划分来自于CPU而不是内存,假设我们认为\(10000H\)\(100FFH\)为一个段,段的起始地址为\(10000H\),大小为\(100H\);我们也可以把\(10000H~1007FH\)\(10080H~100FFH\)认为是两个段,起始地址分别为\(10000H\)\(10080H\),大小都为\(80H\)

  • 段的长度:基础地址=段地址*16,则段的起始地址为16的倍数;偏移地址为16位,则一个段的最大长度为\(64KB\)

寄存器CS、IP

CS和IP指示了CPU当前要读取指令的地址,其中CS为代码段寄存器,IP为指令指针寄存器。

在8086PC机中,CPU将从M*16+N开始读取一条指令并执行,如下动图

4

(8086CPU当前状态:CS:2000H,IP:0000H;内存20000H~20009H中存放可执行机器码)

  1. 初始状态:CS:2000H,IP:0000H;

  2. CS、IP中的内容送入加法器得到物理地址;

  3. 地址加法器将物理地址送入输入输出控制电路;

  4. 输入输出控制电路将20000H送上地址总线;

  5. 从内存单元20000H中得到机器指令B8 23 01,通过数据总线送入CPU;

  6. 输入输出控制电路将机器指令送入指令缓冲器;

  7. 执行指令mov ax, 0123H,IP的值增加

执行下一段重复上述过程。

修改CS、IP中的指令

程序员沟通过修改寄存器中的内容实现对CPU的控制,而CPU从何处执行指令是由CS、IP中的内容决定的,因此,改变CS、IP的内容可以控制CPU执行目标指令。

;同时修改CS、IP的内容
jmp 段地址;偏移地址
;仅修改IP的内容
jmp 合法寄存器

\(e.g.\)

jmp 2AE3: 3,执行后CS=2AE3H,IP=0003H

jmp ax,执行前ax=1000H,CS=2000H,IP=0003H;执行后ax=1000H,CS=2000H,IP=1000H

注:mov IP, ax不合法

段寄存器DS和[address]

\(8086CPU\)中,DS寄存器通常用来存放要访问的段地址

\(e.g.\)10000H(1000:0)中的数据读取到al

mov bx, 1000H
mov ds, bx
mov al. [0]  ; […]表示一个内存单元

注:DS是段寄存器, mov ds, 1000H非法。例如,要将AL中的数据/指令送入DS中,指令为:

mov ax, 1000H
mov ds, ax
mov [0], al

mov、add、sub指令

mov 寄存器, 数据          mov ax, 8
mov 寄存器,寄存器         mov ax, bx
mov 寄存器, 内存单元      mov ax, [0]
mov 内存单元, 寄存器      mov [0], ax
mov 段寄存器, 寄存器      mov ds, ax

寄存器SS、SP

CPU的栈机制

在基于\(8086CPU\)编程时,可以将一段内存当作栈使用。最基本的两个指令PUSH实现入栈,POP实现出栈。如下图将10000H~1000FH这段内存用作栈使用

push ax / push […]  ; 将寄存器AX/[内存单元]中的数据送入栈中
pop ax / pop […]   ; 将栈顶数据去除送入AX/[内存单元]中
5

SS、SP

\(8086CPU\)中,段寄存器SS存放栈顶地址,SP存放偏移地址。任意时刻,SS:SP指向栈顶元素

下图描述了\(8086CPU\)push指令的执行过程,pop过程相反

6

注:入栈时,栈顶从高地址向低地址方向增长

当栈为空时,按照SS:SP指向栈顶的原则,SS:SP指向栈最底部单元下面的单元

若栈顶超界,可能会覆盖原本存放的数据,会引发一系列的错误

第一个程序

程序从写出到执行

graph LR test.asm--编译masm-->test.obj--连接link-->test.exe--加载command-->内存中的程序
  1. 编写汇编源程序;

  2. 对源程序编译连接;

    1. 使用编译程序对源程序进行编译,生成目标文件;

    2. 用连接程序对目标文件进行连接,生成可执行文件;

可执行程序

  • 程序(机器码)和数据(源程序定义的数据)

  • 相关的描述信息(大小。占的内存空间)

  1. 执行可执行文件中的程序;

操作系统按照可执行文件的描述信息,将可执行文件的机器码和数据载入内存,进行相关的初始化(设置CS:IP指向),然后由CPU执行程序。

源程序

assume cs:codesg

codesg segment  ; 定义一个段,段的名称为"codesg",段从此开始
                ; 标号:codesg指代一个地址,最终被编译、连接程序处理为一个段的段地址

    mov ax, 0123H
    mov bx, 0456H
    add ax, bx
    add ax, ax

    mov ax, 4c00H
    int 21H
codesg ends  ; 名称为"condesg"的段结束

end

下面对程序进行说明

伪指令:

没有对应的机器指令,不被CPU执行,由编译器执行

  • segmentend是一对伪指令,用来定义一个段,XXX是段的名称;每个程序至少有一个段;
  • end是汇编语言的结束标记,编译器在编译汇编程序过程中,遇到end则结束对源程序的编译;
  • assume将具有特殊用途的段与相关的段寄存器相关联汇编指令程序;

汇编指令

源程序中的汇编指令组成了最终由计算机执行的程序,这里的程序是源程序中最终由计算机执行、处理的指令或数据,程序最先以汇编指令的形式存在源程序中,经编译、连接后转为机器码,存储在可执行文件中。

[BX]和loop指令

[bx]和内存单元

要完整地描述一个内存单元,需要内存单元的地址内存单元的长度

  • 用[0]表示内存单元,0表示单元的偏移地址

  • 用[bx]表示内存单元,偏移地址在bx

段前缀

在指令mov ax, [bx]中,偏移地址由bx给出,段地址在ds中;我们也可以显式地给出段地址所在的寄存器,例如mov ax, es:[bx]

"ds:"、"cs:"、"ss:"、"es:"被称为段前缀

loop

定义“( )”表示寄存器段寄存器内存单元中的内容,(al)、(bl)等为字节型,(ds)、(ax)为字型例如

  • (ax)=0010H表示ax中的内容为0010H

  • (21000H) = 0010H表示2000:1000处的内容为0010H

  • (ax) = ((ds)*16+2)表示mov ax, [2]

  • (ax) = (ax)+2 表示add ax, 2

  • (sp) = (sp)-2表示push ax

  • (ax) = ((ss)*16+(sp)) \\ (sp) = (sp)+2表示pop ax

loop指令:用于实现循环功能,cx中存放循环次数

loop 标号

CPU执行loop指令时,进行两步操作

  1. (cx) = (cx)-1

  2. 判断(cx)是否为0

    • \((cx) = 0\)向下执行程序

    • \((cx) \not= 0\)转至标号处执行程序

包含多个段的程序(P123)

更灵活地定位内存地址的方法

and 和 or指令

  • and:逻辑与,按位与运算

  • or:逻辑或,安慰或运算

[bx+idata]

在前面,我们使用[bx]来指明一个内存单元,还可以用另一种更灵活的方式:[bx+idata],它的偏移地址为(bx)+idata

例如:mov ax, [bx+200]

将一个长度为2个字节的内存单元的内容送入ax中,段地址在ds中,偏移地址为bx中的内容加上\(200\),即(ax)=((ds)*16+(bx)+200),也可以写成如下格式:

mov ax, [200+bx]
mov ax, 200[bx]  
mov ax, [bx].200 

SI和SD

sisd是与bx功能相近的寄存器,但是不能分成两个8位寄存器使用

下面3组指令功能相同

mov bx, 0
mov ax, [bx]

mov si, 0
mov ax, [si]

mov di, 0
mov ax, [di]

不同寻址方式的灵活应用

  • [idata]:用一个常量表示地址,用于直接定位一个内存单元

  • [bx]:用一个变量表示内存地址,用于间接定位一个内存单元

  • [bx+idata]:在一个起始地址的基础上使用变量间接定位内存单元,借此可以从不同的起始地址定位字符串中的字符,详见P145

  • [bx+si]

  • [bx+si+idata]

数据处理的两个基本问题

  • 处理的数据在什么地方

  • 处理的数据有多长

这两个问题需要在机器指令中给出说明,以保证计算机正常工作

为了描述简洁,定义reg表示寄存器,包括ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,sp,bp,si,di;定义sreg表示段寄存器,包括ds,cs,ss,es

bx,si,di和bp

\(8086CPU\)中,这4个寄存器用在[…]中进行内存单元寻址,他们可以单独使用,也可以组合使用,例如:

mov ax, [bx]
mov ax, [bx+si]
mov ax, [bp]
mov ax, [bp+si]
mov ax, [bx+si+idata]
mov ax, [bp+si+idata]

使用[bx],段地址默认在ds中,使用[bp],段地址默认在ss

注:[bx+bp],[si+di]是错误的组合

机器指令处理的数据在什么地方

处理数据的机器指令可以分为:读取、写入、运算。对于机器指令来说,它们不关心数值,而是指令执行前一刻,将要处理的数据的位置,例如,CPU内部、内存中、端口

mov bx, [0]    ;内存,ds:0单元
mov bx, ax     ;CPU内部,ax寄存器
mov bx, 1      ;CPU内部,指令缓冲器

汇编语言中数据位置的表达

  • 立即数(idata)对于直接包含在机器指令中的数据,执行前在CPU的指令缓冲器中,在汇编指令中直接给出,例如mov ax, 1

  • 寄存器:指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名,例如mov ax, bx

  • 段地址和偏移地址:指令要处理的数据在内存中,用[…]给出,例如mov ax, [bx+si+1]

寻址方式

o_220708075715_7.png (917×833) (cnblogs.com)

指令要处理的数据有多长

\(8086CPU\)中,可以处理两种尺寸的数据,byteword

在机器指令中要指令指令进行的是字操作还是字节操作

  • 利用寄存器名,比如axal

  • 利用word/byte ptr指明

例如:

mov ax, 1
mov al, 1

mov word ptr ds:[0], 1
mov byte ptr ds:[0], 1
  • 默认指明,例如push只进行字操作

div指令——除法

  • 除数:8位或16位,在一个reg或内存单元中

  • 被除数:除数为8位,被除数为16位,在AX中存放;除数为16位,被除数为32位,在AX和DX中中存放(DX存高16位,AX存低16位)、

  • 结果:除数为8位,AL存储商,AH存储余数;除数为16位,AX存储商,DX存储余数

例如:

div byte ptr ds:[0]
;含义如下
(al)=(ax)/((ds)*16+0)的商
(ah)=(ax)/((ds)*16+0)的

实现100001/100(100001>0XFFFF,所以为16位除法)

mov dx, 1
mov ax, 86A1H
mov bx, 100
div bx

dd伪指令

dbdw定义字节型数据和字型数据,而dd用来定义双字型数据占两个字

dup

与db,dw和dd配合使用,进行数据的重复,例如:db 3 dup(0)相当于db 0, 0 , 0

转移指令的原理

修改IP或同时修改CS和IP的指令称为转移指令,分为以下几类

  • 段内转移:只修改IP,例如jmp ax

    • 短转移:范围-128-127

    • 近转移:范围-32768-32767

  • 段间转移:同时修改CS和IP,例如jmp 1000:0

offset——取得标号的偏移地址

jmp指令

jmp指令需要给出两种信息

  • 转移的目的地址

  • 转移的距离

依据位移进行转移

jmp short 目的标号

实现段内短转移,一次最多向前越过128个字节,向后越过127个字节

CPU在执行jmp时不需要知道转移的目的地址,转移指令没有告诉CPU的目的地址,而是告诉CPU要转移的位移

8位位移=标号处的地址-jmp指令后的第一个字节的地址

jmp near ptr 标号

实现段内近转移,转移范围为-32768-32767,16位位移=表好处的地址-jmp指令后的第一个字节的地址

转移的目的地址在指令中的jmp命令

jmp far ptr 标号

(CS)=标号所在段的段地址,(IP)=标号在段中的偏移地址,

通过debug可以发现,指令对应的机器码包含转移的目的地址

转移地址在寄存器中的jmp指令

jmp 16位reg

(IP)=(16位reg)

转移地址在内存中的jmp指令

jmp word ptr 内存单元地址(段内转移)
jmp dword ptr 内存单元地址(段间转移)
  • 段内转移:内存单元地址处存放的一个字是转移的目的偏移地址,可以用寻址方式(参见后文)的任一格式给出

  • 段间转移:从内存单元地址处开始存放的两个字,高地址是转移的目的段地址,低地址是转移的目的偏移地址

    • (CS)=(内存单元地址+2)

    • (IP)=(内存单元地址)

jcxz指令——有条件转移指令

有条件转移指令为短转移,在对应的机器码中包含转移的位移,而不是目的地址

jcxz 标号
;功能:
if((cx)==0) jmp short 标号

当(cx)=0时,转到标号处执行,(IP)=(IP)+8

8位位移=标号处地址-jcxz指令后的第一个字节的地址

loop指令——循环指令

循环指令均为短转移,在对应的机器码中包含转移的位移,而不是目的地址

loop 标号 ((cx)=(cx-1),如果(cx)≠0,转到标号处执行)
  1. (cx) = (cx)-1

  2. 如果(cx)≠0,(IP)=(IP)+8

8位位移=标号处地址loop指令后的第一个字节的地址

根据位移进行转移的意义

上面提到的几种汇编指令

jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 

IP的修改是根据目的地址转移起始地址之间的位移进行,这样方便了程序段在内存中的浮动转配,即在内存中的不同位置都可以执行

(如果机器码中包含标号的地址,而且标号处的指令不在目的地址处,程序就会出错)

CALL和RET指令

ret指令

ret指令修改IP中的内容,实现近转移,CPU执行ret指令时,进行

  1. (IP)=((ss)*16+(sp))

  2. (sp)=(sp)+2

相当于进行pop IP

retf指令

retf指令修改CSIP中的内容,实现远转移,CPU执行retf指令时,进行

  1. (IP)=((ss)*16+(sp))

  2. (sp)=(sp)+2

  3. (CS)=((ss)*16+(sp))

  4. (sp)=(sp)+2

相当于进行

pop IP
pop CS

callret是转移指令,都能修改IP或同时修改CSIP

call指令

  1. 将当前的IPCSIP压入栈中

  2. 转移

依据位移进行转移的call指令

call 标号;将当前IP压入栈中,转到标号处执行
  1. (sp)=(sp)-2

  2. ((ss)*16+(sp))=(IP)

  3. (IP)=(IP)+16

相当于进行

push IP
jmp near ptr 标号

16位位移=标号处的地址-call指令后的第一个字节的地址

转移的目的地址在指令中的call指令

call far ptr 标号

实现段间转移

  1. (sp)=(sp)-2

  2. ((ss)*16+(sp))=(CS)

  3. (sp)=(sp)-2

  4. ((ss)*16+(sp))=(IP)

(CS)=标号所在段的段地址;(IP)=标号在段中的偏移地址

相当于进行:

push CS
push IP
jmp far ptr 标号

转移地址在寄存器中的call指令

call reg(16)
  1. (sp)=(sp)-2

  2. ((ss)*16+(sp))=(IP)

  3. (IP)=(16位reg)

相当于进行:

push IP
jmp 16位reg

转移地址在内存中的call指令

call word ptr 内存单元地址

相当于

push IP
jmp word ptr 内存单元地址
call dword ptr 内存单元地址

相当于

push CS
push IP
jmp dword ptr 内存单元地址

mul指令

  • 两数相乘:都是8位,一个在AL中,另一个在reg或内存字节单元中;都是16位,一个在AX中,另一个在reg或内存单元中

  • 结果:8位乘法结果在AX中;16位乘法高位在DX中,低位在AX

《汇编语言》(第三版)王爽 著

链接:https://pan.baidu.com/s/1adqH6znzDA1pAqkK7xDt3w?pwd=1226
提取码:1226

posted on 2022-05-26 21:56  Euler0525  阅读(782)  评论(0编辑  收藏  举报