实验1 查看CPU和内存,用机器指令和汇编指令编程
一、预备知识:Debug的使用
我们以后所有的实验中,都将用到Debug程序,首先学习一下它的主要用法。
(1)什么是Debug?
Debug是Dos、Windows都提供的实模式(8086方式)程序的调试工具。使用它,可以查看CPU的各种寄存器中的内容、内存的情况和机器码级跟踪程序的运行。
(2)我们用到的Debug功能。
- 用Debug的R命令查看、修改CPU寄存器的内容;
- 用Debug的D命令查看内存中的内容;
- 用Debug的E命令改写内存中的内容;
- 用Debug的U命令将内存中的机器指令翻译成汇编指令;
- 用Debug的T命令执行一条机器指令;
- 用Debug的A命令以汇编指令的格式在内存中写入一条机器指令。
Debug的命令比较多,共有20多个,但这6个命令是和汇编学习密切相关的。在以后的实验中,我们还会用到一个P命令。
(3)进入Debug。
Windows 11 下实验环境搭建教程:https://waylee.net/thread-2757-1-1.html
Debug是在DOS方式下使用的程序。我们在进入Debug前,应先进入到DOS方式。用以下方式可以进入DOS。(如果您是XP或以下系统可以参考以下内容,否则请参考上面的链接)
①重启计算机,进入DOS方式,此时
②在Windows中进入DOS方式,此时进入的是虚拟8086模式的DOS。
下面说明在Windows 2000 中进入Debug的一种方式,在其它Windows系统中进入的方法与此类似。(XP以上版本参考上面的链接)
选择【开始】菜单中的【运行】命令,如图2.28所示,打开【运行】对话框,如图2.29所示,在文本框中输入“command”后,单击【确定】按钮。
进入Dos方式后,如果显示为窗口方式,可以按下Alt+Enter组合键将窗口变为全屏方式。然后运行Debug程序,如图2.30所示。这个程序在不同的Windows系统中所在的路径不尽相同,在Windows 2000 中通常在c:\winnet\system下。由于系统指定了搜索路径,所以在任何一个路径中都可以运行。
(4)同R命令查看、改变CPU寄存器的内容。
我们已经知道了AX、BX、CX、DX、CS、IP这6个寄存器,现在看一下它们之中的内容,图图2.31所示。其他寄存器如SP、BP、SI、DI、DS、ES、SS、标志寄存器等我们先不予理会。
注意CS和IP的值,CS=0CA2,IP=0100,也就是说,内存0CA2:0100处的指令为CPU当前要读取、执行的指令。在所有寄存器的下方,Debug还列出了CS:IP所指向的内存单元处所存放的机器码,并将它翻译为汇编指令。可以看到,CS:IP所指向的内存单元为0CA2:0100,此处存放的机器码为02 75 48,对应的汇编指令为ADD DH,DI+48。
Debug输出的右下角还有一个信息:“DS:0048=0”,我们以后会进行说明,这里同样不必深究。
还可以用R命令来改变寄存器中的内容,如图2.32所示。
2.32
若要修改一个寄存器中的值,比如AX中的值,可用R命令后加寄存器名来进行,输入“r ax”后按Enter键,将出现“:”作为输入提示,在后面输入要写入的数据后按Enter键,即完成了对AX中内容的修改。若想看一下修改的结果,可再用R命令查看,如图2.32所示。
在图2.33中,一进入Debug,用R命令查看,CS:IP指向0B39:0100,此处存放的机器码为40,对应的汇编指令为INC AX;
接着,用R命令将IP修改为200,则CS:IP指向0B39:0200,此处存放的机器码为5B,对应的汇编指令是POP BX;
接着,用R命令将CS修改为ff00,则CS:IP指向ff00:0200,此处存放的机器码为51,对应的汇编指令是PUSH CS。
(5)用Debug的D目录查看内存中的内容
用Debug的D命令,可以查看内存中的内容,D命令的格式较多,这里只介绍在本次实验中用到的格式。
如果我们想知道内存10000H处的内容,可以用“d 段地址:偏移地址”的格式来查看,如图2.34所示。
2.34
要查看内存10000H处的内容,首先将这个地址表示为段地址:偏移地址的格式,可以是1000:0,然后用“d 1000:0”列出1000:0处的内容。
使用“d 段地址:偏移地址”的格式,Debug将列出从指定内存单元开始的 128 个内存单元的内容。图2.34中,在使用d 1000:0后,Debug列出了1000:0~1000:7F中的内容。
使用D命令,Debug将输出3部分内容(如图2.34所示)。
①中间从指定地址开始的128个内存单元的内容,用十六进制的格式输出,每行的输出从16的整数倍的地址开始,最多输出16个单元的内容。从图中,我们可以知道,内存1000:0单元中的内容是72H,内容1000:1单元中的内容是64H,内存1000:11处的内容是61H,内存1000:10~1000:1F中的内容都在第二行。注意在每行的中间有一个“-”,它将每行的输出分为两部分,这样便于查看。比如,要想从图中找到1000:6B单元中的内容,可以从1000:60找到行,“-”前面是1000:60~1000:67的8个单元,后面是1000:68~1000:6F的8个单元,这样我们就可以从1000:68单元向后数3个单元,找到1000:6B单元,可以看到,1000:6B中的内容为67H,
②左边是每行的起始地址
③右边是每个内存单元中的数据对应的可显示的ASCII码字符。比如,内存单元1000:0、1000:1、1000:2中存放的数据是72H、64H、73H,它对应的ASCII字符分别是“r”、“d”、“s”;内存单元1000:36中的数据是0AH,它没有对应可显示的ASCII字符,Debug就用“.”来代替。
注意,我们看到的内容中的内容,在不同的计算机中是不一样的,也可能每次用Debug看到的内容都不相同,因为我们用Debug看到的都是原来就在内存中的内容,这些内容受随时都有可能变化的系统环境的影响。当然,我们也可以改变内容、寄存器中的内容。
我们用d 1000:9 查看 1000:9处的内容,Debug将怎样输出呢?如图2.35所示。
2.35
Debug从1000:9开始显示,一直到1000:88,一共是128个字节。第一行中的1000:0~1000:8单元中的内容不显示。
在一进入Debug后,用D命令直接查看,将列出Debug预设的地址处的内容,如图2.36所示。
2.36
在使用“d 段地址:偏移地址”之后,接着使用D命令,可列出后续的内容,如图2.37所示。
也可以指定D目录的查看范围,此时采用“d 段地址:起始偏移地址 结尾偏移地址”的格式。比如要看1000:0~1000:9中的内容,可以用“d 1000:0 9”实现,如图2.38所示。
2.37~2.38
如果我们就想查看内存单元 10000H 中的内容,可以用2.39中的任何一种方法看到,因为途中的所有“段地址:偏移地址”都表示了10000H这一物理地址。
(6)用Debug的E命令改写内存中的内容。
可以用E命令来改写内存中的内容,比如,要将内存1000:0~1000:9单元中的内容分别写为0、1、2、3、4、5、6、7、8、9,可以用“e 起始地址 数据 数据 数据 ……”的格式来进行,如图2.40所示。
图2.40中,先用D命令查看1000:0~1000:f单元的内容,再用E命令修改从1000:0开始的10个单元的内容,最后用D命令查看1000:0~1000:f中内容的变化。
也可以采用提问的方式来一个一个地改写内存中的内容,如图2.41所示。
如图2.41中,可以用E命令以提问的方式来逐一地修改从某一地址开始的内存单元中的内容,以从1000:10单元开始为例,步骤如下。
①输入 e 1000:10,按Enter键。
②Debug显示起始地址 1000:0010 ,和第一单元(即 1000:0010 单元)的原始内容:6D,然后光标停在“.”的后面提示输入想要写入的数据,此时可以有两个选择:其一为输入数据(我们输入的是0),然后按空格键,即用输入的数据改成当前内存单元;其二位不输入数据,直接按空格键,则不对当前内存单元进行改写。
③当前单元处理完成后(不论是改写或没有改写,只要按了空格键,就表示处理完成),Debug将接着显示下一个内存单元的原始内容,并提示进行修改,读者可以用同样的方法处理。
④所有希望改写的内存单元改写完毕后,按Enter键,E命令操作结束。
可以用E命令向内存中写入字符,比如,用E命令从内存1000:0开始写入数据1、字符“a”、数值2、字符“b”、数值3、字符“c”,可采用图2.42中的方法进行。
从图2.42中可以看出,Debug对E命令的执行结果是,向1000:0、1000:2、1000:4 单元中写入数值1、2、3,向1000:1、1000:3、1000:5 单元中写入字符“a”、“b”、“c”的ASCII码值:61H、62H、63H。
也可以用E命令向内存中写入字符串,比如,用E命令从内存1000:0开始写入:数值1、字符串“a+b”、数值2、字符串“C++”、字符3、字符串“IBM”,如图2.43所示。
2.43
(7)也可以用E命令向内存中写入机器码,用U命令查看内存中机器码的含义,用T命令执行内存中的机器码。
机器码 |
对应的汇编指令 |
b80100 |
mov ax,0001 |
b90200 |
mov cx,0002 |
01c8 |
add ax,cx |
可用如图2.44中所示的方法进行。
2.44
如何查看写入的或内存中原有的机器码所对应的汇编指令呢?可用用U命令。比如可用用U命令将从1000:0开始的内存单元中的内容翻译为汇编指令,并显示出来,如图2.45所示。
2.45
图2.45中,首先用E命令向从1000:0开始的内存单元中写入了8个字节的机器码;然后用D命令查看内存1000:0~1000:1f中的数据(从数据的角度看一下写入的内容);最后用U命令查看从1000:0开始的内存单元中的机器指令和它们所对应的汇编指令。
U命令的显示输出分为3部分,每一条机器指令的地址、机器指令、机器指令所对应的汇编指令。我们可以看到:
1000:0 处存放的是写入的机器码 b8 01 00 所组成的机器指令,对应的汇编指令是 mov ax,1;
1000:3 处存放的是写入的机器码 b9 02 00 所组成的机器指令;对应的汇编指令是 mov cx,2;
1000:6 处存放的是写入的机器码 01 c8 所组成的机器指令;对应的汇编指令是 add ax,cx;
1000:8 处存放的是写入的机器码 03 49 43 所组成的机器指令;对应的汇编指令是 add cx,[bx+di+42]。
由此,我们可以再一次看到内存中的数据和代码没有任何区别,关键在于如何解释。
如何执行我们写入的机器指令呢?使用Debug的T命令可以执行一条或多条指令,简单地使用T命令,可以执行CS:IP指向的指令,如图2.46所示。
2.46
图2.46中,首先用E命令向从1000:0开始的内存单元写入了8个字节的机器码;如何用R命令查看CPU中寄存器的状态,可以看到,CS=0B39H、IP=0100H,指向内存0B39:0100;若要用T命令控制CPU执行我们写到1000:0的指令,必须先让CS:IP指向1000:0;接着用R命令修改CS、IP中的内容,使CS:IP指向1000:0。
完成上面的步骤后,就可以使用T命令来执行我们写入的指令了(此时,CS:IP指向我们的指令所在的内存单元)。执行T命令后,CPU执行CS:IP指向的指令,则1000:0处的指令b8 01 00(mov ax,0001)得到执行,执行后,Debug显示输出CPU寄存器的状态。
注意,指令执行后,AX中的内容被改为1,IP改变为IP+3(因为 mov ax,0001的指令长度为3个字节),CS:IP指向下一条指令。
接着图2.46,我们继续使用T命令执行下面的指令,如图2.47所示。
2.47
在图2.47中,用T命令继续执行后面的指令,注意每条指令执行后,CPU相关寄存器内容的变化。
(8)用Debug的A命令以汇编指令的形式在内存中写入机器指令。
前面我们使用E命令写入机器指令,这样很不方便,最好能直接以汇编指令的形式写入指令。为此,Debug提供了A命令。A命令的使用方法如图2.48所示。
2.48
图2.48中,首先用A命令,以汇编语言向从1000:0开始的内存单元中写入了几条指令,然后用D命令查看A命令的执行结果。可以看到,在使用A命令写入指令时,我们输入的是汇编指令,Debug将这些汇编指令翻译为对应的机器指令,将它们的机器码写入内存。
使用A命令写入汇编指令时,在给出的起始地址后直接按Enter键表示操作结束。
如图2.49中,简单地用A命令,从一个预设的地址开始输入指令。
2.49
本次实验中需要用到的命令
查看、修改CPU中的寄存器的内容:R命令
查看内存中的内容:D命令
修改内存中的内容:E命令(可以写入数据、指令,在内存中,他们实际上没有区别)
将内存中的内容解释为机器指令和对应的汇编指令:U命令
执行CS:IP指向的内存单元处的指令:T命令
以汇编指令的形式向内存中写入指令:A命令
在预备知识中,详细讲解了Debug的基础功能和用法。在汇编语言的学习中,Debug是一个经常用到的工具,在学习预备知识中,应该一边看书一边在机器上操作。
前面提到,我们的原则是:以后用到的,以后再说。所以在这里只讲一些在本次实验中需要用到的命令的相关的使用方法。以后根据需要,我们会讲解其他的用法。
二、实验任务
(1)使用Debug,将下面的程序段写入内存,逐条执行,观察每条指令执行后CPU中相关寄存器中内容的变化,
序号 |
机器码 |
汇编指令 |
1 |
b8 20 4e |
mov ax,4e20H |
2 |
05 16 14 |
add ax,1416H |
3 |
bb 00 20 |
mov bx,2000H |
4 |
01 d8 |
add ax,bx |
5 |
89 c3 |
mov bx,ax |
6 |
01 d8 |
add ax,bx |
7 |
b8 1a 00 |
mov ax,001AH |
8 |
bb 26 00 |
mov bx,0026H |
9 |
00 d8 |
add al,bl |
10 |
00 dc |
add ah,bl |
11 |
00 c7 |
add bh,al |
12 |
b4 00 |
mov ah,0 |
13 |
00 d8 |
add al,bl |
14 |
04 9c |
add al,9CH |
提示,可用E命令和A命令以两种方式将指令写入内存。注意用T命令执行时,CS:IP的指向。
雪舞答:
一、使用E命令是将机器码写入内存,使用A命令是以汇编指令的形式向内存中写入指令。使用A指令较为方便,所以这里使用A命令来实现将指令写入初始地址为1000:0的内存单元
二、使用U命令查看从1000:0开始的内存单元中的机器指令和它们对应的汇编指令(检查一下刚才输入的有没有错误):
三、若要用T命令控制CPU执行我们写到的1000:0的指令,必须先让CS:IP指向1000:0,先用R命令查看各寄存器存储的内容,再用R命令修改CS、IP的内容,使CS:IP指向1000:0
四、一次执行T命令,观察每条指令后的变化:
- 第1条指令执行后4e20值赋予ax,IP为0003
- 第2条指令执行后ax的值再加1416,得到6236,IP为0006
- 第3条指令执行后2000赋予bx,IP为0009
- 第4条指令执行后ax的值为ax+bx,值为8236,IP为000B
- 第5条指令执行后ax的值赋予给bx,bx值为8236,IP为000D
- 第6条指令执行后ax的值为ax+bx,值为8236+8236=1046C,溢出去掉第一位为046C,IP为000F
- 第7条指令执行后001A赋予给ax,值为001A,IP为0012
- 第8条指令执行后0026赋予给bx,值为0026,IP为0015
- 第9条指令执行后al=al+bl,值为1A+26=40,al为40,ax为0040,IP为0017
- 第10条指令执行后ah=ah+bl,值为00+26=26,ah为26,ax为2640,IP为0019
- 第11条指令执行后bh=bh+al,值为00+40=40,bh为40,bx为4026,IP为001B
- 第12条指令执行后0赋予给ah,ax的值为0040,IP为001D
- 第13条指令执行后al=al+bl,值为40+26=66,ax的值为0066,IP的值为001F
- 第14条指令执行后al=al+9c,值为102,溢出所以为02,ax的值为0002,IP的值为0021
(2)将下面3条指令写入从2000:0开始的内存单元中,利用这3条指令计算2的8次方。
mov ax,1
add ax,ax
jmp 2000:0003
雪舞答:
一、使用A命令将上面的汇编指令写入到2000:0开始的内存单元中
jmp 2000:0003的意思是让CS:IP重新指向2000:0003处,而这里此时存储的指令是“add ax,ax”
二、实验R命令将CS:IP指向2000:0。
之后只有T命令执行16次(让jmp 2000:0003执行8次)就可以得到AX=0100,对应的十进制就是256啦。
[实际上,这里是死循环,只是在Debug中可以通过逐条执行的机制控制程序的随时终止;在后面我们可以通过寄存器cx和loop指令对循环进行控制,具体见
第五章]
(3)查看内存中的内容。
PC机主板上的ROM中写有一个生产日期,在内存FFF00H~FFFFFH的某几个单元中,请找到这个生产日期并试图改变它。
提示,如果读者对实验的结果感到疑惑,请仔细阅读第1章中的1.15节。
一、使用D命令查看FFFF:0可以查看数据
在右边可以看到01/01/92之类的时间信息即为生产日期
二、可以用E命令试图修改,并用D命令查看修改是否成功,实际上是不能修改的,因为ROM是只读存储器,不能直接修改。
(4)向内存从D8100H开始的单元中填写数据,如:
-e D810:000 01 01 02 02 03 03 04 04
请读者先填写不同的数据,观察产生的现象;再改变填写的地址,观察产生的现象。
提示,如果读者对实验的结果感到疑惑,请仔细阅读第1章中的1.15节。
输入书中的程序后,可以在屏幕上看到不同色彩的奇怪字符,原因是这样子的:
b800:0000开始的一段内存是8086pc机显存地址空间,往其中填写不同的数据时,可以在屏幕上显示不同的内容,具体见175页的实验9“根据材料编程”里面介绍了一些屏幕操作的知识。
参考答案:https://tinylab.org/assembly/sy/sy1.htm