全局变量
一般我们认为未初始化的全局变量和静态局部变量都是存放在.bss段中,初始化了的全局变量和静态局部变量存放在.data段中。
实验环境
host:Ubuntu 20.04
gcc:gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
//a.c
int a;
int b=10;
extern int c;
int main(void)
{
static int static_init_var = 10;
static int static_uninit_var;
swap(&c,&b);
}
//b.c
int c = 100;
void swap(int *a, int *b)
{
int *temp;
temp = a;
a = b;
b = temp;
}
gcc -c a.c -fno-stack-protector
gcc -c b.c -fno-stack-protector
ld a.o b.o -e main -o ab
现在来看一下三个elf文件的符号信息。
$ readelf -s a.o b.o ab
File: a.o
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS a.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_uninit_var.1917
6: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_init_var.1916
7: 0000000000000000 0 SECTION LOCAL DEFAULT 6
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 5
11: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM a
12: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 b
13: 0000000000000000 39 FUNC GLOBAL DEFAULT 1 main
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND c
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND swap
File: b.o
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS b.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 7
8: 0000000000000000 0 SECTION LOCAL DEFAULT 4
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 c
10: 0000000000000000 43 FUNC GLOBAL DEFAULT 1 swap
File: ab
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000004001c8 0 SECTION LOCAL DEFAULT 1
2: 0000000000401000 0 SECTION LOCAL DEFAULT 2
3: 0000000000402000 0 SECTION LOCAL DEFAULT 3
4: 0000000000404000 0 SECTION LOCAL DEFAULT 4
5: 0000000000404008 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 FILE LOCAL DEFAULT ABS a.c
8: 0000000000000000 0 FILE LOCAL DEFAULT ABS b.c
9: 0000000000404000 4 OBJECT GLOBAL DEFAULT 4 b
10: 0000000000401027 55 FUNC GLOBAL DEFAULT 2 swap
11: 0000000000404004 4 OBJECT GLOBAL DEFAULT 4 c
12: 0000000000404008 0 NOTYPE GLOBAL DEFAULT 5 __bss_start
13: 0000000000401000 39 FUNC GLOBAL DEFAULT 2 main
14: 0000000000404008 0 NOTYPE GLOBAL DEFAULT 4 _edata
15: 0000000000404010 0 NOTYPE GLOBAL DEFAULT 5 _end
16: 0000000000404008 4 OBJECT GLOBAL DEFAULT 5 a
readelf -s 输出的Ndx列表示的是所在段的编号,可以通过readelf -t来查看。
$ readelf -t a.o b.o ab
File: a.o
There are 13 section headers, starting at offset 0x3b8:
Section Headers:
[Nr] Name
Type Address Offset Link
Size EntSize Info Align
Flags
[ 0]
NULL 0000000000000000 0000000000000000 0
0000000000000000 0000000000000000 0 0
[0000000000000000]:
[ 1] .text
PROGBITS 0000000000000000 0000000000000040 0
0000000000000027 0000000000000000 0 1
[0000000000000006]: ALLOC, EXEC
[ 2] .rela.text
RELA 0000000000000000 00000000000002e8 10
0000000000000048 0000000000000018 1 8
[0000000000000040]: INFO LINK
[ 3] .data
PROGBITS 0000000000000000 0000000000000068 0
0000000000000008 0000000000000000 0 4
[0000000000000003]: WRITE, ALLOC
[ 4] .bss
NOBITS 0000000000000000 0000000000000070 0
0000000000000004 0000000000000000 0 4
[0000000000000003]: WRITE, ALLOC
[ 5] .comment
PROGBITS 0000000000000000 0000000000000070 0
000000000000002c 0000000000000001 0 1
[0000000000000030]: MERGE, STRINGS
[ 6] .note.GNU-stack
PROGBITS 0000000000000000 000000000000009c 0
0000000000000000 0000000000000000 0 1
[0000000000000000]:
[ 7] .note.gnu.property
NOTE 0000000000000000 00000000000000a0 0
0000000000000020 0000000000000000 0 8
[0000000000000002]: ALLOC
[ 8] .eh_frame
PROGBITS 0000000000000000 00000000000000c0 0
0000000000000038 0000000000000000 0 8
[0000000000000002]: ALLOC
[ 9] .rela.eh_frame
RELA 0000000000000000 0000000000000330 10
0000000000000018 0000000000000018 8 8
[0000000000000040]: INFO LINK
[10] .symtab
SYMTAB 0000000000000000 00000000000000f8 11
0000000000000198 0000000000000018 11 8
[0000000000000000]:
[11] .strtab
STRTAB 0000000000000000 0000000000000290 0
0000000000000055 0000000000000000 0 1
[0000000000000000]:
[12] .shstrtab
STRTAB 0000000000000000 0000000000000348 0
000000000000006c 0000000000000000 0 1
[0000000000000000]:
File: b.o
There are 12 section headers, starting at offset 0x290:
Section Headers:
[Nr] Name
Type Address Offset Link
Size EntSize Info Align
Flags
[ 0]
NULL 0000000000000000 0000000000000000 0
0000000000000000 0000000000000000 0 0
[0000000000000000]:
[ 1] .text
PROGBITS 0000000000000000 0000000000000040 0
000000000000002b 0000000000000000 0 1
[0000000000000006]: ALLOC, EXEC
[ 2] .data
PROGBITS 0000000000000000 000000000000006c 0
0000000000000004 0000000000000000 0 4
[0000000000000003]: WRITE, ALLOC
[ 3] .bss
NOBITS 0000000000000000 0000000000000070 0
0000000000000000 0000000000000000 0 1
[0000000000000003]: WRITE, ALLOC
[ 4] .comment
PROGBITS 0000000000000000 0000000000000070 0
000000000000002c 0000000000000001 0 1
[0000000000000030]: MERGE, STRINGS
[ 5] .note.GNU-stack
PROGBITS 0000000000000000 000000000000009c 0
0000000000000000 0000000000000000 0 1
[0000000000000000]:
[ 6] .note.gnu.property
NOTE 0000000000000000 00000000000000a0 0
0000000000000020 0000000000000000 0 8
[0000000000000002]: ALLOC
[ 7] .eh_frame
PROGBITS 0000000000000000 00000000000000c0 0
0000000000000038 0000000000000000 0 8
[0000000000000002]: ALLOC
[ 8] .rela.eh_frame
RELA 0000000000000000 0000000000000210 9
0000000000000018 0000000000000018 7 8
[0000000000000040]: INFO LINK
[ 9] .symtab
SYMTAB 0000000000000000 00000000000000f8 10
0000000000000108 0000000000000018 9 8
[0000000000000000]:
[10] .strtab
STRTAB 0000000000000000 0000000000000200 0
000000000000000a 0000000000000000 0 1
[0000000000000000]:
[11] .shstrtab
STRTAB 0000000000000000 0000000000000228 0
0000000000000067 0000000000000000 0 1
[0000000000000000]:
File: ab
There are 10 section headers, starting at offset 0x3250:
Section Headers:
[Nr] Name
Type Address Offset Link
Size EntSize Info Align
Flags
[ 0]
NULL 0000000000000000 0000000000000000 0
0000000000000000 0000000000000000 0 0
[0000000000000000]:
[ 1] .note.gnu.property
NOTE 00000000004001c8 00000000000001c8 0
0000000000000020 0000000000000000 0 8
[0000000000000002]: ALLOC
[ 2] .text
PROGBITS 0000000000401000 0000000000001000 0
000000000000005e 0000000000000000 0 1
[0000000000000006]: ALLOC, EXEC
[ 3] .eh_frame
PROGBITS 0000000000402000 0000000000002000 0
0000000000000058 0000000000000000 0 8
[0000000000000002]: ALLOC
[ 4] .data
PROGBITS 0000000000404000 0000000000003000 0
0000000000000008 0000000000000000 0 4
[0000000000000003]: WRITE, ALLOC
[ 5] .bss
NOBITS 0000000000404008 0000000000003008 0
0000000000000008 0000000000000000 0 4
[0000000000000003]: WRITE, ALLOC
[ 6] .comment
PROGBITS 0000000000000000 0000000000003008 0
000000000000002b 0000000000000001 0 1
[0000000000000030]: MERGE, STRINGS
[ 7] .symtab
SYMTAB 0000000000000000 0000000000003038 8
0000000000000198 0000000000000018 9 8
[0000000000000000]:
[ 8] .strtab
STRTAB 0000000000000000 00000000000031d0 0
000000000000002d 0000000000000000 0 1
[0000000000000000]:
[ 9] .shstrtab
STRTAB 0000000000000000 00000000000031fd 0
0000000000000052 0000000000000000 0 1
[0000000000000000]:
经过这里可以分析,在编译之后未链接之前,已经初始化的全局变量和静态局部变量都是在.data段,未经初始化的静态局部变量是在.bss段,
而未经初始化的全局变量是放在common块,common块最早来源于Fortran,早期Fortran没有动态分配空间的机制,程序员必须事先声明它所需的零时使用空间大小。Fortran把这种空间叫做common块 —《程序员的自我修养》
gcc的-fno-common允许我们把所有未初始化的全局变量不以common块的形式处理,或者以int global attribute((nocommon));一旦一个未初始化的全局变量不是以common块的形式存在,那么他就相当于一个强符号,如果其他目标文件中还有同一个变量的强符号定义,链接时就会发生符号重定义的错误。
但是在链接之后,通过readelf -s ab可以看出初始化的全局变量和静态局部变量依旧在.data段,未经初始化的全局变量和静态局部变量都在.bss段了。
原因分析
这里涉及到弱符号与强符号的概念,在链接期间,如果有多个同名的强符号则会出现重定义的错误,如果有多个同名的弱符号只有一个强符号,则以强符号的空间大小来分配空间,如果有没有强符号,则以最大的弱符号为主来分配空间。未初始化的全局变量就是一个弱符号,所以在编译期间,它并没有被放在.bss段,原因是在链接的时候有可能会遇到其他编译单元的同名符号,编译期间无法确定其分配大小,但是再链接期间,所有编译单元各段都组合好,此时可以确定符号所需要的确定空间,链接结束就会将其放入.bss段中。