0 寫在前面
爲了更深刻的瞭解程序的實現原理,近期我學習了IBM-PC相關原理,並手工編寫了一些x86彙編程序。html
在2017年的計算機組成原理中,曾對MIPS體系結構及其彙編語言有過必定的瞭解,考慮到x86體系結構在目前的普遍應用,我經過兩個月左右的時間對x86的相關內容進行了學習。git
在《x86彙編語言實踐》系列中(包括本篇、x86彙編語言實踐(1)、x86彙編語言實踐(2)、x86彙編語言實踐(3)以及x86彙編語言複習筆記),我經過幾個具體案例對x86彙編語言進行實踐操做,並記錄了本身再編寫彙編代碼中遇到的困難和心得體會,與各位學習x86彙編的朋友共同分享。github
我將我編寫的一些彙編代碼放到了github上,感興趣的朋友能夠點擊屏幕左上角的小貓咪進入個人github,或請點擊這裏下載源代碼。學習
1 程序設計複習1
1-1 練習要點
-
字符串中查找指定字符url
-
字符串中刪除指定字符(使用快慢指針)spa
-
子程序調用的堆棧參數傳遞.net
1-2 實現思路
-
在數據段存儲好待查找的CHAR,和目標字符串STR1,並將兩者初始化設計
-
主程序中首先將CHAR和STR1壓棧3d
-
調用FIND_CH子程序查找是否有CHAR指針
-
若找到CHAR則調用DELX刪除STR1中的X
-
爲了在STR1的原內存空間上操做字符串的修改動做,採用快慢指針的方式進行刪除。
1-3 重點難點
-
參數傳遞:使用堆棧進行參數傳遞,須要將參數壓棧,注意子程序返回時,必須增長一個常數偏移量RET X。這裏的X爲壓入參數所佔的字節數,一般爲2的倍數,以保證堆棧平衡
-
子程序保存現場:在子程序中,每每要用到不少寄存器,但咱們但願在子程序返回時,調用子程序位置處周圍的變量仍能恢復,這就須要在調用的子程序中保存現場,即子程序中所用到或修改的全部寄存器,都必須壓棧處理
-
子程序中的堆棧尋址:使用BP寄存器尋址,這是爲了避免修改SP指針,避免弄亂堆棧棧頂指針SP
-
快慢指針:與高級語言程序設計中的思路相似,首先將快慢指針指向STR1的頭部,以後循環STR1的長度LEN次,若快指針SI指向的位置的字符不爲CHAR,則將SI複製到慢指針DI,不然只將SI++。這裏用到的技巧是可使用LODSB和STOSB自動實現快指針SI與慢指針DI的自增操做。
1-4 代碼實現
1 STACK SEGMENT PARA STACK 2 DW 100H DUP(?) 3 STACK ENDS 4 5 DATA SEGMENT PARA 6 LEN EQU 12 7 CHAR DB 'X' 8 STR1 DB 'CHENQIXIAN',13,10,'$' 9 MSG1 DB 'X IN CHENQIXIAN',13,10,'$' 10 MSG2 DB 'NOT FOUND X IN CHENQIXIAN',13,10,'$' 11 STR2 DB LEN DUP(?) 12 DATA ENDS 13 14 CODE SEGMENT PARA 15 ASSUME CS:CODE,DS:DATA,SS:STACK 16 OUTPUT MACRO MSG 17 PUSH AX 18 PUSH DX 19 MOV DX,OFFSET MSG 20 MOV AH,9 21 INT 21H 22 POP DX 23 POP AX 24 ENDM 25 26 DELX PROC 27 DELETEX: 28 PUSH BP 29 MOV BP,SP 30 PUSH SI 31 PUSH DI 32 PUSH CX 33 PUSH AX 34 35 MOV SI,[BP+4] 36 MOV DI,[BP+6] 37 MOV CX,LEN 38 39 DELX_LP: 40 LODSB 41 CMP AL,'X' 42 JE DELX_CONTINUE 43 STOSB 44 DELX_CONTINUE: 45 LOOP DELX_LP 46 47 DELETEXRET: 48 MOV AL,'$' 49 STOSB 50 POP AX 51 POP CX 52 POP DI 53 POP SI 54 POP BP 55 RET 4 56 DELX ENDP 57 58 FIND_CH PROC 59 FINDCHAR: 60 PUSH BP 61 MOV BP,SP 62 PUSH AX 63 PUSH DI 64 PUSH CX 65 66 MOV AX,[BP+4] 67 MOV DI,[BP+6] 68 MOV CX,LEN 69 70 CLD 71 REPNZ SCASB 72 JZ FOUND 73 OUTPUT MSG2 74 JMP SHORT FINDCHARRET 75 FOUND: 76 OUTPUT MSG1 77 MOV DX,OFFSET STR1 78 PUSH DX 79 PUSH DX 80 CALL DELX 81 FINDCHARRET: 82 POP CX 83 POP DI 84 POP AX 85 POP BP 86 RET 4 87 FIND_CH ENDP 88 89 MAIN PROC FAR 90 MAINPROC: 91 MOV AX,DATA 92 MOV DS,AX 93 MOV ES,AX 94 95 MOV DX,OFFSET STR1 96 PUSH DX 97 MOV DL,CHAR 98 XOR DH,DH 99 PUSH DX 100 CALL FIND_CH 101 102 EXIT: 103 MOV AX,4C00H 104 INT 21H 105 MAIN ENDP 106 107 CODE ENDS 108 END MAIN
1-5 實現效果截圖
1-5-1 程序運行結果
經驗證,發現輸出結果符合預期
1-5-2 查看刪除後內存中新的字符串
經驗證,發現內存中的結果符合預期
2 程序設計複習2
2-1 練習要點
-
字符的輸入輸出
-
數字讀入存儲邏輯
-
數字的最優輸出方式
2-2 實現思路
-
首先爲讀入字符和輸出數字分別單獨編寫子程序
-
主程序中循環調用讀入字符,因爲題目固定讀入兩位十進制數,所以讀入的第一個數乘10加上第二個讀入的數,即爲讀入的數字
-
在輸出上的改進:還是除10顯示,但此次保存餘數。爲了獲得正序輸出,將每次的餘數壓棧,這樣在顯示的時候就是從高位向低位顯示了。此外,在輸出時對前導0進行了過濾處理,須要注意的是當遇到第一個非0數字後,須要將標誌位置1,這樣之後的數字0就能夠正常顯示
2-3 代碼實現
1 STACK SEGMENT PARA STACK 2 DW 100H DUP(?) 3 STACK ENDS 4 5 DATA SEGMENT PARA 6 LEN EQU 5 7 X DB 0 8 Y DB 0 9 Z DB ? 10 NL DB 13,10,'$' 11 DATA ENDS 12 13 CODE SEGMENT PARA 14 ASSUME CS:CODE,DS:DATA,SS:STACK 15 NEWLINE MACRO 16 PUSH AX 17 PUSH DX 18 MOV DX,OFFSET NL 19 MOV AH,9 20 INT 21H 21 POP DX 22 POP AX 23 ENDM 24 GETNUM PROC 25 INPUT: 26 MOV AH,1 27 INT 21H 28 SUB AL,30H 29 XOR AH,AH 30 RET 31 GETNUM ENDP 32 33 OUTPUT PROC 34 PRINT: 35 PUSH DX 36 PUSH CX 37 PUSH BX 38 PUSH AX 39 NEWLINE 40 MOV CX,LEN 41 MOV BX,10 42 PRINT_LP1: 43 XOR DX,DX 44 DIV BX 45 PUSH DX 46 LOOP PRINT_LP1 47 48 MOV CX,LEN 49 MOV BX,0 50 PRINT_LP2: 51 POP DX 52 CMP DL,0 53 JNE PRINT_LP2_1 54 CMP BX,0 55 JZ PRINT_LP2_2 56 PRINT_LP2_1: 57 MOV BX,1 58 MOV AH,2 59 OR DL,30H 60 INT 21H 61 62 PRINT_LP2_2: 63 LOOP PRINT_LP2 64 POP AX 65 POP BX 66 POP CX 67 POP DX 68 RET 69 OUTPUT ENDP 70 71 72 MAIN PROC FAR 73 MAINPROC: 74 MOV AX,DATA 75 MOV DS,AX 76 MOV ES,AX 77 78 CALL GETNUM 79 MOV BL,10 80 MUL BL 81 MOV X,AL 82 CALL GETNUM 83 ADD X,AL 84 85 CALL GETNUM 86 MOV BL,10 87 MUL BL 88 MOV Y,AL 89 CALL GETNUM 90 ADD Y,AL 91 92 MOV AL,X 93 MOV BL,Y 94 MUL BL 95 96 CALL OUTPUT 97 98 EXIT: 99 MOV AX,4C00H 100 INT 21H 101 MAIN ENDP 102 103 CODE ENDS 104 END MAIN
2-4 運行結果
顯然,運行結果符合預期。
3 程序設計複習3
3-1 練習要點
-
字符串讀取:0AH號中斷調用
-
字符串拷貝
-
子程序調用參數的傳遞與保持
3-2 實現思路
-
首先爲讀入字符串和輸出字符串分別單獨編寫子程序
-
輸入待插入字符串後,首先調用第一次拷貝字符串子程序,判斷條件爲讀取到空格即中止拷貝。注意邊界條件的判斷,以及最後一次拷貝後SI與DI的保持
-
緊接着在主程序中將SI壓棧保存,將SI指向待插入字符串首地址,調用插入子程序。將待插入字符串拼接到目標串尾部
-
最後將SI彈出棧恢復,即又指向原列表空格後的第一個字符的位置處,調用第二次拷貝字符串子程序。此時邊界判斷條件爲’$’符號
-
輸出目標串
3-3 代碼實現
1 STACK SEGMENT PARA STACK 2 DW 100H DUP(?) 3 STACK ENDS 4 5 DATA SEGMENT PARA 6 LEN EQU 32 7 LIST DB 'ABOVE ZEBRA$' 8 TEMP DB LEN DUP(?) 9 NL DB 13,10,'$' 10 STR1 DB LEN-1 11 DB ? 12 DB LEN DUP(?) 13 DATA ENDS 14 15 CODE SEGMENT PARA 16 ASSUME CS:CODE,DS:DATA,SS:STACK 17 NEWLINE MACRO 18 PUSH DX 19 PUSH AX 20 MOV DX,OFFSET NL 21 MOV AH,9 22 INT 21H 23 POP AX 24 POP DX 25 ENDM 26 27 OUTPUT MACRO MSG 28 PUSH DX 29 PUSH AX 30 NEWLINE 31 MOV DX,OFFSET MSG 32 MOV AH,9 33 INT 21H 34 POP AX 35 POP DX 36 ENDM 37 38 INPUT PROC 39 INPUTSTR1: 40 PUSH DX 41 PUSH AX 42 PUSH SI 43 44 MOV DX,OFFSET STR1 45 MOV AH,0AH 46 INT 21H 47 MOV SI,OFFSET STR1+2 48 MOV AL,STR1+1 49 XOR AH,AH 50 ADD SI,AX 51 MOV BYTE PTR [SI],'$' 52 53 POP SI 54 POP AX 55 POP DX 56 RET 57 INPUT ENDP 58 59 COPY PROC 60 STRCPY: 61 LODSB 62 CMP AL,20H 63 JE COPYRET 64 STOSB 65 JMP STRCPY 66 COPYRET: 67 STOSB 68 RET 69 COPY ENDP 70 71 INSERT PROC 72 INSERT_STR1: 73 MOV CL,STR1+1 74 XOR CH,CH 75 INSERT_LP: 76 LODSB 77 STOSB 78 LOOP INSERT_LP 79 MOV AL,20H 80 STOSB 81 RET 82 INSERT ENDP 83 84 COPY2 PROC 85 STRCPY2: 86 LODSB 87 CMP AL,'$' 88 JE COPYRET2 89 STOSB 90 JMP STRCPY2 91 COPYRET2: 92 STOSB 93 RET 94 COPY2 ENDP 95 96 MAIN PROC FAR 97 MAINPROC: 98 MOV AX,DATA 99 MOV DS,AX 100 MOV ES,AX 101 OUTPUT LIST 102 CALL INPUT 103 MOV SI,OFFSET LIST 104 MOV DI,OFFSET TEMP 105 CALL COPY 106 PUSH SI 107 MOV SI,OFFSET STR1+2 108 CALL INSERT 109 POP SI 110 CALL COPY2 111 OUTPUT TEMP 112 113 EXIT: 114 MOV AX,4C00H 115 INT 21H 116 MAIN ENDP 117 118 CODE ENDS 119 END MAIN
3-4 運行結果
4 程序設計複習4
4-1 練習要點
-
16進制輸出方式
-
從10向A的轉化
-
2號中斷調用輸出單個字符
4-2 實現思路
-
首先在數據段初始化一個64位數字
-
注意因爲一個字是2個字節16位,所以在輸出時,要依次在基地址的基礎上+2
-
因爲是循環訪問數據段中的除數,所以用SI寄存器記錄數據段中除數的位置,每次循環都要使用兩次INC指令,保證訪問到下一個字中的內容。
-
訪問除數必須用WORD PTR [SI],不然會提示 ’must have size’
-
判斷16進制輸出的數字是否大於10,若不大於則直接輸出,不然須要加7(在ASCII數值上‘9’和‘A’之間差8),注意從數字轉換爲ASCII碼此處必須用ADD 30H 來代替 OR 30H,不然會出現錯誤。
4-3 代碼實現
1 STACK SEGMENT PARA STACK 2 DW 100H DUP(?) 3 STACK ENDS 4 5 DATA SEGMENT PARA 6 NUM DW 1606H,1160H,1234H,0FFFFH 7 DIVISOR DW 1000H,100H,10H,1H 8 DATA ENDS 9 10 CODE SEGMENT PARA 11 ASSUME CS:CODE,DS:DATA,SS:STACK 12 13 OUTPUT PROC 14 PRINT: 15 MOV SI,OFFSET DIVISOR 16 MOV CX,4 17 OUTPUT_LP: 18 XOR DX,DX 19 DIV WORD PTR [SI] 20 PUSH DX 21 CMP AL,10 22 JB OUTPUT_CONTINUE 23 ADD AL,7 24 OUTPUT_CONTINUE: 25 ADD AL,30H 26 MOV DL,AL 27 MOV AH,2 28 INT 21H 29 INC SI 30 INC SI 31 POP AX 32 LOOP OUTPUT_LP 33 RET 34 OUTPUT ENDP 35 36 MAIN PROC FAR 37 MAINPROC: 38 MOV AX,DATA 39 MOV DS,AX 40 MOV ES,AX 41 42 MOV AX,NUM 43 CALL OUTPUT 44 45 MOV AX,NUM+2 46 CALL OUTPUT 47 48 MOV AX,NUM+4 49 CALL OUTPUT 50 51 MOV AX,NUM+6 52 CALL OUTPUT 53 54 EXIT: 55 MOV AX,4C00H 56 INT 21H 57 MAIN ENDP 58 59 CODE ENDS 60 END MAIN
4-4 運行結果
【數據段】
【運行結果】