註:應該是新浪部落格的一個bug:“define”前面加%是顯示亂碼的,只能把%用100代替,因此下文看到100define時不要被嚇到。
-----------------------------------------------------------------------------------------------------------------------------------------
已經看到第3章“保護模式”,反覆讀那段“吸引眼球”的pmtest1.asm,總還是霧蒙蒙的感覺。對其中的SECTION,BITS等關鍵字不甚瞭解,於是知道要專門熟悉一下nasm彙編了。
“可能有很多人開始學彙編時用的都是MASM“,作者猜的太對了,我當初學的就是王爽的那本(中國這樣的寫作者太少了)。於淵提及“學習成本”這個概念,很是贊同,反正AT&T和INTEL風格的彙編語句最終都翻成一樣的機器碼,那學通一樣就夠,反正是彙編工具嘛。
所謂“熟悉一下nasm彙編”,就是用nasm寫一些基礎的子程式,慢慢體會,同時也累積一個自己的彙編子程式庫,就像libc似的。
1,100define plus(a,b) ((a)+(b))
這種格式的宏跟C很像,預定義之後這樣使用:mov ax,plus(1,2)
nasm的宏處理器便會把它翻譯成:mov ax,3 準確說是翻譯成"mov ax,((1)+(2))"
至於((a)+(b))為什麼要加這麼多括弧,是防止變數a,b本身又是運算式,加上括弧是保證宏替代後,運算次序不被打亂。
2,org是做什麼的
王爽的書裡沒提到org,但清華大學的那本黃皮書裡是有的(和國內大多數書一樣,那本書完全不是寫給自學者讀的)。
--------------------------------------------------------------------------------------
在 NASM組合語言彙編BIN格式的程式中,舉例說明ORG的意義
ORG 100H
START:
movax,START; 這裡相當於 mov ax,100h
……
上面有一個標籤 START ,執行mov ax,START時,ax=100h,如果不寫ORG 100H,那麼就是相當於 ORG0H,這樣的話,ax=00h
這就是說,地址引用的數值 = 該地址在檔案內的位移+ org 的數值。
更本質的說,org指令是提前告訴編譯器:“你正在編譯的這段的程式碼片段將來肯定會被載入到記憶體的XXX地址處”。編譯器據此來計算每個標籤(label)對應的物理地址。(這句話是我自己加上來的)
--------------------------------------------------------------------------------------
分割線裡就講得很清楚了,摘自http://hi.baidu.com/chinfs/blog/item/535d0eed42866addb21cb18d.html 僅摘了文章開頭,已足夠說明含義,且原文後半部分講的有些繞
3,寫一個最原始的echo
nasm的多行宏是很強大的,現在就用它來實現最原始的echo功能,在螢幕1行1列處顯示一串字元。為方便重複利用,代碼都寫到一個標頭檔echo.mac裡,需要使用時加上句%include "echo.mac"就好了
----------
%ifndef echo_mac ;這跟c很像,也是為了防止標頭檔的重複包含
100define echo_mac
%include"esbp.mac" ;這是我自己寫的另一個多行宏,方便把標籤地址映射到相應的es,bp寄存器。
%macro echo 1
jmp%%start ;nasm真是人性化:%%是專門為多行宏定義的一種本地標籤,即,你調用這個宏n次,nasm會
;把%%start轉化為n個不同的標籤,調用n+1次,則轉化成n+1個不同的標籤。設想沒有這個功能
;的話,同一個宏在一段代碼的n個位置展開後,就得到n個一模一樣的%%start,那是多麼恐怖
%%local:db %1
%strlen bytenum%1 ;用C語言描述就是——#define bytenum strlen(%1)
%%start:movcx,bytenum ;開始為int 10h中斷填充相關參數 cx儲存要顯示的字串長度,即幾個byte
movah,13h ;int 10h中斷的13h號子功能:在指定行列顯示es:bp指向的字串
moval,1h ;al設定顯示輸出方式,1表示字串只含顯示字元,其顯示內容在bl,顯示後,游標位置改變
mov dh,0 ;第0行
movdl,0 ;第0列
movbh,0 ;0頁為顯示頁
movbl,00000100b ;紅色字元
esbp%%local ;%%local即字串所在記憶體位址,用esbp宏將其映射到es,bp寄存器
int 10h
%endmacro
%endif
----------
這是esbp.mac檔案:
----------
%ifndef esbp_mac
100define esbp_mac
%macro esbp 1
;the address passed here must be a 16-bit digit,namely not greaterthan 0xffff
push ax
push dx
mov ax,%1
mov dx,%1
and dx,0x000f
shr ax,4
mov bp,dx
mov es,ax
pop dx
pop ax
%endmacro
%endif
----------
試試效果如何吧,下面的測試檔案boot.asm
----------
%include "echo.mac"
org 0x7c00
start:echo 'hello! oranges world'
times 510-($-start) db 0
dw 0aa55h
----------
編譯成bin檔案,並dd到虛擬磁碟的mbr,bochs調試,
這個石器時代的echo宏就做好啦!
4,稍微增強這個echo的功能,能夠將字串顯示到指定行列。下面是echoAtLC.mac
----------
%ifndef echoAtLC_mac
100define echoAtLC_mac
%include "esbp.mac"
%macro echoAtLC 3
jmp %%start
%%local:db %1
%strlen bytenum %1
%%start:mov cx,bytenum
mov ah,13h
mov al,1h
mov dh,%2
mov dl,%3
mov bh,0
mov bl,00000100b
esbp %%local
int 10h
%endmacro
%endif
----------
5,把echo再做強一些,使其能將字串顯示到游標所在位置
思路是:利用int 10h的3號子功能擷取游標所在行列,再調用echoAtLC宏就好了。下面是echoUnderCursor.mac
----------
%ifndef echoUnderCursor_mac
100define echoUnderCursor_mac
%include "echoAtLC.mac"
%macro echoUnderCursor 1
;get the line&column cursor locate
push dx
push cx
push ax
push bx
mov ah,3h
mov bh,0
int 10h ;dx changed,and it storesline,column
pop bx
pop ax
pop cx
echoAtLC %1,dh,dl
pop dx ;dxrecovered
%endmacro
----------
6,readCursor_D.mac和setCursor_page_line_column.mac
這兩個標頭檔內的宏,分別用來讀取游標資訊和設定游標位置,即對int 10h的3號和2號子功能的封裝
標頭檔readCursor_D.mac
----------
;change dx
;dh store line,dl store column
%ifndef readCursor_D_mac
100define readCursor_D_mac
%macro readCursor_D 0
push bx
push ax
push cx
mov bh,0
movah,3h ;3號子功能的出口參數分別放在放在cx,dx中,cx裡的資訊我用不上,於是pop覆蓋了
int 10h
pop cx
pop ax
pop bx
%endmacro
%endif
----------
標頭檔setCursor_page_line_column.mac
----------
;change no reg
%ifndef setCursor_mac
100define setCursor_mac
%macro setCursor_page_line_column 3
push ax
push bx
push dx
mov bh,%1
mov dh,%2
mov dl,%3
movah,2h ;2號子功能就是設定游標的文本座標的
int 10h
pop dx
pop bx
pop ax
%endmacro
%endif
----------
此類的標頭檔越寫越多,我開始注意統一規範:例如,第一行注釋,講明這個宏執行後是否對寄存器造成影響,如果是,在宏的名字後統一加_D尾碼,像比readCursor_D,_D就是說這個宏dangerous。例如,假如這個宏需要的參數多於2個,我就將參數的名字資訊附加到宏的名字裡,像比setCursor_page_line_column,這樣一看就知道要輸入哪些參數。例如,宏的名字盡量跟標頭檔的名字一樣。(當然,不包括.mac尾碼部分了)
7,實現游標的斷行符號功能 cursorEnter.mac
----------
;change no reg
%ifndef cursorEnter_mac
100define cursorEnter_mac
%include "setCursor_page_line_column.mac"
%include "readCursor_D.mac"
%macro cursorEnter 0
push dx
readCursor_D ;擷取當前游標的行,列資訊
incdh ;將游標的行數值加1,實現換行
setCursor_page_line_column 0,dh,0 ;最後一個參數0,將游標置於第0列,實現斷行符號----->這就實現一個