【问题标题】:Why is it possible to override symbols from some static libraries but not others?为什么可以覆盖某些静态库中的符号而不能覆盖其他库中的符号?
【发布时间】:2018-08-14 00:16:05
【问题描述】:

我正在开发一个链接 Clang 的工具,我需要对某些操作进行少量更改。为了缩短开发时间,而不是重建 Clang,我决定重新定义我的程序代码中感兴趣的符号,并让链接器处理其余部分:in most cases,在两个程序代码中定义的符号的程序版本并且静态库在链接时优先考虑,而不会大惊小怪。 (链接的答案与 Linux 有关,但我发现它通常也可以在 macOS 上工作。)

当我使用可从 LLVM 网站下载的 macOS 库存 Clang 构建时,这非常有用。但是,我目前正在尝试切换到我公司的自定义 Clang(我从源代码构建过一次,并希望以相同的方式进一步修改),现在我得到重复符号错误。

我不知道是什么导致了这个问题。我的项目的链接器标志保持不变(除了一个新的静态库):重要的是,它们不包含-all_load 或其表亲-force_load,它们告诉链接器尝试包含静态库中定义的每个符号。当我在股票档案和自定义档案中使用nm 检查它们时,我试图覆盖的符号的定义方式相同。不同之处在于我构建 LLVM 的方式,但仅仅知道这一点并不能真正帮助我弄清楚我需要改变什么。

例如,假设我想重新定义clang::Qualifiers::getAsString() const。使用股票 LLVM 库我可以做到这一点,但现在我会得到一个重复的符号错误:

duplicate symbol __ZNK5clang10Qualifiers11getAsStringEv in:
  .../Objects-normal/x86_64/TypePrinter.o
  clang+llvm-internal/lib/libclangAST.a(TypePrinter.cpp.o)

使用nm -f darwin 来检查两个档案,对于__ZNK5clang10Qualifiers11getAsStringEv,我会得到非常相似的结果:

# clang+llvm-6.0.0/lib/libclangAST.a
                 (undefined) external __ZNK5clang10Qualifiers11getAsStringEv
0000000000000bb0 (__TEXT,__text) external __ZNK5clang10Qualifiers11getAsStringEv


# clang+llvm-internal/lib/libclangAST.a
                 (undefined) external __ZNK5clang10Qualifiers11getAsStringEv
0000000000000d00 (__TEXT,__text) external __ZNK5clang10Qualifiers11getAsStringEv

那么,假设或多或少相同的符号定义和相同的链接器标志,为什么我以前能够以这种方式覆盖静态库符号,为什么我不再能够?

【问题讨论】:

    标签: macos linker


    【解决方案1】:

    这部分前提并不完全正确:

    In most cases,在程序代码和静态库中定义的符号的程序版本在链接时优先,无需大惊小怪。 (链接的答案与 Linux 有关,但我发现它通常也可以在 macOS 上工作。)

    链接的答案似乎正确,但我最初误解了它。实际行为,如将 -Wl,-why_load 传递给 Clang(或 -why_load 给链接器)所证明的那样,如下所示:

    1. 如果引用了一个符号,请尝试在程序代码中找到它的定义。
      • 如果它在程序代码中定义,你就完成了;不要搜索静态库。
    2. 如果程序代码中没有定义,在静态库中查找.__SYMDEF文件,就知道是哪个目标文件了。
    3. 使用所有该对象文件中的定义。

    问题是切换到自定义 Clang 时,我不小心引入了对在同一目标文件中定义的符号的引用作为我正在重新定义的符号,导致链接器看到这两个定义。我能够通过使用链接器的-why_load 参数来解决问题,然后查找导致问题目标文件被加载的符号。然后我将该符号的定义复制到我的程序中,现在链接器不再抱怨了。

    这个故事的主旨是,这种技术在 macOS 上不如在 Linux 上可靠,而且如果你这样做,你就必须全力以赴。最好获取整个源文件并将其复制到您的项目中,而不是尝试分段选择符号。

    【讨论】:

      【解决方案2】:

      实际上这种行为对于 Linux 来说是相同的,请参阅此复制器:

      第一种情况:构建符号位于不同目标文件中的库:

      //val.cpp  - contains needed symbol
      int val=42;
      
      //wrong_main.cpp - contains duplicate symbol
      int main(){
         return 21;
      }
      
      >>> g++ -c val.cpp -o val.o
      >>> g++ -c wrong_main.cpp -o wrong.o
      >>> ar rcs libsingle.a val.o wrong.o
      

      链接此库有效,不会发出main-error 的多重定义,因为根本没有使用目标文件wrong_main.o 中的符号:

      //main.cpp
      extern int val;
      int main(){
         return val;
      } 
      
      >>> g++ main.cpp -L. -lsingle -o works 
      

      第二种情况:两个符号在同一个目标文件中:

      //together.cpp  - contains both, needed and duplicate, symbols
      #include "val.cpp"
      #include "wrong_main.cpp"
      
      >>> g++ -c together.cpp -o together.o
      >>> ar rcs libtogether.a all.o
      

      链接 libtogether.a 不起作用:

      >>> g++ main.cpp -L. -ltogether -o doesntwork
      ./libtogether.a(all.o): In function `main':
      all.cpp:(.text+0x0): multiple definition of `main'
      /tmp/cc38isDb.o:main.cpp:(.text+0x0): first defined here
      collect2: ld returned 1 exit status
      

      链接器要么从静态库中获取整个目标文件,要么不获取任何内容。在这种情况下,val 是必需的,因此目标文件 together.o 将被使用,但它也包含重复符号 main,因此链接器会发出错误。

      链接器如何在 Linux 上工作(在 MacOS 上非常相似)的一个很好的描述是 this article

      【讨论】:

      • 感谢您的澄清!
      猜你喜欢
      • 2014-05-29
      • 2014-04-19
      • 2012-10-16
      • 1970-01-01
      • 1970-01-01
      • 2020-04-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多