NASM网际编译器手册(五)

第四章 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会构造一个的表格。前缀符号..@是一种用来区分本地标号的
格式机制,这方面的内容见第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

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检查段是对齐。

posted on 2009-01-06 16:46  cnlmjer  阅读(383)  评论(0编辑  收藏  举报