更新日志
此笔记将持续更新。 一直坚持到我期末考完(是的,考完我就再也不看这个**汇编了)
该笔记的更新进度就是笔者的学习的进程,因此我认为这份笔记是比较有参考借鉴意义的。笔者会把自己的困惑和一些独特的感悟也写进来,希望能对大家的学习有所帮助和启发。内容很基础,所以学习要扎实。
-----2023.12.19
纯手敲,真的很累的,如果对你有一点帮助,请点一个★收藏吧!
目录
目录
一、DEBUG的常用命令
(1)汇编命令 A
含义:assemble “汇编”(结合英文单词的含义可以帮助你更好地记住这个命令的功能),将后面的汇编指令转换为机械码并写入指定的位置(中括号的内存地址)。注意,若缺省起始地址,则从当前 CS:100 地址开始存放。
格式:A [起始地址]
举个例子:
A [1000:20]
MOV AX, 5
在这个例子中,我们将汇编语言MOV AX, 5以机器码的形式存在了地址1000:20中。
(2)反汇编命令 U
含义:Unassemble 译为“反汇编”。汇编命令A是将汇编命令转译为机器语言,因此反汇编语言U就是将机器语言转译为汇编语言。若缺省结束地址则默认为32个字节的目标代码。若缺省起始地址,则从默认地址(当前的CS:IP)开始或者从上一次反汇编结束的地方继续。一般我们会在debug的开头使用,以查看程序的代码是否正常。
格式:U [起始地址][结束地址]‘
举个例子:
U ; 从默认地址开始反汇编
U DS:0000 ;从DS:0000开始反汇编
界面说明:
界面左边:“076C:0000” 是以逻辑地址形式显示每条汇编指令所在存储器的首单元地址。它可以帮助你定位你的指令的位置,方便后面设置断点或使用其他指令(如:G指令)。
界面中间:“B86A07” 是每条汇编指令对应的机器指令代码(这个好像不太重要,一般不看)。
界面右边:“AX, 076A” 是处理了符号之后的汇编指令。
(3)显示、修改寄存器命令R
含义:Registry译为“寄存器”。显示该寄存器的内容并可以进行修改。若缺省寄存器名,则显示当前所有寄存器的内容以及值,但是不可以修改。注意:R命令只能显示、修改16位寄存器,对于标志寄存器(例如进位标志CF,零标志ZF和符号标志SF等)只能通过执行指令的方式修改。
格式:R [寄存器名]
举个例子:
R ;显示所有寄存器内容
R AX ; 查看AX寄存器中的内容,并可以修改
界面说明:
中间两行显示13个寄存器的内容,8个符号分表对应表示OF, DF, IF, SF, ZF, AF, PF, CF标志位的状态。
最后一行显示下一条将要执行的指令,若指令中有存储单元寻址方式的操作数,则指令后显示该操作数的地址和数值。
(4)显示存储单元命令 D
含义:Display 译为“显示”。该命令可以显示存储单元的内容,以帮助我们查看内存中的数据。
格式:D [起始地址][结束地址]
注意:若缺省起始地址,则从默认地址开始查看。若缺省结束地址,则默认显示128个单元的内容。
举个例子:
D ; 从默认地址开始查看
D 1000:0 ; 从指定单元1000:0开始查看,默认长度
D DS:0 ; 从指定单元查看DS中的数据
D 0 5 ; 查看DS段中0-5单元中的6个数据
界面说明:
界面左边:“075A:0100”以逻辑地址形式显示存储单元地址
界面中间:存储单元中的实际存放内容(的16进制),每一行显示16个单元,每个单元存放着一个字数据
界面右边:存储单元中值对应的ASCII码值;若是不可显示的ASCII值则显示“.”
(5)运行命令G
含义:GO,译为“继续”。从指定位置开始执行,到断点处结束。若缺省起始地址,则默认从CS:IP开始执行一条指令;若缺省断点,则直接运行到程序结束。
格式:G[起始地址][断点地址]
注意:断点地址必须是某条指令的起始地址,否则查看到的程序可能会出错
举个例子:
G ; 从当前位置连续执行程序到结束
G=0 ; 从0地址执行到结束
G 14 ; 从当前位置执行程序到14H,中断
(6)跟踪命令T
含义:Trace,译为“跟踪”。该命令用于执行程序的单步跟踪,从指定地址开始执行条数,若缺省条数则默认执行一条指令;如缺省起始地址,则默认从CS:IP开始执行。
格式:T[=起始地址][指令条数]
举个例子:
T ; 从当前位置单步执行1条指令
T 3 ; 从当前位置单步执行3条指令
T=4 2 ; 从4地址单步执行2条指令
界面说明:
界面上面两行同R命令
界面最后一行显示下一条命令的汇编指令
(7)退出命令Q
Quit,退出debug环境,返回操作系统
二、8086系统指令
我们学习系统指令需要掌握多个方面的知识点,这样可以帮助我们更好地理解指令更好的方法是一边实操编译一边学习,在一遍遍debug中你就逐渐掌握了,所以请打开你的记事本/NotePad++和DosBOX。
我们需要知道指令的操作数的个数,每个操作数可使用的寻址的方式(能否用立即数等),这个指令的目的和结果,这个指令执行时标志位是否参与了,执行后标志位是否改变了,是否需要预设参数,是否有隐含的寄存器......(是不是头大了)
1. 数据传送类指令
这类指令不影响标志位的状态
(1)传送指令 MOV
MOV <目的操作数>, <源操作数>
使用源操作数的值为目的操作数赋值,该指令执行完目的操作数的值和源操作数一样。
注意:源操作数不变,目的操作数改变, 类比C++里的 “=” 赋值符号
举个例子:
MOV AX, 2 ; 立即数
MOV AX, BX ; 寄存器的值
MOV AX, [BX] ; 寄存器的值代表的地址
需要注意的点:
① 目的操作数不可以以立即数作为寻址方式。
错误示范:MOV 1000H, AX
② CS不可以用作目的操作数,因为CS用于存储当前指令的起始地址。
错误示范:MOV CS, AX
③ IP不能作为操作数,因为IP是用于存储当前指令的偏移地址。
错误示范:MOV IP, AX 和 MOV AX, IP
④ 立即数不能直接为段寄存器赋值(CS\DS\SS\ES)
错误示范: MOV DS, 5000H
⑤ 两个操作数不能同时为段寄存器(CS\DS\SS\ES)
错误示范: MOV DS, CS
⑥ 两个操作数不能同时为存储器寻址方式
错误示范:MOV [SI], [DI]
⑦ 两个操作数必须类型匹配
错误示范:MOV [BX], 1 (俩操作数类型不确定,这个时候就需要ptr出场了)
错误示范:MOV AX, BL (两个操作数类型不匹配,AX是16位,BL是8位)
错误示范:MOV AL,3824H (不能用16位数据对8位寄存器赋值,8位寄存器最多存255H)
⑧ 立即数不可以作为目的操作数
错误示范:MOV 35H, AL
⑨ 两个操作数不能同时为存储器单元
错误示范: MOV X, Y
⑩ 寻址方式错误:
错误示范: MOV [AX], BX (AX不能作为间址寄存器)
(2)交换指令XCHG
exchange “交换”, 将指定的两个操作数的值相交换。需要注意的是操作数不可以是段寄存器。
格式:XCHG OPRD1, OPRD2
例如:
XCHG AH, AL ; 将AX寄存器的高8位和低8位互换值
(3)地址传送指令LEA
Load Effective Address的缩写,意思是载入有效地址。顾名思义,LEA就是将源操作数的有效地址送给目的寄存器。简而言之就是传地址用的。
区分一下LEA和MOV:
假设数据段定义如下:
DATA SEGMENT
TABLE DW 0040H
DW 3000H
DATA ENDS
那么:
MOV BX, TABLE ------(BX) = 0040H
LEA BX, TABLE ------(BX) = 0000H 因为TABLE偏移地址是0000H
(4)其他地址传送指令LDS/LES
LDS和LES,Load DS/ES 译为加载到DS或ES
格式:
LDS <16位通用寄存器>, <32位任何一种存储单元寻址方式>
LES <16位通用寄存器>, <32位任何一种存储单元寻址方式>
乍一看32位-16位明显不对劲,其实只有低字数据传送到了指定目的寄存器,高字数据传送给DS/ES寄存器。
举个例子:
已知数据段中DATA DD 12345678H
LDS AX, DATA
指令的执行结果为(AX)=5678H (DS)=1234H
(5)标志位地址传送指令
复习一下标志位先:
CF 进位标志位 : 运算结果有进位或借位时CF=1, 否则CF=0
OF 溢出标志位 : 运算结果超出了数据表示范围时OF=1, 否则OF=0
SF 符号标志位 : 运算结果为负数时SF=1, 否则SF=0
ZF 零标志位:运算结果为0时,ZF=1,否则ZF=0
① 标志位保存指令LAHF(没怎么用过啊)
格式: LAHF (Load AH from Flags )
功能:将FLAG的低八位送至AH寄存器保存
LAHF指令的使用场景比较少见,通常只在需要读取标志位信息的特定情况下使用。在实际编程中,常用的方式是使用条件转移指令来判断标志位的值,而不是直接读取标志寄存器。
FLAG的低八位:
- Carry Flag(CF):用于存储无符号算术操作的进位或借位结果。
- Parity Flag(PF):用于存储上一条指令结果中1的个数的奇偶性。
- Auxiliary Carry Flag(AF):用于存储BCD(二进制编码的十进制)操作的进位或借位结果。
- Zero Flag(ZF):当上一条指令的结果为零时被置位。
- Sign Flag(SF):当上一条指令的结果为负数时被置位。
- Trap Flag(TF):用于调试,开启单步执行模式后会被置位。
- Interrupt Flag(IF):用于控制中断的开关,当IF被置位时,允许中断请求。
- Direction Flag(DF):用于字符串操作指令中控制字符串的方向,当DF被置位时,字符串向低地址方向移动。
② 标志位读取指令SAHF
格式: SAHF (Store AH into Flags)
功能:使用AH寄存器中的内容更新FLAG的低8位
(6)表转换指令XLAT
格式:XLAT ("Translate Byte to AL using Lookup Table" 查找表实现字节的转换)
功能:将数据段有效地址位(BX+AL)单元中的数据送AL;
该指令常用于查表操作:设置BX为数组的首地址,AL为指定元素在数组中的下标
(7)堆栈操作指令
① 进栈指令PUSH
格式:PUSH 源操作数 (是一个16位寄存器)
原理过程:先修改栈顶指针SP-2 ->SP,再将数据保存在栈顶位置:源操作数->[SP]
举个例子:
设(AX)=7823H,(SP)=0FFFEH
则指令:
PUSH AX
过程如下:
① 修改指针 (SP)-2 -> SP
(SP) = 0FFFCH
② 保存数据 7823H->[SP]
[0FFFCH]=7823H
如图所示:
② 出栈指令POP
格式:POP 目的操作数 (目的操作数是一个16位的通用寄存器,不能是CS)
原理过程:首先将栈顶位置的数据保存于目的操作数指定的位置中,[SP]->目的操作数。然后修改栈顶指针:(SP)+2->SP
堆栈指令注意事项:
① PUSH指令中的显式操作数是源操作数
② POP指令中的显式操作数的目的操作数
16位系统中,PUSH指令的操作数不可一味立即数
若堆栈指令的显示操作数是存储单元,则应注意是否需要强制类型(非符号地址的形式需要强制类型)
2. 算数运算类指令
这类指令均会影响标志位的状态,因此我们在学习这类指令的时候要清楚四个标志位的变化是如何的。那么这四个标志位是什么呢?我们在重新复习一下:
① 零标志 ZF --- Zero Flag
当运算结果为0时设置ZF=1,否则ZF=0 (就是运算结果和ZF总有一个是0)
举个例子:
3AH+7CH=0B6H ZF=0
84H+7CH=00H ZF=1
② 符号标志 SF --- Sign Flag
当运算结果为负数时SF=1,否则SF=0
也就是说运算结果的符号位就是SF的状态,注意看下面的图片我标注的是哪一位哈:
举个例子:
3AH + 7CH = 0B6H SF = 1
84H + 7CH = OOH SF=0
③ 进位标志 CF --- Carry Flag
加法时CF是进位标志;减法时CF是借位标志
若有进借位,CF=1;否则CF=0
举个例子:
3AH + 7CH = 0B6H , CF = 0
0AAH + 7CH = 26H , CF = 1
④ 溢出标志 OF --- Overflow Flag
若数据结果炒煮了数据表示范围,则OF=1;否则OF=0
溢出的判断1:正+正=负 ; 负+负=正
举个例子:
8位范围:-128~+127(无符号:0~255) 16位范围:-32768~+32767(无符号数:0~65535)
需要注意的是OF主要针对有符号数,对于无符号数可以用CF兼做溢出标志。
溢出和进位的区别:
进位标志CF:表示无符号数的运算结果是否超出了范围。无论CF位何值,无符号数的运算结果均正确。
溢出标志OF:表示有符号数的运算结果是否超出了范围。当OF=1时有符号数的运算结果不正确。其设置就是把数据当作有符号数来判断的。
啥意思呢?我们还是以这两个例子来单独说明:
一、
3AH + 7CH = B6H
作为无符号数: 58+124=182 在范围内,无进位 CF=0
作为有符号数: 58+124=182 再范围外,有溢出 OF=1
二、
AAH+7CH=126H
作为无符号数:170 + 124 = 294 在范围外,有进位 CF=1
作为有符号数: -86 + 124 = 38 在范围内,无溢出 OF=0
了解了四种标志位是怎么回事之后,我们可以开始学习算数运算指令了
(1)加法指令(3种)
1. 不带进位的加法指令ADD:DST + SRC -> DST
2. 带进位的加法指令 ADC: DST + SRC + CF -> DST (多用于多字节数据的高字节加法)
3. 增量指令 INC:自增指令,类比C++的i++;多用于指针、计数器的修改操作。
很简单是吧!上例子:
例一、编写程序指令序列将两个32位数据d1和d2的和保存在d1上。
思路:因为寄存器是16位的,我们分别将d1和d2拆开成两个高字节和两个低字节,然后分别相加。需要注意的是要先加低字节,因为高字节相加需要加上进位标志。
代码如下:
MOV AX, word ptr d2 (AX) = 4312H MOV DX, word ptr d2+2 (DX) = 7856H ADD word d1, AX 低字和=9735H ADC word d1+2, DX 高字和=01BDH
低位:
CF=0 SF=0 OF=0 ZF=0
高位:
CF=1 SF=0 OF=0 ZF=0
注意:INC指令不影响CF标志位!
再来一个例题
计算(-56)+(-67)=? 并给出四个标志位的状态。
(-56)的补码=11001000
(-67)的补码=10111101
CF=1 ZF=0 SF=1 OF=0
这只是热身,下面这道题掌握了的话这方面的题就基本没问题了
请写出下列指令序列中每条指令执行后的结果,及标志位变化的情况:
立即数的开头如果是字母,前面要加一个0
1. 0FBH 这是一个简单的立即数传递
2. 2 FBH+7=102 但是AL是低8位,所以是2
3. 1 进位了
4. 0 非零
5. 0 没有溢出
6. 0 正数
7. 4652 立即数传递
8. 1FE 立即数传递
9. 0 2+FE = 100取低位0
10. 1 有进位
11. 1 是零
12. 0 没有溢出
13. 0 非负数
14. 3742 [1FE+2]=[200]=4652H 也就是4652H+F0F0H=(1)3742
15. 1 有进位
16. 0 不为零
17. 0 没有溢出
18. 0 是一个正数
判断溢出时使用哪个标志由我们自己决定:
若把操作数当作无符号数,则使用CF
若把操作数当作有符号数,则使用OF
(2)减法指令(4种)
1. 不带借位的减法指令 SUB: DST-SRC->DST
2. 带借位的减法指令SBB:DST-SRC-CF->DST
3. 减量指令:DEC OPRD: 自减 类比i--;不影响进位标志位(自增自减都不影响CF)
4.求相反数指令:NEG OPRD:0-OPRD->OPRD 相反数就是各位取反末尾加一
比较特殊是,对于NEG来说,当OPRD=0时,CF=0;反之亦然;当OPR位负的最小值时OF=1,反之亦然。
举几个例子把:
MOV AX, 0FF64H
NEG AL
AL=64H=01100100B取反得:10011011B 末位加一得:10011100B=9C
CF=1 SF=1 OF=0 ZF=0
SUB AL, 9DH
AL=9C-9D=1FF
CF=1 SF=1 ZF=0 OF=0
NEG AX
AX=FFFFH=1111111111111111H取反=000000000000000末位加一0000000000000001B
也即是0001H
CF=1 OF=0 ZF=0 SF=0
DEC AL
AL=01H递减之后=00H
CF=1(不变) OF=0 SF=0 ZF=1
NEG AX
AX=0000H取反1111111111111111B加1得到(1)0000000000000000B也即是0000H
CF=0 ZF=1 SF=0 OF=0
(3)乘法指令
乘法指令仅对CF和OF标志位有影响,对其他标志位无定义
无符号乘法指令:MUL SRC
有符号乘法指令:IMUL SRC
隐含目标寄存器AL/AX
当SRC(源操作数)为8位时,(AL)*(SRC)-> AX
当SRC(源操作数)为16位时,(AX)*(SRC)-> (DX:AX)
当乘积的高半部分为0或符号拓展时,CF=OF=0
当乘积的高半部分不为0或符号拓展时,CF=OF=1
(4)除法指令
除法指令对各个标志位都无定义(好耶)
无符号数除法指令:DIV
有符号数除法指令:IDIV
隐含被除数寄存器,16位/32位分别为AX/(DS:AX)
当SRC为8位时:(AX) / (OPR), 商->AL 余数->AH
当SRC为16位时:(DS:AX)/(OPR), 商->AX 余数->DX
需要注意的几点:
1. 对于IDIV指令,余数与被除数符号相同。
2. 商溢出(超过8/16位表示范围),则商无法表示。
3. 若除数为0,则除法无法计算
(5)符号拓展指令
CBW: (AL)->AX 字节转换为字
CWD: (AX)->DX:AX 字转换为双字
常用于有符号数除法时,被除数的位数拓展;
类型转换指令不影响标志位状态。无符号数的数据拓展直接用0拓展更高位;有符号数就用最高位拓展。
例如:若被除数为AL,则拓展指令为MOV AH, 0;
例如:(都是有符号数)
MOV AL, 80H 80H=10000000B取反加一得10000000B=-128D->AL
CBW 在AL的最高位前面补8个相同的位(FF) AX=FF80H
ADD AL, 255 255D=11111111B+10000000B=01111111B=127=007FH
CBW 最高位是0,所以拓展0,AX=007FH
NEG AX 取反加一,AX=FF81H
CWD 补上相同的位,DX:AX=FFFF FF81H
来一道练习题,写出下面每个指令结束后寄存器的内容
MOV BX, 23ABH
(BX)=23ABH
ADD BL, 0ACH
(BL)=57H (BX)=2357H OF=1 CF=1 SF=0
MOV AX, 23F5H
(AX)= 23F5H
ADD BH, AL
BH=18H (BX)=1857H OF=0 CF=1 SF=0
SBB BX, AX
(BX)=F461H OF=0 CF=1 SF=1
ADC AX, 12H
(AX)=2408H OF=0 CF=0 SF=0
SUB BH, -9
(BH)=FDH OF=0 CF=1 SF=1
编程题:
编写程序段,完成(C-120+AXB)/C的计算,并把所得的商和余数分别存入X和Y中,其中A,B,C,X和Y都是有符号的字变量。
思路:
1)首先计算AXB: MOV AX, A 和 IMUL B得到32位存放在DX:AX
2)加上C:C是一个16位的数据,因此我们需要先将他拓展到32位(CWD)但是CWD会破坏原本DX:AX里的数据,因此要先转移里面原有的数据。
3)减120:低位的AX减去120(SUB)高位的DX减0(SBB)
4)除以C:IDIV
5) 保存结果,DX和AX分别存入Y和X单元
MOV AX, A
IMUL B
MOV CX, AX
MOV BX, DX
MOV AX, C
CWD
ADD AX, CX
ADC DX, BX
SUB AX, 120
SBB DX, 0
IDIV C
MOV X, AX
MOV Y, DX
3. 逻辑运算类指令
(1)与 AND
会使 CF=OF=0
举个例子:若(AL)=B7H,则AND AL,F8H的执行过程为:
显而易见,CF=OF=0 SF=1 ZF=0
除此之外,AND还常用于将一个操作数(寄存器)的某些位清零:
操作数的清零位为0,其他位为1。举个例子:
若要将BL的第0位和第3位清零,则操作数是11110110B,指令就是AND BL,0F6H
(2)或 OR
会使CF=OF=0,其他位正常变动。
举个例子,(AL)=61H,则OR AL, 78H的执行过程为:
CF=0 SF=0 OF=0 ZF=0
除此之外,逻辑或指令常用于将一个操作数的若干位置1。
操作数中,置1位为1,其他位为0.
举个例子,若将BL的第1,3,4,6位置1,则操作数为01011010B,指令就是OR BL, 5AH
(3)非 NOT
对所有标志位都无影响
(4)异或 XOR
CF=OF=0,其他位正常变化。
功能有二:
其一是用于将一个操作数的若干位取反:
操作数设定为:取反位为1,其他位保持0
举个例子:若要将BX的最低四位取反,则操作数为00001111B,指令为XOR BX, 0FH
其二是将某个寄存器的值清0
操作数设定为本身,举个例子:
若要清空AX寄存器,则指令为XOR AX, AX 或者是MOV AX, 0(没有前者优雅)
(5)测试
CF=OF=0
三、伪指令
伪指令是构成汇编语言程序的一种语句;是一种指示性语句,为汇编程序提供一些辅助信息。
类型:
(1)常量与变量定义:定义数据变量;
例如:
DATA SEGMENT
NUM1 DB 10 ; 定义一个字节型变量NUM1,并给他赋予初值10
NUM2 DW ? ; 这里使用了通配符“?”来定义一个未初始化值的变量NUM2,分配内存空间但是没有赋值
DATA ENDS
常见的助记符:
DB(字节,Define Byte):占用一个字节(8位)的内存空间
DW(字,Define Word):占用了两个字节(16位)的内存空间
DD(双字,Define DoubleWord):占用了四个字节(32位)的内存空间
变量定义伪指令的操作数是数值时,均以二进制补码形式在内存中保存
例题1:变量定义伪指令如下,请推理出内存中数值的存放情况:
A1 DB 10
A2 DW NUM, 100H, -1
A3 DD 4*8
如上图所示,解释很清晰明了。需要注意的是内存中存放的是16位数,因此需要先将10进制数转化位16进制数,然后再放入内存中。除此之外DD,DW,DB几个数据类型的内存大小决定了占几个格子(一个格子就是一个字节)。需记住一个原则:低位放前面,高位放后面(8086是一个小段字节CPU),因此填的时候应该是从下往上填。
例题2:当变量伪指令代码如下时,内存情况是如何?
STR1 DB 'A','B'
STR2 DW 'AB'
STR3 DD 'BA'
STR4 DB 'ABCD'
需要注意的是当操作数是字符或者字符串是,一般使用DB来定义,并且内存中是以ASCII码的形式来存放。
例题3:若变量定义伪代码如下所示,则内存空间情况是如何?
BUF1 DB 5,6,?
BUF2 DW 100H,?
通配符“?”可以只分配存储空间,而不赋予初值(看起来是不是和上面写的EQU相反?)在内存中空的地方直接用“--”表示即可。
DUP复制说明符
格式: <重复次数> DUP(<重复数据>)
举例:
DATA SEGMENT ; 定义数据段
BUF DB 10 DUP(0) ; 定义一个字节型数组BUF,包含10个元素,每个元素的值为0
DATA ENDS ; 结束数据段
; 等于下面这一段代码:
DATA SEGMENT ; 定义数据段
BUF DB 0,0,0,0,0,0,0,0,0,0 ; 定义一个字节型数组BUF,包含10个元素,每个元素的值为0
DATA ENDS ; 结束数据段
例题4:若变量定义伪代码如下所示,则内存空间情况是如何?
DATA2 DB 3 DUP (3 DUP (2) , 7)
不难,从內部开始拆开嵌套一层一层分析即可。
(2)程序指示:安排程序中的数据存放位置;
一个汇编语言源程序是由数据段、堆栈段和代码段组成。数据段可有可无;堆栈段可以自己定义,如果没有定义则由系统自动分配;代码段必须要有。通常的结构为数据段在前,堆栈段其次,代码段最后。
一般结构如下:
DATA SEGMENT
......
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA ; 段说明ASSUME
START:
......
MOV AX, 4C00H
INT 21H ; 这两句的作用是返回DOS的功能调用,是所有汇编语言源程序的结束语句
END START
CODE ENDS
END START
; 这里的近标号START是用来标记程序入口的符号,表示程序从START处开始执行
(3)段定义:程序中使用到的逻辑段的说明;
格式如下:
段名 SEGMENT
...... ; 段体
段名 ENDS
段名:
1. 必须是一个合法的标识符,首位段名要一致。
2. 当段名作为操作数是,表示立即数,其值为段地址。
段体:
1. 数据段中主要为数据定义的伪指令。
2. 代码段中主要为汇编指令。
堆栈段
举个例子就明白了:(略写)
STACK1 SEGMENT STACK
DW 256 DUP(?)
STACK1 ENDS
这里创建了256个未初始化值的字单元
简化的源程序结构:
格式如下:
.model small
.stack
.data
......
.code
.startup
......
.exit
end
其中
.start 就是:
MOV AX, DATA
MOV DS, AX
这两个初始化操作
.exit 就是
MOV AX, 4C00H
INT 21H
.model small 是一条汇编语言的指令,它告诉编译器你打算使用小型内存模式。这意味着你的程序只能有一个代码段和一个数据段,每个段的大小不能超过64KB。
段约定伪指令ASSUME
就是结构模板中的:ASSUME CS:CODE, DS:DATA 它的功能就是指明逻辑段和段寄存器的对应关系。要注意段寄存器和逻辑段之间不一定是一一对应的关系。
伪指令的特点:
没有对应的机器指令;由汇编程序在翻译汇编语言源程序的时候执行。
强制属性操作符PTR
这个PTR有点复杂,且听我慢慢讲
将标识符的类型属性临时性地强制为指定的类型(临时性:仅在本条指令内有效)
格式:
<类型名> PTR <表示符>
<类型名> PTR <任何一种存储单元寻址方式>
这里的类型名常用的有:BYTE, WORD, DWORD, NEAR, FAR
举个例子:
MOV AX, [BX]
; ↑这个是错的,可能会导致报错信息。我们稍后讲
MOV AX, WORD PTR [BX]
; ↑这个是正确的用法
有几个比较特殊的情况:
1. DS:[0]的意思是DS段偏移量为0的内存单元,大小是一个字
MOV BX, DS:[0]
MOV DS[0], AX
; 数据段DS中偏移地址是0的内存单元(1个字)的内容赋给BX
; 把寄存器AX(1个字)的内容赋给DS偏移量为0的内存单元(1个字)
; 因为BX和AX都是一个16位的寄存器,所以系统会默认操作数的尺寸是一个字,因此不需要ptr指定
大小是一个字的有哪些?我们来整理一下:
2.
埋个坑,等题目写了再来补充
EQU指令
是“Equal”的缩写,译为等同的。它不会为符号常量分配空间,只是在汇编时进行符号替换。与=伪指令不同的是用EQU定义过的符号不可以被重新定义。
举个例子:
NUM EQU 100
; NUM被定义为100
MOV AX, NUM
; 将100赋予给AX
ORG指令
是'Origin'的缩写,译为“起源”。将数值或指令从“数值表达式”所指定的位置开始存放。
格式:ORG <数值表达式>
例如:
ORG 100H
X1 DW 23H
DB 23H
ORG 200H
X2 DB 'ABC',0DH,0AH
X3 DB ?
那么他们的存储情况就如图所示:
等号伪指令
格式:<标识符> = <表达式>
功能:定义标识符来代替表达式的值
它和EQU的区别是等号伪指令只能定义数值,但是它可以重复定义一个符号。
标识符
是程序中定义的符号,可作为地址或者数值使用;类比C++里的标识符(但是汇编语言中的标识符大小写不敏感)。除此之外似乎没什么需要注意的了,略写。
常量:代表所定义的数值或符号
变量:代表数据所存放的存储单元地址
标号/子程序名:代表指令所存放的存储单元地址