我正试图深入了解链接和加载阶段。
当翻译单元被编译/组装成单个目标文件时,我知道它会创建一个找到的每个变量/函数的符号表。
如果变量仅使用static关键字具有文件范围,则它将在符号表中标记为local。
但是,当链接器生成最终的可执行文件时,是否有最终的符号表,并且所有文件都遇到了每个条目?
我很困惑,因为如果我们将一个声明为static的变量意味着只有一个文件中的文件范围,那么每次在可执行文件中遇到此变量时,编译器是否必须引用最终的符号表来查看其实际范围,还是为它生成特殊代码?
非常感谢。
答案 0 :(得分:1)
当翻译单元被编译/组装成单个目标文件时,我知道它会创建一个找到的每个变量/函数的符号表。
大部分准确:本地(也称为堆栈,又称自动存储持续时间)变量永远不会放入符号表中(除非使用古老的调试格式,例如STABS)。
你不需要接受我的话:这是微不足道的观察:
$ cat foo.c
int a_common_global;
int a_global = 42;
static int a_static = 43;
static int static_fn()
{
return 44;
}
int global_fn()
{
int a_local = static_fn();
static int a_function_static = 1;
return a_local + a_static + a_function_static;
}
$ gcc -c foo.c
$ readelf -Ws foo.o
Symbol table '.symtab' contains 14 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 a_static
6: 0000000000000000 11 FUNC LOCAL DEFAULT 1 static_fn
7: 0000000000000008 4 OBJECT LOCAL DEFAULT 3 a_function_static.1800
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 0 SECTION LOCAL DEFAULT 7
10: 0000000000000000 0 SECTION LOCAL DEFAULT 5
11: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM a_common_global
12: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 a_global
13: 000000000000000b 34 FUNC GLOBAL DEFAULT 1 global_fn
这里有一些值得注意的事情:
a_local
未出现在符号表a_function_static
"随机"数字附加到其名称。这是a_function_static
在不同的功能中不会发生碰撞。a_static
和static_fn
有LOCAL
关联另请注意,虽然a_static
和static_fn
出现在符号表中,但这只是 以帮助调试。后续链接不使用本地符号,可以安全删除。
运行strip --strip-unneeded foo.o
后:
$ readelf -Ws foo.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 SECTION LOCAL DEFAULT 1
2: 0000000000000000 0 SECTION LOCAL DEFAULT 3
3: 0000000000000000 0 SECTION LOCAL DEFAULT 4
4: 0000000000000000 0 SECTION LOCAL DEFAULT 5
5: 0000000000000000 0 SECTION LOCAL DEFAULT 6
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM a_common_global
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 a_global
9: 000000000000000b 34 FUNC GLOBAL DEFAULT 1 global_fn
当链接器生成最终的可执行文件时,是否有最终的符号表,所有文件都遇到了每个条目?
是。像这样添加main.c
:
$ cat main.c
extern int global_fn();
extern int a_global;
int a_common_global = 23;
int main()
{
return global_fn() + a_common_global + a_global;
}
$ gcc -c main.c foo.c
$ gcc main.o foo.o
$ readelf -Ws a.out
Symbol table '.symtab' contains 69 entries:
Num: Value Size Type Bind Vis Ndx Name
...我省略了无趣的条目(有很多)。
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
34: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
35: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.c
36: 0000000000201030 4 OBJECT LOCAL DEFAULT 23 a_static
37: 000000000000061c 11 FUNC LOCAL DEFAULT 13 static_fn
38: 0000000000201034 4 OBJECT LOCAL DEFAULT 23 a_function_static.1800
50: 0000000000000627 34 FUNC GLOBAL DEFAULT 13 global_fn
63: 00000000000005fa 34 FUNC GLOBAL DEFAULT 13 main
64: 000000000020102c 4 OBJECT GLOBAL DEFAULT 23 a_global
我很困惑,因为如果我们将一个声明为static的变量意味着只有一个文件中的文件范围,那么每次在可执行文件中遇到此变量时,编译器是否必须引用最终的符号表来查看其实际范围,还是为它生成特殊代码?
在链接阶段,编译器(通常)根本不会被调用。并且链接器不会(不需要)关注LOCAL
个符号。
通常,链接器只做两件事:
global_fn
和a_global
的引用)解析为其定义(此处为main.o
)和在foo.o
中为a_static
和a_function_static
应用重定位并不需要他们的名字;只有他们在foo.o
部分内的偏移,因为这个输出应该清楚:
.data
请注意偏移$ objdump -dr foo.o
foo.o: file format elf64-x86-64
Disassembly of section .text:
...
000000000000000b <global_fn>:
b: 55 push %rbp
c: 48 89 e5 mov %rsp,%rbp
f: 48 83 ec 10 sub $0x10,%rsp
13: b8 00 00 00 00 mov $0x0,%eax
18: e8 e3 ff ff ff callq 0 <static_fn>
1d: 89 45 fc mov %eax,-0x4(%rbp)
20: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 26 <global_fn+0x1b>
22: R_X86_64_PC32 .data
26: 8b 45 fc mov -0x4(%rbp),%eax
29: 01 c2 add %eax,%edx
2b: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 31 <global_fn+0x26>
2d: R_X86_64_PC32 .data+0x4
31: 01 d0 add %edx,%eax
33: c9 leaveq
34: c3 retq
和0x22
的重定位如何不对名称(0x2d
和a_static
分别说明)。
答案 1 :(得分:0)
这是不正确的:
当翻译单元被编译/组装成单个目标文件时,我知道它会创建一个找到的每个变量/函数的符号表。
目标文件只包含有关编译单元引用和定义的全局符号的信息。
但是,当链接器生成最终的可执行文件时,是否有最终的符号表,并且所有文件都遇到了每个条目?
可执行文件将包含通用符号(需要在可加载库中定义的符号)。可加载库只包含通用符号,但它可以定义这些符号并引用它们。
如果定义静态变量XYX,则在编译时该名称将消失。
如果定义全局函数(未在可加载库中导出),则链接时该名称将消失。
我在这里过分简化的一点是,编译器和链接器支持可选的包含可能描述处理中遇到的所有符号的调试信息。
有关符号的调试信息必须包含有关定义符号的模块的信息。
调试信息通常在对象和可执行文件中与运行或链接这些文件所需的信息完全分开。实际上,调试信息通常可以从这些文件中轻松删除。