📄 nasm.txt
字号:
foo equ 1
bar dw 2
然后有两行的代码:
mov ax,foo
mov ax,bar
尽管它们有看上去完全相同的语法,但却产生了完全不同的操作码
NASM为了避免这种令人讨厌的情况,拥有一个相当简单的内存引用语未能。规则
是任何对内存中内容的存取操作必须要在地址上加上方括号。但任何对地址值
的操作不需要。所以,形如'mov ax,foo'的指令总是代表一个编译时常数,不管它
是一个 'EQU'或一个变量的地址;如果要取变量'bar'的内容,你必须与
'mov ax,[bar]'。
这也意味着NASM不需要MASM的'OFFSET'关键字,因为MASM的代码'mov ax,offset bar'
同NASM的语法'mov ax,bar'是完全等效的。如果你希望让大量的MASM代码能够被
NASM汇编通过,你可以编写'%idefine offset'让预处理器把'OFFSET'处理成一个无
操作符。
这个问题在'a86'中就更混乱了。
NASM因为关注简洁性,同样不支持MASM和它的衍生产品支持的的混合语法,比如像
:'mov ax, table[bx]',这里,一个中括号外的部分加上括号内的一个部分引用一个
内存地址,上面代码的正确语法是:'mov ax,[table+bx] 。同样,'mov ax,es:[di]'
也是错误的,正确的应该是'mov ax,[es:di]'。
2.2.3 NASM不存储变量的类型。
NASM被设计成不记住你声明的变量的类型。然而,MASM在看到'var dw 0'时会记住
类型,然后就可以隐式地合用'mov var, 2'给变量赋值。NASM不会记住关于变量
'var'的任何东西,除了它的位置,所以你必须显式地写上代码'mov word [var],2'。
因为这个原因,NASM不支持'LODS','MOVS','STOS','SCANS','CMPS','INS',或'OUTS'
指令,仅仅支持形如'LODSB','MOVSW',和'SCANSD'之灰的指令。它们都显式地指定
被处理的字符串的尺寸。
2.2.4 NASM不会 `ASSUME'
作为NASM简洁性的一部分,它同样不支持'ASSUME'操作符。NASM不会记住你往段寄
存器里放了什么值,也不会自动产生段替换前缀。
2.2.5 NASM不支持内存模型。
NASM同样不含有任何操作符来支持不同的16位内存模型。程序员需要自己跟踪那
些函数需要far call,哪些需要near call。并需要确定放置正确的'RET'指令('RETN'
或'RETF'; NASM接受'RET'作为'RETN'的另一种形式);另外程序员需要在调用外部函
数时在需要的编写CALL FAR指令,并必须跟踪哪些外部变量定义是far,哪些是near。
2.2.6 浮点处理上的不同。
NASM使用跟MASM不同的浮点寄存器名:MASM叫它们'ST(0)','ST(1)'等,而'a86'叫它们
'0','1'等,NASM则叫它们'st0','st1'等。
在版本0.96上,NASM现在以跟MASM兼容汇编器同样的方式处理'nowait'形式的指令,
0.95以及更早的版本上的不同的处理方式主要是因为作者的误解。
2.2.7 其他不同。
由于历史的原因,NASM把MASM兼容汇编器的'TBYTE'写成'TWORD'。
NASM以跟MASM不同的一种方式声明未初始化的内存。MASM的程序员必须使用
'stack db 64 dup (?)', NASM需要这样写:'stack resb 64',读作"保留64字节"。为了
保持可移植性,NASM把'?'看作是符号名称中的一个有效的字符,所以你可以编写这样
的代码'? equ 0', 然后写'dw ?'可以做一些有用的事情。'DUP'还是一个不被支持的语法。
另外,宏与操作符的工作方式也与MASM完全不同,可以到参阅第4,第5章。
第三章 NASM语言
----------------
3.1 NASM源程序行的组成。
就像很多其他的汇编器,每一行NASM源代码包含(除非它是一个宏,一个预处理操作
符,或一个汇编器操作符,参况第4,5章)下面四个部分的全部或某几个部分:
label: instruction operands ; comment
通常,这些域的大部分是可选的;label,instruction,comment存在或不存在都是允
许的。当然,operands域会因为instruction域的要求而必需存或必须不存在。
NASM使用反斜线(\)作为续行符;如果一个以一个反斜线结束,那第二行会被认为
是前面一行的一部分。
NASM对于一行中的空格符并没有严格的限制:labels可以在它们的前面有空格,或
其他任何东西。label后面的冒号同样也是可选的。(注意到,这意味着如果你想
要写一行'lodsb',但却错误地写成了'lodab',这仍将是有效的一行,但这一行不做
任何事情,只是定义了一个label。运行NASM时带上命令行选项'-w+orphan-labels'
会让NASM在你定义了一个不以冒号结尾的label时警告你。
labels中的有效的字符是字母,数字,'-','$','#','@','~','.'和'?'。但只有字母
'.',(具有特殊含义,参阅3.9),'_'和'?'可以作为标识符的开头。一个标识符还可
以加上一个'$'前缀,以表明它被作为一个标识符而不是保留字来处理。这样的话,
如果你想到链接进来的其他模块中定义了一个符号叫'eax',你可以用'$eax'在
NASM代码中引用它,以和寄存器的符号区分开。
instruction域可以包含任何机器指令:Pentium和P6指令,FPU指令,MMX指令还有甚
至没有公开的指令也会被支持。这些指令可以加上前缀'LOCK','REP','REPE/REPZ'
或'REPNE'/'REPNZ',通常,支持显示的地址尺寸和操作数尺寸前缀'A16','A32',
'O16'和'O32'。关于使用它们的一个例子在第九章给出。你也可以使用段寄存器
名作为指令前缀: 代码'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'的语法风格,在这种风格中,寄存器名
前必须加上一个'%'符号),或者它们可以是有效的地址(参阅3.3),常数(3.4),或
表达式。
对于浮点指令,NASM接受各种语法:你可以使用MASM支持的双操作数形式,或者你
可以使用NASM的在大多数情况下全用的单操作数形式。支持的所以指令的语法
细节可以参阅附录B。比如,你可以写:
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机器指令,但还是被用在了instruction域中的指
令,因为使用它们可以带来很大的方便。当前的伪指令有'DB','DW','DD','DQ'和
‘DT’,它们对应的未初始化指令是'RESB','RESW','RESD','RESQ'和'REST','INCBIN'
命令,'EQU'命令和'TIEMS'前缀。
3.2.1 `DB'一类的伪指令: 声明已初始化的数据。
在NASM中,`DB', `DW', `DD', `DQ'和`DT'经常被用来在输出文件中声明已初始化
的数据,你可以多种方式使用它们:
db 0x55 ; just the byte 0x55
db 0x55,0x56,0x57 ; three bytes in succession
db 'a',0x55 ; character constants are OK
db 'hello',13,10,'$' ; so are string constants
dw 0x1234 ; 0x34 0x12
dw 'a' ; 0x41 0x00 (it's just a number)
dw 'ab' ; 0x41 0x42 (character constant)
dw 'abc' ; 0x41 0x42 0x43 0x00 (string)
dd 0x12345678 ; 0x78 0x56 0x34 0x12
dd 1.234567e20 ; floating-point constant
dq 1.234567e20 ; double-precision float
dt 1.234567e20 ; extended-precision float
'DQ'和'DT'不接受数值常数或字符串常数作为操作数。
3.2.2 `RESB'类的伪指令: 声明未初始化的数据。
`RESB', `RESW', `RESD', `RESQ' and `REST'被设计用在模块的BSS段中:它们声明
未初始化的存储空间。每一个带有单个操作数,用来表明字节数,字数,或双字数
或其他的需要保留单位。就像在2.2.7中所描述的,NASM不支持MASM/TASM的扣留未
初始化空间的语法'DW ?'或类似的东西:现在我们所描述的正是NASM自己的方式。
'RESB'类伪指令的操作数是有严格的语法的,参阅3.8。
比如:
buffer: resb 64 ; reserve 64 bytes
wordvar: resw 1 ; reserve a word
realarray resq 10 ; array of ten reals
3.2.3 `INCBIN':包含其他二进制文件。
'INCBIN'是从老的Amiga汇编器DevPac中借过来的:它将一个二进制文件逐字逐句地
包含到输出文件中。这能很方便地在一个游戏可执行文件中包含中图像或声音数
据。它可以以下三种形式的任何一种使用:
incbin "file.dat" ; include the whole file
incbin "file.dat",1024 ; skip the first 1024 bytes
incbin "file.dat",1024,512 ; skip the first 1024, and
; actually include at most 512
3.2.4 `EQU': 定义常数。
'EQU'定义一个符号,代表一个常量值:当使用'EQU'时,源文件行上必须包含一个label。
'EQU'的行为就是把给出的label的名字定义成它的操作数(唯一)的值。定义是不可更
改的,比如:
message db 'hello, world'
msglen equ $-message
把'msglen'定义成了常量12。'msglen'不能再被重定义。这也不是一个预自理定义:
'msglen'的值只被计算一次,计算中使用到了'$'(参阅3.5)在此时的含义。注意
‘EQU’的操作数也是一个严格语法的表达式。(参阅3.8)
3.2.5 `TIMES': 重复指令或数据。
前缀'TIMES'导致指令被汇编多次。它在某种程序上是NASM的与MASM兼容汇编器的
'DUP'语法的等价物。你可以这样写:
zerobuf: times 64 db 0
或类似的东西,但'TEIMES'的能力远不止于此。'TIMES'的参数不仅仅是一个数值常
数,还有数值表达式,所以你可以这样做:
buffer: db 'hello, world'
times 64-$+buffer db ' '
它可以把'buffer'的长度精确地定义为64字节,’TIMES‘可以被用在一般地指令上,
所以你可像这要编写不展开的循环:
times 100 movsb
注意在'times 100 resb 1'跟'resb 100'之间并没有显著的区别,除了后者在汇编
时会快上一百倍。
就像'EQU','RESB'它们一样, 'TIMES'的操作数也是严格语法的表达式。(见3.8)
注意'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] ; assembles as [ebx*4+ebx]
mov eax,[label1*2-label2] ; ie [label1+(label1-label2)]
有些形式的有效地址在汇编后具有多种形式;在大多数情况下,NASM会自动产生
最小化的形式。比如,32位的有效地址'[eax*2+0]'和'[eax+eax]'在汇编后具有
完全不同的形式,NASM通常只会生成后者,因为前者会为0偏移多开辟4个字节。
NASM具有一种隐含的机制,它会对'[eax+ebx]'和'[ebx+eax]'产生不同的操作码;
通常,这是很有用的,因为'[esi+ebp]'和'[ebp+esi]'具有不同的缺省段寄存器。
尽管如此,你也可以使用关键字'BYTE','WORD','DWORD'和'NOSPLIT'强制NASM产
生特定形式的有效地址。如果你想让'[eax+3]'被汇编成具有一个double-word的
偏移域,而不是由NASM缺省产生一个字节的偏移。你可以使用'[dword eax+3]',
同样,你可以强制NASM为一个第一遍汇编时没有看见的小值产生一个一字节的偏
移(像这样的例子,可以参阅3.8)。比如:'[byte eax+offset]'。有一种特殊情
况,‘[byte eax]'会被汇编成'[eax+0]'。带有一个字节的0偏移。而'[dword
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -