ELF(Executable Linkable Format)是Linux系统下的一种可执行可链接文件的格式,是COFF格式的变种。在Linux系统中包括了可重定位文件(.o文件),可执行文件(/bin/bash文件),共享目标文件(.so)和核心转储文件(core dump)。
ELF文件头(ELF Header):位于ELF文件的头部,包含了描述整个文件的基本属性。
代码段(.text):用于存放程序代码,只读属性
数据段(.text):用于存放程序中经初始化的全局变量和静态局部变量,读写属性
bss段(.bss):用于存放程序中未经初始化的全局变量和静态局部变量。在目标文件中,这个段并不占据实际空间,它仅仅只是一个占位符。
为了更好的探究bss段的一些细节,我们来看以下的一段代码(SimpleTest.c)
先使用gcc -c SimpleTest.c生成目标文件SimpleTest.o
使用objdump -h SimpleTest.o查看目标文件的结构和内容:
从图中我们可以看出,bss段只有“ALLOC”,没有“CONTENTS”,这表明这个段在这个文件中并没有实际内容。另外bss段的长度也足以使我们产生疑问:为什么bss段的长度是4而不是8?在SimpleTest.c文件中未初始化的全局变量和静态局部变量总共占了8字节,那为什么存放这两个变量的bss段的长度只有4呢?
为了解决这个问题,我们可以使用readelf -S SimpleTest.o来查看文件中符号表
Ndx表示的是段在ELF文件中的下标,其中的4便是bss段。通过readelf我们可以看到,static_var2是存放在bss段中的,而global_uninit_var却并没有存放于任何段中,只是一个未定义的“COMMON”符号。这涉及到了强符号和弱符号以及编译器的具体实现。
在C/C++语言的编译器中,默认函数名和初始化的全局变量是强符号,未初始化的全局变量为弱符号。由于C/C++语言编译器允许多个文件模块中出现同名的不同类型的弱符号,所以在编译期间,编译器并不能正确的知道这个弱符号所占的大小是多少,所以只能使用一个未定义的COMMON符号来代替。只有在链接期间,链接器在链接所有文件模块后,才能得出这些同名不同类型的弱符号所需占用的空间大小。举个例子,假设现在有两个文件模块a.o和b.o,其中a.o中有一个int类型名为global的未初始化全局变量,而b.o中有一个double类型也名为global的未初始化的全局变量。这样在编译过程中,由于编译器无法知道这个名为global的全局变量究竟应该占多大的内存空间,所以它使用了一个COMMEN来表示。在链接的过程中,链接器在对这两个模块进行链接后,知道了这个global变量最大需要分配8个字节的空间,此时global变量在bss段中所需的大小也就确定了。
ELF文件头(ELF Header):位于ELF文件的头部,包含了描述整个文件的基本属性。
代码段(.text):用于存放程序代码,只读属性
数据段(.text):用于存放程序中经初始化的全局变量和静态局部变量,读写属性
bss段(.bss):用于存放程序中未经初始化的全局变量和静态局部变量。在目标文件中,这个段并不占据实际空间,它仅仅只是一个占位符。
为了更好的探究bss段的一些细节,我们来看以下的一段代码(SimpleTest.c)
int printf(const char* format,...);
int global_init_var = 100;
int global_uninit_var;
void func(int i)
{
printf("%d\n",i);
}
int main(void)
{
static int static_var = 101;
static int static_var2;
int a = 1;
int b;
func(static_var + static_var2 + a + b);
return 0;
}
先使用gcc -c SimpleTest.c生成目标文件SimpleTest.o
使用objdump -h SimpleTest.o查看目标文件的结构和内容:
从图中我们可以看出,bss段只有“ALLOC”,没有“CONTENTS”,这表明这个段在这个文件中并没有实际内容。另外bss段的长度也足以使我们产生疑问:为什么bss段的长度是4而不是8?在SimpleTest.c文件中未初始化的全局变量和静态局部变量总共占了8字节,那为什么存放这两个变量的bss段的长度只有4呢?
为了解决这个问题,我们可以使用readelf -S SimpleTest.o来查看文件中符号表
Ndx表示的是段在ELF文件中的下标,其中的4便是bss段。通过readelf我们可以看到,static_var2是存放在bss段中的,而global_uninit_var却并没有存放于任何段中,只是一个未定义的“COMMON”符号。这涉及到了强符号和弱符号以及编译器的具体实现。
在C/C++语言的编译器中,默认函数名和初始化的全局变量是强符号,未初始化的全局变量为弱符号。由于C/C++语言编译器允许多个文件模块中出现同名的不同类型的弱符号,所以在编译期间,编译器并不能正确的知道这个弱符号所占的大小是多少,所以只能使用一个未定义的COMMON符号来代替。只有在链接期间,链接器在链接所有文件模块后,才能得出这些同名不同类型的弱符号所需占用的空间大小。举个例子,假设现在有两个文件模块a.o和b.o,其中a.o中有一个int类型名为global的未初始化全局变量,而b.o中有一个double类型也名为global的未初始化的全局变量。这样在编译过程中,由于编译器无法知道这个名为global的全局变量究竟应该占多大的内存空间,所以它使用了一个COMMEN来表示。在链接的过程中,链接器在对这两个模块进行链接后,知道了这个global变量最大需要分配8个字节的空间,此时global变量在bss段中所需的大小也就确定了。