转自:https://www.shiyanlou.com/courses/running/332
一、课程简介
- 声明:该课程基于《汇编语言(第2版)》郑晓薇 编著,机械工业出版社。本节实验取自教材中第二章的《实例二 进入计算机》。
- 实验环境:
1.DOS 环境
实验环境中安装有dosemu
可以模拟DOS环境,并提供DEBUG
、MASM
、LINK
等汇编语言开发程序。
2.进入DOS和DEBUG
在桌面上双击dosemu图标,直接进入DOS。再做如下操作:
C:〉D: ——回车后进入D盘
D:〉CD DOS ——进入DOS子目录
D:dos〉DIR ——列出目录中的文件
D:dos〉DEBUG ——进入DEBUG
二、进入计算机
微型计算机的字长与微处理器的寄存器位数有关。
以Intel 80X86系列微处理器为例:
CPU是8086/8088、80286的字长是16位(二进制位bit),那么它们的寄存器的位数一定是16位的;
32位字长的微机CPU是80386/80486或者Pentium系列,它们的寄存器的位数则是32位的。
在汇编语言中,数值后面分别用字母B、H、D代表二进制(Binary)、十六进制(Hexadecimal)、十进制数(Decimal)(十进制数可以省略D)。
在计算机中还规定采用字节、字、双字等单位来表示数据。
字节(Byte):8位二进制数。如00000101B,或表示成05H;10000101B,或表示成85H。
字(Word):16位二进制数,等于2字节。如1100010111010110B,或表示成C5D6H。
双字(Double Word):32位二进制数,又称为双精度数,等于4字节。如23456789H。
2.1 8086寄存器组
8086寄存器都是16位的寄存器,根据用途可分为4种类型。分别是数据寄存器、地址寄存器、段寄存器和控制寄存器。如图所示:
数据寄存器中每个寄存器又可以分为2个8位的寄存器:AH、AL,BH、BL,CH、CL,DH、DL。H表示高字节(高8位)寄存器、L表示低字节(低8位)寄存器。例如 用AX寄存器存放一个字1234H,表示为(AX)=1234H,即高字节12放在AH,低字节34放在AL中。
地址寄存器包括指针和变址寄存器SP、BP、SI、DI四个16位寄存器。顾名思义,它们可用来存放存储器操作数的偏移地址。另外,它们也可以作为通用寄存器用。
8086CPU有4个16位的段寄存器,分别是CS代码段寄存器、DS数据段寄存器、ES附加段寄存器、SS堆栈段寄存器。
控制寄存器包括IP和FLAGS(又称为PSW程序状态字)两个16位寄存器,用于控制程序的执行。
IP 指令指针寄存器,用来存放代码段中的偏移地址,指出当前正在执行指令的下一条指令所在单元的偏移地址。
FLAGS标志寄存器中的某位代表CPU的1个标志,表示出CPU的某种执行状态。最低位为D0,最高位为D15。8086CPU的标志寄存器共有9个标志,分别为6个条件码标志和3个控制标志。如图:
(1)条件码标志 (D0~D7+D11)
CF 进位标志:当指令执行结果的最高位向前有进位时,CF=1,否则CF=0
PF 奇偶标志:当指令执行结果中1的个数为偶数个时,PF=1,否则PF=0
AF 辅助进位标志:当指令执行结果的第3位(半字节)向前有进位时,AF=1,否则AF=0
ZF 零标志:当指令执行结果为0时,ZF=1,结果不为0时,ZF=0
SF 符号标志:当指令执行结果的最高位(符号位)为负时,SF=1,否则SF=0
OF 溢出标志:当指令执行结果有溢出(超出了数的表示范围)时,OF=1,否则OF=0
(2)控制标志 (D8~D10)
TF 陷阱标志:在DEBUG调试时,TF=1,采用单步执行方式,即进入陷阱;TF=0,正常执行程序
IF 中断标志:设置IF=1,允许CPU响应可屏蔽中断,IF=0则不响应
DF 方向标志:执行串处理指令时,若设置DF=0,存储单元的地址寄存器的值自动增加,若设置DF=1,存储单元的地址寄存器的值自动减小
例: 两个二进制数相加运算,有关标志位自动发生变化。
根据计算结果可知CPU会自动地把标志位设为:CF=0,SF=1,ZF=0,OF=0,PF=0,即无进位,结果为负数,结果不为0,没有溢出,奇数个1。 对溢出的判断也可以从简单的角度理解,因为进行运算的二进制数是补码,可看出本题是一个负数和一个正数相加,结果为负数,不溢出。若两个正数相加,结果为负数,或者两个负数相加,结果为正数,那都是溢出了,说明8位补码已经表示不了该结果。
数的补码表示:
在计算机中,对带符号数可用真值和机器数两个概念表示。真值,就是带有“+”、“-”号的实际数值;所谓机器数,则是把“+”、“-”符号数值化(0、1)后所得到的计算机实际能表示的数。
机器数有三种码表示,分别是原码、反码和补码。汇编语言中,数都是以补码的形式表示的,因此必须掌握数的补码表示和补码的运算。这三种码的定义如下:
原码:原码将最高位作为符号位,正数为0,负数为1,其余7位作为数值位。
反码:正数的反码与正数的原码一样。而求负数的反码时,符号位为1,数值位在原码的基础上求反。
补码:正数的补码与正数的原码一样。求负数的补码时,符号位为1,数值位在原码的基础上求反加1。
例: 十进制数+5和-5分别表示成二进制数原码、反码和补码:
[+5]原 = [+5]反 = [+5]补 = 00000101B
[-5]原 = 10000101B
[-5]反 = 11111010B
[-5]补 = 11111011B
2.2 内存
在汇编语言中,先要对内存地址和存储单元的概念进行学习。对存储单元的标识可以用物理地址或逻辑地址表示。
物理地址是内存单元的真实地址,存储单元的物理地址是唯一的。Intel8086CPU有20根地址线,因此其存储空间可达2的20次方=1M个字节单元(1MB)。地址都是从0开始的,在20位地址线的存储空间中采用十六进制表示的物理地址范围是00000H~FFFFFH。
逻辑地址是用户编程时使用的地址,分为段地址和偏移地址两部分。在8086汇编语言中,把内存地址空间划分为若干逻辑段,每段由一些存储单元构成,每段最大为65536个字节单元。用段地址指出是哪一段,偏移地址标明是该段中的哪个单元。段地址和偏移地址都是16位二进制数。逻辑地址的形式:
段地址:偏移地址
例如:在上图中,内存划分出了若干段。0号段,1号段,...,每一段都有0号单元、1号单元、2号单元,...。每段的长度可以不一样,如0号段从0号单元到0FH号单元共16个字节单元,1号段从0号单元到0139H号单元共314个字节单元。用段地址表示段号,偏移地址代表每一段中的单元号,比如0000:0002H代表0号段的2号单元,0001:0002H代表1号段的2号单元,以此类推。因此,偏移地址的通俗含义是在该段内,相对于段地址偏移了多少个单元。
用户编程时采用的逻辑地址在CPU执行程序时都要转换成实际的物理地址,这个转换过程是由CPU中的地址加法器自动完成的。转换时先将16位的段地址左移4位,相当于乘以16或十六进制的10H,再和偏移地址相加。转换公式为:
物理地址 = 段地址 × 10H + 偏移地址
例: 若某单元的逻辑地址为0001:0002H,其物理地址 = 0001H×10H + 0002H = 00012H
另一单元的逻辑地址为3020:055AH,其物理地址 = 3020H×10H + 055AH = 3075AH
存储器逻辑分段类型如下:
代码段——用于存放指令,段地址存放在段寄存器CS
数据段——用于存放数据,段地址存放在段寄存器DS
附加段——用于辅助存放数据,段地址存放在段寄存器ES
堆栈段——是重要的数据结构,可用来保存数据、地址和系统参数,段地址存放在段寄存器SS
存储单元中的数据称为存储单元内容,一个实际的存储单元只能存放一个字节(8位二进制)的数据。
存储单元的地址和内容的表示形式:用括号将地址括起来即代表单元的内容。如
(3075AH)=12H //表示3075AH号单元中的内容是12H,称为字节单元; (37692H)=5678H //表示37692H单元和37693H单元一起存放5678H,该单元是字单元。
字单元在存储的时候,高字节放在高地址单元,低字节放在低地址单元,即56H放在37693H单元,78H放在37692H单元。如图:
有关CPU和存储单元的概念我们已经了解了,那么如何观察实际机器内部的情况呢?能不能看到具体的寄存器、标志、存储单元的内容呢?可不可以修改和控制它们呢?
这一系列的疑问我们可以在调试工具软件DEBUG的支持下得到解答。通过上机实验,可加强相关理论概念的理解;而掌握了DEBUG这个有力工具,就可以深入到机器内部进行观察了。
2.3 调试工具DEBUG
在DOS操作系统和Windows操作系统中,都提供了调试工具DEBUG。DEBUG是为汇编语言设计的一种调试工具,它通过单步、设置断点等方式为程序员提供了非常有效的调试手段。利用它可以观察和修改CPU的寄存器、内存单元;可以跟踪程序的运行,发现程序的错误。
实验楼环境中采用dosemu来模拟DOS环境,进入DOS环境中可以直接启动DEBUG程序。
1. DEBUG的主要命令
DEBUG命令有20多个,我们主要学习最常用的命令(具体见后面)
R ——查看和修改寄存器
D ——查看内存单元
E ——修改内存单元
U ——反汇编,将机器指令变为汇编指令
T /P——单步执行
G ——连续执行程序
A ——输入汇编指令
Q ——退出
2. 进入DOS
DEBUG要先进入DOS环境中再使用,在实验楼虚拟环境中进入DOS的方法:(具体操作图解见实验楼)
- 在桌面上双击“Xfce终端”程序进入Linux的命令行终端
- 在启动的Xfce命令行界面中输入 Dosemu 进入DOS环境,Dosemu 也有其他参数,可以在“Xfce终端”中输入 Dosemu –help 查看
- 退出DOS环境,在DOS中输入命令 exitemu
- 或者在桌面上双击dosemu图标,直接进入DOS
进入后的DOS环境:
本书用到的简单的DOS命令:
cd ——首先要用cd 退回到根目录C>下
dir ——显示文件列表
md hb ——建立hb子目录
cd hb ——进入hb子目录
copy d:dosmasm.exe c:hb ——将D盘dos目录下的masm.exe拷贝到C盘hb目录下
copy d:doslink.exe c:hb ——将D盘dos目录下的link.exe拷贝到C盘hb目录下
cd .. ——退回到上一级目录
e:——进入e盘
cls ——清屏
type——显示文本文件内容(如type c:hbabc.asm)
DOS和DEBUG命令都支持大小写。
3. 进入DEBUG
要观察计算机内部的情况,可直接进入DEBUG。如果要调试及观察可执行文件,则要在DEBUG后加上文件名和扩展名.EXE。我们先观察,因此直接键入 DEBUG 进入系统,如图所示。
DEBUG的提示符是小短线- ,在其后输入命令。
(1)R命令——查看和修改寄存器
R命令有两种用法:
直接键入R——将显示CPU所有的寄存器和标志位;
修改寄存器——在R后跟写寄存器名,回车后先显示寄存器的内容,在冒号后键入新的值;再用R命令就可看到修改后的内容了。如图1所示,将AX寄存器的值改为1234H。
图1:用R命令查看和修改寄存器
由图可知,由于此时DEBUG进入的是操作系统环境,R命令显示的是系统下的寄存器的值。可看出,AX、BX、CX、DX均为0,如果将AX寄存器的值修改为1234H,执行R AX之后在冒号后输入1234即可。注意,DEBUG下的数据都是十六进制数。
再来看四个段寄存器DS、ES、SS、CS的值都是0AFAH,说明现在系统处在同一个逻辑段中(不同的系统环境下,段寄存器的值可能不一样,dosemu虚拟机中为07BEH)。操作系统根据内存的情况为各段分配段地址,因此每台机器或每次运行时段地址值可能会不一样。IP指令指针寄存器的值是0100H,表示将要执行的指令在代码段的0100H单元中。该指令单元的逻辑地址应该由CS:IP构成,即0AFA:0100H。
我们来看在寄存器的下面那一行的表示。该行显示的是代码段的一条指令的反汇编。所谓反汇编,指的是将二进制的机器指令显示成汇编指令。由三部分构成:最左边0AFA:0100表示该指令所在单元的逻辑地址,中间1E表示该指令的机器码,第3列显示为汇编指令PUSH DS,该指令的作用是将DS入栈。(dosemu虚拟机中为TEST测试指令)。 通过DEBUG,我们就可知道一条汇编指令翻译成机器代码是什么值了;反之也一样,对一条机器指令也可得知它代表什么汇编指令。
在图的右边显示的是CPU标志寄存器各标志位的状态,可对照表2-1观察一下现在系统的状态。
(2)D命令——查看内存单元
内存每16个字节单元为一小段,逻辑段必须从小段的首址开始。用D命令可以查看存储单元的地址和内容。
D命令格式为:
D 段地址:起始偏移地址 [结尾偏移地址]
例如:
D DS:0 //查看数据段,从0号单元开始 D ES:0 //查看附加段,从0号单元开始 D DS:100 //查看数据段,从100H号单元开始 D 0200:5 15 //查看0200H段的5号单元到15H号单元(在虚拟机上该命令不能执行)
D命令的执行情况如图2所示
图2:用D命令查看存储单元
其中左边一列为逻辑地址,中间部分为存储单元的内容。每行为16个字节单元,中间的小横线用于区分前8个单元和后8个单元。在逻辑地址中只给出每行第一个单元的偏移地址,其余15个单元的偏移地址没有标出。可以推断出图中第一行单元的偏移地址从0000H到000FH,第二行单元的偏移地址为0010H~001FH,以此类推。右边部分显示出内存单元中的ASCII码表示的字符,无法显示时用小点代替。
图2中:
第一条D命令显示的是数据段存储单元的内容,可以看到数据段的段地址为DS,其值0B05H。0号单元的内容为CDH,1号单元为20H ,…,第15号单元的内容为03H;第二行0010H号(16号)单元的内容为69H,它是小写字母i的ASCII码,因此右边区域中显示了i ,表示该单元的值69H可以看成ASCII码。
第二条D命令显示0200H段中的内容,也是从0号单元开始。
第三条D命令从0200H段的5号单元开始显示直到15H号单元。
如果在D后面直接写出偏移地址,则显示当前数据段下偏移地址开始的内存单元,如:
D 10 //从数据段10H号单元开始显示 D100 //从数据段100H号单元开始显示
注意:多次键入D,可连续显示后面的单元内容。
(3)E命令——修改内存单元
用E命令可以改写多个存储单元的内容。格式为:E 起始地址 修改值 修改值 …
例如:将数据段中的DS:3~DS:5 三个单元的内容修改为14、15、16。命令为
E DS:3 14 15 16
如图3所示
图3:用E命令修改存储单元
用D DS:0命令显示后,可以看到,这三个单元的值由原来的9F 00 9A修改为14 15 16。
如果E后面直接跟偏移地址,则修改当前数据段下偏移地址所指单元值;还可以用E命令修改其它段的存储单元内容。
E 10 //修改当前数据段10H号单元内容 E ES:100 //修改附加段100H号单元内容 D ES:100 //查看一下100H单元的内容是否修改了
(4)U命令 ——反汇编
程序员编写的汇编语言源程序经过汇编(编译)后生成了二进制的机器指令代码,而U命令可将二进制的机器指令变为助记符形式的汇编指令,因此称之为“反汇编”。通过U命令,我们可以得到机器指令与汇编指令的对照,了解机器指令的存储情况,如图4所示
图4:用U命令显示汇编程序段(可见机器指令与汇编指令的对照)
左边为代码段中存储单元的逻辑地址,段地址CS的值为0AFEH,偏移地址从0100H开始。紧靠偏移地址的一列为机器指令代码,右边部分是机器指令对应的汇编指令。例如第一行中,机器指令为7419H,它对应的汇编指令为JZ 011B,该指令是条件转移指令,表示当结果为0时跳转到偏移地址011BH单元中的指令继续执行。而0AFE:011BH单元的指令为MOV BX,0034,是一条传送指令。(dosemu虚拟机中是另一组不同的指令)。
注意:多次键入U,可连续显示后面的程序部分。
U后跟偏移地址,则从该地址开始反汇编。如:
U 0 //从代码段0号单元开始反汇编 U100 //从代码段100H号单元开始反汇编
需要注意的是,图4中显示的程序代码并不是用户编写的程序,因为在输入DEBUG命令时没有写用户程序名.EXE。
这段程序代码是系统代码段中保存的内容,有可能是系统程序,也有可能是无效的代码。
(5)A 命令——输入汇编指令
在DEBUG中,使用A命令可以输入汇编指令,系统自动地将键入的汇编指令翻译成机器代码,并相继地存放在从指定地址开始的存储区中。由于DEBUG下的数值默认为十六进制数,因此先要将十进制数转换成十六进制数。
例如,计算Z=35+27的汇编指令为:
MOV AX,23H ADD AX,1BH MOV [0000],AX
加法的结果Z=62=3EH。变量Z用存储单元[0000]表示。这三条指令可在DEBUG下用A命令直接输入。
输入A命令后,系统自动地给出逻辑地址为0AEE:0100(CS:偏移地址),在其后输入汇编指令,回车后可输入下一条指令,直接回车则退出输入。操作过程如图5所示:
图5:用A命令输入汇编指令
也可以在A命令后给出指令的存放地址,如A CS:0000,表示从代码段的0号单元开始存放输入的指令。
(6)T/P命令——单步执行
输入完指令后,应该执行它。T命令可以一条一条地执行指令。P命令的作用与T命令相同,当遇到中断指令INT n和调用指令CALL时,应该使用P命令,以确保程序正常执行。这是因为INT n指令和CALL指令都要转移到子程序去执行,T命令进入子程序后可能无法返回;而P命令则直接执行该指令,并将结果带回。遇到循环指令LOOP时也应该使用P命令,可以使循环快速结束。
本次执行前,先用R命令查看指令指针寄存器IP的值是否为0100,如果不是,用R IP命令修改为0100。表示现在要从CS:0100单元开始执行指令。T命令每执行一次,都要显示当前寄存器的状况,我们可以随时了解指令的执行情况。计算Z=35+27的汇编指令的执行过程,如图6所示
图6:用T命令单步执行三条指令
查看执行结果:第一次执行T命令后,AX寄存器的值改为0023,第二次执行后,AX的值变成003E了,说明已经执行完加法ADD指令了,第三次执行T后,寄存器的值并未发生变化,说明第三条指令没有对寄存器操作。第三条指令MOV [0000],AX是把结果保存到数据段的存储单元0号字单元中,用D DS:0命令查看该单元的值已经为003EH了(两个字节单元为一个字单元)。
T命令还可以连续执行多条指令。如上例中连续执行3条指令,可用如下T命令:
-T 3
T命令也可以设置开始地址和执行条数。如上例中从0100H开始连续执行3条指令,可用如下T命令:
-T =0100 3
(7)G命令——连续执行程序
有关连续执行命令G的用法我们放到后面章节中学习。
(8)Q命令 ——退出DEBUG
键入Q,回车后退出DEBUG,返回到DOS下。
提示:DEBUG的更多命令及用法参见本书附录C。