学习汇编前你应该知道的知识4 汇编程序
4.1 汇编程序框架
data SEGMENT '数据段,编程者可以把数据都放到这个段里
....数据部分
'数据格式是: 标识符 db/dw 数据。
data ENDS'数据段结束处。
edata SEGMENT '附加数据段,编程者可以把数据都放到这个段里
....附加数据部分
edata ENDS'附加数据段结束处。
code SEGMENT'代码段,实际的程序都是放这个段里。
start:MOV AX,data '前面的start表示一个标识位,后面用到该位,如果用不到,就可以不加
code ENDS'代码段结束。
END start'整个程序结束,并且程序执行时由start那个位置开始执行。
上面就是一个程序的框架结构。在这个结构中,有三个段,DS,ES,CS。这三个段分别存数据,附加数据,代码段。
4.2 编写我们的Hello,world思路。
开始编写我们的第一个程序。
程序要求:显示一个“Hello,Mr.286.”怎么样?
思路:
1 要显示一个字符串,根据前面我让你们记的七八个指令够吗?答案是:不仅够,而且还用不完。
首先定义一下总可以吧。
hellostr db 'Hello,Mr.286.$'
最后的$不要忘了。
2 首先要考虑的问题就是找中断,找到合适的中断,该中断就能帮我们完成这个显示任务。我找到(在哪找到的,怎么找到的,别问我,到网上或书上都能找到):
-------------------------------------------
中断INT 21H功能09H
功能描述: 输出一个字符串到标准输出设备上。如果输出操作被重定向,那么,将无法判断磁盘已满
入口参数: AH=09H
DS:DX=待输出字符的地址
说明:待显示的字符串以’$’作为其结束标志
出口参数: 无
-------------------------------------------
由上面看到,我们所需要作的就是把DS指向数据段,DX指向字符串的地址,AH等于9H,调用21h中断。
mov ds,数据段地址
lea dx,hellostr 'hellostr已在前面1中定义了。
mov ah,9h
int 21h。
由于只要在调用int 21h之前把准备的东西准备齐就行了,所以int 21h前面三行的顺序并不重要。
3 退出程序,运行完总要退出呀。再查中断手册
--------------------------------------------
中断INT 21H功能4CH
功能描述: 终止程序的执行,并可返回一个代码
入口参数: AH=4CH
AL=返回的代码
出口参数: 无
--------------------------------------------
mov ah,4Ch
mov al,0
int 21h
或
mov ax,4c00h
int 21h
这里需要说明的是返回代码有什么用,返回给谁?返回给操作系统,因为是操作系统DOS调用的这个程序,这个返回值可以通过批处理中的errorlevel得到,这里不多说明,实际上操作系统很少处理这一值,因此al你随便写什么值影响都不大。
4.3 程序实现
data SEGMENT
msg DB 'Hello, Mr.286.$'
data ENDS
code SEGMENT
start:MOV AX,data
code ENDS
END start
4.4 编译运行。
把上面程序保存成hello286.asm后,就可以编译运行了。进入DOS,进入汇编目录,如果还没下载,到前面找下载地址。
=================================================
E:\Download\Masm>masm hello286.asm
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987.
Object filename [hello286.OBJ]:
Source listing
Cross-reference [NUL.CRF]:
说明:上面连续三个回车,表示我要的都是默认值。下面是零个警告,零个严重错误,(当然了,我的程序还敢错吗?)
E:\Download\Masm>link hello286
Microsoft (R) Overlay Linker
Copyright (C) Microsoft Corp 1983-1987.
Run File [HELLO286.EXE]:
List File [NUL.MAP]:
Libraries [.LIB]:
LINK : warning L4021: no stack segment
说明:三个回车仍要默认,后面有个警告,没有栈段,这个没关系,没有的话系统会自动给一个。
E:\Download\Masm>hello286
Hello, Mr.286.
说明:运行成功。
E:\Download\Masm>
4.4 深度思考
4.4.1 是不是数据必须放数据段,代码必段放代码段呢?
答,代码必段放代码段,否则你怎么执行呀?但数据也可以放到代码段,只是程序要作修改。
code SEGMENT
start:MOV AX,data
code ENDS
END start
编译后仍然可以。
4.4.2 我编的程序在内存中是什么样子的呢?
------------------------------------------------------------------------
E:\Download\Masm>debug hello286.exe
-u
1420:0000 B81F14
1420:0003 8ED8
1420:0005 8D160000
1420:0009 B409
1420:000B CD21
1420:000D B8004C
1420:0010 CD21
1420:0012 FF362421
1420:0016 E87763
1420:0019 83C406
1420:001C FF362421
-d 141f:0000 L20
141F:0000
141F:0010
-q
E:\Download\Masm>
------------------------------------------------------------------------------
上面是什么呀?还记得前面说的吗?
1420:0000 B81F14
段址:偏址 机器语言
1420:0012后面的是垃圾数据,不用管它,把上面程序与源程序作一个比较,看有什么不用,差别在于把标号语言转成实际地址了。
程序前两行一执行,数据段地址就变成了141f,而那个字符串偏移地址在0000,由(LEA
所以刚才的程序在内存中就变成了:
141f:0000 Hello, Mr.286.$
1420:0000 B81F14
1420:0003 8ED8
1420:0005 8D160000
1420:0009 B409
1420:000B CD21
1420:000D B8004C
1420:0010 CD21
刚刚学习了8086/8088汇编语言,发现寻址方式非常重要,于是做了一个小总结,请各位笑纳。
概念:
现在就重点讨论寻址方式,说白了也就是cpu怎么样从指令中得到操作数的问题。另外再强调一点操作数还分种类:
1)数据操作数:全都是在指令当中参加操作的数据。
2)转移地址操作数:在指令当中不是参加运算或被处理的数据了,而是转移地址。
还可以按照下面分类方式:
1)源操作数src
2)目的操作数dst
源操作数都是指令当中的第2个操作数,在执行完指令后操作数不变。而目的操作数是指令当中的第1个操作数,在执行完操作指令后被新的数据替代。
我们就围绕这几种操作数,也就是操作数所在的位置展开讨论。
先说数据操作数,它分3大类共7种。
1)立即数寻址方式:是针对立即操作数的寻址方式。在指令当中直接给出,它根本就不用寻址。
例1:mov ax,1234h
在这里1234h和5678h都是立即操作数,在指令当中直接给出。
2)寄存器寻址方式:是针对寄存器操作数的寻址方式,它在寄存器中我们就用这中方式来找到它。
例2:mov
在这里ax,bx,ds都算是寄存器寻址,例1中的ax也是寄存器寻址方式。
3)存储器寻址方式:针对在内存中的数据(存储器操作数)都用这种方式来寻找,一共有5种(这是我自己的说法,便于记忆)。
不得不提及以下的概念:由于8086/8088的字长是16bit,能够直接寻址2的16次方也就是64kb,而地址总线是20bit,能够直接寻址2的20次方也就是1M空间,所以把内存分为若干个段,每个段最小16byte(被称为小节),最大64kb,它们之间可以相互重叠,这样一来内存就被分成以16byte为单元的64k小节,cpu就以1小节为单位寻址:在段寄存器中给出段地址(16bit),在指令当中给出段内偏移地址(16bit),然后把段地址左移4bit再与偏移地址求和就得到数据在内存当中的实际物理地址了,因而可以找到数据。
1.存储器直接寻址方式:在指令当中以
例3:mov
在这里[1234h]和VALUE就是在指令中直接给出的数据所在内存段的偏移地址(16bit)。
VALUE是符号地址,是用伪指令来定义的,它代表一个在内存中的数据(也就是它的名字)。es:是段前缀符,用来指出段地址,在这之前应该将段地址添入段中,本例中是es,默认是ds,也就是不需给出。应该注意
2.寄存器间接寻址:不是在指令中直接给出数据在内存中的偏移地址,而是把偏移地址放到了寄存器中。
例4:mov
这里[bx]就是寄存器间接寻址,bx中应方入段内偏移地址。其中:若使用bx,si,di默认段地址为ds,若使用bp则默认段地址为ss,并且允许段跨越,也就是加段前缀符。注意:在寄存器两边加
3.寄存器间接相对寻址:偏移地址是bx,bp,si,di中的内容再与一个8bit或16bit 的位移量之和。
例5:mov
在这里[bx]+12h,[si]+5678h,[bp]+1234h都是寄存器间接相对寻址。12h是8bit位移量,1234h和5678h是16bit位移量。若使用bx,si,di则默认段寄存器是ds,若使用bp则默认段寄存器是ss,并且允许段跨越。
4.基址变址寻址:偏移地址是一个基址寄存器和一个变址寄存器内容的和,既:bx或bp中的一个与si或di中的一个求和而得到。
例6:mov
上面[bx+si]和[bp+di]都是基址变址寻址。若使用bx做基址寄存器则默认段地址为ds,若使用bp为基址寄存器则默认段为ss,允许段跨越。
5.基址变址相对寻址:偏移量是一个基址寄存器一个变址寄存器只和再与一个8bit或一个16bit位移量只和得到。
例7:mov
[bx+si]+12h和[bp+di]+1234h就是基址变址相对寻址。若使用bx做基址寄存器则默认段是ds,若使用bp做基址寄存器则默认段为ss。允许段跨越。
下面是转移地址操作数的寻址方式:
1)段内直接转移
例8:jmp
其中,short是段内短转移的操作符,用以指出是转移到当前位置前后不超过±127字节的地方。而NEW_ADDR是要转移到的符号地址,它的位置应该在当前ip指针所在偏移地址不超过
±127的地方。否则语法出错。
例9:jmp
其中near ptr是段内近转移的操作符,用以指出转移到当前位置前后不超过±32767的地方。NEAR_NEW_ADDR是要转移到的符号地址。
2)段内间接转移:cs的内容不变,而ip的内容放在寄存器中或者存储器中给出。
例10:jmp
这种寻址方式是在寄存器或存储器中找到要转移到的地址,而地址是16bit的,因而寄存器必须为16bit,如:bx,我们用word
3)段间直接寻址:cs和ip的内容全都变化,由指令当中直接给出要转移到的某一个段内的某一个偏移地址处。
例11:jmp
1234h送入cs中作为新的段地址,5678h送入ip中作为新的偏移地址。far
4)段间间接寻址:cs和ip的内容全变化,由指令当中给出的一个4字节连续存储单元,其中低2字节送入ip作为偏移地址,高2字节送入cs作为段地址。
例12:jmp
dword
另外作为特殊的寻址方式还有三种:I/O寻址,串寻址,隐含寻址。它们都分别针对I/O指令,串操作指令以及无操作数的指令,而且都比较简单,读者自行总结。
到此为止说明了8086/8088cpu中的所有寻址方式,我这里只是个总结,具体的细节还要大家自己钻研课本,才能理解。
写的有些仓促可能有些遗漏或错误,还请谅解。
-------------------------------------------------------------------------------------
一定要记住寻址方式特别重要,这是我“毕生” 的总结,请笑纳,笑纳。