Assembly 指示符
汇编指示符通常以特殊字符或关键字开头,并且不同的汇编器支持的汇编指示符可能不同
ORG 指示符
ORG 指示符用于设置程序的起始地址。它可以让程序从指定的内存地址开始运行。例如:
ORG 0x7C00
上述代码使用 ORG 指示符将程序的起始地址设置为 0x7C00,这是在引导扇区中很常见的做法。
%assign指示符
%assign
是一个预处理器指令,它的作用是将一个符号常量与一个值相关联。当程序进行编译时,所有对该符号的引用都将被替换为该值。这个过程在编译期间执行,而不是在运行时。
在大多数情况下,%assign
被用来定义常量。这些常量可以用于各种目的,例如:
- 在程序中使用标记来标识某些代码段
- 定义数组大小和其他数据结构的大小
- 定义寄存器或内存地址
下面是一个使用%assign
定义常量的示例:
%assign BUFFER_SIZE 64 ; 定义一个名为BUFFER_SIZE的符号常量,其值为64
%assign BASE_ADDRESS 0x4000 ; 定义一个名为BASE_ADDRESS的符号常量,其值为0x4000
在上面的例子中,我们定义了两个符号常量:BUFFER_SIZE
和BASE_ADDRESS
。这些符号常量可以被程序中的任何地方引用,例如:
mov eax, [BASE_ADDRESS] ; 将从地址0x4000开始的值加载到eax寄存器中
add ecx, BUFFER_SIZE ; 将ecx寄存器的当前值增加64
在这个例子中,BASE_ADDRESS
和BUFFER_SIZE
被分别用于寻址和计算数组长度。
需要注意的是,符号常量在程序编译期间就被固定下来了。因此,一旦定义后,它就不能再被修改(此处说得不能再修改是指不能在运行时赋值,但是你可以在编译期反复定义,每次定义初始化不同的值也是允许的,程序只会保留最后一个%assign)。如果需要运行时改变值,可以使用宏定义或其他方法来实现。
%equ 指示符
equ 是一种指示符,它用于定义一个符号常量,类似于 C 语言中的 #define 常量。通过 equ 指示符,可以避免在程序中直接使用数字或字符串等常量,提高程序的可读性和可维护性。例如:
PI equ 3.1415926
上述代码将定义一个名为 PI 的符号常量,它的值为 3.1415926。在程序中使用 PI 常量时,它将被替换为该常量的值。
%define 指示符
%define 是一种宏指令,它允许将一个字符串替换为另一个字符串或数字。在使用 %define 指令时,可以为一个字符串或数字定义一个符号常量,并将其替换为另一个字符串或数字。例如:
%define PI 3.1415926
上述代码将定义一个名为 PI 的符号常量,它的值为 3.1415926。在程序中使用 PI 常量时,它将被替换为该常量的值。
区别
在 NASM 汇编语言中,%assign
、%equ
和 %define
都是预处理器指令,用于定义符号常量。它们的主要区别在于:
-
%assign
用于定义一个可重复赋值的符号常量,例如可以使用%assign MY_CONST 10
定义MY_CONST
符号常量,并且在程序运行期间可以重新分配并更改该符号常量的值。 -
%equ
用于定义一个不可重复赋值的符号常量,例如可以使用%equ MY_CONST 10
定义MY_CONST
符号常量,但在程序运行期间不能更改该符号常量的值,否则会发出警告。 -
%define
也用于定义一个可重复赋值的符号常量,但与%assign
不同的是,它允许符号常量的定义包含文本替换宏(text replacement macro)或函数宏(function macro),从而可以执行更复杂的符号替换操作。
例如,以下示例展示了这三个指令的使用方式及其效果:
; 使用 %assign 定义可重复赋值符号常量
%assign MAX_SIZE 100
%assign LOOP_COUNT 10
%assign MAX_SIZE 200 ;
; 使用 %equ 定义不可重复赋值符号常量
%equ MY_CONST 10
; MY_CONST = 20 ; 无法更改已定义的符号常量
; 使用 %define 定义可重复赋值符号常量和文本替换宏
%define ADD(x,y) x + y
section .text
global _start
_start:
mov eax, ADD(10, 20) ; 将 ADD(10, 20) 替换为 10 + 20
总之,在 NASM 中,%assign
、%equ
和 %define
可以用于定义符号常量,但它们的用途和限制略有不同,需要根据实际需求进行选择。
数据指示符
在数据段分配数据空间有两种方式:①RES’X’系列指示符,但是仅仅分配空间,不初始化空间;②D’X’系列指示符,定义数据空间的同时给定一个初始值。
两者中的X可以由以下字母替代:
‘X’ | 单位 | 解释 |
---|---|---|
B | 字节 | Define Byte |
W | 字(双字节 ) | Define Word |
D | 双字(四字节 ) | Define Doubleword |
Q | 四字(八字节 ) | Define Quadword |
T | 十字节 | Define Ten Bytes |
RES’X’系列指示符
使用方式为:identifier RES’X’ size,其中 identifier 是变量名,size 是要分配的字节数。
RESB
.data
buffer RESB 100 ; 预留 100 个字节的内存空间,用于存储数据
RESW
.data
numList RESW 10 ; 预留 20 个字节的内存空间,用于存储 10 个字数据
RESD
.data
dataBlock RESD 5 ; 预留 20 个字节的内存空间,用于存储 5 个双字数据
RESQ
.data
bigNum RESQ 1 ; 预留 8 个字节的内存空间,用于存储1个四字数据
REST
.data
myData REST 1 ; 预留 10 个字节的内存空间,用于存储1个十个字节的数据
上述代码使用 RESB 指令定义了一个名为 buffer 的字节型变量,它预留了 100 个字节的内存空间。程序可以通过 buffer 变量来访问和操作这些内存单元。
RESW
D’X’系列指示符
DB
.data
char1 DB 'A' ; 定义一个字节字符变量,初始值为 'A'
byteArr DB 10h, 20h, 30h, 40h ; 定义一个字节数组,初始值为 10h, 20h, 30h, 40h
DW
.data
num1 DW 1234h ; 定义一个字数值变量,初始值为 1234h
wordArr DW 10h, 20h, 30h, 40h ; 定义一个双字整数数组,初始值为 10h, 20h, 30h, 40h
DD
.data
num1 DD 12345678h ; 定义一个双字数值变量,初始值为 12345678h
intArr DD 10, 20, 30, 40 ; 定义一个双字整数数组,初始值为 10, 20, 30, 40
DQ
.data
num1 DQ 1234567890ABCDEFh ; 定义一个八字数值变量,初始值为 1234567890ABCDEFh
doubleArr DQ 10e-10, 20e-20, 30e-30, 40e-40 ; 定义一个双精度浮点数数组,初始值为 10e-10, 20e-20, 30e-30, 40e-40
DT
.data
str DT 'baichao' ; 定义一个十个字节字符串变量,初始值为 'baichao'
dataBlock DT 10h, 20h, 30h, 40h, 50h, 60h, 70h, 80h, 90h, 100h
; 上面这行代码定义了一个名为 dataBlock 的十个字节数据块。它不是一个数组,而是一个连续的内存空间,其中包含了 10 个字节的数据。
TIMES 指示符
TIMES
用于重复指定次数的操作或指令。例如:
myArr TIMES 5 dd 0
上述代码创建了一个名为 myArr
的数组,包含五个双字大小(每个双字占四个字节)的元素,每个元素初始值为 0。
实际上,TIMES
可以与各种类型的操作和指令结合使用,以执行特定次数的重复操作,例如:
TIMES 3 ADD dword ptr [eax], 1 ; 将 eax 寄存器指向的内存单元增加 1,重复三次
.text 指示符
.text 指示符用于定义代码段(Code Segment),即存放程序中执行代码的内存区域。代码段中包括各种汇编语句和指令,例如 mov、add、jmp 等。例如:
.text
global _start
_start:
mov eax, 1
xor ebx, ebx
int 0x80
上述代码使用 .text 指示符定义了一个 _start 标签,表示程序的入口点。在 _start 标签下方的指令将会被编译成机器指令,并放到代码段中。
.data 指示符
.data 指示符用于定义数据段(Data Segment),即存放程序中各种全局变量和静态数据的内存区域。数据段通常包括 DB、DW、DD、DQ 等数据类型的变量或数组等。例如:
.data
msg DB "Hello, World!", 0
count DW 10
array DD 100 DUP (0)
上述代码使用 .data 指示符定义了三个变量,分别是 msg 字符串、count 整数和 array 数组。这些变量在程序运行期间保持不变,因此需要放在数据段中。
.bss 指示符
.bss 指示符用于定义 BSS 段(Block Started by Symbol),用于保存没有初始值或初值为0的全局变量和静态变量,当程序加载时,bss段中的变量会被初始化为0。这个段并不占用物理空间——因为完全没有必要,这些变量的值固定初始化为0,因此何必占用宝贵的物理空间。因此,通过使用 BSS 段来存储初始值为0的变量,可以节省可执行文件的大小和内存的消耗,使得程序在加载和运行时更加高效。例如:
.bss
buffer resb 1024
上述代码使用 .bss 指示符定义了一个名为 buffer 的字节数组,长度为 1024。由于该数组没有被初始化,因此它的所有元素都被自动设置为零。
.bss和.data的区别
.bss 和 .data 用于定义不同类型的数据段,其中 .bss 定义了未初始化变量的内存空间,而 .data 定义了已初始化常量的内存空间。
因此,可以将 .bss 中的变量看作是程序运行期间需要动态分配空间的变量,它们的值在程序执行前不确定。而.data 中的常量则是程序运行期间不会改变的数值,它们的值在程序编译时就已经确定。
需要注意的是,在一些汇编语言中,.data 还可以存储可修改的全局变量,所以并不能完全把 .data 区域理解为只存储常量。但是,通常建议将常量放在 .data 中,变量放在 .bss 中,以便更好地管理和维护程序代码。
.debug段
顾名思义,用于保存调试信息。
.dynamic段
用于保存动态链接信息。
.fini段
用于保存进程退出时的执行程序。当进程结束时,系统会自动执行这部分代码。
.init段
用于保存进程启动时的执行程序。当进程启动时,系统会自动执行这部分代码。
.rodata段
用于保存只读数据,如const修饰的全局变量、字符串常量。
.symtab段
用于保存符号表。
section 指示符
.section
是一个汇编语言中的伪指令,它的作用是定义和切换不同代码段。在大多数汇编语言中,程序通常包含 .data
、.bss
和 .text
这三个不同的代码段(有些编程语言还可能增加其他的代码段),每个代码段都有其特定的属性和作用:
.data
代码段通常用于声明初始化的数据,例如变量、数组等。.bss
代码段通常用于声明未初始化的数据,例如全局变量、数组等,这些变量在程序执行前该段内存空间会被置为 0。.text
代码段用于存储程序的实际代码。
下面是一个示例程序,展示了如何使用 .section
定义不同的代码段:
.section .data
myVar db "Hello, world!", 0
.section .bss
myArray resw 10
.section .text
global _start
_start:
mov eax, 4
mov ebx, 1
mov ecx, myVar
mov edx, 13
int 0x80
mov eax, 1
xor ebx, ebx
int 0x80
上述示例程序中,.data
代码段定义了一个名为 myVar
的字符串变量,并初始化为字符串 “Hello, world!”。.bss
代码段定义了一个名为 myArray
的字型数组,大小为 20 字节(10 个字)。.text
代码段存储了程序的实际代码,其中 _start
标号表示程序的入口点,通过执行系统调用来将 myVar
输出到控制台,并退出程序。
除了上述示例程序中定义的几个标准的代码段之外,一些汇编器也允许使用自定义的代码段名称,以便更好地组织和管理代码。下面是一个示例程序,展示了如何使用自定义的代码段:
.section .rodata
myString db "This is a read-only string", 0
.section .custom_code
global myFunction
myFunction:
push ebp
mov ebp, esp
mov eax, 123
pop ebp
ret
上述示例程序中,.rodata
代码段定义了一个名为 myString
的字符串变量,并标记为只读。.custom_code
代码段定义了一个名为 myFunction
的函数,并将其标记为可导出符号。
值得注意的是,在不同汇编语言中,.section
可能存在差异,需要参考所使用汇编器的手册或文档。
.global/.extern 指示符
.global
和 .extern
都是汇编语言中的伪指令,用于定义和引用符号。
.global
的详细定义与用法:
在汇编语言中,.global
是一个伪指令,用于将一个标号(symbol)定义为全局符号,也就是可以在其他代码段或文件中使用的符号。当使用 .global
声明一个符号时,该符号可以被其他代码段或文件引用,这样就可以实现程序的模块化设计和重用性支持。一般来说,.global
用于将函数名或变量名声明为全局符号。
下面是一个示例程序,展示了如何使用 .global
定义全局符号:
.section .text
.global myPrintf
myPrintf:
; 这里是函数的具体代码
ret
上述示例程序中,使用 .global
将 myPrintf
标号定义为全局符号,可以在其它代码段、文件或项目中调用该函数。
.extern
的详细定义与用法:
在汇编语言中,.extern
是一个伪指令,用于声明外部定义的符号,并将其引入当前代码段中。所谓的外部符号,就是在其他代码段或文件中定义的符号,例如函数名、变量名等。
当程序需要访问其他代码段或文件中定义的符号时,可以使用 .extern
声明该符号,并将其引入到当前代码段中。这样,在程序执行时,就可以正确地解析并调用该符号了。
下面是一个示例程序,展示了如何使用 .extern
定义外部符号:
.extern myPrintf; 声明外部符号 myPrintf
.section .data
myString db "Hello, world!", 0
.section .text
global _start
_start:
push myString ; 将字符串地址压入栈中
call myPrintf; 调用外部函数 myPrintf
add esp, 4 ; 清空栈
mov eax, 1 ; 设置系统调用号 1(即 exit)
xor ebx, ebx ; 返回码为 0
int 0x80 ; 执行系统调用
上述示例程序中,在 .extern
指令中声明了一个名为 myPrintf
的外部符号,然后在 .text
代码段中调用了该符号。该示例程序通过调用外部函数 myPrintf
将字符串 “Hello, world!” 输出到控制台,之后退出程序。
需要注意的是,在不同的汇编器中,.global
和 .extern
的语法和用法可能存在差异。在一些汇编语言中,可能会使用其他类似的指令来实现全局符号和外部符号的定义和引用,例如 .export
、.public
、.import
、.include
等。
.align 指示符
在汇编语言中,.align
是一个伪指令,用于将下一个数据或指令对齐到某个边界上。所谓的对齐,就是使数据或指令占据的字节长度恰好是某个值的倍数,通常为 2、4、8、16 等。
一般情况下,CPU 处理器读取大块内存时会按照特定的字节长度进行操作,如果没有进行适当的对齐,则可能会导致性能问题或者错误发生。因此,在编写汇编代码时,使用 .align
指令来保证数据和指令对齐是非常重要的。
.align
的详细定义与用法如下:
.align N
其中,N
表示所需对齐的字节数,必须是 2 的幂(即 2、4、8、16 等)。该指令将当前代码段插入适当数量的 NOP 操作码,使下一个数据或指令的地址对齐到 N
字节的边界上。
下面是一些例子,展示了不同场景下如何使用 .align
来进行数据和指令对齐:
- 对齐数据
.section .data
myArray:
.byte 0x11, 0x22, 0x33, 0x44 ; 未对齐的数组
.align 4 ; 将下一个数据对齐到 4 字节边界上
myAlignedArray:
.byte 0x11, 0x22, 0x33, 0x44 ; 对齐后的数组
上述示例程序中,未对齐的 myArray
数组占用了 4 个字节,但其开始地址并不是 4 的倍数。使用 .align
指令将下一个数据对齐到 4 字节边界上后,新的数组 myAlignedArray
开始地址恰好是 4 的倍数。
- 对齐指令
.section .text
.global _start
_start:
mov eax, 10 ; 执行一条未对齐的指令
add eax, 20 ; 执行另一条未对齐的指令
.align 4 ; 将下一个指令对齐到 4 字节边界上
alignedInstruction:
jmp _start ; 对齐后的跳转指令
上述示例程序中,执行的前两条指令未进行对齐,可能会降低程序的性能。使用 .align
指令将下一个指令对齐到 4 字节边界后,新的指令 alignedInstruction
的开始地址恰好是 4 的倍数。
需要注意的是,在实际应用中,由于 CPU 处理器的架构和操作系统的限制等因素,对齐操作可能会引起一些微妙的问题,需要根据具体的情况进行选择和调整。