朋友们,我有两个文件, a.c 和 b.c 。 我在 a.c 中定义了一个函数 foo,它是从 b.c. 调用的。
据我了解,当编译器尝试编译 b.c 时,它会发现foo
的实现不在 b 中,因此它将在符号表中添加 foo 的条目,该条目将在链接时解析。我正确理解了这个概念。现在,我在 b.c 中有一个不同的函数 printf,它是在 glibc 中实现的。据我了解, printf 可以在加载时或运行时链接。如果 printf 将在运行时链接,则每次调用 printf 都必须有一个存根,该存根将在运行时使用系统调用进行解析。
我的问题是“我的理解正确吗???+编译器如何确定函数 foo 将由链接器解析而不是在运行时解析???”
我注意到一些类似的问题,但无法理解它们的重要性???
-fPIC
未指定,则编译器只会发出对未定义符号的调用。在这种情况下,链接器将在其他 .o 文件或库中搜索符号,并在链接时插入直接引用,基本上将其粘贴到空白中。这正是您通常构建程序的方式(而不是库)。如果程序使用动态库,则可能存在一些无法在链接时修复的符号。如果是这样,链接器将检查库是否具有它们,并将留给动态链接器在运行时完成作业。 也可以在共享库中执行此操作,只需动态链接器始终在运行时将地址粘贴到程序中,但这样做将意味着
shared
库无法共享:每个程序必须有自己的副本和自己的修复。这就是为什么这种情况不会发生。如果符号不在同一个文件中,并且指定了
-fPIC
is
,则编译器不会直接使用符号名称。相反,它通过 PLT(过程链接表)调用函数,并通过 GOT(全局偏移表)获取其他符号的地址。GOT 是由链接器创建的特殊表,它基本上只是一个未定义符号引用的列表,类似于您在常规非 PIC 程序中找到的符号引用(除了它们通常是到基址的偏移量)得到了)。动态链接器在运行时填补空白。编译器将 GOT 的地址安排在特定的 CPU 寄存器中,以便始终可以找到该表。 PLT 是由链接器创建的一组蹦床。编译器创建到 PLT 的跳转,动态链接器将 PLT 设置为跳转到函数的实际位置。实际上,在很多情况下,加载库时动态链接器不会填充 PLT:PLT 在第一次使用 GOT 调用时会自行填充(它是自修改代码)。
这就是通常使用
-fPIC
编译器
不知道函数何时会被解析。它只知道它自己无法解决它。事实上,当您使用动态库时,链接器甚至不会尝试完全解析符号(尽管我认为它确实检查它们是否在库中定义);这意味着可以通过提供另一个同名函数来覆盖库中的特定函数。像 tsocks
这样的工具将其与 LD_PRELOAD
一起使用来拦截库调用。