[原]nasm语法

工具:
nasm 汇编
gcc  编译c
ld    进行链接
kscope 查看源代码
make 工程管理
khexedit  分析二进制文件

一:
nasm源文件布局:
像其他汇编器一样, nasm源文件包含四个域的组合。(除了宏, 或者预编译器指示, 或者汇编指示 )
label标号: 指令 操作数 ;注释
通常, 这些域是可选的。 当然, 操作数域是根据指令的要求来放置,或者去掉的。

nasm使用/作为行链接符, 如果一行以/结尾, 下一行认为是本行的继续.

nasm对于空格没有限制; 标号可以在前面包含空格, 或者指令前可以没有空格. 标号后的冒号是可选的. (可以使用-w+orphan-label选项来提示某行只有标号).

标号中的字符包括字母, 数字, _$#@~.?  可以字母开头,  . (.有特殊的含义!), _和? 也可以作为头部. 一个标识符可能也有$前缀, 表明是作为标识符读取的, 并不是保留字; 因此, 如果你链接的其他模块定义了符号eax, 你可以通过$eax去区分符号和寄存器. 标识符最大的长度为4095.

指令域可能包含任何机器指令: 奔腾指令, 奔6指令, FPU指令, MMX指令, 甚至未文档化的指令,都可以被支持. 指令可能包含前缀LOCK, REP, REPE, REPZ, 或者REPNZ, REPNE. 显式的地址大小和操作数大小前缀A16, A32, A64, O16 和O32, O64 也被提供了. 一个使用他们的例子在10章.
你可以使用段寄存器名字作为指令前缀: 编码 es mov [bx], ax 等价于 mov [es:bx], ax.  但是对于LODSB等类似指令, 没有操作数, 但是也需要一个段寄存器, 除了es lodsb 没有其他办法.

一个指令一下情况需要前缀: 前缀是cs, a32, lock, repe可以独立出现在一行, nasm会生成带前缀的代码.

另外, 对于实际的机器指令, nasm支持伪指令.

指令操作数有很多形式: 寄存器, 有效地址, 常数, 或者表达式.

对于x87浮点指令, nasm接受叫大范围的语法, 使用双操作数, 或者使用nasm本地单操作数.例如
fadd st1
fadd st0, st1
fadd st1, st0
fadd to st1
几乎所有x87引用内存的浮点指令都使用前缀DWORD, QWORD, TWORD, 表明内存操作数的大小.

2伪指令:
 DB DW DD DQ DT DY; 他们的未初始化对应物是RESB RESW RESD RESQ REST RESO和 RESY, INCBIN命令, EQU命令, TIMES前缀.

2.1DB声明初始化数据
db  0x55   db 0x55, 0x56, 0x57
db 'a', 0x55
db 'hello', 13, 10, '$'
dw 0x1234
dw 'a'   ;数字
dw 'abc' ;对其二字节 
dd 0x12345678
dd 1.234567e20
dq 0x1234567abc
dq 1.23456e20
dt  1.2345e20
DT, DO, DY 不接受数字常量作为操作数
2.2RESB  声明为初始化数据
RESB RESW RESQ RESD REST RESO RESY 用来声明模块的BSS区域: 声明为初始化的存储区域(block start by symbol). 每个产生一个操作, 字节数, 字数, 双字数, 或者其他要保留的大小. RESB类型伪指令是一个重要的表达式.
例如:
buffer:  resb 64
wordvar: resw 1
realarray: resq 10
ymmval: resy  1 在ymm寄存器中

2.3INCBIN包含外部二进制文件:
INCBIN从早期的DevPac汇编器中借来的: 包含一个二进制文件verbatim到输出文件中去. 这可以便于包含图形文件, 声音文件直接到一个游戏执行文件中去. 可以有一下3种调用方式:
incbin "file.dat"   整个文件
incbin "file.dat", 1024  忽略头1024字节
incbin "file.dat", 1024, 512 忽略头1024字节, 最多包含512字节
INCBIN即是指示, 也是宏.

2.4EQU:定义常数
EQU定义一个符号赋值常数: 当使用EQU, 源代码必须包含标号.
message  db  'hello'
mslen   equ   $-message

2.5TIMES: 重复指令或者数据
TIMES前缀引起指令汇编多次. 等价于DUP
zerobuf:   times  64  db 0
或者类似; 但是TIMES功能更多. times的参数不仅仅是数字常量, 也可以是表达式
buffer: db  'hello'
times  64  -$+buffer  db ' '
将会保存足够的空间, 使buffer达到64. 最后, times可以应用到一般指令, 你可以编码平凡非回转循环在其中:
times  100  movsb
注意到: times 100 resb  1 和 resb 100没有区别, 除了后者可以更快的汇编.
times的操作数是重要的表达式.
注意到times不能应用到宏中: 原因是times在宏阶段后处理, 允许times使用参数包含表达式, 例如64-$+buffer.
为了重复多余一行的代码, 或者复杂宏, 使用预编译指令%rep

3有效地址:
一个有效地址是任何操作数, 在指令中引用内存. 有效地址, 在nasm中有很简单的语法: 由表达式组成, 等价于期望的地址, 被方括号包围:
wordvar  dw 123
mov   ax , [wordvar]
mov   ax, [word+1]
mov   ax, [es:wordvar +bx]
任何不符合这个规则的东西都不是有效的内存引用.例如: es:wordvar[bx]
更复杂的有效地址, 例如包含多个寄存器:
mov  eax , [ebx*2 + ecx + offset]
mov ax, [bp + di + 8]
nasm有能力对这些有效地址作代数运算, 因此这些看起来不合法的表达式, 是正确的:
mov  eax, [ebx*5] ;汇编为 ebx*4 + ebx
mov  eax, [label1*2-label2] ;汇编为: label1 + (label1 - label2)
一些形式是有效地址, 含有超过一种汇编形式; 大多数情况下, nasm会生成最小的形式. 例如对于[eax*2 + 0]和[eax + eax], 有两种不同的汇编形式, nasm会生成后者.

nasm有一个提示机制, 可以使[eax+ebx] 和[ebx+eax]生成不同的代码; 这个偶尔有用, 因为[esi+ebp]和[ebp+esi]有不同的默认段寄存器.

然而, 你可以强制nasm生成有效地址以特殊形式, 通过使用关键字BYTE, WORD, DWORD, NOSPLIT. 如果你使用[eax+3]汇编成双字偏移, 而不是单字节, 你可以使用[dword eax+3].
类似的, 你可以强制nasm使用单字节偏移对一个小的数值, 没有在第一遍中发现.,[byte eax+offset].
作为特例, [byte eax]编码[eax+0], 使用一个字节偏移0, [dword eax]编码双字偏移. 正常形式[eax]没有偏移域.

以上描述的在你从16位代码中访问32位段中的数据时也是有用的. 可以查看10.2混合大小寻址章.
特别地, 如果访问已知偏移的数据比当前16位值大, 如果你不指明这是个dword偏移, nasm将会使偏移的高字节丢失.

类似的, nasm会分割[eax*2]为[eax+eax]因为允许偏移部分被省略, 空间节省; 事实上, 他也会分割[eax*2 + offset]为[eax+eax +offset]. 你可以通过使用NOSPLIT来抵抗这种行为: [nosplit eax*2]强制按字面生成[eax*2 + 0].

64位模式下, nasm会默认生成绝对地址. rel关键字使之生成rip相关地址.

3.5常数:
nasm理解四种形式的常数:数字, 字符, 字符串, 浮点数.
4.1数字常数:
nasm允许你指定数字以各种进制: 你可以添加后缀H, X, Q, O, B用于16进制, 8进制, 2进制, 你也可以使用0x, 或者前缀$.
当前nasm使用0h 0o 0q 0b只是16, 8 ,2进制.
例如:
mov  ax  , 200
mov  ax, 0200
mov  ax, 0200d
mov ax, 0d200
mov ax, 0c8h
mov ax, $0c8
mov ax 310o
mov ax, 11001000b

4.2字符串:
字符串由"", '', ``包含. 单引号, 双引号等价的, 反引号用于特殊字符.
db  `/u263a`
db `/xe2/x98/xba`
db 0e2h, 098h, 0bah

4.3字符常量
mov  eax, 'abcd'

4.4字符串常量
db  'hello'

4.6浮点数
db  -0.2
dw -0.5
dd  1.2
dd 1.222222222
dd 0x1p+2
dq 1.e10

5表达式:
nasm表达式类似于c. 表达式等价于64为整数, 将会在后来被调整到合适大小.
nasm支持两种表达式词法记号, 允许在当前汇编位置计算: $和$$词法记号. $计算汇编位置, 在包含行的开始; 所以你可以编码无限循环 JMP $. $$计算当前段开始; 所以你可以通过$-$$得知在当前段中的位置.

nasm提供算术操作:
|位或, ^异或 &位与 << >>位移 + - 加减, * / // % %% 乘法和除法
/无符号乘法, //有符号除法, %无符号, %%有符号求模操作.

nasm不提供对于有符号的求模操作符的强力操作的保证.

因为%字符被宏预编译扩展使用, 你应该保证被空格后接有符号, 无符号求模操作符.
单操作符: + - ~ ! SEG
~求补, !求反, SEG求操作数段地址.

6SEG WRT
当写16位大程序, 必须被分割为多个小段, 因此很有必要去定位段的地址的一部分, 通过一个符号. NASM支持seg操作符, 来完成这个功能.

seg操作符返回符号所在的段的基址, 定义段基地址相对于符号的偏移是有意义的. 因此代码:
mov  ax, seg symbol
mov  es, ax
mov bx, symbol
将会加载ES:BX指向符号的有效指针.

事情可能会比这个复杂: 因为16位段和组可能会重叠, 你可能偶尔希望通过不同的段基地址来引用一些符号. nasm允许这样作, 通过使用wrt关键字, 你可以这样做:
mov  ax, werid_seg
mov  es, ax
mov  bx, symbol wrt werid_seg
去用不同的, 但是功能等价的指针指向符号symbol 加载到es:bx.(with reference to)

nasm支持段内的远调用, 和远跳转, 通过call segment:offset, segment, 和offset均代表立即数值. 因此要远调用一个过程, 你可能编码如下:
call    (seg  procedure): procedure
call    weird_seg : (procedure wrt weird_seg)
加入括号是为了清楚, 展示以上指令语法分析的目的.

nasm支持语法call far procedure 作为上面第一种的同义词. jmp在以上例子中功效等同于call:

为了声明一个对数据的远指针在数据段中, 你应该这样:
dw   symbol,  seg  symbol
nasm没有类似的同义语, 虽然你可以使用宏来发明一个.

7抑制优化 STRICT
当在2或以上优化选项下, 汇编代码, nasm会使用大小限定符, 但是会给他们最小的大小. 关键字strict可以用于抑制这种优化.
例如: 在16位模式下:
push dword 33
被编码为66 6a 21, 而  push dword 33编码为 66 68 21 00 00 00

8关键表达式:
尽管nasm有可选的多边优化, 有若干表达式必须在第一遍中被解析, 这些称为关键表达式.

第一遍用于决定所有汇编代码的大小, 在第二遍, 当生成所有的代码时, 知道所有的代码引用的符号地址. 因此, nasm不能处理的事是代码大小决定于后面声明的符号的值, 在代码被提问时. 例如:
times (label-$) db 0
label: db  'where am i'
这种情况下times的参数合法的等价于任何值; nasm将会拒绝这种情况, 因为他不能在首次见到时知道times行的大小.
times (label-$+1) db 0
label: db "now where am i"
这种情况下, 任何值都错.

nasm拒绝这些例子, 通过一个叫做关键表达式的概念, 被定以为一个表达式, 其值需要在第一遍中计算出来, 因此只能依赖于前面定义的符号. times的参数是关键表达式.

本地变量:
对于以.开始的符号, nasm给予特殊的对待. 一个标号以一个原点开始, 被认为是局部变量, 意味着和以前的非局域变量存在联系, 例如:
label
.loop
jne  .loop
ret
label2
.loop
jne .loop
ret
在上面的代码片段中, 每个jne指令跳转到前面的行, 因为两个.loop的定义是隔离的通过前面的非局部变量的联系的效力.

这种局部标号的处理借鉴于amiga汇编器; 然而, nasm更进了一步, 允许访问局部变量从代码的另一个部分. 这通过定义局部变量根据前面的非局部变量: 第一个.loop的定义实际是定以为label1.loop, 第二个是label2.loop, 因此, 你如果真的需要
label3
jmp label1.loop
有时是有用的, 在宏里, 例如, 可以定义一个标号可以从任何地方引用, 但是不会干预正常的局部标号机制. 这种标号不能是非局部的, 因为定义他的宏不知道label的全名. nasm因此引入了第三类标号, 可能值在宏中使用: 如果以..@开始的一个标号, 不会对局部标号产生作用:
label
.local:
..@foo:
label2:
.local:
jmp ..@foo
nasm有能力去定义其他特殊符号, 以..开始例如
..start, 用来指定obj文件的入口.









 


 












作者:liyonghelpme 发表于2010/6/12 0:19:00 原文链接
阅读:3319 评论:0 查看评论
posted @ 2010-06-12 00:19  liyonghelpme  阅读(500)  评论(0编辑  收藏  举报