Kaito's Blog

致力成为一枚silver bullet.

0%

汇编总结(1):基础、存储器、寄存器、汇编程序

以下所述的汇编指令均以8086CPU为基础所讲述。

1、基础

1.1、机器语言

如果要介绍汇编语言,首先要说一下机器语言,机器语言的定义如下:

机器语言是机器指令的集合,机器指令就是一台机器可以正确执行的命令,计算机的机器指令是一列二进制数字,计算机将之转变为一列高低电平,以驱动计算机硬件进行运算。

每一种CPU都有自己的指令集,早期程序员通过用0、1数字编写的程序打在纸带或卡片上,然后输入计算机完成运算。

1.2、汇编语言

正是由于机器语言的难以辨别和记忆,于是就产生了汇编语言。

程序员通过汇编代码编写程序,通过汇编编译器,最终还是转化为机器语言,最终让计算机执行。

编写汇编代码 –> 汇编编译器(编译) –> 机器码 –> 计算机(执行)

汇编有3类指令:

  • 汇编指令:机器码助记符,是汇编语言的核心,有对应的机器码
  • 伪指令:由编译器执行,没有对应机器码
  • 其他符号:如+、-、*、/,编译器识别,没有对应机器码

1.3、存储器与总线

CPU要想执行指令,必须由存储器(内存)提供指令和数据。

指令和数据在内存或磁盘上没有任何区别,都是二进制信息。

内存的一个存储单元可以存储1个字节,即8个二进制位。

CPU要想进行数据的读写,必须进行下面3类信息交互:

  • 存储单元的地址(地址信息)
  • 读/写的命令(控制信息)
  • 读/写的数据(数据信息)

CPU与存储器分别通过地址总线、控制总线、数据总线进行信息交互。

  • 地址总线:地址总线宽度决定CPU寻址能力
  • 控制总线:控制总线决定CPU对其他器件的控制能力
  • 数据总线:数据总线宽度觉得CPU与其他器件数据传送一次数据传送量

2、存储器

2.1、内存地址空间

假如一个CPU地址总线宽度为10,那么可寻址1024个内存单元,这1024个内存单元就是这个CPU的内存地址空间

2.2、控制外设

在计算机系统中,所有可用程序控制其工作的设备,都必须受到CPU的控制。

但是CPU不能直接控制外设,直接控制这些设备的是插在扩展插槽的接口卡。

这些扩展插槽通过总线与CPU相连,即接口卡通过总线连接CPU,CPU控制接口卡,从而实现CPU对外设的间接控制。

CPU –> 接口卡 –> 外设

2.3、RAM和ROM

存储器分为随机存储器(RAM)和只读存储器(ROM),使用如下:

  • RAM:存放CPU使用的程序和数据
  • 装有BIOS的ROM
  • 接口卡上的RAM

不管是上述的哪种类型,CPU控制它们的时候,都是当做内存对待,即当做由若干存储单元组成的逻辑存储器,这个逻辑存储器就是内存地址空间

3、寄存器

寄存器是CPU中程序员可以读写的部件,通过改变寄存器的内容来实现对CPU的控制。

8086CPU有14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW

3.1、通用寄存器

其中AX、BX、CX、DX是通用寄存器,存放一般性数据。1个16位寄存器可以存储16位的数据。

为了向上一代CPU兼容,这4个寄存器可以分为AH、AL,BH、BL,CH、CL,DH、DL

3.2、物理地址

CPU要想访问内存单元,就要给出内存单元的物理地址,内存是一个一维的线性空间,每一个内存单元都有唯一的地址,这个地址就是物理地址

8086CPU是16位结构的,其数据总线是16位宽,但地址总线是20位,1MB寻址能力。

但内部是16位结构,内部一次性处理、传输地址为16位,所以在内部采用2个16位地址合成方式形成一个20位的物理地址进行内存寻址。

物理地址 = 段地址 * 16 + 偏移地址(段地址*16即左移4位)

其本质含义是:CPU在访问内存时,用一个基础地址(段地址*16)和相对于基础地址的偏移地址相加,得到物理地址。

由于偏移地址为16位,16位地址的寻址能力是64KB,所以一个段的长度最大为64K。

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

3.3、段寄存器

8086CPU有4个段寄存器:CS、DS、SS、ES

CS为代码段寄存器,IP为指令指针寄存器。

任意时刻,CPU将CS:IP指向的内容当做当前指令执行。

8086CPU工作过程:

  • 从CS:IP指向的内存单元读指令,指令进指令缓冲器
  • IP指向下一条指令
  • 执行指令,跳到步骤1,重复这个过程

CPU根据什么将内存中的信息看做指令?答案是CPU将CS:IP指向的内存单元的内容看做指令。

使用jmp 段地址:偏移地址jmp 寄存器修改CS:IP或IP的地址。

在编程时,根据需要可以将长度为N的一组代码,存在一组连续的内存单元中,这段内存单元叫做代码段

同样的,可以根据需要将长度为N的一组内存单元,专门存储数据,这段内存单元叫做数据段

8086CPU自动取DS寄存器中的数据为内存单元的段地址。

例如mov [0] cs表示将寄存器cs的值复制到段地址为DS便宜地址为0的内存单元中。

字在内存中存储时,用2个地址连续的内存单元存放,字的低字节放在低地址单元,高字节放在高地址单元。

3.4、栈

栈是一种特殊的访问方式的存储空间,特点是:后进数据先出去。

8086CPU提供入栈和出栈的指令:PUSHPOP

任意时刻,寄存器SS:SP指向栈顶元素。

push指令执行:

  • SP=SP-2
  • 向SS:SP指向的字单元送入数据

pop指令执行:

  • 从SS:SP指向的字单元读数据
  • SP=SP+2

在编程时,要时刻注意栈顶越界的问题。

用栈来可以暂存以后需要恢复的寄存器中的内容。

push和pop实质上是一种内存传送指令。

同样的,编程时我们可以将长度为N的一组连续内存单元当做栈空间使用,叫做栈段

一个栈段的容量最大为64KB。

3.5、标志寄存器

标志寄存器作用:

  • 存储相关指令某些执行结果
  • 为CPU执行相关指令提供行为依据
  • 控制CPU相关工作方式

8086CPU中有个叫flag寄存器,共16位,每一位代表专门的含义:

  • 第6位ZF零标志位:相关指令执行后,结果为0则zf=1,不为0则zf=0
  • 第2位PF奇偶标志位:相关指令执行后,结果所有bit位中1的个数为偶数,pf=1,否则pf=0
  • 第7位SF符号标志位:相关指令执行后,结果是否为负,结果为负sf=1,否则sf=0
  • 第0位CF进位标志位:对于无符号数运算,CF记录运算结果最高有效位向更高位的进位值/借位值
  • 第11位OF溢出标志位:对于有符号数运算,是否发生溢出,溢出则of=1,否则of=0
  • 第10位DF方向标志位:在串处理指令中,控制操作后si、di增减,df=0每次操作后si、di递增,否则递减

4、汇编程序

4.1、基础

汇编程序包括:

  • 伪指令:编译器执行,没有对应机器码
  • 汇编指令:编译器编译成机器码

伪指令包括:

  • XXX segment … XXX ends:定义一个段
  • end:结束标记
  • assume:关联某一寄存器和一个段

一个汇编程序中,所有将被计算机所处理的信息:指令、数据、栈,被划分到不同的段中。

汇编程序的最后一定要有返回:

1
2
mov ax,4c00H
int 21H

汇编程序从写出到执行的过程:

1
2
3
编程 --> 1.asm --> 编译 --> 1.obj --> 连接 --> 1.exe --> 加载 -->内存中的程序 --> 运行

edit masm link command CPU

“()”中的元素可以有3种:

  • 寄存器名
  • 段寄存器名
  • 内存单元物理地址

4.2、寻址方式

寻址方式分以下几种:

寻址方式 含义 名称 举例
[idata] EA=idata;SA=(ds) 直接寻址 [idata]
[bx] EA=(bx);SA=(ds) 寄存器间接寻址 [bx]
[si] EA=(si);SA=(ds) 寄存器间接寻址 [si]
[di] EA=(di);SA=(ds) 寄存器间接寻址 [di]
[bp] EA=(bp);SA=(ss) 寄存器间接寻址 [bp]
[bx+idata] EA=(bx)+idata;SA=(ds) 寄存器相对寻址 结构体:[bx].idata; 数组:idata[si],idata[di]; 二维数组:[bx][idata]
[si+idata] EA=(si)+idata;SA=(ds) 寄存器相对寻址 同上
[di+idata] EA=(di)+idata;SA=(ds) 寄存器相对寻址 同上
[bp+idata] EA=(bp)+idata;SA=(ss) 寄存器相对寻址 同上
[bx+si] EA=(bx)+(si);SA=(ds) 基地变址寻址 二维数组:[bx][si]
[bx+di] EA=(bx)+(di);SA=(ds) 基地变址寻址 同上
[bp+si] EA=(bp)+(si);SA=(ss) 基地变址寻址 同上
[bp+di] EA=(bp)+(di);SA=(ss) 基地变址寻址 同上
[bx+si+idata] EA=(bx)+(si)+idata;SA=(ds) 相对基址变址寻址 结构中的数组:[bx].idata[si]; 二维数组:idata[bx][si]
[bx+di+idata] EA=(bx)+(di)+idata;SA=(ds) 相对基址变址寻址 同上
[bp+si+idata] EA=(bp)+(si)+idata;SA=(ss) 相对基址变址寻址 同上
[bp+di+idata] EA=(bp)+(di)+idata;SA=(ss) 相对基址变址寻址 同上

在需要暂存数据时,一般都使用栈。

4.3、循环

loop指令执行步骤:

  • (cx)=(cx)-1
  • cs不为0跳转至标号处执行,为0向下执行

cx中存放循环次数

1
2
3
4
5
    mov cx,循环次数

s:
循环执行的程序段
loop s

在编写汇编程序时,在DOS方式下,要使用0:200~0:2ff内存空间,这段空间是空闲的。

4.4、定义数据

mov ax,[bx]默认段地址在ds中,如果不想放在ds中,可以使用段前缀例如ds:cs:ss:es:来显示指定段地址所在的段寄存器。

  • dw定义字型数据,如dw 0123h
  • dd定义双字型数据,如dd 1
  • db定义字符数据,如db 'unix'
  • dup表示数据的重复,如dd 3 dup (0)相当于db 0,0,0

寄存器disi功能同bx,这2个寄存器不能分成2个8位寄存器使用,例如以下方式实现相同功能:

1
2
3
4
5
6
7
8
mov bx,0
mov ax,[bx+123]

mov si,0
mov ax,[si+123]

mov di,0
mov ax,[di+123]

可以在程序中定义多个段,用于定义数据和栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;定义数据段
data ends
stack segment ;定义栈段
dw 0,0,0
stack segment
code segment ;定义代码段
start: mov ax,stack ;栈数据放入ax
mov ss,ax
mov sp,20h
mov ax,data ;数据段放入ax
mov ds,ax
mov bx,0
mov cx,8
s: push [bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0: pop [bx]
add bx,2
loop s0

mov ax,4c00h
int 21h
code ends
end start ;程序从start处开始

4.5、数据存放位置和大小

2个问题:

  • 机器指令处理的数据在什么地方?
    • 指令执行的前一刻
    • 数据只会存在3个地方:CPU内部、内存、端口
  • 要处理的数据有多长?
    • 可以用 word ptrbyte ptr表示字和字节型数据

div指令表示除法:

  • 除数:分8位和16位两种
  • 被除数:默认在ax或dx和ax中
    • 除数是8位,被除数为16位,默认在ax中
    • 除数是16位,被除数为32位,则在dx和ax中,dx放高16位,ax放低16位
  • 结果:
    • 除数是8位,商放在al中,余数放在ah中
    • 除数是16位,商放在ax中,余数放在dx中

4.6、转移指令

修改IP或修改CS、IP的指令统称为转移指令。

根据范围划分:

  • IP修改范围-128~127:短转移
  • IP修改范围-32768~32767:近转移

转移指令分类:

  • 无条件转移指令(jmp)
  • 条件转移指令
  • 循环指令(loop)
  • 过程
  • 中断

操作符offset表示去的标号的便宜地址,由编译器处理。

jmp指令转移分为以下几类:

  • jmp short 标号(转到标号处执行指令)(段内短转移):(IP)=(IP)+8位位移
  • jmp near ptr 标号(段内近转移):(IP)=(IP)+16位位移
  • jmp far pt 标号(段间转移):(CS)=标号所在段地址 (IP)=标号所在段中的偏移地址
  • jmp 16位寄存器:(IP)=(16位寄存器)
  • jmp word ptr 内存单元地址(段内转移):内存单元地址开始存放一个字作为转移的偏移地址
  • jmp dword ptr 内存单元地址(段间转移):内存单元地址开始存放两个字,高地址处的字为段地址,低地址为偏移地址

jcxz指令:

  • 条件转移指令,短转移
  • jcxz 标号 = if((cx)==0) jmp short 标号

loop指令:

  • 循环指令,短转移
  • loop 标号 = (cx)--; if((cx)!=0) jmp short 标号

编译器会对转移位移进行检测,如果转移范围超出界限,则编译时报错。

ret和retf指令:

  • ret指令用栈中的数据,修改IP,实现近转移
    • (IP)=((ss)*16+(sp))
    • (sp)=(sp)+2
    • 相当于pop IP
  • retf指令用栈中的数据,修改CS和IP,实现远转移
    • (IP)=((ss)*16+(sp))
    • (sp)=(sp)+2
    • (CS)=((ss)*16+(sp))
    • (sp)=(sp)+2
    • 相当于pop IP pop CS

call指令:

  • 将当前IP或CS和IP压入栈
  • 转移
  • 执行call 标号=push IP jmp near ptr 标号
  • 执行call far ptr 标号=push CS push IP jmp far ptr 标号
  • 执行call 16位寄存器=push IP jmp 16位寄存器
  • 执行call word ptr 内存单元地址=push IP jmp word ptr 内存单元地址
  • 执行call dword ptr 内存单元地址=push CS push IP jmp dword ptr 内存单元地址

call和ret命令配合使用,完成子程序的调用,支持了汇编的模块化设计。

mul指令:

  • 2数相乘,要么都是8位,要么都是16位。
    • 如果是8位,一个默认在AL中,另一个在8位寄存器或内存字节单元
    • 如果是16位,一个默认在AX中,另一个在16为寄存器或内存字单元
  • 结果:
    • 如果是8位乘法,结果默认在AX
    • 如果是16为乘法,高位默认在DX中,低位在AX
  • 格式
    • mul 寄存器
    • mul 内存单元:
      • mul byte ptr ds:[0]
      • mul word ptr [bx+si+8]

调用子程序,参数传递:

  • 用寄存器来存储参数和结果
  • 用栈传递参数

在子程序开始调用之前,将子程序所用到的寄存器内容都保存到栈中,子程序返回前再恢复。

adc指令:

  • 进位加法指令,利用CF位上记录的进位值
  • adc 操作对象1,操作对象2=操作对象1=操作对象1+操作对象2+CF
  • 例如adc ax,bx相当于(ax)=(ax)+(bx)+CF

CPU提供adc指令的目的,就是用来进行加法的第二步运算的。adcadd配合就可以对更大的数据进行加法运算。

sbb指令:

  • 带借位减法指令,利用CF位上记录的借位值
  • sbb 操作对象1,操作对象2=操作对象1=操作对象1-操作对象2-CF
  • 例如sbb ax,bx相当于(ax)=(ax)-(bx)-CF

同理,CPU提供sbb指令的目的,就是用来进行减法借位运算的。

cmp指令:

  • 比较指令,相当于减法指令,只是不保存结果
  • cmp 操作对象1,操作对象2=操作对象1-操作对象2,但不保存结果,只改变标识寄存器
  • 例如cmp ax, ax相当于(ax)-(ax),则zf=1,pf=1,sf=0,cf=0,of=0

cmp指令在比较无符号数和有符号数的比较时,结果可能不同。

无符号数比较结果条件转移指令:

指令 含义 检测标志位
je 等于则转移 zf=1
jne 不等于则转移 zf=0
jb 低于则转移 cf=1
jnb 不低于则转移 cf=0
ja 高于则转移 cf=0且zf=0
jna 不高于则转移 cf=1或zf=1

8086CPU提供2个指令对df位进行设置:

  • cld指令:将df置0
  • std指令:将df置1

pushf和popf指令:

  • pushf:将标志寄存器的值压栈
  • popf:从栈中弹出数据,送入标志寄存器
  • 作用:为直接访问标识寄存器提供了方法
如果此文章能给您带来小小的工作效率提升,不妨小额赞助我一下,以鼓励我写出更好的文章!