8086CPU汇编语言学习入门(持续更新中。。。)

8086寻址方式:段地址:偏移地址 内存地址=段地址*16(或左移动1位,末尾补0)+偏移地址=实际地址
如:2000:4700=>20000+4700=24700h这个地址,原因为:cpu总线为20,内存地址为32位,所以需要使用此寻址方案

使用DOSbox的方法:
1.打开dosBOX软件
2.会出现一个z盘符,我们需要把虚拟的盘挂载到一个指定的文件夹
挂载方法:mount c c:\masm 把工作目录挂载到已经存在的masm文件夹,这样在本地都可以对文件进行管理了
3.debug.exe 的使用:不区分大小写
-q 退出
-r 查看,修改CPU寄存器的内容
-r ax 查看Ax寄存器的值,输入值就为更改(默认为以16进制输入和显示)

-d 查看内存中的内容
可以看到预设内存中的128个字节的内容,-d 可以看到后边128个字节
-d 段地址:偏移地址 可以查询这个地址后面的128个字节内容

-e 改变内存中的内容
-e 开始地址(段地址:偏移地址) data1 data2 …(最大数为FF)
-e 开始地址(段地址:偏移地址)
这样可以一个字节一个字节的进行替换修改,按空格修改下一个字节,回车结束

-u 将内存中的机器指令翻译成汇编指令
-u 开始地址(段地址:偏移地址)可以把这片地址看着是汇编指令的机器码,通过-u能解析为汇编指令

cs寄存器是用来存放代码的,ip寄存器为偏移指针

-a 以汇编指令的格式在内存中写入机器指令
-a 073f:0100(cs:ip默认的起始地址)mov ax,0123
mov ax,bx
add ax,bx

-t 执行机器指令
-t 自动执行CS:1P位置的指令。

修改CS的值: -r cs
修改ip的值:-r ip

8086CPU,是16位,16位作为一个字,把数据放寄存器时,一个字节为8位,高8位放高字节,低8位放低字节,当指针指向一个内存地址时,指向的是低位
当说:0地址单元存放的字节类型数据是对应0地址的8位数据
当说,0地址字节存放的字型数据是对用地址0,1地址中的数据,0地址存放的是低位,1地址存放的是高位
注意:字节型数据是8位,字型数据是一个字,为16位

mov ax,4e20=====>aL=4e,ah=20

jmp 转移指令:可同时修改CS:ip
jum 段地址:偏移地址 这样下一条指令就会转移到此地址进行执行
或者单独修改IP的方法:
jmp ax(某一合法寄存器)

DS段寄存器
通过汇编指令读取内存单元数据
解决方案:用DS寄存器存放要访问的段地址,有[]给出偏移量(电路这样设计的)
例:注意只能通过间接赋值的方式,而不能使用mov ds,1000H
mov bx,1000H //把段地址先存放到通用寄存器BX中
mov ds,bx //把1000H段地址间接赋值给DS
mov al,[0] //将1000:0 数据存到al中

mov [0],al //将al中的数据写到1000:0中

在内存中读取两个两个字节(1个字)的数据时要先读高位,再读低位(显示时是低位在前,高位在后)

关于段地址和偏移量,是又程序员自己定义的,可以多样性
如:123B0H,起始偏移地址:123BH,起始偏移量0000H
123B0H,起始偏移地址:1230H,起始偏移量0080H

案例:
1.累加数据段中的前三个单元中的数据:段地址123BH(单元指的是8位的数据)
mov ax,123BH //段地址放入通用寄存器
mov ds,ax //把地址赋值给段地址寄存器
mov al,0 //初始化ax低位寄存器
add al,[0] //把123B0h的数据与ax寄存器的低位中的数相加
add al,[1] //把123B1h的数据与ax寄存器的低位中的数相加
add al,[2] //把123B2h的数据与ax寄存器的低位中的数相加

2.累加数据段中的前三个字型数据:段地址123BH(字型数据指的是16位的数据)
mov ax,123BH //段地址放入通用寄存器
mov ds,ax //把地址赋值给段地址寄存器
mov al,0 //初始化ax寄存器
add aX,[0] //把123B0h、123B1h的数据与ax寄存器中的数相加
add aX,[2] //把123B2h、123B3h的数据与ax寄存器中的数相加
add aX,[4] //把123B4h、123B5h的数据与ax寄存器中的数相加

mov指令:
寄存器赋值:
mov ax,8h
mov ax,bx
mov ax,[0]
mov al,[0] //一个字节8位赋值
mov ah,[0] //一个字节8位赋值

内存赋值
mov [0],ax
mov [0],ds

段寄存器赋值
mov ds,ax

add指令:
结果放寄存器:
add ax,8
add ax,bx
add ax,[0]

结果放内存单元:
add [0],ax

内存不能直接相加,必须使用寄存器
add [1][2] //语法错误

通用寄存器不能与DS段寄存器直接相加
add ds,ax //语法错误

sub减法运算,规律与add用法相同
sub 被减数 减数 //sbu ax,bx => ax=ax-bx

对于通用寄存器,直接使用就是16位,如ax
分开使用就是 8位,如al

栈的操作:
栈是先进后出,栈指针默认指向栈顶的下一个地址
入栈指令:push
出站指令:push

知识点:
1.cpu如何知道一段内存被当做栈使用?
8086CPU中,有两相关栈的寄存器:
栈段寄存器:SS
栈指针偏移寄存器:SP

栈操作:栈操作都是16位的数据,占两个单元内存空间
设置1000h:10h位栈区域(不包括10h)
mov ax,1000h
mov ss,ax //赋值栈段寄存器
mov sp,10h //赋值栈偏移的大小

栈内存空间为1000:0h—1000:fh 共计16个字节空间(内存单元空间)

2.执行push和pop的时候,如何知道哪个单元是栈顶单元
SS:SP无时无刻都指向栈顶的下一个地址

超界问题:
8086CPU计算机本身是不能保证对栈的操作不会越界.只知道栈顶的位置,不知道程序安排的栈空间有多大。
越界带来的后果:
入栈的数据太多导致超界,导致其他空间内容被改写
出栈时栈空了仍然继续出栈导致超界

杜绝:
在编程时,要合理安排栈空间大小

8086CPU提供的栈操作机制:
1.在ss,sp中存放栈顶的段地址和偏移地址,入栈和出栈指令根据SS:SP指示的地址,按照栈的方式访问内存单元。

2.push指令的执行步骤:
1.SP=SP-2
2.向SS:SP指向的字单元中送入数据

3.POP指令执行的步骤:
1.从SS:SP指向的字单元中读取数据
2.SP=sp-2

栈操作案例:
mov ax,1000h
mov ss,ax
mov sp,0010H

mov ax,001AH
mov bx, 001BH

push ax //Ax的数据入栈
push bx //BX的数据入栈

pop ax //栈顶的数据出栈,数据放入AX,同时栈指针往上移动2个内存单元
pop bx //栈指针指向的栈顶出栈,数据放入BX中

结果:ax,bx的值通过先入后出的方式重新对ax,bx赋值,同时也在没有中间变量的情况下,做了数据的交换

栈的总结:
push\pop实质是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中
给出的,而是由SS:sp指出的

执行push和pop指令时,sp中的内容会自动改变

段的总结:
基础:
物理地址=段地址*16+偏移量地址

做法:
编程时,可以根据需要将一组内存单元定义为一个段

可以将起始地址为16的倍数,长度为N(N<=64k)的一组地址连续的内存单元,定义为一个段

将一段内存定义为一个段,用一个段的地址指示段,用偏移地址访问段内的单元—在程序中可以完全由程序员安排

数据段:DS定义段 [X]定义需要偏移的内存单元的数据
代码段:CS中定义代码段地址,IP定义偏移地址,代码将从CS:IP开始执行
栈段:SS定义栈段,SP定义偏移地址,SS:SP指向栈顶==》栈空间的后一个单元

数据段和栈段可以共用内存空间:
mov bx,1000h
mov ds,bx
mov ss, bx
mov sp,20H

汇编语言程序格式
assume cs:abc //代码段需要与代码寄存器CS关联,要从cs寄存器开始
abc segment //代码的开始 代码段名为abc
mov ax,2
add ax,ax

mov ax,4c00h //代码返回值,固定格式
int 21h //调用中断,固定格式

abc ends //代码段的结束
end //程序的结束

汇编程序的编译:后边加";"后,不进行提示,直接执行到结束。
1.使用编辑器,编辑代码,文件名为.asm
2.使用masm.exe 编译aa.asm,会生成aa.obj
3.使用link.exe 链接aa.obj,生成可执行文件aa.exe

debug检查程序执行结果:
debug aa.exe

cx寄存器为程序占用的空间大小寄存器

-g(Go):在debug下快速的执行,直到遇到断点或者程序结束,然后就可以查看寄存器和内存单元,查看结果。
-g 程序段地址:偏移地址 程序会在执行到此地址,一般选择为程序输入完成后的下一行地址

汇编语法规定内存单元使用[X]
MOV AX,[0] //段地址在ds中,偏移地址在0这个偏移量地址中存放的数据,操作数是ax为字
mov al, [bx] //段地址在ds中,偏移量在bx寄存器地址中存放的数据

约定1:
内存单元或寄存器中的内容,在使用书面表达的时候使用()
ax中的内容为0010H描述为:(ax)=0010H
2000:1000处的内容为0010H 描述为:(21000H)=0010H
MOV AX,[2] 描述为:(AX)=((Ds)*16+2)
mov [2],ax 描述为:((ds)*16+2)=(ax)
add ax,2 描述为:(ax)=(ax)+2
add ax,bx 描述为:(ax)=(ax)+(bx)
push ax 描述为:(sp)=(sp)-2 ((ss)*16+(sp))=(ax)
pop ax 描述为 (ax)=((ss)*16+(sp) (sp)=(sp)+2

约定2:idata表示常量
书写格式为:mov ax,[idata],实际格式如:mov ax,[0]; mov ax [1];…
mov bx ,idata 实际格式如:mov bx,1;mov bx,2;需要输入实际的数据
非法指令:mov ds,idata 格式如:mov ds,1,mov ds 2;

CX寄存器:用于存储LOOP值,当遇到loop后,先检查cx的值是否为0,若为0就向下执行,否则执行跳转到loop标号处执行。

inc ax 为自身ax累加1;

注意:[0]取值的问题
mov al,[0]
MOV BL, [1] 改写:mov bl,ds:[0]
编译器进过编译后,会变成如下代码:
mov al,0
MOV BL, 1
结果变成了直接赋值,导致程序错误。
解决办法:
mov ax,2000h
mov ds,ax //给数据段赋值
mov bx,0
mov al ds:[bx]//段地址:偏移量 -->这样就不会出现编译错误的情况
—[bx]为bx寄存器所指向的地址中的值
当使用mov ax,[常量]的时候,需要改成 mov ds:[1]
而使用mov ax, [寄存器]的时候,则能正确编译。

案例:编程计算以下8个数据的和,结果放在ax寄存器中
0123H 0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H
assume cs:code
code segment
dw 0123H ,0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H//将数据放入代码段

abc: mov bx ,0 //数据的偏移量(对于dw列表,dw属于数据存放的区域)
mov ax,0 //初始化结果保存的寄存器
mov cx,8 //loop循环的次数寄存器

s:add ax,cs:[bx] //获取到数据区的元素,段地址:偏移量
add bx,2 //因为0123H这些数据都是16位的,所以要偏移2,才能指向下一个元素
loop s

 mov ax,4c00h
 int 21h

code ends
end abc //abc是告诉编译器程序存储的空间开始的位置,进行一个标号。
//这样在程序执行的时候,就错开了存放数据的区域

案例:利用栈,将数据逆序存放。
0123H ,0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H
思路:
1.程序运行时,定义的数据存放在cs:0-cs:f单元中,共8个字节单元
2.依次将这8个数据入栈,然后依次出栈到这8个字单元中,从而实现数据的逆序存放
3.需要定义空数据,如下的dw 0,0,0,0,0,0,0,0
assume cs:codesg
codesg segment
dw 0123H ,0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

abc: mov ax,cs
	  mov ss,ax
	  mov sp,30h
	  
	  mov bx,0
	  mov cx,8
	  
	s:push cs:[bx]
	   add bx,2
	   loop s
	   
	   
	 mov bx,0
	 mov cx,8
	 
  s0:pop cs:[bx]
	  add bx,2
	  loop s0

codesg ends
end abc

将数据、代码、栈放入不同的段
assume cs:code,ds:data,ss:stack
data segment
dw 0123H ,0456H 0789H,0DEFH,0FEDH,0987H,06542H,09632H
deat ends

stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends

code segment

#初始化各段寄存器
start: mov ax,stack
mov ss,ax
mov sp,20h
mov ax,data
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

在正常程序中:
数据存放的地址为DS+100H,100h为程序数据的前缀,如al
DS=075A0H,数据存放的地址段为:075A0H+100=076A0 内存单元开始地址为076A:00

例子:
assume cs:code,ds:data
data segment
db ‘unix’
db ‘fork’

data ends

code segment
start: mov al,‘a’
mov bl,‘b’

   mov ax,4c00h
   int 21h

code ends
end start

在执行完成debug时,查看‘unix’'fork’的地址:
DS=075A0h
数据不是从075A0:0 开始
而是从DS+100H=075A0H+100h(程序前缀地址)=076A:0

在实际开发中,常数:
11就为十进制11
11h为十六进制的数据

大小写转换规律:大写+20h=小写 小写-20H=大写
b 62h 0110 0010
B 42h 0100 0010 //区别:在第六位不一样

当大写转小写时,只需要与(and) 1101 1111就可以进行转换
0110 0010 b
0010 0000 AND

0110 0010 B

0100 0010 B
1101 1111 OR

0100 0010 b

and,or的用法,每一位进行计算
and a,b ===>a=a and b
or a,b ===>a=a or b

代码实现:大小写转换
assume cs:codesg,ds:datasg
datasg segment
db ‘Basic’ //字符串表示法
db ‘INdifseDEeD’

datasg ends

codesg segment
start:
mov ax,datasg //程序执行后,系统分配的地址单元DS寄存器的值
mov ds,ax

 #第一串:小写转大写
 mov bx,0 //基础地址,从0开始
 mov cx,5 //有5个字符

s:mov al,[bx] //取出B
and al,11011111b
mov [bx],al //结果又保存在[bx]中
inc bx
loop s

#第二串:大写转小写
mov bx,5 //第二个字符串在第一个的基础上偏移5个单元
mov cx,11 //第二串有11个字符

s0:mov al,[bx]
or al,0010 0000b
mov [bx],al
inc bx
loop s0

mov ax,4c00h
int 21h

codesg ends
end start

[bx+idata]的含义:
表示一个内存单元,偏移地址为(bx)+idata
mov ax,[bx+200]的含义:
1.将一个内存单元的内容送入ax
2.这个内存单元的长度为2字节
3.内存单元的段地址在ds中,偏移地址为200h,即为:ds:200+(bx)这个内存中的值
4数学描述为:(ax)=((ds)*16+200+(bx))
其他写法也可以为:
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200

[BX+idata]操作内存的案例:
大小写转换优化:当两个字符串长度相同时的优化,优化原因,当长度相同时,有很多相同的代码
assume cs:codesg,ds:datasg
datasg segment
db ‘BasIc’
db ‘mAdMs’

datasg ends
codesg segment
start:
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5

#以下为代码优化
s:mov al,[bx] //取第一串第一个字符
and al,1101 1111b
mov [bx],al //and后结果放回bx地址的内存中

mov al,[5+bx] //取第二串第一个字符
or al,0010 0000b
mov [5+bx],al //or 后结果放回第二个字符串中的第一个字符的内存中
inc bx
loop s

codesg ends
end start

SI,DI寄存器,不能拆分成两个8位:主要用于内容的copy
SI寄存器:源变址寄存器source index
DI寄存器:目标变址寄存器 destination index
案例:用寄存器SI和DI实现将字符串‘welcome to masm!'复制到它后边的数据区域
分析:
源数据的起始地址为datasg:0
目标数据的起始地址为datasg:16
用ds:si指向要复制的原始字符串
用ds:di指向目标空间
然后用循环来完成复制

assume cs:codesg,ds:datasg
datasg segment
db ‘welcome to masm!’
db ‘…’

datasg ends

codesg segment
start:
mov ax,datasg
mov ds,ax //数据段地址

 mov si,0  //偏移量为0,指向源字符串第一个地址
 mov di,16  //偏移量为16,指向目标地址的第一个地址
 mov cx,8  //循环次数计数器

s:mov ax,[si]//把源的第一个字符给通用寄存器
mov [di],ax //通用寄存器把第一个字符放入目标第一个地址里边
add si,2 //因为SI和DI都是16位的地址,copy的时候也是copy两个字节,
add di,2 //而一个字符为一个字节,所有这里要+2,16个字符只需要copy8次
loop s

mov ax,4c00h
int 21h

codesg ends
end start

[BX+SI]和[BX+DI]方式指定地址
[BX+SI]表示内存单元:偏移地址为(bx)+(SI),即bx中的数值加上SI中的数值
mov ax,[bx+si]的含义:
1.将一个内存单元的内容送入ax
2.这个内存单元的长度为2个字节(字单元),存放1个字
3.偏移地址为:bx中的数值加上SI中的数值
4.段地址在ds中,偏移地址在0这个偏移量地址中存放的数据
数学化描述为:
(ax)=((ds)*16+(bx)+(si))
其他写法为:
mov ax,[bx][si]

[bx+si+idata]和[BX+DI+idata]方式指定地址,三个地址中的数据叠加后的偏移量,段地址在DS中
[BX+SI+idata]表示内存单元
偏移地址为(bx)+(si)+idata,即:bx中的数值+SI中的数值+idata

mov ax,[bx+si+idata]的含义:
1.将内存单元的内容送入ax
2.这个内存单元的长度为2字节,存放1个字
3.偏移地址为bx中的数值+si中的数值+idata,段地址在ds中,偏移地址在0这个偏移量地址中存放的数据
数学表达式为:
(ax)=((ds)*16+(bx)+(si)+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
mov ax,[bx][si]==>idata=0

灵活运用不同的寻址方法,案例:
编程将datasg段中的每个单词的头一个字母改为大写字母
assume cs:codesg,ds:datasg
datasg segment
db ‘1.file…’ //都为16个字符
db ‘2.edit…’
db ‘3.send。。。。。。。。…’
db ‘4.view…’
db ‘5.option…’
db ‘6.help…’

datasg ends

codesg segment
start:
mov ax,datasg //把随机的DS段地址赋值给ax
mov ds,ax //ds将指向系统指定的段地址

mov bx,0 //第一行数据第一个单元
mov cx,6 //需要循环6次

s:mov a,[bx+3]//第一行的首地址+3指向文件头第一个单词首字符
and al,1101 1111b //转大写运算
mov [bx+3],al //转换完成,放回原位
add bx,16 //bx+16将转换到下一个字符串中的首地址

loop s

codesg ends
end start

案例:灵活运用不同的寻址方式
编程将datasg段中的每个单词改为大写字母
assume cs:codesg,ds:datasg
datasg segment
db 'ibm '//16个字符
db 'dec '//可以看出4行3列的二维数组:4*3,
db 'dos '//这样需要两层循环可以解决
db 'vax '//先循环改变第一串每个字母为大写,然后
//将地址+16转到下一行,然后再调用第一次的循环
datasg ends

codesg segment
start:
mov ax,datasg
mov ds,ax //存入段地址
mov bx,0 //偏移量初始化为0
mov cx,4 //循环计数

s0:mov si,0 //将源寄存器初始化为0
mov cx,3 //---->这个cx只有一个,第一串循环完了,就为0,
//当第二个循环来了,会继续减,判断当然不为0,这样程序乱了-----》需要解决

s:mov al,[bx+si] //第一次偏移量为[0+0]指向首行第一个字符
and al,1101 1111b//运算转换为大写
mov [bx+si],al //将转换后存入[0+0]首行第一个字符
inc si //源寄存器+1
loop s //循环执行第一行改为大写

add bx,16 //第一行循环完成,

loop s0

二重循环解决一个cx的问题:
assume cs:codesg,ds:datasg
datasg segment
db 'ibm ’
db 'dec ’
db 'dos ’
db 'vax ’

datasg segment

start:
mov ax,datasg
mov ds,ax
mov bx,0 //初始化偏移量
mov cx,4 //循环计数

s0:mov dx,cx //外层循环次数存入dx通用寄存器,值为4,每次到这来dx会随着CX-1
此行可以改为 #mov ds:[40H],cx//解决寄存器稀缺问题

mov si,0  //初始化源寄存器
mov cx,3  //内存循环赋值,循环3次

s1:mov al,[bx+si] //取[0+0]偏移处的地址
and al,1101 1111b//运算转换为大写
mov [bx+si],al //运算转换为大写存入
inc si //偏移+1指向第一行ds:01处的单元

loop s1  //循环完成第一行的大写转换

add bx,16 //第一行完成,转换到第二行的偏移量为16
mov cx,dx //dx存放的是内循环没有执行时候的值,即内循环的次数,赋值给循环计数器

此行可以改为#mov cx,ds:[40H] //解决寄存器稀缺问题:
loop s0

codesg ends
end start

在开发中,寄存器是非常稀缺的资源,所以需要将寄存器存储改为内存空间存储
也可以使用栈保存数据:解决寄存器稀缺问题

assume cs:codesg,ds:datasg
datasg segment
db 'ibm ’
db 'dec ’
db 'dos ’
db 'vax ’

datasg segment

stacksg sement
dw 0,0,0,0,0,0,0,0
stacksg ends

start:
mov ax,datasg
//mov dx,ax—>此行改为栈存储
-----------栈存储,
mov ss,ax
mov sp,16
-----------此内容替换上面那句:mov dx,ax
mov ds,ax
mov bx,0
mov cx,4

s0://mov ds:[40H],cx
---------栈存储
push cx //---->将外层循环的cx压入栈

---------此内容替换上面那句:mov ds:[40H],CX

mov si,0
mov cx,3

s1:mov al,[bx+si]
and al,1101 1111b//运算转换为大写
mov [bx+si],al
inc si
loop s

add bx,16

-----增加栈存储
pop cx
-----此内容为增加

loop s0

BX寄存器:基址寄存器
只有bx,bp,SI,DI可以放在[]中进行内存单元寻址,除BX以外的通用寄存器都不能放在[]里边
CX,ax,dx,ds不能放在[]中进行内存寻址

注意:[BX+BP],[SI+DI]这种寻址是错误的
bx-----si
\ /
\ /
/
idata
/
/
/
bp---- di
只能有以上的组合,[bx+si],[bp+di],[bx+di],[si+bp]

bx与bp的区别:
bx默认段地址为DS段(数据段)
bp默认段地址为SS段 (栈段地址)
es为附加段寄存器,存储附加的基段地址

mov ax,[bp]–>默认为ss:[bp] (ax)=((ss)*16+(bp))
mov ax,ds:[bp]–>指定ds段 (ax)=((ds)*16+(bp))
mov ax,es:[bp] -->指定为es段 (ax)=((es)*16+(bp))
mov ax,[bx] —>默认DS段 (ax)=((ds)*16+(bx))
mov ax,ss:[bx] -->指定SS段 (ax)=((ss)*16+(bx))
mov ax,[bp+idata]–>默认ss段 (ax)=((ss)*16+(bp)+idata)
mov ax,[bp+si] —>默认ss段 (ax)=((ss)*16+(si))
mov ax,[bp+si+idata]—>默认ss 段(ax)=((ss)*16+(si)+idata)
mov ax,cs:[bx+si] —>指定CS段 (ax)=((cs)*16+(bx)+(si))

在指令中如何确定需要处理饿数据有多长,是一个字节,还是一个字
1.看操作符mov ax,1 这为一个字的操作
2.mov al,1 这为一个字节的操作
3.当在操作指令中没有操作数,如mov ds:[0],1 这样就不知道1需要存放多大的空间内
解决方法:word ptr为一个字的空间,即2个字节16位
byte ptr为一个字节,即8位
可以写成:mov word ptr ds:[0],1 —>将1存储到偏移地址为0的空间,内存大小为一个字16位
inc word ptr[bx]—>bx指向的内存单元,是一个字,即16位2个字节
inc word ptr ds:[0] ---->偏移地址为0的那个地址单元的数据+1,操作的内存为2个字节
add word ptr [bx],1---->bx指向的内存空间的值+1,内存空间为一个字,16位

	  mov byte ptr ds:[0],1 --->将1存储到偏移地址为0的空间,内存大小为1个字节8位

[BX+10H+SI]=[bx].10h[si]

除法指令:DIV
DIV 寄存器===>AX/寄存器(8位)=AL…AH
===>DX AX/寄存器(16位)=AX…DX

被除数 AX DX AX—>要变成32位(dx)*10000h+(ax)
除数 8位内存/寄存器 16位内存/寄存器
商 AL AX
余数 AH DX

DIV BL =>(ax)/(bl)=(al)…(ah)
div byte ptr ds:[0]
=>(ax)/((ds)*16+0)=(al)…(ah)
div byte ptr[bx+si+8]=>(ax)/((bs)*16+(bx)+(si)+8)=(al)…(ah)
div bx
=>((bx)*10000h+(ax))/(bx)=(ax)…(bx) //*10000意思为变成32位的数
div word ptr es:[0]=>((dx)*10000h+(ax))/((ds)*16+0)=(ax)…(dx)
div word ptr[bx+si+8]
=>((dx)*10000h+(ax))/((ds)*16+(bx)+(si)+8)=(ax)…(dx)

切记:提前在默认的寄存器中设置好被除数,且默认寄存器不作为别的用处。

案例:
利用除法指令计算100001/100=1000…1=====>186A1H/64H
需要进行16位的除法,因为被除数186A1h已经超过了16位,被除数就得用两个16位的寄存器存储
被除数:186A1H===>DX=1 AX=86A1
除数:64H===>BX=64H
商:AX
余数:DX

MOV DX,1
MOV AX,86A1H
MOV BX,64H
DIV BX

---->AX=03EB=1000D,DX=1

利用除法指令计算:1001/100=10…1==>3E9H/64H
需要使用8位的除法就可以了
被除数:3E9H===>AX=3E9H
除数:64h===>bl=64h
商:al
余数:ah

mov ax,3e9h
mov bl,64h
div bl

----->al=0A,AH=01

现实中:双字型数据的定义案例100001/100,商存放在数据单元中(除数后边的单元中)
data segment
db 100001 //可以定义1个字节的数据
dw 100 //可以定义两个字节的数据,dd 可以定义4个字节32位的数据
dw 0
data ends

mov ax,data
mov ds,ax //定义被除数的地址
mov ax,ds:[0]// AX保存的为被除数的低位16位,低位在前
mov dx,ds:[2] //偏移2个字节,被除数高位在后16位在DX
div word ptr ds:[4] //被除数在[4]中,1个字节

mov ds:[6],ax//结果自动保存在ax中,还需要转存到内存单元中。

dup功能:伪指令,用于重复数据
db 3 dup(0) -->定义了3个字节,他们都是0,等同于:db 0,0,0
db 3 dup(0,1,2)–>定义了9个字节,即3个字节重复3次,等同于:db 0,1,2,0,1,2,0,1,2
db 3 dup(‘abc’,‘ABC’)–>定义了18个字节,即6个字节重复3次,等同于:db ‘abcABCabcABCabcABC’

offset取得标号的偏移地址操作符
用法:offset 标号
例子:
assume cs:codeseg
codeseg segment

start:mov ax,offset start //相当于mov ax,001AH
ss: mov ax,offset ss //相当于mov ax,3 因为上面那条指令占三个字节

练习:将s处的指令复制到s0处:
assume cs:codesg
codesg segment
s:mov ax,bx //占两个字节
mov si,offset s
mov di,offset s0

mov ax,cs:[si] //此处两句就将s处的一条指令放入到s0指向的空字节中
mov cs:[di],ax

s0:nop //空字节占一个字节
nop

codesg ends
ends
转移指令jmp
jmp shart 标号
jmp near 标号
jmp far 标号
jmp word ptr 内存单元地址
jmp dword ptr 内存单元地址

jmp short 标号:段内转移,转移的范围为8位位移,即-128~127之间的地址范围都能跳转
也就是说,标号处的地址~跳转指令处的地址范围最多为127,往前跳可以到-128的位置,否则标号超限
assume cs:codesg
codesg segment
start:
mov ax,0
jmp short s
add ax,1

区域内jum到s的位置之间的地址不能大于127.

s:inc ax//编译器会编译成:add ax,01

jmp near ptr 标号:范围为16位距离的位移范围,范围为:-32769~32767
(ip)=(ip)+16位的地址范围
超过范围编译报错:jump out of range by 1 bytes.

jmp short和jmp near编译成机器指令后,机器指令是跳转到指令相对位置,机器码是位移地址

jmp far ptr 标号:汇编后,直接把跳转到的内存地址直接写到机器码中了,包含了CS和偏移地址IP

jmp后边可以跟16位的寄存器,直接跳到指定位置,如:
jmp ax
jmp bx

jmp word ptr:段内转移,从内存单元地址处开始存放一个字,是转移的目的偏移地址

mov ax,0123h
mov ds:[0],ax //MOV [BX],AX 相同,因为bx默认段地址是ds
jmp word ptr ds:[0] //执行后,(IP)=0123H

jmp dword ptr :段间转移,从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移量
两个字的内存单元:段地址+目标偏移地址

00 (IP)
02 (cs)

mov ax,0123h
mov ds:[0],ax //DS=0123H
mov word ptr ds:[2],0//DS高位置0,数据为012300
jmp dword ptr ds:[0] //执行后,(cs)=0,(ip)=0123h

mov ax,0123h
mov [bx],ax
mov word ptr [bx+2],0//4个字节,高地址是段地址,低地址是偏移地址
jmp dword ptr [bx] //执行后,(cs)=0,(ip)=0123h

jmp指令总结:
jmp 标号:
1.段间转移(远转移):jmp far ptr 标号
2.段内短转移:jmp short 标号,8位的位移
3.段内近转移:jmp near ptr 标号,16位的位移

jmp 寄存器
jmp bx,16位的位移

jmp 内存单元(表示跳到的地址)
1.段内转移:jmp word ptr 内存单元地址;jmp word ptr [bx],8个字节
2.段间转移:jmp dword ptr 内存单元地址;jmp dword ptr [bx],4个字节

jmp 后边不能跟立即数,如jmp 2000:0100 但是在debug中可以使用,用于直接调试

有条件跳转指令:jcxz 判断(cx)=0,则跳转到标号处执行,当(cx)不等于0时,程序向下执行
当(cx)=0时,(IP)=(IP)+8位位移,即标号处的地址~指令后的第一个字节的地址
对ip的修改范围都是-128~127,位移指的是机器码中的位移,而不是目的地址,目的地址是实际的标号位置的地址。
例:
assume 测试:codesg
codesg segment
start:
mov ax,2000h
mov ds,ax
mov bx,0

s:mov cx,[bx]
  jcxz,ok
  inc bx
  inc bx
  jmp short s

ok:mov dx,bx
mov dx,bx
mov ax,4c00h
int 21h

codesg ends
end start

例:
assume cs:codesg
codesg segment
start:
mov cx,6h //076a:0000 B90600
mov ax,10h //076A:0003 b81000

s:add ax,ax  //076a:0006 03c0
  loop s     //076a:0008 e2fc  loop 0006  //fc为补码=-4
  mov ax,4c00h //076a:000a  //执行loop后,会跳到这一行,然后跳转到当前地址-4就执行到s标号处。
  int 21h

codesg ends
end start

注意:在设计编译时,考虑到loop在跳转时,机器码中包含的s地址,则程序段内存中的偏移地址有了严格限制,因为是绝对地址,容易引发错误,
当机器码中包含的是位移,这样无论S处的指令实际地址是多少,loop指令转移的是相对位移保持不变
这样的设计,方便了程序段在内存中浮动装配。

模块化程序设计

调用字程序 call
返回ret

mov ax,0
call s //调用s

mov ax,4c00h
int 21h

s:add ax,1
ret //返回

实质:流程转移指令,它们都是修改IP,或者同时修改CS和IP

cpu执行call指令,进行两步操作
1.将当前ip或cs和ip压入栈中 push
2.跳转到标号处执行指令 jmp near ptr 标号 16位位移范围-32768~32767,位移由编译程序在编译时算出

当前ip压栈:(sp)=(sp)-2 (ss)*16+(sp)=(ip)
ip转移:(ip)=(ip)+16位的位移

指令call far ptr 标号 实现的是段内转移
cpu执行call far ptr 标号时的操作
1.(sp)=(sp)-2
((ss)*16+(sp))=(cs)
(sp)=(sp)-2
((ss)*16+(sp))=(ip)

2.(cs)=标号所在的段地址
(ip)=标号所在的偏移地址

call far ptr标号相当于:
push cs
push ip
jmp far ptr 标号

案例:
mov ax,0
call far ptr s

mov ax,4c00h
int 21h

s:add ax,1
ret

转移地址在寄存器中的call指令
call 16位寄存器
功能:
(sp)=(sp)-2
((ss)*16+(sp))=(ip)
(ip)=(16位寄存器)
相当于:
push ip
jmp 16位寄存器

如:
mov ax,0
call ax

mov ax,4c00h
int 21h

转移地址在内存中的call指令
call word ptr 内存单元地址
例:
mov sp,10h //栈顶指针
mov ax,0123h
mov ds:[0],ax //数据段偏移位置
call word ptr ds:[0]
执行后:(ip)=0123h,(sp)=0eh -->10h-2=0eh

call dword ptr 内存单元地址
相当于:
push cs
push ip
jmp dword ptr 内存单元地址

例:
mov sp,10h //栈顶指针
mov ax,0123h
mov ds:[0],ax//低地址
mov word ptr ds:[2],0 //高地址
call dword ptr ds:[0]
执行后:(cs)=0,(ip)=0123h,(sp)=0ch—>10h-2-2=0ch

ret 的运用,相当于pop ip

assume cs:codesg,ss:stack
stack segment
db 16 dup(0)
codesg segment
mov ax,4c00h
int 21h

start:mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret

codesg ends
end start

retf的运用,相当于pop ip ,pop cs,在代码中比ret多了一步push cs的操作
例:
assume cs:odesg,ss:stack
stack segment
db 16 dup(0)
stack ends

codesg segment
mov ax,4c00h
int 21h

start:mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0

  retf

codesg ends
end start

call 和ret 案例:计算2的n次方
错误案例:没有定义栈段是非常危险的
assume cs:code
code segment
start:
mov ax,1
mov cx,3
call s
mov bx,ax
mov ax,4c00h
int 21h

s:add ax,ax
  loop s
  ret 

code ends
end start

正确程序:
assume cs:code,ss:stack
stack segment
db 8 dup(0)
db 8 dup(0)

stack ends
code segment

start:
mov ax,stack
mov ss,ax
mov sp,16 //定义栈段

  mov ax,1000
  call s
  
  mov ax,4c00h
  int 21h
  
s:add ax,ax
 ret  

code ends
end start

mul乘法指令:
格式:
mul 寄存器
mul 内存单元
8位乘法 16位乘法
被乘数(默认) AL AX
乘数 8位寄存器或内存单元 16位寄存器或内存单元
结果 AX DX(高位)AX(低位)

mul bl===>(ax)=(al)(bl)
mul byte ptr ds:[0]===>(ax)=((al)
(ds)*16+0))

mul word ptr [bx+si+8]===>(ax)=((ax)*((ds)16+(bx)+(si)+8)
===>(dx)=((ax)
((ds)*16+(bx)+(si)+8)

1.计算:100*10==》100和10都小于255,可以做8位乘法
mov al,100
mov bl,10
mul bl
结果:(ax)=1000(03e8h)

2.计算100*1000===》10000大于255,所以必须使用16位的乘法
mov ax,100
mov bx,10000
mul bx
结果:(dx)=000fh,(ax)=4240h
即:F4240H=1000000

案例:根据提供的N,计算N的三次方
思考:
1.将参数N存储在什么地方
2.计算得到的数值,储存在什么地方
用寄存器传递参数
1.参数放到bx中,即(bx)=N
2.子程序中用多个mul指令计算N^3
3.将结果放到dx和ax中:(dx:ax)=N^3

要求:将data段中第一组数据的3次方,保存在后面一组dword单元中
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8//一个字
dd 0,0,0,0,0,0,0,0//双字4个字节
data ends
code segment

code segment
start: mov ax,data
mov ds,ax
mov si,0 //第一组的第一个元素
mov di,16//跳过第一组,指向第二组第一个元素

   ;循环处理
   mov cx,8//循环8次
   
  s:mov bx,[si]//把第一个元素拿出来给bx
   call cube //方法调用
   
   mov [di],ax//把结果低16位放di
   mov [di].2,dx//高位放到第二个字的高位
   add si,2//跳过0,1单元
   add di,4//跳过第二组第一个元素(双字4字节)
loop s//继续计算下一个  
   
  cube:
     mov ax,bx //求出BX的三次方返回
	 mul bx
	 mul bx
	 ret
  
  
  
   mov ax,4c00h
   int 21h

code ends
end start

寄存器比较稀缺,使用内存单元批量传递数据
方案:
1.将批量数据放到内存中,然后将他们所在的内存空间的首地址放在寄存器中,传递给需要的子程序
2.对于具有批量数据的返回记过,也可以用同样的方法

编程:将data段中的字符串转为大写
assume cs:code
data segment
db ‘conversation’
data ends
code segment
start:mov ax,data//得到系统随机分配的段地址
mov ds,ax
mov si,0
mov cx,12 //循环12次

	  call capital
	  
	  mov ax,4c00h
	  int 21h
	  
capital: and byte ptr [si],1101 1111b//运算转换为大写,结果放在[si]
		inc si
		loop capital
		ret

code ends
end start

案例:使用栈传递参数
原理:由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数
任务:计算(a-b)^3,a,b为word型数据
1.进入子程序前,参数a,b入栈
2.调用子程序,将使栈顶存放ip
3.结果:(dx:ax)=(a-b)^3

设a=3,b-1,计算(a-b)^3
mov ax,1
push ax
mov ax,3
push ax //此时栈中的数据为从下到上:1(a),3(b)
call difcube //调用子程序时,会将此行的下一行地址压入栈中

difcube:
push bp // 把bp原有的内容压入栈,防止本程序使用BP,导致原数据丢失,
//此时栈中的数据为从下到上:1(a),3(b),call的下一条指令地址,bp旧数据
mov bp,sp //将栈指针指向栈顶,即bp旧数据的位置
mov ax,[bp+4]//由于栈中的数据是2个字节,同时栈中的数据存放是高地址往低地址存放,即栈低的地址最高,所以此次+4才能取到a
sub ax,[bp+6]//将栈中的b取出,进行a-b放到ax中
mov bp,ax //A-B结果给bp
mul bp //dx:ax中放结果(a-b)*(a-b)
pop pb //将旧的bp从栈中还回去
ret 4 //===>当子程序返回时,栈中保存的call后边的一条指令的地址自动出栈,ret 4就是将指针指向开始的位置,因为现在a,b两个还没有出栈
//pop ip ;add sp,n即让栈指针指向栈低,恢复到栈的初始状态,当指针指向初始位置时,将栈内的a,b数据为无效数据。

总结:参数传递问题
1.通过寄存器,确定:资源少
2.通过内存单元
3.通过栈

子程序标准框架:
1.子程序中使用的寄存器入栈
2.子程序中使用的寄存器出栈(ret/retf)
解决方案:
在子程序的开始,将要用到的所有寄存器中的内容都保存起来,在子程序返回前再恢复

标志寄存器:按位起作用
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF

OF:Overflow 溢出 OV=1 NV=0
DF:Direction 方向 DN=1 UP=0
SF:Sigh 符号 NG=1 PL=0
ZF:zero 零值ZR=1 NZ=0
PF:parity 奇偶PE=1 PO=0
CF:carty 进位CY=1 NC=0

adc指令:带进位加法指令
格式:adc 操作对象1,操作对象2
功能:操作对象1=操作对象1+操作对象2+CF位
例:abc,ax,bx==->(ax)=(ax)+(bx)+CF

ADC指令做大数加法案例:
1.计算1E F000 1000H+20 1000 1EF0H,结果放ax(高16位),bx(次高16位),cx(低16位)中。
程序:CF需要提前置0
mov ax,1
sub ax,ax;//cf置0

mov ax,001EH
MOV BX,0FEDH
mov cx,1000H
ADD CX,1EFOH//第一次低位相加,若有进位就把cf置1
ADC BX,1000H//高位相加
ADC AX,0020H

128位的数据相加
671E 0F04 54F6 887A 0A8E 78E6 0A8F 0A452

  • 0452 58F5 78E6 0A8E 8B7A 54F6 0EF04 0571

功能分析:128位需要8个字单元,由低地址单元到高地址单元,依次存放由低到高的各个字
ds:si指向存储第一个数的内存空间
ds:di指向存储第二个数的内存空间
运算结果存储在第一个数的存储空间中

data segment
dw 0A452,0A8F,78E6,0A8E,887A,54F6,0F04,671E
dw 0571,0FE04,54F6,8B7A,0A8E,78E6,58F5,0452
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
call add128

  mov ax,4c00h
  int 21h

add128:
push ax
push cx
push si
push di

   sub ax,ax
   
 s:mov ax,[si]
   adc ax,[di]
   mov [si],ax
   inc si
   inc si
   inc di
   inc di
   
   loop s
   
   pop di
   pop si
   pop cx
   pop ax
   
   ret

code ends
end start
注意:sub ax,ax不能替换成mov ax,0 ,其目的是将CF置为0,只有算术运算后才会影响次标志位
两个inc di不能替换成add di,2,虽然其结果相同,但是inc的结果不会参与CF标志位的变化,但是add会参与

sbb指令:带借位减法指令
格式:sbb 操作数1,操作数2
功能:
操作对象1=操作对象1-操作对象2
与sub的区别,利用CF位上记录的借位值
例如:
sbb ax,bx===>(ax)=(ax)-(bx)-CF

案例:对任意大的数做减法运算

计算003E 1000H-0020 2000H
程序:
mov bx,1000H
mov ax,003EH
sub bx,2000H
sbb ax,0020H

cmp 无符号数比较与标志位取值
cmp 操作对象,操作对象2
功能:操作对象1-操作对象2=标志位寄存器的值变化
cmp ax,bx
(ax)=(bx) ZF=1
(ax)≠(bx) ZF=0
(ax)>(bx) CF=0且ZF=0
(ax)<(bx) CF=1
(ax)≥(bx) CF=0
(ax)≤(bx) CF=1或 ZF=1
比较指令的设计思路:通过做减法运算影响标志寄存器,标志寄存器的相关位的取值,体现比较的结果

JXX 标号转移指令,根据标志位进行判断,符号就转移

根据单个
je/jz 相等/结果为0 ZF=1
jne/jnz 不等/结果不为0 ZF=0
js 结果为负 SF=1
JNS 结果非负 SF=0
JO 结果溢出 OF=1
JNO 结果非溢出 OF=0
JP 奇偶位为1 PF=1
JNP 奇偶位不为1 PF=0
JB/Jnae/jc 低于/不高于等于/有借位 CF=1
JNB/jae/jnc 不低于/高于等于/无错位 CF=0

注:j=jump e=equal n=not b=below a=above l=less g=greater s=sign c=carry p=parity o=overflow z=zero

根据无符号数比较结果进行转移的指令
jb/jnae/jc 低于则转移 CF=1
jnb/jnae/jnc 低于则转移 CF=0
jna/jbe 不高于则转移 CF=1或ZF=1
ja/jbe 不高于则转移 CF=1且ZF=0

根据有符号数比较结果进行转移的指令
jl/jnge 小于则转移 SF=1且OF=0
jnl/jge 不小于转移 SF=0且OF=0
jle/jng 小于等于则转移 SF=0或OF=1
jnle/jg 不小于等于则转移 SF=1且OF=1

运用案例:
1.如果(ah)=(bh),则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)
cmp ah,bh
je s
add ah,bh
jmp short ok
s:add ah,ah
ok:ret

2.如果ax=0,则(ax)=(ax)+1

;ax获取值
add ax,0//目的是:当[ax]=0,结果也为0,ZF=1,否则结果不为0,ZF=0
jnz s //AZ=0时跳转
inc ax
s:…

条件转移指令案例:
1.双分枝结构
cmp ah,bh
je s //相等转到S
add ah,bh
jmp short ok
s:add ah ,ah
ok:ret

if(a==b){
a=a+b;
}eles{
a=a+b}

2.给出数据,求字节个数
data segment
db 8,11,8,1,8,5,63,38
data ends

a.求数值为8的个数
b.大于8的字节个数
c.小于8的字节个数

编程思路:
a.求数值为8的个数
初始设置(ax)=0,然后用循环依次比较每个字节的值,找到一个和8相等的数就将ax+1.
code segment
start:mov ax,data
mov ds,ax
mov bx,0 //第一个元素的偏移量
mov ax,0 //计数
mov cx,8 //循环次数

	s:cmp byte ptr [bx],8//用8进行比对
	  jne next //不相同就跳转,不相同就继续
	  inc ax// 相同就让ax+1
	  
next:inc bx //地址+1
	 loop s
	 
	 
	mov ax,4c00h
	int 21h

code ends

b.大于8的字节个数
code segment
start: mov ax,data
mov ds,ax
mov bx,0
mov ax,0

	s:cmp byte str [bx],8
	  jna next
	  inc ax
	  
  next:inc bx
       loop s
	   
mov ax,4c00h
int 21h

将data段中的第一个字符串复制到它后面的空间中
data segment
db ‘welcome to masm!’
db 16 dup(0)

data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8//16个字节,使用双字节进行存储,使用8个循环

	 s:mov ax,[si]
	   mov [di],ax
	   add si,2
	   add di,2
	   loop s
	  
	  mov ax,4c00h
	  int 21h

code ends
end start

以上可以使用DF标志位进行简化代码
DF-方向标志位
功能:
在串处理指令中,控制每次操作后si,di的增减
DF=0,每次操作后si,di递增
DF=1,每次操作后si,di递减

串传送指令:
movsb 按照一个字节8位进行传送
功能:
1.((es)*16+(di))=((ds)*16+(si))
2.如果DF=0,则(si)=(si)+1
(di)=(di)+1
如果DF=1,则(si)=(si)-1
(di)=(di)-1

movsw 按照以一个字16位进行传送
1.((es)*16+(di))=((ds)*16+(si))
2.如果DF=0,则(si)=(si)+2
(di)=(di)+2
如果DF=1,则(si)=(si)-2
(di)=(di)-2
对DF位进行设置的指令
cld指令,将DF位设为0 (clear)
std指令,将DF位设为1 (setup)
优化:将data段中的第一个字符串复制到它后面的空间中
data segment
db ‘welcome to masm!’
db 16 dup(0)

data ends
code segment
start: mov ax,data
mov ds,ax
mov es,ax//设置副地址段,指向ds的同一片空间
mov si,0
mov di,16

	   cld //非常重要,用于设置DF=0,让si和di在使用movsb时自增
	   
	    mov cx,16//16个字节,使用一个字节进行存储,使用16个循环
		
	 s:movsb//-->代替了这几个操作:mov ax,[si];mov [di],ax; add si,2;add di,2
	   		   
	   loop s
	  
	  mov ax,4c00h
	  int 21h

code ends
end start

rep指令重复指令.
rep movsb===>s:movsb;loop s
rep movsw===>s:movsw;loop s
优化:将data段中的第一个字符串复制到它后面的空间中
data segment
db ‘welcome to masm!’
db 16 dup(0)

data ends
code segment
start: mov ax,data
mov ds,ax
mov es,ax//设置副加段,指向ds的同一片空间
mov si,0
mov di,16

	   cld //非常重要,用于设置DF=0,让si和di在使用movsb时自增
	   
	    mov cx,16//16个字节,使用一个字节进行存储,使用16个循环
		
		rep movsb//代替如下两行
		
	 ;s:movsb//-->代替了这几个操作:mov ax,[si];mov [di],ax; add si,2;add di,2
	   		   
	   ;loop s
	  
	  mov ax,4c00h
	  int 21h

code ends
end start

运用实例:
用串传送指令,将F000段中的最后16个字符复制到data段中
data segment
db 16 dup(0)
data ends

code segment
start:mov ax,0F000H
mov ds,ax //把数据段赋值给段地址寄存器
mov si,0FFFFH //源寄存器保存为F0000段中的最后一个地址,指向最后一个元素
mov ax,data //获取需要存储的首地址
mov es,ax //首地址给副段寄存器
mov di,15 //指向源地址的最后一个元素
mov cx,16 //循环16次

  std//将DF=1,按照si,di减1的操作,
  
  rep movsb按照字节循环16次
  
  mov ax,4c00h
  int 21h

code ends
end start

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书分为三部分。第一部分是基础部分,以8086/8088为背景,以DOS和PC兼容机为软硬件平台,以MASM和TASM为汇编器,介绍汇编语言的有关概念,讲解汇编语言程序设计技术。第二部分是提高部分,以80386为背景,以新一代微处理器Pentium为目标,细致和通俗地介绍了保护方式下的有关概念,系统和详细地讲解了保护方式下的编程技术,真实和生动地展示了保护方式下的编程细节。第三部分是上机实验指导。 本书的第一部分适合初学者,可作为学习汇编语言程序设计的教材。本书的第二部分适合已基本掌握8086/8088汇编语言的程序员,可作为学习保护方式编程技术的教材或参考书,也可作为其他人员了解高档微处理器和保护方式编程技术的参考书,还可作为程序员透彻地了解Windows程序设计技术的参考书。 第一部分 基础部分 第1章 绪论 1.1 汇编语言概述 1.1.1 汇编语言 1.1.2 汇编语言的特点 1.1.3 恰当地使用汇编语言 1.2 数据的表示和类型 1.2.1 数值数据的表示 1.2.2 非数值数据的表示 1.2.3 基本数据类型 1.3 Intel系列CPU简介 1.3.1 8位微处理器 1.3.2 16位微处理器 1.3.3 32位微处理器 1.3.4 Pentium和Pentium Pro 1.4 习题 第2章 8086/8088寻址方式和指令系统 2.1 8086/8088寄存器组 2.1.1 8086/8088 CPU寄存器组 2.1.2 标志寄存器 2.2 存储器分段和地址的形成 2.2.1 存储单元的地址和内容 2.2.2 存储器的分段 2.2.3 物理地址的形成 2.2.4 段寄存器的引用 2.3 8086/8088的寻址方式 2.3.1 立即寻址方式 2.3.2 寄存器寻址方式 2.3.3 直接寻址方式 2.3.4 寄存器间接寻址方式 2.3.5 寄存器相对寻址方式 2.3.6 基址加变址寻址方式 2.3.7 相对基址加变址寻址方式 2.4 8086/8088指令系统 2.4.1 指令集说明 2.4.2 数据传送指令 2.4.3 堆栈操作指令 2.4.4 标志操作指令 2.4.5 加减运算指令 2.4.6 乘除运算指令 2.4.7 逻辑运算和移位指令 2.4.8 转移指令 2.5 习题 第3章 汇编语言及其程序设计初步 3.1 汇编语言的语句 3.1.1 语句的种类和格式 3.1.2 数值表达式 3.1.3 地址表达式 3.2 变量和标号 3.2.1 数据定义语句 3.2.2 变量和标号 3.3 常用伪指令语句和源程序组织 3.3.1 符号定义语句 3.3.2 段定义语句 3.3.3 汇编语言源程序的组织 3.4 顺序程序设计 3.4.1 顺序程序举例 3.4.2 简单查表法代码转换 3.4.3 查表法求函数值 3.5 分支程序设计 3.5.1 分支程序举例 3.5.2 利用地址表实现多向分支 3.6 循环程序设计 3.6.1 循环程序举例 3.6.2 多重循环程序举例 3.7 习题 第4章 子程序设计和DOS功能调用 4.1 子程序设计 4.1.1 过程调用和返回指令 4.1.2 过程定义语句 4.1.3 子程序举例 4.1.4 子程序说明信息 4.1.5 寄存器的保护与恢复 4.2 主程序与子程序间的参数传递 4.2.1 利用寄存器传递参数 4.2.2 利用约定存储单元传递参数 4.2.3 利用堆栈传递参数 4.2.4 利用CALL后续区传递参数 4.3 DOS功能调用及应用 4.3.1 DOS功能调用概述 4.3.2 基本I/O功能调用 4.3.3 应用举例 4.4 磁盘文件管理及应用 4.4.1 DOS磁盘文件管理功能调用 4.4.2 应用举例 4.5 子程序的递归和重入 4.5.1 递归子程序 4.5.2 可重入子程序 4.6 习题 第5章 输入输出与断 5.1输 入和输出的基本概念 5.1.1 I/O端口地址和I/O指令 5.1.2 数据传送方式 5.1.3 存取RT/CMOS RAM 5.2 查询方式传送数据 5.2.1 查询传送方式 5.2.2 读实时钟 5.2.3 查询方式打印输出 5.3 断 5.3.1 断和断传送方式 5.3.2 断向量表 5.3.3 断响应过程 5.3.4 外部断 5.3.5 内部断 5.3.6 断优先级和断嵌套 5.3.7 断处理程序的设计 5.4 基本输入输出系统BIOS 5.4.1 基本输入输出系统BIOS概述 5.4.2 键盘输入 5.4.3 显示输出 5.4.4 打印输出 5.5 软断处理程序举例 5.5.1 打印I/O程序 5.5.2 时钟显示程序 5.6 习题 第6章 简单应用程序的设计 6.1 字符串处理 6.1.1 字符串操作指令 6.1.2 重复前缀 6.1.3 字符串操作举例 6.2 十进制数算术运算调整指令及应用 6.2.1 组合BCD码的算术运算调整指令 6.2.2 未组合BCD码的算术运算调整指令 6.2.3 应用举例 6.3 DOS程序段前缀和特殊情况处理程序 6.3.1 DOS程序段前缀PSP 6.3.2 对Ctrl+C键和Ctrl+Break键的处理 6.4 TSR程序设计举例 6.4.1 驻留的时钟显示程序 6.4.2 热键激活的TSR程序 6.5 习题 第7章 高级汇编语言技术 7.1 结构和记录 7.1.1 结构 7.1.2 记录 7.2 宏 7.2.1 宏指令的定义和使用 7.2.2 宏指令的用途 7.2.3 宏指令参数的使用 7.2.4 特殊的宏运算符 7.2.5 宏与子程序的区别 7.2.6 与宏有关的伪指令 7.2.7 宏定义的嵌套 7.3 重复汇编 7.3.1 伪指令REPT 7.3.2 伪指令IRP 7.3.3 伪指令IRPC 7.4 条件汇编 7.4.1 条件汇编伪指令 7.4.2 条件汇编与宏结合 7.5 源程序的结合 7.5.1 源程序的结合 7.5.2 宏库的使用 7.6 习题 第8章 模块化程序设计技术 8.1 段的完整定义 8.1.1 完整的段定义 8.1.2 关于堆栈段的说明 8.1.3 段组的说明和使用 8.2 段的简化定义 8.2.1 存储模型说明伪指令 8.2.2 简化的段定义伪指令 8.2.3 存储模型说明伪指令的隐含动作 8.3 模块间的通信 8.3.1 伪指令PUBLIC和伪指令EXTRN 8.3.2 模块间的转移 8.3.3 模块间的信息传递 8.4 子程序库 8.4.1 子程序库 8.4.2 建立子程序库 8.4.3 使用举例 8.5 编写供Turbo C调用的函数 8.5.1 汇编格式的编译结果 8.5.2 汇编模块应该遵守的约定 8.5.3 参数传递和寄存器保护 8.5.4 举例 8.6 习题 第二部分 提高部分 第9章 80386程序设计基础 9.1 80386寄存器 9.1.1 通用寄存器 9.1.2 段寄存器 9.1.3 指令指针和标志寄存器 9.2 80386存储器寻址 9.2.1 存储器寻址基本概念 9.2.2 灵活的存储器寻址方式 9.2.3 支持各种数据结构 9.3 80386指令集 9.3.1 数据传送指令 9.3.2 算术运算指令 9.3.3 逻辑运算和移位指令 9.3.4 控制转移指令 9.3.5 串操作指令 9.3.6 高级语言支持指令 9.3.7 条件字节设置指令 9.3.8 位操作指令 9.3.9 处理器控制指令 9.4 实方式下的程序设计 9.4.1 说明 9.4.2 实例 9.5 习题 第10章 保护方式下的80386及其编程 10.1 保护方式简述 10.1.1 存储管理机制 10.1.2 保护机制 10.2 分段管理机制 10.2.1 段定义和虚拟地址到线性地址转换 10.2.2 存储段描述符 10 2.3 全局和局部描述符表 10.2.4 段选择子 10.2.5 段描述符高速缓冲寄存器 10.3 80386控制寄存器和系统地址寄存器 10.3.1 控制寄存器 10 3.2 系统地址寄存器 10.4 实方式与保护方式切换实例 10.4.1 演示实方式和保护方式切换的实例(实例一) 10.4.2 演示32位代码段和16位代码段切换的实例(实例二) 10.5 任务状态段和控制门 10.5.1 系统段描述符 10.5.2 门描述符 10.5.3 任务状态段 10.6 控制转移 10.6.1 任务内无特权级变换的转移 10.6.2 演示任务内无特权级变换转移的实例(实例三) 10.6.3 任务内不同特权级的变换 10.6.4 演示任务内特权级变换的实例(实例四) 10.6.5 任务切换 10.6.6 演示任务切换的实例(实例五) 10.7 80386的断和异常 10.7.1 80386的断和异常 10.7.2 异常类型 10.7.3 断和异常的转移方法 10.7.4 演示断处理的实例(实例六) 10.7.5 演示异常处理的实例(实例七) 10.7.6 各种转移途径小结 10.8 操作系统类指令 10.8.1 实方式和任何特权级下可执行的指令 10.8.2 实方式及特权级0下可执行的指令 10 8.3 只能在保护方式下执行的指令 10.8.4 显示关键寄存器内容的实例(实例八) 10.8.5 特权指令 10.9 输入/输出保护 10.9.1 输入/输出保护 10.9.2 重要标志保护 10.9.3 演示输入/输出保护的实例(实例九) 10.10 分页管理机制 10.10.1 存储器分页管理机制 10.10.2 线性地址到物理地址的转换 10.10.3 页级保护和虚拟存储器支持 10.10.4 页异常 10.10.5 演示分页机制的实例(实例十) 10.11 虚拟8086方式 10.11.1 V86方式 10.11.2 进入和离开V86方式 10.11.3 演示进入和离开V86方式的实例(实例十一) 10.11.4 V86方式下的敏感指令 10.12 习题 第11章 80486及Pentium程序设计基础 11.1 80486程序设计基础 11.1.1 寄存器 11.1.2 指令系统 11.1.3 片上超高速缓存 11.2 80486对调试的支持 11 2.1 调试寄存器 11.2.2 演示调试故障/陷阶的实例 11.3 Pentium程序设计基础 11.3.1 寄存器 11.3.2 指令系统 11.3.3 处理器的识别 11.3.4 片上超高速缓存 11.4 基于Pentium的程序优化技术 11.4.1 流水线优化技术 11.4.2 分支优化技术 11.4.3 超高速缓存代化技术 11.5 习题 第三部分 上机实验指导 第12章 实验指导 12.1 实验的一般步骤 12.2 汇编器和连接器的使用 12.2.1 MASM的使用 12.2.2 LINK的使用 12.2.3 TASM的使用 12.2.4 TLINK的使用 12.3 调试器DEBUG的使用 12.3.1 启动和退出DEBUG 12.3.2 命令一览 12.3.3 利用DEBUG调试程序 12.4 Turbo Debugger的使用 12.4.1 启动和退出TD 12.4.2 利用TD调试汇编程序 参考文献 附录 Pentium指令与标志参考表

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值