实验4 汇编应用编程和c语言程序反汇编分析
一、实验结论
1.实验任务1
编程:在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串'welcome to masm!'
实现以上实验任务的源代码task1.asm如下所示:
1 assume cs:code, ds:data 2 data segment 3 db 'welcome to masm!' ;存放字符的数据 4 db 00000010B, 00100100B, 01110001B ;存放字符颜色属性的数据 5 data ends 6 code segment 7 start:mov ax, data 8 mov ds, ax ;设置数据段地址,指明数据从哪里来 9 mov ax, 0b800H 10 mov es, ax ;设置附加段地址,指明数据到哪里去 11 mov bx, 16 ;获取颜色数据的偏移地址 12 mov di, 1824 ;附加段数据的偏移地址 13 mov cx, 3 ;设置循环次数为3 14 s1: mov si, 0 ;数据段数据的偏移地址 15 push cx ;使用cx之前保存其原来的数据 16 mov cx, 16 ;设置循环次数为16 17 s2: mov al, ds:[si] ;取第(si+1)个字符数据 18 mov ah, ds:[bx] ;取第(bx-15)个颜色属性数据 19 mov es:[di], ax ;将携带颜色属性的字符写入到显存中 20 add di ,2 ;附加段的偏移地址自增2,以便下一次写入字符数据 21 inc si ;取下一个字符的ASCII码 22 loop s2 23 add di, 128 ;开始写入下一行 24 inc bx ;改变下一行字符的颜色属性 25 pop cx ;恢复cx的原先值 26 loop s1 27 mov ah, 4ch 28 int 21h 29 code ends 30 end start
将上述源程序task1.asm汇编、链接为程序task1.exe,其运行结果如下所示:

实现思路:
对于本实验任务来说,最为关键的还是需要理解 80×25 彩色模式是怎样的一个模式。而了解了80×25 彩色模式之后就可以推算出字符应该写入到
显存的何处位置,才会有中央展示的效果。80×25的彩色字符模式,即一屏25行80列。每一行显示一个字符需要两个字节(低位字节存放字符的ASCⅡ
码值,高位字节存放字符的显示属性),一共160个字节。默认,在显存第0列显示时,对应的显存空间为B8000H ~ B8F9FH。这里展示我们需要写入
三行数据的显存偏移地址:

由80×25的彩色组织模式可以得出,我们需要连续写入32个字节的数据。从上述表中可以看出我们需要在偏移地址为1760位置处开始写入数据,但是在
1760处开始写入数据的话,显示的数据就不是在屏幕中央了。所以,根据写入的32字节数据以及1760~1919这个范围,我们可以计算出在中央显示数据
的显存偏移地址为[(1760+80)-16]=1824,同时也可以计算出下一行数据应该写入的显存偏移地址为(1824+160)=1984。但是为了只使用一个di寄存器来
间接寻址,我们需要考虑第一行最后一个数据后到第二行第一个数据开始所相差的字节数为[(80-16)*2] = 128。这便是“add di, 128”这条语句的由来。解
决了这些问题之后,只需要考虑使用双重循环即可,内层循环负责写入一行数据,外层循环控制写入几行数据。设置双重循环期间需要注意cx寄存器的
使用,在使用cx控制内层循环的时候需要将原cx的值保存到栈当中,然后再去设置cx寄存器的值。
2. 实验任务2
实验任务:编写子程序printStr,实现以指定颜色在屏幕上输出字符串。调用它,完成字符串输出。
使用任意一款文本编辑器,编写8086汇编源程序task2.asm。源代码如下:
1 assume cs:code, ds:data 2 data segment 3 str db 'try', 0 4 data ends 5 6 code segment 7 start: 8 mov ax, data 9 mov ds, ax 10 11 mov si, offset str ;data数据段的间接寻址方式 12 mov al, 2 ;设置颜色入口参数为绿色 13 call printStr ;调用子程序printStr 14 15 mov ah, 4ch 16 int 21h 17 18 printStr: 19 push bx 20 push cx 21 push si 22 push di 23 ;将寄存器的状态保存到栈中 24 mov bx, 0b800H 25 mov es, bx ;设置附加段地址,指明数据到哪里去 26 s: mov cl, [si] ;将data数据段中偏移地址为si的字节数据存放到cl寄存器中 27 mov ch, 0 ;将寄存器ch设置为0 28 jcxz over ;如果cx寄存器的值为零则跳转到over继续执行;否则,不作任何操作 29 mov ch, al ;将颜色入口参数绿色放到ch寄存器中 30 mov es:[di], cx ;将带有颜色属性的字符写入到显存空间中 31 inc si ;取data数据段中的下一个字节数据 32 add di, 2 ;附加段的偏移地址自增2,以便下一次写入 33 jmp s ;无条件跳转到s继续执行 34 35 over: ;从栈中恢复寄存器的状态 36 pop di 37 pop si 38 pop cx 39 pop bx 40 ret 41 42 code ends 43 end start
将上述汇编源程序task2.asm汇编、链接成程序task2.exe,其运行结果如下所示:

对源程序task2.asm做如下的修改:
把line3改为:
1 str db 'another try', 0
把line12改为:
1 mov al, 4
将更改后的汇编源程序task2.asm汇编、链接成程序task2.exe,其运行结果如下所示:

问题分析:
(1)line19-22, line36-39,这组对称使用的push、pop,这样用的目的是什么?
答:由于line24~line34之间的汇编代码改变了bx、cx、si、di这四个寄存器的值,而且这些寄存器的改变是出现在子程序当中的。那么在子程序被调用的
时候这四个寄存器的原有值会被更改,当子程序返回之前将四个寄存器的原值从栈中恢复,就不会导致主调函数中的寄存器值出现意外的值。其目
的就是在子程序被调用之后将各寄存器的状态恢复到子程序被调用之前的状态。
(2)ine30的功能是什么?
答:line30行的代码内容为“mov es:[di], cx”。首先,根据源代码先前描述知道寄存器es的值为B800H,而di初始值为0。其次,cx的低位是存储字符的
ASCII值(字符的ASCII值来自于data数据段),而cx的高位是存储字符颜色的属性值(属性值来自于入口参数al)。那么“mov es:[di], cx”的功能
就是“将带有颜色属性的字数据字符写入到地址为B800:0的显存空间中”。
3. 实验任务3
使用任意一款文本编辑器,编写8086汇编源程序task3.asm。源代码如下:
1 assume cs:code, ds:data 2 data segment 3 x dw 1984 4 str db 16 dup(0) 5 data ends 6 7 code segment 8 start: 9 mov ax, data 10 mov ds, ax 11 mov ax, x 12 mov di, offset str 13 call num2str 14 15 mov ah, 4ch 16 int 21h 17 18 num2str: 19 push ax 20 push bx 21 push cx 22 push dx 23 24 mov cx, 0 25 mov bl, 10 26 s1: 27 div bl 28 inc cx 29 mov dl, ah 30 push dx 31 mov ah, 0 32 cmp al, 0 33 jne s1 34 s2: 35 pop dx 36 or dl, 30h 37 mov [di], dl 38 inc di 39 loop s2 40 41 pop dx 42 pop cx 43 pop bx 44 pop ax 45 46 ret 47 code ends 48 end start
实验任务:
(1)对task3.asm进行汇编、链接,得到可执行程序后,在debug中使用u命令反汇编,使用g命令执行到line15(程序退出之前),使用d命令查看数据段
内容,观察是否把转换后的数字字符串'1984'存放在数据段中str标号后面的单元。

(2)对task3.asm源代码进行修改、完善,把task2.asm中用于输出以0结尾的字符串的子程序加进来,实现对转换后的字符串进行输出。
答:实现对转换后的字符串进行输出的task3.asm完整源程序如下所示:
1 assume cs:code, ds:data 2 data segment 3 x dw 1984 4 str db 16 dup(0) 5 data ends 6 7 code segment 8 start: 9 mov ax, data 10 mov ds, ax 11 mov ax, x 12 mov di, offset str 13 call num2str 14 mov al, 2 ;设置颜色入口参数为绿色 15 call printStr ;调用printStr子程序 16 17 mov ah, 4ch 18 int 21h 19 20 num2str: 21 push ax 22 push bx 23 push cx 24 push dx 25 26 mov cx, 0 27 mov bl, 10 28 s1: 29 div bl 30 inc cx 31 mov dl, ah 32 push dx 33 mov ah, 0 34 cmp al, 0 35 jne s1 36 s2: 37 pop dx 38 or dl, 30h 39 mov [di], dl 40 inc di 41 loop s2 42 43 pop dx 44 pop cx 45 pop bx 46 pop ax 47 48 ret 49 50 printStr: 51 push bx 52 push cx 53 push si 54 push di 55 56 mov bx, 0b800H 57 mov es, bx 58 mov di, 0 59 mov si, offset str ;将数据段的偏移地址指向存放数字字符的str 60 s: mov cl, [si] 61 mov ch, 0 62 jcxz over 63 mov ch, al 64 mov es:[di], cx 65 inc si 66 add di, 2 67 jmp s 68 69 over: pop di 70 pop si 71 pop cx 72 pop bx 73 ret 74 75 code ends 76 end start
将更改后的汇编源程序task3.asm汇编、链接成程序task3.exe,其运行结果如下所示:

4. 实验任务4
使用任意一款文本编辑器,编写8086汇编源程序task4.asm。源代码如下:
1 assume cs:code, ds:data 2 data segment 3 str db 80 dup(?) 4 data ends 5 6 code segment 7 start: 8 mov ax, data 9 mov ds, ax 10 mov si, 0 11 12 s1: 13 mov ah, 1 14 int 21h 15 mov [si], al 16 cmp al, '#' 17 je next 18 inc si 19 jmp s1 20 next: 21 mov cx, si 22 mov si, 0 23 s2: mov ah, 2 24 mov dl, [si] 25 int 21h 26 inc si 27 loop s2 28 29 mov ah, 4ch 30 int 21h 31 code ends 32 end start
将上述的task4.asm汇编、链接得到程序task4.exe。运行此程序然后输入一个字符串并以#结束,程序运行结果如下所示:

实验任务:
(1)line12-19实现的功能是?
1 mov ah, 1 2 int 21h ;使用软中断的1号子功能,从键盘中读入一个字符 3 mov [si], al ;将从键盘中读入的一个字符存放到data数据段中 4 cmp al, '#' ;将读入的字符与'#'比较 5 je next ;如果读入的字符是'#',那么跳转到next程序段继续执行;否则不作任何操作 6 inc si ;data数据段的偏移地址自增1 7 jmp s1 ;继续读入下一个字符
答:综合上述汇编代码注释,line12-19实现的功能是“一直从键盘读入字符,并将读入的字符存放到data数据段当中,直到遇到输入符号为'#'为止”。
(2)line21-27实现的功能是?
1 mov cx, si ;设置循环的执行次数为读入字符的个数 2 mov si, 0 ;数据段中取数据的间接寻址方式 3 s2: mov ah, 2 4 mov dl, [si] ;将数据段中偏移量为si的字节数据存放到dl寄存器中 5 int 21h ;使用软中断的2号子功能,在显示屏上输出存放在dl中的字符 6 inc si ;取data段的下一个字节数据 7 loop s2
答:综合上述汇编代码注释,line21-27实现的功能是“将存放在data数据段中的字符打印到显示屏上,打印的字符个数就是读入的字符个数”。
5. 实验任务5
在visual studio集成环境中,编写一个简单的包含有函数调用的c程序。代码如下:
1 #include<stdio.h> 2 int sum(int, int); 3 4 int main() { 5 int a = 2, b = 7, c; 6 c = sum(a, b); 7 return 0; 8 } 9 10 int sum(int x, int y) { 11 return (x + y); 12 }
在line6, line11分别设置断点,在调试模式下,查看反汇编代码。line6的反汇编代码如下图所示:

line11的反汇编代码如下图所示:

实验任务:
分析反汇编代码,从汇编的角度,观察高级语言中参数传递和返回值是通过什么实现的,以及参数的入栈顺序,返回值的带回方式。
line6反汇编代码的部分注释:
5: int a = 2, b = 7, c; mov dword ptr [ebp-8],2 ;在main函数栈帧中距栈底为8的位置定义双字变量a并为其赋初值2 mov dword ptr [ebp-14h],7 ;在main函数栈帧中距栈底为14的位置定义双字变量b并为其赋初值7 6: c = sum(a, b); mov eax,dword ptr [ebp-14h] push eax ;先将双字变量b压栈 mov ecx,dword ptr [ebp-8] push ecx ;然后将双字变量a压栈 call 0040137F add esp,8 mov dword ptr [ebp-20h],eax ;在main函数栈帧中距栈底为20的位置定义双字变量b并为其赋sum函数的返回值eax 7: return 0; xor eax,eax ;将eax快速置零 8: }
line11反汇编代码的部分注释:
10: int sum(int x, int y) { push ebp ;使用栈底指针寄存器之前,先将其原先值保存到栈中 mov ebp,esp ;原栈顶变为新栈底,可以看作sum函数压栈,此时栈底为main方法栈帧 sub esp,0C0h ;重新设置栈顶指针寄存器的值,为新栈开辟内存空间 push ebx push esi push edi ;保存寄存器状态以便恢复 lea edi,[ebp+FFFFFF40h] mov ecx,30h mov eax,0CCCCCCCCh rep stos dword ptr es:[edi] ;串传送指令,将eax寄存器的值转送到es:[edi]的内存空间中,传送方向取决于DF标志寄存器的值,而传送次数取决于ecx的值。 mov ecx,40C003h call 0040120D 11: return (x + y); mov eax,dword ptr [ebp+8] ;在main函数栈帧中取距原esp为8的双字数据即副本a,注意main函数中定义的a变量存储在原ss:[ebp-8]的位置。 add eax,dword ptr [ebp+0Ch];在main函数栈帧中取距原esp为12的双字数据即副本b,然后与eax即副本a做加法运算并将运算结果送回eax中, ;注意main函数中定义的变量b存储在原ss:[ebp-14]的位置。 12: } pop edi pop esi pop ebx ;将寄存器的状态复原 add esp,0C0h ;还原esp,与“sub esp,0C0h”操作相对应 cmp ebp,esp call 00401217 mov esp,ebp ;新栈底恢复为原栈顶,相当于sum函数出栈,此时栈中只有main方法 pop ebp ;还原原栈底
ret
答:根据上述反汇编代码的注释分析,从汇编的角度来看,高级语言中参数传递是通过栈来实现的,并且靠近主调函数栈顶中的参数相当于是形参,
而靠近主调函数栈底中的参数相当于实参,这就是在高级语言的值传递过程中,形参和实参互不影响的原因。然后被调函数的返回值在该例中
是通过eax寄存器来存储的,程序返回到主调函数可以通过eax寄存器来获取被调函数的返回值。至于函数调用过程中参数的如入栈顺序可以从
line6的反汇编代码中看出,在该例主调函数的函数调用过程中,参数b先入栈,参数a后入栈;而在被调函数的执行过程中,副本a先被访问,
副本b后被访问。
浙公网安备 33010602011771号