NASM doc翻译 by chio.yang@gmail.com
第一章 介绍
1.1 什么是NASM?
Netwide Assembler, NASM, 是为可移植性和模块化所设计的80x86汇编器。它支持一系列目标文件,包括Linux/NetBSD/FreeBSD a.out, ELF, COFF, Microsoft 16-bit OBJ和Win32。它也可以输出普通二进制文件(Plain Binary files)。它的语法被设计成简单并且易于理解,和Intel的语法相似但没那么复杂。它支持Pentium,P6, MMX, 3DNow!,SSE和SSE2操作码,并且带有宏功能。
1.1.1 为什么再发明一个汇编器?
1.1.2 Licence情况
1.2 联系信息
第二章 运行NASM
2.1 NASM命令行语法
为了汇编一个文件,你可以发出如下命令:
$ nasm –f <format> <filename> [-o <output>]
例如:
$ nasm –f elf myfile.asm
将汇编myfile.asm到一个ELF对象文件myfile.o
$ nasm –f bin myfile.com –o myfile.com
将汇编myfile.asm到一个raw binary文件myfile.com
要得到可取得的使用指令,输入得到使用帮助
$ nasm –h
Linux下输入file nasm 可以查看你的系统是a.out或ELF,如:
$ File nasm
ELF:
$ Nasm: ELF 32-bit LSB executable i386(386 and up) Version 1
a.out:
$ nasm: Linux/386 demand-page executable(QMAGIC)
2.1.1 –o选项:指定输出文件名
正常情况下,NASM会为你的输出文件选择文件名;这主要依靠对象文件格式。对于Microsoft对象文件格式(obj 和win32), 它将去掉.asm(或.s)扩展名,并替代为.obj.对于Unix对象文件格式(aout, coff, elf和as86),它将替代为.o。对于rdf, 它将使用.rdf, 对于bin格式,它仅仅简单地去掉扩展名,因此myfile.asm将产生输出文件myfile.
如果输出文件已经存在,NASM将覆盖它,除非它和输入文件同名,这种情况它给出一个警告并使用nasm,out作为输出文件。
对于上述行为不可接受时,NASM提供了-o命令行选项来允许你指定你期望的文件名。如 :
$ nasm –f bin program.asm –o program.com
$ nasm –f bin driver.asm –o driver.sys
2.1.2 –f选项:指定输出文件格式
如果你不提供-f选项给NASM,它将自动为你选择一个输出文件格式。在NASM的发行版中,缺省的总是bin; 如果你编译你自己的NASM副本,你可以在编译时重定义OF_DEFAULT并选在你想要的缺省输出文件格式。
像-o一样,-f和输出文件格式之间的空白是可选的,因此-f elf 和 –felf都是合法的。
一个完全的有效输出文件格式可以获得,你可以打:
$ nasm –hf
2.1.3 –l选项:生成一个列表文件
2.2快速开始:MASM用户
2.2.1 NASM是大小写敏感的
2.2.2 NASM对内存引用要求方括号[]
NASM本质上是为了语法简化而设计。
Foo equ 1
Bar dw 2
在MASM中:
mov ax, foo
mov ax, bar
可能让人产生疑惑
但是在NASM中:
Foo总是编译期常量,要访问变量bar的内容,你必须使用:
mov ax, [bar]
因此MASM中的关键字OFFSET在NASM中完全不存在,因为不需要。
MASM: mov ax, offset bar
NASM: mov ax, bar
两者完全相同,都表示将bar变量的地址移到ax中
另外,NASM中一个非常大的简化:一切都是标签label
同样,NASM也不支持混合语法:
MASM: mov ax, table[bx]
NASM: mov ax, [table+bx]
MASM: mov ax, es:[di]
NASM: mov ax, [es:di]
2.2.3 NASM不保存变量类型
NASM设计中,选择了不记忆你声明的变量的类型。而MASM可以记住,
var dw 0
mov var, 2
MASM中能填充var开始的两个字节地址的值为2
而NASM除了知道var的开始地址,关于它的大小根本不知道,因此你必须显式的写出:
Mov word [var], 2
因此NASM不支持LODS, MOVS, STOS, SCAS, CMPS, INS, OUTS指令
仅仅支持形如LODSB, MOVSW, SCASD这类明确指定了字符串组成类型的操作。
2.2.4 NASM没有ASSUME
2.2.5 NASM不支持内存模型
2.2.6 浮点差异
2.2.7 其他不同
由于历史原因,NASM使用关键字TWORD而MASM及兼容汇编器使用TBYTE
在声明未初始化的存储区的区别:
MASM: stack db 64 dup (?)
NASM: stack resb 64
NASM不支持DUP
另外,宏和标示是完全不同
第三章 NASM语言
3.1 NASM源代码行布局
label: instruction operands ; comment
当一行过长时,可以使用反斜杠(\)续行
有效的标签是字母,数字,_,$,@,~,.,?
标识符可以以字母,.,_,?开头,但一般.开头的标示有特殊意义,$开头暗示它将作为一个标示,而不是关键字,如$eax作为标示,而不是作为寄存器
指令域可以包含任意的机器指令: Pentium和P6指令,FPU, MMX指令,甚至未归档的指令也都支持。指令可以带前缀如LOCK, REP, REPE/REPZ, REPNE/REPNZ.
显示的address-size和operand-size前缀A16, A32, O16, O32也提供支持。你也可以使用段寄存器名作为指令前缀: es mov [bx], ax等价于 mov [es:bx], ax;但我推荐后面一种语法,因为它很清晰。
3.2 伪指令
当前支持的伪指令有:DB, DW, DD, DQ, DT
它们的未初始化形式:RESB, RESW, RESD, RESQ, REST
还有: INCBIN, EQU, TIMES
3.2.1 DB一类:声明初始化的数据
db 0x55 ;仅一个字节,值为0x55
db 0x55, 0x56, 0x57 ;三个连续字节
db ‘a’, 0x55 ;字符常量是允许的
db ‘hello’, 13, 10, ‘$’ ;字符串常量也是允许的
dw 0x1234 ;0x34 3x12 低地址->高地址
dw ‘a’ ;0x61 0x00
dw ‘ab’ ;0x61 0x62 一个字
dw ‘abc’ ;0x61 0x62 | 0x63 0x00 两个字
dd 0x12345678 ;0x78 0x56 0x34 0x12 一个双字
dd 1.234567e20 ;浮点常量
dq 1.234567e20 ;双精度浮点
dt 1.234567e20 ;扩展精度浮点
DQ和DT不接受数字常量或字符常量作为操作数(言外之意,只用于浮点数??)
3.2.2 RESB一类:声明未初始化的数据
RESB, RESW, RESD, RESQ, REST被设计用于一个模块的 BSS段(section):它们声明未初始化的存储空间。每个带单个操作数,这表示要保留的字节数、字数、双字数或其他。
buffer: resb 64 ;保留64字节
wordvar: resw 1 ;保留一个字
realarray resq 10 ;保留10个实数
3.2.3 INCBIN: 包含外部二进制文件
当需要直接包含图形或声音数据到一个游戏可执行文件时,这个伪指令是相当方便。它可以如下方式使用:
incbin “file.dat” ;包含整个文件
incbin “file.dat”, 1024 ;略过第一个1024字节
incbin “file.dat”, 1024, 512;略过第一个1024字节,并实际包含之多512字节
3.2.4 EQU:定义常量
EQU定义一个符号为给定的常数值:当EQU被使用时,源代码行必须包含一个标签。这种定义是绝对的,并且不能以后改变。因此,例如:
message db ‘hello, world’
msglen equ $-message
这样,msglen被定义为常数12. msglen可能以后不被重定义。这不是一个预处理定义: msglen的值只被evaluate一次, 在定义处使用$的值,而不是无论何处引用并使用该处引用的$去被求值。
3.2.5 TIMES:重复指令或数据
TIMES前缀引起指令被汇编多次:
zerobuf: times 64 db 0
TIMES实际功能更多,TIMES的参数不止是数字常量,也可以是数字表达式,如:
buffer: db ‘hello, world’
times 64-$+buffer db ‘ ‘
这样将准确保留足够的空间来使整个buffer长度达到64
再如:times 100 movsb
3.3 有效地址
有效地址有非常简单的语法:它们包含表达式来求得需要的地址的值,表达式以方括号包围。
wordvar dw 123
mov ax, [wordvar]
mov ax, [wordvar+1]
mov ax, [es:wordvar+bx]
3.4 常量(立即数)
3.4.1 数字立即数
3.4.2 字符常量
3.4.3 字符串常量
3.4.4 浮点常量
3.5 表达式
表达式语法上和C相似
NASM不保证整数的大小,这个常被用来在编译期求表达式的值:因为NASM能编译和运行在64-bit系统上,所以不要假设表达式在32-bit寄存器中求值并因此试图故意使用整数溢出。这可能不工作。NASM唯一保证的是由ANSI C保证的:你总是至少工作在32-bit
NASM在表达式中支持两种特殊的tokens,允许计算来包含当前汇编位置:$和$$.
$能求值包含表达式的当前行首,而$$求值到当前section的开始,因此通过使用($-$$)你可以告知进入section多远
3.5.1 |:按位或操作符
3.5.2 ^:按位异或操作符
3.5.3 &:按位与操作符
3.5.4 <<和>>:移位操作符
3.5.5 +和-: 加减操作符
3.5.6 *, /, //, %, %%:乘和除
* 乘
/ 无符号除
// 有符号除
% 无符号求余
%% 有符号求余
3.5.7 一元操作符:+,-, ~, SEG
+ 正
- 负
~ 求反
SEG 提供操作数的段地址
3.6 SEG和WRT
3.7 STRICT: 抑制优化
3.8 Critical Expressions
NASM是两遍汇编器。因此它不能处理非常复杂以至需要三次或更多遍的源文件。
第一遍用来决定所有汇编的代码和数据的大小,以便第二遍生成所有代码时知道代码引用的所有的符号地址。因此NASM不能处理的是这样的代码,它们的大小依赖于在该代码后声明的符号的值。例如:
times (label-$) db 0
label: db ‘Where am I?’
NASM拒绝这样的例子,这里有一个概念成为Critical Expression(关键表达式)
定义:这个表达式的值要求在第一遍被计算,并且因此它必须仅依赖于定义在它前面的符号。
下面的例子:
mov ax, symbol1
symbol1 equ symbol2
symbol2:
在第一遍中,不能决定symbol1的值,因为symbol2还没有看到
3.9 局部标签local label
NASM给予以.开头的符号以特殊意义。以句点.开头的标签被视为局部标签,这意味着它和它前面的非局部标签相关联。如:
label1 ; some code
.loop
; some more code
jne .loop
ret
label2 ; some code
.loop
; some more code
jne .loop
ret
上述两个.loop分别与label1和label2相关联
NASM提供了更进一步的方式定义局部标签
label1 ; some code
label.loop
; some more code
jne label1.loop
ret
label2 ; some code
.loop
; some more code
jne label2.loop
ret
这样定义局部标签和定义非局部标签形式上一样了