信息来源:剑雨销香 CNIDP组织 联合制作
目录详细介绍
第一章:简介
第二章:运行NASM
第三章:NASM语法介绍
第四章:NASM的预处理器
第五章:汇编方向性
第六章:输出格式
第七章:写16位代码(DOS,Windows 3/3.1)
第八章:写32位代码(Unix,Win32,DJGPP)
第九章:16位与32位混和编程
第十章:问题解答
---------------- Copyright (c) 2003 CNIDP 剑雨销香 all rights reserved ----------------
本来我们也开始翻译这个,想不到已经有人翻译好了,衷心感谢那些翻译的人!
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:04:28
--
nasm网际编译器手册:
此手册为NASM,网际汇编器:一个目标为Intelx86系列处理器,开放源码的编译器。
第一章:简介
1.1节 什么是NASM?
1.1.1节 为什么用另外的编译器?
1.1.2节 许可证
1.2节 联络方式
1.3节 安装
1.3.1节 在MS_DOS或Windows下安装NASM
1.3.2节 在Unix下安装NASM
第二章:运行NASM
第2.1节 :那NASM命令行参数
第2.1.1节:-o参数:指定输出文件名
第2.1.2节:-f参数:指定输出文件格式
第2.1.3节:-l参数:输出列表文件
第2.1.4节:-E参数:输出错误信息到文件中
第2.1.5节:-s参数:输出错误信息到屏幕
第2.1.6节:-i参数:指定头文件路径
第2.1.7节:-p参数:预编译头文件
第2.1.8节:-d参数:预编译宏
第2.1.9节:-u参数:取消宏定义
第2.1.10节:-e参数:只预处理
第2.1.11节:-a参数:根本不预处理
第2.1.12节:-w参数:允许或禁止汇编警告信息
第2.1.13节:NASM的环境变量
第2.2节 对于MASM用户的快速入门
第2.2.1节 NASM是区分大小写的
第2.2.2节 NASM对内存操作需要方括号
第2.2.3节 NASM不存储存变量类型
第2.2.4节 NASM不用ASSUME关键字
第2.2.5节 NASM不支持内存模型
第2.2.6节 浮点数方面的区别
第2.2.7节 其它区别
第三章
第3.1节 NASM源码行号的分布
第3.2节 伪指令
第3.2.1节 DB及相关指令:声明初始化数据
第3.2.2节 RESB及相关指令:声明未初始化数据
第3.2.3节 INCBIN:包含外部二进制文件
第3.2.4节 EQU:定义常量
第3.2.5节 TIMES:重复指令或数据
第3.3节 有效地址
第3.4节 常量
第3.4.1节 数字常量
第3.4.2节 字符常量
第3.4.3节 字符串常量
第3.4.4节 浮点数常量
第3.5节 表达式
第3.5.1节 |:位运算符或
第3.5.2节 ^位运算符异或
第3.5.3节 &位运算符与
第3.5.4节 <<和>>:位移动操作符
第3.5.5节 +和-:加和减操作符
第3.5.6节 *,/,//,%和%%:乘法与除法操作
第3.5.7节 一元操作符:+,-,~和SEG
第3.6节 SEG和WRT
第3.7节 临界资源表达式
第3.8节 本地变量
第4章 NASM的预处理器
第4.1节 单行的宏操作
第4.1.1节 正常处理方式:%define
第4.1.2节 立即处理的单行宏:%xdefine
第4.1.3节 未定义的宏:%undef
第4.1.4节 预处理变量:%assign
第4.2节 多行宏的操作
第4.2.1节 重载多行宏:%macro
第4.2.2节 本地宏标识
第4.2.3节 贪婪的宏参数
第4.2.4节 默认宏参数
第4.2.5节 %0:宏参数记数
第4.2.6节 %rotate:旋转宏参数
第4.2.7节 控制台的宏参数
第4.2.8节 用宏参数做为条件语句
第4.2.9节 禁止列表展开
第4.3节 条件汇编
第4.3.1节 %ifdef:测试单行宏的存在性
第4.3.2节 %ifctx:测试上下相连的堆栈
第4.3.3节 %if:测试任意数字表达式
第4.3.4节 %ifidn和%ifidni:测试试验性文本标记
第4.3.5节 %ifid,%ifnum,%ifstr:测试标记类型
第4.3.6节 %error:报告用户自定义的错误
第4.4节 预处理循环:%rep
第4.5节 包含其它文件
第4.6节 上下相关的堆栈
第4.6.1节 %push和%pop:创建和移走上下环境
第4.6.2节 前后相关的本地变量
第4.6.3节 前后相关的单行宏
第4.6.4节 %repl:重命名一个前后相关环境
第4.6.5节 用前后相关堆栈的一个例子:IFs块
第4.7节 标准宏
第4.7.1节 _NASM_MAJOR_和_NASM_MINOR_:NASM版本信息
第4.7.2节 _FLLE_和_LINE_:文件名和行号
第4.7.3节 STRUC 与ENDSTRUC:定义结构数据类型
第4.7.4节 ISTRUC,AT 与IEND :定义结构的变量
第4.7.5节 ALIGN 和ALIGNB:数据对齐方式
第5章 汇编方向性
第5.1节 BITS:指定目标处理器模式
第5.2节 SECTION或SEGMENT:改变和定义段
第5.2.1节 _SECT_宏
第5.3节 ABSOLUTE:定义绝对标号
第5.4节 EXTERN:从其它模块导入符号
第5.5节 GLOBAL:向其它模块输入信息
第5.6节 COMMON:定义通用数据块
第6章 输出格式
第6.1节 bin:平坦二进制输出
第6.1.1节 ORG:二进制文件源码
第6.1.2节 bin扩展到SECTION方向
第6.2节 obj:微软的OMF目标文件格式
第6.2.1节 obj扩展到SECTION方向
第6.2.2节 GROUP:定义段的组信息
第6.2.3节 UPPERCASE:禁止输出时大小区别
第6.2.4节 IMPORT:导入DLL符号
第6.2.5节 EXPORT:导出DLL符号
第6.2.6节 ..start:定义程序输入点位置
第6.2.7节 obj 扩展到EXTERN 方向
第6.2.8节 obj扩展到COMMON方向
第6.3节 win32:微软Win32目标文件
第6.3.1节 win32:扩展到SECTION方向
第6.4节 coff:通用目标文件格式
第6.5节 elf:Linux ELF目标文件
第6.5.1节 elf扩展到SECTION方向
第6.5.2节 位置独立代码:elf指定符号和WRT
第6.5.3节 elf扩展到GLOBAL方向
第6.5.4节 elf扩展到COMMON方向
第6.6节 aout:Linux a.out目标文件
第6.7节 aoutb:NetBSD/FreeBSD/OpenBSD a.out目标文件
第6.8节 as86:Linux as86 目标文件
第6.9节 rdf:重定位动态目标文件格式
第6.9.1节 需求库:LIBERARY方向
第6.10节 dbg:Debuging 格式
第7章 写16位代码(DOS,Windows 3/3.1)
第7.1节 生成.EXE文件
第7.1.1节 用obj格式生成.exe文件
第7.1.2节 用bin格式生成.exe文件
第7.2节 生成.com文件
第7.2.1节 用bin格式生成.com文件
第7.2.2节 用obj格式生成.com文件
第7.3节 生成.sys文件
第7.4节 与16位C程序的接口
第7.4.1节 外部符号名
第7.4.2节 内存模型
第7.4.3节 函数定义与函数调用
第7.4.4节 数据项的访问
第7.4.5节 c16.mac:16位C接口的帮助宏
第7.5节 Borland Pascal程序接口
第7.5.1节 Pascal调用转换
第7.5.2节 Borland Pascal段命名限制
第7.5.3节 在Pascal程序中用c16.mac
第8章 写32位代码(Unix,Win32,DJGPP)
第8.1节 32位C语言接口
第8.1.1节 外部符号名
第8.1.2节 函数定义与函数调用
第8.1.3节 数据项的访问
第8.1.4节 c32.mac:32位C接口的帮助宏
第8.2节 写NetBSD/FreeBSD/OpenBSD和Linux/ELF共享库
第8.2.1节 包含GOT地址
第8.2.2节 寻找程序本地数据项
第8.2.3节 寻找外部和通用数据项
第8.2.4节 导出符号到库用户
第8.2.5节 在库外部调用过程
第8.2.6节 生成库文件
第9章16位与32位混和编程
第9.1节 混和尺寸的跳转
第9.2节 不同尺寸段间的寻址
第9.3节 其它的混和尺寸指令
第10章 问题解答
第10.1节 常见问题
第10.1.1节 NASM 生成无效的代码
第10.1.2节 我程序中跳转出界了
第10.1.3节 ORG不能工作了
第10.1.4节 TIMES 不能工作了
第10.2节 Bugs程序中的问题
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:06:03
--
第1章 简介
1.1 什么是NASM?
网际汇编器,NASM是一个模块化和便携性的80x86汇编编译器。它支持多种目标格式包含Linux的a.out
,ELF,NetBSD/FreeBSD,COFF,Microsoft的16位OBJ和Win32。 它输出平坦模式的二进制文件。
它在语法设计上简单且容易理解,和Intel的相似但没有那么复杂。 它支持Pentium,P6和MMX操作码,
并且宏操作方面兼容。
1.1.1 为什么不用其它编译器?
网际编译器是在基于comp.lang.asm.x86(也可能是alt.lang.asm我记不清了)上的思想成长
起来的。它在本质上不象周围很好的自由x86系列编译器, 并且应有人写一个。
a86是比较好的, 但并不免费,通常你不能得到任何32位兼容的除非你付费。 它只支持DOS。
gas是免费的, 并且适应于DOS和Unix, 但它不是很好用, 由于它被设计成相对于gcc的后端,
所以必须给它输入一些正确的密码。 导致它的错误检查是很小。 因此, 从这方面来看待和
真正写些代码来说,它的语法是很难懂的。 另外你不能用它写16位代码(正常情况下)。
as86是Linux专用的, 并且有很多文档(至少对我来说)。
MASM不是很好用, 它太贵了, 并且只能在DOS下运行。
TASM好一些, 但仍在和MASM相兼容, 意味着将会有不计其数的指令的官文。 它的语法本质上
和MASM相同。 但它也很贵。 只在DOS下运行。
因此, 这里为了编码的快乐,用NASM。现在它仍然处理试验阶段-我不能保证它这些编译器好,但请向我
们报告程序中的问题,修改意见及帮助信息, 和任何你现有的信息。 (感谢很多人会这样做, 你知道
你也会) , 我们将不断的发展它。
1.1.2 软件许可协议
请查看许可协议文件,做为NASM描述档案的一部分提供, 你可以在许可协议下使用NASM。
1.2 联系方式
当前版本的NASM(从0.98)是由H.Peter Anvin(
hpa@zytor.com)维护的。
如果你想报告任何程序问题, 请先读一下第10.2节.
NASM有一个网页为:htt//www.cryogen.com/Nasm.
原始作者可邮寄电子信箱:
jules@earthcorp.com 和
anakin@pobox.com
最新版本的NASM已经上传到ftp.kernel.org,sunsite.unc.edu,ftp.simtel.net和ftp.coast.net.
通告将发布在 comp.lang.asm.x86,alt.lang.asm,comp.os.linux.announce和
comp.archives.msdos.announce(最后一个将自动传到ftp.simtel.net上)
如果你没有新闻组可以访问,或者更喜欢用电子邮件进行交流,你可以发送一行包含
subscribe nasm-announce的内容的邮件到
majordomo@linux.kernel.org.
如果你想了解NASM beta版的有关信息,请发送一封含有subscribe nasm-beta信息的电子邮件到
http://www.pop417.com/bbs/mailtmajordomo@linux.kernel.org.
1.3安装
1.3.1 在MS_DOS 或Windows下安装NASM
当你得到NASM的DOS版本的文件时,nasmXXX.zip(XXX表示NASM的版本号),将它解压到当前目录下
(例如:C:\\NASM)
这个文件包含4个执行文件:NASM的执行文件为:nasm.exe和nasmw.exe和NDISASM的执行文件:
ndisasm.exe,ndisasmw.exe.这个文件夹里文件名后有w为一个win32可执行文件,被设计在windows95
或windows NT下运行,另外的是16位的DOS执行程序。
NASM文件要运行它的自运行文件,因此拷贝(至少)nasm.exe和nasmw.exe的一个到你的目录下,或
选择一个编辑autoexec.bat文件将nasm的路径加到你的PATH目录上。 (如果你要节省空间可以删除它);
然而,你可以保留这个文件或测试程序。
如果你下载了DOS的源码文件包, nasmXXXs.zip,nasm目录将包含完事的NASM源代码,你可以选择一个
MAKEFILE(推荐)来重新编译NASM,README文件列出了这些MAKEFILE之间的不同和用什么编译程序编译
的。 注意源文件:insnsa.c,insnsd.c,insnsi.h和insnsn.c是自动从Perl角本文件:
主指令列表文件insns.dat生成的。
文件macros.c是通过另一个Perl角本从standard.mac生成的。
虽然NASM 0.98的发布包含这些生成文件,但如果你改变了insns.dat,standard.mac或相关文档,
你还是需要重新编译它。(因此你需要一个Perl解释器),也许以后的源码发布文档根本就不包含
这些文件。Perl在不同平台(包含DOS和Windows)的输出变化,在
http://www.cpan.org/可以找到。
1.3.2 在Unix下安装NASM
一旦你得到NASM的Unix源码文件:nasm-X.XX.tar.gz(这里X.XX表示档案中包含NASM的版本号),你
就可以把它解压到如:/usr/local/src的目录下,这个档案一旦解压会生成它自己的子目录:nasm-X.XX.
NASM是一个自配置压缩包:你解压后,用cd命令到它解压的目录下,然后用type ./configure命令,
这个shell角本将会找到最好的c编译器来编译NASM并建立相应的Makefiles文件。
一旦NASM自动配置后,你就可以用命令type make来编译nasm和ndisasm二进制文件,然后用命令install
来将它们安装到/usr/loacl/bin下,将nasm.1和ndisasm.1的帮助文件安装到/usr/local/man/man1目录
下。对于有选择的,你可以给出如--prefix来配置角本(关于安装的详细信息见INSTALL文件),或者安装
你自己的程序。
NASM也提供一些关于RDOFF自定义目标文件格式的工具,这些在NASM压缩包的rdoff子目录下,如果你想用
他们的话。
如果NASM自配置失败的话,你可以用Unix的失败后退makefile文件Makefile.unx来编译它。对Makefile
文件拷贝或重命名文件, 并试关重新编译。在rdoff子目录下也有一个Makefile.unx文件。
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:09:55
--
第二章 运行NASM
2.1NASM命令行参数
为了汇编一个文件,你必须用一个命令行来实现:
nasm -f [-o ]
例子:
nasm -f elf myfile.asm
将文件myfile.asm汇编成一个elf目标文件myfile.o.
nasm -f bin myfile.asm -o myfile.com
将文件myfile.asm汇编成一个二进制文件myfile.com.
为了生成一个列表文件,用参数-l来给出一个列表文件名,例如:
nasm -f coff myfile.asm -l myfile.lst
为了得到更多关于NASM指令用法请用命令:
nasm -h
这也可以列出有效的输出文件格式,及它们是什么.如果你用Linux,但不确信你的系统是a.out还是ELF,
用命令file nasm
(在你安装NASM一进制文件的哪个目录),如果它显示如下信息:
nasm:ELF 32-bit LSB executable i386 (386 and up) Version 1
哪么你的系统为ELF,当你想生成Linux目标文件时应该用-f elf。
如果它显示:nasm:Linux/i386 demand-paged executable(QMAGIC)
或相似的一些信息,你的系统应该是a.out的.你应该用use -f aout来代替(Linux a.out系统被认为淘汰了,
现在很小见)
象一些Unix编译器,NASM是默认的除非它出错;你瘵不能看到它输出任何信息,除非它显示错误信息。
2.1.1 -o参数 指定输出文件名
NASM正常情况下将为你指定输出文件名;它对目标文件格式是独立的。对于微软的目标格式文件(obj和
win32),它将从你的源文件移走asm扩展名(或者你用-NASM来忽略)并加上obj扩展名.对于Unix目标文件格式
(aout.coff,elf和as86),它将为rdf加上.o,它将用.rdf,而对于二进制文件它只是简单的移去扩展名.
所以myfile.asm文件将生成名为myfile的输出文件.如果输出文件存在,NASM将覆盖它,除非它有相同名字
的输入文件, 这种情况它将组出一个警告并用nasm.out来代替输出文件名。
这种替换行为是不允许的,NASM提供-o命令行参数来允许你指定你想要的输出文件名。
你可以在-o后边加空格或不加空格,然后在加文件名,如:
nasm -f bin program.asm -o program.com
nasm -f bin driver.asm -odriver.sys
2.1.2 -f参数:指定输出文件格式
如果你不用-f参数指定文件格式,NASM将为你指定一种格式。在NASM的发布版本中,默认的为bin;
如果你编译你自己的NASM拷贝,你可以在编译时重新定义OF_DEFAULT并且选择你想默认的文件名。
象 -o参数一样,在-f 和输出文件格式之间的空格是可选的。所以-f elf 和-felf都是有效。
一个完整的输出文件格式列表可能过命令nasm -h来得到。
2.1.3 -l参数:生成列表文件
如果你用NASM的-l参数,后面加文件名(通常中间加),NASM将生成一个源码列表文件。生成的代码和
地址被列在左边,还有真正的源码,并带有多行宏的扩展。(除了指定在要求在源码列表不扩展:见
第4.2.9节)在右边。例如nasm -f elf myfile.asm -l myfile.lst
2.1.4 -E参数:将错误信息输出到文件中
在MS-DOS下将程序的错误信息输入到一个文件中是比较困难(虽然这有方法),因此NASM通常将警告信息
和错误信息显示在屏幕上,如果你想捕获这些信息并存入到编辑器中将是比较困难的事.
NASM因此提供了-E参数然后加上文件名将会使错误输出到文件中而不是标准错误设备上。因此你可以通过
命令typing nasm -E myfile.err -f obj myfile.asm 重新将错误信息输出到文件中。
2.1.5 -s参数:将错误输出到标准输出上
-s参数将错误信息重新定位到标准输出面不是标准错误上,因此它能在MS-DOS下运行。为了汇编文件
myfile.asm并且将它通过管道输出到其它程序中,你可以通命令:
nasm -s -f obj myfile.asm |more
关于-E参数的详细信息请看第2.1.4节
2.1.6 -i参数:头文件设置路径
当NASM在源文件中查看%include 定向符号时(详细信息见第4.5节),它将搜索在命令行中指定的目录
而不只是当前目录中文件。你也可以从一个宏库文件中包含文件,例如,用命令:
nasm -ic:\\macrolib\\ -f obj myfile.asm
(通常在-i和路径名中的空格是可选的)
NASM ,在源码级兼容方面比较感兴趣,不用理解不同操作系统间文件名的转换。你提供的字符串做为-i参数
将写到头文件的名字。因此在上面的例子中加反斜杠是需要的。在Unix下,在前面加上一个斜杠也是需要的。
(你可以利用这点,如果你真想做的话,那么参数-ifoo将引起%include "bar.i"来搜索文件foobar.i...)
如果你想要定义一个标准头文件搜索路径,在Unix系统上象/usr/include,你应该在NASM的环境变量中用
一个或多个-i定向符。(见第2.1.13节)
为了与许多的C编译器的Makefile相兼容,这个参数也可以指定为-I。
2.1.7 -p参数:预包含头文件
NASM允许你通过-p参数指定文件预包含到你的源文件中,所以命令:
nasm myfile.asm -p myinc.inc
和运行nasm myfile.asm 然后在文件的开始置换%include "myinc.in"等价。
和-I,-D,-U参数一样,这个操作也可以写成-P。
2.1.8 -d 参数:预定义宏
-p参数给出了一个在源文件开始处可选择%include 定向符的方法,-d参数给出了可选择%define定向符的
方法。你可以将代码 nasm myfile.asm -dFOO=100写成%define FOO 100D 在文件开始时,你可以关掉宏
的值如:-dFOO 等价与%define FOO。这个定向符的表格对汇编时的参数选择有用, 如%ifdef
例如-dDEBUG。
为了和许多C编译器的Makefile文件兼容,这个参数可以写成-D.
2.1.9 -u参数:取消一个宏的定义
-u参数在已经预定义一个宏的情况下取消一个宏的定义。并且自动取消前面命令行中的-p或-d参数。
例如:命令行参数:nasm myfile.asm -dFOO=100 -uFOO
的作用是在程序中取消以前定义的FOO宏。这对于在Makefile中重载参数比较有用。
为了许多C编译器的Makefile兼容,这个参数可以指定为-U。
2.1.10 -e参数:只进行预处理
NASM 允许在它运行时进行预处理到某一点。用-e参数(不需参数)将会使NASM在它的输入文件中进行预处理,
扩展所有宏参数,移去注释和预处理符,并且在标准输出上显示结果文件(也可以用-o参数将结果存到
文件中)。
这个参数不能应用于那些对独立符号值进行预处理的操作如:%assign tablesize($-tablestart)
将在只进行预处理模式中引起一个错误。
2.1.11 -a参数:根本不进行预处理
如果NASM被做为一个编译器的后端使用,为了节约时间和提高速度,它可能假设编译器已经进行了预处理,
而完全不需要预处理。-a参数不需要参数,NASM将肢用一个什么都不做的stub预处理来替换这个参数。
2.1.12 -w参数:允许/禁止汇编警告
NASM在汇编码方案的过程中将检测很多条件,来提醒用户,但是一个错误不致于生成一个文件是这些条件
做为链接错误来报错并且在消息的前面有‘warning’这个词,Warning 不会阻止NASM生成文件并返回给系统
一个成功的状态。
有些情况可以忽略:它们只是想提醒用户。因而NASM允许用户用-w命令行参数来允许可禁止指定类别的
警告。警告类别可以用名字来描述,例如:orphan-labels,你可以用-w+orphan-labels来允许这个级别的
警告用-w-orphan-labels来禁止它.
可用的警告信息有:
macro-params 包含所有错误号参数执行的多行宏警告信息.这个警告类别默认是允许的;关于如
何禁止它的例子见第4.2.1节.
orphan-labels 包含警告哪些没有指令但用冒号定义的标号的源码行.NASM默认情况下没有对这种
条件进行规定;如果你想进一步了解它见第3.1节的例子.
number-overflow 包含不适用于32位的数字信息(例如:打入太多个f而产生0x7ffffffff的错误)
这个警告默认情况是允许的.
2.1.13 NASM的环境变量
如果你定义了一个叫NASM的环境变量,程序将会把它解释为为一个外部命令行参数,在真正的命令行以前
进行处理.你可以用这个来为头文件定义标准的搜索路径.通过在NASM变量中用-i操作.
这个变量的值用空格分开,以致于-s -ic:\\nasmlib 将做为2个单独的操作.然而,这意味着变量
-dNAME="my name"不会做你想要做的事.因为它将在空格处分开并且NASM命令行处理器将会得到两个无意义
的字 -dNAME="my and name".
为了可以这样做,NASM提供了一个操作,如果你在变量的前面字符加一个不为负号的字符,NASM将会这个符号
做为分隔符,所以设置变量 !-s!-ic:\\nasmlib 等价于-s-ic:\\nasmlib,但!-dNAME="my name"将会工作.
2.2 MASM用户快速入门
如果你用MASM写过程序或用与MASM相兼容的TASM或a86,这节试着说明MASM与NASM在语法上的主要区别.
如果你没有用过MASM,哪么请跳过这一节.
2.2.1 NASM是大小写区分的
一个简单的区别是NASM是区分大小写的,你调用标号foo,Foo或FOO是不一样的。如果你编译过DOS或OS/2的
.OBJ文件,你可以执行UPERCASE 指令(文档见第6.2节)来保证向其它模块输出的所有符号都为大写。
但在这里NASM只在标号间对它们进行区分。
2.2.2 NASM 需要用方括号引用内存
NASM在语法上被设计为尽量简单。一个实用的设计目标为用户看一行NASM代码时就告诉它产生的操作码是
什么。你不能在MASM中这样做:如果你定义如下符号
foo equ 1
bar dw 2
那么下面两行代码
mov ax,foo
mov ax,bar
将产生完全不同的操作码,尽管他们有相同的语法。NASM避免这种由有相同语法而产生不同情况的内存
引用。这个规则不难实现:对于任何访问内存内容的地方用方括号,而任何访问变量地址的则不用。
所以一条指令:mov ax,foo将会总是参考编译时的内容,无论它是一个EQU还是一个变量的地址;
访问一个变量的内容时,你必须用mov ax,[bar]
这也意味着NASM不需要MASM的OFFSET关键字,所以MASM的mov ax,offset bar与NASM的mov ax,bar意义
相同。如果你试着在NASM得到大量的MASM代码,你将会用%idefine offset来生成物大量的OFFSET预处理
关键字做为无意义的工作。
这会在a86中产生一些迷惑,用一个冒号结束一个标号的定义和一个变量区分引起a86采用NASM形式的语义。
所以在a86中,mov ax,var有另外的意义决定于var是否被定义成变量;dw 0(一个标号)或者 var dw 0(
一个WORD尺寸的变量)。NASM比较起来是很简单的:任何符号都是一个标号。
NASM在简单性方面很出色,也不支持象MASM复杂的语法格式和它的拷贝,如:mov ax,[table+bx],同样的
mov ax,es:[di]是错的而mov ax,[es:di]是对的。
2.2.3 NASM不存储变量类型
NASM在设计时,就选择了不记忆你定义的变量类型。然而看到var dw 0 MASM将记下你定义了一个字长的变量,
然后在指令mov var,2前 向这个尺寸填入一个值,而NASM则故意不记这个符号除非它开始时,所以你必须用
这样的指令:mov word [var],2.
由于这个原因,NASM不支持LODS,MOVS,STOS,SCAS,CMPS,INS或OUTS指令,但支持表格处理如:LODSB,MOVSW和
SCASD,这些指令明确指定了处理字串的单位。
2.2.4 NASM不用ASSUME.
做为NASM简单性的一部分,它不支持ASSUME关键字,NASM不保留你将什么值放入段寄存器的动作.并且也
永远不会自动生成一个段重载前缀。
2.2.5 NASM不支持内存模型
NASM不支持任何提供16位内存模型的操作符。程序员必须保留哪个函数为远调用哪个函数为近调用。
对于放入RET指令的正确表格也是负责的。(RETN或RETF;NASM接受RET做为RETN表格的一个替换);另外,
程序员也必须自己处理CALL FAR指令对于当外部调用函数时,并且也必须保留外部变量是far 还是near的
信息。
2.2.6 浮点数的区别
NASM 用不同的名字引用浮点寄存器;MASM叫它们为ST(0),ST(1),a86叫它们0,1;NASM叫它们st0,st1;
2.2.7 其它区别
由于历史原因,NASM用关键字TWORD,而MASM和其它编译器用TBYTE。
NASM同MASM一样不定义未初始化的内存;为了读一个64字节的保留内容。MASM程序员用stack db 64 dup(?),
NASM则用stack resb 64,为了有限的兼容,NASM对待问号做为一个有效字符,你可以用? equ 0然后写
dw ?将会有效。然而DUP是不被支持的。
除了以上说明这些,宏和定向符完全和MASM不同,详细信息请见第四章和第五章。
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:14:39
--
第三章 NASM语言
3.1 NASM源码行的分布
象许多编译器一样,每个NASM的源码行(除非它是一个宏,一个预处理符或一个汇编定向符请见第四章和
第五章)都包含4个域
标号: 指令 操作符 ;注释
通常,这些域的一些为可选的:一个标号,一个指令和一个注释是可选的,当然,在指令域不存在时,操作符域
是必需的.
NASM在一行中是不限制空格的个数的:标号前可以有空格,指令前可以没有空格,标号后的冒号是可选的.(
注意,这意味着如果你愿意用一行来写lodsb,然后用lodab,那么这行只定义了一个标号而且是一个什么也
不做的有效代码行.运行用命令行-w+orphan-labels运行NASM,编译器将会提示你:一个没有冒号的标号行被
定义.)
标号里的有效字符为:字符,数字,_,$,#,@,~,.和?.定义标号的第一个字符必须为字母.,_和?,(详细信息
见第3.8节).一个标号如果带前缀$时,表示它将做为一个标号来处理而是一个保留字;因此,如果你用一个叫做
eax的符号来链接其它的模块,你应该在NASM的代码中用$eax来区别其它寄存器.
这个指令域可以包含什么机器指令:Pentium和P6的指令集,FPU指令,MMX指令和其它未公开的指令.这个指令集
通常也可以用前缀:LOCK,REP,REPE/REPZ或REPNE/REPNZ.编译器用前缀A16,A32,O16和O32来表示显式的地址
尺寸和操作符尺寸,其中第9章有关于这方面的一个例子.你也可以用段寄存器的名字做为一条指令的前缀:
coding es mov [bx],ax等价于指令:mov [es:bx],ax。我们推荐后一种方法,因为它包含了这种编译器的
其它一些语法特性, 而对于指令:LODSB则不需要操作数但也可以加一个段寄存器的名字,这对于从es lodsb
的形式是一种不清楚的语法。
对于一些不需要前缀的指令如:CS,A32,LOCK,REPE自己可以单独做为一行,则NASM为他们产生前缀字节。
除了现存的一些机器指令外,NASM也支持一定数量的伪操作符,详细信息见第3.2节。
指令可以操作一些表格:它们可以是用寄存器名字命名的寄存器(如ax,bp,ebx,cr0:NASM不用gas-style语
法,这种语法中的寄存器名字前必须加a %sign),或有效地址(见第3.3节),常量(见3.4节)或表达式(
见3.5节)。
对于浮点数指令,NASM则有一个范围很广的语法:你可以象MASM一样用两个操作符形式,你也可以用NASM
本身的许多单操作符指令。所有支持的指令格式见附见A,例如你可以写:
fadd st1 ;this sets st0:=st0+st1
fadd st0,st1 ;so does this
fadd st1,st0 ;this sets st1:=st1+st0
fadd to st1 ;so does this
所有对内存引用的浮点数指令必须用前缀:DWORD,QWORD或TWORD来表示它所引用内存的尺寸。
3.2 伪操作符
伪操作符的意思是:它们不是真正的x86机器指令,但由于它们易于理解,所以经常在指令域中使用。当前的
伪操作符指令有:DB,DW,DD,DQ和DT,与其它指令一起用闹噶钣校篟ESB,RESW,RESD,RESQ和REST,
INCBIN命令,EQU命令,TIMES前缀。
3.2.1 DB和有关指令:定义初始化数据
DB,DW,DD,DQ和DT在MASM中用的很多,它们是用来定义输出文件的初始化数据了解的,它们使用的方式很
多:
db 0x55 ;只定义一个字节0x55
db 0x55,0x56,0x57 ;成功定义三个字节
db \'a\',0x55 ;定义字符常量
db \'hello\',13,10,\'$\' ;这是字串常量
dw 0x1234 ;0x34 0x12
dw \'a\' ;0x41 0x00(它只是一个数字)
dw \'ab\' ;0x41 0x42(字符常量)
dw \'abc\' ;0x41 0x42 0x43 0x00(字串)
dd 0x12345678 ;0x78 0x56 0x34 0x12
dd 1.234567e20 ;浮点数常量
dq 1.234567e20 ;双精度浮点数
dt 1.234567e20 ;扩展的双精度浮点数
DQ和DT不能接受数字常量和字符串常量做为操作数。
3.2.2RESB及相关符号:定义未初始化的数据
RESB,RESW,RESD,RESQ和REST被用在一个模块的BSS段:它们定义未初始化的存储空间。这些操作符中的
每一个都可以带一个操作数,这个操作数可以是一些字节,字,或双字来保存空间。这些在第2.2.7节中
描述。NASM不支持MASM/TASM用DW来定义未初始化的空间或类似的语法:RESB伪操作数是一个临界的表达式。
见第3.7节说明。
例如:
buffer: resb 64 ;保留64个字节
wordvar: resw 1 ;保留一个字
realarry resq 10 ;10个实数列表
3.2.3 INCBIN:包含外部的二进制文件
INCBIN是从旧的Amiga汇编器DevPac引用来的:它包含一个二进制文件verbatim到一个输出文件。这点对于
将一图象与声音数据直接加到一个关于游戏的程序是十分方便的。它可以有以下三种表示方式:
incbin "file.dat" ;包含整个文件
incbin "file.dat",1024 ;跳过前1024个字节
incbin "file.dat",1024,512 ;跳过前1024个字节,并且最多只包含512个字节
3.2.4 EQU:定义常量
EQU字义一个给定的符号为常数:当EQU被用时,源码行必须包含一个标号。EQU的用法是给一个数定义一个
标号名字。这个定义是固定的,不能在以后的代码改变它。例如:
message db \'hello,world\'
msglen equ $-message
定义msglen为常数12.msglen不能在以后重新定义。这也不是一个预处理定义:msglen是当时就被赋值的,
用$符号(见第三3.5节关于$的说明)定义时,意思是说它的值为后面字串的长度。要注意的是EQU也是一个
临界表达式。(说明见3.7)。
3.2.5 TIMES:重复指令和数据
TIMES前缀将使指定的指令多次进行编译。这个指令等价于在MASM及其兼容的编译器中的DUP指令的用法。
在你的代码中可以这样写:
buffer: db \'hello,world\'
times 64-$+buffer db \' \'
以上指令将会使用一个存储空间,这个空间会存储缓冲区上限到64时的总长度。最后,TIMES可以和普通指
令用, 你可以用以下指令来执行一个不用回退的动作:
times 100 movsb
要注意的是,指令times 100 resb 1和 resb 100之间是没有什么区别的,准确的说后者在编译器的内部运行
时将比前者快100倍左右。
TIMES指令与EQU,RESB及其它相关指令一样,是一个临界的表达式(见第3.7节说明)
注意,TIMES也不被用于宏操作上:原因是TIMES将在宏操作后面才处理,因此编译器允许TIMES后面加参数
象上面的64-$+buffer一样。为了重复执行一个以上的代码行或复杂的宏,应该用预处理符%rep。
3.3 关于有效地址
一个有效地址是一个引用内存有效地址的指令中的操作数,在NASM中,有更简单的语法:它由一个含有方
括号,和方括号中的要求地址组成。例如:
wordvar dw 123
mov ax,[wordvar]
mov ax,[wordvar+1]
mov ax,[es:wordvar+bx]
在NASM中任何不符合上面规则的表达式都不是有效的内存引用。例如: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在这种情况将会生成最小的一种格式,这将不同于32位的有效地址
[eax*2+0]和[eax+eax],NASM通常对后者将会用4 个字节来存储一个0偏移。
NASM有一个提示机制来使[eax+ebx]和[ebx+eax]来生成不同的操作数;由于[esi+ebp]和[ebp+esi]用不同的
默认值寄存器。
然而,你可以用关键字BYTE,WORD,DWORD和NOSPLIT来限制NASM在一个特殊的表格中生成一个有效地址。
如果你想用双字偏移来编译[eax+3]来代替NASM通常的一个字节, 你可以用[dword eax+3].同样,你可以
用[byte eax+offset]来限制NASM用一个字节偏移来编译一个第一遍没有编译的小值。(第3.7节有这样的
一个例子)[byte eax]将会用一个人偏移来编译[eax+0],而[dword eax]将用一个双字节的0偏移。
正常格式的[eax]是没有偏移的这个域的。
同样的,NASM由于允许偏移域和空间被存储,所以会将[eax*2]分成[eax+eax];事实上,它也会将[eax*2+
offset]分成[eax+eax+offset]。你也可以NOSPLIT关键字来阻止这种情况产生:[nosplit eax*2]将会
变成[eax*2+0]。
3.4 常量
NASM将常量分成4种类型:数字,字符,字串和浮点数。
3.4.1 数字常量
数字常量是简单的数字。NASM允许你指定不同进制来定义数字常量。你可以用后缀H,Q,B来分别表示十六
进制,八进制,二进制,或者你也可以用前缀0x来表示C形式的十六进制,或者你也可以用前缀$来表示
Pascal形式的十六进制。注意,前缀$有两种用法(见第3.1节),所以一个十六进制用前缀$来表示时,
必须用一个数字做为第一个字母。
例如:
mov ax,100 ;十进制
mov ax,0a2h ;十六进制
mov ax,$0a2 ;还是十六进制,0是需要的
mov ax,0xa2 ;还是十六进制
mov ax,777q ;十进制
mov ax,10010011b ;二进制
3.4.2 字符常量
一个字符常量为一个由单引号或以引号包含的最多面手个字符的形式。这种引用方式对NASM没有区别,除非
有后的常量用单引号引用允许在里面用双引号,反之亦然。
多于一个字符的字符常量将会以little-endian来排列,例如:
mov eax,\'abcd\'
那么常量将不是0x61626364,而是0x64636261,所以如果你想将一个值存到内存中,它将读成abcd而不是
dcba。这也是字符常量被Pentium\'sR CPUID指令接受的原因。
3.4.3 字串常量
字串常量只能被命令为DB系列和INCBIN伪操作符处理,
一个字串常量看起来更象一个字符常量,只是长度不同。它的处理方式为根据实际情况将最大尺寸
的字符常量连接而成。所以下而的例子是等价的:
db \'hello\' ;字串常量
db \'h\',\'e\',\'l\',\'o\' ;等价的字符常量
下面的也是相互等价的:
dd \'ninechars\' ;双字字串常量
dd \'nine\',\'char\',\'s\';为三个双字常量
db \'ninechars\',0,0,0;看起来更合理些
注意当用db做为操作数时,一个象‘ab’这样的常量做为字串常量时将会尽量变成一个短的字符常量,
因为db \'ab\'将简单和\'db \'a\'等价。同样,三个字符和四个字符的常量用dw来处理时是一样的。
3.4.4 浮点数常量
浮点数只能用DD,DQ和DT伪操作来处理。他们将在一个传统的格式表达:数字,数值,然后是一系列可选项
的数字然后是一个以指数E结尾。所以NASM将会区分出dd 1 为定义一个整数常量而dd 1.0为定义一个浮点数
常量。
例子:
dd 1.2 ;一个简单例子
dd 1.e10 ;10,000,000,000
dd 1.e+10 ;与1.e10相同
dd 1.e-10 ;0.000 000 000 1
dd 3.141592653589793238462;pi
NASM不能对浮点数常量进行编译时算术运算。这是因为NASM被设计成便携的,虽然它经常生成可以在x86上
运行的代码,汇编程序将用ANSI C的编译器运行在任何系统上。然而,编译器将不能保证Intel的数字格式
上浮点数的兼容性,所以NASM只能处理它自己的浮点数运算,使汇编器由于一点好处而增加尺寸。
3.5 表达式
NASM里的表达式与C语言中的相似。
NASM不能保证编译时表达式的整数尺寸:因此NASM能够很轻松的在64位系统上编译并运行,
不能假设表达式等同与32位寄存器而故意使整型溢出。这样它或许不能工作。NASM能保证ANSI C做到的都
能做到:你到少在32位上工作。
NASM支持两种特殊的表达式, 允许计算当前汇编的位置:$和$$,$为到表达式行开始的位置且包含表达
式,所以你可以用JMP $来处理一个无限循环的操作。$$当前段的位置,所以你可以用($-$$)得到当前位置到
当前段有多远的。
按优等权的等级,将算术操作符列在下面
3.5.1 |:或操作
|操作给出了一个或逻辑运算,和OR机器指令一样。OR是NASM中算术运算级别最低的。
3.5.2 ^:异或运算
^提供一个异或运算操作。
3.5.3 & 与运算操作
&提供一个与运算操作。
3.5.4 <<和>>:位移动操作
<<给出了一个左移的操作,与C语言一样,所以5<<3等价与5 乘除或49。>>给出了一个位右移操作;在NASM中
移动操作经常是无符号的,所以从左边开始移位相当填入0而不是最高位的一个符号扩展。
3.5.5 +和-:加与减运算
+和-运算完全为最常见的加减运算。
3.5.6 *,/,//,%和%%:乘法与除法运算
* 是乘法运算符,/和//都是除法运算符:/是无符号除法运算,//是有符号除法运算。同样,%和%%提供了
无符号和有符号的取模运算。
NASM象ANSI C一样不能保证有答号取模的精度。
因此%在宏操作中用的更广泛一些,你应该尽量保证无论什么地方有符号与无符号取模后面都有空格。
3.5.7 一元操作符:+,-,~和SEG
在NASM的表达式语法中最高优先权为一元操作符。-代表取反操作,+什么都不做(是为了和-对应),
~计算一个单元的补数,而SEG提供了操作数所在段的地址 (将在第3.6节中解释)。
3.6 SEG和WRT
当写一个很长的16位程序时,通常将分成很多段,因此这将经常需要一个对引用段的标号。NASM提供了SEG
操作符来完成这个需求。
SEG操作符返回一个符号的相关段地址。定义一个符号的段基址是很有意义的,如:
mov ax,seg symbol
mov es,ax
mov bx,symbol
将用一个有效的符号指针来取ES:BX。
这样可能会变得复杂:由于16位段与组可能有叠加,你可能想用不同的段基址来引用一些符号。NASM允许你
这样做,用WRT(With Reference To)关键字。所你可以这样做:
mov ax,weird_seg ;weird_seg是一个段基址
mov ea,ax
mov bx,symbol wrt weird_seg
取ES:BX的方法不同,但实现的功能相同,指针都指向符号symbol。
NASM支持一个远调用(内部段)及一个段:偏移形式的跳转。这里段和偏移都是立即数。所以进行一个远
调用。你也可以用以下代码:
call (seg procedure):procedure
call weird_seg:(procedure wrt weird_seg)
(为了更详细说明括号里的为注释,可以不要).
NASM支持远调用的语法,和上面第一种用法相同。JMP和CALL工作方法相同。
在数据段里为了定义一个远指针,你必须这样写:
dw symbol,seg symbol
虽然你可以经常构造一些宏观世界处理,但NASM支持象上面的不方便的方式。