解析库中的符号

库、链接、初始化和 C++ 系列的第 2 部分

作者:Darryl Gove 和 Stephen Clamage,2011 年 5 月

第 1 部分 — 库和链接简介
第 2 部分 — 解析库中的符号

链接应用程序时,链接程序检查应用程序中的所有符号,以验证这些符号是否能解析成链接行中指定的库中的符号。引发这一操作的标志是 -z defs,只要链接应用程序,就会自动包括此标志。链接库时,默认情况下 包括此标志。这意味着,即使不能解析库的所有外部依赖项,也可照常链接库。

ldd 是用于检查可执行文件或库链接到哪些共享对象的命令。清单 1 显示在主可执行文件和其中一个共享库上运行 ldd 的结果。

清单 1:使用 ldd 识别链接的库
  $ ldd main
  lib2.so =>       codes/library/lib2.so
  lib1.so =>       codes/library/lib1.so
  libc.so.1 =>     /lib/libc.so.1
  libm.so.2 =>     /lib/libm.so.2
  $ ldd lib1.so
  $

输出显示该应用程序依赖于 lib1.solib2.solibc.solibm.so。当 ldd 用于 lib1.so 时,它报告该库没有依赖项。应当按照与应用程序相同的方式对库进行链接,以便记录它们的运行时依赖关系。通过在库的链接行上传递 -z defs,即可开始识别未解析的符号,如清单 2 所示。

清单 2:使用 -z defs 识别未解析的依赖项
  $ cc -G -Kpic lib1.c -o lib1.so -z defs
  Undefined                       first referenced
  symbol                             in file
  printf                              lib1.o
  ld: fatal: Symbol referencing errors. No output written to lib1.so

错误信息显示 printf() 在库中有未解析的符号。这可以通过使用 -lc 在链接行上显式列出 libc.so 来修复,如清单 3 所示。

清单 3:将 libc 显式列为库的依赖项
  $ cc -G -Kpic lib1.c -o lib1.so -lc -z defs
  $ ldd lib1.so
  libc.so.1 =>     /lib/libc.so.1

列出 libc.so 后,链接继续,并将 libc.so 记录为该库的依赖项。

建议在链接行包括相关的库,并使用标志 -z defs 创建库。这样做可以确保应用程序的任何支持库中不会有任何丢失的符号。如果未采用此方法,则可能要到链接主应用程序时才能检测到未解析的符号,或者在动态加载库的情况下,可能要到运行时才能检测到。清单 4 显示经过修改后存在未解析依赖项的 lib1.c 代码。

清单 4:经过修改后存在未解析依赖项的库代码
  $ more lib1.c
  #include ‹stdio.h›

  void widget();

  void f()
  {
  printf("In library 1\n");
  widget();
  }

清单 5 显示了库的编译过程,以及随后编译应用程序的过程。链接应用程序时,链接程序会报告未解析的符号。

清单 5:编译使用了带有未解析符号的库的应用程序
  $ cc -G -Kpic lib1.c -o lib1.so
  $ cc -o main main.c -L. -R'$ORIGIN' -l1 -l2
  Undefined                       first referenced
  symbol                             in file
  widget                              ./lib1.so
  ld: fatal: Symbol referencing errors. No output written to main

本文所讨论的方法在构建依赖库的应用程序时非常有用。不过,应用程序的链接步骤给了链接程序检查所有已解析符号的机会。当应用程序在运行时通过调用 dlopen() 加载库时,这些方法将变得非常重要。在这种情况下,构建库时使用显式依赖项并使用标志 -z defs 警告存在未解析符号将非常重要。加载并非所有符号均已解析的库可能会导致应用程序失败。

另外值得调查的是,应用程序是否链接了任何未被使用的库。对应用程序或库使用实用程序 ldd-r -U 选项时,ldd 将报告未使用的库以及已使用但当前未解析的库。

建议总结

  • 应使用链接程序选项 -z defs 来链接库,以便早期检测未解析的符号。
  • 链接库时,应在链接行显式列出该库所需要的库。列出所有需要的库有助于链接程序确定库的正确初始化顺序。
  • 对库使用 ldd -r -U 可验证库是否有不需要的依赖项或未解析的符号。
修订版 1,2011 年 4 月 27 日