Linker 最终可执行文件是否使用符号表检查变量范围
我试图深入理解链接和加载阶段 当一个翻译单元被编译/组装成一个单一的对象文件时,我知道它会为找到的每个变量/函数创建一个符号表 如果某个变量仅具有文件作用域(例如使用static关键字),它将在符号表中标记为local 但是,当链接器生成最终的可执行文件时,是否存在最终的符号表,其中包含所有文件遇到的每个条目 我很困惑,因为如果我们在一个文件中有一个声明为静态的变量,这意味着只有文件作用域,那么每次在可执行文件中遇到这个变量时,编译器是否必须引用最终的符号表来查看它的实际作用域,还是为它生成特殊的代码 谢谢。这是不正确的: 当一个翻译单元被编译/组装成一个单一的对象文件时,我知道它会为找到的每个变量/函数创建一个符号表Linker 最终可执行文件是否使用符号表检查变量范围,linker,compiler-construction,executable,elf,Linker,Compiler Construction,Executable,Elf,我试图深入理解链接和加载阶段 当一个翻译单元被编译/组装成一个单一的对象文件时,我知道它会为找到的每个变量/函数创建一个符号表 如果某个变量仅具有文件作用域(例如使用static关键字),它将在符号表中标记为local 但是,当链接器生成最终的可执行文件时,是否存在最终的符号表,其中包含所有文件遇到的每个条目 我很困惑,因为如果我们在一个文件中有一个声明为静态的变量,这意味着只有文件作用域,那么每次在可执行文件中遇到这个变量时,编译器是否必须引用最终的符号表来查看它的实际作用域,还是为它生成特殊
目标文件将只包含有关编译单元引用和定义的全局符号的信息 但是,当链接器生成最终的可执行文件时,是否存在最终的符号表,其中包含所有文件遇到的每个条目 可执行文件将包含通用符号(需要在可加载库中定义的符号)。可加载的库只包含通用符号,但它可以定义这些符号并引用它们 如果定义静态变量XYX,则编译时该名称将消失 如果定义了一个全局函数(该函数未在可加载库中导出),则链接时该名称将消失 我在这里所做的一点过于简单化是,编译器和链接器支持可选地包含调试信息,这些信息可能描述处理过程中遇到的所有符号 有关符号的调试信息必须包括有关定义符号的模块的信息 调试信息通常在对象和可执行文件中与运行或链接这些文件所需的信息完全分离。事实上,调试信息通常可以很容易地从这些文件中剥离出来 当一个翻译单元被编译/组装成一个单一的对象文件时,我知道它会为找到的每个变量/函数创建一个符号表 这是最准确的:本地(aka stack,aka automatic storage duration)变量永远不会放入符号表中(除非使用古老的调试格式,例如) 你不必相信我的话:这是一个微不足道的观察:
$ 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_函数\u static
在其名称后面附加了“随机”数。这使得不同函数中的函数静态不会发生冲突a_static
和static_fn
具有LOCAL
链接a_static
和static_fn
时,这样做只是为了帮助调试。本地符号不被后续链接使用,可以安全删除
运行strip之后--剥离不需要的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
。。。我省略了不感兴趣的条目(有很多)
我很困惑,因为如果我们在一个文件中有一个声明为静态的变量,这意味着只有文件作用域,那么每次在可执行文件中遇到这个变量时,编译器是否必须引用最终的符号表来查看它的实际作用域,还是为它生成特殊的代码
在链接阶段,编译器(通常)根本不会被调用。而且链接器不(不需要)注意本地
符号
通常,链接器只做两件事:
main.o
到global\u fn
和a\u global
的引用)解析为它们的定义(在foo.o
中)foo.o
中的a_static
和a_function_static
应用重定位实际上不需要它们的名称;只有它们在.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
$objdump-dr foo.o
o:文件格式elf64-x86-64
第节的分解。正文:
...
000000000000000b:
b:55%rbp
c:48 89 e5 mov%rsp,%rbp
f:48 83欧共体10分$0x10,%rsp
13:b8 00 mov$0x0,%eax
18:e8 e3 ff ff ff callq 0
1d:89 45 fc移动%eax,-0x4(%rbp)
20:8b 15 00 mov 0x0(%rip),%edx#26
22:R_X86_64_PC32.数据
26:8b 45 fc mov-0x4(%rbp),%eax
29:01 c2添加%eax,%edx
2b:8b 05 00 mov 0x0(%rip),%eax#31
2d:R_X86_64_PC32。数据+0x4
31:01 d0添加%edx,%eax
33:c9-Q
34:c3 retq
请注意,偏移量
0x22
和0x2d
处的重定位如何不涉及名称(a_static
和a_function_static.1800
分别)。这取决于:某些符号可以保留用于调试目的“对象文件将仅包含有关全局符号的信息”——这也是不正确的(至少在大多数ELF平台和大多数编译器上)我一直在读到目标文件将包含一个al的符号表
$ 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