文章作者:剑雨销香 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支持象上面的不方便的方式。
3.7 临界表达式
NASM的一个局限性在于它是一个两遍的编译器;不象TASM或其它的编译器,
它只做两遍编译。因而它不能应付哪些要二遍以上的复杂源码文件。
第一遍编译用来检查被编译数据和代码的尺寸,而第二遍编译用来生成物所有代码,已知的符号地址和代
码引用地址。所以NASM不能处理的一件事情是代码的尺寸由一个在定义在代码后的决定的情况。例如:
times (label-$) db 0
label: db \'Where am I?\'
在这个例子中,TIME后的参数根本什么都不等于。NASM将拒绝这种情况由于它不能确TIMES行的尺寸。下面的
代码也是不对的:
times (label-$+1) db 0
label: db \'Now where am I?\'
这里对于TIMES后参数的任何值的定义都是错的!NASM所拒绝的这些例子及相关表达式被称为临界表达式。也
就是说明一个表达式的值必须在第一遍编译时就确定,并且只依赖于它前面的符号定义。对于TIMES前缀是一
个临界表达式;同样对于RESB系列的参数伪操作数的参数也是临界表达式。
临界表达式可以使上下文保持好:考虑以下代码:
mov ax,symbol1
symbol1 equ symbol2
symbol2:
在第一遍编译时,NASM不能决定symbol1的值,由于定义symbol1的symbol2没有被NASM找到。第二遍编译时,
它遇到了mov ax,symbol1,它由于仍然不知道symbol1的值所以仍然不能生成代码。当它处理下一行时,它
找到EQU并且得到解决symbol1的值,但这已经太晚了。
NASM通过定义一个EQU的右边临界表达式来解决这个问题。这样在第一遍编译时symbol1将会被拒绝。
这有一些相关的例子:
mov eax,[ebx+offset]
offest equ 10
NASM在第一遍编译时,必须计算出指令:mov eax,[ebx+offset]的尺寸,虽然不知道offset 的值。它无法
知道offset是一个1字节的小值还是短整型格式有效地址的编码。它所能知道的就是在第一遍编译中,offset
应为一个代码 的一个符号,它可能用4个字节来填充。所以它为了适应4字节地址部分来记算指令的尺寸。
在第二遍编译中,为了维持这个判断,它要被迫使指令变得很大,所以这种方式产生的代码将不再是以前
哪么小。这种问题可以通过在offset前定义它或用[byte ebx+offset]来限制有效地址的尺寸。
3.8 本地标号
NASM对标号开始的部分进行了特殊处理,一个在单周期开始的标号为一个本地标号,也就是说它与以前的非
本地标号有关。所以例如:
label1 ;一些代码
.loop ;一些代码
jne .loop
ret
label2 ;一些代码
.loop ;更多的代码
jne .loop
ret
在上面的代码中,每个JNE都跳到它前面的行中,这是因为两个.loop的定义被前面的非本地标号分开了。
本地标号的处理方式是从旧Amiga汇编器DevPac中借鉴来的。然而NASM对其进一步发展了,允许访问其它代
码中的本地标号。这意味着在前面非本地标号中定义标号是可以的。上面的第一个.loop的定义是定义一个
符号label1.loop,第二个定义是定认一个符号label2.loop所以你可以这样写代码:
label3 ;一些代码
;更多代码
jmp label1.loop
这样做是可以的-在宏中,例如可以定义一个在任何地方被访问的标号但不能被正常本地标号机制所打扰。
这样的标号不能是非本地标号,因为它将被除数后来本地标号的定义,引用所打扰;它也不能是本地的,因
为定义它的宏不知道这个标号的全名字。NASM因此引用了第三种标号,它只在宏定义中有用;如果一个标号
用一个前缀:..@,那么它在本地标号机制中什么都不做。所以你可以写:
label1: ;一个非本地标号
.local: ;这是一个标号label1.local
..@foo:;这是第三种标号
label2: ;另一个非本地宏
.local: ;这是一个标号label2.local
jmp ..@foo ;这将向前跳三行。
NASM也兼容在双周期定义其它的指定符号例如:..start用来指定obj输出格式的入口点。(见第6.2.6节)。
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:17:34
--
第四章 NASM的预处理
NASM包含一个功能强大的宏处理器,它支持条件汇编,多级文件包含,两种宏格式(单行与多行)以及对外部宏的
\'context stack\'机制.预处理操作用一个%符号做为开始.
4.1 单行宏
4.1.1 常用方法:%define
单行宏用%define预处理定向符来定义.这个定义方式与C语言有些相似;所以你可以这样:
%define ctrl 0x1F &
%define param(a,b) ((a)+(a)*(b))
mov byte [param(2,ebx)],ctrl \'D\'
这将扩展为:
mov byte [(2)+(2)*(ebx)], 0x1F& \'D\'
当一个单行宏的扩展中包含另一个宏时,另一个宏的扩展将在调用时展开,而不是在定义时.
所以下面的代码:
%define a(x) 1+b(x)
%define b(x) 2*x
mov ax,a(8)
等价于mov ax,1+2*8,虽然宏b没有在a定义时定义.宏用%define定义时是区分大小写的:
%define foo bar只能将foo展开为bar,而Foo或FOO则不行.但用%idefine代替%define(i的意思为不敏感)可以
在宏定义时不区分大小写.所以%idefine foo bar 对foo,Foo,FOO,fOO都可以扩展成bar.
当编译器检测到一个宏调用与前面的宏展开相同时,会阻止这种循环引用与无穷的回路.也就是说如果这种
情况发生时预处理器只展开第一次调用的宏,例如:
%define a(x) 1+a(x)
mov ax,a(3)
这个a(3)将只展开一次即1+a(3),并且不再展开,这个方式很有用,例子见第8.1节例子.
你可以重载一个单行的宏,如果你这样写:
%define foo(x) 1+x
%define foo(x,y) 1+x*y
预处理器将根据你调用次数来处理上面的两种宏,所以,foo(3)将展开1+3而foo(ebx,2)将展开成为1+ebx*2,
然而你定义了
%define foo bar
那么将不会有什么其它foo的定义允许:一个没有参数的宏禁止在它后面再定义与其同名的带参数的宏,相反
也一样。
但这对单行宏的重定义不会的影响:你可以用下面的定义:
%define foo bar
然后在后面对字进行重新定义:
%define foo baz
foo宏在任何地方执行时,会按照最近的定义来展开。这对于用%assign符号定义一个单行宏时特别有用(见
第4.4.1节说明)。
你也可以用NASM的命令行参数‘-d\'来重新定义一个单行宏。见第2.1.8节说明。
当定义一个单行宏时,你可以用%+伪操作符来连接字串,例如:
%define _myfunc _otherfunc
%define cextern(x) _ %+ x
cextern (myfunc)
在第一次调用展开时,第三行将变成"_myfunc",在后面的调用时它将会展开为"_otherfunc"
4.1.2 立即执行的单行宏:%xdefine
%define机制描述了前面提到的用后约束方式来定义单行宏,也就是说当你执行宏时将参考其它宏来进行
展开。所以你如果当时就改变这个宏时,宏展开的值也会变化。在它们不是相象的值时,这种方式会很有
用。如:
%assign ofs 0
%macro arg 1
%xdefine %1 dword [esp+ofs]
%assign ofs ofs+4
%endmacro
arg a
arg b
在上面的例子中,我们如果用%define来代替%xdefine那么宏a和b都会展开成dword [esp+8],这是错的。而我
们用%xdefine宏来在它定义时展开的话,那么a将会展开为dword [esp+ofs],而b将会展开为dword [esp+4]。
%xdefine宏是大小写敏感的,象%idefine与%define一样,%ixdefine可以使大小写不区分。
4.1.3 取消宏定义:%undef
单行宏可以用%undef命令取消,例如:
%define foo bar
%undef foo
mov eax,foo
将会扩展成mov eax, foo,这是由于用%undef的foo宏已经不存在了。
也可以用命令行参数\'-u\'来将预定义的宏取消。见第2.1.9节说明。
4.1.4 预处理变量:%assign
可以用%assign来将单行宏定义为一个变量(和%idefine与%define一样,%iassign不区分大小写)。
%assign被用来给无参数的宏定义一个数字,这个值可以在表达式中指定,在%assign被预处理时,这个值将
被计算。
象%define一样,宏%assign也是可以重定义的。
例如:
%assign i i+1
将增加宏的值。
%assign对于控制%rep预处理符循环的结束很有用。
见第4.4节的例子。另一个关于%assign的用法见第7.4节和第8.1节。
用%assign的表达式是一个临界的表达式(见第3.7节),并且表达式必须是一个纯数字(而不是一个代码或数
字的地址重定位引用,或一个寄存器的执行)。
4.2 多行宏:%macro
多行宏与MASM和TASM中的宏类型有些相似:一个NASM中的多行宏象下面这样:
%macro prologue 1
push ebp
mov ebp,esp
sub esp,%1
%endmacro
这里定义了一个C样式的宏prologue:所以你可以象下面这样用call来调用这个宏:
myfunc: prologue 12
它会扩展成三行:
myfunc: push ebp
mov ebp,esp
sub esp,12
宏%macro后面的数字1定义了prologue宏将要接受的参数的个数。宏里面的符号%1是对调用宏时第一个参数的引
用。当宏要引用多个参数时,它会用%2,%3来表示。
多行宏象单行宏一样是区分大小写的,除非你用%imacro来定义多行宏。
如果你要将逗号做为多行宏中参数的一部分时,你可以将整个参数放到一个括号中,如下所示:
%macro silly 2
%2: db %1
%endmacro
silly \'a\',letter_a ;letter_a:db \'a\'
silly \'ab\',string_ab ;string_ab:db \'ab\'
silly {13,10},crlf ;crlf :db 13,10
4.2.1 重载多行宏
与单行宏一样,多行宏也可以用相同名字不同参数的宏在后面进行重载。这也包含一个参数都一带的宏,所以
你可以定义:
%macro prologue 0
push ebp
mov ebp,esp
%endmacro
定义一个重载的宏将不会分配本地的堆栈空间。然而有时,你可能想用\'overlod\'重载一个机器指令;如:
%macro push 2
push %1
push %2
%macro
这样你的代码会变成:
push ebx ;这行不是宏调用
push eax,ecx ;这行是
通常,NASM对上同两行会出警告信息,因为push被定义一个宏,然后会用一个没有定义的参数来执行宏。这将
会生成正确的代码,但是编译器将给出一个警告。这个警告可以用命令行参数-w-macro参数来禁止。(见第
2.1.12节)。
4.2.2 本地宏标号
NASM允许你将一个多行宏定义为一个本地宏:所以多次调用相同的宏每次将会用不同的标号。你可以用前缀
%%来达到这个目的。你可以象下面的代码构造一个指令实现如果Z标志位为0则指行RET指令:
%macro retz 0
jnz %%skip
ret
%%skip:
%endmacro
你可以多次调用这个宏,在你调用这个宏时NASM将会生成一个\'real\'的名字来代替符号%%skip。
当用数字2345来调用每个宏时,NASM会构造一个..@2345.skip的表格。前缀符号..@是一种用来区分本地标号的
格式机制,这方面的内容见第3.8节。你应该避免用这种格式定义自己的宏(..@前缀然后是一个数字加另一个
周期)。这种情况的宏将与本地宏符号相冲突。
4.2.3 贪婪的宏参数
宏参数经常将整个命令行定义到一个参数中,然后从前面选择一或二个参数。下面是一个例子, 它向MS-DOS的
文件中写入一个你想要写入的字串:
writefile [filehandle],"hello,world",13,10
NASM允许你根据需要来定义宏的最后一个参数,这意味着你可以用超过宏本身定义的参数来调用宏,所有多余
定义的宏都会用分隔符入到下一次定义中。如下面的代码:
%macro writefile 2+
jmp %%endstr
%%str: db %2
%%endstr: mov dx,%%str
mov cx,%%endstr-%%str
mov bx,%1
mov ah,0x40
%endmacro
上面的代码将这样调用宏:第一逗号前的文本,[filehandle]被用来做为第一个宏的参数并且当%1被引用时扩展,
而所有后面的文本都将放到%2中来代替db所面的内容。
NASM中定义宏的这种扩充形式为在%macro所在行后面加参数个数再加+来实现。
如果你定义了一个扩充宏,你将会有效的告诉NASM它就将宏后面参数的个数从指定的数值扩充到无穷个;例
如,NASM将会根据后面参数个数为2,3,4或更多时来函来相应处理调用writefile。NASM将会在重载时不用这
种情况,即不允许你定义其它带4个参数的writefile。
当然,上面的宏观世界可以做为一个非扩充宏来使用,在这种情况下,它将象下面这样调用 :
writefile [filehandle],{"hello,world",13,10}
NASM支持将逗号入到宏参数中的机制,因此你应该选择一种你常用的宏定义机制。
写上面宏更好的解释见第5.2.1节。
4.2.4 默认的宏参数
NASM允许你用一范围的参数个数来定义一人多行宏。如果你这样做,你必须指定一个默认的参数。
例:
%macro die 0-1 "Painful program death has occurred."
writefile 2,%1
mov ax,0x4c01
int 0x21
%endmacro
这个宏在调用时会显示一条明显的错误信息(关于writefile的宏见第 4.2.3节):在退出前将把错误信息写到输出
设备上,它的调用没有参数,它是在宏定义中支持的一个默认的错误信息。通常你可以给这种类型的宏提供一
参数的最大个数与最小个数。最小参数在设用宏时用,你也可以指定一个默认的可选的参数。所以一个宏可以
用以下方式定义:
%macro foobar 1-3 eax,[ebx+2]
它可以用1到3个参数调,%1总是被调用,%2可以不指定而用默认值eax,如果不指定%3,则用默认值[ebx+2].你也
可以在宏定义中省略默认参数的定义,这种情况下,将默认值将是空格.一个能带可变参数的宏是比较有用的,因为
%0将被用(见第4.2.5节).来检测在宏调用是有多少个参数被用.
这种默认的机制可以与贪婪-参数机制一起使用;所以这种宏的模式将会变得用途很广,如下:
%macro die 0-1+"Painul program death has occurred.",13,10
通过用*符号,宏的最大参数个数将为无限的.当然,在这个例子里提供一个完整的默认参数集合是不可能的.关于
具体用法见第4.2.6节.
4.2.5 %0:宏参数记数器
对于一个可变参数的宏,参数%0的引用将返回一个包令调用宏时参数个数的数值型常量.这个用法与%rep相同(见
第4.4节)在第4.2.6节中给出一个相关的例子.
4.2.6 %rotate:对宏参数进行循环操作
Unix的shell程序员对shell中的移动命令应该比较落后了解,它允许一个shell角本中的参数(做为$1,$2引用)
向左移位,所以参数$2将代替$1,而最前面的$1将根本不再需要。
NASM提供了一个类似的机制,用%rotate格式。正如它的名字一样,它不象Unix中的shift命令会把最左边的参数
丢掉:而是移到最右边,反过来也一样。
%rotate 参数带一个数值参数来执行(可以是表达式)。这个宏参数指定的将右边的参数向左边移动多少个位
置。如果%rotate的参数为负,那宏的其余参数将向右边做循环移位操作。
所以下边一对宏可以存储和恢复一组寄存器的值:
%macro multipush 1-*
%rep %0
push %1
%rotate 1
%endrep
%endmacro
这个宏将用PUSH指令将参数从左到右依次调用。它首先用PUSH调用第一个参数%1,然后执行%rotate将所有参
数缶左移一位,即第二个参数变为%1。重复这个过程多次,(通过用%rep的参数%0完成)最后使每一个参数
都得到调用。
注意符*是一个最大宏参数个数的表示,也就是说在没有上限参数个数的情况下,你可以用这种多级压栈。
用这种宏对于做一个POP等价的宏而不需要指定参数是很方便的。最理想的是,你可以写一个多级压栈,将上
面的代码剪切和拷贝,然后将名字改为multipop,然后注意将出栈的寄存器以与它们相反的次序放在宏后面。
这个代码如下:
%macro multipop 1-*
%rep %0
%rotate -1
pop %1
%endrep
%endmacro
这个宏开始调用时将它所带的参数向右移动一位,而最后一个参数将出现在%1位置。然后开始出栈,然后再
开始右移,第二个倒数第二个参数将变成%1。这些参数将按与前面相反的次序进行迭代。
4.2.7 宏参数的连接
NASM的宏可以将后面的参数与它们周围的文本相连接。这将允许你在宏定义的过程中生成一系列的符号,例如
你可以用以下的宏生成一个按表中的偏移来确定的键盘码表。
%macro keytab_entry 2
keypos%1 equ $-keytab
db %2
%endmacro
keytab:
keytab_entry F1,128+1
keytab_entry F2,128+2
keytab_entry Return,13
这段代码将展开成:
keytab:
keyposF1 equ $-keytab
db 128+1
keyposF2 equ $-keytab
db 128+2
keyposReturn equ $-keytab
db 13
你也可以很容易用%1foo来将一个字符串接到宏的后面。
如果你将一个数字加到宏后面,可以在foo后面加上定义的foo1,foo2。你不能可以用%11为表示,在这种情况下
将会带11个参数。你必须用形式%{1}1来分隔第一个参数(给出的宏参数的个数)与第二个参数(将要连接的
文本)。这种连接可应用其它内连目标格式的预处理。如宏本地标识符(第4.2.2节)和相关本地标识(第4.6.
2节)。上面的情况中,用符号%和后面的文本放在括号里在语法上是有歧义的:%{%foo}bar将文本bar接到真正
的标识符:%%foo后面。(由于NASM用本地标识宏的真实名字的用法,意味着%{%foo}bar 和%%foobar都能扩
展成相同的形式,相反也是兼容)。
4.2.8 条件扩展宏参数
NASM通过给包含条件扩展代码来处理宏参数。开始,你可以用宏的参数%1来代替语法%+1,这使NASM处理
一个包含条件扩展的宏参数。并且如果宏被一个无效的条件扩展代码调用时,则会引起预处理器输出一个
错误提示信息。
对于通用的用法,你可以用一个%-1宏来使NASM来做一个相反的条件扩展。所以在第4.2.2节定义 的retz宏将
用下面一个更通用的条件来代替。
%macro retc 1
j%-1 %%skip
ret
%%skip
%endmacro
这样当用retc ne调用宏时会引起一个与JE一样的操作,或用retc po来调用宏时则会生成一个JPE调用。%+1参数
的引用用于象指令:CXZ,ECXZ等条件扩展;然而%-1则会对上面的情况输出一个错误信息,由于没有相反
的条件代码存在。
4.2.9 禁止列表扩展
当NASM从你的程序中产生一个列表文件时,它将生成一个扩展的多行宏来表示当调用宏时的内容并列出每个
表达式的内容。它允许你查看扩展宏生成时代码;然而有些宏将会使列表不清楚。NASM在这里提供了.nolist限
定符,使你可以包含一个宏定义使在列表文件中禁止扩展宏操作。.nolist限定符可以直接跟在参数个数后面。
象:
%macro foo 1.nolist
或
%macro bar 1-5+.nolist a,b,c,d,e,f,g,h
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:20:59
--
4.3条件汇编
同C预处理器一样,NASM允许对源文件的段中的某一段进行汇编。通用的语法为:
%if<条件>
;如果条件为真时则出现的代码
%elif<条件2>
;如果条件不为真而条件2为真则处理
%else
;如果条件和条件2都假时则处理
%endif
如果%else子句象%elif子句一样为可选的,你可以在后面用很多的%elif子句。
4.3.1 %ifdef:测试单行宏的存在
在一个条件汇编块的开始用%ifdef MACRO来汇编子代码,如果把MACRO没有定义 ,则会执行%elif和%else之
间的代码块。
例如:
不调试一个程序时,你可以用下面代码:
;执行一些函数
%ifdef DEBUG
writefile 2,"Function performed succesfuly",13,10
%endif
;离开并做一些其它工作
如果你用命令行参数-dDEBUG则会生成一条调试信息,而移去这个参数时,将会生成这个程序的release版本。
你可以用宏%ifndef来代替%ifdef来测试一个宏的不存在。你也可以用宏%elifdef和%elifndef来测试%elif块中的宏
定义。
4.3.2 %ifctx:测试上下相关块
条件汇编结构%ifctx ctxname将引起预处理器的上下相关堆栈用ctxname命名时处理if汇编了块。
和%ifdef 一样,%elif的相反格式为%ifnctx,%elifctx和%elifnctx都是被支持的。
关于上下相关堆栈见第4.6节。第4.6.5节有一个关于%ifctx的例子
4.3.3 %if:测试数字表达式的任意性
条件汇编结构%if expr将会在数字表达式expr为非0时,引起执行if汇编子块。这个特点的一个例子为:决定什么
时候退出%rep预处理循环。(见第3.7节)
%if 扩展了常用的NASM表达式语法,从而提供了一种在正常表达式中无法处理的相关操作符。符号:=,<,
>,<=,>=和<>测试相等,小于,大于,小于等于,大于等于和不等于。C格式==和!=做为=和<>被支持。另外低级
操作符&&,^^和||用AND,XOR,OR来表示也是有效的。它们象C逻辑符一样(虽然C没有逻辑异或),返回0或1,对于
非0值返回1(如果对于^^,输入为0和1则返回1)。这类操作符返回1为真,返回0为假。
4.3.4 %ifidn和%ifidni:测试外部文本标记
结构%ifidn text1 , text2在if text1和text2成立时将引起一个if代码块的执行。在单行扩展后是相同的段文本。空格间
区别不计算在内。%ifidni与%ifidn相拟,但是大小写区分的。
如下面的宏观世界将一个寄存器或数字压栈,并且允许你将ip做为一个真正的寄存器。
%macro pushparam 1
%ifidni %1,ip
cal %%label
%%label:
%else
push %1
%endif
%endmacro
象%if结构一样,%ifidn和%elifidn配对,%ifnidn的作用和%elifnidn正好相反,同样,%ifidni有%elifidni,%ifnidni和
%elifnidni。
4.3.5 %ifid,%ifnum,%ifstr:测试标记类型
有些宏根据参数是否为数字,字串,标识符来执行不同中的功能。例如:一个字串输出宏要区分参数是一个
字串常量还是一个存在串的指针。
这种条件下可用结构%ifid来判断(可以有空格)后面的参数如果存在并且为标识符则运行if代码块。%ifnum也
类似,但是测试的是一个数字常数,而%ifstr则是测试一个字串。
例如:在第4.2.3节定义的写文件宏可以用%ifstr来扩展:
%macro writefile 2-3+
%ifstr %2
jmp %%endstr
%if %0=3
%%str: db %2,%3
%else
%%str: db %2
%endif
%%endstr:mov dx,%%str
mov cx,%%endstr-%%str
%else
mov dx,%2
mov cx,%3
%endif
mov bx,%1
mov ah,0x40
int 0x21
%endmacro
这个writefile宏可以用下面两种方法调用。
在第一种方法中,strpointer做为已经存在字串的地址被用,而length则为它的长度;在第二种调用中,一个字串
被传给一个宏,它将会自己计算出地址和长度。
注意在%ifstr中%if的用法:这里检测了宏是否带两个参数(由于字串是一个单独的字串常量,与db %2相同)或
更多的情况(在这种情况里,前两个数将被放到%3中,然后用db %2,%3被需要处理)。
%elifxxx,%ifnxx和%elifnxxx的用法适用于%ifid,%ifnum和%ifstr。
4.3.6 %error:报告用户定义的错误
预处理器定向符%error将引起NASM在代码中定义的错误发生时报告一个错误。所以其它用户想汇编你的源文件
时,你可以保证代们定义正确的宏。如下:
%ifdef SOME_MACRO
;一些设置
%elifdef SOM_OTHER_MACRO
;做一些其它设置
%else
%error Neither SOME_MACRO nor SOME_OTHER_MACRO was defined
%endif
这样当用户在不符合你代码要求的情况下汇编则会给出相应警告提示, 这比等待机器死机或出现未知错误要
好。
4.4预处理循环:%rep
NASM的TIMES前缀,虽然有用,但不能多次执行一个多行扩展的宏,由于它是在宏扩展后才被除数NASM处理
的。NASM提供了另一种循环格式,这是预处理级的:rep。
定向符%rep和 %endp(%rep可以带一个数字参数或表达式,而 %endp则无参数)用来包含一个chunk代码。
然后预处理器就可以反复的执行这个宏多次了。
%assing i 0
%rep 64
inc word [table+2*i]
%assign i i+1
%endrep
这将会生成一个64个INC指令,在[table]到[table+126]的内存中则为按字增长的数值。
对于一些复杂的终止条件可从重复循环中跳出哑,你可以用%exitrep定义符来终止循环,如下:
fibonacci:
%assign i 0
%assign j 1
%rep 100
%if j>65535
%exitrep
%endif
dw j
%assign k j+i
%assign i j
%assign j k
%endrep
fib_number equ ($-fibonacci)/2
这个过程将会生成一个16位的Fibonacci序列的数值。注意最大重复的次数必须传给%rep。这可以避免NASM进
入一个无限循环的预处理中(多任务或多用户的系统中)从而导致系统的内存泄漏和其它应用程序的崩溃。
4.5 包含其它头文件
又是一个与C预处理相拟的语法,NASM预处理器允许你将其它源文件包含到你自己的代码中, 这可以通过
定向符%include 来实现:
%include "macros.mac"
这将把文件macro.mac的内容包含到源文件中%include 定向符的位置上。
所包含的文件会被在当前目录查找 (你运行NASM的目录,或NASM的执行目录,及源文件目录),也可用NASM
的命令行参数-i 参数来指定。
标准C中的为避免一个文件被包含多次的操作在NASM中也实用:如果文件macro.mac有以下形式:
%ifndef MACROS_MAC
%define MACOROS_MAC
;现在定义一些宏
%endif
当包含一个文件多次时,将不会引起错误,这是因为第二次运行时将不会做任何事情。你也可以用NASM的命令
行参数-p(见第2.1.7节)来明确表示当一个文件没有用%include 定向符包含时包含文件。
4.6 上下相关堆栈
对于一个定义宏为本地的标号用处不是很强大:有你可能想要共用几个宏的调用。一个REPEAT...UNTIL循环的
例子REPEAT表达式需要对UNTIL宏定义的标号的引用。然而你可能想用宏来嵌套循环。
NASM提供了一个上下相关堆栈的操作。预处理器将维护一个上下相关堆栈,每一个元素都是用一个名字来标
记。你可以用%push定义符来增加一个相关堆栈,也可以用%pop来移去。你可以在相关堆栈中定义一个本地
的标号。
4.6.1 %push 和%pop:创建和移除相关块
%push定向符用来创建一个新的相关块并将它放到相关堆栈的上面,%push需要一个参数,来为相关块命名。如
%push foobar
这个指令在堆栈上创建了一个叫foobar的相关块。你可以将几个相关块用一个名字:它们仍然可以区分开。
定向符%pop不需要参数,移除最上的相关块并消除它及相关的标号。
4.6.2 上下相关标号
与%%foo定义一个本地特殊宏的标号一样,%$foo被用来定义一个本地相关堆栈上的一个相关块。所以下面用
REPEAT和UNTIL给出例子:
%macro repeat 0
%push repeat
%$begin:
%endmacro
%macro until 1
j%-1 %$begin
%pop
%endmacro
然后用下面的代码调用它:
mov cx,string
repeat
add cx,3
scasb
until e
这段代码将在string中4个字节为单元的进行扫描,直到找到与AL中相同的字节。
如果你想定义可访问堆栈中最上面的相关块的下一个相关块,可以用%$$foo或%$$$fooo来处理再下一个
4.6.3 相关本地单行宏
NASM允许你定义一个单行宏来对指定相关块操作:
%define %$localmac 3
定义一个单行宏%$localmac到堆栈的上面相关块,当然在后面的%push命令中,它仍然可用 名字%$$localmac
访问。
4.6.4 %repl:重命名一个相关块
如果你需要改变堆栈上面的一个相关块的名字(例如它用%ifctx区分),你可以在一个%push后用一个%pop;但
这将会消除所有关地这个相关块的本地标号。
NASM提供了一个定向符%repl来给一个相关块改名,而不用涉及到有关的宏和标号,所以你可以象下面这样:
%pop
%push newname
而用无消除的版本:%repl newname。
4.6.5相关堆栈的一个例子:Block IFs
这个例子可以展示相关堆栈的所有特性,包含条件汇编%ifctx来做为一个宏集合来执行IF语句块。
%macro if 1
%push if
j%-1 %$ifnot
%endmacro
%macro else 0
%ifctx if
%repl else
jmp %$ifend
%$ifnot:
%else
%error "expected \'if\' before \'else\'"
%endif
%endmacro
%macro endif 0
%ifctx if
%$ifnot:
%pop
%elifctx else
%$ifend:
%pop
%else
%error "expected \'if\' or \'else\' before \'endif\'"
%endif
%endmacro
这个代码是这节给出的比较健壮的REPEAT和UNTIL宏,由于它用了条件汇编来检查宏运行的正确性(例如在
if 前没有调用endif)并且如果有问题用一个%error来处理。
另外,endif宏也能区分是直接在if后还是在else后的情况。这里用条件汇编将定是相关块是在堆栈上面还是不是
在上面的判断。
而else宏必须保存堆栈中的相关块,以使%$ifnot象if宏被endif宏定义一样而被引用 ,但这必须改变相关块的名字
使endif知道这有一个中间的else。它可以用%repl来实现。
这些宏的一个简单调用 为:
cmp ax,bx
if ae
cmp bx,cx
if ae
mov ax,cx
else
mov ax,bx
endif
else
cmp ax,cx
if ae
mov ax,cx
endif
endif
IF块宏的处理比较好,这就是说将一个相关块压入堆栈用inner 描述,而它上面的一个则描述outer if;
因而else和else if 经常和最后一个未匹配的if或else组合。
4.7标准宏
NASM定义了一些标准宏,它们在源文件开始处理时就已经定义了。如果你需要程序不用预定义的宏汇编,可以
使用定向符%clear来清除预处理器的操作。
许多用户级的汇编定向符(见第5章)通常做为执行基本操作定向符的宏来使用;这在第5章中有说明。其它的
标准宏将在下面说明。
4.7.1 _NASM_MAJOR_和_NASM_MINOR_;NASM版本
单行宏_NASM_MAJOR_和_NASM_MINOR_将扩展成NASM的版本号。所以,在NASM0.96的宏中,_NASM_MAJOR_
将为0则_NASM_MINOR_将为96
4.7.2 _FILE_和_LINE:文件名和行数
象C语言的预处理一样,NASM允许用户发现文件名与包含当前行的行号。宏_FILE_将扩展成一个当前输入文件
的字串(可以用%include 定向符改变)而_LINE_则定义了在当前文件中包含当前行号的数值常量。这些宏对
调试一个宏信息时有用,在一个宏定义(单行宏或多行宏)中调用 一个_LINE_宏时,将会返回一个行号,而不
是定义。所以它可以检测一个代码块是否崩溃,如下面的例子将一个行号宏写入EAX中并输出类似\'line 155:still
here\'的信息。
%macro notdeadyet 0
push eax
mov eax,_LINE_
call stillhere
pop eax
%endmacro
然后你调用notdeadyet时就可以知道代码是在哪里死掉的了。
4.7.3 STRUC和ENDSTRUC:定义结构类型
NASM的内部并不允许定义数据结构,而预处理器则将它们做为一个宏的集合来执行。宏STRUC和ENDSTRUC用
来定义一个数据类型。
STRUC带一个参数,用来命名定义的数据类型。这个名字用一个带0值的字符定义,后面加上_size后缀再定义
一个EQU的值来给出此结构的大小。一旦STRUC定义成功,你就可以用RESB相关的伪指令来定义所需的域,
最后用ENDSTRUC来完成整个类型的定义。
上面的例子将定义一个叫mytype的宏,其中包含一个长字,一个字和一个字节及一个字串:
struc mytype
mt_long: resd 1
mt_word: resw 1
mt_byte: resb 1
mt_str: resb 32
endstruc
上面的代码定义了6个符号:mt_long从0开始(从类型mytype开始到长字域的偏移)。mt_word为4,mt_byte为 6
mt_str为 7,my_type_size为 39,而mytype本身则为0。将类型名字定义为0的原因是对本地标识机制的响应:如果
你的结构成员中有与另外的结构有相同的名字,你可以用以下代码改写上的的结构:
struc mytype
.long: resd 1
.word: resw 1
.byte: resb 1
.str: resb 32
endstruc
这样定义后结构的成员将变为 :mytype.long,mytype.word,mytype.byte和mytype.str。
NASM内部不对结构支持也不支持你定义的任何结构成员的格式与引用(本地标识符除外),所以指定mov
ax,[mystruc.mt_word]是无效的。mt_word将象其它常量一样处理,所以正确的代码指令应为:mov ax,[mystruc+
mt_word]或mov ax,[mystruc+mytype.word]。
4.7.4 ISTRUC,AT和IEND:定义一个结构的实例
当定义完一个结构后,下一步你将为你的结构定义一个实例。NASM提供了相对简单的ISTRUC机制。定义类型
mytype的一个例子如下:
mystruc: istruc mytype
at mt_long,dd 123456
at mt_word,dw 1024
at mt_byte,db \'x\'
at mt_str,db \'hello,world\',13,10,0
iend
AT宏的功能是用前缀TIMES汇编将位置移到指定结构域的正确位置,并且定义指定的数据。前提为这些结构
成员必须与它们在结构中定义的顺序相同。如果一个结构成员定义的数据需要多行业指定,那么剩余的源码
行可以跟在AT行后,如:
at mt_str, db 123,134,145,156,167,178,189
db 190,100,0
根据个人需要,你也可以完全禁止AT行后面的代码部分:
at mt_str
db \'hello,world\'
db 13,10,0
4.7.5 ALIGN和ALIGNB:数据对齐
ALIGN和ALIGNB宏可以将数据或代码按照字,长字,段或其它边界形式对齐。(有些汇编器用EVEN调用)。
ALIGN和ALIGNB宏的语法为:
align 4;4字节对齐
align 16;16字节对齐
align 8,db 0;定义8个0,相当于NOP
align 4,resb 1;在BSS中4字节对齐
alignb 4;与上一行相等
上面的宏的第一个参数必须为2的指数幂;它也会计算当前值到所要对齐的字节需要的字节数。然后用TIMES前
缀放在它的第二个参数来执行这个对齐操作。
如果第二个参数不指定时,则ALIGN的默认值为NOP,而ALIGNB的默认值为RESB 1。所以第二个参数如果指定
时两个宏将相同。正常情况下,你可以将ALIGN放到代码和数据段中,而ALIGNB放到BSS段中,除了特殊指定
否则不需要第二个参数。
ALIGN和ALIGNB是一个相对简单的宏,它们一不进行错误检查:它不检查它们的第一个参数是否为2的指数幂或
它们的第二个参数是否为大于一字节的代码。在这种情况下,它将会继续错误的做下去。
ALIGNB(或ALIGN带两个参数RESB 1)将会用下面的定义形式:
struc mytype2
my_byte: resb 1
alignb 2
mt_word: resw 1
alignb 4
mt_long: resd 1
mt_str: resb 32
endstruc
这样可以保证结构的成员与结构的起始位置字节对齐。
一个最终警告:ALIGN和ALIGNB是工作在段的开始时,而不是在最终执行文件中地址空间。当你只需要一个4字
节对齐的操作而定义了一个16字节的对齐,是一种资源的浪费。NASM将不会为ALIGN和ALIGNB检查段是对齐。
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:22:43
--
第五章 汇编定向符
NASM,虽然避免象MASM和TASM的官方编译器但也被迫支持一些宏。下面将描述这些内容。
NASM的定向符有两种类型:用户级的用户定向符和原始级的原始定向符。典型的说,每一个定向符都有一个用户级的和原始级的。在通常情部优下,我们推荐用户使用用户级的,而宏调用原始级的。
原始级的定向符将用方括号包含;用户级的则无所谓。
在这章描述的定向符中,每个目标文件格式都能可选的支持外部的定向符,为的是在文件格式中控制得的更
容易些。这些指定格式的格式定向符将在第六章描述。
5.1 BITS:指定目标处理模式
BITS定向符指定了NASM生成的代码是16位处理模式还是32位处理模式的。这个语法分别是BITS16或BIT32。
在最许多情况下,你不必明确的用BITS。被设计用在32位系统中的aout,coff,elf和win32目标格式,会使NASM来
选择32位的默认模式。obj目标格式允许你为每一个段指定USE16或USE32,NASM将根据它的操作模式为设置。
所以人用BITS定向符将不再需要。
用BITS定向符的主要原因是在一个平坦模式的二进制文件中写一个32位;这是由于二进制的输出格式默认为16
位模式来为写成DOS和COM的程序准备,DOS的.SYS设备驱动程序 和系统引导程序。
你在一个16位的DOS程序为了用32位指令而用BITS32为指定生成模式。如果你这样做编译器将会由于生成了
一个在16位机器上运行32位代码而生成错误的代码。
当NASM在BITS16下的状态时,用32位的数据用0x66加前缀,而用0x67前缀则表示一个32位的地址。在32位的
状态时,正好是相反:32位指令不需要前缀,16位数据需要0x66而16位地址则需要0x67。
BITS有一个等价的原始格式,[BITS 16]和[BITS 32]。用户级格式是一个不能调用原始格式的宏。
5.2 SECTION或SEGMENT:改变和定义段
SECTION定向符(SEGMENT是一个等价形式)来改变输出文件的段。在一些目标文件格式,一个数的数量和名
字是可以修改;可以按照用户的需要来构造宏。如果你想切换到一个来存在段,SECTION将会给出一条错误信息
或定义一个新段。
Unix目标格式或二进制目标格式,为代码,数据和非初始化数据段提供.text,.data和.bss段名字。相对来说,目标
格式不能识别指定的目标段名字并会去掉段名字的引导阶段。
5.2.1 _SECT_宏
SECTION定向符与它的原始格式的区别为不常用在用户级的格式。原始格式[SECTION xyz],可以简单的将目标段
切换到一个给定的段。用户格式SECTION xyz,首先定义了一个单行宏_SECT_来成为一个原始宏[SECTION]定向
能执行的方式 ,所以用户定向符:
SECTION .text
将扩展成
%define _SECT_[SECTION.text]
[SECTION.text]
用户可以发现用它们自己的宏来使用可能很有用。例如,在第4.2.3节中定义的writefile宏将会用下面复杂的格式
重写:
%macro writefile 2+
[section.data]
%%str: db %2
%%endstr:
_SECT_
mov dx,%%str
mov cx,%%endstr-%%str
mov bx,%1
mov ah,0x40
int 0x21
%endmacro
上面的宏格式交一个字符串传到输出文件,首先将临时切换到文件的数据,用SECTION的原始格式不能修改
_SECT_。它将在数据段定义字串,然后执行_SECT_切换回先前执行的段。在前一个版本的宏中避免跳过数据段
为了包含一个JMP指令,在某些方面个复杂的OBJ格式模块中用户可以在不同的代码段中进行汇编。
5.3 ABSOLUTE:定义绝对标号
ABSOLUTE定向符是SECTION的一个变化格式:它对一个不直接在物理段的子代码在给定的绝对地址开始。你只
能在RESB相关指令中使用。ABSOUTE用法如下:
absolute 0x1a
kbuf_chr resw 1
kbuf_free resw 1
kbuf resw 16
这个例子描述了一个PC BIOS数据区的段,在段地址为0x40:定义了kbuf_chr为0x1a,kbuf_free为0x1c和kbuf为0x1e。
ABSOLUTE的用户级格式象SECTION一样,在它被执行时被定义为_SECT_。
STRUC和ENDSTRUC可以用ABSOLUE(也可以用_SEC_)宏定义。ABSOLUTE不一定带一个绝对的参数:它可以
带一个表达式(一个临界表达式:见第3.7节)也可以是一个段中的值。例如:一个TSR象运行时的BSS一样重
用它的setup代码:
org 100h ;它是一个.COM程序
jmp setup ;跳到后的设置代码
;TSR中的驻留部分
setup: ;现在写一个安装TSR的代码
absolute setup
runtimevar1 resw 1
runtimevar2 resd 20
tsr_end:
在安装代码上定义了一些变量,所以在安装运行完后,它将为运行TSR存储空间来重新放数据。符号\'tsr_end\'
用来计算TSR的驻留部分的总尺寸。
5.4 EXTERN:从其它模块中引入符号
EXTERN与MASM的定向符EXTRN和C语言的关键字extern相似:它被用来引用一个不在模块中定义的符号,但
在其它模块中定义 了它并且被这个模块引用。不是每一种文件格式都支持引用的变量:bin格式就不支持。
EXTERN定向符可以带很多参数,每一个参数是一个符号的名字:
extern _printf
extern _sscanf,_fscanf
有一些目标文件格式提供了EXTERN定向符的外部特性。在所有情况下,外部特性用一个后缀的冒号跟在目标
文件指定文件的符号名。例如目标格式允许你来定义一个默认段的外部基位置 为一个定向符dgroup
extern _variable:wrt dgroup
EXTERN的原始格式与它的用户格式的区分为一次只能带一个参数:多个参数的支持在预处理器级上执行。
你可以用EXTERN多次定义相同的变量 :NASM将会忽略第二个及后面的重定义。你不能用EXTERN定义象
EXTERN的变量 。
5.5 GLOBAL:导出符号到其它模块
GLOBAL是EXTERN的结束标志:如果一个模块用EXTERN定义了一个符号并引用它,为了避免连接器的错误,其
它的模块必须定义这个标号并用GLOBAL来定义。一些编译器用PUBLIC来支持这个操作。
GLOBAL定向符必须在符的定义前出现。
GLOBAL的用法与EXTERN的用法相同,但它必须用在与GLOBAL定向符相同的模块中,例如:
global _main
_main: ;一些代码
GLOBAL,象EXTERN一样允许目标格式定义一个自己的扩展用一个冒号区分,elf目标格式例如 ,可以允许你
指定全局数据项是functions或data
global hashlookup:function,hashtable:data
象EXTERN一样,GLOBAL原始格式与用户级区别为它一次只用一个参数。
5.6 COMMON:定义常用数据区
COMMON定向符用来定义一个常用的变量,常用变量可以是一个非初始化数据段的全局变量,如下
common intvar 4
与下面功能相似
global intvar
section .bss
intvar resd 1
不同的是如果多个模块定义了相同的变量,那么在连接时这些变量将合并,在所有模块对这人变量的引用 会
在内丰中一个指定的位置。
象GLOBAL和EXTERN,COMMON支持目标格式指定的扩展,例如obj格式允许常用变量为NEAR或FAR而elf格式
则允许你指定一个常用变量的对齐格式:
common commvar 4:near ;在OBJ文件中
ommon intarray 100:4 ;在ELF中:4个字节对齐
象EXTERN和GLOBAL一样,COMMON的原始格式与用户级格式的区别为它一次只能带一个参数。
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:27:43
--
第六章 输出格式
NASM是一个方便的编译器,用来 设计在任何ASNI的C支持的平台中编译,并生成物一个在不同Intelx86操作系统上
输出.对于这个原因,它可以有很多输出格式,用NASM的命令行参数-f可以选择所要的格式.对每种格式,都有它自已
的基于NASM的扩展语法,本章将进行详细说明.
在第2.1.1节中,NAM将在输入文件名的基础上选择一个默认的输出文件名和输出文件格式。这种方法是将输入
文件名的扩展名(.asm,.s或其它你用的名字)移去然后加上输出格式定义的扩展名。这个扩展将在下面给出。
6.1 bin:Flat-Form二进制格式输出
bin格式不能生成目标文件:它只产生你写的代码。这种\'\'纯二进制\'\'文件用在MS-DOS的.COM 和.SYS二进制文件
中。纯二进制文件主要用于对开发操作系统和引导程序的开发。bin支持三种标准的段名字:.text,.data和.bss。
NASM首先输出.text段的内容,然后是四字节对齐的.data段内容。如果你用SECTION定向符指定,你写的代码将
默认的放到.text段中。16位模式的bin格式(见第5.1节)为了在系统中用一个32位代码,你需要指定一个BIT32定向
符。bin没有默认的扩展格式:它只是去除源文件的扩展名,如NASM对binprog.asm汇编时将会生成二进制文件
binprog。
6.1.1 ORG:二进制格式的起点
bin格式提供了一个附加的定向符,它在第5章列出:ORG。ORG定向符的功能是在NASM将程序装入内存中时,
指定的一个起始位置。例如,下面的代码将生成长字0xc00000104:
org 0x100
dd label
label:
不象与MASM相关编译器提供的ORG定向符一样允许你在目标文件中跳转并重写你生成过的代码,NASM的ORG
定向符的只是确定初始值。它指定一个偏移值用来加在文件中所有的内部地址上;它不允许MASM版本所做的
任何欺骗行为。更多的信息见第10.1.3节。
6.1.2 bin扩展到SECTION定向符
bin输出格式允许你将SECTION(或SEGMENT)定向符来为一个需要对齐的段进行处理。这可以在段定义行后加
ALIGN来实现:
section .data align=16
这将会切换到section .data并指定它必须为16字节对齐的。ALIGN指定了段开始地址的最低多少位必须为0。
这个值必须为二个指数幂。
6.2 obj:微软的OMF目标文件格式
obj文件格式(NASM不叫的omf的原因是因为一些历史原因)是由MASM和TASM的一种输出格式,它主要用在16位
的DOS连接器生成.EXE文件。它也是OS/2的一种格式。obj提供一个默认的输出文件扩展名:.obj。
虽然obj不全是一个16位的格式,NASM还是支持32位扩展模式。通常32位obj格式在Borland的Win32编译器中使
用,来代替微软的新的win32目标格式。
obj格式不定义任何特殊的段名子:你可以定义你想要的名子。obj格式中典型的段名字CODE,DATA和BSS。
如果你在指定SEGMENT定向符前在源文件中包含代码,NASM将为你生成一个叫_NASMDEFSEG的段。当你在
obj文件中定义一个段时,NASM也会定义一个段名字,以使你可以访问段的地址。如下所示:
segment data
dvar: dw 1234
segment code
function: mov ax,data ;给出一个数据的段地址
mov ds,ax ;将它移到DS中
inc word [dvar] ;现在这个引用将操作
ret
obj格式也允许使用SEG和WRT操作符,所以你可以这样写代码:
extern foo
mov ax,seg foo ;给出foo的段
mov ds,ax
mov ax,data ;另一个段
mov es,ax
mov ax,[ds:foo] ;这将访问\'\'foo\'\'
mov [es:foo wrt data],bx ;这样引用
6.2.1 obj的SEGMENT定向符扩展
obj输出格式将定向符SEGMENT(或SECTION)进行扩展使你可以对段的属性进行更多的定义。这个操作可以通
过在段定义行的后面加扩展限定符来实现。如下:
segment code private align=16
定义一个段代码并定义它为一个原始格式的段并且必须为16字节对齐的。
下面为可用的定向符:
PRIVATE,PUBLIC,COMMON和STACK将指令段的综合特性。PRIVATE段将不能用连接器和其它段连接;
PUBLIC和STACK段将在连接时间进行连接;COMMON段将每一个段相互覆盖的连接而不是end-to-end连接。
ALIGN的用法象上面一样,指定段的开始位置最少多少位为0。给出的段值为2的指数幂可以从1-4096;实际上,
为1时只支持1,2,4,16,256到4096,如果为8时则可以是16,32,64和128到256等等。4096字节的偏移将是一个
PharLap扩展并且不被所有连接器支持。
CLASS将用来指定段的类别;这个特性将指示连接器将相同类型的段在输出文件中放一起。这个类型的名字可
以是任何词:如CLASS=CODE。
OVERLAY象CLASS一样带任何一个参数,并对overlay相兼容的连接器提供overlay信息。段可以用USE16和USE32
定义,它对在目标格式中对选择进行记录,并且当段分别是16位和32位时确保NASM能用默认的汇编。
当写OS/2目标文件时,你可以用FLAT来定义一个32位的段,用来为一个指定的FLAT的组定义一个段基址,如果组滑有定义时也定义组。obj文件格式允许段来定义一个预定义绝对段地址,虽然没有连接器用这个特性;但
NASM允许你用SEGMENT SCREEN ABSOLUTE=0XB800来定义一个段。ABSOLUTE和ALIGN关键字将是相互排斥的。NASM的默认段属性为PUBLIC,ALIGN=1,无类型,无重叠和USE16。
6.2.2 GROUP:定义段的组
obj格式也允许段是可组的,所以一个段寄存器可以对组中的所有段进行引用 。NASM则提供GROUP定向符:
segment data
;一些数据
segment bss
;一些未初始化数据
group dgroup data bss
将定义一个名字为dgroup的组包含data和bss段。象SEGMENT一样,GROUP可以将组的名字定义为一个符号,所
以你可以用var wrt data或var wrt dgroup来在一个数据段中引用一个变量,这取决于呶一个段值为在你当前的段
寄存器。如果你引用一个var,而var是在一个段组中的一个段中被定义的,那么NASM将默认从段组的开始定义
var的偏移,而不是段的。SEG变量,将返回组基址而不是段基址。
NASM允许一个段成为多个组的一部分,但你这样做将会生成一个警告信息。在多个段组中的段里定义的变量
将默认与包含这个段的第一个段组相关联。一个组不一定包含任何段:你可以用WRT引用一个在组中不包含你
引用的变量。对于OS/2,则定义了一个特殊组FLAT,没有段在它里面。
6.2.3 UPPERCASE:在输出中禁止大小写
虽然NASM本身是大小敏感的,有一些OMF连接器不是;然而它对于NASM输出单向敏感是有用的。UPPERCAT的
格式定义符将对在目标格式文件中的所有段,组和符号名在它们写入前来变成大写。在一个源码文件中,NASM
仍然是大小写区别的;如果愿意要可以将整个文件变成大写。UPPERCAT单独用一行表示,并不带参数。
6.2.4 IMPORT:引入DLL符号
如果你用NASM写一个DLL的引入库,IMPORT格式定义符定义一个从DLL引用的符号。你可以象EXTERN一样,使
用IMPORT定向符。IMPORT定向符带两个参数,用空格来分隔,它们分别为你想引用的符号名和你相引入的库
名字:
import WSAStartup wsock32.dll
第三个可选的参数将给出一个在引入库中的名字,一旦你在代码中引用它时,则和这个符号相同。如:
import asyncsel wsock32.dll WSAAsyncSelect
6.2.5 EXPORT:引出DLL符号
如果你用NASM写了一个DLL,EXPORT格式描述符定义一个可以外部引用DLL的全局符号。你可以象定义GLOBAL
一样定义EXPORT定向符。EXPORT带一个参数用来给你引出的名字命名,它将在你的源文件中定义。第二个可
选参数(用空格与第一个参数分隔)给出了这个符号的外部名:你在程序中用DLL时想要程序知道的符号名。
如果这个名字和内部名相同,你可以不用第二个参数。
更多的参数将用来定义外部符的属性。这些参数象第二个参数,用空格分隔,如果更多的参数给出,外部名也
必须指定,它甚至可以和内部名一样。下面为可用属性:
resident表示外部符号是可以在系统启动时保存的。这对通过名字引入的符号通常有用。
nodata表示引出符号是一个不用初始化数据的函数。
parm=NNN,这里NNN是一个整数,设置参数的个数,这些参数是在32位和16位段间门调用的符号。
数值的属性表示一个符应该用一个数字(序号)导出,并给出一个预计的数字。例如:
export myfunc
export myfunc TheRealMoreFormalLookingFunctionName
export myfunc myfunc 1234 ;通过序号导出
export myfunc myfunc resident parm=23 nodata
6.2.6 ..start:定义程序的入口点
OMF连接器需要一个目标文件被连接到程序中的入口点,当程序运行时将会从这里执行。如果用NASM定义目标
文件的入口点,你可以用符号..start来指定。
6.2.7 obj对EXTERN定向符的扩展
如果你定义了一个外部符号:extern foo
然后 用mov ax,foo将会给你一个从段基址(定义foo所在的段)到foo的偏移。拟你可以象下面这样访问fo
mov ax,seg foo;给出段基址的引用
mov es,ax ;将它移至ES中
mov ax,[es:foo] ;给出\'\'foo\'\'的偏移
这是一个不很广泛的用法:如果你想对一个已知的段或组中的外部符号进行访问可以用dgroup。如果DS已经包
含了dgroup,你应该写成:mov ax,[foo wrt dgroup]
然而,这时你想访问fo将很难,NASM允许你在一个变格式的中定义foextern fowrt dgroup
这种格式使NASM假装在引用foo的段基址是在一个实际的dgroup中;所以seg foo将返回dgroup并写表达式
foo将与foo wrtr dgroup等价。默认的WRT机制鹏程万里你程序中任何相关的组或段中出员的外部符号。它也可以
用在common变量中:见第6.2.8节
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:28:02
--
6.2.8 obj对COMMON定向符的扩展
obj格式允许common变量为一个near或far;NASM允许你指定一个变量的语法用法:
common nearvar 2:near ;\'\'nearvar\'\'是一个near common
common farar 10:far ;\'\'farvar\'\'是一个far
Far common变量可以大于64Kb的尺寸,OMF规定它们用一个给定的元素个数的尺寸定义。所以一个10字节的far common变量
将用10个一字节的元素定义,5个二字节,和2个五字节可一个10字节。一些OMF连接器需要这个尺寸,和变量
尺寸一样,为了在多个模块中解决common变量的定义和匹配。然而NASM认你必须指定你far common变量中
元素的指尺寸 。如下的语法:
common c_5by2 10:faq 5;二个五字节
common c_2by5 10:far 2;五个二字节
如果元素尺寸未指定则默认为1。当元素的尺寸指定时FAR关键字可是以不用,这是因为只有far common变量才
需要元素尺寸,如下定义:
common c_5by2 10:5;二个五字节
common c_2by5 10:2;五个二字节
附加的扩展为,obj中的COMMON定向符象EXTERN(见第6.2.7节)一样也支持默认的WRT操作。所以你可以写:
common foo 10:wrt dgroup
common bar 16:far 2:wrt data
common baz 24:wrt data:6
6.3 win32:微软的Win32目标文件格式
win332输出格式生成微软的Win32目标文件,主要为用象Visual C++等微软的连接器。而Borland的Win32编译器不
用这种格式,而是用obj格式代替(见第6.2节).
win32指提供的默认输出扩展名为.obj.注意虽然微软说Win32目标文件是COFF(通用目标文件格式)标准的,但微软
生成的目标文件夹与象DJGPP COFF连接器生成的不兼容性,相反也一样。这是由于PC相关重定位的语法上的
区别。为了产生DJGPP的COFF文件可以使用NASM的coff输出格式;相反,coff格式也不能生成Win32连接器产生的
目标文件格式。
6.3.1 win32对SECTION定向符的扩展
象obj格式一样,win32允许你在SECTION定向符行上指定附加的信息,如你定义段的类型与属性。段的类型和
属性在NASM自动生成时为.text,.data和.bss,但这可以通过下面的限定符修改:
code或等价text,定义了一个代码段。 这使段可以是只读和可执行的,但不可写,也表示连接器用一
个代码段来处理它。
data和bss定义了一个数据段,类似代码段。数据段是可读写的,但不能执行。data定义了一个初始化
了的数据段,而bss定义了一个非初始化的数据段。
info定义了一个信息段,不能通过连接器包含,但可以将信息传给连接器。如定义一个叫.drectve的信息
段使连接器将段的内容做为一个命令行操作:
align=,象obj一样处理段数据的对齐。你可以指定的最大值为64:Win32目标文件格式不能包含大于这个
段偏移值。如果参数不指定则默认为code段为16个字节,data段(BSS)为4字节,信息段为1字节(不对齐):
section .text code align=16
section .data data align=4
section .bss bss align=4
其它段的名字象.text一个默认对待。
6.4 coff:通用目标文件格式
coff输出用DJGPP连接器生成的COFF目标文件。coff提供了一个默认的扩展名:.o.
cof格式支持SECTION定向符的扩展同win32一样,除了不支持align限定符和info段类型。
6.5 elf:Linux ELF目标文件
elf输出格式生成ELF32(执行和连接格式)目标文件,在Linux下使用。elf提供了一个默认的扩展名:.o.
6.5.1 elf对SECTION的扩展
象obj格式一样,elf允许你在SECTION定向符后指定附加信息来对你定义的段的类型与属性。段类型与属性在
NASM自动生成时为.text,.data和.bss并能用下面的限定符修改:
alloc定义一个段在程序运行时就装入内存。noalloc定义则相反常用于一个信息段或注释段。
exec定义当程序运行时是可执行的。noexec定义不可以。
write定义 程序运行时是可以写的。onwrite定义不可写。
progbits定义一个段将显式的在目标文件中存储内容:一个原始代码可数据段,如nobits定义一个段
将不用显式的内容如BSS段。
align=象obj一样对齐数据。
NASM默认的设置为:
section .text progbits alloc exec nowrite align=16
section .data progbits alloc noexec write align=4
section .bss nobits alloc noexec write align=4
section other progbits alloc noexec nowrite align=1
(任何不为.text,.data和.bss的段都将象上面代码一样对待)
6.5.2 位置独立代码:elf指定符和WRT
ELF的指定格式包含足够的特性使位置独立代码生成(PIC),这使ELF可以弹性的共享其它 的库。这也就是说NASM
必须能在ELF目标文件中生成大量奇怪的重定位信息,如果这个源代码是用PIC写的。由于ELF不支持段基址的
引用,所以WRT操作符不能用它的正常用法;然而NASM的elf输出格式可以用不同格式的WRT用法,用PIC分隔
的重定位类型。elf定义了5种指定的符号包含PIC的重定位类型,你可以将它们放在WRT操作符的右边。它们是
..gotpc,..gotoff,..got,..plt和..sym。它们的功能 如下:
引用符号时用wrt标记全局偏移表的基址时,..gotpc将结束从当前段位置到全局偏移表的距离。
(_GOBAL_OFFSET_TABLE 是一个标准符号名用来引用GOT)。所以你需要将$$加到结果上来得到真实的GOT地址。
在你自己的段中引用一个位置时用wrt ..gotoff将会全出从GOT开始的位置到指定位置的距离,所以将地址 加到
GOT上将会给出一个你想要的真实地址 。
用wrt ..got对一个外部或全局符号的引用将会使连接器在构造GOT时包含符号的地址和给出GOT开始到入口的
距离 。所以你可以在GOT上加一个地址,从结果地址中取出再接到符号的地址上。
用wrt ..plt引用一个过程名将使连接器为符号构造一个可连接的过程表入口,并给出PLT入口的地址。你可以只
在上下相关环境中生成物一个PC相关的重定位信息(如一个CALL或JMP的目标),ELF对PLT的入口绝对引用 包含
不能重定位的类型。
用wrt ..sym对一个符号名引用时会使NASM写一个原始的重定位,而不是一个段开始的相关重定位并加上符号
偏移,它将写一个重定位记录来直接处理符号方面的问题。这个做法对于一个常见的动态连接是有必要的。
如何使用重定位类型的一个完全解释是在NASM第8.2节中的一个定共享库的例子。
6.5.3 elf对GLOBAL定向符的扩展
ELF目标文件可以包含关于一个全局符号更多信息:它们可以包含它们的尺寸和类型。这将很少进行调试,但
程序用一个共享库写时是需要的。NASM也支持对GLOBAL定向符的一些扩展,允许你指定这些特性,你可以指
定一个全局变量是一个函数或数据目标通过用冒号后缀和function或data(object与data相同),例如:
global hashlookup:function, hashtable:data
导出全局符号hashlookup是一个函数而hashtable为一个数据。你也可以用一个数学表达式指定与符号相关数据
的尺寸,如:
global hashtable:data (hashtable.end-hashtable)
hashtable:
db this,that,theother ;一些数据
.end:
这使NASM自动计算表的长度并将信息放到ELF表格中。当写一个共享库代码时定义全局符的类型和长度是有用
的,更多信息见第8.2.4节。
6.5.4 elf对COMMON定向符的扩展
ELF也允许你在common变量上指定对齐方式。这通过在变量 的名字和大不上气不接下气放一个数字(2的指数幂)
实现,中间用冒号来实现,如下面将使一个双字的数组4字节对齐。
common dwordarray 128:4
数组的整个尺寸 为128个字节,而它是4字节对齐的。
6.6 aout:Linux a.out目标格式
aout格式生成a.out目标文件,用在早期的Linux系统上.(与其它a.out目标文件在magic数字前四个字节上有区别,
有些a.out执行文件,如NetBSD的,支持位置独立代码,而Linux的则不能执行)a.out提供的默认扩展名为.o.
a.out是一种非常简单的目标格式,它不支持指定的定向符,指定的符号和SEG或WRT的用法及对任何标准定向
符的扩展。它只支持三种标准的段名字:.text,.data和.bss。
6.7 aoutb:NetBD/FreeBSD/OpenBSD a.out目标文件
aoutb格式生成a.out目标文件,这种格式用在各种自由的BSD Unix版本:NetBSD,FreeBSD和OpenBSD。对简单的
目标文件,这种目标格式和aout一样除了文件前面的4个字节magic数字不同。然而,aoutb格式象elf格式一样
支持位置独立的代码,所以你可以用它来写BSD的共享库。aoutb提供了一个默认的扩展名为.o.aoutb不支持指定
的定向符,符号,只支持三个标准的段名:.text,.data和.bss。然而它也和elf一样支持WRT的用法,提供位置独立
的代码重定位类型。更多这种属性的文件见第6.5.2节
aoutb象elf一样支持GLOBAL定向符:见第6.5.3节这方面的文档。
6.8 as86:Linux的as86目标文件
Linux的16位编译器as86有它自己的非标准的目标文件格式。虽然它的相关连接器ld86生成相似的a.out二进制输
出,但在as86和ld86间目标文件的用法的交流不是a.out本身。NASM支持这种格式,是因为它在这方面有用。
as86提供默认的扩展名为.o。as86是一种非常简单的目标格式(从NASM用户观点)。它除了支持三种标准的段名:
.text,.data和.bss外不支持其它的定向符,符号和SEG或WRT的用法。
6.9 rdf:重定位动态目标文件格式
rdf输出格式生成RDOFF目标文件。RDOFF(重定位动态目标文件格式)是一种home-grown目标格式,为了NASM自身
设计并影响编译器内部结构的文件格式。RDOFF不用在出名的操作系统上。哪些写它们自己操作系统的人用这
种RDOFF做为它们的目标格式化,它主要设计用来单独包含那些少量的官方文件头。Unix NASM档案和DOS档案
包含源代码和一个rdoff子目录用来设置RDOFF:一个RDF连接器,一个RDF静态库,一个RDF文件查看器及在
Linux下的装入和执行RDF的程序。rdf只支持标准的段名:.text,.data和.bss。
6.9.1 所需库:LIBRARY定向符
RDOFF包含一个机制为目标文件决定将一个给出库连接到模块上,在装入时或运行时。通常LIBRARY定向符带
一个参数做为模块的名字:library mylib.rdl
6.10 dbg:调试格式
dbg输出格式不在NASM的默认匹配中构造。如果你要从源码文件中构造自己的NASM执行文件,你可以在
outform.h或命令行定义OF_DBG来包含dbg输出格式。dbg格式并不输出目标文件;而是输出一个文本文件,它包
含了一个在NASM主程序和输出听到端模块转换的完整列表。它主要是为了帮助那些想写自己输出驱动程序的人
,在主程序根据不同需要生成驱动程序时能有一个清楚的思路。dbg格式的用法如下:
nasm -f dbg filename.asm
这将生成一个错误诊断文件:filename.dbg。但这对哪些不同目标格式的文件将不会工作太好,因为每一种格式都
定义了自己的宏(通常用用户级的定向符),而这些宏在dbg格式中可能没有定义。这里可以运行两次NASM,以用
一些私有的目标格式进行预处理:
nasm -e -f rdf -o rdfprog.i rdfprog.asm
nasm -a -f dbg rdfprog.i
这将预处理rdfprog.asm到rdfprogi里,保存选中的rdf目标格式为了确保RDF指定定向符可以正确的转成原始模
式。这种预处理的源码将用dbg格式生成最终的错误输出。
这种情况对于obj格式不适用,因为obj的SEGMENT和GROUP定向符号地于用符号定义段和组是有影响的;dbg则
不会这样做,所以程序将不编译。如果你想从目标源文件中得到一条dbg信息,则你必须定义你自己的符号
(如用EXTERN)。dbg可以接受任何段的名字,定向符,并将它们记录到它的输文件中?
--------------------------------------------------------------------------------
-- 作者:航天奇侠
-- 发布时间:2004-7-1 11:31:49
--
第七章 写16位代码(DOS,Windows3/3.1)
这章主要介绍写MS-DOS或Windows3.x下运行的16位代码是遇到的方法.主要说明如何将程序连接成.EXE或.COM
文件,如何写.SYS设备驱动程序,如何提供汇编语言和16位C编译器和Borland Pascal之间的接口.
7.1 生成.EXE文件
任何在DOS下运行的大型程序都在做为一个.EXE文件来构造:只有.EXE文件需要跨过64K段的内部结构.Windows
程序由于不支持.COM格式也必须构造成.EXE文件.通常你生成.EXE文件时用obj输出格式生成一个或多个obj文件.
然后用连接器将它们连到一起.然而,NASM也可以生成用于bin格式的简单的DOS.EXE文件(用DB和DW来构造.EXE
文件头).和一个支持这个的宏压缩包.感谢Yann Guidon对这个代码的描述.
NASM也支持本了的.EXE文件,做为将来版本的另一种输出格式.
7.1.1 用obj格式生成的.EXE文件
这一段描述了通过.OBJ文件连接成.EXE文件的方法.16位程序语言是用一个适合的的连接器压缩的;如果把你没有
话,这有一个叫VAL的自由连接器,来自x2ftp.oulu.,fi的LZH文档格式.一个LZH文档可以在ftp.simtel.net找到.这还有
一个叫FREELINK的\'free\'连接器(虽然它不带源码),可以从www.pcorner.com上找到,另外还有DJ Delori写的djlink可
以在www.delorie.com上找到.当将几个.OBJ文件连接进一个.EXE文件中时,你应该保证这些obj文件中的一个为程序
定义了开始.(用..start指定符定义,见第6.2.6节)如果没有模块定义起始点时,则连接器将不知道输出文件头中入口
给定的值;如果定义过多的起始点,连接器也不知道会用哪一个.这里给出呈个用NASM源码编译成.OBJ的文件和连
接器连接成.EXE文件的例子.这解释了定义一个堆栈,初始化段寄存器,定义开始点的基本规则,这个文件也包含在
NASM文档中的test子目录中,名字为objexe.asm:
segment code
..start: mov ax,data
mov ds,ax
mov ss,ax
mov sp,stacktop
初始化部分将DS设为数据段,并将SS和SP初始为提供堆栈的顶部.注意将一个指定移入SS时,将禁止中断,所以在
SS和SP间取数时和没有堆栈执行时将不会生成堆栈.指定符..start定义了这个代码的开始点,这也是说这个点将为
结果执行文件的输入点.
mov dx,hello
mov ah,09
int 0x21
上面的主程序:将一个欢迎信息指针将入DS:DX(hello是一个隐式相关的段数据,将在设置代码中送入DS中,所以指
针是有效的)并调用DOS的显示字串命令.
mov ax,0x4c00
int 0x21
这将用另一个DOS系统调用终止程序.
segment data
hell db \'hello,world\',13,10,\'$\'
数据段包含了我们想要显示的字串.
segment stack stack
resb 64
stacktop:
上面的代码定义了一个包含64个字节非初始化的堆栈段,然后将指针移到它的顶端.堆栈段定向符定义了一个名
字为stack的堆栈,类型为STACK.对于正确运行程序后者不需要,但如果你的程序没有STACK类型的段则连接器将发
出一个警告信息.将上面的文件编译成.obj文件并连接成一个有效的.exe文件,运行这个文件时会显示\'hello,world\'
信息并退出.
7.1.2用bin格式生成.exe文件
.exe文件格式的简单在于:它可以通过写一个纯的二进制程序并粘在一个32字节头后面来构造一个.exe文件.这个
头可以简单的用NASM的DB,DW命令来生成,所以你可以用一个bin的输出格式来直接生成.exe文件.
在NASM的文档中的misc目录下有一个叫exebin.mac的宏文件.它定义了三个宏:EXE_begin,EXE_stack和EXE_end.
为了用上面的方法生成一个.EXE文件,你可以在你的源码文件中的开始处用%include包含一文件exebin.mac.然后
你就可以用