(一)、8086汇编学习之基础知识、通用寄存器、CS/IP寄存器与Debug的使用
(二)、8086汇编学习之DS寄存器、SS/SP寄存器
(三)、8086汇编学习之[BX],CX寄存器与loop指令,ES寄存器等
(四)、8086汇编学习之代码段、数据段、栈段与段地址寄存器
(五)、8086汇编学习之寻址方式、数据类型以及几个数据操作指令
一、offset伪指令:
offset 标号:可以获取标号所在位置指令的偏移地址(即IP值)。
比如下面这段程序:
;将第一条指令复制到start_two的两个字节的位置
assume cs:codeseg
codeseg segment
start:
mov ax,bx
mov si,offset start ;获取入口地址IP偏移值
mov di,offset start_two ;获取目标地址IP偏移值
mov dx,cs:[si] ;将第一条指令放到中间寄存器中
mov cs:[di],dx ;从中间寄存器完成到目标地址的复制
start_two:
;nop指令(No Operation)空操作,只占用一个字节不进行任何操作,并使IP加1。
;在Intelx86系列CPU中,其机器指令为0X90,所以可以看到起所在字节内存值均为90
;nop指令一般用来进行指令内存对齐的操作
nop
nop
mov ax,4C00H
int 21H
codeseg ends
end start
测试结果:
二、jmp指令的几种格式:
1、描述:
jmp指令的本质是修改CS:IP,CS:IP修改以后自然会跳转到指定位置执行指令
;jmp的几种写法与作用:
jmp short 标号 ;转移到编号处执行指令(段内短转移),(IP)=(IP)+8位位移,该式表明新IP是旧IP的偏移,而与段地址无关
jmp near 标号 ;转移到编号处执行指令(段内近转移),(IP)=(IP)+16位位移
jmp far 标号 ;转移到编号处执行指令(段间转移),(CS):(IP)=标号处的CS:IP
;(Debug中查看jmp对应机器指令的地址部分是完整的CS:IP地址,eg:EA0B01BD0B==>cs:IP=0BBD:010B)
jmp 16位reg ;修改IP
jmp word ptr 内存单元地址 ;(段内转移)修改IP
jmp dword ptr 内存单元地址 ;(段间转移)修改CS:IP(高两位为CS,低两位为IP)
“jmp X 标号”看似是跳转,其本质还是修改CS:IP
jmp near 标号 ;修改IP的为标号标识地址的偏移部分
jmp far 标号 ;修改CS为标号标识地址的段地址部分,修改IP的为标号标识地址的偏移部分
jmp ax ;修改IP为ax的值
jmp word ptr [0] ;修改IP为ds:[0]为地址的一个字的内容
jmp dword ptr [6] ;修改IP为ds:[6]为地址的一个字的内容,修改CS为ds:[8]为地址的一个字的内容
小结:所谓段间段内的区别在于:段间修改IP,而段内修改CS:IP
注意:
jmp near与jmp far可以在debug中运行,但是jmp word ptr [bx]与jmp dword ptr [bx]就不行。因为word与dword还有ptr对于debug调试器来说不认识,(Debug只认识指令与立即数不认识伪指令)
我们说jmp的本质是修改CS:IP,但是修改IP的方式不一定是给出指令的地址,而是给出跳转地址相对于当前跳转指令的下一个指令首地址的偏移值。比如:
addr1 mov ax,1
addr2 jmp s
addr3 mov ax,2
addr4 s:
addr5 mov ax,3
机器指令并不是EB “addr5”,而是EB “addr5-addr3”,并且是以差值的补码形式存储。关于补码,可以参考:计算机的编码问题与数据上溢
2、简单应用:
如何使得程序执行jmp指令后,CS:IP重新指向第一条指令。如下:
;在数据段给定入合适的值,使得jmp指令执行后,CS:IP指向第一条指令
assume cs:codeseg
data segment
db 16 dup (0)
data ends
codeseg segment
start:
mov ax,data
mov ds,ax
mov bx,0
jmp word ptr [bx+1] ;读取ds:[1]与ds:[2]放到IP中,那么IP就变为0,从第一条指令重新开始
codeseg ends
end start
另外一种方式:
;在空出的位置填入合适的内容,使得jmp指令执行以后CS:IP指向第一条指令:mov ax,data
assume cs:codeseg
data segment
dd 12345678H
data ends
codeseg segment
start:
mov ax,data
mov ds,ax
mov bx,0
mov [bx],__ ;IP填0:mov [bx],bx
mov [bx+2],__ ;CS填cs即可:mov [bx+2],cs
mov [bx+2],cs
jmp dword ptr ds:[0]
codeseg ends
end start
三、jcxz与loop的原理:
1、jcxz指令:
jcxz指令:为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中,包含转移的位置,而不是目的地址。对IP的修改范围为-128~127(用补码表示)。
格式:“jcxz 标号”
如果(cx)=0,就跳到标号处执行,否则继续向下执行。
操作:
①当(cx)=0时,(IP)=(IP)+8位位移
②8位位移=标号处地址-jcxz指令后第一个字节地址。
③编译器在编译时便算出来偏移地址大小,用补码表示。
举例:
寻找指定数据段开始的第一个内容为0的字节,将其偏移地址放到dx寄存器中。
;利用jcxz指令实现在内存2000H段中查找第一个值为0的字节找到后将他的偏移地址存放在dx中。
assume cs:codeseg
codeseg segment
start:
mov ax,2000H
mov ds,ax
mov bx,0
;寻找单字节值为0的地址
again:
mov cx,0
mov cl,ds:[bx]
jcxz ok ;当找到的值存到cx中,找到0,存入CX的也为0,那么会自动跳到ok处
inc bx
jmp short again
;找到后跳到该处将地址放到DX中
ok:
mov dx,bx
mov ax,4C00H
int 21H
codeseg ends
end start
测试结果:
2、loop指令:
loop则与jcxz是相反的道理,当cx不等于0时jmp执行,等于零时越过loop指令顺序执行。编译器在编译时,遇到转移位移越界的问题会报编译错误。
利用loop实现与上面程序相同的功能:
;利用loop指令实现在内存2000H段中查找第一个值为0的字节找到后将他的偏移地址存放在dx中。
assume cs:codeseg
codeseg segment
start:
mov ax,2000H
mov ds,ax
mov bx,0
;寻找单字节值为0的地址
again:
mov cx,0
mov cl,ds:[bx]
inc cx ;当cx为0时,自加一次为1,在loop时自减一次为0就结束循环
inc bx
loop again
;找到后跳到该处将地址放到DX中
ok:
dec bx;与inc指令作用相反,自减:(bx)=(bx)-1
mov dx,bx
mov ax,4C00H
int 21H
codeseg ends
end start
**再强调一遍:**jmp指令修改的是CS:IP寄存器,并且在段内转移时IP是相对值(相对jmp之后第一个字节的值),而不是相对CS:0的值。在段间转移时则是直接给定绝对地址CS:IP。jcxz与loop都属于段内转移(且都是段内短转移,修改范围为-128~127),所以其机器指令中的地址值,都是跳转目的地址相对jcxz/loop指令后的第一个字节的字节数。jcxz是CX不为0时进行jcxz的跳转,loop是CX为0时跳出循环。
四、一个“混乱”的程序与分析:
下面的程序能否正常结束?
assume cs:codeseg
codeseg segment
mov ax,4C00H
int 21H
start:
mov ax,0
s:
nop
nop
mov di,offset s
mov si,offset s2
mov ax,cs:[si]
mov cs:[di],ax
s0:
jmp short s
s1:
mov ax,0
int 21H
mov ax,0
s2:
jmp short s1
nop
codeseg ends
end start
按理来说,由于入口在start处,而
mov ax,4C00H
int 21H
在入口之前,并且从程序上看来,没有jmp指令直接跳转到第一条指令处,因为第一条指令处没有标号。但是我们知道除了使用jmp加标号的形式跳转,也可以采用jmp加非标号的形式跳转,至于没有标号就一定不能执行前两条指令是不成立的。我们仔细来分析:
我们程序在编译过程中产生一个“.lst”文件,我们就其部分内容进行分析,如下图:
将s2处的数据”EB F6”复制到了s处,而不是将s2处的“jmp short s1”复制到了s处,EBF6的作用是修改IP使得IP减8,所以复制到s处的数据被解析同样的效果,从s(0008)处减8即减到0000处,因而是可以正常执行退出指令的,程序可以正常退出。
我们也可以在Debug中调试查看,结果分析如下:
我们在可执行文件中查看二进制机器码,与编译完成后从cs:0地址开始的数据是相同的。