一、实验目的
1、理解并掌握汇编程序组成与结构
2、掌握汇编语言源程序编写→汇编→链接→调试的工具和方法
3、理解汇编程序中地址表示、段寄存器的用法
4、理解和掌握寄存器间接寻址方式[bx]
5、通过汇编指令loop的使用理解编程语言中循坏的本质
二、实验准备
1、学习/复习第5章使用[bx]和loop实现循坏的编程应用示例(教材5.5节,5.8节)
2、复习第3章“栈”的知识
3、结合第4章课件,复习完整汇编程序编写→汇编→连接→运行→调试的方法
4、复习8086汇编中内存单元地址的表示,以及段寄存器DS,SS,ES,CS的用途
三、实验结论
1、实验任务1
使用任意一款文本编辑器,编写8086汇编源程序ex1.asm。源代码如下:
;ex1.asm assume cs:code code segment mov ax, 0b810h mov ds, ax mov byte ptr ds:[0], 1 mov byte ptr ds:[1], 1 mov byte ptr ds:[2], 2 mov byte ptr ds:[3], 2 mov byte ptr ds:[4], 3 mov byte ptr ds:[5], 3 mov byte ptr ds:[6], 4 mov byte ptr ds:[7], 4 mov ah, 4ch int 21h code ends end
源程序编写完以后,使用masm、link对ex1.asm进行汇编、链接,得到可执行文件ex1.exe,并运行该可执行文件。该源程序的汇编、链接的命令以及运行结果的截图如下:使用debug加载可执行ex1.exe后,使用d命令查看程序段前缀PSP所占的256个字节,如下图所示:使用r命令查看寄存器,并结合可执行文件中寄存器CX的值,使用u命令对ex1.exe进行精确反汇编,反汇编的结果如下图所示:使用g命令执行到程序退出执行之前(即源代码文件中line16之前),观察结果。(程序的结果并未输出到显示器上)
2、实验任务2
使用任意一款文本编辑器,编写8086汇编程序ex2.asm。源代码如下:
1 ; ex2.asm 2 assume cs:code 3 code segment 4 mov ax, 0b810h 5 mov ds, ax 6 7 mov bx, 0 8 mov ax, 101H 9 mov cx, 4 10 s: mov [bx], ax 11 add bx, 2 12 add ax, 101H 13 loop s 14 15 mov ah, 4ch 16 int 21h 17 code ends 18 end
源程序编写完以后,使用masm、link对ex1.asm进行汇编、链接,得到可执行文件ex1.exe,并运行该可执行文件。该源程序的汇编、链接的命令以及运行结果的截图如下:使用r命令查看寄存器,并结合可执行文件中寄存器CX的值,使用u命令对ex2.exe进行精确反汇编,反汇编的结果如下图所示:灵活使用t命令/p命令、g命令,对ex2.exe进行调试。例如,先用t命令执行前2条指令,完成对DS的赋值;再用g命令进行调试,直接执行到CS:0016;此时,下一条指令即为循坏指令loop,所以使用p命令进行调试,跳过循坏体内部,直接得到循环的结果。执行过程的命令如下图:最后再用t命令执行程序返回的两条指令。重新调试这段源程序,并且最后用p命令执行中断指令int 21,发现执行后,显示出“Program terminated normally”,返回到debug中。执行结果如下图:把ex2.asm中line9mov cx,4改成mov cx,8,保存后重新汇编、链接、运行,所得结果如下图所示:
结合上述实验和观察,分析、对比ex2.asm和ex1.asm,它们实现的是否是相同的功能和效果?在具体实现上有什么不同?
由上述实验结果可知,ex2.asm与ex1.asm具有相同的功能和效果,都是向b810:0~7中写入了1 1 2 2 3 3 4 4,并在屏幕上输出4个相同的图案。不同的是ex1.asm是直接对显存地址进行赋值,而ex2.asm是通过寄存器间接赋值,并且对于多次重复的赋值操作,采用了loop用循环来实现。
3、实验任务3
综合使用loop,[bx],编写完整汇编程序,实现向内存b800:07b8开始的连续16个字单元重复填充字数据0237H。汇编源程序如下:
assume cs:code code segment mov ax,0b800h ;在汇编源程序中,数据不能以字母开头,所以要在前面加0 mov ds,ax mov bx,07b8h mov cx,16 s: mov [bx],0237h inc bx inc bx loop s mov ax,4c00h int 21h code ends end
对该汇编源程序进行汇编和连接,如下图:
使用cls命令清屏后,运行该源程序,运行如下图:
把填充的字数据,从0237H改成0239H,再次保存后,进行汇编、连接和运行,结果如下图:
把填充的字数据,从0237H改成0437H,再次保存后,进行汇编、连接和运行,结果如下图:
猜测并分析,这个字数据中高位字节里存放的是什么信息,低位字节里存放的是什么信息。
当填充的字数据为0237H时,运行结果是16个绿色的”7“;改变低位字节,把填充的字数据改成0239H后,运行结果是16个绿色的”9“,颜色没变,但数字变了。再改变高位字节,把填充的字数据改成0437H后,运行结果是16个红色的”7“,即数字没变,但颜色变了。所以猜测这个字数据的高位字节存放输出信息的颜色,低位字节存放输出信息的内容。在该猜测下,我先把填充的字数据改成0238H,运行结果如下:
发现改了低位字节后,运行结果果然是改变了输出信息的内容,没改变颜色。再改变高位字节,将填充的字数据改成0638H,运行结果如下:
4、实验任务4
编写完整汇编源程序,实现向内存0:200~0:23F依次传送数据0~63(3FH)。汇编源程序如下:
assume cs:code code segment mov ax,0020h mov ds,ax mov bx,0 mov cx,64 s: mov [bx],bx inc bx loop s mov ax,4c00h int 21h code ends end
源程序编写完成以后,进行汇编和连接,操作过程如下图:
然后用debug工具进行调试,用r命令查看寄存器,并根据寄存器CX的值对该程序进行反汇编。用g命令直接执行到循环之前:
再用p命令执行到循环结束。在程序退出之前,用d命令查看内存0:200~0:23F,确认是否将0~3F传送至此段内存中。结果如下图:
选做:利用栈的特性,综合使用loop,push实现以上内容。
由于8086CPU的入栈操作是从高地址到低地址单元方向,所以入栈时从所传送数据的最大值开始,即应该将3fH最先进栈。又因为8086CPU的入栈和出栈操作都是以字为单位进行的,所以在传送数据的时候,必须将相邻的两个数据一起入栈。将3f3eH放到寄存器bx中,同时进栈,则接下来应该将3d3cH入栈,所以需要将bx的低8位寄存器bl和高8位寄存器bh都减2,依此类推。
汇编源程序如下:
assume cs:code code segment mov ax,0020h mov ss,ax mov sp,0040h mov bx,3f3eh mov cx,64
s: push bx sub bl,2 sub bh,2 loop s mov ax,4c00h int 21h code ends end
对上面的源程序进行汇编和连接,再用debug工具进行调试。用r命令查看寄存器,并根据寄存器CX的值,对源程序进行反汇编:
直接用g命令执行到程序结束之前,再用d命令查看0:200~0:23f,确认将数据0~3F传送到此段内存区域中,查看结果如下图:
5、实验任务5
完成教材实验4(3)(P121)
下面的程序的功能是将“mov ax,4c00h"之前的指令复制到内存0:200处,补全程序。上机调试,跟踪运行结果。
1 assume cs:code 2 code segment 3 4 mov ax,cs 5 mov ds,ax 6 7 mov ax,0020h 8 mov es,ax 9 10 mov bx,0 11 mov cx,17h 12 s: mov al,[bx] 13 mov es:[bx],al 14 inc bx 15 loop s 16 17 mov ax,4c00h 18 int 21h 19 20 code ends 21 end
提示:
(1)复制的是什么?从哪里到哪里? (2)复制的是什么?有多少个字节?你如何知道要复制的字节的数量? 该程序实现的功能是将“mov ax,4c00h”之前的指令复制到内存0:200处,即复制从第4行开始到第16行结束的指令的机器码。由已给出的代码“mov ax 0020h”和“mov es,ax”可知,应该是用段寄存器es存放目标内存空间的段地址。那么要实现程序的复制,还需要获得代码段的段地址,所以代码“mov ax,____"和"mov ds,ax"应该是将代码段的段地址赋给段寄存器ds,因为代码段的段地址在cs中,所以该空应该填”cs“。寄存器cx中应该存放循环的次数,由于一开始不知道这段代码有多少个字节,所以可以先填”0“,然后将该源程序进行汇编和连接,再用debug工具进行反汇编,得到正确的机器码的字节数,再修改cx的值,具体操作如下图: 从以上的反汇编可知,该源程序有0017h个字节,所以cx的值为0017h。修改cx的值以后,重新对源程序进行汇编、连接,并用debug工具调试。 将源程序反汇编,并用g命令直接执行到中断程序之前,然后用d命令查看内存0:200中的内容,发现内存0:200中的前17h个字节中的内容已经变成了源程序的机器码。6、实验任务6(选做*)
在Linux环境下,编写、汇编、运行、调试一个32位的Intel风格的x86汇编程序。
第1步,使用vim或其他任意文本编辑器,编写一个32位的Intel风格的x86汇编程序example.asm。
1 ; example.asm 2 3 global _start 4 5 section .data 6 msg db "a simple test", 0xa 7 len equ $ - msg 8 9 section .text 10 _start: 11 mov eax, 4 12 mov ebx, 1 13 mov ecx, msg 14 mov edx, len 15 int 0x80 ; 调用linux下的int 0x80中断的4号子功能,输出字符串 16 17 mov eax, 1 18 mov ebx, 0 19 int 0x80 ; 调用linux下的int 0x80中断的1号子功能,退出
在Linux环境下,使用vim编辑器编写以上源程序,如下图:
第2步,使用nasm,对example.asm进行汇编。
nasm -f elf32 example.asm
其中,选项-f表示指定目标文件格式。这里指定目标文件格式是elf32格式。
一开始用以上命令进行编译时,出现了错误example.asm:1:error:parser:instruction expected,后来发现是老师给的代码中用于定义全局变量的”global“打成了”glogal“,由于无脑抄代码,所以没有发现。修改以后,源程序就可以被成功编译了(上面截图的代码是修改前的,不想重新截图了)。
编译成功后,用ls命令查看目录中是否生成了目标文件example.o,编译及查看结果如下图:
第3步,使用ld,用example.o进行链接。
ld -m elf_i386 example.o -o example
其中,选项-m指定仿真模式。这里指定仿真Intel 32的模式。选项-o用于指定可执行文件的名称。这里指定生成的可执行文件名是example。
链接成功以后会生成可执行文件example,链接和用ls命令查看的结果如下:
注:第一次用ld进行链接时并没有成功,而是被系统提示sudo apt install binutils,后来查了一下知道ld是GNU binutils工具集下的一个重要工具,执行安装命令以后,就可以成功链接了。
第4步,执行可执行文件example。
./example
执行后,在shell命令终端查看返回值:
echo $?
执行结果和查看结果如下图:
echo $?的返回值是0,对应源码文件中line18寄存器ebx的值。
将example.asm中line18行中寄存器ebx的值改成别的数值,比如7。重新汇编、链接、运行。用echo $?查看返回值,发现这次返回值为7,如下图:
四、实验总结