标签 8086CPU 下的文章

前言
上期我们学习了用[bx]定位内存的方法,今天让我们进一步学习一些定位内存的方法。

and和or指令
这是两个位运算指令,and表示逻辑与,or表示逻辑或,格式如下:

and/or 寄存器名,参数值

可以实现对寄存器内的数及参数值进行位运算,结果存在该寄存器中。

[bx+idata]
(idata泛指常数,由编程者决定)
可以表示值为(bx)+idata的偏移地址(即bx寄存器的值加上idata这一常数作为偏移地址)。

[bx+di/si]
di和si寄存器功能与bx相近,但是不能分割为两个八位寄存器。
[bx+di]、[bx+si]分别表示(bx)+(di)、(bx)+(si)作为偏移地址。

[bx+di/si+idata]
表示(bx)+(di/si)+idata)作为偏移地址。

简单应用一下
练个题~
编程,将datasg段中每个单词改成小写字母

assume cs:codesg,ds:datasg

datasg segment
  db 'Far             '
  db 'You             '
  db 'HJC             '
  db 'BBS             '
datasg ends

codesg segment
start:
codesg ends

end start

首先,我们还没有学习判断语句,所以要改变大小写只能用别的方式。观察一下大小写字母的ASCLL码,注意到大小写字母只在第五位有区别,只要将大写字母的第五位改成1,即可实现大写转小写,由此,可以选择使用逻辑或(即前面的or指令)。
由此,我们可以使用两层循环,在每层中修改字符串前三字节的第五位为1。
还有一个问题是两层循环的实现,我们只有cx一个寄存器,所以应该用另外一个寄存器在内层循环前暂存cx的值,就用dx吧~
代码:

assume cs:codesg,ds:datasg

datasg segment
  db 'Far             '
  db 'You             '
  db 'HJC             '
  db 'BBS             '
datasg ends

codesg segment

  start:mov ax,datasg
        mov ds,ax
        mov bx,0
        mov cx,4
        
     s0:mov dx,cx    ;暂存cx
        mov si,0
        mov cx,3
        
      s:mov al,[bx+si]
        or al,00100000b
        mov [bx+si],al
        inc si
        loop s       ;内层循环
        
        add bx,16
        mov cx,dx    ;恢复cx
        loop s0      ;外层循环
        
        mov ax,4c00H
        int 21H
codesg ends
end start

数据标号和assume指令
数据标号是一种特殊的标号。之前我们介绍的“标号:”格式的标号,只能表示一个内存地址。而数据标号则具有表示地址和数据的双重功能。其格式如下:

标号名 db/dw/dd 数据1,数据2,...

可以看到,该标号在定义数据时使用。下面是其使用格式:
首先要在使用前用伪指令assume说明使用数据标号的段:

assume ds:data

这样ds寄存器就与data段产生了关联。在调用data段中的数据时,如果使用数据标号替代,编译器就会将其识别为以ds为段地址、以数据标号的偏移地址为偏移地址的内存地址或该内存地址代表的数据(有点绕,自己断下句,下面看个例子)。下面假设data段里有如下声明:

data segment
  a dw 0,0
  b db 1,2,3,4
data ends

则a、b即为data:[0]和data:[4]的数据标号。之后在代码中将ds的值设为data的段地址:

mov ax,data
mov ds,ax

即可使用该标号。下面是在一些指令中使用数据标号的例子。当其代表内存单元时:

mov ax,a    ;代表mov word ptr ax,ds:[0]
add b,al    ;代表add ds:[4],al
inc b[2]    ;代表inc ds:[4+2]
dec a[1]    ;代表dec ds:[2]

作为一个地址时,在数据标号前加上offset即可。
需要注意的悬,数据标号同时代表了一个内存单元的长度。也就是说,当其作为数据进行操作时,要注意位数问题。以下代码会导致编译错误:

mov al,a
sub ax,b

原因是al、ax的位数和a、b的位数对不上。
这里需要知道,assume是伪指令,只用来提示编译器,用assume进行所谓“关联”也并不会将段地址存入指定的段寄存器里。这个需要自己注意~

结语
今天我们学习了更多定位内存地址的方式,有助于我们更方便的编程。我是faryou,下次见!

前言
在汇编中,我们有时会遇到需要循环的情况,这时就需要loop指令来简化代码,而[bx]和loop指令联用则能达到更方便的功能。

先补点寄存器知识
在8086CPU下,寄存器eax为位寄存器,ah、al分别为ax的高、低8位寄存器,即ax的前、后两半。bx、cx、dx同理~
需要注意的是,汇编中的mov、add等指令只能对同样长度的寄存器&空间使用。

简说[bx]
来看看下面的代码:

mov ax,[bx]

它的功能很简单,即将位于内存中ds:bx的数据送入ax寄存器。
而下面的代码:

mov [bx],ax

则是与上面相反,将ax寄存器中的数据送入内存中ds:bx的位置。
可以看到,[bx]的含义即将ds视为段地址、bx视为偏移地址所指向的内存中数据。

loop指令
有点高级语言编程基础的人一看到loop一定会知道是循环。的确,汇编中loop也是循环指令,但它和其他语言的循环有着不小的区别。
首先了解一下loop指令的格式:loop 标号。当程序执行到loop指令时,会进行如下操作:
(1)cx寄存器中的值自减1
(2)判断cx寄存器的值是否为0,为0则向下执行,不为0则转到标号处运行
由此可见,loop指令可以通过改变cx的值实现计数。
简单做个题目~
用循环计算2100

assume cs:code
code segment
    mov ax,1
    mov cx,100
p:  add ax,ax
    loop p
    mov ax,4c00h
    int 21h
code ends
end

联合使用[bx]和loop指令
直接做个题吧:
计算ffff:0~ffff:b单元中数据的和,结果存在dx中。
首先要明确:ffff:0~ffff:b单元中的数据为8位,而dx为16位寄存器,故不能直接累加。
解决办法:用16位寄存器ax作中介,先将一个单元的数据转到al(ax的后八位,见第一节),再用ax累加到dx上。
代码:

assume cs:code
code segment
    mov ax,0ffffh
    mov ds,ax
    mov bx,0
    mov dx,0
    mov cx,12
s:  mov al,[bx]
    mov ah,0
    add dx,ax
    inc bx
    loop s
    mov ax,4c00h
    int 21h
code ends
end

再来一个:
将ffff:0~ffff:b单元的数据复制到0:200~0:20b单元中
说明:由于CPU限制,mov指令不能直接把单元中的数据转移到另外一个单元,故要用一个寄存器中转。

assume cs:code
code segment
    mov ax,0ffffh
    mov ds,ax
    mox ax,0020h
    mov es ax
    mox bx,0
    mov cx,12
s:  mov dl,[bx]
    mov es:[bx],dl
    inc bx
    loop s
    mov ax,4c00h
    int 21h
code ends
end

结语
今天我们学习了在汇编中使用循环及[bx],需要实操以熟练。我是faryou,下次再见!

前言
我们在前面已经学习了编写有多个段的程序,但是目前学习的内容中没有可以用于在不同段之间转移的指令。下面我们学习如何在不同的段之间“跳一跳”~

前置知识:伪指令offset
offset是用于获得某标号处在内存中的偏移地址的伪指令,使用格式:

offset 标号

它可以帮助我们在编程时快速调用标号的地址(否则我们需要手动数某处代码在内存中的位置,那就太麻烦了)。

转移指令的原理
所有转移指令原理相同,都是通过修改CS:IP的值实现转移(关于CS:IP详见8086CPU工作原理)。

jmp无条件转移指令
jmp是无条件转移指令。所谓“无条件”是指跳转前不进行判断,直接转移。jmp有多种使用格式,下面分别进行说明:

jmp short 标号
jmp near ptr 标号

以上二指令用于跳转到标号处(需要注意的是,本用法对应的机器码中不通过直接指定地址来进行跳转,而是通过跳转目标与当前地址的偏移差进行跳转,偏移差在编译时就已计算完成,方便兼容)。其中jmp short用于进行8位位移(即支持位移到当前指令前后范围为-128~127),jmp near ptr用于进行16位位移(即支持位移到当前指令前后范围为-32768~32767)。本指令仅修改IP的值。

jmp far ptr 标号

上面的指令用于段间转移,会同时改变CS:IP的值。当目标位置与当前位置不在同段中可用本指令。
上述指令中的目标位置均在编程时指定,若转移目标的地址存放在寄存器中或内存中时,应使用下面的指令:

jmp 16位寄存器名

该指令可以转移到16位寄存器中存放的地址,仅修改IP的值。

jmp word ptr 内存单元地址
jmp dword ptr 内存单元地址

上面两条指令的目标地址存放在内存单元中,其中jmp word ptr用于段内转移,只修改IP的值;jmp dword ptr用于段间转移,会同时修改CS:IP的值,其中目标地址后的第一个字存放目标段地址,第二个字存放目标偏移地址。

jcxz指令
前面的jmp指令为无条件转移指令,接下来介绍的jcxz指令为有条件转移指令。格式:

jcxz 标号

jcxz为短转移指令,仅支持自身位置上下128位内的转移。jcxz会判断cx的值是否为0,若为0则转移,不为零则向下执行。

再谈loop
关于loop,我在之前的理解一下[bx]和loop指令中介绍过了,不再详细说明,这里再补充一下,loop也为短转移指令(同jcxz)。

转移指令在编译时的原理
我们已经知道,jmp short、jcxz等指令只支持8位位移。编译器在编译这些指令时会直接计算好偏移地址增加量,故而在debug中看不到真实地址名。同理,jmp near ptr、jmp word ptr只支持16位位移,编译器也会计算好偏移量。只有对于段间转移地址,编译器会将目标地址写入代码。
因此,当段间转移指令中的目标地址超范围时,编译器会直接报错。

总结
本文介绍了一些转移指令及其编译原理。我是faryou,再见!

前言
我自学汇编到现在,可以说有一段时间了。最近学会了8086CPU的工作原理,连同CS、IP两个特殊的寄存器一起分享给大家~~

CS、IP寄存器介绍
要理解CS、IP(段地址、偏移地址),可以通过我下面这个形象的比方:
假设A和B只有两张能容纳三个数字的纸条,而他们需要进行四位数的交流。正常情况下,三个数字的纸条无法容纳四位数,但此时因为有两张纸条,所以A可以预先和B商量好暗号,从而达到通信目的。比如说,A要告诉B8086这个四位数,那么A可以先在一张纸条上写上086,另一张纸条上写800。由于B与A有约定,所以B可以通过800(段地址)*10+086(偏移地址)得到8086(物理地址)这一数据。
CS、IP就是这样两张纸条。在8086CPU中,这一约定可表示为物理地址=段地址*16+偏移地址。

8086CPU工作原理
前面我们了解了CS、IP,下面我带领大家来学习一下8086CPU的工作原理——
(以下过程的前提:CS中的内容为2000H,IP中的内容为0000H)
首先,在CS、IP发生改变后,CPU将CS、IP送入地址加法器,地址加法器利用物理地址=段地址*16+偏移地址求出物理地址。完成计算后,得到的结果数(此处为20000H)送入输入输出控制电路。
输入输出控制电路将得到的数值送入地址总线。
内存在得到这个物理地址后,将从内存20000H单元开始存放的机器指令(此处假设为B8 23 01,转化为汇编指令即mov ax,0123H)通过数据总线送回输入输出控制电路。
输入输出控制电路将命令送入指令缓冲器。此时指令缓冲器会对IP的值进行改变(IP中的数=IP中的原数+指令字节数,例如此处命令B8 23 01长3个字节,因此IP加上3改为0003H)。
IP完成变更后,执行控制器开始执行命令(此处命令B2 23 01将寄存器ax中的值改为0123H)。
完成以上操作后,CPU开始根据现有的CS、IP开始执行下一条指令。
由此可见,CS、IP指示CPU工作的地址,因此,我们需要熟练运用这一点。
最后补一个小知识:在8086CPU刚开始工作时,CS:IP=FFFFH:0000H,FFFF0H是CPU开机后执行的第一条指令。

结语
本文一步步讲解了8086CPU的基本工作过程,真正的将这一原理投入应用,才是学习它的最终目的。我是faryou,再见!