【我所認知的BIOS】->彙編語言之宏彙編1

【我所認知的BIOS】->彙編語言之宏彙編1

By LightSeed

2010-2-2 

其實早就想寫點關於彙編語言的文章了,但是最近感覺比較累,自己也比較懶今天才動手寫。哎。。。真是身心俱疲,房價漲了,小菜也漲了,妹兒的要求也漲了,但是哥的工資還是不漲。這社會我快混不下去了。但是想想做男人嘛,有什麼大不了的。做人,最重要的是心態要好,要穩。

1、我體會到的C和彙編

目前我還做的legacyBIOS,裏面全是彙編語言。老實說,最近在study C語言的東西,也在研究用C生成ASM的文件,細細想來C確實很強大,彙編語言的效率確實也高。從C生成的彙編語言來看的話,如果是組織的好,實際的彙編語言的效率應該是比C高的。關於彙編語言的基礎性的東西我就不多贅述了。在這裏我想談談我在仿寫ADU那個debug tool的時候用的一些彙編語言的裏的高級語句。(當然先說清楚,這也是參考了BIOS code的東西,讓我自己寫我想我是寫不出來的啦。),話有說回來,對於程序員來說,如果讓我在C和彙編語言之間選擇,當然還是比較喜歡用C了。(最近我在study ACPI的東西的時候,就試圖用C來寫一個ACPI dump的工具,發覺用C寫真的好方便哦,兩天就能搞出來一個功能了,但是如果用匯編的話,就算知道算法,估計寫起來也比較費神。)

在組織數據段中的數據的時候,按照結構體的形式來存儲有關聯的數據是一種明智的選擇。但是由於這個結構體需要輸入的東西比較多,那麼每次都手動去輸入相關數據的話,估計煩也煩死了。所以宏彙編在這裏就其了很大的作用。

2、宏彙編

(以下這段話是引用自網上的,對於這些定義放在這裏純粹是爲了這篇文章的完整性。)

----------------------------引用開始----------------------

在程序設計中,爲了簡化程序的設計,將多次重複使用的程序段用宏指令代替。宏指令是指程序員事先定義的特定的指令,這種指令是一組重複出現的程序指令塊的縮寫和替代。宏指令定義後,凡在宏指令出現的地方,宏彙編程序總是自動地把它們替換成對應的程序指令塊。宏指令有時也稱爲宏,包含宏定義和宏調用。

使用宏指令的好處是:簡化源程序的編寫,傳遞參數靈活,功能更強。

2.1宏指令的定義

宏是源程序中的一段具有獨立功能的程序代碼。它只要在源程序中定義一次,就可以多次調用,調用時只要使用一個宏指令語句就可以了。宏指令定義由開始僞指令MACRO、宏指令體、宏指令定義結束僞指令ENDM組成。格式如下:

宏指令名  MACRO

 [形式參數1,形式參數2 形式參數N]

;宏指令體(宏體)

ENDM

其中,宏指令名是宏定義爲宏體程序指令塊規定的名稱,可以是任一合法的名字,也可以是系統保留字(如指令助記符、僞指令操作符等),當宏指令名是某個系統保留字時,則該系統保留字就被賦予新的含義,從而失去原有的意義。MACRO語句到ENDM語句之間的所有彙編語句構成宏指令體,簡稱宏體,宏體中使用的形式參數必須在MACRO語句中列出。

形式參數是出現在宏體內某些位置上可以變化的符號,也可以是任一合法的名字,甚至是寄存器名(筆者:我不建議你這樣使用)。如果形式參數中使用某些寄存器名,那麼在宏彙編展開時,將不認爲這些寄存器名是寄存器本身,而是形式參數,並被實際參數所代替。形式參數可以缺省,也可以有一個或多個。當形式參數多於一個時,形式參數之間用逗號隔開。定義宏指令時,每行的字符數不能多於132個。宏指令定義一般放在源程序的開頭,以避免產生不應發生的錯誤。

宏指令必須先定義後調用(引用)。宏指令可以重新定義,也可以嵌套定義。嵌套定義是指在宏指令體內還可以再定義宏指令或調用另一宏指令。(筆者:其實這和C中的方式也比較相似。擴展一下,彙編語言中的結構體也是這也的。)

2.2宏指令的調用

宏指令一旦定義後,就可以用宏指令名字(宏名)來引用(或調用),這個引用過程即爲宏調用。宏調用的格式爲

宏指令名  實際參數1,實際參數2,實際參數N

其中,實際參數的類型和順序要與形式參數的類型和順序保持一致,宏調用時將一一對應地替換宏指令體中的形式參數。當有兩個以上參數時,中間用逗號、空格或製表符隔開。宏指令調用時,實際參數的數目並不一定要和形式參數的數目一致。當實參個數多於形參的個數時,忽略多餘的實參;當實參個數少於形參個數時,多餘的形參用空串代替。

;---------------------------------引用結束------------------------

哈哈,我們應該向前輩看齊的嘛,多學習前輩的東西,總是不會錯的。

3、條件彙編

IF/IFE僞指令中的表達式可以採用第3章學習的關係運算符EQ(相等)、NE(不相等)、GT(大於)、LT(小於)、GE(大於等於)、LE(小於等於)。關係表達式用0ffffh(非0)表示真,用0表示假。

 條件彙編僞指令

格式

功能說明

IF 表達式

彙編程序求出表達式的值,此值不爲0則條件滿足

IFE 表達式

彙編程序求出表達式的值,此值爲0則條件滿足

IFDEF 符號

符號已定義(內部定義或聲明外部定義),則條件滿足

IFNDEF 符號

符號未定義,則條件滿足

IFB <形參>

用在宏定義體。如果宏調用沒有用實參替代該形參,則條件滿足

IFNB  <形參>

用在宏定義體。如果宏調用用實參替代該形參,則條件滿足

IFIDN <字符串1>,<字符串2>

字符串1與字符串2相同則條件滿足;區別大小寫

IFIDNI <字符串1>,<字符串2>

字符串1與字符串2相同則條件滿足;不區別大小寫

IFDIF <字符串1>,<字符串2>

字符串1與字符串2不相同則條件滿足;區別大小寫

IFDIFI <字符串1>,<字符串2>

字符串1與字符串2不相同則條件滿足;不區別大小寫

對於他們的定義我就不詳細例舉了,只是在後面的敘述中要用到這些語句。所以先列在這裏,如果需要您可以去查更過的資料。

4、實例

上面對文字性的東西定義了太多了,我們來直接看實例吧,這樣比較直觀。(本想在這裏先引用一個簡單的宏彙編的例子的,可是後來又想想算了,如果有人對這篇文章敢興趣的話,我想簡單的宏彙編也應該難不到你。)下面的例子主要是當時在study 宏彙編的時候見到網上的例子,在這裏就直接引用過來,(雖說是從CSDN上的一個老兄blog裏面看到的,但是我感覺是小木偶前輩的例子,所以我一不知道究竟哪位是原作者,您可以看看這裏:

http://blog.csdn.net/benny5609/archive/2008/06/12/2541246.aspx

範例:通用的推入堆棧宏

8086 指令的 PUSH 只能把十六位的寄存器或十六位變量推入堆棧,不能把十六位立即值 (常數) 或八位的寄存器推入堆棧,而底下這個宏範例,push_op,也能使立即值或八位的寄存器推入堆棧。底下是 push_op 源代碼:

 

        page    ,132            ;01

push_op MACRO   arg             ;;03

reg16   =       0               ;;05

reg08   =       0               ;;06

addr    =       0               ;;07

 

;;09 檢查輸入參數是否爲 16 位的寄存器

IRP     reg,<AX,BX,CX,DX,CS,DS,ES,SS,SI,DI,BP,SP,ax,bx,cx,dx,cs,ds,es,ss,si,di,bp,sp>

  IFIDN <reg>,<arg>

        push    arg             ;;12 如果相等的話,推入堆棧

        reg16   =       0ffffh  ;;13 數定虛擬變量爲真

        exitm                   ;;14 跳出 IRP

  ENDIF

ENDM

IF      reg16                   ;;17 reg16 爲真

        exitm                   ;;18 則跳出 push_op

ENDIF

 

;;21 檢查輸入參數是否爲 16 位的寄存器

IRP     reg,<aX,bX,cX,dX,cS,dS,eS,sS,sI,dI,bP,sP,Ax,Bx,Cx,Dx,Cs,Ds,Es,Ss,Si,Di,Bp,Sp>

  IFIDN <reg>,<arg>

        push    arg

        reg16   =       0ffffh

        exitm

  ENDIF

ENDM

IF      reg16

        exitm

ENDIF

 

;;33 檢查輸入參數是否爲 8 位的寄存器

IRP     reg,<al,bl,cl,dl,ah,bh,ch,dh,AH,BH,CH,DH,AL,BL,CL,DL>

  IFIDN <reg>,<arg>

        reg08   =       0ffffh

        exitm

  ENDIF

ENDM

IF      reg08

  IRPC  char,arg                ;;41 取得寄存器名的第一個字母

        push    char&&x         ;;42 推入堆棧

        exitm                   ;;43 跳出 IRPC

  ENDM

        exitm                   ;;45 跳出 push_op

ENDIF

 

;;48 檢查輸入參數是否爲 8 位的寄存器

IRP     reg,<Al,Bl,Cl,Dl,Ah,Bh,Ch,Dh,aL,bL,cL,dL,aH,bH,cH,dH>

  IFIDN <reg>,<arg>

        reg08   =       0ffffh

  ENDIF

ENDM

IF      reg08

  IRPC  char,arg

        push    char&&x

        exitm

  ENDM

        exitm

ENDIF

 

;;62 檢查輸入參數是否爲含有寄存器間接尋址模式,即 [BX][SI]……等等

IRPC    char,arg

  IF    ('&char' eq '[')

        addr=0ffffh

        exitm

  ENDIF

ENDM

IF      addr

        push    arg

        exitm

ENDIF

 

arg_size=((type arg) 1)/2       ;;74 輸入參數之長度

arg_type=(.type arg) and 3      ;;75 輸入參數之類型

 

;;77 檢查輸入參數是否爲變量

IF      arg_type eq 2

  arg_offset  =0

  REPT  arg_size

        arg_address=word ptr arg arg_offset

        push    arg_address

        arg_offset=arg_offset 2

  ENDM

        exitm

ENDIF

 

;;88 檢查輸入參數是否爲標號

IF    arg_type eq 1

        push    bp

        mov     bp,sp

        push    ax

        mov     ax,offset arg

        xchg    ax,[bp]

        mov     bp,ax

        pop     ax

      exitm

;;98 若不是寄存器、尋址模式、變量、標號的話,應爲立即值

ELSE

        push    bp

        mov     bp,sp

        push    ax

        mov     ax,arg

        xchg    ax,[bp]

        mov     bp,ax

        pop     ax

ENDIF

ENDM這個宏結構很明顯,先檢查要推入堆棧的參數是否爲 16 位寄存器( 9 行到第 31 ),如果不是再檢查是否爲 8 位寄存器 ( 33 行到第 60 ),如果不是寄存器的話,再檢查是否爲寄存器間接尋址 ( 62 行到第 72 ),如果不是以上這幾種的話,再檢查是否推入變量到堆棧 ( 77 行到第 87 ),接下來檢查是否推入標號到堆棧 ( 88 行到第 97 ),假如都不是上述情形的話,就是推入立即值到堆棧了 ( 98 行到第 107 )

檢查是否爲寄存器的方法是用不定重複塊 ( IRP ) 來指定要比較的範圍,故引數列 ( 即第 10 行角括號內的引數 ) 包含所有 16 位寄存器名稱,但是因爲參數與引數都被視爲字串,所以大小寫是有差別的,必須在引數列裏包含不同的大小寫排列方式。指定好比較範圍後,再用 IFIDN 比較輸入參數是否爲引數列中的一個,假如是 16 位寄存器的話,則直接把該參數推入堆棧即可,並設定一個虛擬變量,reg16,爲 0ffffh0ffffh 表示真的意思 ( 1213 )。然後再跳出 push_op 堆棧。

小木偶再把 IRP 重複塊的執行方式說明一遍。第 10 行到第 16 行程序碼爲:

IRP     reg,<AX,BX,CX,DX,CS,DS,ES,SS,SI,DI,BP,SP,ax,bx,cx,dx,cs,ds,es,ss,si,di,bp,sp>

  IFIDN <reg>,<arg>

        push    arg             ;;12 如果相等的話,推入堆棧

        reg16   =       0ffffh  ;;13 數定虛擬變量爲真

        exitm                   ;;14 跳出 IRP

  ENDIF

ENDM

IF      reg16                   ;;17 reg16 爲真

        exitm                   ;;18 則跳出 push_op

ENDIF表示在第 10 行到第 16 行程序碼會重複彙編。第一次時,reg 會以 AX 代入彙編,第 11 行是比較 arg reg 這兩字串是否相等,如果相等則彙編第 12 行到第 14 行之間的程序碼,不相等則結束 IFIDN,然後遇到 ENDM,故重複第二次,使 reg BX 代入彙編……一直到 sp 所有引數結束。

14 行,是因爲假如已經找到相符合的寄存器,就沒必要再比較了,這樣可以加快彙編速度。( 雖然也沒快多少。) 17 行到第 19 行,也是這樣的道理,既已找到是把寄存器推入堆棧,也就沒必要彙編以下的程序了,故直接跳出 push_op 堆棧。

您可能會問,第 14 行就已有了 exitm,爲何第 18 行還要有個 exitm 呢?這是因爲 RPTIRPIRPC 這三個重複塊,類似宏結構,若要在中間停止彙編都可以用 exitm 來跳出宏或塊,所以第 14 行是跳出 IRP 塊,第 17 行是跳出 push_op 宏。

33 行到第 61 行,是檢查參數是否爲 8 位寄存器,方法和上述幾乎相同,差別在於 8 位寄存器 ( 例如 ah ) 無法推入堆棧必須改成 16 位寄存器 ( 例如 ax )。所以第 41 行到第 44 行多了個 IRPC 重複塊,此重複塊是爲了取得寄存器的第一個字母,當取得第一個字母就把該字母加上『x』再推入堆棧,然後跳出 IRPC 塊及 push_op 宏。至於該 IRPC 重複塊的運作方式如下:該 IRPC 塊重複次數只有兩次,分別以 8 位寄存器名稱的兩個字母代入彙編,當第一次時即以 8 位寄存器名稱的第一個字母代入,然後加上『x』再推入堆棧,然後立刻跳出 IRPC 塊,故事實上這個重複塊只彙編一次而已。

42 行的『&&x』爲何要有兩個『&』呢?這是因爲根據 MASM 手冊上說每層塊要使用『&』,故第二層要用兩個『&』。

62 行到第 73 行是用來檢查推入堆棧的參數是否爲寄存器間接尋址,寄存器間接尋址模式是像底下的樣子:

 

mov     ax,[bx]

push    [si]

sub     ax,[bx 200h]

觀察以上幾個例子,您會發現,這種尋址模式含有兩個中括號,因此檢驗方法就是以 IRPC 檢查參數中是否有『[( 64 行到第 67 行的 IF 條件彙編 ),假如有的話,會使虛擬變量,addr,設爲真。然後接下來的就直接使該參數推入堆棧,因爲 PUSH 指令就可以直接推入寄存器間接尋址模式。

 

上面這些東西,其實大多是參考前輩的,希望能給正在尋找宏彙編資料的童鞋一點啓發。

 

發佈了61 篇原創文章 · 獲贊 11 · 訪問量 77萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章