四个太阳

导航

实验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后被访问。

posted on 2020-12-15 22:51  四个太阳  阅读(246)  评论(2)    收藏  举报