在链接时或运行时解析引用?

时间:2012-04-03 07:49:43

标签: gcc compiler-construction linker runtime

朋友们,我有两个文件,a.c和b.c。 我在a.c中定义了一个函数foo,它是从b.c。

调用的

根据我的理解,当编译器尝试编译bc时,它会看到foo的实现不在b中,因此它将在符号表中为foo添加一个要解析的os的条目在连接时间。我理解这个概念。

现在,我在b.c中有一个不同的函数printf,它在glibc中实现。据我所知,printf可以在加载时或运行时链接。如果printf将在运行时链接,则每次调用printf都必须有一个存根,它将在运行时使用系统调用解析。

我的问题是"我的理解是否正确??? +编译器如何确定函数foo将由链接器而不是在运行时解析???"

我注意到一些类似的问题,但在这里无法理解它们的意义???

1 个答案:

答案 0 :(得分:3)

我发现你的问题有点难以阅读,所以我不太清楚你是如何理解它的,所以我只是描述它是如何工作的。

  1. 如果符号在同一个文件(b.c)中,则编译器直接引用它。链接器不用于解决任何问题。

  2. 如果符号不在同一文件中,且-fPIC 未指定,则编译器只会调用未定义的符号。在这种情况下,链接器将在其他.o文件或库中搜索符号,并在链接时插入直接引用,方法是将其基本粘贴到空白处。

    这正是您通常构建程序的方式(而不是库)。如果程序使用动态库,则可能存在一些在链接时无法修复的符号。如果是这样,链接器将检查库是否具有它们,并且它将留给动态链接器以在运行时完成作业。

    也可以在共享库中完成此操作,只有动态链接器始终在运行时将地址粘贴到程序中,但这样做意味着共享库不能不要分享:每个程序都必须有自己的副本,并有自己的修复程序。这就是为什么不会发生这种情况。

  3. 如果符号不在同一文件中,并且-fPIC 指定,则编译器不直接使用符号名称。相反,它通过PLT(过程链接表)调用函数,并通过GOT(全局偏移表)获取其他符号的地址。

    GOT是一个由链接器创建的特殊表,它基本上只是一个未定义的符号引用列表,类似于您在常规非PIC程序中找到的那些(除了它们通常偏移到GOT的基础)。动态链接器在运行时填充空白。编译器安排GOT的地址始终位于特定的CPU寄存器中,以便始终可以找到该表。

    PLT是由链接器创建的一组蹦床。编译器创建跳转到PLT,动态链接器设置PLT以反弹到函数的实际位置。实际上,在很多情况下,当加载库时,动态链接器不会填充PLT:PLT在第一次使用GOT(它的自修改代码)调用时自动填充。

    这就是动态库通常使用-fPIC构建的原因:可以为每个程序修改GOT和PLT,同时仍然保持库的文本不被修改,从而允许它们保持共享。

  4. 所以,现在回答你的问题:

    我认为你谈到的'存根'可能是PLT?

    编译器 知道何时解析函数。它只知道它本身无法解决它。实际上,当您使用动态库时,链接器甚至不会尝试完全解析符号(尽管我认为它确实检查它们是否在库中定义);这意味着可以通过提供具有相同名称的另一个函数来覆盖库中的特定函数。像tsocks这样的工具使用LD_PRELOAD来拦截库调用。