看到一篇文章,是介紹nasm語法的:http://blog.csdn.net/hitop0609/article/details/4329454
masm是微軟專門為windows下匯編而寫的,而nasm可以在windows、linux等系統下匯編,故而個人推薦使用nasm。
3.1 nasm 是區分大小寫
例如:符號 foo 與 FOO 是兩個不同的標識符。
3.2 內存操作數表達式
3.2.1 在 nasm 語法里,對 memory 操作數需要加 [ ] 括號
下面的代碼:
foo equ 1 bar dw 2 bits 32 mov eax, foo mov ebx, bar
第 2 指令的意圖是:將 bar 內 的值賦給 ebx 寄存器。但這樣是錯誤的,nasm 只會把 bar 當作是 immediate 賦給 ebx
00000000 0200 ; bar 變量 00000002 B801000000 mov eax,0x1 00000007 BB00000000 mov ebx,0x0 ; 將 bar 地址作為 immediate 賦給 ebx
因此,需要將 bar 用 [ ] 括起來
mov eax, foo
mov ebx, [bar]
nasm 就編譯出正確的代碼:
00000000 0200 ; bar 00000002 B801000000 mov eax,0x1 00000007 8B1D00000000 mov ebx,[dword 0x0] ; bar 的內容賦給 ebx
3.2.2 給 memory 操作數提供一個 displacement 值
下面代碼展示了 [base + disp] 的尋址方式:
section .bss buffer resb 10 section .text bits 32 mov byte [buffer + 0x01] , 'a' mov ebx, buffer movzx eax, byte [ebx + 0x01]
3.2.3 指明 memory 操作數的 operand size
下面展示了為 memory 操作數提供一個 size 情況:
mov byte [buffer + 0x01] , 'a' mov ebx, buffer movzx eax, byte [ebx + 0x01]
代碼中使用 byte 關鍵字對 memory 操作數進行了修飾,指明 memory 操作數的大小為 byte
在這方面,nasm 的語法與微軟的 masm 的語法(Intel 語法)有些不同,masm 的語法是:
mov byte ptr [buffer + 0x01] , 'a' mov ebx, buffer movzx eax, byte ptr [ebx + 0x01]
在 masm 語法中需配合 ptr 指示字。
3.2.4 提供一個 segment
大多數 指令/內存操作數 缺省的 segment 是 DS ,x86/x64 允許為 memory 操作數提供另一個 segment 進行 segment override
在 nasm 語法中,如下:
mov byte [es: buffer + 0x01] , 'a' mov ebx, buffer movzx eax, byte [es: ebx + 0x01]
nasm 語法中,在 [ ] 括號內提供 segment ,不能在 [ ] 括號外提供 segment
而 masm 的語法中是在 [ ] 括號外提供 segment,如下:
mov byte ptr es: [buffer + 0x01] , 'a' mov ebx, buffer movzx eax, byte ptr es: [ebx + 0x01]
3.2.5 指明 memory 操作數的 address size
有些情況下必須指明 memory 操作數的 address size ,否則編譯結果可能不是你想要的結果。下面的例子說明,如何為 memory 操作數指明 address size
例 1:
section .bss buffer resb 10 section .text bits 64 mov rax, [qword buffer] ; 指明 64 位的 address size(絕對地址) mov rax, [buffer] ; 使用 32 位的 address size(絕對地址)
在 nasm 中,對於 絕對地址 形式,缺省是 32 位的,因此,需要明確使用 qword 來指明 64 位的 address size
這段代碼編譯后為:
00000000 48A11400000000000000 mov rax,[qword 0x14] ; 64 位地址 0000000A 488B042514000000 mov rax,[0x14] ; 32 位地址
它們的區別就是一個使用了 64 位地址,一個使用了 32 位地址。
例 2:
section .bss buffer resb 10 section .text bits 16 mov byte [es:dword buffer + 0x01], 'a' ; 指明為 32 位地址 mov ebx, buffer movzx eax, byte [es:ebx+0x01]
上面代碼是 16 位代碼,使用了 dword 指明 memory 操作數是 32 位的地址。
它被編譯為:
00000000 2667C6051D000000 mov byte [dword es:0x1d],0x61 -61 00000009 66BB1C000000 mov ebx,0x1c 0000000F 2667660FB6830100 movzx eax,byte [es:ebx+0x1] -0000
16 位的 address size 被 override 為 32 位地址。
3.3 nasm 偽指令
偽指令不是 x86/x64 機器的真實指令,偽指令是用於給編譯器指示如何進行編譯。
3.3.1 nasm 定義的 7 種數據 size
- byte : 8 位
- word : 16 位
- dword : 32 位
- qword : 64 位
- tword : 80 位
- oword : 128 位
- yword : 256 位
oword 可以對應 Microsoft MASM 的 xmmword 類型,yword 對應 Microsoft MASM 的 ymmword 類型。
tword, oword 以及 yword 使用在 非整型 數據,使用在 float 和 SSE 型數據。
3.3.2 定義初始化數據:db 家族
nasm 定義了用於初始化上面 7 種 size 的 db 家族,它們用於定義初化常量值。
- db : define byte
- dw :define word
- dd :define doubleword
- dq :define quadword
- dt :define tword
- do :define oword
- dy :define yword
正如前面所說的:dt , do , dy 不接受整型數值常量,它們被使用在定義 float 或 SSE 數據常量。dt 可以定義 extended-precision float 數據,do 可以定義 quad-precision float,dy 可定義 ymm 數據。而 dq 可以定義 double-precision float 數據,dd 可以定義 single-precision float 數據。
下面是 NASM Manual 上的例子:
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' ; 0x61 0x00 (it's just a number) dw 'ab' ; 0x61 0x62 (character constant) dw 'abc' ; 0x61 0x62 0x63 0x00 (string) dd 0x12345678 ; 0x78 0x56 0x34 0x12 dd 1.234567e20 ; floating-point constant dq 0x123456789abcdef0 ; eight byte constant dq 1.234567e20 ; double-precision float dt 1.234567e20 ; extended-precision float
3.3.3 定義非初始化數據:resb 家族
程序中使用到的非初始化數據通常放在 bss section 里,bss 代表 uninitialized storage space
nasm 使用了 resb (reserve byte) 家族來定義非初始化數據。
- resb :reserve byte
- resw :reserve word
- resd :reserve doubword
- resq :reserve quadword
- rest :reserve tword
- reso :reserve oword
- resy :reserve yword
resb 相當於 Microsoft MASM 語法中的 db ?
下面是 NASM Manual 的例子:
buffer: resb 64 ; reserve 64 bytes wordvar: resw 1 ; reserve a word realarray resq 10 ; array of ten reals ymmval: resy 1 ; one YMM register
3.3.4 包含 binary 文件
nasm 提供了一種包含 binary(二進制)文件的方法:使用 incbin 偽指令。incbin 偽指令包含的 binary 文件將直將寫入輸出文件中。此偽指令的作用是包含 graphics 以及 sound 這類數據文件。
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.3.5 使用 equ 定義常量
equ 用來為標識符定義一個 整型 常量,它的作用類似 C 語言中的 #define
a equ 0 ; OK b equ 'abcd' ; OK! b = 0x64636261 c equ 'abcdefghi' ; warning! c = 0x6867666564636261 d equ 1.2 ; error! section .data string db 'hello,word',0 len equ $-string ; OK! len = 0x0b section .text textlen equ _end - entry ; OK! textlen = 0x05 _entry: mov ecx, textlen _end:
例子中: b 定義為常量 'abcd' 它將是字符串的 ASCII 碼序列,‘abcdefghi' 常量將會被截斷,整型常量最長為 quadword(8 bytes),而 d 企圖被定義為一個 float 常量,這產生會錯誤。len 和 textlen 被定義為編譯期確定的數值。
3.3.6 使用 times 重復寫數據或指令
times 是一個比較實用偽指令,用來重復定義數據或指令。
下面是一個經典的使用例子:
times 510-($-$$) db 0 dw 0xaa55
這段代碼經常出現在 boot 磁盤 MBR 引導代碼中,目的是除了最后 2 個字節和 code 代碼外的區域全部寫 0 值。
times 還可以使用在重復寫某一條指令,如下:
times 10 nop
這段代碼結果是重復填入了 10 條 nop 指令:
00000000 90 nop 00000001 90 nop 00000002 90 nop 00000003 90 nop 00000004 90 nop 00000005 90 nop 00000006 90 nop 00000007 90 nop 00000008 90 nop 00000009 90 nop
3.4 常量值
nasm 下可以接受 4 種常量:整型常量 ,字符常量 ,字符串常量 以及浮點常量
3.4.1 整型常量
在 nasm 中,常用的整型進制有 4 種:
- decimal :十進制數
- hex :十六進制
- binary :二進制數
- octal :八進制數
每一種進制都有前綴 和后綴 表示法。當數值無前綴和后綴時,它是十進制數。因為缺省是十進制。
3.4.1.1 十進制數表示方法
看一看,下面的例子:
mov ax,200 ; decimal mov ax,0200 ; still decimal mov ax,0200d ; explicitly decimal mov ax,0d200 ; also decimal
十進制的前綴是:0d , 后綴是:d
3.4.1.2 十六進制數表示方法
十六進制使用 0123456789ABCDEF 來表示 16 個數值。類似地,它的前綴是:0h 或 0x (c/c++ 風格)以及 $0 (pascal 風格),后綴是:h
mov ax, 0c8h ; hex
mov ax, 8h ; hex
上面例子中的 hex 數,表明:當以 h 后綴結尾時,如果含有字母 ,必須要以 0 開頭。
mov ax,$0c8 ; hex again: the 0 is required
上面是 pascal 風格的十六進制表示法,使用前綴 $0 (0 是必須的)
mov ax,0xc8 ; hex yet again mov ax,0hc8 ; still hex
上面是使用 0h 前綴和 C/C++ 風格的 0x 表示十六進制數。
3.4.1.3 八進制數表示方法
八進制的前綴可以是:0o 或 0q 后綴可以是:o 或 q
mov ax,310q ; octal
mov ax,310o ; octal again
mov ax,0o310 ; octal yet again
mov ax,0q310 ; octal yet again
3.4.1.4 二進制數表示方法
類似地,二進制的前綴是:0b 后綴是:b
mov ax,11001000b ; binary
mov ax,1100_1000b ; same binary constant
mov ax,0b1100_1000 ; same binary constant yet again
3.4.2 字符常量
在 nasm 中,可以使用 3 種引號來提供字符
- ' ...' (單引號)
- " ..." (雙引號)
- ` ...` (反引號)
如下示例,它們的結果是一樣的:
db 'abcd' db "abcd" db `abcd`
3.4.2.1 提供字符常量
下面看看如何提供字符量:
mov eax, 'a' ; eax = 0x61 mov eax, 'abcd' ; eax = 0x64636261 mov eax, 'abcdefghi' ; eax = 0x64636261 mov eax, `/x61/x62/x63/x64' ; eax = 0x64636261
第 3 條企圖賦超過 4 bytes 的字符常量給 eax, 編譯器會截斷為 4bytes 再賦給 eax, 而第 4 條是另一種字符常量表示法,使用轉義字符表示。
可見:字符常量是以 little-endian 排列
3.4.3 nasm 中的轉義字符
在 nasm 中使用 c 風格的轉義字符,在 / (反斜杠符)后面跟 轉義碼 :/ escape-code
轉義碼(escape-code)包括:字符轉義碼 , 八進制轉義碼 , 十六進制轉義碼
注意:nasm 中的轉義符必須要用 ` `(反引號)來引用
db `/x61` ; right! 'a' db '/x61' ; wrong! '/' , 'x' , '6', '1'
第 1 個用反引號來包含轉義符是正確的。而第 2 個用單引號來包含轉義符,nasm 卻視它為一般的字符串對待
3.4.3.1 字符轉義碼
下面是一些例子:
/' single quote (') /" double quote (") /` backquote (`) // backslash (/) /? question mark (?) /a BEL (ASCII 7) /b BS (ASCII 8) /t TAB (ASCII 9) /n LF (ASCII 10) /v VT (ASCII 11) /f FF (ASCII 12) /r CR (ASCII 13) /e ESC (ASCII 27)
3.4.3.2 八進制轉義碼
/ 后面最多可以跟着 3 位數字,構成八進制轉義碼,如下所示:
/377 Up to 3 octal digits - literal byte /004 ; EOT /006 ; ACK /025 ; NAK /0 ; NULL
3.4.3.3 十六進制轉義碼
十六進制轉義碼以 x 或 X 開頭,如下所示:
db `/x61` ; 'a' db `/x61/x62/x63/x64` ; 'abcd'
上面所示:十六進制轉義碼可以連串提供。
3.4.4 字符串常量
字符串是逐個逐個提供字符,看以下例子:
db 'hello' ; string constant db 'h','e','l','l','o' ; equivalent character constants
提供字符串常量,是從左到右依次寫入到內存。
這里要注意的是:字符常量 與字符串常量 的區別 ,看一看下面的例子
buffer db 'hello' ; 字符串常量 ... ... mov eax, 'hello' ; 字符常量
字符常量與字符串常量最大區別就是:字符常以 littlen-endian 存儲,而字符串常量是從左到度
3.4.5 Unicode 字符串
nasm 定義了兩個操作數符來定義 Unicode 字符串:
- __utf16__
- __utf32__
下面是 nasm 里的例子:
%define u(x) __utf16__(x) %define w(x) __utf32__(x) dw u('C:/WINDOWS'), 0 ; Pathname in UTF-16 dd w(`A + B = /u206a`), 0 ; String in UTF-32
3.4.6 浮點數常量
浮點數變量 可以使用 DB , DW , DD , DQ , DT 以及 DO ,浮點數常量 使用 __float8__ , __float16__ , __float32__ , __float64__ , __float80m__ , __float80e__ , __float128l__ 以及 __float128h__ 來定義。
下面是 nasm 提供的例子:
db -0.2 ; "Quarter precision" dw -0.5 ; IEEE 754r/SSE5 half precision dd 1.2 ; an easy one dd 1.222_222_222 ; underscores are permitted dd 0x1p+2 ; 1.0x2^2 = 4.0 dq 0x1p+32 ; 1.0x2^32 = 4 294 967 296.0 dq 1.e10 ; 10 000 000 000.0 dq 1.e+10 ; synonymous with 1.e10 dq 1.e-10 ; 0.000 000 000 1 dt 3.141592653589793238462 ; pi do 1.e+4000 ; IEEE 754r quad precision
3.5 表達式
在 nasm 里的表達式很像 C 表達式,對於熟悉 C 表達式的人來說幾乎可以馬上上手。
3.5.1 $ 與 $$ 標號
$ 標號表示 nasm 編譯后當前指令位置
$$ 標號表示當前 section 起始位置
看看下面的例子:
bits 64 section .rdata dq 0 section .text mov rax, 0 mov rax, $-$$
它的編譯結果是:
00000000 48B8000000000000 mov rax,0x0 -0000 0000000A 48B80A0000000000 mov rax,0xa -0000 00000014 0000 add [rax],al 00000016 0000 add [rax],al 00000018 0000 add [rax],al 0000001A 0000 add [rax],al
3.5.2 位運算符
與 C 一樣,包括下面:
位運算符
|
描述
|
&
|
位與: AND
|
|
|
位或:OR
|
~
|
位非: NOT
|
^
|
位異或: XOR
|
<<
|
左移
|
>>
|
右移
|
3.5.3 算術運算符
下面是 nasm 所支持的算術運算符
算術運算符
|
描述
|
+
|
加
|
-
|
減
|
*
|
乘
|
/
|
除(無符號數)
|
//
|
除(符號數)
|
%
|
取模(無符號數)
|
%%
|
取模(符號數)
|