nasm

NASM全稱The Netwide Assembler,是一款基於80x86和x86-64平台的彙編語言編譯程式,其設計初衷是為了實現編譯器程式跨平台和模組化的特性。NASM支持大量的檔案格式,包括Linux,*BSD,a.out,ELF,COFF,Mach−O,Microsoft 16−bit OBJ,Win32以及Win64,同時也支持簡單的二進制檔案生成。它的語法被設計的簡單易懂,相較Intel的語法更為簡單,支持目前已知的所有x86架構之上的擴展語法,同時也擁有對宏命令的良好支持。

第一章: 簡介

1.1 什麼是NASM

NASM是一個為可移植性與模組化而設計的一個80x86的彙編器。它支持相當多

的目標檔案格式,包括Linux和'NetBSD/FreeBSD','a.out','ELF','COFF',微軟16

位的'OBJ'和'Win32'。它還可以輸出純二進制檔案。它的語法設計得相當的簡

潔易懂,和Intel語法相似但更簡單。它支持'Pentium','P6','MMX','3DNow!',

'SSE' and 'SSE2'指令集,

1.1.1 為什麼還需要一個彙編器?

NASM當初被設計出來的想法是'comp.lang.asm.x86'(或者可能是'alt.lang.asm'

,我忘了),從本質上講,是因為沒有一個好的免費的x86系例的彙編器可以使用,

所以,必須有人來寫一個。

(*)'a86'不錯,但不是免費的,而且你不可能得到32位代碼編寫的功能,除非你

付費,它只使用在dos上。

(*) 'gas'是免費的,而且在dos下和unix下都可以使用,但是它是作為'gcc'的一

個後台而設計的,並不是很好,'gcc'一直就提供給它絕對正確的代碼,所以它的

錯誤檢測功能相當弱,還有就是對於任何一個想真正利用它寫點東西的人來講,

它的語法簡直太可怕了,並且你無法在裡面寫正確的16位代碼。

(*) 'as86'是專門為Minix和Linux設計的,但看上去並沒有很多文檔可以參考。

(*) 'MASM'不是很好,並且相當貴,還且只能運行在DOS下。

(*) 'TASM'好一些,但卻極入與MASM保持兼容,這就意味著無數的偽操作碼和繁瑣

的約定,並且它的語法本質上就是MASM的,伴隨著的就是一些自相矛盾和奇怪的

東西。它也是相當貴的,並且只能運行在DOS下。

所以,只有NASM才能使您愉悅得編程。目前,它仍在原型設計階段-我們不期望它

能夠超越所有的這些彙編器。但請您發給我們bug報告,修正意見,和其他有用的

信息,還有其他任何你手頭有的對我們有用的信息(感謝所有已經這樣在做了的

人們),我們還會不斷地改進它。

1.1.2 許可條件

請閱讀作為NASM發布的一部分的檔案'Licence',只有在該許可條件下你才可以使

用NASM。

1.2 聯繫信息

當前版本的NASM(0.98.08)由一個開發小組在維護,你可以從'nasm-devel'郵件列表

中得到(看下面的連結),如果你想要報告bug,請先閱讀10.2節

NASM有一個主頁:'http://www.web-sites.co.uk/nasm',更多的信息還可以在

`http://nasm.2y.net/'上獲取。

最初的作者你可以通過email:`[email protected]'和`[email protected]'和他們聯

系,但後來的開發小組並不在其中。

最新的NASM發布被上傳至官方網站`http://www.web-sites.co.uk/nasm'和`ftp.kernel.org',

`ibiblio.org'

公告被發布至`comp.lang.asm.x86', `alt.lang.asm' 和`comp.os.linux.announce'

在網站Sourceforge上的列表是較好的一個列表,它也是最新nasm原始碼與發布的

一個網站,另外的列表也是公開的,但有可能不會被繼續長期支持。

1.3 安裝

1.3.1 在dos和Windows下安裝NASM

如果你拿到了NASM的DOS安裝包,'nasmXXX.zip'(這裡.'XXX'表示該安裝包的NASM版

本號),把它解壓到它自己的目錄下(比如:‘c:\nasm')

該包中會包含有四個執行檔:NASM執行檔'nasm.exe'和'nasmw.exe',還有

NDISASM執行檔'ndisasm.exe'和'ndisasmw.exe'。檔案名稱以'w'結尾的是'Win32'

可執行格式。是運行在'Windows 95'或'Windows NT'的Intel處理器上的,另外的是

16位的'DOS'執行檔。

NASM運行時需要的唯一檔案就是它自己的執行檔,所以可以拷貝'nasm.exe'

和'nasmw.exe'的其中一個到你自己的路徑下,或者可以編寫一個'autoexec.bat'把

nasm的路徑加到你的'PATH'環境變數中去。(如果你只安裝了Win32版本的,你可能

希望把檔案名稱改成'nasm.exe'。)

就這樣,NASM裝好了。你不需要為了運行nasm而讓'nasm'目錄一直存在(除非你把它

加到了你的'PATH'中,所以如果你需要節省空間,你可刪掉它,但是,你可能需要保留

文檔或測試程式。

如果你下載了DOS版的源碼包,'nasmXXXs.zip',那'nasm'目錄還會包含完整的NASM源

代碼,你可以選擇一個Makefiles來重新構造你的NASM版本。

注意源檔案`insnsa.c', `insnsd.c', `insnsi.h'和`insnsn.c'是由'standard.mac'中

的指令自動生成的,儘管NASM0.98發布版中包含了這些產生的檔案,你如果改動了

insns.dat,standard.mac或者檔案,可能需要重新構造他們,在將來的源碼發布中有

可能將不再包含這些檔案,多平台兼容的Perl可以從www.cpan.org上得到。

1.3.2 在unix下安裝NASM

如果你得到了Unix下的NASM源碼包'nasm-x.xx.tar.gz'(這裡x.xx表示該源碼包中的

nasm的版本號),把它解壓壓到一個目錄,比如'/usr/local/src'。包被解壓後會創建

自己的子目錄'nasm-x.xx'

NASM是一個自動配置的安裝包:一旦你解壓了它,'cd'到它的目錄下,輸入'./configuer',

該腳本會找到最好的C編譯器來構造NASM,並據此建立Makefiles。

一旦NASM被自動配置好後,你可以輸入'make'來構造'nasm'和'ndisasm'二進制檔案,

然後輸入'make install'把它們安裝到'/usr/local/bin',並把man頁安裝到

'/usr/local/man/man1'下的'nasm.1和'ndisasm.1'或者你可以給配置腳本一個

'--prefix'選項來指定安裝目錄,或者也可以自己來安裝。

NASM還附帶一套處理'RDOFF'目標檔案格式的實用程式,它們在'rdoff'子目錄下,

你可以用'make rdf'來構造它們,並使用'make rdf_install'來安裝。如果你需

要的話。

如果NASM在自動配置的時候失敗了,你還是可以使用檔案'Makefile.unx'來編譯它們,

把這個檔案改名為'Makefile',然後輸入'make'。在'rdoff'子目錄下同樣有一個

Makefile.unx檔案。

第二章 運行NASM

2.1 NASM命令行語法

要彙編一個檔案,你可以以下面的格式執行一個命令:

nasm -f <format> <filename> [-o <output>]

比如,

nasm -f elf myfile.asm

會把檔案'myfile.asm'彙編成'ELF'格式 的檔案'myfile.o'.還有:

nasm -f bin myfile.asm -o myfile.com

會把檔案'myfile.asm'彙編成純二進制格式的檔案'myfile.com'。

想要以十六進制代碼的形式產生列表檔案輸出,並讓代碼顯示在原始碼的左側,

使用'-l'選項並給出列表檔案名稱,比如:

nasm -f coff myfile.asm -l myfile.lst

想要獲取更多的關於NASM的使用信息,請輸入:

nasm -h

它同時還會輸出可以使用的輸出檔案格式,

如果你使用Linux並且不清楚你的系統是'a.out'還是'ELF',請輸入:

file nasm

(在nasm二進制檔案的安裝目錄下使用),如果系統輸出類似下面的信息:

nasm: ELF 32-bit LSB executable i386 (386 and up) Version 1

那么你的系統就是'ELF'格式的,然後你就應該在產生Linux目標檔案時使用選

項'-f elf',如果系統輸入類似下面的信息:

nasm: Linux/i386 demand-paged executable (QMAGIC)

或者與此相似的,你的系統是'a.out'的,那你應該使用'-f aout'(Linux的'a.out'

系統很久以前就過時了,現在已非常少見。)

就像其他的Unix編譯器與彙編器,NASM在碰到錯誤以前是不輸出任何信息的,所

以除了出錯信息你看不到任何其他信息。

2.1.1 '-o'選項:指定輸出檔案的檔案名稱。

NASM會為你的輸出檔案選擇一個檔案名稱;具體如何做取決於目標檔案的格式,對

於微軟的目標檔案格式('obj'和'win32'),它會去掉你的源檔案名稱的'.asm'擴展

名(或者其他任何你喜歡使用的擴展名,NASM並不關心具體是什麼),並替換上

'obj'。對於Unix的目標檔案格式('aout','coff','elf'和'as86')它會替換成

'.o', 對於'rdf',它會使用'.rdf',還有為'bin'格式,它會簡單地去掉擴展名,所以

'myfile.asm'會產生的一個輸出檔案'myfile'。

如果輸出檔案已經存在,NASM會覆蓋它,除非它的檔案名稱與輸入檔案同名,在這種

情況下,它會給出一個警告信息,並使用'nasm.out'作為輸出檔案的檔案名稱。

在某些情況下,上述行為是不能接受的,所以,NASM提供了'-o'選項,它能讓你指定

你的輸出檔案的檔案名稱,你使用'-o'後面緊跟你為輸出檔案取的名字,中間可以加

空格也可以不加。比如:

nasm -f bin program.asm -o program.com

nasm -f bin driver.asm -odriver.sys

請注意這是一個小寫的o,跟大寫字母O是不同的,大寫的是用來指定需要傳遞的選

項的數目,請參閱2.1.15

2.1.2 `-f'選項:指定輸出檔案的格式。

如果你沒有對NASM使用'-f'選項,它會自己為你選擇一個輸出檔案格式。在發布的

NASM版本中,預設的輸出格式總是'bin';如果你自己編譯你的NASM,你可以在編譯的

時候重定義'OF_DEFAULT'來選擇你需要的預設格式。

就象'-o','-f'與輸出檔案格式之間的空格也是可選的,所以'-f elf'和'-felf'都是

合法的。

所有可使用的輸出檔案格式的列表可以通過運行命令'nasm -hf'得到。

2.1.3 `-l' 選項: 產生列表檔案

如果你對NASM使用了'-l'選項,後面跟一個檔案名稱,NASM會為你產生一個源檔案的列表

檔案,在裡面,地址和產生的代碼列在左邊,實際的原始碼(包括宏擴展,除了那些指定

不需要在列表中擴展的宏,參閱4.3.9)列在右邊,比如:

nasm -f elf myfile.asm -l myfile.lst

2.1.4 `-M'選項: 產生Makefile依賴關係.

該選項可以用來向標準輸出產生makefile依賴關係,可以把這些信息重定向到一個檔案

中以待進一步處理,比如:

NASM -M myfile.asm > myfile.dep

2.1.5 `-F'選項: 選擇一個調試格式

該選項可以用來為輸出檔案選擇一個調試格式,語法跟-f選項相冊,唯一不同的是它產

生的輸出檔案是調試格式的。

一個具體檔案格式的完整的可使用調試檔案格式的列表可通過命令'nasm -f <format> -y'

來得到。

這個選項在預設狀態下沒有被構建時NASM。如何使用該選項的信息請參閱6.10

2.1.6 `-g' 選項:使調試信息有效。

該選項可用來在指定格式的輸出檔案中產生調試信息。

更多的信息請參閱2.1.5

2.1.7 `-E' 選項: 把錯誤信息輸入到檔案。

在'MS-DOS'下,儘管有辦法,但要把程式的標準錯誤輸出重定向到一個檔案還是非常困

難的。因為NASM常把它的警告和錯誤信息輸出到標準錯誤設備,這將導致你在文本編

輯器裡面很難捕捉到它們。

因此NASM提供了一個'-E'選項,帶有一個檔案名稱參數,它可以把錯誤信息輸出到指定的

檔案而不是標準錯誤設備。所以你可以輸入下面這樣的命令來把錯誤重定向到檔案:

nasm -E myfile.err -f obj myfile.asm

2.1.8 `-s' 選項: 把錯誤信息輸出到'stdout'

'-s'選項可以把錯誤信息重定向到'stdout'而不是'stderr',它可以在'MS-DOS'下進行

重定向。想要在彙編檔案'myfile.asm'時把它的輸出用管道輸出給'more'程式,可以這樣:

nasm -s -f obj myfile.asm | more

請參考2.1.7的'-E'選項.

2.1.9 `-i'選項: 包含檔案搜尋路徑

當NASM在源檔案中看到'%include'操作符時(參閱4.6),它不僅僅會在當前目錄下搜尋給

出的檔案,還會搜尋'-i'選項在命令行中指定的所有路徑。所以你可以從宏定義庫中

包含進一個檔案,比如,輸入:

nasm -ic:\macrolib\ -f obj myfile.asm

(通常,在 '-i'與路徑名之間的空格是允許的,並且可選的。)

NASM更多的關注原始碼級上的完全可移植性,所以並不理解正運行的作業系統對檔案的

命名習慣;你提供給'-i'作為參數的的字元串會被一字不差地加在包含檔案的檔案名稱前。

所以,上例中最後面的一個反斜槓是必要的,在Unix下,一個尾部的正斜線也同樣是必要的。

(當然,如果你確實需要,你也可以不正規地使用它,比如,選項'-ifoo'會導致

'%incldue "bar.i'去搜尋檔案'foobar.i'...)

如果你希望定義一個標準的搜尋路徑,比如像Unix系統下的'/usr/include',你可以在環境

變數NASMENV中放置一個或多個'-i'(參閱2.1.19)

為了與絕大多數C編譯器的Makefile保持兼容,該選項也可以被寫成'-I'。

2.1.10 `-p' 選項: 預包含一個檔案

NASM允許你通過'-p'選項來指定一個檔案預包含進你的源檔案。所以,如果運行:

nasm myfile.asm -p myinc.inc

跟在源檔案開頭寫上'%include "myinc.inc"然後運行'nasm myfile.asm'是等效的。

為和'-I','-D','-U'選項操持一致性,該選項也可以被寫成'-P'

2.1.11 `-d'選項: 預定義一個宏。

就像'-p'選項給出了在檔案頭放置'%include'的另一種實現,'-d'選項給出了在文

件中寫'%define'的另一種實現,你可以寫:

nasm myfile.asm -dFOO=100

作為在檔案中寫下面一行語句的一種替代實現:

%define FOO 100

在檔案的開始,你可以取消一個宏定義,同樣,選項'-dFOO'等同於代碼'%define FOO'。

這種形式的操作符在選擇編譯時操作中非常有用,它們可以用'%ifdef'來進行測試,

比如'-dDEBUG'。

為了與絕大多數C編譯器的Makefile保持兼容,該選項也可以被寫成'-D'。

2.1.12 `-u' 選項: 取消一個宏定義。

'-u'選項可以用來取消一個由'-p'或'-d'選項先前在命令行上定義的一個宏定義。

比如,下面的命令語句:

nasm myfile.asm -dFOO=100 -uFOO

會導致'FOO'不是一個在程式中預定義的宏。這在Makefile中不同位置重載一個操

作時很有用。

為了與絕大多數C編譯器的Makefile保持兼容,該選項也可以被寫成'-U'。

2.1.13 `-e'選項: 僅預處理。

NASM允許預處理器獨立運行。使用'-e'選項(不需要參數)會導致NASM預處理輸入

檔案,展開所有的宏,去掉所有的注釋和預處理操作符,然後把結果檔案列印在標

準輸出上(如果'-o'選項也被指定的話,會被存入一個檔案)。

該選項不能被用在那些需要預處理器去計算與符號相關的表達式的程式中,所以

如下面的代碼:

%assign tablesize ($-tablestart)

會在僅預處理模式中會出錯。

2.1.14 `-a' 選項: 不需要預處理。

如果NASM被用作編譯器的後台,那么假設編譯器已經作完了預處理,並禁止NASM的預

處理功能顯然是可以節約時間,加快編譯速度。'-a'選項(不需要參數),會讓NASM把

它強大的預處理器換成另一個什麼也不做的預處理器。

2.1.15 `-On'選項: 指定多遍最佳化。

NASM在預設狀態下是一個兩遍的彙編器。這意味著如果你有一個複雜的源檔案需要

多於兩遍的彙編。你必須告訴它。

使用'-O'選項,你可以告訴NASM執行多遍彙編。語法如下:

(*)'-O0'嚴格執行兩遍最佳化,JMP和Jcc的處理和0.98版類似,除了向後跳的JMP是短跳

轉,如果可能,立即數在它們的短格式沒有被指定的情況下使用長格式。

(*)'-O1'嚴格執行兩遍最佳化,但前向分支被彙編成保證能夠到達的代碼;可能產生比

'-O0'更大的代碼,但在分支中的偏移地址沒有指定的情況下彙編成功的機率更大,

(*)'-On' 多編最佳化,最小化分支的偏移,最小化帶符號的立即數,當'strict'關鍵字

沒有用的時候重載指定的大小(參閱3.7),如果2<=n<=3,會有5*n遍,而不是n遍。

注意這是一個大寫的O,和小寫的o是不同的,小寫的o是指定輸出檔案的格式,可參閱

2.1.1

2.1.16 `-t'選項: 使用TASM兼容模式。

NASM有一個與Borlands的TASM之間的受限的兼容格式。如果使用了NASM的'-t'選項,

就會產生下列變化:

(*)本地符號的前綴由'.'改為'@@'

(*)TASM風格的以'@'開頭的應答檔案可以由命令行指定。這和NASM支持的'-@resp'

風格是不同的。

(*)括弧中的尺寸替換被支持。在TASM兼容模式中,方括弧中的尺寸替換改變了操作

數的尺寸大小,方括弧不再支持NASM語法的運算元地址。比如,'mov eax,[DWORD VAL]'

在TASM兼容語法中是合法的。但注意你失去了為指令替換預設地址類型的能力。

(*)'%arg'預處理操作符被支持,它同TASM的ARG操作符相似。

(*) `%local'預處理操作符。

(*) `%stacksize'預處理操作符。

(*) 某些操作符的無前綴形式被支持。 (`arg', `elif',`else', `endif', `if',

`ifdef', `ifdifi', `ifndef', `include',`local')

(*) 還有很多...

需要更多的關於操作符的信息,請參閱4.9的TASM兼容預處理操作符指令。

2.1.17 `-w'選項: 使彙編警告信息有效或無效。

NASM可以在彙編過程中監視很多的情況,其中很多是值得反饋給用戶的,但這些情況

還不足以構成嚴重錯誤以使NASM停止產生輸出檔案。這些情況被以類似錯誤的形式

報告給用戶,但在報告信息的前面加上'warning'字樣。警告信息不會阻止NASM產生

輸出檔案並向作業系統返回成功信息。

有些情況甚至還要寬鬆:他們僅僅是一些值得提供給用戶的信息。所以,NASM支持'-w'

命令行選項。它以使特定類型的彙編警告信息輸出有效或無效。這樣的警告類型是

以命名來描述的,比如,'orphan-labels',你可以以下列的命令行選項讓此類警告信息

得以輸出:'-w+orphan-labels',或者以'-w-orphan-labels'讓此類信息不能輸出。

可禁止的警告信息類型有下列一些:

(*)`macro-params'包括以錯誤的參數個數調用多行的宏定義的警告。這類警告信息

預設情況下是輸出的,至於為什麼你可能需要禁止它,請參閱4.3.1。

(*)`orphan-labels'包含源檔案行中沒有指令卻定義了一個沒有結尾分號的label的

警告。預設狀況下,NASM不輸出此類警告。如果你需要它,請參閱3.1的例子。

(*) 'number-overflow'包含那些數值常數不符合32位格式警告信息(比如,你很容易打

了很多的F,錯誤產生了'0x7fffffffffff')。這種警告信息預設狀況下是打開的。

2.1.18 `-v'選項: 列印版本信息。

輸入'NASM -v'會顯示你正使用的NASM的版本號,還有它被編譯的時間。

如果你要提交bug報告,你可能需要版本號。

2.1.19 `NASMENV'環境變數。

如果你定義了一個叫'NASMENV'的環境變數,程式會被把它認作是命令行選項附加的一

部分,它會在真正的命令行之前被處理。你可以通過在'NASMENV'中使用'-i'選項來定

義包含檔案的標準搜尋路徑。

環境變數的值是通過空格符分隔的,所以值'-s ic:\nasmlib'會被看作兩個單獨的操

作。也正因為如此,意味著值'-dNAME="my name"不會象你預期的那樣被處理, 因為它

會在空格符處被分開,NASM的命令行處理會被兩個沒有意義的字元串'-dNAME="my'和

'name"'給弄混。

為了解決這個問題,NASM為此提供了一個特性,如果你在'NASMENV'環境變數的第一個

字元處寫上一個非減號字元,NASM就會把這個字元當作是選項的分隔設定。所以把環

境變數設成'!-s!-ic:\nasmlib'跟'-s -ic:\nasmlib'沒什麼兩樣,但是

'!-dNAME="my name"就會正常工作了。

這個環境變數以前叫做'NASM',從版本0.98.32以後開始叫這個名字。

2.2 MASM用戶速成。

如果你曾使用MASM寫程式,或者使用在MASM兼容模式下使用TASM, 或者使用'a86',

本節將闡述MASM與NASM語法之間的主要區別。如果你沒有使用過MASM,那最好先

跳過這一節。

2.2.1 NASM是大小寫敏感的。

一個簡單的區別是NASM是大小寫敏感的。當你調用你的符號'foo','Foo',或

'FOO'時,它們是不同的。如果你在彙編'DOS'或'OS/2', '.OBJ'檔案,你可以使

用'UPPERCASE'操作符來保證所有的導出到其他代碼模式的符號都是大寫的;但

是,在僅僅一個單獨的模組中,NASM會區分大小寫符事情。

2.2.2 NASM需要方括弧來引用記憶體地址。

NASM的設計思想是語法儘可能簡潔。它的一個設計目標是,它將在被使用的過程

中,儘可能得讓用戶看到一個單行的NASM代碼時,就可以說出它會產生什麼操作

碼。你可以在NASM中這樣做,比如,如果你聲明了:

foo equ 1

bar dw 2

然後有兩行的代碼:

mov ax,foo

mov ax,bar

儘管它們有看上去完全相同的語法,但卻產生了完全不同的操作碼

NASM為了避免這種令人討厭的情況,擁有一個相當簡單的記憶體引用語未能。規則

是任何對記憶體中內容的存取操作必須要在地址上加上方括弧。但任何對地址值

的操作不需要。所以,形如'mov ax,foo'的指令總是代表一個編譯時常數,不管它

是一個 'EQU'或一個變數的地址;如果要取變數'bar'的內容,你必須與

'mov ax,'。

這也意味著NASM不需要MASM的'OFFSET'關鍵字,因為MASM的代碼'mov ax,offset bar'

同NASM的語法'mov ax,bar'是完全等效的。如果你希望讓大量的MASM代碼能夠被

NASM彙編通過,你可以編寫'%idefine offset'讓預處理器把'OFFSET'處理成一個無

操作符。

這個問題在'a86'中就更混亂了。

NASM因為關注簡潔性,同樣不支持MASM和它的衍生產品支持的的混合語法,比如像

:'mov ax, table',這裡,一個中括弧外的部分加上括弧內的一個部分引用一個

記憶體地址,上面代碼的正確語法是:'mov ax,[table+bx] 。同樣,'mov ax,es:[di]'

也是錯誤的,正確的應該是'mov ax,[es:di]'。

2.2.3 NASM不存儲變數的類型。

NASM被設計成不記住你聲明的變數的類型。然而,MASM在看到'var dw 0'時會記住

類型,然後就可以隱式地合用'mov var, 2'給變數賦值。NASM不會記住關於變數

'var'的任何東西,除了它的位置,所以你必須顯式地寫上代碼'mov word [var],2'。

因為這個原因,NASM不支持'LODS','MOVS','STOS','SCANS','CMPS','INS',或'OUTS'

指令,僅僅支持形如'LODSB','MOVSW',和'SCANSD'之灰的指令。它們都顯式地指定

被處理的字元串的尺寸。

2.2.4 NASM不會 `ASSUME'

作為NASM簡潔性的一部分,它同樣不支持'ASSUME'操作符。NASM不會記住你往段寄

存器里放了什麼值,也不會自動產生段替換前綴。

2.2.5 NASM不支持記憶體模型。

NASM同樣不含有任何操作符來支持不同的16位記憶體模型。程式設計師需要自己跟蹤那

些函式需要far call,哪些需要near call。並需要確定放置正確的'RET'指令('RETN'

或'RETF'; NASM接受'RET'作為'RETN'的另一種形式);另外程式設計師需要在調用外部函

數時在需要的編寫CALL FAR指令,並必須跟蹤哪些外部變數定義是far,哪些是near。

2.2.6 浮點處理上的不同。

NASM使用跟MASM不同的浮點暫存器名:MASM叫它們'ST(0)','ST(1)'等,而'a86'叫它們

'0','1'等,NASM則叫它們'st0','st1'等。

在版本0.96上,NASM現在以跟MASM兼容彙編器同樣的方式處理'nowait'形式的指令,

0.95以及更早的版本上的不同的處理方式主要是因為作者的誤解。

2.2.7 其他不同。

由於歷史的原因,NASM把MASM兼容彙編器的'TBYTE'寫成'TWORD'。

NASM以跟MASM不同的一種方式聲明未初始化的記憶體。MASM的程式設計師必須使用

'stack db 64 dup (?)', NASM需要這樣寫:'stack resb 64',讀作"保留64位元組"。為了

保持可移植性,NASM把'?'看作是符號名稱中的一個有效的字元,所以你可以編寫這樣

的代碼'? equ 0', 然後寫'dw ?'可以做一些有用的事情。'DUP'還是一個不被支持的語法。

另外,宏與操作符的工作方式也與MASM完全不同,可以到參閱第4,第5章。

第三章 NASM語言

3.1 NASM源程式行的組成。

就像很多其他的彙編器,每一行NASM原始碼包含(除非它是一個宏,一個預處理操作

符,或一個彙編器操作符,參況第4,5章)下面四個部分的全部或某幾個部分:

label: instruction operands ; comment

通常,這些域的大部分是可選的;label,instruction,comment存在或不存在都是允

許的。當然,operands域會因為instruction域的要求而必需存或必須不存在。

NASM使用反斜線(\)作為續行符;如果一個以一個反斜線結束,那第二行會被認為

是前面一行的一部分。

NASM對於一行中的空格符並沒有嚴格的限制:labels可以在它們的前面有空格,或

其他任何東西。label後面的冒號同樣也是可選的。(注意到,這意味著如果你想

要寫一行'lodsb',但卻錯誤地寫成了'lodab',這仍將是有效的一行,但這一行不做

任何事情,只是定義了一個label。運行NASM時帶上命令行選項'-w+orphan-labels'

會讓NASM在你定義了一個不以冒號結尾的label時警告你。

labels中的有效的字元是字母,數字,'-','$','#','@','~','.'和'?'。但只有字母

'.',(具有特殊含義,參閱3.9),'_'和'?'可以作為標識符的開頭。一個標識符還可

以加上一個'$'前綴,以表明它被作為一個標識符而不是保留字來處理。這樣的話,

如果你想到連結進來的其他模組中定義了一個符號叫'eax',你可以用'$eax'在

NASM代碼中引用它,以和暫存器的符號區分開。

instruction域可以包含任何機器指令:Pentium和P6指令,FPU指令,MMX指令還有甚

至沒有公開的指令也會被支持。這些指令可以加上前綴'LOCK','REP','REPE/REPZ'

或'REPNE'/'REPNZ',通常,支持顯示的地址尺寸和運算元尺寸前綴'A16','A32',

'O16'和'O32'。關於使用它們的一個例子在第九章給出。你也可以使用段暫存器

名作為指令前綴: 代碼'es mov ,ax'等效於代碼'mov [es:bx],ax'。我們推薦

後一種語法。因為它和語法中的其它語法特性一致。但是對於象'LODSB'這樣的

指令,它沒有運算元,但還是可以有一個段前綴, 對於'es lodsb'沒有清晰地語法

處理方式

在使用一個前綴時,指令不是必須的,像'CS','A32','LOCK'或'REPE'這樣的段前綴

可以單獨出現在一行上,NASM僅僅產生一個前綴位元組。

作為對實際機器指令的擴展,NASM同時提供了一定數量的偽操作指令,這在3.2節

詳細描述。

指令運算元可以使用一定的格式:它們可以是暫存器,僅僅以暫存器名來表示(比

如:'ax','bp','ebx','cr0':NASM不使用'gas'的語法風格,在這種風格中,暫存器名

前必須加上一個'%'符號),或者它們可以是有效的地址(參閱3.3),常數(3.4),或

表達式。

對於浮點指令,NASM接受各種語法:你可以使用MASM支持的雙運算元形式,或者你

可以使用NASM的在大多數情況下全用的單運算元形式。支持的所以指令的語法

細節可以參閱附錄B。比如,你可以寫:

fadd st1 ; this sets st0 := st0 + st1

fadd st0,st1 ; so does this

fadd st1,st0 ; this sets st1 := st1 + st0

fadd to st1 ; so does this

幾乎所有的浮點指令在引用記憶體時必須使用以下前綴中的一個'DWORD',QWORD'

或'TWORD'來指明它所引用的記憶體的尺寸。

3.2 偽指令。

偽指令是一些並不是真正的x86機器指令,但還是被用在了instruction域中的指

令,因為使用它們可以帶來很大的方便。當前的偽指令有'DB','DW','DD','DQ'和

‘DT’,它們對應的未初始化指令是'RESB','RESW','RESD','RESQ'和'REST','INCBIN'

命令,'EQU'命令和'TIEMS'前綴。

3.2.1 `DB'一類的偽指令: 聲明已初始化的數據。

在NASM中,`DB', `DW', `DD', `DQ'和`DT'經常被用來在輸出檔案中聲明已初始化

的數據,你可以多種方式使用它們:

db 0x55 ; just the byte 0x55

db 0x55,0x56,0x57 ; three bytes in succession

db 'a',0x55 ; character constants are OK

db 'hello',13,10,'$' ; so are string constants

dw 0x1234 ; 0x34 0x12

dw 'a' ; 0x41 0x00 (it's just a number)

dw 'ab' ; 0x41 0x42 (character constant)

dw 'abc' ; 0x41 0x42 0x43 0x00 (string)

dd 0x12345678 ; 0x78 0x56 0x34 0x12

dd 1.234567e20 ; floating-point constant

dq 1.234567e20 ; double-precision float

dt 1.234567e20 ; extended-precision float

'DQ'和'DT'不接受數值常數或字元串常數作為運算元。

3.2.2 `RESB'類的偽指令: 聲明未初始化的數據。

`RESB', `RESW', `RESD', `RESQ' and `REST'被設計用在模組的BSS段中:它們聲明

未初始化的存儲空間。每一個帶有單個運算元,用來表明位元組數,字數,或雙字數

或其他的需要保留單位。就像在2.2.7中所描述的,NASM不支持MASM/TASM的扣留未

初始化空間的語法'DW ?'或類似的東西:現在我們所描述的正是NASM自己的方式。

'RESB'類偽指令的運算元是有嚴格的語法的,參閱3.8。

比如:

buffer: resb 64 ; reserve 64 bytes

wordvar: resw 1 ; reserve a word

realarray resq 10 ; array of ten reals

3.2.3 `INCBIN':包含其他二進制檔案。

'INCBIN'是從老的Amiga彙編器DevPac中借過來的:它將一個二進制檔案逐字逐句地

包含到輸出檔案中。這能很方便地在一個遊戲執行檔中包含中圖像或聲音數

據。它可以以下三種形式的任何一種使用:

incbin "file.dat" ; include the whole file

incbin "file.dat",1024 ; skip the first 1024 bytes

incbin "file.dat",1024,512 ; skip the first 1024, and

; actually include at most 512

3.2.4 `EQU': 定義常數。

'EQU'定義一個符號,代表一個常量值:當使用'EQU'時,源檔案行上必須包含一個label。

'EQU'的行為就是把給出的label的名字定義成它的運算元(唯一)的值。定義是不可更

改的,比如:

message db 'hello, world'

msglen equ $-message

把'msglen'定義成了常量12。'msglen'不能再被重定義。這也不是一個預自理定義:

'msglen'的值只被計算一次,計算中使用到了'$'(參閱3.5)在此時的含義。注意

‘EQU’的運算元也是一個嚴格語法的表達式。(參閱3.8)

3.2.5 `TIMES': 重複指令或數據。

前綴'TIMES'導致指令被彙編多次。它在某種程式上是NASM的與MASM兼容彙編器的

'DUP'語法的等價物。你可以這樣寫:

zerobuf: times 64 db 0

或類似的東西,但'TEIMES'的能力遠不止於此。'TIMES'的參數不僅僅是一個數值常

數,還有數值表達式,所以你可以這樣做:

buffer: db 'hello, world'

times 64-$+buffer db ' '

它可以把'buffer'的長度精確地定義為64位元組,’TIMES‘可以被用在一般地指令上,

所以你可像這要編寫不展開的循環:

times 100 movsb

注意在'times 100 resb 1'跟'resb 100'之間並沒有顯著的區別,除了後者在彙編

時會快上一百倍。

就像'EQU','RESB'它們一樣, 'TIMES'的運算元也是嚴格語法的表達式。(見3.8)

注意'TIMES'不可以被用在宏上:原因是'TIMES'在宏被分析後再被處理,它允許

’TIMES'的參數包含像上面的'64-$+buffer'這樣的表達式。要重複多於一行的代

碼,或者一個宏,使用預處理指令'%rep'。

3.3 有效地址

一個有效地址是一個指令的運算元,它是對記憶體的一個引用。在NASM中,有效地址

的語法是非常簡單的:它由一個可計算的表達式組成,放在一個中括弧內。比如:

wordvar dw 123

mov ax,[wordvar]

mov ax,[wordvar+1]

mov ax,[es:wordvar+bx]

任何與上例不一致的表達都不是NASM中有效的記憶體引用,比如:'es:wordvar'。

更複雜一些的有效地址,比如含有多個暫存器的,也是以同樣的方式工作:

mov eax,[ebx*2+ecx+offset]

mov ax,

NASM在這些有效地址上具有進行代數運算的能力,所以看似不合法的一些有效地址

使用上都是沒有問題的:

mov eax,[ebx*5] ; assembles as [ebx*4+ebx]

mov eax,[label1*2-label2] ; ie [label1+(label1-label2)]

有些形式的有效地址在彙編後具有多種形式;在大多數情況下,NASM會自動產生

最小化的形式。比如,32位的有效地址'[eax*2+0]'和'[eax+eax]'在彙編後具有

完全不同的形式,NASM通常只會生成後者,因為前者會為0偏移多開闢4個位元組。

NASM具有一種隱含的機制,它會對'[eax+ebx]'和'[ebx+eax]'產生不同的操作碼;

通常,這是很有用的,因為'[esi+ebp]'和'[ebp+esi]'具有不同的預設段暫存器。

儘管如此,你也可以使用關鍵字'BYTE','WORD','DWORD'和'NOSPLIT'強制NASM產

生特定形式的有效地址。如果你想讓'[eax+3]'被彙編成具有一個double-word的

偏移域,而不是由NASM預設產生一個位元組的偏移。你可以使用'[dword eax+3]',

同樣,你可以強制NASM為一個第一遍彙編時沒有看見的小值產生一個一位元組的偏

移(像這樣的例子,可以參閱3.8)。比如:''。有一種特殊情

況,‘'會被彙編成'[eax+0]'。帶有一個位元組的0偏移。而'[dword

eax]'會帶一個double-word的0偏移。而常用的形式,'[eax]'則不會帶有偏移域。

當你希望在16位的代碼中存取32位段中的數據時,上面所描述的形式是非常有用

的。關於這方面的更多信息,請參閱9.2。實際上,如果你要存取一個在已知偏

移地址處的數據,而這個地址又大於16位值,如果你不指定一個dword偏移,

NASM會讓高位上的偏移值丟失。

類似的,NASM會把'[eax*2]'分裂成'[eax+eax]' ,因為這樣可以讓偏移域不存在

以此節省空間;實際上,它也把'[eax*2+offset]'分成'[eax+eax+offset]',你

可以使用‘NOSPLIT'關鍵字改變這種行為:`[nosplit eax*2]'會強制

`[eax*2+0]'按字面意思被處理。

3.4 常數

NASM能理解四種不同類型的常數:數值,字元,字元串和浮點數。

3.4.1 數值常數。

一個數值常數就只是一個數值而已。NASM允許你以多種方式指定數值使用的

進制,你可以以後綴'H','Q','B'來指定十六進制數,八進制數和二進制數,

或者你可以用C風格的前綴'0x'表示十六進制數,或者以Borland Pascal風

格的前綴'$'來表示十六進制數,注意,'$'前綴在標識符中具有雙重職責

(參閱3.1),所以一個以'$'作前綴的十六進制數值必須在'$'後緊跟數字,而

不是字元。

請看一些例子:

mov ax,100 ; decimal

mov ax,0a2h ; hex

mov ax,$0a2 ; hex again: the 0 is required

mov ax,0xa2 ; hex yet again

mov ax,777q ; octal

mov ax,10010011b ; binary

3.4.2 字元型常數。

一個字元常數最多由包含在雙引號或單引號中的四個字元組成。引號的類型

與使用跟NASM其它地方沒什麼區別,但有一點,單引號中允許有雙引號出現。

一個具有多個字元的字元常數會被little-endian order,如果你編寫:

mov eax,'abcd'

產生的常數不會是`0x61626364',而是`0x64636261',所以你把常數存入記憶體

的話,它會讀成'abcd'而不是'dcba'。這也是奔騰的'CPUID'指令理解的字元常

數形式(參閱B.4.34)

3.4.3 字元串常數。

字元串常數一般只被一些偽操作指令接受,比如'DB'類,還有'INCBIN'。

一個字元串常數和字元常數看上去很相像,但會長一些。它被處理成最大長

度的字元常數之間的連線。所以,以下兩個語句是等價的:

db 'hello' ; string constant

db 'h','e','l','l','o' ; equivalent character constants

還有,下面的也是等價的:

dd 'ninechars' ; doubleword string constant

dd 'nine','char','s' ; becomes three doublewords

db 'ninechars',0,0,0 ; and really looks like this

注意,如果作為'db'的運算元,類似'ab'的常數會被處理成字元串常量,因

為它作為字元常數的話,還不夠短,因為,如果不這樣,那'db 'ab'會跟

'db 'a''具有同樣的效果,那是很愚蠢的。同樣的,三字元或四字元常數會

在作為'dw'的運算元時被處理成字元串。

3.4.4 浮點常量

浮點常量只在作為'DD','DQ','DT'的運算元時被接受。它們以傳統的形式表

達:數值,然後一個句點,然後是可選的更多的數值,然後是選項'E'跟上

一個指數。句點是強制必須有的,這樣,NASM就可以把它們跟'dd 1'區分開,

它只是聲明一個整型常數,而'dd 1.0'聲明一個浮點型常數。

一些例子:

dd 1.2 ; an easy one

dq 1.e10 ; 10,000,000,000

dq 1.e+10 ; synonymous with 1.e10

dq 1.e-10 ; 0.000 000 000 1

dt 3.141592653589793238462 ; pi

NASM不能在編譯時求浮點常數的值。這是因為NASM被設計為可移植的,儘管它

常產生x86處理器上的代碼,彙編器本身卻可以和ANSI C編譯器一起運行在任

何系統上。所以,彙編器不能保證系統上總存在一個能處理Intel浮點數的浮

點單元。所以,NASM為了能夠處理浮點運算,它必須含有它自己的一套完整

的浮點處理例程,它大大增加了彙編器的大小,卻獲得了並不多的好處。

3.5 表達式

NASM中的表達式語法跟C里的是非常相似的。

NASM不能確定編譯時在計算表達式時的整型數尺寸:因為NASM可以在64位系

統上非常好的編譯和運行,不要假設表達式總是在32位的暫存器中被計算的,

所以要慎重地對待整型數溢出的情況。它並不總能正常的工作。NASM唯一能

夠保證的是:你至少擁有32位長度。

NASM在表達式中支持兩個特殊的記號,即'$'和'$$',它們允許引用當前指令

的地址。'$'計算得到它本身所在原始碼行的開始處的地址;所以你可以簡

單地寫這樣的代碼'jmp $'來表示無限循環。'$$'計算當前段開始處的地址,

所以你可以通過($-$$)找出你當前在段內的偏移。

NASM提供的運算符以運算優先權為序列舉如下:

3.5.1 `|': 位或運算符。

運算符'|'給出一個位級的或運算,所執行的操作與機器指令'or'是完全相

同的。位或是NASM中優先權最低的運算符。

3.5.2 `^': 位異或運算符。

`^' 提供位異或操作。

3.5.3 `&': 位與運算符。

`&' 提供位與運算。

3.5.4 `<<' and `>>': 位移運算符。

`<<' 提供位左移, 跟C中的實現一樣,所以'5<<3'相當於把5乘上8。'>>'提

供位右移。在NASM中,這樣的位移總是無符號的,所以位移後,左側總是以

零填充,並不會有符號擴展。

3.5.5 `+' and `-': 加與減運算符。

'+'與'-'運算符提供完整的普通加減法功能。

3.5.6 `*', `/', `//', `%'和`%%': 乘除法運算符。

'*'是乘法運算符。'/'和'//'都是除法運算符,'/'是無符號除,'//'是帶

符號除。同樣的,'%'和'%%'提供無符號與帶符號的模運算。

同ANSI C一樣,NASM不保證對帶符號模操作執行的操作的有效性。

因為'%'符號也被宏預處理器使用,你必須保證不管是帶符號還是無符號的

模操作符都必須跟有空格。

3.5.7 一元運算符: `+', `-', `~'和`SEG'

這些只作用於一個參數的一元運算符是NASM的表達式語法中優先權最高的。

'-'把它的運算元取反,'+'不作任何事情(它只是為了和'-'保持對稱),

'~'對它的運算元取補碼,而'SEG'提供它的運算元的段地址(在3.6中會有

詳細解釋)。

3.6 `SEG'和`WRT'

當寫很大的16位程式時,必須把它分成很多段,這時,引用段內一個符號的

地址的能力是非常有必要的,NASM提供了'SEG'操作符來實現這個功能。

'SEG'操作符返回符號所在的首選段的段基址,即一個段基址,當符號的偏

移地址以它為參考時,是有效的,所以,代碼:

mov ax,seg symbol

mov es,ax

mov bx,symbol

總是在'ES:BX'中載入一個指向符號'symbol'的有效指針。

而事情往往可能比這還要複雜些:因為16位的段與組是可以相互重疊的,

你通常可能需要通過不同的段基址,而不是首選的段基址來引用一個符

號,NASM可以讓你這樣做,通過使用'WRT'關鍵字,你可以這樣寫:

mov ax,weird_seg ; weird_seg is a segment base

mov es,ax

mov bx,symbol wrt weird_seg

會在'ES:BX'中載入一個不同的,但功能上卻是相同的指向'symbol'的指

針。

通過使用'call segment:offset',NASM提供fall call(段內)和jump,這裡

'segment'和'offset'都以立即數的形式出現。所以要調用一個遠過程,你

可以如下編寫代碼:

call (seg procedure):procedure

call weird_seg:(procedure wrt weird_seg)

(上面的圓括弧只是為了說明方便,實際使用中並不需要)

NASM支持形如'call far procedure'的語法,跟上面第一句是等價的。'jmp'

的工作方式跟'call'在這裡完全相同。

在數據段中要聲明一個指向數據元素的遠指針,可以象下面這樣寫:

dw symbol, seg symbol

NASM沒有提供更便利的寫法,但你可以用宏自己建造一個。

3.7 `STRICT': 約束最佳化。

當在彙編時把最佳化器打開到2或更高級的時候(參閱2.1.15)。NASM會使用

尺寸約束('BYTE','WORD','DWORD','QWORD',或'TWORD'),會給它們盡可

能小的尺寸。關鍵字'STRICT'用來制約這種最佳化,強制一個特定的操作

數為一個特定的尺寸。比如,當最佳化器打開,並在'BITS 16'模式下:

push dword 33

會被編碼成 `66 6A 21',而

push strict dword 33

會被編碼成六個位元組,帶有一個完整的雙字立即數`66 68 21 00 00 00'.

而當最佳化器關閉時,不管'STRICT'有沒有使用,都會產生相同的代碼。

3.8 臨界表達式。

NASM的一個限制是它是一個兩遍的彙編器;不像TASM和其它彙編器,它總是

只做兩遍彙編。所以它就不能處理那些非常複雜的需要三遍甚至更多遍彙編

的原始碼。

第一遍彙編是用於確定所有的代碼與數據的尺寸大小,這樣的話,在第二遍

產生代碼的時候,就可以知道代碼引用的所有符號地址。所以,有一件事

NASM不能處理,那就是一段代碼的尺寸依賴於另一個符號值,而這個符號又

在這段代碼的後面被聲明。比如:

times (label-$) db 0

label: db 'Where am I?'

'TIMES'的參數本來是可以合法得進行計算的,但NASM中不允許這樣做,因為

它在第一次看到TIMES時的時候並不知道它的尺寸大小。它會拒絕這樣的代碼。

times (label-$+1) db 0

label: db 'NOW where am I?'

在上面的代碼中,TIMES的參數是錯誤的。

NASM使用一個叫做臨界表達式的概念,以禁止上述的這些例子,臨界表達式

被定義為一個表達式,它所需要的值在第一遍彙編時都是可計算的,所以,

該表達式所依賴的符號都是之前已經定義了的,'TIMES'前綴的參數就是一個

臨界表達式;同樣的原因,'RESB'類的偽指令的參數也是臨界表達式。

臨界表達式可能會出現下面這樣的情況:

mov ax,symbol1

symbol1 equ symbol2

symbol2:

在第一遍的時候,NASM不能確定'symbol1'的值,因為'symbol1'被定義成等於

'symbols2',而這時,NASM還沒有看到symbol2。所以在第二遍的時候,當它遇

上'mov ax,symbol1',它不能為它產生正確的代碼,因為它還沒有知道'symbol1'

的值。當到達下一行的時候,它又看到了'EQU',這時它可以確定symbol1的值

了,但這時已經太晚了。

NASM為了避免此類問題,把'EQU'右側的表達式也定義為臨界表達式,所以,

'symbol1'的定義在第一遍的時候就會被拒絕。

這裡還有一個關於前向引用的問題:考慮下面的代碼段:

mov eax,[ebx+offset]

offset equ 10

NASM在第一遍的時候,必須在不知道'offset'值的情況下計算指令

'mov eax,[ebx+offset]'的尺寸大小。它沒有辦法知道'offset'足夠小,足以

放在一個位元組的偏移域中,所以,它以產生一個短形式的有效地址編碼的方

式來解決這個問題;在第一遍中,它所知道的所有關於'offset'的情況是:它

可能是代碼段中的一個符號,而且,它可能需要四位元組的形式。所以,它強制

這條指令的長度為適合四位元組地址域的長度。在第二遍的時候,這個決定已經

作出了,它保持使這條指令很長,所以,這種情況下產生的代碼沒有足夠的小,

這個問題可以通過先定義offset的辦法得到解決,或者強制有效地址的尺寸大

小,象這樣寫代碼:

3.9 本地Labels

NASM對於那些以一個句點開始的符號會作特殊處理,一個以單個句點開始的

Label會被處理成本地label, 這意味著它會跟前面一個非本地label相關聯.

比如:

label1 ; some code

.loop

; some more code

jne .loop

ret

label2 ; some code

.loop

; some more code

jne .loop

ret

上面的代碼片斷中,每一個'JNE'指令跳至離它較近的前面的一行上,因為'.loop'

的兩個定義通過與它們前面的非本地Label相關聯而被分離開來了。

對於本地Label的處理方式是從老的Amiga彙編器DevPac中借鑑過來的;儘管

如此,NASM提供了進一步的性能,允許從另一段代碼中調用本地labels。這

是通過在本地label的前面加上非本地label前綴實現的:第一個.loop實際上被

定義為'label1.loop',而第二個符號被記作'label2.loop'。所以你確實需要

的話你可寫:

label3 ; some more code

; and some more

jmp label1.loop

有時,這是很有用的(比如在使用宏的時候),可以定義一個label,它可以

在任何地方被引用,但它不會對常規的本地label機制產生干擾。這樣的

label不能是非本地label,因為非本地label會對本地labels的重複定義與

引用產生干擾;也不能是本地的,因為這樣定義的宏就不能知道label的全

稱了。所以NASM引進了第三類label,它只在宏定義中有用:如果一個label

以一個前綴'..@'開始,它不會對本地label產生干擾,所以,你可以寫:

label1: ; a non-local label

.local: ; this is really label1.local

..@foo: ; this is a special symbol

label2: ; another non-local label

.local: ; this is really label2.local

jmp ..@foo ; this will jump three lines up

NASM還能定義其他的特殊符號,比如以兩個句點開始的符號,比如

'..start'被用來指定'.obj'輸出檔案的執行入口。(參閱6.2.6)

相關詞條

相關搜尋

熱門詞條

聯絡我們