内容目录

一、什么是nasm汇编

nasm使用在windows、linux等系统下的汇编。

二、语法介绍

2.1 nasm 是区分大小写

例如:符号 foo 与 FOO 是两个不同的标识符。

2.2 内存操作数表达式

2.2.1 在 nasm 语法里,对 memory 操作数需要加 [ ] 括号

下面的代码:

foo    equ    1
bar    dw     2

    bits 32

    mov eax, foo
    mov ebx, bar
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第 2 指令的意图是:将bar 内的值赋给ebx 寄存器。但这样是错误的,nasm 只会把 bar 当作是 immediate 赋给 ebx

00000000  0200                            ; bar 变量
00000002  B801000000        mov eax,0x1
00000007  BB00000000         mov ebx,0x0  ; 将 bar 地址作为 immediate 赋给 ebx 
  • 1
  • 2
  • 3

因此,需要将 bar 用 [ ] 括起来

mov eax, foo
mov ebx, [bar]
  • 1
  • 2

nasm 就编译出正确的代码:

00000000  0200                                    ; bar
00000002  B801000000        mov eax,0x1
00000007  8B1D00000000       mov ebx,[dword 0x0]    ; bar 的内容赋给 ebx
  • 1
  • 2
  • 3

2.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] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.2.3 指明 memory 操作数的 operand size

下面展示了为 memory 操作数提供一个 size 情况:

mov byte [buffer + 0x01] , 'a'
mov ebx, buffer
movzx eax, byte [ebx + 0x01]
  • 1
  • 2
  • 3

代码中使用 byte 关键字对 memory 操作数进行了修饰,指明 memory 操作数的大小为 byte

在这方面,nasm 的语法与微软的 masm 的语法(Intel 语法)有些不同,masm 的语法是:

mov byte ptr [buffer + 0x01] , 'a'
mov ebx, buffer
movzx eax, byte ptr [ebx + 0x01]
  • 1
  • 2
  • 3

在 masm 语法中需配合 ptr 指示字。

2.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]
  • 1
  • 2
  • 3

nasm 语法中,在 [ ] 括号内提供 segment ,不能在 [ ] 括号外提供 segment

而 masm 的语法中是在 [ ] 括号外提供 segment,如下:

mov byte ptr es: [buffer + 0x01] , 'a'
mov ebx, buffer
movzx eax, byte ptr es: [ebx + 0x01]
  • 1
  • 2
  • 3
  • 4

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(绝对地址)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在 nasm 中,对于 绝对地址 形式,缺省是 32 位的,因此,需要明确使用 qword 来指明 64 位的 address size

这段代码编译后为:

00000000  48A11400000000000000   mov rax,[qword 0x14]    ; 64 位地址
0000000A  488B042514000000       mov rax,[0x14]          ; 32 位地址
  • 1
  • 2

它们的区别就是一个使用了 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]           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面代码是 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
  • 1
  • 2
  • 3
  • 4
  • 5

16 位的 address size 被 override 为 32 位地址。

2.3 nasm 伪指令

伪指令不是 x86/x64 机器的真实指令,伪指令是用于给编译器指示如何进行编译。

2.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 型数据。

2.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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.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
  • 1
  • 2
  • 3
  • 4

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
···

2.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: 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

例子中: b 定义为常量 ‘abcd’ 它将是字符串的 ASCII 码序列,‘abcdefghi’ 常量将会被截断,整型常量最长为 quadword(8 bytes),而 d 企图被定义为一个 float 常量,这产生会错误。len 和 textlen 被定义为编译期确定的数值。

2.3.6 使用 times 重复写数据或指令

times 是一个比较实用伪指令,用来重复定义数据或指令。

下面是一个经典的使用例子:

times 510-($-$$) db 0

 dw 0xaa55
  • 1
  • 2
  • 3

这段代码经常出现在 boot 磁盘 MBR 引导代码中,目的是除了最后 2 个字节和 code 代码外的区域全部写 0 值。

times 还可以使用在重复写某一条指令,如下:

times 10 nop
  • 1

这段代码结果是重复填入了 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.4 常量值

nasm 下可以接受 4 种常量:整型常量 ,字符常量 ,字符串常量 以及浮点常量

2.4.1 整型常量

在 nasm 中,常用的整型进制有 4 种:

  • decimal :十进制数
  • hex :十六进制
  • binary :二进制数
  • octal :八进制数

每一种进制都有前缀 和后缀 表示法。当数值无前缀和后缀时,它是十进制数。因为缺省是十进制。

2.4.1.1 十进制数表示方法

看一看,下面的例子:

mov     ax,200          ; decimal
mov     ax,0200         ; still decimal
mov     ax,0200d        ; explicitly decimal
mov     ax,0d200        ; also decimal 
  • 1
  • 2
  • 3
  • 4

十进制的前缀是:0d , 后缀是:d

2.4.1.2 十六进制数表示方法

十六进制使用 0123456789ABCDEF 来表示 16 个数值。类似地,它的前缀是:0h 或 0x (c/c++ 风格)以及 $0 (pascal 风格),后缀是:h

    mov ax, 0c8h         ; hex
    mov ax, 8h           ; hex
  • 1
  • 2

上面例子中的 hex 数,表明:当以 h 后缀结尾时,如果含有字母 ,必须要以 0 开头。

 mov     ax,$0c8         ; hex again: the 0 is required 
  • 1

上面是 pascal 风格的十六进制表示法,使用前缀 $0 (0 是必须的)

        mov     ax,0xc8         ; hex yet again
        mov     ax,0hc8         ; still hex 
  • 1
  • 2

上面是使用 0h 前缀和 C/C++ 风格的 0x 表示十六进制数。

2.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 
  • 1
  • 2
  • 3
  • 4

2.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
  • 1
  • 2
  • 3

2.4.2 字符常量

在 nasm 中,可以使用 3 种引号来提供字符

  • ’ …’ (单引号)
  • ” …” (双引号)
  • ` …` (反引号)

如下示例,它们的结果是一样的:

db  'abcd'
db  "abcd"
db  `abcd`
  • 1
  • 2
  • 3

2.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   
  • 1
  • 2
  • 3
  • 4

第 3 条企图赋超过 4 bytes 的字符常量给 eax, 编译器会截断为 4bytes 再赋给 eax, 而第 4 条是另一种字符常量表示法,使用转义字符表示。

可见:字符常量是以 little-endian 排列

2.4.3 nasm 中的转义字符

在 nasm 中使用 c 风格的转义字符,在 / (反斜杠符)后面跟 转义码 :/ escape-code

转义码(escape-code)包括:字符转义码 , 八进制转义码 , 十六进制转义码

注意:nasm 中的转义符必须要用 (反引号)来引用

db  `/x61`     ; right!  'a'
db  '/x61'     ; wrong!  '/' , 'x' , '6', '1'
  • 1
  • 2

第 1 个用反引号来包含转义符是正确的。而第 2 个用单引号来包含转义符,nasm 却视它为一般的字符串对待

2.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) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.4.3.2 八进制转义码

/ 后面最多可以跟着 3 位数字,构成八进制转义码,如下所示:

    /377        Up to 3 octal digits - literal byte
    /004        ; EOT
    /006        ; ACK
    /025        ; NAK
    /0          ; NULL
  • 1
  • 2
  • 3
  • 4
  • 5

2.4.3.3 十六进制转义码

十六进制转义码以 x 或 X 开头,如下所示:

    db `/x61`                            ; 'a'
    db `/x61/x62/x63/x64`                 ; 'abcd'    
  • 1
  • 2

上面所示:十六进制转义码可以连串提供。

2.4.4 字符串常量

字符串是逐个逐个提供字符,看以下例子:

      db    'hello'               ; string constant
      db    'h','e','l','l','o'   ; equivalent character constants
  • 1
  • 2

提供字符串常量,是从左到右依次写入到内存。

这里要注意的是:字符常量 与字符串常量 的区别 ,看一看下面的例子

buffer    db 'hello'                     ; 字符串常量
          ... ...

          mov eax, 'hello'               ; 字符常量       
  • 1
  • 2
  • 3
  • 4

字符常量与字符串常量最大区别就是:字符常以 littlen-endian 存储,而字符串常量是从左到度

2.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
  • 1
  • 2
  • 3
  • 4
  • 5

2.4.6 浮点数常量

浮点数变量 可以使用 DB , DW , DD , DQ , DT 以及 DO ,浮点数常量 使用 float8 , float16 , float32 , float64 , float80mfloat80e , 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.5 表达式

在 nasm 里的表达式很像 C 表达式,对于熟悉 C 表达式的人来说几乎可以马上上手。

2.5.1 与$ 标号

nasm标号表示nasm编译后当前指令位置$ 标号表示当前 section 起始位置

看看下面的例子:

bits 64

section .rdata
    dq 0


section .text

    mov rax, 0
    mov rax, $-$$
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

它的编译结果是:

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.5.2 位运算符

与 C 一样,包括下面:

位运算符 描述
& 位与: AND
| 位或:OR
~ 位非: NOT
^ 位异或: XOR
<< 左移
>> 右移

2.5.3 算术运算符

下面是 nasm 所支持的算术运算符

算术运算符 描述
+
*
/ 除(无符号数)
// 除(符号数)
% 取模(无符号数)
%% 取模(符号数)

One Comment

发表评论