.section .data
汇编程序中以.
开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)或伪操作(Pseudo-operation),由于它不是真正的指令所以加个“伪”字。.section
指示把代码划分成若干个段(Section),程序被操作系统加载执行时,每个段被加载到不同的地址,操作系统对不同的页面设置不同的读、写、执行权限。.data
段保存程序的数据,是可读可写的,相当于C程序的全局变量。本程序中没有定义数据,所以.data
段是空的。
.section .text
.text
段保存代码,是只读和可执行的,后面那些指令都属于.text
段。
.globl _start
_start
是一个符号(Symbol),符号在汇编程序中代表一个地址,可以用在指令中,汇编程序经过汇编器的处理之后,所有的符号都被替换成它所代表的地址值。在C语言中我们通过变量名访问一个变量,其实就是读写某个地址的内存单元,我们通过函数名调用一个函数,其实就是跳转到该函数第一条指令所在的地址,所以变量名和函数名都是符号,本质上是代表内存地址的。
.globl
指示告诉汇编器,_start
这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号。_start
就像C程序的main
函数一样特殊,是整个程序的入口,链接器在链接时会查找目标文件中的_start
符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序都要提供一个_start
符号并且用.globl
声明。如果一个符号没有用.globl
声明,就表示这个符号不会被链接器用到。
_start:
这里定义了_start
符号,汇编器在翻译汇编程序时会计算每个数据对象和每条指令的地址,当看到这样一个符号定义时,就把它后面一条指令的地址作为这个符号所代表的地址。而_start
这个符号又比较特殊,它所代表的地址是整个程序的入口地址,所以下一条指令movl $1, %eax
就成了程序中第一条被执行的指令。
下面重点讨论讨伪指令 .align
一个值得讨论的伪指令是.align,它可能在很多时候不被人注意,但是不恰当的使用将导致程序无法运行,这种可能性在ARM系统上几乎是百分之百的发生。
.align的作用在于对指令或者数据的存放地址进行对齐,有些CPU架构要求固定的指令长度并且存放地址相对于2的幂指数圆整,否则程序无法正常运行,比如ARM;有些系统却不需要,如果不遵循地址的圆整规则,程序依然可以正确执行,只是降低了一些执行效率,比如i386。.align的作用范围只限于紧跟它的那条指令或者数据,而接下来的指令或者数据的地址由上一条指令的地址和其长度决定。这里给出一个很好的用来测试.align作用的例子,首先在i386上进行测试。
.section .text #定义代码段
data:
.byte 0x11
.align 2
.globl _start
_start:
movl data, %ebx
movl $1, %eax
int $0x80
这个程序没有实际的应用意义。为了防止编译出的目标文件中不同的段均从0开始而看不到.align的效果,这里只定义一个代码段,.byte数据将被编译进代码段,_start中的第一条指令将紧跟在0x11数据之后,我们使用 as -o test.o test.S && objdump -D test.o来查看反汇编的结果:
Disassembly of section .text:
00000000 :
0: 11 8d 76 00 bb 00 adc %ecx,0xbb0076(%ebp)
00000004 <_start>:
4: bb 00 00 00 00 mov $0x0,%ebx
9: a1 00 00 00 00 mov 0x0,%eax
e: cd 80 int $0x80
objdump尝试将代码段中的所有二进制数据当作指令解析,所以不要关心非代码段反汇编后的指令adc %ecx,0xbb0076(%ebp)。我们需要关心的是mov $0x0,%ebx所在的地址4,显然它和.align指定的4可以除尽,也即相对齐于4的倍数的地址。为了作一比较,移除.align 4,得到以下的反汇编结果,显然此时的第一条mov指令的对齐地址是1。
Disassembly of section .text:
00000000 :
0: 11 bb 00 00 00 00 adc %edi,0x0(%ebx)
00000001 <_start>:
1: bb 00 00 00 00 mov $0x0,%ebx
6: a1 00 00 00 00 mov 0x0,%eax
b: cd 80 int $0x80
对于i386编译器来说.align的参数4直接指明了对齐地址的圆整目标,汇编器总是要找到下一个可以整除该参数的地址作为.align后的那条指令的存放地址。.align参数的取值必须是2的幂指数,2^0到2^31都是合法的,而其它的值将会遭遇编译错误的提示:
# as -o test.o test.S
test.S: Assembler messages:
test.S:4: Error: alignment not a power of 2
对于ARM编译器来说.align的用法和i386有相当大的差异,这也就是.align被混乱使用的根本原因。将上面的汇编程序改成ARM平台的汇编语言后的结果如下:
.section .test
data:
.byte 0x11
.align 2
.global _start
_start:
mov r0, #data
mov r7, #1
svc 0x00000000
与它对应的反编译结果如下:
# arm-linux-as asm.S -o asm.o && arm-linux-objdump -D asm.o
......
Disassembly of section .test:
00000000 <data>:
0: 00000011 .word 0x00000011
...
00000010 <_start>:
10: e3a00000 mov r0, #0 ; 0x0
14: e3a07001 mov r7, #1 ; 0x1
18: ef000000 svc 0x00000000
1c: 00000000 andeq r0, r0, r0
Disassembly of section .ARM.attributes:
......
出乎意料的是mov r0,4 #0指令的地址是0x10,也即16,不是4。ARM汇编器并不直接使用.align提供的参数作为对齐目标,而是使用2^n的值,比如这里的参数为4,那么圆整对象为2^4 = 16。这也就是为什么在ARM平台的Uboot或者Linux内核汇编中会出现.align 5的根本原因。.align此时的取值范围为0-15,当取值为0,2或者不提供参数时均圆整于4。如果尝试使用大于15的值,将会得到编译器的如下抱怨:
# arm-linux-as asm.S -o asm.o
asm.S: Assembler messages:
asm.S:4: Error: alignment too large: 15 assumed
另一点需要注意的是:ARM尝试使用0来填充.byte占用的一个字(ARM平台为4bytes)中剩余的比特位,但是其余的字将会用nop指令填充,这一点与i386也是不同的。以上的讨论关注于目标文件.o,而非最终的可执行文件,一个对于链接器的猜疑是它会不会改变我们的圆整目标,庆幸的是它不会,它会找到当前.o文件中的最大圆整值,并且以它的倍数偏移。由ARM支持非对齐地址的访问,所以指定指令的对齐即可保证程序的正确执行,但要保证程序的访问效率,对数据的对齐也是尤为重要。
如果分析过Uboot,可能会遇到类似.balignl的操作,对于是用如此生僻指令的开发习惯,这并不是好的做法,除非是迫不得已,否则它可以用更通用的指令代替。.balignl指令是.balign指令的变体,一个完整的指令是格式如下:
.balign[wl] align, fill_value, max_padding
.balign的所有参数均是可选的,align参数类细雨.align伪指令参数,但是这里它直接作为对齐参数,类似于i386平台的.align参数。fill_value用来填充空白字节的值,没有提供该参数,默认将是用nop指令填充。max_padding指定了最多可填充的字节数,如果实际需要填充的字节数大于该值那么整个.balign伪指令将失效。.balign使用字节来填充,比如.balign 4,0x10;.balignw使用双字节来填充,比如.balignw 4,0x1122;.balignl使用四字节来填充,比如.balignl 4,0x11223344。注意所有需要填充的字节数必须是[wl]参数指定的整倍数,否则将遭遇汇编器的抱怨:
.byte 0x11
.balignl 16,0x33445566
.global _start
_start:
mov r7, #1
swi 0x0000000
# arm-linux-as arm.S -o arm.o
arm.S: Assembler messages:
arm.S:3: Error: alignment padding (15 bytes) not a multiple of 4
arm.S:3: Error: alignment padding (15 bytes) not a multiple of 4
为了保证整倍数关系,如果删除.byte 0x11或者改成如下代码,抱怨将消失:
.byte 0x11
.ascii "123"
.balignl 16,0x33445566
.global _start
_start:
mov r7, #1
swi 0x0000000
# arm-linux-as arm.S -o arm.o
注意 .ascii和.asciz的区别是,.asciz会在字符串后自动添加结束符\0.