所有的GNU汇编程序伪指令都以句号(.)打头,后面接上伪指令的名字,通常是由小写字母组成。

不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)或伪操作(Pseudo-operation),由于它不是真正的指令所以加个“伪”字。

一、段相关伪指令

.section

.section伪指令是所有汇编程序伪指令中最为重要的指令之一,主要作用是定义内存段。

.section指示把代码划分成若干个段(Section),程序被操作系统加载执行时,每个段被加载到不同的地址,操作系统可以对不同的段设置不同的读、写或执行权限。每一个段以段名为开始,以下一个段名或者文件结尾为结束。

.section name [, "flags"[, @type[,flag_specific_arguments]]]

参数name表示段的名字。

对于ELF文件格式来说,flags参数(注意使用的时候需要将参数包在一对双引号之间)有以下几种取值,且可以按照任何组合使用:

  • a:该段可被分配。
  • w:该段可写。
  • x:该段可执行。
  • M:该段可被合并(如果有别的段和这个段属性一样的话)。
  • S:该段包含“\0”结尾的字符串。
  • G:该段是某个段组中的一员。
  • T:该段被用于线程本地化存储(TLS,Thread Local Storage)。

.subsection

这是一个ELF段栈操作命令,每一个ELF段都可以包含数个子段。.subsection伪指令的语法如下:

.subsection name

参数name是一个子段的名称。.subsection伪指令用于将当前的子段替换成name命名的子段,但是当前的段并不会改变。所以,该伪指令后面的所有汇编指令将被放置到当前段中的name命名的子段内。同时,在定义这个子段之前的当前段的前一个子段将被放到段栈的顶部。

.pushsection

这也是一个ELF段栈操作命令,该伪指令语法如下:

.pushsection name [, subsection] [, "flags"[, @type[,arguments]]]

.pushsection伪指令会将当前的段及其子段放到段栈的顶端,同时将当前的段替换成参数name指明的段,如果subsection参数存在的话,也会将当前的子段替换成subsection指明的子段。当然,前提是该段及其子段必须事先使用.section伪指令和.subsection伪指令定义过。

其它flags、type和arguments参数的作用和在前面.section伪指令中的一样。

.popsection

该伪指令没有任何参数,其作用是将当前的段(及其子段)替换成段栈顶的段(及其子段),并且将这个存放在栈顶的段(及其子段)删除掉。

.previous

该伪指令没有任何参数,作用是使当前段(及其子段)和放在段栈顶的段(及其子段)交换位置。 多个连续的.previous伪指令会在当前的段(及其子段)和栈顶的段(及其子段)之间反复切换。 

例如,对于以下伪指令:

.section A
.subsection 1
    # Now in section A subsection 1
    .word 0x1234
.section B
.subsection 0
    # Now in section B subsection 0
    .word 0x5678
.subsection 1
    # Now in section B subsection 1
    .word 0x9abc
.previous
    # Now in section B subsection 0
    .word 0xdef0
.previous
    # Now in section B subsection 1 again

最后两个.previous伪指令将连续在B段0子段和B段1字段之间切换。

.org

.org伪指令的语法如下:

.org new-lc , fill

指定从当前段的位置加上new-lc参数指定的字节数后的位置开始存放代码,并且从当前地址到新的位置之间的内存单元用fill参数指定的数据进行填充,如果fill没指定,则默认用0填充。

.org伪指令只能增加位置计数器的值,或者不变也可以,但是不能向后移动,否则编译的时候会报错。

.org伪指令无法执行跨段的操作,只能在本段中移动位置。

二、字符串相关伪指令

.ascii

该伪指令分配一段专门存放字符串的空间,其语法如下:

.ascii "string"...

.ascii伪指令后面接上0个或多个用逗号隔开的字符串作为参数。该伪指令会把每个字符串(结尾不自动加“\0”字符)中的字符放在连续的地址空间上。

.asciz

.asciz伪指令和前面说.ascii伪指令的功能非常相似,只不过会自动在每个字符串后面加上一个“\0”字符(z代表0)。

.string

.string伪指令本身等同于.asciz伪指令。

不过.string指令可能会有一些变种,如:

.string16 "str"
.string32 "str"
.string64 "str"

和基本的.string伪指令不同的是,字符串中的每一个8位字符都会被扩展成16位、32位或64位,并且扩展的字节序规则遵循目标系统的。

例如,对于如下伪指令:

.string32 "BYE"

其等价于:

.string   "B\0\0\0Y\0\0\0E\0\0\0"  /* 小端字节序  */
.string   "\0\0\0B\0\0\0Y\0\0\0E"  /* 大端字节序  */

三、数据定义相关伪指令

.byte

该伪指令语法如下:

.byte expressions

参数可以是0个或多个以逗号分割的单字节(8位)数据。该伪指令会把所有数据放到从当前位置开始的连续地址空间上。

.hword、.short

.hword和.short伪指令完全等价,其语法及作用和.byte伪指令基本相同,只不过参数是0个或多个以逗号分割的半字(16位)数据。

.int、.long、.word

.int、.long和.word伪指令完全等价,其语法及作用和.byte伪指令基本相同,只不过参数是0个或多个以逗号分割的4字节(32位)数据。

.quad

该伪指令的语法如下:

.quad bignums

参数是0个或多个以逗号分割的大数,对于每一个大数,将从当前位置开始分配连续8个字节的空间存放。

.octa

该伪指令和.quad伪指令的语法和功能基本相同,只不过对于每一个大数,将从当前位置开始分配连续16个字节的空间存放。

.single、.float

.single和.float伪指令的功能完全一样,其语法如下:

.single flonums
.float flonums

参数可以是0个或多个以逗号分割的单精度浮点数(32位)。该伪指令会把所有数据放到从当前位置开始的连续地址空间上。

.double

该伪指令和前面介绍的.single和.float伪指令作用基本相同,只不过参数是0个或多个以逗号分割的双精度浮点数(64位)。

四、数据填充相关伪指令

.fill

该伪指令的语法如下:

.fill repeat , size , value

参数repeat、size和value都是常量表达式。.fill伪指令的作用是反复拷贝size个字节,重复repeat次。 其中,repeat参数可以大于或者等于0,size也是要大于等于0,但最多不能超过8,如果超过8,也只取8。 size个字节的内容将被填充为参数value指定的值。如果size的大小大于value的存储所需要的容量,则将高位用0来填充。

参数size和value为可选项。如果第二个逗号和value不存在,则默认全都填充为0。如果第一个逗号和之后的size及value都不存在,则假定 size为1。

.space、.skip

.space和.skip伪指令的功能完全相同,它们的语法如下:

.space size , fill
.skip size , fill

用参数fill的值填充参数size指定字节数的连续空间。如果没有fill参数的话,默认用0填充。

五、符号相关伪指令

符号在汇编程序中代表一个地址或一个常量值,可以用在汇编指令中。汇编程序经过汇编器的处理之后,代表地址的符号都会被替换成它所代表的地址值。在C语言中我们通过变量名访问一个变量,其实就是读写某个地址的内存单元,我们通过函数名调用一个函数,其实就是跳转到该函数第一条指令所在的地址,所以变量名和函数名都是符号,本质上是代表内存地址的。

Linux汇编程序中的符号只能由小写字母(a~z)、大写字母(A~Z)、数字(0~9)、“.”和“_”等字符组成。当标号为0~9的数字时为局部符号。由于局部符号可以重复出现,如果直接使用可能会引起歧义,因此可以使用如下方法进行引用:

  • 数字符号后面加上f:在引用的地方之前的符号(如0f)。
  • 数字符号后面加上b:在引用的地方之后的符号(如1b)。

.global、.globl

.global和.globl伪指令功能完全相同,它们的语法如下:

.global symbol
.globl symbol

.global或.globl伪指令告诉汇编器,声明的symbol这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号,这样这个符号就可以被外部使用。所以,如果你在某个局部程序中声明了全局symbol,那么与这个局部程序链接的其它局部程序也能访问到这个symbol符号。需要注意的是,该伪指令只是声明了一个符号,并没有定义该符号,如果该符号没有在任何文件中被定义的话,那链接的时候会报错。

.local

和前面说的伪指令功能刚好相反,会将一个符号声明成局部的,也就是只有在本汇编程序文件中访问,别的文件访问不到。

这个伪指令一般不会用到,因为如果定义了一个符号,但没有声明其是全局的,那么它默认将是一个局部符号。

.weak

该伪指令的语法如下:

.weak names

.weak伪指令会将names包含的一组以逗号分割的符号都声明为弱符号,只是声明并没有定义,并且这个弱符号也是可以被其它文件访问到的。

和前面说的全局符号不同的是,如果一个声明的弱符号没有在别的地方被定义的话,那编译的时候并不会报错。

.set、.equ

.set和.equ伪指令功能完全相同,它们的语法如下:

.set symbol, expression
.equ symbol, expression

该伪指令的作用是定义一个参数symbol指定的符号,并将expression的值赋给这个符号。前面也提到过,这个符号默认将是一个局部符号。

.equiv

该伪指令语法如下:

.equiv symbol, expression

.equiv伪指令和前面介绍的.set、.equ伪指令基本相同,只不过如果符号symbol已经被定义则编译的时候会报错。

.type

该伪指令用来设置一个符号的属性值,其语法如下:

.type name , type description

参数name是符号的名字,接着一个逗号,后面是要设置符号的属性。属性一般有以下三种:

  • %function:该符号用来表示一个函数名。
  • %object:该符号用来表示一个数据对象。
  • %tls_object:该符号用来表示一个线程本地存储数据对象。

六、数据对齐相关伪指令

一般汇编语言在编译和链接的时候都是在某个段及其子段中按顺序放置的,中间不会有什么间隙。但是,有时候必须要求某些代码必须满足一些对齐的规则,比如必须4K对齐,那被放置的地址必须低12位都是0。这个时候就需要使用对齐相关的伪指令。

.balign

该伪指令的语法如下:

.balign[wl] num_bytes [, fill_value]

第一个参数num_bytes指明要对齐的字节数,其数值必须是2的指数,例如2、4、8、16等。

第二个参数fill_value是可选的,如果指定的话,会用这个数值来填充当前位置到指定对齐位置之间的空隙。填充的规则如下:

  • 默认情况下,没有任何w或l后缀的话,会把参数fill_value当做1个字节数据,然后逐字节填充。
  • 如果有w后缀的话,会将参数fill_value当做2个字节的数据,然后两个两个字节的填充。
  • 如果有l后缀的话,会将参数fill_value当做4个字节的数据,然后四个四个字节的填充。

如果第二个参数fill_value没有指定的话,且伪指令没有w或l后缀,还有.balign伪指令前面放的是代码而不是数据,那么会默认使用空指令(NOP)填充。

其它情况下,都默认使用数据0来填充。也就是说,如果该伪指令包含了w或l后缀,即使没有指定fill_value参数,也一定会用数据0来填充。

.p2align

该伪指令的语法如下:

.p2align[wl] exponent [, fill_value]

可以看出来.p2align和.balign的语法非常像,只不过第一个参数exponent表示的是指数,而不是具体的数值。

举个例子,如果要保证4K对齐的话,下面两条伪指令是等价的:

.balign 4096
.p2align 12

.align

该伪指令的语法如下:

align exponent [, fill_value]

.align伪指令和.p2align的功能完全一样,可以理解为是.p2align伪指令的另一个别名。但是,需要注意的是.align伪指令后面不能加w或l。

七、条件判断相关伪指令

.if、.elseif、.else和.endif伪指令组合起来使用可以允许按照某些条件来放置不同的汇编指令和其它的伪指令,它们的语法如下:

.if[modifier] expression
// ...
[.elseif expression
// ...]
[.else
// ...]
.endif

.if伪指令用来开始一个条件判断,每一个.if伪指令都需要有一个配对的.endif伪指令。.if伪指令可以在后面跟一个.else伪指令,当.if伪指令后面的条件不满足时,将只放置.else后面的汇编代码和其它伪指令。.if伪指令后也可以跟一个或多个.elseif条件判断伪指令,进行多重条件判断。

.if伪指令有很多种变种,将它们总结如下表:

.if伪指令及其变种用处
.if absolute expression 条件表达式不为0时才会放置后面的汇编指令和伪指令。
.ifne absolute expression 和.if伪指令作用一样。
.ifeq absolute expression条件表达式为0时才会放置后面的汇编指令和伪指令。
.ifge absolute expression条件表达式大于等于0时才会放置后面的汇编指令和伪指令。
.ifle absolute expression条件表达式小于等于0时才会放置后面的汇编指令和伪指令。
.ifgt absolute expression条件表达式大于0时才会放置后面的汇编指令和伪指令。
.iflt absolute expression条件表达式小于0时才会放置后面的汇编指令和伪指令。
.ifb text参数为空时才会放置后面的汇编指令和伪指令。
.ifnb text参数不为空时才会放置后面的汇编指令和伪指令。
.ifc string1,string2当两个字符串相等时才会放置后面的汇编指令和伪指令。
.ifnc string1,string2当两个字符串不等时才会放置后面的汇编指令和伪指令。
.ifeqs string1,string2和.ifc伪指令作用基本相同,只不过字符串必须要包含在一对双引号中。
.ifnes string1,string2和.ifnc伪指令作用基本相同,只不过字符串必须要包含在一对双引号中。
.ifdef symbol如果符号被定义了才会放置后面的汇编指令和伪指令。

.ifndef symbol                                       

.ifnotdef symbol

如果符号没有被定义了才会放置后面的汇编指令和伪指令。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐