elf文件中的.bss段,存放未初始化的全局变量,将.data和.bss分开的理由是为了节约磁盘空间,.bss不占实际的磁盘空间,为什么.bss不占磁盘空间呢?
#include <stdio.h>
int a[1000];
int b[1000] = {1};
int main()
{
printf("123\n");
return 0;
}
这里编写了一个test.c文件,gcc编译gcc test.c -o test之后,使用ls -l test 命令就可以得到可执行文件的信息,文件的带下为12696
ls -l test
-rwxrwxr-x 1 xxx xxx 12696 Dec 1 01:04 test
size test
text data bss dec hex filename
1174 4568 4032 9774 262e test
接着我们修改源程序:
#include <stdio.h>
int a[1000] = {1};
int b[1000] = {1};
int main()
{
printf("123\n");
return 0;
}
ls -l test
-rwxrwxr-x 1 xxx xxx 16696 Dec 1 01:09 test
size test
text data bss dec hex filename
1174 8568 8 9750 2616 test
可以看到大小从12696变成了16696,与之前相比,该文件占据的大小涨了4000字节,这不就是我们的数组a[1000]的大小吗?我们所在的改动仅仅是初始化了a[1000],让这个数组的所在段从.bss段改到了.data段。通过size test命令查看bss段的大小也减小了。这就证明了.bss段中的数据并没有占据磁盘空间,从而节约了磁盘的空间。linux环境下的c语言,初始值为零和没有赋初始值的变量放在BSS段,因为这些值都是零,所以就不需要放到文件里面,等程序加载的时候再赋值就好了。
int a[1000]既然不占据实际的磁盘空间(是指不占据应该分配的内存大小),那么它的大小和符号存在哪呢?
.bss段占据的大小存放在ELF文件格式中的段表(Section Table)中,段表存放了各个段的各种信息,比如段的名字、段的类型、段在elf文件中的偏移、段的大小等信息。 我们可以通过命令readelf -S test来查看test可执行文件的段表:
There are 31 section headers, starting at offset 0x3978:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400356 00000356
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000030 0000000000000018 AI 5 24 8
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003f0 000003f0
0000000000000030 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000400420 00000420
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 0000000000400430 00000430
0000000000000182 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005b4 000005b4
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000004005c0 000005c0
0000000000000008 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000004005c8 000005c8
0000000000000034 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400600 00000600
00000000000000f4 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601040 00001040
0000000000001f60 0000000000000000 WA 0 0 32
[26] .bss NOBITS 0000000000602fa0 00002fa0
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00002fa0
0000000000000035 0000000000000001 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00003865
000000000000010c 0000000000000000 0 0 1
[29] .symtab SYMTAB 0000000000000000 00002fd8
0000000000000678 0000000000000018 30 47 8
[30] .strtab STRTAB 0000000000000000 00003650
0000000000000215 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
这里再额外说明一个很有趣的地方,在elf文件结构中,有一个字符串表.strtab,里面存放的是elf文件中各个段的名字以及变量名等字符串,字符串表中记录了这些字符串以及对应的下标,需要用到这些字符串时,直接用偏移下标去取就行了。段表中存放的段的名字这一项,就是存的.strtab中对应字符串的偏移。
.bss段所占空间的大小存在哪里解决了,那么就剩下符号了。 符号当然对应的存在符号表.symtab中了。我们可以通过命令readelf -s test来查看:
41: 0000000000000000 0 FILE LOCAL DEFAULT ABS
42: 0000000000600e18 0 NOTYPE LOCAL DEFAULT 19 __init_array_end
43: 0000000000600e28 0 OBJECT LOCAL DEFAULT 22 _DYNAMIC
44: 0000000000600e10 0 NOTYPE LOCAL DEFAULT 19 __init_array_start
45: 00000000004005c8 0 NOTYPE LOCAL DEFAULT 17 __GNU_EH_FRAME_HDR
46: 0000000000601000 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
47: 00000000004005b0 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
48: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
49: 0000000000601040 0 NOTYPE WEAK DEFAULT 25 data_start
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
51: 0000000000602000 4000 OBJECT GLOBAL DEFAULT 25 b
52: 0000000000602fa0 0 NOTYPE GLOBAL DEFAULT 25 _edata
53: 00000000004005b4 0 FUNC GLOBAL DEFAULT 15 _fini
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
55: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 25 __data_start
56: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
57: 0000000000601048 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
58: 00000000004005c0 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
59: 0000000000400540 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
在第51行,我们看到了定义的全局数组b[1000],4000那一项表明数据的大小是4000字节,OBJECT代表是一个变量,GLOBAL代表是作用域是全局的。
可以得出结论:
.bss不占据实际的磁盘空间,只在段表中记录大小,在符号表中记录符号。当文件加载运行时,才分配空间以及初始化