引言
記錄一下學習彙編過程中寫的一些小程序,代碼都是在DosBox上完成的,記錄的目的也是爲了在後面想要複習相關知識點的時候有一個很好的資料和練習素材,同時也希望能夠爲後來學習的朋友起一點指導作用。
1. Hello World!
DATA SEGMENT
str db 'Hello World$' ;要輸出的字符串必須要以$結尾
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA ;將CS和CODE,DS和DATA段建立聯繫
START:
MOV BX,DATA ; 段寄存器不能被立即數直接賦值,需要先賦值給其他寄存器,然後再賦值
MOV DS,BX
LEA DX,str
MOV AH,9 ; 執行9號系統調用,從DX寄存器中取出值並打印
INT 21H ; 產生一個系統中斷
MOV AH,4CH ;將控制權返回給終端
INT 21H
CODE ENDS
END START
ps:
- LEA和MOV其實作用差不多,LEA是load effective address 的縮寫,其實就是把後一個參數的地址傳遞給第一個參數,而MOV則是傳遞後面的值,值是地址就是地址,是值就是值。lea其實和mov+offset功能一樣,唯一不同lea是硬指令,在指令執行的時候纔會計算,而offset在彙編階段就已經完成計算了。傳送門
- INT 21H其實就是執行系統調用,那執行哪一個呢?玄機就在前面的mov中,有些同學可能對系統調用表不清楚,可以隨時查閱這裏–>傳送門。
2. 計算平均值
DATA SEGMENT
sum DW 1,2,3,4,5,6,7,8,9
DATA ENDS
STACK SEGMENT
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:
MOV BX, DATA
MOV DS, BX
MOV CX, 9 ; loop使用CX作爲判斷條件 即設置循環次數
XOR AX, AX ;清零
XOR BX, BX
MOV SI, OFFSET sum ; 得到sum的起始地址
again:
ADD AX, [SI]
ADD SI, 2 ; 索引指向下一項
LOOP again ; 循環結構語句,其中隱含使用CX寄存器,每次加1
;CDQ
MOV CL, 9
DIV CL ; 商在AX, 餘數在DX
MOV DL, AL
ADD DL, 30H
MOV AH, 02H ;2號系統調用,從DL取出值輸出
INT 21H
MOV AH, 4CH ;控制權返回給終端,不寫的話顯示完就卡那了
INT 21H
CODE ENDS
END START
3. 鍵盤輸入1-7,顯示相應爲星期幾
DATA SEGMENT
msg1 db 'Monday$'
msg2 db 'Tuesday$'
msg3 db 'Wednesday$'
msg4 db 'Thyrsday$'
msg5 db 'Friday$'
msg6 db 'Saturday$'
msg7 db 'Sunday$'
msg db 'input number(1-7):$'
table dw disp1,disp2,disp3,disp4
dw disp5,disp6,disp7
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV BX, DATA
MOV DS, BX
again:
LEA DX, msg
MOV AH, 9 ; 9號系統調用,從DX取數據並輸出
INT 21H
XOR AX, AX ; 清空AX,不然後面乘4低位會出現錯誤
MOV AH, 01H ; 1號系統調用,將字符輸入到AL中
INT 21H
;MOVZX AX, AL
SUB AX, 100H ; 系統調用會在AX上加100h
SUB AX, 30H ; ascll碼,減30h變裸數字
; 不在範圍內重新輸入
CMP AL, 1 ; 減法操作,但不保存結果,只改變狀態寄存器
JB again ; 無符號小於則跳轉 CF=1
CMP AL, 7
JA again ; 無符號大於則跳轉 CF=0,ZF=0
DEC AL
MOV AX, AL
MOV AH, 0 ; 意義不大,主要是前面類型不合適,這裏補救一下
SHL AX, 1 ; 乘2,匹配到對應項地址
MOV BX, AX
;jmp dword ptr table[AX] ; 16位dos環境中,AX不能用作偏移地址
JMP WORD PTR TABLE[BX] ; jmp實際需要的是一個地址
disp1:
MOV AX, OFFSET msg1
jmp disp
disp2:
MOV AX, OFFSET msg2
jmp disp
disp3:
MOV AX, OFFSET msg3
jmp disp
disp4:
MOV AX, OFFSET msg4
jmp disp
disp5:
MOV AX, OFFSET msg5
jmp disp
disp6:
MOV AX, OFFSET msg6
jmp disp
disp7:
MOV AX, OFFSET msg7
jmp disp
disp:
MOV DX, AX
MOV AH, 9
INT 21H
MOV AH, 4CH
INT 21H ;控制權返回給終端
CODE ENDS
END START
ps:
- 切記,16位dos環境中,AX不能用作偏移地址,所以我們使用BX。
- 從table中取label的時候因爲沒有類型,需要我們指定類型,從而確定長度。
4. 模擬函數調用過程
sum函數的邏輯如下:
int sum(int lhs, int rhs){
int T1 = 1;
int T2 = 2;
return lhs + rhs + T1 + T2;
}
ASSUME CS:CODE,DS:DATA,SS:STACK
STACK SEGMENT
db 20 dup(0)
STACK ENDS
DATA SEGMENT
db 20 dup(0)
str db "hello world!$"
DATA ENDS
CODE SEGMENT
START:
MOV AX, DATA
MOV DS, AX
MOV AX, STACK
MOV SS, AX
;開始業務邏輯部分
MOV AX, 3h ;傳遞參數 立即數不能直接傳遞 para1
PUSH AX
MOV AX, 4H ; para2
PUSH AX
CALL sum ; 調用sum
MOV DL, AX
MOV AH, 02h; 從DX取值並輸出
INT 21H
MOV AH, 4CH ; 將控制權轉移給終端
INT 21H ; 產生系統中斷
sum:
PUSH bp
MOV BP, SP ; 初始化函數堆棧
SUB SP, 20 ; 20字節留作局部變量
; 保護寄存器
PUSH BX
PUSH CX
PUSH DX
; 定義兩個局部變量
MOV word ptr SS:[BP - 2], 1h
MOV word ptr SS:[BP - 4], 2h
; 修改寄存器
MOV BX, 2h
MOV CX, 3h
MOV DX, 4h
; 計算部分 兩個參數加上常數1+2
MOV AX,SS:[BP + 6]; param1
ADD AX,SS:[BP + 4]; param2
ADD AX,SS:[BP - 2]; 1
ADD AX,SS:[BP - 4]; 2
ADD AX, 30H; 輸出爲ascll碼,加上48
//|--------|
//|param2 3|
//|param1 4|
//|ret_addr| 返回地址就是CALL指令下一條指令的地址
//|ebp | EBP其內存放一個指針,該指針指向系統棧最上面一個棧幀的底部
//|ebp-2 1h|
//|ebp-4 2h|
//|--------|
; 恢復寄存器
POP DX
POP CX
POP BX
MOV SP,BP
POP BP
RET
CODE ENDS
END START
ps:
- 函數的返回值一般存放在EAX中。
- 對於使用棧的函數調用過程一定要清楚,在參數和棧幀起點ebp之間還有一個ret_addr,存放着調用這個函數的原函數的EIP寄存器的位置,即程序計數器的位置,這樣與ebp結合可以精確的在子函數執行完以後回到原函數。
5. 彙編生成1-N共N位不重複的隨機數
比如1-5,就會生成12345, 34152,54312等等。
;生成八位的隨機字符串
DATA SEGMENT
result db " " ; 9位
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX, DATA
MOV DS, AX
MOV SI, 0 ; 目前到第幾個隨機數
doRand: ; 這裏設置一個label方便其他程序調用
CreateRandNumdo1: ; 每執行一次可以確定一個隨機數
CALL getRand ; 這個隨機數在AL中
XOR DI, DI
XOR CX, CX
LEA DI, result ; di中存着result的起始地址
; 這裏在前幾次會有很多無意義的執行,但是因爲初始值爲0,所以正確性是可以保證的
MOV CX, 8 ; CX爲loop默認使用的寄存器,SI爲現在已有的隨機字符數,也就是循環這麼多次
CreateRandNumdo2:
MOV BH, [DI]; 每次通過[DI]取一個值
CMP BH, AL ; 比較此次得到的隨機數和已有的隨機數
JZ CreateRandNumdo1 ; 出現重複的時候再次生成隨機數
INC DI
LOOP CreateRandNumdo2
; CreateRandNumdo1結束以後,AL中存放着此次要插入的值,插入到result[SI]中
;ADD AL, 30H ; 輸出爲ASCLL,所以加上30H
MOV result[SI], AL
INC SI ; 現在對第幾個隨機數賦值
CMP SI, 8 ; 已經生成了完成了我們需要的隨機字符串
JB doRand; 開始生成下一個隨機字符 JB 無符號大於則跳轉
MOV CX, 8
MOV SI, 0
ToASCLL: ; 目前全部的值都是數字,需要轉換成ASCLL碼
ADD result[SI], 30H
INC SI
LOOP ToASCLL
MOV result[SI], 24H ; $的ASCLL碼爲36, 給字符串末尾加上$
LEA DX, result
MOV AH, 9 ; 從DX中讀取字符串並輸出
INT 21H
;註釋掉的部分爲測試getRand
;--------------------------------------
;call getRand
;MOV DL, AL
;ADD DL, 30H ; 顯示ascll碼,加30H
;MOV AH, 02H ; 從DL取出數字顯示
;INT 21H
;--------------------------------------
MOV AH, 4CH ; 控制權交給終端
INT 21H
getRand: ;獲取一個隨機數,放到AL中
XOR AL, AL
MOV AX, 0H
; 彙編中使用in/out來訪問系統的io空間
OUT 43H, AL ;將0送到43h端口
IN AL, 40H ;將40h端口的數據送到AL寄存器
MOV BL, 8 ;除以8,得到範圍0-7的餘數
DIV BL ;商在AL, 餘數在AH
MOV AL, AH ;
MOV AH, 0
INC AL ; 加1,得到範圍1-8
RET
CODE ENDS
END START
ps:
- 彙編是直接面向硬件的,它可以訪問系統的mem空間,也可以直接訪問系統的io空間。彙編中使用in/out來訪問系統的io空間。傳送門
- 事實上這個代碼還有優化空間,就是檢測的時候沒必要每次都檢查8次,正確性可以保證,但是浪費一些指令。所以可以用一個寄存器保存現在已經完成的隨機字符的數量,在CreateRandNumdo1給CX賦當前已經生成的字符數。
- 代碼太難重用了,可以用一個寄存器保存需要生成的長度,類似於全局變量,替換這個代碼滿天飛的8。。
總結
後面還會持續更新這篇博客。先不做總結。