通过本节我们将了解:
- 内存分段方式与访问
- movsb、movsw、ins、dec、cld、std、div、neg、cbw、cwd、sub、idiv、jcxz、cmp等
- INTEL8086标志寄存器
段地址的初始化
在X86架构(三)汇编指令与显卡控制中有这么一条语句可能没讲清楚
mov [0x7c00+number+0x00], dl
该段程序是在编译后存储在主引导扇区的,所以默认的段:偏移地址为0x0000:0x0000所以段偏移是以0x0000计算的,如下图所示。第一条指令的偏移地址为0x0000,对应图中最左列。当这段程序被加载到0x0000:0x7c00开始执行后,因指令是由地址低到高顺序执行的,所以指令的偏移地址并不影响指令的执行,但是当我们要访问定义在后方的数据时,假设数据的偏移地址为0x012e(在硬盘中),当加载到0x0000:0x7c00后,数据的地址就变成了 0x012e+0x7c00了,这就是为什么要用0x7c00+number
来定位数据地址的原因。这下应该讲清楚了,可以查看上一节进行确认哦!!!
换句话说,编译后的汇编语言源程序将从某个内存段中,偏移地址为0 的地方开始加载。如果有一个标号label_a
,它在编译时计算的汇编地址是0x05,当程序被加载到内存后,它在段内的偏移地址仍然是0x05,任何使用这个标号来访问内存的指令都不会产生问题。但是,如果程序加载时,不是从段内偏移地址为0 的地方开始的,而是0x7c00,label_a
的实际偏移地址就是0x7c05。这时,所有访问label_a
的指令仍然会访问偏移地址0x05,这样就会出问题,所以需要加上0x7c00。
逻辑地址0x0000:0x7c00
对应的物理地址是0x07c00
,该地址又是段0x07c0
的起始地址。所以,我们可以把这512字节的区域看成一个单独的段,段的基地址是0x07c0
,段长512 字节。这也就是前面说的一个物理地址可以对应多个逻辑地址。
在0x07c0:0x0000
逻辑地址的划分下,前面的那条指令可以简化成
mov [number], dl
8086标志寄存器
8086处理器的标志寄存器如下图所示
其中:
- OF —— 溢出标志位
OF 标志用于指示两个有符号数的运算结果是否错误
mov ah,0x70
add ah, ah
;从无符号数的角度来看0x70+0x70 < 0xFF, 结果正确
;从有符号数的角度来看0x70+0x70,理论上的计算结果超出了寄存器
;AH所能容纳的有符号数的范围-128~127,所以破坏了符号位,故OF=1
- DF —— 方向标志位
该标志位用于改变处理器的运行状态。通过将这一位清零或者置1,可控制movsb
和movsw
的传送方向。
mosb/movsw的说明参见后文 - IF —— 中断允许标志位
用于控制是否响应外部中断请求,置1时响应,反正不响应 - TF —— 跟踪标志位
为调试程序位而设定的陷阱控制位,当该位置1时,CPU处于单步执行状态,每执行一条指令产生一次内部中断,当该位清零后,CPU恢复正常工作。 - SF —— 符号标志位
处理器执行的很多算术逻辑运算都会影响到该位,如dec
指令,如果计算结果的最高位是0,处理器把SF位置0,否则SF位置1。 - ZF —— 零标志位
当处理器执行一条算术或者逻辑运算指令后,算术逻辑部件送出的结果除了送到目的操作数指定的位置外,还送到一个或非门。或非门的输入全为0 时,输出为1;输入不全为0,输出为0。或非门的输出送到标志寄存器的ZF位。故计算结果为0,ZF位被置成1,表示计算结果为零为TRUE
- AF —— 辅助进位标志位
当执行加法或减法运算,使结果的低4位向高4位有进位或借位时,AF=1;否则AF=0 - PF —— 奇偶标志位
算术逻辑运算的结果的低8位中如果偶数个为1的比特,则PF=1;否则PF=0 - CF —— 进位标志位
当处理器进行算术操作时,如果最高位有向前进位或借位的情况发生,则CF=1;否则CF=0。
mov al 0x80
add al, al ; 执行后产生进位,CF = 1
NOTE 这些标志位常结合汇编指令一起使用,如下图所示
8086通用寄存器的特殊用法
- BX 基址寄存器(Base Address Regiser)
mov [bx], dl ;将dl中的内容复制到以[bx]的内容为偏移地址的内存中
; 此种用法`仅适用于bx寄存器`!!!
- AX 累加器(Accumulator)
- CX 计数器(Counter)
- DX 数据寄存器(Data)
- SI 源索引寄存器(Source Index)
- DI 目标索引寄存器(Destination Index)
SI和DI也叫变址寄存器
故8086支持基址+变址寄存器寻址
;INTEL8086 处理器只允许以下几种基址寄存器和变址寄存器的组合
[bx+si]
[bx+di]
[bp+si]
[bp+di]
汇编指令
- movsb
- movsw
数据搬运指令
用于把数据从内存中的一个地方批量地传送(复制)到另一个地方,movsb以字节为单位,movsw以字为单位。原始数据串的段地址由DS指定,偏移地址由SI指定,简写为DS:SI;要传送到的目的地址由ES:DI指定;传送的字节数或者字数由CX指定。传送分为正向传送和反向传送:
正向传送是指传送操作的方向是从内存区域的低地址端到高地址端;反向传送则正好相反。正向传送时,每传送一个字节或者一个字,SI
和DI
加1 或者加2;反向传送时,每传送一个字节或者一个字时,SI
和DI
减去1或者减去2。不管是正向传送还是反向传送,也不管每次传送的是字节还是字,每传送一次,CX
的内容自动减一。
TIP 不妨理解为数组正向遍历与反向遍历
NOTE DS为数据段寄存器;ES为附加段寄存器;CX、SI、DI为通用寄存器
cld ;将`DF`标志清零,正向传送
mov ds, 0x8000
mov si, 0x0000 ;源地址 0x8000:0x0000
mov es, 0x4000
mov di, 0x0000 ;目的地址 0x4000:0x0000
mov cx, 2
movsw ;将字数据从0x8000:0x0000移动到0x4000:0x0000
- cld
方向标志清零指令
cld
指令用于将DF
标志清零,以指示movsb/movsw传送是正向的 - std
方向标志置位指令
std
指令用于将DF
标志置1,以指示movsb/movsw传送是反向的 - rep
指令反复执行前缀,与ZF
标志位相关,当ZF
标志位为零(计算结果不为0)时,重复执行莫条指令
rep movsw ;当CX不为零重复执行movsw
;因为 movsw没执行一次,CX的内容自动减一,而CX减一后的结果影响ZF标志位
- loop
循环指令
用于重复执行一段相同的代码
loop digit ;转移到digit标号执行
与rep
相同,每执行一次loop指令,CX
的内容减1;当CX
为0(ZF
为1)时,不转移到指定位置执行,继续执行后面的指令。
- inc
加一指令
操作数可以是8位或者16位的寄存器,也可以是字节或者字内存单元
inc al
inc byte [bx]
inc word [labe_a]
- dec
减一指令
操作数可以是8位或者16位的寄存器,也可以是字节或者字内存单元
dec al
dec byte [bx]
dec word [labe_a]
- jns
条件转移指令
当标志寄存器的SF位不为1时跳转,jns是不是 jmp not signed(SF != 1)呢?可以是吧 - NASM编译器的
$
和$$
标记
$
标记等同于标号,是一个隐藏在当前行行首的标号。因此,jmp near $
的意思是,转移到当前指令继续执行.可以理解为while(1)
$$
标记,代表当前汇编节(段)的起始汇编地址 - jz与jnz
转移指令
jz
-ZF = 0
转移
jnz
-ZF != 0
转移
__NOTE__关于ZF
(零标志位)的说明请参见前文 - jo与jno
转移指令
jo
-OF = 0
转移
jno
-OF != 0
转移
__NOTE__关于OF
(溢出标志位)的说明请参见前文 - jc与jnc
转移指令
jc
-CF = 0
转移
jnc
-CF != 0
转移
__NOTE__关于CF
(进位标志位)的说明请参见前文 - jp与jnp
转移指令
jp
-PF = 0
转移
jnp
-PF != 0
转移
__NOTE__关于PF
(奇偶标志位)的说明请参见前文
Attention
转移指令必须出现在影响标志的指令之后,如
dec si ;dec指令影响SF寄存器
jns show
- cmp
比较指令
cmp dst, src
dst - 8位或者16位通用寄存器,8位或者16位内存单元
src - 与目的操作数宽度一致的通用寄存器、内存单元或者立即数
cmp al, 0x08
cmp dx, bx
NOTE cmp指令仅仅根据计算的结果设置相应的标志位,而不保留计算结果
cmp指令将会影响到CF、OF、SF、ZF、AF 和PF 标志位
目的操作数是被测量的对象,源操作数是测量的基准,各种比较结果后通常跟随相应的条件转移指令,如下图所示。
-jcxz
条件转移指令(jmp if cx is zero)
当CX 寄存器的内容为零时则转移
jcxz show ;如果CX寄存器的内容为零,则转移;否则不转移,继续往下执行。
代码示例
NOTE 代码取自《X86汇编语言:从实模式到保护模式》
该程序被存储在硬盘的主引导扇区(偏移地址以0x0000计算),被加载到0x07c0:0x0000
开始执行
;代码清单6-1
;文件名:c06_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:2011-4-12 22:12
jmp near start
mytext db 'L',0x07,'a',0x07,'b',0x07,'e',0x07,'l',0x07,' ',0x07,'o',0x07,\
'f',0x07,'f',0x07,'s',0x07,'e',0x07,'t',0x07,':',0x07
number db 0,0,0,0,0
start:
mov ax,0x07c0 ;设置数据段基地址
mov ds,ax ;ds寄存器一般保存数据段基地址
mov ax,0xb800 ;设置附加段基地址
mov es,ax ;这里附加段指向显存位置,存放在es寄存器中
cld ;将方向标志位DF清零,以指示传送是负方向的
;movsw指令原始数据串需要存放在ds:si位置
;mytext位于ds指向的基地址中,因此只要把偏移mytext存入si寄存器即可
;目的地址为es:di
mov si,mytext
mov di,0 ;当前es指示显存起始位置,因此只要把偏移0存入di即可
;计算movsw执行的次数
mov cx,(number-mytext)/2 ;实际上等于13
rep movsw ;一次传送一个字(两个字节)
;此时Label offset被移到显存
;得到标号所代表的偏移地址
mov ax,number ;此代码目的旨在显示number的偏移地址
;计算各个数位
mov bx,ax ;bx指向当前number偏移地址
mov cx,5 ;循环次数
mov si,10 ;除数
digit:
xor dx,dx ;dx(被除数高16位)清零 被除数dx:ax
div si ;除法
mov [bx],dl ;保存数位,将数据移动到bx内存指向的偏移的内存中
inc bx ;bx自加1,指向下一个内存单元
loop digit ;cx不等于0,继续执行digit起始地址的代码
;显示各个数位
mov bx,number
mov si,4 ; si = 4,但是我们实际显示的位数是5位,所以jns就解释了啊
show:
mov al,[bx+si] ;从后往前显示
add al,0x30 ;转为ASCII码
mov ah,0x04 ;显示属性
mov [es:di],ax ;es指向0xb800; di在前面movsw时已自增
add di,2
dec si
jns show ;上一条指令符号位为SF=0(结果为非负)时跳转
mov word [es:di],0x0744
jmp near $ ;无限循环
;$当前指令偏移,$$当前代码段起始位置
;512减去最后填充的0x55和0xaa,剩余510个字节内存长度
;再减去($-$$)计算得到的所用内存长度,就是要填充的长度
times 510-($-$$) db 0
db 0x55,0xaa
要细心领悟哦,指令与指令、指令与标志位的关系哦,收藏起来有需要的时候来看哦,bro…