1. 程式人生 > 實用技巧 >垂直水平居中彙總

垂直水平居中彙總

一、實驗目的

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指令實現的,子程式的返回值儲存在一個暫存器中,子程式執行完畢後,主程式通過該暫存器將返回值賦給變數。