实验4 汇编应用编程和c语言程序反汇编分析

一、实验目的

1、理解80×25彩色字符模式显示原理

2、理解转移指令jmp,loop,jcxz的跳转原理,掌握使用其实现分支和循坏的用法

3、理解转移指令call,ret,retf的跳转原理,掌握组合使用call和ret/retf编写汇编子程序的方法,掌握参数传递方式

4、理解标志寄存器的作用

5、理解条件转移指令je,jz,ja,jg,jl等的跳转原理,掌握组合使用汇编指令cmp和条件转移指令实现分支和循环的用法

6、了解在visual stdio/Xcode等环境或利用gcc命令行参数反汇编C语言程序的方法,理解编译器生成的反汇编代码

7、综合应用寻址方式和汇编指令完成应用编程

 

二、实验准备

实验前,请复习/学习教材以下内容:

第9章 转移指令的原理

第10章 call和ret指令

第11章 标志寄存器

 

三、实验结论

1、实验任务1

教材[实验9 根据材料编程](P187-189)

编程:在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串“welcome to masm!”。

在内存地址空间中,B8000H~BFFFFH供32KB的空间,为80×25彩色字符模式的显示缓冲区。该显示缓冲区分为8页,每页4KB(≈4000B),每页有25行,一行可显示80个字符,一个字符占两个字节,共160个字节,低位字节存储字符的ASCII码,高位字节存储字符的颜色属性。

屏幕中间对应的显存位置的计算:以B800H作为段地址,要在屏幕中间显示三行字符串,这三行字符串应分别显示在第12、13、14行。要显示的字符串占16个字节,每个字符的颜色属性占16个字节,共32个字节,因为一行有160个字节,所以要让字符显示在屏幕中间,应从每行的第33列,即第64个字节开始存放字符及其颜色属性。第12行的开始处的偏移地址为1760,再加上64,等于1824,转换为十六进制就是720H。所以要显示的第一行字符串的起始位置偏移地址应为720H,加上160个字节就是第二行字符串起始位置的偏移地址,加上320个字节就是第3行字符串的起始位置的偏移地址。

字符串颜色属性的设置:根据属性字节的格式即可按位设置属性字节

黑底绿字:属性字节为00000010B,十六进制为02H;

绿底红字:属性字节为00100100B,十六进制为24H;

白底蓝字:属性字节为01110001B,十六进制为71H。

 1 assume cs:codesg
 2 data segment
 3     db 'welcome to masm!'
 4 data ends
 5 
 6 codesg segment
 7 start:  mov ax,data
 8         mov ds,ax    ;字符串的段地址
 9         mov di,0     ;字符串的偏移地址
10 
11         mov ax,0b800H 
12         mov es,ax     ;显示缓冲区的段地址  
13         mov si,720H   ;要显示的第一行字符串起始位置的偏移地址
14 
15         mov cx,16
16 s:      mov al,ds:[di]
17         mov es:[si],al      ;设置第一行字符串的ASCII码
18         mov es:[si+160],al  ;设置第二行字符串的ASCII码
19         mov es:[si+320],al  ;设置第三行字符串的ASCII码
20         mov byte ptr es:[si+1],02H     ;设置第一行字符串的颜色属性
21         mov byte ptr es:[si+160+1],24H ;设置第二行字符串的颜色属性
22         mov byte ptr es:[si+320+1],71H ;设置第三行字符串的颜色属性
23         inc di   ;字符串的偏移地址加1
24         add si,2 ;显示缓冲区的偏移地址加2
25         loop s
26     
27         mov ax,4c00H
28         int 21H
29 codesg ends
30 end start

将程序编译、连接后的执行结果如下:

 

2、实验任务2

编写子程序printStr,实现以指定颜色在屏幕上输出字符串。调用它,完成字符串输出。

 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   ;设置入口参数ds
10 
11     mov si, offset str  ;设置字符串起始地址的偏移地址
12     mov al, 2           ;设置字符串颜色
13     call 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     mov di, 0
27 s:  mov cl, [si]
28     mov ch, 0
29     jcxz over
30     mov ch, al
31     mov es:[di], cx
32     inc si
33     add di, 2
34     jmp s
35 
36 over:pop di
37      pop si
38      pop cx
39      pop bx
40      ret
41 
42 code ends
43 end start

汇编、运行程序,观察运行结果:

对源程序做如下修改:

把line3改为:

1 str db 'another try', 0

把line12改为:

1 mov  al,4

再次汇编、运行程序,观察运行结果:

基于运行结果,理解源代码,以及,组合使用转移指令call和ret实现子程序的原理与方法,在line18-40中:

line19-22,line36-39,这组对称使用的push、pop,这样用的目的是什么?

line19-22将bx、cx、si、di逐个入栈,line36-39使栈中的数据逐个出栈,并分别赋值给di、si、cx、bx,这样就可以让寄存器bx、cx、si、di恢复到原来的值。

line30的功能是什么?

寄存器cx的低位字节保存了字符的ASCII码,高位字节保存了字符的颜色属性,line30的功能就是将寄存器cx中有颜色的字符送到显存地址中。

 

3、实验任务3

使用任意文本编辑器,录入汇编程序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

阅读源代码,理解子程序num2str的汇编实现。

子任务1:

对task3.asm进行汇编、连接,得到可执行程序后,在debug中使用u命令反汇编,使用g命令执行到line15(程序退出之前),使用d命令查看数据段内容,观察是否把转换后的数字字符串“1984”存放在数据段中str标号后面的单元。

在debug中使用u命令进行反汇编,找到指令“mov ah,4ch”对应的cs:ip:076C:000E

用g命令执行到程序结束之前,然后用d命令查看数据段内容,发现转换后的数字字符串“1984”存放在数据段中str标号后面的单元:

子任务2:

对task3.asm源代码进行修改、完善,把task2.asm中用于输出以0结尾的字符串的子程序加进来,实现对转换后的字符串进行输出。

修改、完善后的代码:

 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 si,offset str
15     call 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 s:  mov cl, [si]
60     mov ch, 0
61     jcxz over
62     mov ch, 2  ;设置字符串颜色
63     mov es:[di], cx
64     inc si
65     add di, 2
66     jmp s
67 
68 over: pop di
69       pop si
70       pop cx
71       pop bx
72       ret
73 code ends
74 end start

运行结果:

把task3.asm源代码中,line3中的整数改成0~2559之间的任意数值,运行测试,观察结果。

 

4、实验任务4

 使用任意文本编辑器,录入汇编源程序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

汇编、连接、运行程序,输入一个字符串并以#结尾(比如,2020,bye#),观察运行结果。

结合运行结果,理解程序功能,了解软中断指令。具体地:

line12-19实现的功能是?

利用软中断的1号子功能,从键盘输入单个字符,并将字符保存到al中,通过cmp指令和je指令判断当前字符是不是#。如果当前字符是#,则指令“ cmp al,'#' "执行后zf=1,当zf=1时,满足je指令的转移条件;否则不满足转移条件,就将偏移地址si加1,继续从键盘输入字符。

line21-27实现的功能是?

利用软中断的2号子功能,在屏幕上输出单个字符。执行完s1的代码后,si是输入的最后一个字符的偏移地址,因为一个字符占一个字节,所以si的值对应输入字符的个数,将si赋给cx,得到输出单个字符的循环次数,然后再将si赋值0,作为第一个字符的偏移地址。line23-25就是利用2号子功能输出单个字符。然后偏移地址si加1,继续输出下一个字符。

 

5、实验任务5

 在visual stdio集成环境中,编写一个简单的包含有函数调用的c程序。代码如下:

 1 #include <stdio.h>
 2 int sum(int, int);
 3 int main() {
 4     int a = 2, b = 7, c;
 5     c = sum(a, b);
 6     return 0;
 7 }
 8 
 9 int sum(int x, int y) {
10     return (x + y);
11 }

在line7,line13分别设置断点,在调试模式下,查看反汇编代码。

分析反汇编代码,从汇编的角度,观察高级语言中参数传递和返回值是通过什么实现的,以及参数入栈顺序,返回值的带回方式,等等。


主函数的反汇编代码:

 1 #include <stdio.h>
 2 int sum(int, int);
 3 int main() {
 4 001717B0  push        ebp  
 5 001717B1  mov         ebp,esp  
 6 001717B3  sub         esp,0E4h  
 7 001717B9  push        ebx  
 8 001717BA  push        esi  
 9 001717BB  push        edi  
10 001717BC  lea         edi,[ebp-0E4h]  
11 001717C2  mov         ecx,39h  
12 001717C7  mov         eax,0CCCCCCCCh  
13 001717CC  rep stos    dword ptr es:[edi]  
14 001717CE  mov         ecx,offset _FD17926B_demo@cpp (017C003h)  
15 001717D3  call        @__CheckForDebuggerJustMyCode@4 (017130Ch)  
16     int a = 2, b = 7, c;
17 001717D8  mov         dword ptr [a],2   ;把2赋给a
18 001717DF  mov         dword ptr [b],7   ;把7赋给b
19     c = sum(a, b);
20 001717E6  mov         eax,dword ptr [b] ;把b赋值给寄存器eax
21 001717E9  push        eax               ;eax入栈,即参数b的值先入栈
22 001717EA  mov         ecx,dword ptr [a] ;把a赋值给寄存器ecx
23 001717ED  push        ecx               ;ecx入栈,即参数a的值入栈
24 001717EE  call        sum (017116Dh)    ;通过call指令调用sum()函数
25 001717F3  add         esp,8  
26 001717F6  mov         dword ptr [c],eax ;将寄存器eax的值赋给c
27     return 0;
28 001717F9  xor         eax,eax  ;通过异或使寄存器eax清零
29 }
30 001717FB  pop         edi  
31 001717FC  pop         esi  
32 001717FD  pop         ebx  
33 001717FE  add         esp,0E4h  
34 00171804  cmp         ebp,esp  
35 00171806  call        __RTC_CheckEsp (0171235h)  
36 0017180B  mov         esp,ebp  
37 0017180D  pop         ebp  
38 0017180E  ret

sum()函数的反汇编代码:

 1 int sum(int x, int y) {
 2 00171740  push        ebp  
 3 00171741  mov         ebp,esp  
 4 00171743  sub         esp,0C0h  
 5 00171749  push        ebx  
 6 0017174A  push        esi  
 7 0017174B  push        edi  
 8 0017174C  lea         edi,[ebp-0C0h]  
 9 00171752  mov         ecx,30h  
10 00171757  mov         eax,0CCCCCCCCh  
11 0017175C  rep stos    dword ptr es:[edi]  
12 0017175E  mov         ecx,offset _FD17926B_demo@cpp (017C003h)  
13 00171763  call        @__CheckForDebuggerJustMyCode@4 (017130Ch)  
14     return (x + y);
15 00171768  mov         eax,dword ptr [x] ;将参数x的值赋给寄存器eax
16 0017176B  add         eax,dword ptr [y] ;寄存器eax中的值与参数y的相加,并将和保存到eax中
17 }
18 0017176E  pop         edi  
19 0017176F  pop         esi  
20 00171770  pop         ebx  
21 00171771  add         esp,0C0h  
22 00171777  cmp         ebp,esp  
23 00171779  call        __RTC_CheckEsp (0171235h)  
24 0017177E  mov         esp,ebp  
25 00171780  pop         ebp  
26 00171781  ret  

参数b先入栈,然后参数a入栈,参数的入栈顺序是参数列表自右向左。在主程序中调用子程序是通过call指令实现的,子程序的返回值保存在一个寄存器中,子程序执行完毕后,主程序通过该寄存器将返回值赋给变量。

 

 

 

posted @ 2020-12-12 19:35  whr09  阅读(217)  评论(4编辑  收藏  举报