NASM怎样c 定义数组局部数组

用nasm汇编做引导程序
用nasm汇编做引导程序。
题目要求:做一个引导程序,开机后显示个问题,并等待用户输入,输入完毕后关机。
我用汇编写了上述程序,并把它加入软盘的引导区。开机后计算机会自动加载这段程序并运行。
引导区简介:
操作系统的“开端”一般被认为是引导程序),它是操作系统程序中最早被计算机硬件系统加载入内存并执行的部分,引导程序一般规定长度为个字节,就是从这字节开始,操作系统被一步步装载入计算机内存,进而最终控制整台计算机。当计算机加电启后,首先转去执行中的程序进行硬件自检,如果自检成功,则开始尝试在可引导介质中依次寻找引导程序,可引导介质就是我们计算机的软盘驱动器,光盘驱动器以及硬盘等存储设备,搜索的顺序是按照里设置的引导顺序进行的。
对于软盘来说,计算机会检查软盘的面磁道扇区(被称为引导扇区),由于每个扇区字节,因此刚好容纳下引导程序。如果该扇区最后两个字节依次是和,那么就表明该扇区存储的是一段引导程序,进而由程序将这个字节依次复制到开始的内存单元,然后计算机会跳转到地址处执行。
启动程序代码开始
检查回车键
跳至执行命令
把这段程序保存为文件,如
我们使用对这段代码进行编译。使用需要配置系统环境变量。鼠标右键点击我的电脑属性高级选项环境变量,在中加入所在路径。这样我们就完成了环境变量的配置。打开,进入源程序所在目录,输入,回车。然后,我们可以发现在源程序同目录出现了一个字节的新文件。
我们用二进制编辑工具打开myboot文件(推荐使用ultraedit或winhex),并同时打开bochs虚拟机A盘的镜像文件(我的是boot.img)。把boot.img的前512字节删除,然后把myboot加入boot.img的前端。再次启动bochs。我们就大功告成了。
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!请问nasm中[SECTION .gdt]的意思
[问题点数:50分,结帖人stuman]
本版专家分:20
结帖率 98.67%
CSDN今日推荐
本版专家分:69416
2012年11月 其他开发语言大版内专家分月排行榜第一2011年5月 其他开发语言大版内专家分月排行榜第一2010年6月 其他开发语言大版内专家分月排行榜第一2010年5月 其他开发语言大版内专家分月排行榜第一2010年4月 其他开发语言大版内专家分月排行榜第一2008年1月 其他开发语言大版内专家分月排行榜第一2007年12月 其他开发语言大版内专家分月排行榜第一2007年11月 其他开发语言大版内专家分月排行榜第一2007年4月 其他开发语言大版内专家分月排行榜第一2006年12月 其他开发语言大版内专家分月排行榜第一2006年11月 其他开发语言大版内专家分月排行榜第一2006年10月 其他开发语言大版内专家分月排行榜第一2006年7月 其他开发语言大版内专家分月排行榜第一2006年6月 其他开发语言大版内专家分月排行榜第一2005年11月 其他开发语言大版内专家分月排行榜第一2005年10月 其他开发语言大版内专家分月排行榜第一2005年9月 其他开发语言大版内专家分月排行榜第一2005年6月 其他开发语言大版内专家分月排行榜第一2005年5月 其他开发语言大版内专家分月排行榜第一2005年3月 其他开发语言大版内专家分月排行榜第一2005年2月 其他开发语言大版内专家分月排行榜第一2004年10月 其他开发语言大版内专家分月排行榜第一2004年9月 其他开发语言大版内专家分月排行榜第一2005年2月 硬件使用大版内专家分月排行榜第一2004年8月 硬件/嵌入开发大版内专家分月排行榜第一
2012年10月 其他开发语言大版内专家分月排行榜第二2011年7月 其他开发语言大版内专家分月排行榜第二2010年3月 其他开发语言大版内专家分月排行榜第二2007年10月 其他开发语言大版内专家分月排行榜第二2007年9月 其他开发语言大版内专家分月排行榜第二2005年3月 Windows专区大版内专家分月排行榜第二2005年2月 Windows专区大版内专家分月排行榜第二2005年6月 扩充话题大版内专家分月排行榜第二2006年9月 其他开发语言大版内专家分月排行榜第二2006年5月 其他开发语言大版内专家分月排行榜第二2006年3月 其他开发语言大版内专家分月排行榜第二2006年2月 其他开发语言大版内专家分月排行榜第二2005年12月 其他开发语言大版内专家分月排行榜第二2005年4月 其他开发语言大版内专家分月排行榜第二2004年11月 其他开发语言大版内专家分月排行榜第二2005年3月 硬件使用大版内专家分月排行榜第二
2011年11月 其他开发语言大版内专家分月排行榜第三2011年8月 其他开发语言大版内专家分月排行榜第三2008年10月 其他开发语言大版内专家分月排行榜第三2004年9月 硬件/嵌入开发大版内专家分月排行榜第三
本版专家分:0
本版专家分:80
本版专家分:881
本版专家分:30
匿名用户不能发表回复!|
CSDN今日推荐NASM 纯汇编打造简单中文操作系统(6 vesa.inc 显卡绘图)
六 vesa.inc 显卡绘图
[ORG 0x0000]
%macro GetXYAddr 2
XOR EAX,EAX
XOR EBX,EBX
XOR EDX,EDX
MOV AX,%2 ;第2个参数Ypos
MOV BX,%1 ;第1个参数Xpos
;坐标在显存中的计算方式Y*(屏幕宽度)+X/2
MOV CX,[SYSVAR+8]
MOV EDI,EDX ;进位
SHL EDI,CL
OR EDI,EAX ;积
ADD EDI,EBX
MOV EAX,EDI
;------------------------------------------------------------------------------------------------
;计算色调5:5:5到5:6:5,以下是RGB颜色存放结构
;R DW 0;红色一个字
;G DW 0;绿色一个字
;B DW 0;蓝色一个字
;参数是2个字节
;-----------------------------------------------------------------------------------------------
PUSH EBP ;保存IP返回内容
MOV EBP,ESP
XOR EBX,EBX
XOR EDX,EDX
XOR ECX,ECX
MOV AX,[ESP+0x0C] ;R第一个参数
DIV CL ;计算R/32
MOV BL,AH ;取莫(取余),把颜色值R放到EBX
MOV CL,6 ;每次移动6位
SHL BX,CL ;移动6位
MOV AX,[ESP+0x0A] ;G
DIV CL ;计算G
OR BL,AH ;取莫(取余),把颜色值G放到EBX
MOV CL,5 ;动5位
SHL BX,CL ;数据向左移动一个字节=8位
MOV AX,[ESP+0x08] ;B
DIV CL ;计算B
OR BL,AH ;取莫(取余),把颜色值B放到EBX
MOV AX,BX ;返回计算好的结果
MOV ESP,EBP
;----------------------------------------------------------------------------------------------
;push WORD 是否使用双缓冲1使用0不使用
;push WORD color
;PUSH WORD y
;PUSH WORD x
;------------------------------------------------------------------------------------------------
MOV EBP,ESP
XOR EAX,EAX
XOR EBX,EBX
XOR EDX,EDX
XOR ECX,ECX
XOR EDI,EDI
MOV AX,[ESP+0x0A] ;第三个参数Ypos
MOV BX,[ESP+0x08] ;第二个参数Xpos
;坐标在显存中的计算方式Y*(屏幕宽度)+X/2
MOV CX,[SYSVAR+8]
MOV EDI,EDX ;进位
SHL EDI,CL
OR EDI,EAX ;积
ADD EDI,EBX
;SHR EDI,CL ;edi 除 8
MOV EAX,EDI
MOV BX,[ESP+0x0C] ;第一个参数颜色
CMP [ESP+0x0E],WORD 1
MOV EDI,DWORD [DS:SYSVAR+0] ;SYSVAR+0 = 0xe0000000
MOV [DS:EDI+EAX],WORD BX
;先画到缓冲区然后复制到显示映射地址mmx以后再说
;以4字节方式移动
MOV EDI,DWORD VBE_FILP ;获得缓冲区地址
MOV [DS:EDI+EAX],WORD BX ;把计算好的颜色放到指定的坐标
;-------------------------------------------------------------------------------------
;获的指定像素点颜色
;PUSH WORD 1从缓冲区取0从显示映射内存取
;PUSH WORD y
;PUSH WORD x
;------------------------------------------------------------------------------------------------
MOV EBP,ESP
XOR EAX,EAX
XOR EBX,EBX
XOR EDX,EDX
XOR ECX,ECX
XOR EDI,EDI
MOV AX,[ESP+0x0A] ;第2个参数Ypos
MOV BX,[ESP+0x08] ;第1个参数Xpos
;坐标在显存中的计算方式Y*(屏幕宽度)+X/2
MOV CX,[SYSVAR+8]
MOV EDI,EDX ;进位
SHL EDI,CL
OR EDI,EAX ;积
ADD EDI,EBX
;SHR EDI,CL ;edi 除 8
MOV EAX,EDI
CMP [ESP+0x0C],WORD 1
MOV EDI,DWORD [DS:SYSVAR+0] ;SYSVAR+0 = 0xe0000000
MOV AX,[DS:EDI+EAX]
MOV EDI,DWORD VBE_FILP ;获得缓冲区地址
MOV AX,[DS:EDI+EAX] ;把计算好的颜色放到指定的坐标
;--------------------------------------------------------------------------------------
;PUSH WORD 1使用缓冲区0不使用缓冲区
;PUSH WORD starty
;PUSH WORD startx
;PUSH WORD endy
;PUSH WORD endx
;PUSH WORD COLOR
;绘制一个指定区域的方块
;----------------------------------------------------------------------------------------------
;开遍2个2字节变量
XOR EBX,EBX
XOR EDX,EDX
XOR EAX,EAX
MOV DX,[ESP+14+4] ;x
MOV BX,[ESP+16+4] ;y
MOV [ESP+0],DX 保存x
MOV [ESP+2],BX 保存y
MOV BX,[ESP+16+4]
MOV [ESP+2],BX
PUSH WORD [ESP+18+4];缓冲模式
PUSH WORD [ESP+8+6] ;颜色
PUSH WORD [ESP+2+4] ;y
PUSH WORD [ESP+0+6] ;x
CALL SetPoint
ADD [ESP+2],WORD 1 ;y++
MOV BX,[ESP+2]
CMP BX,[ESP+12+4]
ADD [ESP+0],WORD 1 ;x++
MOV BX,[ESP+0]
CMP BX,[ESP+10+4]
CMP [ESP+18+4],WORD 0
push word 0
;0=常规拷贝,1=与目标or,2=与目标and,3=与目标xor
push word [ESP+16+6] ;dy
push word [ESP+14+8] ;dx
push word [ESP+12+10] ;endy
push word [ESP+10+12] ;endx
push word [ESP+16+14] ;starty
push word [ESP+14+16] ;startx
CALL BitBlt
MOV ESP,EBP
;---------------------------------------------------------------------------------
;重绘指定大小的区域
;PUSH WORD starty
;PUSH WORD startx
;PUSH WORD endy
;PUSH WORD endx
;这个函数实现强制刷新区域
;通过指定的区域进行刷新,建立消息系统以后会发送这个消息到所有的窗体
;包括桌面如果这些窗体的某个部分存在于这个刷新范围内那么刷新
;----------------------------------------------------------------------------------------------
RefreshRect:
;开遍2个6字节变量
XOR EBX,EBX
XOR EAX,EAX
MOV AX,[ESP+12+4] ;x
MOV BX,[ESP+14+4] ;y
MOV [ESP+0],AX 保存x
MOV [ESP+2],BX 保存y
MOV BX,[ESP+2]
MOV [ESP+14+4],BX
PUSH WORD 1
PUSH WORD 0 ;需要计算颜色
PUSH WORD [ESP+14+4+4] ;y
PUSH WORD [ESP+12+4+6] ;x
CALL SetPoint
ADD [ESP+14+4],WORD 1 ;y++
MOV BX,[ESP+14+4]
CMP BX,WORD [ESP+10+4]
ADD [ESP+12+4],WORD 1 ;x++
MOV BX,[ESP+12+4]
CMP BX,WORD [ESP+8+4]
push word 0
;0=常规拷贝,1=与目标or,2=与目标and,3=与目标xor
push word [ESP+16+6] ;dy
push word [ESP+14+8] ;dx
push word [ESP+12+10] ;endy
push word [ESP+10+12] ;endx
push word [ESP+16+14] ;starty
push word [ESP+14+16] ;startx
CALL BitBlt
MOV ESP,EBP
;------------------------------------------------------------------------------------
;DrawPixelBuf显示颜色点阵
;PUSH DWORD 缓冲区地址
;PUSH WORD
缓冲区大小
;PUSH WORD
matrix_width/endy
;PUSH WORD
matrix_height/endx
;PUSH WORD
;PUSH WORD
;从startx,starty点开始画横向endx个点从向endy个点的矩阵
;通过startx,starty把从缓冲区地址拿出来的像素颜色送到相应的
;显卡映射地址
;--------------------------------------------------------------------------------------
DrawPixelBuf:
MOV EBP,ESP
SUB ESP,4 ;申请局部变量
;先实现单2字节移动也就是一次移动一个像素点.以后再说mmx移动
XOR EAX,EAX
XOR EBX,EBX
MOV AX,[ESP+8+4]
MOV [ESP+0],AX
;startx放到局部变量
MOV BX,[ESP+10+4]
MOV [ESP+2],BX
;starty放到局部变量
ADD [ESP+12+4],AX
ADD [ESP+14+4],BX
;[ESP+16+4]
;缓冲区大小-字节
MOV ESI,[ESP+18+4]
;缓冲区地址
MOV BX,[ESP+8+4]
MOV [ESP+0],BX
;初始化x再次循环
MOV AX,[DS:ESI]
XOR AL,0x11 ;如果是需要挖掉的颜色0x1111那么不显示
JZ ..@mask
XOR AL,0x11 ;如果不是需要挖掉的颜色0x1111那么恢复
PUSH WORD [ESP+2+4];y
PUSH WORD [ESP+0+6];x
CALL SetPoint
ADD [ESP+0],WORD 1
MOV BX,[ESP+0]
CMP [ESP+12+4],BX
ADD [ESP+2],WORD 1
MOV BX,[ESP+2]
CMP [ESP+14+4],BX
;push word 0
;0=常规拷贝,1=与目标or,2=与目标and,3=与目标xor
;push word [ESP+10+4] ;dy
;push word [ESP+8+4] ;dx
;push word [ESP+14+4] ;endy
;push word [ESP+12+4] ;endx
;push word [ESP+10+4] ;starty
;push word [ESP+8+4] ;startx
;CALL BitBlt
MOV ESP,EBP
;--------------------------------------------------------------------------
;BitBlt类似windows的GDI函数
;push word 0=常规拷贝,1=与目标or,2=与目标and,3=与目标xor
;push word dy
;push word dx
;push word endy
;push word endx
;push word starty
;push word startx
;----------------------------------------------------------------------------
MOV EBP,ESP
XOR EAX,EAX
XOR EBX,EBX
MOV AX,[ESP+8+8] ;x
MOV BX,[ESP+10+8] ;y
MOV [ESP+0],AX ;x
MOV [ESP+2],BX ;y
MOV AX,[ESP+16+8] ;dx
MOV BX,[ESP+18+8] ;dy
MOV [ESP+4],AX ;dx
MOV [ESP+6],BX
;[ESP+12+8] endx
;[ESP+14+8] endy
;[ESP+16+8] dx
;[ESP+18+8] dy
;[ESP+20+8] 0/1/2/3
MOV BX,[ESP+8+8]
MOV [ESP+0],BX ;恢复x坐标从新计数
MOV BX,[ESP+16+8]
MOV [ESP+4],BX
CMP [ESP+20+8],WORD 0 ;常规操作
CMP [ESP+20+8],WORD 1 ;or
CMP [ESP+20+8],WORD 2 ;and
CMP [ESP+20+8],WORD 3 ;xor
j0:JMP non0
j1:JMP or1
j2:JMP and2
j3:JMP xor3
GetXYAddr [ESP+0],[ESP+2] ;x,y,ret=eax
MOV EDI,VBE_FILP ;获得缓冲区地址
MOV ESI,[DS:EDI+EAX]
;把计算好的颜色放到指定的坐标
GetXYAddr [ESP+4],[ESP+6]
MOV EDI,DWORD [DS:SYSVAR+0] ;SYSVAR+0 = 0xe0000000
MOV [DS:EDI+EAX],SI
;从缓冲区取得的颜色放到指定坐标的显示内存
ADD [ESP+0],WORD 1 ;x++
ADD [ESP+4],WORD 1;目标x坐标累加
MOV BX,[ESP+0]
CMP BX,[ESP+12+8]
ADD [ESP+2],WORD 1
ADD [ESP+6],WORD 1 ;目标y坐标累加
MOV BX,[ESP+2]
CMP BX,[ESP+14+8]
MOV ESP,EBP
;汉字显示根据内存地址显示,汉字点阵16*16=32字节点阵
;PUSH WORD 汉字编码0xB0A1开始
;PUSH WORD 颜色
;PUSH WORD 是否使用背景颜色0不使用1使用
;PUSH WORD
;PUSH WORD 1是与背景求反色0不求反色
;PUSH WORD y坐标
;PUSH WORD x坐标
MOV EBP,ESP
XOR EAX,EAX
XOR EDX,EDX
XOR ESI,ESI
;a0a0编码开始
MOV AL,[ESP+21+8] ;取区码
MOV CL,0xA0
;b0-a0计算区
MOV EDX,EAX
XOR EAX,EAX
MOV AL,[ESP+20+8] ;取位码
MOV CL,0xA0
;计算区内的第几个汉字
ADD EDX,EAX
;区码和位码相加
MOV EAX,EDX
;把计算好的区位码送到EAX
XOR EDX,EDX
MOV ESI,EDX
SHL ESI,CL
OR ESI,EAX
SUB ESI,0xBE0 ;调整到b040到第一个汉字啊
ADD ESI,FontLibrary ;取字库地址+偏移
MOV AX,[ESP+8+8]
MOV DX,[ESP+10+8]
MOV [ESP+2],AX ;x
MOV [ESP+4],DX ;y
MOV [ESP+0],WORD 0 ;清空变量
MOV AL,[DS:ESI] ;获得点阵
PUSH WORD 1
;使用双缓冲
PUSH WORD [ESP+18+14] ;颜色
PUSH WORD [ESP+10+16] ;y
PUSH WORD [ESP+8+18]
CALL SetPoint
CMP [ESP+16+12],WORD 0 ;是否使用背景
PUSH WORD 1
;使用双缓冲
PUSH WORD [ESP+14+14] ;背景颜色
PUSH WORD [ESP+10+16] ;y
PUSH WORD [ESP+8+18]
CALL SetPoint
ADD [ESP+8+8],WORD 1 ;x坐标+1
;下一个8位点阵
MOV AL,[DS:ESI] ;获得点阵
JNC backg1
PUSH WORD 1
;使用双缓冲
PUSH WORD [ESP+18+14] ;颜色
PUSH WORD [ESP+10+16] ;y
PUSH WORD [ESP+8+18]
CALL SetPoint
CMP [ESP+16+12],WORD 0 ;是否使用背景
PUSH WORD 1
;使用双缓冲
PUSH WORD [ESP+14+14] ;背景颜色
PUSH WORD [ESP+10+16] ;y
PUSH WORD [ESP+8+18]
CALL SetPoint
ADD [ESP+8+8],WORD 1 ;x坐标+1
SUB [ESP+8+8],WORD 16
ADD [ESP+10+8],WORD 1 ;y+1
;下一个8位点阵
ADD [ESP+0],WORD 1
CMP [ESP+0],WORD 16
MOV AX,[ESP+4] ;y
MOV BX,[ESP+2] ;x
push WORD 0;0=常规拷贝,1=与目标or,2=与目标and,3=与目标xor
push WORD AX;dy
push WORD BX;dx
push AX ;endy
push BX ;endx
push WORD [ESP+4+10];starty
push WORD [ESP+2+12];startx
CALL BitBlt
MOV ESP,EBP
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!欲穷千里目,更上一层楼
NASM中文手册
&第一章: 简介 1.1 什么是NASM NASM是一个为可移植性与模块化而设计的一个80x86的汇编器。它支持相当多 的目标文件格式,包括Linux和'NetBSD/FreeBSD','a.out','ELF','COFF',微软16 位的'OBJ'和'Win32'。它还可以输出纯二进制文件。它的语法设计得相当的简 洁易懂,和Intel语法相似但更简单。它支持'Pentium','P6','MMX','3DNow!', 'SSE' and 'SSE2'指令集, 1.1.1 为什么还需要一个汇编器? NASM当初被设计出来的想法是'comp.lang.asm.x86'(或者可能是'alt.lang.asm' ,我忘了),从本质上讲,是因为没有一个好的免费的x86系例的汇编器可以使用, 所以,必须有人来写一个。 (*)'a86'不错,但不是免费的,而且你不可能得到32位代码编写的功能,除非你 付费,它只使用在dos上。 (*) 'gas'是免费的,而且在dos下和unix下都可以使用,但是它是作为'gcc'的一 个后台而设计的,并不是很好,'gcc'一直就提供给它绝对正确的代码,所以它的 错误检测功能相当弱,还有就是对于任何一个想真正利用它写点东西的人来讲, 它的语法简直太可怕了,并且你无法在里面写正确的16位代码。 (*) 'as86'是专门为Minix和Linux设计的,但看上去并没有很多文档可以参考。 (*) 'MASM'不是很好,并且相当贵,还且只能运行在DOS下。 (*) 'TASM'好一些,但却极入与MASM保持兼容,这就意味着无数的伪操作码和繁琐 的约定,并且它的语法本质上就是MASM的,伴随着的就是一些自相矛盾和奇怪的 东西。它也是相当贵的,并且只能运行在DOS下。 所以,只有NASM才能使您愉悦得编程。目前,它仍在原型设计阶段-我们不期望它 能够超越所有的这些汇编器。但请您发给我们bug报告,修正意见,和其他有用的 信息,还有其他任何你手头有的对我们有用的信息(感谢所有已经这样在做了的 人们),我们还会不断地改进它。 1.1.2 许可条件 请阅读作为NASM发布的一部分的文件'Licence',只有在该许可条件下你才可以使 用NASM。
1.2 联系信息 当前版本的NASM(0.98.08)由一个开发小组在维护,你可以从'nasm-devel'邮件列表 中得到(看下面的链接),如果你想要报告bug,请先阅读10.2节 NASM有一个主页:'http://www.web-sites.co.uk/nasm',更多的信息还可以在 `http://nasm.2y.net/'上获取。 最初的作者你可以通过email:`jules@dsf.org.uk'和和他们联 系,但后来的开发小组并不在其中。 最新的NASM发布被上传至官方网站`http://www.web-sites.co.uk/nasm'和`ftp.kernel.org', `ibiblio.org' 公告被发布至`comp.lang.asm.x86', `alt.lang.asm' 和`comp.os.linux.announce' 如果你想了解NASM beta版的发布,和当前的开发状态,请通过在 `http://groups.yahoo.com/group/nasm-devel', `http://www.pairlist.net/mailman/listinfo/nasm-devel' and `http://sourceforge.net/projects/nasm' 注册来捐助'nasm-devel'邮件列表。 在网站Sourceforge上的列表是较好的一个列表,它也是最新nasm源代码与发布的 一个网站,另外的列表也是公开的,但有可能不会被继续长期支持。 1.3 安装 1.3.1 在dos和Windows下安装NASM 如果你拿到了NASM的DOS安装包,'nasmXXX.zip'(这里.'XXX'表示该安装包的NASM版 本号),把它解压到它自己的目录下(比如:&c:/nasm') 该包中会包含有四个可执行文件:NASM可拟行文件'nasm.exe'和'nasmw.exe',还有 NDISASM可执行文件'ndisasm.exe'和'ndisasmw.exe'。文件名以'w'结尾的是'Win32' 可执行格式。是运行在'Windows 95'或'Windows NT'的Intel处理器上的,另外的是 16位的'DOS'可执行文件。 NASM运行时需要的唯一文件就是它自己的可执行文件,所以可以拷贝'nasm.exe' 和'nasmw.exe'的其中一个到你自己的路径下,或者可以编写一个'autoexec.bat'把 nasm的路径加到你的'PATH'环境变量中去。(如果你只安装了Win32版本的,你可能 希望把文件名改成'nasm.exe'。)
就这样,NASM装好了。你不需要为了运行nasm而让'nasm'目录一直存在(除非你把它 加到了你的'PATH'中,所以如果你需要节省空间,你可删掉它,但是,你可能需要保留 文档或测试程序。 如果你下载了DOS版的源码包,'nasmXXXs.zip',那'nasm'目录还会包含完整的NASM源 代码,你可以选择一个Makefiles来重新构造你的NASM版本。 注意源文件`insnsa.c', `insnsd.c', `insnsi.h'和`insnsn.c'是由'standard.mac'中 的指令自动生成的,尽管NASM0.98发布版中包含了这些产生的文件,你如果改动了 insns.dat,standard.mac或者文件,可能需要重新构造他们,在将来的源码发布中有 可能将不再包含这些文件,多平台兼容的Perl可以从上得到。 1.3.2 在unix下安装NASM 如果你得到了Unix下的NASM源码包'nasm-x.xx.tar.gz'(这里x.xx表示该源码包中的 nasm的版本号),把它解压压到一个目录,比如'/usr/local/src'。包被解压后会创建 自己的子目录'nasm-x.xx' NASM是一个自动配置的安装包:一旦你解压了它,'cd'到它的目录下,输入'./configuer', 该脚本会找到最好的C编译器来构造NASM,并据此建立Makefiles。 一旦NASM被自动配置好后,你可以输入'make'来构造'nasm'和'ndisasm'二进制文件, 然后输入'make install'把它们安装到'/usr/local/bin',并把man页安装到 '/usr/local/man/man1'下的'nasm.1和'ndisasm.1'或者你可以给配置脚本一个 '--prefix'选项来指定安装目录,或者也可以自己来安装。 NASM还附带一套处理'RDOFF'目标文件格式的实用程序,它们在'rdoff'子目录下, 你可以用'make rdf'来构造它们,并使用'make rdf_install'来安装。如果你需 要的话。 如果NASM在自动配置的时候失败了,你还是可以使用文件'Makefile.unx'来编译它们, 把这个文件改名为'Makefile',然后输入'make'。在'rdoff'子目录下同样有一个 Makefile.unx文件。
第二章 运行NASM 2.1 NASM命令行语法 要汇编一个文件,你可以以下面的格式执行一个命令: nasm -f &format& &filename& [-o &output&] 比如, 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'的,那你应该使用'-f aout'(Linux的'a.out' 系统很久以前就过时了,现在已非常少见。)
就像其他的Unix编译器与汇编器,NASM在碰到错误以前是不输出任何信息的,所 以除了出错信息你看不到任何其他信息。 2.1.1 '-o'选项:指定输出文件的文件名。 NASM会为你的输出文件选择一个文件名;具体如何做取决于目标文件的格式,对 于微软的目标文件格式('obj'和'win32'),它会去掉你的源文件名的'.asm'扩展 名(或者其他任何你喜欢使用的扩展名,NASM并不关心具体是什么),并替换上 'obj'。对于Unix的目标文件格式('aout','coff','elf'和'as86')它会替换成 '.o', 对于'rdf',它会使用'.rdf',还有为'bin'格式,它会简单地去掉扩展名,所以 'myfile.asm'会产生的一个输出文件'myfile'。 如果输出文件已经存在,NASM会覆盖它,除非它的文件名与输入文件同名,在这种 情况下,它会给出一个警告信息,并使用'nasm.out'作为输出文件的文件名。 在某些情况下,上述行为是不能接受的,所以,NASM提供了'-o'选项,它能让你指定 你的输出文件的文件名,你使用'-o'后面紧跟你为输出文件取的名字,中间可以加 空格也可以不加。比如: nasm -f bin program.asm -o program.com nasm -f bin driver.asm -odriver.sys 请注意这是一个小写的o,跟大写字母O是不同的,大写的是用来指定需要传递的选 项的数目,请参阅2.1.15 2.1.2 `-f'选项:指定输出文件的格式。 如果你没有对NASM使用'-f'选项,它会自己为你选择一个输出文件格式。在发布的 NASM版本中,缺省的输出格式总是'bin';如果你自己编译你的NASM,你可以在编译的 时候重定义'OF_DEFAULT'来选择你需要的缺省格式。 就象'-o','-f'与输出文件格式之间的空格也是可选的,所以'-f elf'和'-felf'都是 合法的。 所有可使用的输出文件格式的列表可以通过运行命令'nasm -hf'得到。 2.1.3 `-l' 选项: 产生列表文件 如果你对NASM使用了'-l'选项,后面跟一个文件名,NASM会为你产生一个源文件的列表 文件,在里面,地址和产生的代码列在左边,实际的源代码(包括宏扩展,除了那些指定 不需要在列表中扩展的宏,参阅4.3.9)列在右边,比如: nasm -f elf myfile.asm -l myfile.lst
2.1.4 `-M'选项: 产生Makefile依赖关系. 该选项可以用来向标准输出产生makefile依赖关系,可以把这些信息重定向到一个文件 中以待进一步处理,比如: NASM -M myfile.asm & myfile.dep 2.1.5 `-F'选项: 选择一个调试格式 该选项可以用来为输出文件选择一个调试格式,语法跟-f选项相册,唯一不同的是它产 生的输出文件是调试格式的。 一个具体文件格式的完整的可使用调试文件格式的列表可通过命令'nasm -f &format& -y' 来得到。 这个选项在缺省状态下没有被构建时NASM。如何使用该选项的信息请参阅6.10 2.1.6 `-g' 选项:使调试信息有效。 该选项可用来在指定格式的输出文件中产生调试信息。 更多的信息请参阅2.1.5 2.1.7 `-E' 选项: 把错误信息输入到文件。 在'MS-DOS'下,尽管有办法,但要把程序的标准错误输出重定向到一个文件还是非常困 难的。因为NASM常把它的警告和错误信息输出到标准错误设备,这将导致你在文本编 辑器里面很难捕捉到它们。 因此NASM提供了一个'-E'选项,带有一个文件名参数,它可以把错误信息输出到指定的 文件而不是标准错误设备。所以你可以输入下面这样的命令来把错误重定向到文件: nasm -E myfile.err -f obj myfile.asm 2.1.8 `-s' 选项: 把错误信息输出到'stdout' '-s'选项可以把错误信息重定向到'stdout'而不是'stderr',它可以在'MS-DOS'下进行 重定向。想要在汇编文件'myfile.asm'时把它的输出用管道输出给'more'程序,可以这样: nasm -s -f obj myfile.asm | more 请参考2.1.7的'-E'选项. 2.1.9 `-i'选项: 包含文件搜索路径
当NASM在源文件中看到'%include'操作符时(参阅4.6),它不仅仅会在当前目录下搜索给 出的文件,还会搜索'-i'选项在命令行中指定的所有路径。所以你可以从宏定义库中 包含进一个文件,比如,输入: nasm -ic:/macrolib/ -f obj myfile.asm (通常,在 '-i'与路径名之间的空格是允许的,并且可选的。) NASM更多的关注源代码级上的完全可移植性,所以并不理解正运行的操作系统对文件的 命名习惯;你提供给'-i'作为参数的的字符串会被一字不差地加在包含文件的文件名前。 所以,上例中最后面的一个反斜杠是必要的,在Unix下,一个尾部的正斜线也同样是必要的。 (当然,如果你确实需要,你也可以不正规地使用它,比如,选项'-ifoo'会导致 '%incldue &bar.i'去搜索文件'foobar.i'...) 如果你希望定义一个标准的搜索路径,比如像Unix系统下的'/usr/include',你可以在环境 变量NASMENV中放置一个或多个'-i'(参阅2.1.19) 为了与绝大多数C编译器的Makefile保持兼容,该选项也可以被写成'-I'。 2.1.10 `-p' 选项: 预包含一个文件 NASM允许你通过'-p'选项来指定一个文件预包含进你的源文件。所以,如果运行: nasm myfile.asm -p myinc.inc 跟在源文件开头写上'%include &myinc.inc&然后运行'nasm myfile.asm'是等效的。 为和'-I','-D','-U'选项操持一致性,该选项也可以被写成'-P' 2.1.11 `-d'选项: 预定义一个宏。 就像'-p'选项给出了在文件头放置'%include'的另一种实现,'-d'选项给出了在文 件中写'%define'的另一种实现,你可以写: nasm myfile.asm -dFOO=100 作为在文件中写下面一行语句的一种替代实现: %define FOO 100
在文件的开始,你可以取消一个宏定义,同样,选项'-dFOO'等同于代码'%define FOO'。 这种形式的操作符在选择编译时操作中非常有用,它们可以用'%ifdef'来进行测试, 比如'-dDEBUG'。 为了与绝大多数C编译器的Makefile保持兼容,该选项也可以被写成'-D'。 2.1.12 `-u' 选项: 取消一个宏定义。 '-u'选项可以用来取消一个由'-p'或'-d'选项先前在命令行上定义的一个宏定义。 比如,下面的命令语句: nasm myfile.asm -dFOO=100 -uFOO 会导致'FOO'不是一个在程序中预定义的宏。这在Makefile中不同位置重载一个操 作时很有用。 为了与绝大多数C编译器的Makefile保持兼容,该选项也可以被写成'-U'。 2.1.13 `-e'选项: 仅预处理。 NASM允许预处理器独立运行。使用'-e'选项(不需要参数)会导致NASM预处理输入 文件,展开所有的宏,去掉所有的注释和预处理操作符,然后把结果文件打印在标 准输出上(如果'-o'选项也被指定的话,会被存入一个文件)。 该选项不能被用在那些需要预处理器去计算与符号相关的表达式的程序中,所以 如下面的代码: %assign tablesize ($-tablestart) 会在仅预处理模式中会出错。 2.1.14 `-a' 选项: 不需要预处理。 如果NASM被用作编译器的后台,那么假设编译器已经作完了预处理,并禁止NASM的预 处理功能显然是可以节约时间,加快编译速度。'-a'选项(不需要参数),会让NASM把 它强大的预处理器换成另一个什么也不做的预处理器。 2.1.15 `-On'选项: 指定多遍优化。 NASM在缺省状态下是一个两遍的汇编器。这意味着如果你有一个复杂的源文件需要 多于两遍的汇编。你必须告诉它。
使用'-O'选项,你可以告诉NASM执行多遍汇编。语法如下: (*)'-O0'严格执行两遍优化,JMP和Jcc的处理和0.98版类似,除了向后跳的JMP是短跳 转,如果可能,立即数在它们的短格式没有被指定的情况下使用长格式。 (*)'-O1'严格执行两遍优化,但前向分支被汇编成保证能够到达的代码;可能产生比 '-O0'更大的代码,但在分支中的偏移地址没有指定的情况下汇编成功的机率更大, (*)'-On' 多编优化,最小化分支的偏移,最小化带符号的立即数,当'strict'关键字 没有用的时候重载指定的大小(参阅3.7),如果2&=n&=3,会有5*n遍,而不是n遍。 注意这是一个大写的O,和小写的o是不同的,小写的o是指定输出文件的格式,可参阅 2.1.1 2.1.16 `-t'选项: 使用TASM兼容模式。 NASM有一个与Borlands的TASM之间的受限的兼容格式。如果使用了NASM的'-t'选项, 就会产生下列变化: (*)本地符号的前缀由'.'改为 (*)TASM风格的以开头的应答文件可以由命令行指定。这和NASM支持的 风格是不同的。 (*)扩号中的尺寸替换被支持。在TASM兼容模式中,方括号中的尺寸替换改变了操作 数的尺寸大小,方括号不再支持NASM语法的操作数地址。比如,'mov eax,[DWORD VAL]' 在TASM兼容语法中是合法的。但注意你失去了为指令替换缺省地址类型的能力。 (*)'%arg'预处理操作符被支持,它同TASM的ARG操作符相似。 (*) `%local'预处理操作符。 (*) `%stacksize'预处理操作符。 (*) 某些操作符的无前缀形式被支持。 (`arg', `elif',`else', `endif', `if', `ifdef', `ifdifi', `ifndef', `include',`local') (*) 还有很多... 需要更多的关于操作符的信息,请参阅4.9的TASM兼容预处理操作符指令。 2.1.17 `-w'选项: 使汇编警告信息有效或无效。
NASM可以在汇编过程中监视很多的情况,其中很多是值得反馈给用户的,但这些情况 还不足以构成严重错误以使NASM停止产生输出文件。这些情况被以类似错误的形式 报告给用户,但在报告信息的前面加上'warning'字样。警告信息不会阻止NASM产生 输出文件并向操作系统返回成功信息。 有些情况甚至还要宽松:他们仅仅是一些值得提供给用户的信息。所以,NASM支持'-w' 命令行选项。它以使特定类型的汇编警告信息输出有效或无效。这样的警告类型是 以命名来描述的,比如,'orphan-labels',你可以以下列的命令行选项让此类警告信息 得以输出:'-w+orphan-labels',或者以'-w-orphan-labels'让此类信息不能输出。 可禁止的警告信息类型有下列一些: (*)`macro-params'包括以错误的参数个数调用多行的宏定义的警告。这类警告信息 缺省情况下是输出的,至于为什么你可能需要禁止它,请参阅4.3.1。 (*)`orphan-labels'包含源文件行中没有指令却定义了一个没有结尾分号的label的 警告。缺省状况下,NASM不输出此类警告。如果你需要它,请参阅3.1的例子。 (*) 'number-overflow'包含那些数值常数不符合32位格式警告信息(比如,你很容易打 了很多的F,错误产生了'0x7fffffffffff')。这种警告信息缺省状况下是打开的。 2.1.18 `-v'选项: 打印版本信息。 输入'NASM -v'会显示你正使用的NASM的版本号,还有它被编译的时间。 如果你要提交bug报告,你可能需要版本号。 2.1.19 `NASMENV'环境变量。 如果你定义了一个叫'NASMENV'的环境变量,程序会被把它认作是命令行选项附加的一 部分,它会在真正的命令行之前被处理。你可以通过在'NASMENV'中使用'-i'选项来定 义包含文件的标准搜索路径。 环境变量的值是通过空格符分隔的,所以值'-s ic:/nasmlib'会被看作两个单独的操 作。也正因为如此,意味着值'-dNAME='my name'不会象你预期的那样被处理, 因为它 会在空格符处被分开,NASM的命令行处理会被两个没有意义的字符串'-dNAME=&my'和 'name&'给弄混。 为了解决这个问题,NASM为此提供了一个特性,如果你在'NASMENV'环境变量的第一个 字符处写上一个非减号字符,NASM就会把这个字符当作是选项的分隔符。所以把环 境变量设成'!-s!-ic:/nasmlib'跟'-s -ic:/nasmlib'没什么两样,但是
'!-dNAME=&my name&就会正常工作了。 这个环境变量以前叫做'NASM',从版本0.98.32以后开始叫这个名字。 2.2 MASM用户速成。 如果你曾使用MASM写程序,或者使用在MASM兼容模式下使用TASM, 或者使用'a86', 本节将阐述MASM与NASM语法之间的主要区别。如果你没有使用过MASM,那最好先 跳过这一节。 2.2.1 NASM是大小写敏感的。 一个简单的区别是NASM是大小写敏感的。当你调用你的符号'foo','Foo',或 'FOO'时,它们是不同的。如果你在汇编'DOS'或'OS/2', '.OBJ'文件,你可以使 用'UPPERCASE'操作符来保证所有的导出到其他代码模式的符号都是大写的;但 是,在仅仅一个单独的模块中,NASM会区分大小写符事情。 2.2.2 NASM需要方括号来引用内存地址。 NASM的设计思想是语法尽可能简洁。它的一个设计目标是,它将在被使用的过程 中,尽可能得让用户看到一个单行的NASM代码时,就可以说出它会产生什么操作 码。你可以在NASM中这样做,比如,如果你声明了: 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: i 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 0x ; 0x78 0x56 0x34 0x12 dd 1. ; floating-point constant dq 1. ; double-precision float dt 1. ; 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&, ; 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 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,0a2 hex mov ax,$0a2 ; hex again: the 0 is required mov ax,0xa2 ; hex yet again mov ax,777 octal
mov ax, binary 3.4.2 字符型常数。 一个字符常数最多由包含在双引号或单引号中的四个字符组成。引号的类型 与使用跟NASM其它地方没什么区别,但有一点,单引号中允许有双引号出现。 一个具有多个字符的字符常数会被little-endian order,如果你编写: mov eax,'abcd' 产生的常数不会是`0x',而是`0x',所以你把常数存入内存 的话,它会读成'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. ; 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_ 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'足够小,足以 放在一个字节的偏移域中,所以,它以产生一个短形式的有效地址编码的方 式来解决这个问题;在第一遍中,它所知道的所有关于'offset'的情况是:它 可能是代码段中的一个符号,而且,它可能需要四字节的形式。所以,它强制 这条指令的长度为适合四字节地址域的长度。在第二遍的时候,这个决定已经 作出了,它保持使这条指令很长,所以,这种情况下产生的代码没有足够的小, 这个问题可以通过先定义offset的办法得到解决,或者强制有效地址的尺寸大 小,象这样写代码: [byte ebx+offset] 3.9 本地Labels NASM对于那些以一个句点开始的符号会作特殊处理,一个以单个句点开始的 Label会被处理成本地label, 这意味着它会跟前面一个非本地label相关联. 比如: label1 ; some code .loop ; some more code jne .loop ret label2 ; some code .loop ; some more code jne .loop ret 上面的代码片断中,每一个'JNE'指令跳至离它较近的前面的一行上,因为'.loop' 的两个定义通过与它们前面的非本地Label相关联而被分离开来了。 对于本地Label的处理方式是从老的Amiga汇编器DevPac中借鉴过来的;尽管 如此,NASM提供了进一步的性能,允许从另一段代码中调用本地labels。这 是通过在本地label的前面加上非本地label前缀实现的:第一个.loop实际上被 定义为'label1.loop',而第二个符号被记作'label2.loop'。所以你确实需要 的话你可写:
label3 ; some more code ; and some more jmp label1.loop 有时,这是很有用的(比如在使用宏的时候),可以定义一个label,它可以 在任何地方被引用,但它不会对常规的本地label机制产生干扰。这样的 label不能是非本地label,因为非本地label会对本地labels的重复定义与 引用产生干扰;也不能是本地的,因为这样定义的宏就不能知道label的全 称了。所以NASM引进了第三类label,它只在宏定义中有用:如果一个label 以一个前缀开始,它不会对本地label产生干扰,所以,你可以写: label1: ; a non-local label .local: ; this is really label1.local : ; this is a special symbol label2: ; another non-local label .local: ; this is really label2.local
this will jump three lines up NASM还能定义其他的特殊符号,比如以两个句点开始的符号,比如 '..start'被用来指定'.obj'输出文件的执行入口。(参阅6.2.6)
第四章 NASM预处理器 NASM拥有一个强大的宏处理器,它支持条件汇编,多级文件包含,两种形式的 宏(单行的与多行的),还有为更强大的宏能力而设置的&context stack'机制 预处理指令都是以一个'%'打头。 预处理器把所有以反斜杠(/)结尾的连续行合并为一行,比如: %define THIS_VERY_LONG_MACRO_NAME_IS_DEFINED_TO / THIS_value 、 会像是单独一行那样正常工作。 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代表'insensitive'),你可以一次定义所有的大小写不同的宏。所以
'%idefine foo bar'会导致'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.1.5) 你可以在命令行中使用'-d'选项来预定义宏。参阅2.1.11 4.1.2 %define的增强版: `%xdefine' 与在调用宏时展开宏不同,如果想要调用一个嵌入有其他宏的宏时,使用 它在被定义的值,你需要'%define'不能提供的另外一种机制。解决的方案
是使用'%xdefine',或者它的大小写不敏感的形式'%xidefine'。 假设你有下列的代码: %define isTrue 1 %define isFalse isTrue %define isTrue 0 val1: db isFalse %define isTrue 1 val2: db isFalse 在这种情况下,'val1'等于0,而'val2'等于1。这是因为,当一个单行宏用 '%define'定义时,它只在被调用时进行展开。而'isFalse'是被展开成 'isTrue',所以展开的是当前的'isTrue'的值。第一次宏被调用时,'isTrue' 是0,而第二次是1。 如果你希望'isFalse'被展开成在'isFalse'被定义时嵌入的'isTrue'的值, 你必须改写上面的代码,使用'%xdefine': %xdefine isTrue 1 %xdefine isFalse isTrue %xdefine isTrue 0 val1: db isFalse %xdefine isTrue 1 val2: db isFalse 现在每次'isFalse'被调用,它都会被展开成1,而这正是嵌入的宏'isTrue' 在'isFalse'被定义时的值。 4.1.3 : 连接单行宏的符号: `%+' 一个单行宏中的单独的记号可以被连接起来,组成一个更长的记号以 待稍后处理。这在很多处理相似的事情的相似的宏中非常有用。 举个例子,考虑下面的代码: %define BDASTART 400 Start of BIOS data area
struc tBIOSDA ; its structure .COM1addr RESW 1 .COM2addr RESW 1 ; ..and so on endstruc 现在,我们需要存取tBIOSDA中的元素,我们可以这样: mov ax,BDASTART + tBIOSDA.COM1addr mov bx,BDASTART + tBIOSDA.COM2addr 如果在很多地方都要用到,这会变得非常的繁琐无趣,但使用下面 的宏会大大减小打字的量: ; Macro to access BIOS variables by their names (from tBDA): %define BDA(x) BDASTART + tBIOSDA. %+ x 现在,我们可以象下面这样写代码: mov ax,BDA(COM1addr) mov bx,BDA(COM2addr) 使用这个特性,我们可以简单地引用大量的宏。(另外,还可以减少打 字错误)。 4.1.4 取消宏定义: `%undef' 单行的宏可以使用'%undef'命令来取消。比如,下面的代码: %define foo bar %undef foo mov eax, foo 会被展开成指令'mov eax, foo',因为在'%undef'之后,宏'foo'处于无定义 状态。 那些被预定义的宏可以通过在命令行上使用'-u'选项来取消定义,参阅 2.1.12。 4.1.5 预处理器变量 : `%assign' 定义单行宏的另一个方式是使用命令'%assign'(它的大小写不敏感形式
是%iassign,它们之间的区别与'%idefine','%idefine'之间的区别完全相 同)。 '%assign'被用来定义单行宏,它不带有参数,并有一个数值型的值。它的 值可以以表达式的形式指定,并要在'%assing'指令被处理时可以被一次 计算出来, 就像'%define','%assign'定义的宏可以在后来被重定义,所以你可以这 样做: %assign i i+1 以此来增加宏的数值 '%assing'在控制'%rep'的预处理器循环的结束条件时非常有用:请参 阅4.5的例子。另外的关于'%assign'的使用在7.4和8.1中的提到。 赋给'%assign'的表达式也是临界表达式(参阅3.8),而且必须可被计算 成一个纯数值型(不能是一个可重定位的指向代码或数据的地址,或是包 含在寄存器中的一个值。) 4.2 字符串处理宏: `%strlen' and `%substr' 在宏里可以处理字符串通常是非常有用的。NASM支持两个简单的字符 串处理宏,通过它们,可以创建更为复杂的操作符。 4.2.1 求字符串长度: `%strlen' '%strlen'宏就像'%assign',会为宏创建一个数值型的值。不同点在于 '%strlen'创建的数值是一个字符串的长度。下面是一个使用的例子: %strlen charcnt 'my string' 在这个例子中,'charcnt'会接受一个值8,就跟使用了'%assign'一样的 效果。在这个例子中,'my string'是一个字面上的字符串,但它也可以 是一个可以被扩展成字符串的单行宏,就像下面的例子: %define sometext 'my string' %strlen charcnt sometext 就像第一种情况那样,这也会给'charcnt'赋值8 4.2.2 取子字符串: `%substr'
字符串中的单个字符可以通过使用'%substr'提取出来。关于它使用的 一个例子可能比下面的描述更为有用: %substr mychar 'xyz' 1 ; equivalent to %define mychar 'x' %substr mychar 'xyz' 2 ; equivalent to %define mychar 'y' %substr mychar 'xyz' 3 ; equivalent to %define mychar 'z' 在这个例子中,mychar得到了值'z'。就像在'%strlen'(参阅4.2.1)中那样, 第一个参数是一个将要被创建的单行宏,第二个是字符串,第三个参数 指定哪一个字符将被选出。注意,第一个索引值是1而不是0,而最后一 个索引值等同于'%strlen'给出的值。如果索引值超出了范围,会得到 一个空字符串。 4.3 多行宏: `%macro' 多行宏看上去更象MASM和TASM中的宏:一个NASM中定义的多行宏看上去就 象下面这样: %macro prologue 1 push ebp mov ebp,esp sub esp,%1 %endmacro 这里,定义了一个类似C函数的宏prologue:所以你可以通过一个调用来使 用宏: myfunc: prologue 12 这会把三行代码扩展成如下的样子: myfunc: push ebp mov ebp,esp sub esp,12 在'%macro'一行上宏名后面的数字'1'定义了宏可以接收的参数的个数。 宏定义里面的'%1'是用来引用宏调用中的第一个参数。对于一个有多 个参数的宏,参数序列可以这样写:'%2','%3'等等。 多行宏就像单行宏一样,也是大小写敏感的,除非你使用另一个操作符 &%imacro'
如果你必须把一个逗号作为参数的一部分传递给多行宏,你可以把整 个参数放在一个括号中。所以你可以象下面这样编写代码: %macro silly 2 %2: db %1 %endmacro silly 'a', letter_ letter_a: db 'a' silly 'ab', string_ string_ab: db 'ab' silly {13,10}, crlf: db 13,10 4.3.1 多行宏的重载 就象单行宏,多行宏也可以通过定义不同的参数个数对同一个宏进行多次 重载。而这次,没有对不带参数的宏的特殊处理了。所以你可以定义: %macro prologue 0 push ebp mov ebp,esp %endmacro 作为函数prologue的另一种形式,它没有开辟本地栈空间。 有时候,你可能需要重载一个机器指令;比如,你可能想定义: %macro push 2 push %1 push %2 %endmacro 这样,你就可以如下编写代码:
this line is not a macro call push eax, but this one is 通常,NASM会对上面的第一行给出一个警告信息,因为'push'现在被定义成 了一个宏,而这行给出的参数个数却不符合宏的定义。但正确的代码还是 会生成的,仅仅是给出一个警告而已。这个警告信息可以通过
'-w'macro-params&命令行选项来禁止。(参阅2.1.17)。 4.3.2 Macro-Local Labels NASM允许你在多行宏中定义labels.使它们对于每一个宏调用来讲是本地的:所 以多次调用同一个宏每次都会使用不同的label.你可以通过在label名称前面 加上'%%'来实现这种用法.所以,你可以创建一条指令,它可以在'Z'标志位被 设置时执行'RET'指令,如下: %macro retz 0 jnz %%skip ret %%skip: %endmacro 你可以任意多次的调用这个宏,在你每次调用时的时候,NASM都会为'%%skip' 建立一个不同的名字来替换它现有的名字.NASM创建的名字可能是这个样子 的:'..@2345.skip',这里的数字2345在每次宏调用的时候都会被修改.而 前缀防止macro-local labels干扰本地labels机制,就像在3.9中所 描述的那样.你应该避免在定义你自己的宏时使用这种形式(前缀,然后 是一个数字,然后是一个句点),因为它们会和macro-local labels相互产生 干扰. 4.3.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 int 0x21 %endmacro 那上面使用'writefile'的例子会如预期的那样工作:第一个逗号以前的文本 [filehandle]会被作为第一个宏参数使用,会被在'%1'的所有位置上扩展,而 所有剩余的文本都被合并到'%2'中,放在db后面. 这种宏的贪婪特性在NASM中是通过在宏的'%macro'一行上的参数个数后面加 上'+'来实现的. 如果你定义了一个贪婪宏,你就等于告诉NASM对于那些给出超过实际需要的参 数个数的宏调用该如何扩展; 在这种情况下,比如说,NASM现在知道了当它看到 宏调用'writefile'带有2,3或4个或更多的参数的时候,该如何做.当重载宏 时,NASM会计算参数的个数,不允许你定义另一个带有4个参数的'writefile' 宏. 当然,上面的宏也可以作为一个非贪婪宏执行,在这种情况下,调用语句应该 象下面这样写: writefile [filehandle], {&hello, world&,13,10} NASM提供两种机制实现把逗号放到宏参数中,你可以选择任意一种你喜欢的 形式. 有一个更好的办法来书写上面的宏,请参阅5.2.1 4.3.4 缺省宏参数. NASM可以让你定义一个多行宏带有一个允许的参数个数范围.如果你这样做了, 你可以为参数指定缺省值.比如: %macro die 0-1 &Painful program death has occurred.& writefile 2,%1 mov ax,0x4c01 int 0x21 %endmacro 这个宏(它使用了4.3.3中定义的宏'writefile')在被调用的时候可以有一个 错误信息,它会在退出前被显示在错误输出流上,如果它在被调用时不带参数
,它会使用在宏定义中的缺省错误信息. 通常,你以这种形式指定宏参数个数的最大值与最小值; 最小个数的参数在 宏调用的时候是必须的,然后你要为其他的可选参数指定缺省值.所以,当一 个宏定义以下面的行开始时: %macro foobar 1-3 eax,[ebx+2] 它在被调用时可以使用一到三个参数, 而'%1'在宏调用的时候必须指定,'%2' 在没有被宏调用指定的时候,会被缺省地赋为'eax','%3'会被缺省地赋为 '[ebx+2]'. 你可能在宏定义时漏掉了缺省值的赋值, 在这种情况下,参数的缺省值被赋为 空.这在可带有可变参数个数的宏中非常有用,因为记号'%0'可以让你确定有 多少参数被真正传给了宏. 这种缺省参数机制可以和'贪婪参数'机制结合起来使用;这样上面的'die'宏 可以被做得更强大,更有用,只要把第一行定义改为如下形式即可: %macro die 0-1+ &Painful program death has occurred.&,13,10 最大参数个数可以是无限,以'*'表示.在这种情况下,当然就不可能提供所有 的缺省参数值. 关于这种用法的例子参见4.3.6. 4.3.5 `%0': 宏参数个数计数器. 对于一个可带有可变个数参数的宏, 参数引用'%0'会返回一个数值常量表示 有多少个参数传给了宏.这可以作为'%rep'的一个参数(参阅4.5),以用来遍历 宏的所有参数. 例子在4.3.6中给出. 4.3.6 `%rotate': 循环移动宏参数. Unix的shell程序员对于'shift' shell命令再熟悉不过了,它允许把传递给shell 脚本的参数序列(以'$1,'$2'等引用)左移一个,所以, 前一个参数是&$1'的话 左移之后,就变成&$2'可用了,而在'$1'之前是没有可用的参数的。 NASM具有相似的机制,使用'%rotate'。就象这个指令的名字所表达的,它跟Unix 的'shift'是不同的,它不会让任何一个参数丢失,当一个参数被移到最左边的 时候,再移动它,它就会跳到右边。 '%rotate'以单个数值作为参数进行调用(也可以是一个表达式)。宏参数被循环 左移,左移的次数正好是这个数字所指定的。如果'%rotate'的参数是负数,那么 宏参数就会被循环右移。
所以,一对用来保存和恢复寄存器值的宏可以这样写: %macro multipush 1-* %rep %0 push %1 %rotate 1 %endrep %endmacro 这个宏从左到右为它的每一个参数都依次调用指令'PUSH'。它开始先把它的 第一个参数'%1'压栈,然后调用'%rotate'把所有参数循环左移一个位置,这样 一来,原来的第二个参数现在就可以用'%1'来取用了。重复执行这个过程, 直到所有的参数都被执行完(这是通过把'%0'作为'%rep'的参数来实现的)。 这就实现了把每一个参数都依次压栈。 注意,'*'也可以作为最大参数个数的一个计数,表明你在使用宏'multipush'的 时候,参数个数没有上限。 使用这个宏,确实是非常方便的,执行同等的'POP'操作,我们并不需要把参数 顺序倒一下。一个完美的解决方案是,你再写一个'multipop'宏调用,然后把 上面的调用中的参数复制粘贴过来就行了,这个宏会对所有的寄存器执行相反 顺序的pop操作。 这可以通过下面定义来实现: %macro multipop 1-* %rep %0 %rotate -1 pop %1 %endrep %endmacro 这个宏开始先把它的参数循环右移一个位置,这样一来,原来的最后一个参数 现在可以用'%1'引用了。然后被pop,然后,参数序列再一次右移,倒数第二个 参数变成了'%1',就这样,所以参数被以相反的顺序一一被执行。 4.3.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',它会把第一个1跟第二个分开 这个连结特性还可以用于其他预处理问题中,比如macro-local labels(4.3.2) 和context-local labels(4.7.2)。在所有的情况中,语法上的含糊不清都可以 通过把'%'之后,文本之前的部分放在一个括号中得到解决:所以'%{%foo}bar 会把文本'bar'连接到一个macro-local label:&%%foo'的真正名字的后面(这 个是不必要的,因为就NASM处理macro-local labels的机制来讲,'%{%foo}bar 和%%foobar都会被扩展成同样的形式,但不管怎么样,这个连结的能力是在的) 4.3.8 条件代码作为宏参数。 NASM对于含有条件代码的宏参数会作出特殊处理。你可以以另一种形式 '%+1'来使用宏参数引用'%1',它告诉NASM这个宏参数含有一个条件代码, 如果你调用这个宏时,参数中没有有效的条件代码,会使预处理器报错。 为了让这个特性更有用,你可以以'%-1'的形式来使用参数,它会让NASM把
这个条件代码扩展成它的反面。所以4.3.2中定义的宏'retz'还可以以 下面的方式重写: %macro retc 1 j%-1 %%skip ret %%skip: %endmacro 这个指令可以使用'retc ne'来进行调用,它会把条件跳转指令扩展成'JE', 或者'retc po'会把它扩展成'JPE'。 '%+1'的宏参数引用可以很好的把参数'CXZ'和'ECXZ'解释为有效的条件 代码;但是,'%-1'碰上上述的参数就会报错,因为这两个条件代码没有相 反的情况存在。 4.3.9 禁止列表扩展。 当NASM为你的源程序产生列表文件的时候,它会在宏调用的地方为你展开 多行宏,然后列出展开后的所有行。这可以让你看到宏中的哪些指令展 开成了哪些代码;尽管如此,有些不必要的宏展开会把列表弄得很混乱。 NASM为此提供了一个限定符'.nolist',它可以被包含在一个宏定义中,这 样,这个宏就不会在列表文件中被展开。限定符'.nolist'直接放到参数 的后面,就像下面这样: %macro foo 1.nolist 或者这样: %macro bar 1-5+.nolist a,b,c,d,e,f,g,h 4.4 条件汇编 跟C预处理器相似,NASM允许对一段源代码只在某特定条件满足时进行汇编, 关于这个特性的语法就像下面所描述的: %if&condition& ;if &condition&满足时接下来的代码被汇编。 %elif&condition2& ; 当if&condition&不满足,而&condition2&满足时,该段代码被汇编。 %else
;当&condition&跟&condition2&都不满足时,该段代码被汇编。 %endif '%else'跟'%elif'子句都是可选的,你也可以使用多于一个的'%elif'子句。 4.4.1 `%ifdef': 测试单行宏是否存在。 '%ifdef MACRO'可以用来开始一个条件汇编块,跟在它后面的代码当且仅 当一个叫做'MACRO'单行宏被定义时才会被会汇编。如果没有定义,那么 '%elif'和'%else'块会被处理。 比如,当调试一个程序时,你可能希望这样写代码: ; perform some function %ifdef DEBUG writefile 2,&Function performed successfully&,13,10 %endif ; go and do something else 你可以通过使用命令行选项'-dDEBUG'来建立一个处理调试信息的程序,或 不使用该选项来产生最终发布的程序。 你也可以测试一个宏是否没有被定义,这可以使用'%ifndef'。你也可以在 '%elif'块中测试宏定义,使用'%elifdef'和'%elifndef'即可。 4.4.2 `ifmacro': 测试多行宏是否存在。 除了是测试多行宏的存在的,'%idmacro'操作符的工作方式跟'%ifdef'是一 样的。 比如,你可能在编写一个很大的工程,而且无法控制存在链接库中的宏。你 可能需要建立一个宏,但必须先确保这个宏没有被建立过,如果被建立过了, 你需要为你的宏换一个名字。 如果你定义的一个有特定参数个数与宏名的宏与现有的宏会产生冲突,那么 '%ifmacro'会返回真。比如: %ifmacro MyMacro 1-3 %error &MyMacro 1-3& causes a conflict with an existing macro. %else %macro MyMacro 1-3
; insert code to define the macro %endmacro %endif 如果没有现有的宏会产生冲突,这会建立一个叫'MyMacro 1-3&的宏,如果 会有冲突,那么就产生一条警告信息。 你可以通过使用'%ifnmacro'来测试是否宏不存在。还可以使用'%elifmacro' 和'%elifnmacro'在'%elif'块中测试多行宏。 4.4.3 `%ifctx': 测试上下文栈。 当且仅当预处理器的上下文栈中的顶部的上下文的名字是'ctxname'时,条 件汇编指令'%ifctx ctxname'会让接下来的语句被汇编。跟'%ifdef'一样, 它也有'%ifnctx','%elifctx','%elifnctx'等形式。 关于上下文栈的更多细节,参阅4.7, 关于'%ifctx'的一个例子,参阅4.7.5. 4.4.4 `%if': 测试任意数值表达式。 当且仅当数值表达式'expr'的值为非零时,条件汇编指令'%if expr'会让接 下来的语句被汇编。使用这个特性可以确定何时中断一个'%rep'预处理器循 环,例子参阅4.5。 '%if'和'%elif'的表达式是一个临界表达式(参阅3.8) '%if' 扩展了常规的NASM表达式语法,提供了一组在常规表达式中不可用的 相关操作符。操作符'=','&','&','&=','&='和'&&'分别测试相等,小于,大 于,小于等于,大于等于,不等于。跟C相似的形式'=='和'!='作为'=','&&' 的另一种形式也被支持。另外,低优先级的逻辑操作符'&&','^^',和'||'作 为逻辑与,逻辑异或,逻辑或也被支持。这些跟C的逻辑操作符类似(但C没 有提供逻辑异或),这些逻辑操作符总是返回0或1,并且把任何非零输入看 作1(所以,比如, '^^'它会在它的一个输入是零,另一个非零的时候,总 返回1)。这些操作符返回1作为真值,0作为假值。 4.4.5 `%ifidn' and `%ifidni': 测试文本相同。 当且仅当'text1'和'text2'在作为单行宏展开后是完全相同的一段文本时, 结构'%ifidn text1,text2'会让接下来的一段代码被汇编。两段文本在空格 个数上的不同会被忽略。
'%ifidni'和'%ifidn'相似,但是大小写不敏感。 比如,下面的宏把一个寄存器或数字压栈,并允许你把IP作为一个真实的寄 存器使用: %macro pushparam 1 %ifidni %1,ip call %%label %%label: %else push %1 %endif %endmacro 就像大多数的'%if'结构,'%ifidn'也有一个'%elifidn',并有它的反面的形 式'%ifnidn','%elifnidn'.相似的,'%ifidni'也有'%elifidni',`%ifnidni' 和`%elifnidni'。 4.4.6 `%ifid', `%ifnum', `%ifstr': 测试记号的类型。 有些宏会根据传给它们的是一个数字,字符串或标识符而执行不同的动作。 比如,一个输出字符串的宏可能会希望能够处理传给它的字符串常数或一 个指向已存在字符串的指针。 当且仅当在参数列表的第一个记号存在且是一个标识符时,条件汇编指令 '%ifid'会让接下来的一段代码被汇编。'%ifnum'相似。但测试记号是否是 数字;'%if}

我要回帖

更多关于 js定义数组 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信