asm基礎——nasm使用簡介
編譯命令:
nasm −f <format> <filename> [−o <output>]
引數介紹:
-f:用來指定編譯出來的.o檔案的格式。下面是nasm支援的格式,可以通過nasm -hf來檢視:要檢視本機支援的格式,可以先進入nasm所在的目錄,並執行file nasm命令來檢視:
結合兩張圖,可以確定本機編譯時需要指定的格式是macho64。
-o:用來指定編譯後的檔案的名稱。如果不加引數,則使用原來的.asm的檔名,字尾則根據-f指定的檔案格式有所不同,windows系統是.obj,unix系統是.o。
-l:編譯時生成list檔案,裡面包含程式碼和對應的機器碼等內容。
-M:列印編譯時的依賴檔案,它有各種不同形式的-Mx,這裡不多做介紹,光是-M的話會直接打印出來。
-Ox:優化程式碼,x表示優化的級別,0表示不優化,1-n優化等級依次提高。
-d:定義一個巨集,比如:
nasm myfile.asm −dFOO=100
-u:取消巨集定義,比如:
nasm myfile.asm −dFOO=100 -uFOO
-E:不進行編譯,只是展開所有的巨集,如果沒有接引數來指定檔案,則展開後的程式碼直接打印出來,用-o可以指定展開後的檔案。
其它還有很多的引數,不一一介紹了,可以參考nasmdoc.pdf文件。
nasm中的分段
nasa中使用section關鍵字來分段,後面接的引數有:
1).data,用來定義常量;
2).bbs,用來存放變數;
3).text,用來存放程式碼;
另外,nasm通過global來指定入口。
nasm中的有效地址:
在nasm中,有效地址都需要用[]括起來以獲取其中的內容,下面是一個例子:
var dw 0x55AA
mov ax, [qword var]
對於64位的編譯系統,這裡必須要加上qword,否則編譯會報錯。在nasmdoc.pdf有如下的解釋:
The only instructions which take a full 64−bitdisplacementis loading or storing, using
這裡除了qword,也可以有byte、word等,表示的實際上是一種偏移,比如[byte eax],就是指大小為0的byte偏移。
nasm中的偽指令:
1)宣告已初始化的變數:
db 0x55
dw 0x55AA
dd 0x55AAAA55
2)宣告未初始化的變數:
buffer1: resb 64
buffer2: resw 32
buffer3: resq 16
使用nasm -f bin的格式編譯,得到的二進位制如下:
3)包含二進位制檔案的偽指令:incbin;
4)定義常量:equ:
message db 'hello, world'
msglen equ $−message
5)重複執行指令或者宣告資料:times:
例如下面的程式碼:
times 16 db 0x5A
得到的結果如下:
常用預處理指令:
所有預處理指令都以%開頭。
1)單行巨集定義指令%define:
%define ctrl 0x1F &
%define param(a,b) ((a)+(a)*(b))
mov byte [param(2,ebx)], ctrl 'D'
使用%define會遇到一些問題,比如下面的例子:
%define TRUE 1
%define FALSE TRUE
%define TRUE 0
val1: db FALSE
%define TRUE 1
val2: db FALSE
使用nasm -f bin的格式進行編譯,得到的結果用vim -b開啟,並通過:%!xxd -g 1來檢視,得到的結果如下:
產生這結果的原因是nasm中單行的巨集只有在使用的時候才會展開,對於val1,FALSE的值等於TRUE,而此時TRUE的值是0,所以得到的值也是0;同樣的就得到了val2的值是1。
如果需要在單行巨集定義的時候就展開巨集,可以使用%xdefine這個偽指令,這裡的x就表示expend,“展開”。
同樣是上面的例子,使用%xdefine代替%define,得到的結果就是
使用%xdefine可以立即展開後面接的程式碼中的巨集。
還有一種方法可以得到相同的結果,就是使用%[xxx],它顯式地用來展開巨集。
%define TRUE 1
%define FALSE %[TRUE] ; 注意這裡
%define TRUE 0
val1: db FALSE
%define TRUE 1
val2: db FALSE
它跟使用上例使用%xdefine產生相同的結果。
無論是%define還是%xdefine都有一個加i的版本:%idefine和%ixdefine,這裡的i表示case insensitive,不區分大小寫。
2)用於連線巨集字串和引數的%+,下面是一個例子:
%define BDASTART 400h
%define BDA(x) BDASTART + tBIOSDA. %+ x
struc tBIOSDA
.COM1addr resw 1
.COM2addr resw 1
endstruc
mov ax, BDASTART + tBIOSDA.COM1addr
mov bx, BDA(COM1addr)
兩句mov指令傳遞的資料是一致的,後者使用了巨集定義,看起來更清晰,這就是因為使用了%+的緣故。下面是是巨集展開的結果:
需要注意一點,這裡的%+之後有一個空格。
3)表示巨集名字的%?和%??。
4)%undef,用來取消巨集。
5)%assign與%define相似,用來處理單行的巨集,但要求巨集不帶引數,值是數值。
6)對於多行的巨集,使用%macro,下面是一個例子:
%macro prologue
push rax
push rbx
%endmacro
section .text
prologue
巨集展開後的結果:
從結果看展開是沒有問題的,但是有一個報錯,這是因為nasm中使用%macro時需要指定引數個數,上例中沒有引數,那麼就是0,正確的程式碼應該是這樣的:
%macro prologue 0
push rax
push rbx
%endmacro
section .text
prologue
當引數個數不為0,則在巨集內部使用%1、%2等來訪問引數,下面是一個例子:
%macro prologue 2
push %1
push %2
%endmacro
section .text
prologue rax, rbx
兩個prologue相當於是一個過載。預設的指令似乎也可以過載,但是最好不要。
上例中一是要注意%1%2等使用,另外還需要注意rax,rbx作為引數的傳遞,兩個引數之間使用逗號分隔。但是存在一種情況是單個引數之間本身就包含逗號,這個時候就可以通過用{}將引數包圍起來的方法,下面是一個例子:%macro silly 2
%2: db %1
%endmacro
section .data
silly {0xd, 0xa}, clrf
這裡定義了一個回車,第一個引數包含兩個值。
上面的程式碼還有另一種寫法,這種寫法使用了"Greedy Parameters",它類似於c語言中的不定引數,修改上面的程式碼:
%macro silly 2+
%1: db %2
%endmacro
section .data
silly clrf, 0xd, 0xa
這裡的2+就是"Greedy Parameters"的宣告方式。上例還需要了一下引數的位置,因為"Greedy Parameters"需要放在最後。
在多行巨集裡面可以新增標籤,且這個標籤只在當前的巨集中有效,因此多次呼叫該巨集不會因為標籤問題受到影響。
使用%%xx來定義巨集中的標籤,xx是標籤名,下面是一個例子:
%macro retz 0
jnz %%skip
ret
%%skip:
%endmacro
section .text
retz
retz
展開後就可以看出為什麼標籤不會衝突了:
nasm中的巨集還可以指定預設引數,格式如下:
%macro prologue 0-1 rax
push %1
%endmacro
section .text
prologue
上例中的0-1表示巨集可以有0個或者1個引數,如果是0個,則使用後接的預設的rax。
上例還比較簡單,下面可能稍微複雜一點:
%macro prologue 1-3 rax, rbx
上面的程式碼表示,prologue必須要帶至少一個引數,最多3個引數,如果第2、3個引數不存在,則分別由rax和rbx代替。
還有幾個特殊的引數可以在多行巨集中使用:
a. %0表示引數的個數;
b. %rotate類似shell指令碼中的shift,用來遍歷引數;
最後,%unmacro用來取消多行巨集定義。
7)nasm中可以使用的條件判斷:
%if<condition>
; some code which only appears if <condition> is met
%elif<condition2>
; only appears if <condition> is not met but <condition2> is
%else
; this appears if neither <condition> nor <condition2> was met
%endif