Assembly x64 Intro - Nasm Syntax




1.1 nasm 是区分大小写

例如:符号 fooFOO 是两个不同的标识符。

 

1.2 内存操作数表达式

1.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

1.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 指明 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 指示字。

1.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]

1.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 位地址。

 

1.3 nasm 伪指令

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

1.3.1 nasm 定义的 7 种数据 size

  • byte8
  • word 16
  • dword 32
  • qword 64
  • tword80
  • oword 128
  • yword 256

oword 可以对应 Microsoft MASM 的 xmmword 类型,yword 对应 Microsoft MASM 的ymmword 类型。

tword, oword 以及 yword 使用在 非整型 数据,使用在 float SSE型数据。

1.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.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.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

1.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 被定义为编译期确定的数值。

1.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

 

1.4 常量值

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

1.4.1 整型常量

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

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

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

1.4.1.1 十进制数表示方法

看一看,下面的例子:

        mov     ax,200          ; decimal
        mov     ax,0200         ; still decimal
        mov     ax,0200d        ; explicitly decimal
        mov     ax,0d200        ; also decimal

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

1.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 表示十六进制数。

1.4.1.3 八进制数表示方法

八进制的前缀可以是:0o0q 后缀可以是:oq

        mov     ax,310q         ; octal
        mov     ax,310o         ; octal again
        mov     ax,0o310        ; octal yet again
        mov     ax,0q310        ; octal yet again

1.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.4.2 字符常量

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

  • '...'(单引号)
  • "..."(双引号)
  • `...`(反引号)

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

        db  'abcd'
        db  "abcd"
        db  `abcd`

1.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 排列

1.4.3 nasm 中的转义字符

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

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

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

    db  `\x61`     ; right!  'a'
    db  '\x61'     ; wrong!  '\', 'x', '6', '1'

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

1.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.4.3.2 八进制转义码

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

    \377        Up to 3 octal digits - literal byte
    \004        ; EOT
    \006        ; ACK
    \025        ; NAK
    \0          ; NULL

1.4.3.3 十六进制转义码

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

    db `\x61`                            ; 'a'
    db `\x61\x62\x63\x64`                 ; 'abcd'   

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

1.4.4 字符串常量

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

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

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

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

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

          mov eax, 'hello'               ; 字符常量      

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

1.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.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

 

1.5 表达式

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


1.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


1.5.2 位运算符

与 C 一样,包括下面:

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


1.5.3 算术运算符

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

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



你可能感兴趣的:(Assembly x64 Intro - Nasm Syntax)