垂直水平居中彙總
一、實驗目的
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 ;字串的偏移地址 1011 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指令實現的,子程式的返回值儲存在一個暫存器中,子程式執行完畢後,主程式通過該暫存器將返回值賦給變數。