📄 nasm.txt
字号:
eax]'会带一个double-word的0偏移。而常用的形式,'[eax]'则不会带有偏移域。
当你希望在16位的代码中存取32位段中的数据时,上面所描述的形式是非常有用
的。关于这方面的更多信息,请参阅9.2。实际上,如果你要存取一个在已知偏
移地址处的数据,而这个地址又大于16位值,如果你不指定一个dword偏移,
NASM会让高位上的偏移值丢失。
类似的,NASM会把'[eax*2]'分裂成'[eax+eax]' ,因为这样可以让偏移域不存在
以此节省空间;实际上,它也把'[eax*2+offset]'分成'[eax+eax+offset]',你
可以使用‘NOSPLIT'关键字改变这种行为:`[nosplit eax*2]'会强制
`[eax*2+0]'按字面意思被处理。
3.4 常数
NASM能理解四种不同类型的常数:数值,字符,字符串和浮点数。
3.4.1 数值常数。
一个数值常数就只是一个数值而已。NASM允许你以多种方式指定数值使用的
进制,你可以以后缀'H','Q','B'来指定十六进制数,八进制数和二进制数,
或者你可以用C风格的前缀'0x'表示十六进制数,或者以Borland Pascal风
格的前缀'$'来表示十六进制数,注意,'$'前缀在标识符中具有双重职责
(参阅3.1),所以一个以'$'作前缀的十六进制数值必须在'$'后紧跟数字,而
不是字符。
请看一些例子:
mov ax,100 ; decimal
mov ax,0a2h ; hex
mov ax,$0a2 ; hex again: the 0 is required
mov ax,0xa2 ; hex yet again
mov ax,777q ; octal
mov ax,10010011b ; binary
3.4.2 字符型常数。
一个字符常数最多由包含在双引号或单引号中的四个字符组成。引号的类型
与使用跟NASM其它地方没什么区别,但有一点,单引号中允许有双引号出现。
一个具有多个字符的字符常数会被little-endian order,如果你编写:
mov eax,'abcd'
产生的常数不会是`0x61626364',而是`0x64636261',所以你把常数存入内存
的话,它会读成'abcd'而不是'dcba'。这也是奔腾的'CPUID'指令理解的字符常
数形式(参阅B.4.34)
3.4.3 字符串常数。
字符串常数一般只被一些伪操作指令接受,比如'DB'类,还有'INCBIN'。
一个字符串常数和字符常数看上去很相像,但会长一些。它被处理成最大长
度的字符常数之间的连接。所以,以下两个语句是等价的:
db 'hello' ; string constant
db 'h','e','l','l','o' ; equivalent character constants
还有,下面的也是等价的:
dd 'ninechars' ; doubleword string constant
dd 'nine','char','s' ; becomes three doublewords
db 'ninechars',0,0,0 ; and really looks like this
注意,如果作为'db'的操作数,类似'ab'的常数会被处理成字符串常量,因
为它作为字符常数的话,还不够短,因为,如果不这样,那'db 'ab'会跟
'db 'a''具有同样的效果,那是很愚蠢的。同样的,三字符或四字符常数会
在作为'dw'的操作数时被处理成字符串。
3.4.4 浮点常量
浮点常量只在作为'DD','DQ','DT'的操作数时被接受。它们以传统的形式表
达:数值,然后一个句点,然后是可选的更多的数值,然后是选项'E'跟上
一个指数。句点是强制必须有的,这样,NASM就可以把它们跟'dd 1'区分开,
它只是声明一个整型常数,而'dd 1.0'声明一个浮点型常数。
一些例子:
dd 1.2 ; an easy one
dq 1.e10 ; 10,000,000,000
dq 1.e+10 ; synonymous with 1.e10
dq 1.e-10 ; 0.000 000 000 1
dt 3.141592653589793238462 ; pi
NASM不能在编译时求浮点常数的值。这是因为NASM被设计为可移植的,尽管它
常产生x86处理器上的代码,汇编器本身却可以和ANSI C编译器一起运行在任
何系统上。所以,汇编器不能保证系统上总存在一个能处理Intel浮点数的浮
点单元。所以,NASM为了能够处理浮点运算,它必须含有它自己的一套完整
的浮点处理例程,它大大增加了汇编器的大小,却获得了并不多的好处。
3.5 表达式
NASM中的表达式语法跟C里的是非常相似的。
NASM不能确定编译时在计算表达式时的整型数尺寸:因为NASM可以在64位系
统上非常好的编译和运行,不要假设表达式总是在32位的寄存器中被计算的,
所以要慎重地对待整型数溢出的情况。它并不总能正常的工作。NASM唯一能
够保证的是:你至少拥有32位长度。
NASM在表达式中支持两个特殊的记号,即'$'和'$$',它们允许引用当前指令
的地址。'$'计算得到它本身所在源代码行的开始处的地址;所以你可以简
单地写这样的代码'jmp $'来表示无限循环。'$$'计算当前段开始处的地址,
所以你可以通过($-$$)找出你当前在段内的偏移。
NASM提供的运算符以运算优先级为序列举如下:
3.5.1 `|': 位或运算符。
运算符'|'给出一个位级的或运算,所执行的操作与机器指令'or'是完全相
同的。位或是NASM中优先级最低的运算符。
3.5.2 `^': 位异或运算符。
`^' 提供位异或操作。
3.5.3 `&': 位与运算符。
`&' 提供位与运算。
3.5.4 `<<' and `>>': 位移运算符。
`<<' 提供位左移, 跟C中的实现一样,所以'5<<3'相当于把5乘上8。'>>'提
供位右移。在NASM中,这样的位移总是无符号的,所以位移后,左侧总是以
零填充,并不会有符号扩展。
3.5.5 `+' and `-': 加与减运算符。
'+'与'-'运算符提供完整的普通加减法功能。
3.5.6 `*', `/', `//', `%'和`%%': 乘除法运算符。
'*'是乘法运算符。'/'和'//'都是除法运算符,'/'是无符号除,'//'是带
符号除。同样的,'%'和'%%'提供无符号与带符号的模运算。
同ANSI C一样,NASM不保证对带符号模操作执行的操作的有效性。
因为'%'符号也被宏预处理器使用,你必须保证不管是带符号还是无符号的
模操作符都必须跟有空格。
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'中载入一个指向符号'symbol'的有效指针。
而事情往往可能比这还要复杂些:因为16位的段与组是可以相互重叠的,
你通常可能需要通过不同的段基址,而不是首选的段基址来引用一个符
号,NASM可以让你这样做,通过使用'WRT'关键字,你可以这样写:
mov ax,weird_seg ; weird_seg is a segment base
mov es,ax
mov bx,symbol wrt weird_seg
会在'ES:BX'中载入一个不同的,但功能上却是相同的指向'symbol'的指
针。
通过使用'call segment:offset',NASM提供fall call(段内)和jump,这里
'segment'和'offset'都以立即数的形式出现。所以要调用一个远过程,你
可以如下编写代码:
call (seg procedure):procedure
call weird_seg:(procedure wrt weird_seg)
(上面的圆括号只是为了说明方便,实际使用中并不需要)
NASM支持形如'call far procedure'的语法,跟上面第一句是等价的。'jmp'
的工作方式跟'call'在这里完全相同。
在数据段中要声明一个指向数据元素的远指针,可以象下面这样写:
dw symbol, seg symbol
NASM没有提供更便利的写法,但你可以用宏自己建造一个。
3.7 `STRICT': 约束优化。
当在汇编时把优化器打开到2或更高级的时候(参阅2.1.15)。NASM会使用
尺寸约束('BYTE','WORD','DWORD','QWORD',或'TWORD'),会给它们尽可
能小的尺寸。关键字'STRICT'用来制约这种优化,强制一个特定的操作
数为一个特定的尺寸。比如,当优化器打开,并在'BITS 16'模式下:
push dword 33
会被编码成 `66 6A 21',而
push strict dword 33
会被编码成六个字节,带有一个完整的双字立即数`66 68 21 00 00 00'.
而当优化器关闭时,不管'STRICT'有没有使用,都会产生相同的代码。
3.8 临界表达式。
NASM的一个限制是它是一个两遍的汇编器;不像TASM和其它汇编器,它总是
只做两遍汇编。所以它就不能处理那些非常复杂的需要三遍甚至更多遍汇编
的源代码。
第一遍汇编是用于确定所有的代码与数据的尺寸大小,这样的话,在第二遍
产生代码的时候,就可以知道代码引用的所有符号地址。所以,有一件事
NASM不能处理,那就是一段代码的尺寸依赖于另一个符号值,而这个符号又
在这段代码的后面被声明。比如:
times (label-$) db 0
label: db 'Where am I?'
'TIMES'的参数本来是可以合法得进行计算的,但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'被定义成等于
'symbols2',而这时,NASM还没有看到symbol2。所以在第二遍的时候,当它遇
上'mov ax,symbol1',它不能为它产生正确的代码,因为它还没有知道'symbol1'
的值。当到达下一行的时候,它又看到了'EQU',这时它可以确定symbol1的值
了,但这时已经太晚了。
NASM为了避免此类问题,把'EQU'右侧的表达式也定义为临界表达式,所以,
'symbol1'的定义在第一遍的时候就会被拒绝。
这里还有一个关于前向引用的问题:考虑下面的代码段:
mov eax,[ebx+offset]
offset equ 10
NASM在第一遍的时候,必须在不知道'offset'值的情况下计算指令
'mov eax,[ebx+offset]'的尺寸大小。它没有办法知道'offset'足够小,足以
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -