参考资料 https://blog.youkuaiyun.com/Lirx_Tech/article/details/42340619
NASM(The Netwide Assembler)汇编编译工具介绍:
1) Netwide Assembler,是目前唯一开源且免费的汇编器;
2) 该汇编器只提供编译的功能,但不提供连接的功能,在Linux下编译器产生.o文件后,还需要使用ld链接器和操作系统的库链接才能形成可执行文件,而在Windows下需要使用MASM的ml链接器连接形成.exe文件;
3) 这里我们先介绍实模式编程,由于Linux以及Windows都是运行在保护模式下的,因此我们会将编译好的实模式程序写进虚拟硬盘的主引导扇区中,然后由虚拟机启动来观察程序运行效果,
在Windows中使用Oracle Virtual Box以及VHD,在Linux中使用Bochs和dd磁盘工具;
4) NASM和MASM一样都是遵从Intel汇编语法,因此指令集(即指令的名称相同)相同、语法相同,仅仅就是内存寻址形式以及其它的一些语法的细微处有不同地方;
汇编地址以及标号的本质:
1) 汇编地址的概念在所有汇编编译器里都是统一的,但不过之前在[Intel汇编-MASM]中没有提到过,因此在这里就需要强调一下;
2) 所谓汇编地址,就是编译器给源程序中每条指令定义的地址,由于编译后的程序可以在内存中浮动(即可以装载在内存中的任意位置),因此直接用绝对地址(20位的实模式下的物理内存地址)来给源程序中的指令定位的话将不利于程序在内存中的浮动;
3) 汇编地址定位规则:
一般规则:
i. 如果在没有使用特殊指令的一般情况下(特别是vstart指令),整个源程序中第一条指令的汇编地址为0,之后所有指令的汇编地址都是相对于整个源程序第一条指令的偏移地址,即使程序中分了很多段也是如此,在这种情况下,如果将整个源程序看做一个段的话则汇编地址就是段内偏移地址;
ii. 在NASM中,所有的标号实质上就是其所在处指令的汇编地址(相对于整个源程序第一条指令的偏移地址),在编译后会将所有标号都替换成该汇编地址值(即立即数);
特殊规则:
i. 如果在定义段的时候使用了vstart指令,比如"section my_segment vstart=15",则会提醒汇编器,该段起始指令的汇编地址是15,段内的其它指令的汇编地址都是和该段起始指令地址的偏移量加上15;
NASM使用关键字section来定义段,后面跟一个段的名称(用户自取),接下来跟一些段的属性定义;因此,vstart伪指令就是规定段的起始汇编地址的指令;
如果vstart=0,则段内的汇编指令就是段内的偏移地址了!!!这种手法经常使用!!
ii. 使用NASM规则的标准段,是指section .data、section .text、section .bss,这三种标准段都默认包含有vstart=0的含义,因此段内的指令以及标号的汇编地址都是段内偏移地址,并且在加载程序的时候会自动使cs执行.text、ds指向.bss、es指向.data而无需人手工来执行对段寄存器赋值的步骤,而对于(特殊规则:i.)中的人全权定义段的方式则没有这种自动的步骤,需要亲手对段寄存器进行赋值!
4) 引用标号:
i. 和MASM不一样的是NASM大大简化了对标号的引用,不需要再用seg和offset对标号取段地址和偏移地址了;
ii. 在NASM中,标号就是一个立即数,而这个立即数就是汇编地址而已,仅此而已!!
iii. 在NASM中不再有MASM中数据标号的概念了,也就不存在什么arr[5]之类的内存寻址形式了!!!!
iv. 在NASM中所有出现标号的地方都会用标号的汇编地址替换,因此诸如mov ax, tag之类的指令,仅仅就是将一个立即数(tag的汇编地址)传送至ax而已,而不是取tag地址内的数据了!!!如果要取标号处内存中的数据就必须使用[ ](类似C语言的指针运算符*),比如[tag]就代表取tag地址内存中的数据;
只有段寄存器中存放的才是真实的物理内存段地址(即16的整数倍),标号全部都是源程序的汇编地址!
这种地址模式有利于程序在内存中浮动,只需要在装载程序(程序装入内存的过程)的时候定义一下段寄存器中的内容即可,其余的偏移地址都能由标号(汇编地址)正确表示);
5) 定义标号:
i. NASM定义标号和MASM定义标号的规则略有不同;
ii. NASM在定义标号是可以不使用冒号也可以不使用;
iii. 如果使用了冒号则会是汇编器将其强制视为标号,如果没使用冒号则汇编器会自行推断;
iv. 汇编器推断是否是标号的规则,首先读入单词,如果单词后跟着一个冒号则将其视为标号,如果没有跟冒号则会先核对其是否是某个指令的名字,如果不是则将其视为标号,因此当标号名和指令名冲突的时候要加冒号,否则可以不加;
v. NASM规则中,如果标号指示的是一段数据的定义也可以使用冒号定义(当然也可以不使用冒号),这点和MASM不同(MASM指示数据定义的标号在定义时一定不能加冒号)
vi. 标号定义的规范:定义数据时不要使用冒号,在指令行中定义标号时使用冒号,这样以示区分指令地址和数据区域的入口;
内存寻址最主要的问题就是提供偏移地址(因为段地址有默认的段寄存器给出或者直接使用段前缀表示法来确定段地址),因此这里全部讨论偏移地址的确定方式,而偏移地址也称为有效地址,因此内存寻址主要解决如何确定偏移地址的问题;
NASM中SECTION的概念 参考资料 https://blog.youkuaiyun.com/ruyanhai/article/details/7179878
SECTION是一种组织代码和存储的方式。
NASM支持标准的.data, .text和.bss,编译后的程序文件中的内存地址顺序是.text, .data,用户自定义section。
NASM支持用户自定义section。
同名的section,编译后会放在同一块连续的内存上。
对用户自定义section,按照出现的先后顺序存储,同名的section存储在一起。
例:
[SECTION .data]
var1 db 0x01
[SECTION .text]
MOV AX, var1
[SECTION .data]
var2 db 0x02
编译后,内存为0xB8040000 0102,其中0xB804是MOV AX,0x04的机器码,0x04是标号var1汇编后的偏移地址。
因为汇编后,var1对应的存储区在.data段,被挪到了内存的尾部,因此偏移不是0x00,而变成了0x04。
每个SECTION默认都是按4字节对齐的:
[SECTION .s1]
var1 db 0x1
[SECTION .s2]
var2 db 0x3
[SECTION .s1]
var3 db 0x2
编译后产生的内存:0x01020000 0x03
可以看到SECTION .s1被扩展为4个字节,后面两个字节填0,然后是SECTION .s2
SECTION的对齐方式可以用ALIGN来调整
$$指向当前section相对于段基址的偏移地址,$指向当前行相对于段基址的偏移地址。