汇编-回顾篇(中)

简介: 汇编-回顾篇

当然也可写为0[bx]和5[bx],注意这种写法和C语言中数组的相似之处:C语言中数组表示为a[i],汇编语言中表示为5[bx]。

SI和DI寄存器  

SI和DI功能和BX相似,但不可以拆分为两个8位寄存器。也就是说下面代码等价:

mov bx|si|di,0            
mov ax,[bx|si|di]            
mov ax,[bx|si|di+123]

   

所以在这里可以使用更方便的方式:[bx+si]和[bx+di],这两个式子表示偏移地址为(bx)+(si)的内存单元,使用方法如:mov ax,[bx+si]等价于mov ax,[bx][si]

当然,有了这些表示方法,自然就有[bx+si+idata]和[bx+di+idata],相似的,也可以写成

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

那我们总结一下这些内存寻址方法:

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

[bx]用一个变量表示偏移地址,定位一个内存单元

[bx+idata]用一个常量和一个变量表示偏移地址,可在一个起始地址的基础上间接定位一个内存单元

[bx+si]用两个变量表示偏移地址

[bx+si+idata]用两个变量和一个常量表示偏移地址

使用双循环,使用一个寄存器暂存cs的值,如:

···            
mov cx,4            
s0:mov dx,cx            
mov si,0            
mov cx,3            
s:mov al,[bx+si]            
and al,11011111b            
mov [bx+si],al            
inc si            
loop s            
add bx,16            
mov cx,dx            
loop s0            
···

假如循环比较复杂,没有多余的寄存器可用,我们可以使用内存暂存cx或其他数据:

···            
dw 0            
···            
mov cx,4            
s0:mov ds:[40H],cx            
mov si,0            
mov cx,3            
s:mov al,[bx+si]            
and al,11011111b            
mov [bx+si],al            
inc si            
loop s            
add bx,16            
mov cx,ds:[40H]            
loop s0            
···

这么使用的话注意需要在数据段声明用来暂存的内存,好在程序加载时分配出来。当然,在需要暂存的地方,还是建议使用栈:

···            
dw 0,0,0,0,0,0,0,0            
···            
mov ax,stacksg            
mov ss,ax            
mov sp,16            
···            
mov cx,4            
s0:push cx            
mov si,0            
mov cx,3            
s:mov al,[bx+si]            
and al,11011111b            
mov [bx+si],al            
inc si            
loop s            
add bx,16            
pop cx            
loop s0            
···


数据处理的两个基本问题  


两个基本问题  

1.处理的数据在什么地方

2.要处理的数据有多长

接下来的讨论中,使用reg来表示一个寄存器,使用sreg来表示一个段寄存器。所以:

reg:ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,sp,bp,si,di
sreg:ds,ss,cs,es
bx,si,di和bp  

在8086CPU中,只有这四个寄存器可以使用[···]来进行内存寻址,可以单个出现,或以下面组合出现(常数可以随意出现在这些表示方法中):

bx+si/di

bp+si/di

注:如果使用了bp来寻址,而没有显式的表明段地址,默认使用ss段寄存器,如:

mov ax,[bp]              ;(ax)=((ss)*16+(bp))            
mov ax,[bp+idata]        ;(ax)=((ss)*16+(bp)+idata)            
mov ax,[bp+si]           ;(ax)=((ss)*16+(bp)+(si)+idata)
数据的位置  

绝大部分机器指令都是用来处理数据的,基本可分为读取,写入,运算。在机器指令这个层面上,并不关心数据是什么,而关心指令执行前数据的位置。一般数据会在三个地方,CPU内部,内存,端口。

汇编语言中使用三个概念来表示数据的位置:

立即数(idata)

o对于直接包含在机器指令中的数据,在汇编语言中称为立即数

o例:mov ax,1 add bx,2000h

寄存器

o指令要处理的数据在寄存器中,在汇编指令中给出相应寄存器名

o例:mov ax,bx mov ds,ax

段地址(SA)和偏移地址(EA)

o指令要处理的数据在内存中,在指令中使用[X]方式给出,SA在某个段寄存器中

o例:mov ax,[0] mov ax,[di]

总结一下寻址方式:

 

寻址方式

含义

名称

[idata]

EA=idata;SA=(DS)

直接寻址

[bx|si|di|bp]

EA=(bx|si|di|bp);SA=(DS)

寄存器间接寻址

[bx|si|di|bp+idata]

EA=(bx|si|di|bp+idata);SA=(DS)

寄存器相对寻址

[bx|bp+si|di]

EA=(bx|bp+si|di);SA=(DS|SS)

基址变址寻址

[bx|bp+si|di+idata]

EA=(bx|bp+si|di+idata);SA=(DS|SS)

相对基址变址寻址

数据的长度  

8086CPU中可以指定两种尺寸的数据,byte和word,所以在使用数据的时候要指明数据尺寸。

在有寄存器参与的时候使用寄存器的种类区分

o字:mov ax,1

o字节:mov al,1

在没有寄存器参与的时候,使用X ptr指明内存单元长度,X是word或byte

o字:mov word ptr ds:[0],1 add word ptr [bx],2

o字节:mov byte ptr ds:[0],1 add byte ptr [bx],2

其他默认指明处理类型的指令

opush [1000H],push默认只进行字操作

灵活使用寻址方式的例子,修改下面内存空间中的数据:

段seg:60

 

起始地址

内容

00

‘DEC’

03

‘Ken Oslen’

0C

137

0E

40

10

‘PDP’

···            
mov ax,seg            
mov ds,ax            
mov bx,60h            
mov word ptr [bx].0ch,38    ;第三字段改为38            
add word ptr [bx].0eh,70    ;第四字段改为70            
mov si,0            
mov byte ptr [bx].10h[si],'v'   ;修改最后一个字段的三个字符            
inc si            
mov byte ptr [bx].10h[si],'A'            
inc si            
mov byte ptr [bx].10h[si],'X'            
···

这段代码中地址的使用类似c++中结构体的使用。[bx].idata.[si],就类似与c++中的dec.cp[i]。dec是结构体,cp是结构体中的字符串成员,[i]表示第几个字符。

div指令  

div是除法指令,需要注意以下三点:

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

被除数:默认在AX或DX中,如果除数8位,被除数则为16位,放在AX中;如果除数16位,则被除数32位,在DX和AX中,DX存放高16位,AX放低16位。

结果,除数8位,结果(商)存放在AL中,AH存放余数;如果除数16位,则AX存放商,DX存放余数

格式:div reg或div 内存单元,所以div byte ptr ds:[0]表示:

(al)=(ax)/((ds)*16+0)的商;
(ah)=(ax)/((ds)*16+0)的余数;
div word ptr es:[0]表示:
(al)=[(dx)*10000H+(ax)]/((es)*16+0)的商
(ah)=[(dx)*10000H+(ax)]/((es)*16+0)的余数

例:计算100001/100,因为100001(186A1H)大于65535,则需要存放在ax和dx两个寄存器,那么除数100只能存放在一个16位的寄存器中,实现代码:

mov dx,1            
mov ax,86A1H            
mov bx,100            
div bx            
执行之后(ax)=03E8H(1000),(dx)=1。
伪指令dd  

dd是一个伪指令,类似dw,但dd是用来定义dword(double word,双字),如:

dd 1  ;2字,4字节            
dw 1  ;1字,2字节            
db 1  ;1字节            

将data段中第一个数据除以第二个数据,商存入第三个数据:

···            
data segment            
dd 100001            
dw 100            
dw 0            
data ends            
···            
mov ax,data            
mov ds,ax            
mov ax,ds:[0]            
mov dx,ds:[2]            
div word ptr ds:[4]            
mov ds:[6],ax            
···

总结一下div相关:

div后面跟的是除数

被除数位数是除数两倍

被除数存在ax中或ax+dx(ax低,dx高)

商在ax或al中,余数在ah或dx中(高余数,低商)

dup  

dup是一个操作符,由编译器识别,和db,dw,dd配合使用,如:

db 3 dup (0)表示定义了三个值是0的字节,等价于db 0,0,0

db 3 dup (1,2,3)等价于db 1,2,3,1,2,3,1,2,3 共九个字节

db 3 dup (‘abc’,’ABC’)等价于db ‘abcABCabcABCabcABC’

综上,db|dw|dd 重复次数 dup (重复内容)


转移指令原理  


转移指令  

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

转移行为:

o只修改IP,称为段内转移,如jmp ax

o同时修改CS和IP,称为段间转移,如jmp 1000:0

修改范围(段内转移):

o短转移:修改IP范围-128~127

o近转移:修改IP范围-32768~32767

转移指令分类:

o无条件转移:jmp

o条件转移

o循环指令

o过程

o中断

offset操作符  

offset是由编译器处理的符号,它能去的标号的偏移地址,如:

start:mov ax,offset start            
s:mov ax,offset s            

这里就是将start和s的偏移地址分别送给ax,也就是0和3

jmp指令  

jmp是无条件转移指令,可以只修改IP也可以同时修改CS和IP,只要给出两种信息,要转移的目的地址和专一的距离。

依据位移的jmp指令:jmp short 标号(转到标号处执行指令)。这个指令实现的是段内短转移,对IP修改范围是-128~127,指令结束后CS:IP指向标号的地址,如:

0BBD:0000   start:mov ax,0  (B80000)            
0BBD:0003   jmp short s   (EB03)            
0BBD:0005   add ax,1    (050100)            
0BBD:0008   s:inc ax    (40)

执行之后ax值为1,因为跳过了add指令。

还应注意的是,jmp short短转移指令并不会在机器码中直接写明需要转移的地址(0BBD:0008),jmp的机器码是EB03并没有包含转移的地址,这里的转移距离是相对计算而出的地址,来看下面的执行过程:

1.(CS)=0BBDH,(IP)=0006H,CS:IP指向EB03(jmp short s)

2.读取指令EB03进入指令缓冲器

3.(IP)=(IP)+指令长度,即(IP)=(IP)+2=0008H,之后CS:IP指向add ax,1

4.CPU指向指令缓冲器中的指令EB03

5.执行之后(IP)=000BH,指向inc ax

在jmp short s的机器码中,包含的并不是转移的地址,而是转移的位移,这里的位移是相对计算出来的,用8位一字节来表示,所以表示范围是-128~127,用补码表示。计算方法如是,8位位移=标号处地址-jmp下一条指令的地址。当然还有一种类似的指令是jmp near ptr 标号,是近转移,原理一样,只是表示位移的是字类型16位,表示范围-32768~32767。

jmp+地址远转移  

jmp far ptr 标号实现的是段间转移,也就是远转移,它的机器码中指明了转移的目的地址的CS和IP的值,如下面例子:

0BBD:0000   start:mov ax,0    (B80000)            
0BBD:0003   mov bx,0    (BB0000)            
0BBD:0006   jmp far ptr s    (EA0B01BD0B)            
0BBD:000B   db 256 dup (0)               
0BBD:010B   s:add ax,1               
0BBD:010X   inc ax

可以看出,jmp的机器码中明确指明了跳转位置s的地址0BBD:010B,在低位的是IP的值,高位的是CS的值。

jmp+寄存器|内存转移  

jmp+寄存器:jmp 16位reg,实现的是(IP)=(16位reg),之前讨论过,直接修改IP的值为寄存器中的值。

jmp+内存:jmp加内存使用的时候有两种用法:

jmp word ptr 内存单元地址(段内转移)

o从内存单元地址处开始存放一个座位转移目的的偏移地址的字

o内存单元支持任何寻址方式

o如jmp word ptr ds:[0],执行后(IP)=0123H(ds:[0]中的值是123H)

jmp dword ptr 内存单元地址(段间转移)

o从内存单元地址处开始存放两个字,高位存放段地址,低位偏移地址作为转移的目的地址

o(CS)=(内存单元地址+2),(IP)=(内存单元地址),支持任一种寻址方式

o如jmp dword ptr [bx]跳转到0:123H

jcxz指令  

jcxz指令为条件转移指令,所有的条件转移指令都是短转移,转移范围是-128~127。使用格式是jcxz 标号,功能是如果(cx)=0则跳转到标号处执行;如果(cx)!=0,那么什么也不做继续执行代码。

loop指令  

loop为循环指令,所有的循环指令都是短转移,转移范围是-128~127。使用格式是loop 标号,功能是如果(cx)!=0那么跳转到标号处执行;如果(cx)=0那么什么也不做继续执行程序。

根据位移进行转移的指令总结  

下面几条指令是根据位移进行转移(相对计算转移位置,而不是直接提供转移目的的IP和CS的值)

jmp short 标号

jmp near ptr 标号

jcxz 标号

loop 标号

这些指令之所以是间接计算标号的位置,是为了方便在代码中浮动装配,使得循环体或这些指令的代码段在任何位置都可以执行(不要超跳转范围)。而编译器会对跳转的范围进行检测,如果跳转超过了范围,编译器会报错。

注:jmp 2100:0是debug使用的汇编指令,编译器并不认识。

call和ret指令  

ret和retf  

ret和call都是转移指令,都是修改IP的值,或同时修改CS和IP。

ret指令用栈中的数据修改IP,实现的是近转移;retf指令用栈中的数据修改CS和IP的值,实现远转移。格式:直接用 ret。

ret执行步骤:

1.(IP)=((SS)*16+(SP))
2.(SP)=(SP)+2

retf执行步骤:

1.(IP)=((SS)*16+(SP))
2.(SP)=(SP)+2
3.(CS)=((SS)*16+(SP))
4.(SP)=(SP)+2

所以ret指令相当于 pop ip,执行retf指令相当于执行pop ip,pop cs。

call指令  

call指令也是一个转移指令,执行格式:call 目标(具体使用接下来说明),call的执行步骤:

1.将当前的IP或CS和IP入栈

2.转移

call不能实现短转移,但它实现转移的原理和jmp相同。

根据位移转移:call 标号,近转移,16位转移范围,也是使用相对的转移地址。

执行步骤:

1.(SP)=(SP)-2
2.((SS)*16+(SP))=(IP)
3.(IP)=(IP)+16

所以执行这条命令相当于执行push ip,jmp near ptr 标号。

直接使用地址进行(远)转移:call far ptr 标号,执行步骤:

1.(SP)=(SP)-2
2.((SS)*16+(SP))=(CS)
3.(SP)=(SP)-2
4.((SS)*16+(SP))=(IP)
5.(CS)=标号所在的段的段地址
6.(IP)=标号的偏移地址

所以执行call far ptr 标号相当于执行push cs,push ip,jmp far ptr 标号

使用寄存器的值作为call的跳转地址:call 16位reg

1.(SP)=(SP)-2
2.((SS)*16+(SP))=(IP)
3.(IP)=(16为reg)
相当于执行push ip,jmp 16位reg

使用内存中的值作为call的跳转地址:call word ptr 内存单元地址,当然还有call dword ptr 内存单元地址,这样进行的就是远转移。

联合使用ret和call  

联合使用ret和call实现子程序的框架:

assume cs:code            
code segment            
main:            
···            
call sub1            
···            
mov ax,4c00h            
int 21h            
sub1:            
···            
call sub2            
···            
ret            
sub2:            
···            
ret            
code ends            
end main
mul指令  

mul是乘法指令,使用时应注意,两个相乘的数,要么都是8位,要么都是16位,如果是8位,那么其中一个默认放在al中,另一个在一个8位reg或字节内存单元中;若是16位,则一个默认在ax中,另一个在16位reg或字内存单元中。如果是8位乘法, 则结果放在ax中,结果是16位;若是16位乘法,结果默认在ax和dx中,dx高位,ax低位,共32位。

格式:mul reg 或 mul 内存单元,支持内存单元的各种寻址方式。

如mul word ptr [bx+si+8]代表:

(ax)=(ax)*((ds)*16+(bx)+(si)+8)低16位
(dx)=(ax)*((ds)*16+(bx)+(si)+8)高16位
例:计算100*10
mov al,100            
mov bl,10            
mul bl
参数的传递和模块化编程  
相关文章
汇编语言之常见的汇编指令
汇编语言之常见的汇编指令
1364 0
汇编语言之常见的汇编指令
|
6月前
|
程序员 存储 安全
【汇编】汇编语言的介绍
【汇编】汇编语言的介绍
121 0
【汇编】汇编语言的介绍
|
6月前
|
存储 缓存 Linux
C语言编译过程——预处理、编译汇编和链接详解
C语言编译过程——预处理、编译汇编和链接详解
|
存储 安全 Unix
汇编-回顾篇(上)
汇编-回顾篇
119 0
|
存储 机器学习/深度学习 小程序
汇编-回顾篇(下)
汇编-回顾篇
119 0
|
编译器 C语言 数据安全/隐私保护
汇编语言和本地代码及通过编译器输出汇编语言的源代码
汇编语言和本地代码及通过编译器输出汇编语言的源代码
110 0
|
存储 数据采集 监控
初学汇编
初学汇编
195 0
初学汇编
|
编译器 C语言 C++
用汇编分析C++程序,x86汇编
  说到用汇编的眼光看C++语言,那么怎么阅读汇编代码就成了我们需要解决的一个问题。其实,实话说,汇编其实不难。只是我们需要明白这样几个问题:   (1)汇编是什么语言?   (2)汇编中的主要内容有哪些?   (3)汇编语言是怎么和实际C/C++语言代码一一对应的?
192 0
一次简单的反汇编
一次简单的反汇编代码
1487 0