【问题标题】:Easy check for unresolved symbols in shared libraries?轻松检查共享库中未解析的符号?
【发布时间】:2010-12-09 16:36:06
【问题描述】:

我正在编写一个相当大的 C++ 共享对象库,但遇到了一个让调试很痛苦的小问题:

如果我在头文件中定义了一个函数/方法,并且忘记为它创建一个存根(在开发过程中),因为我正在构建一个共享对象库而不是一个可执行文件,所以在编译时不会出现错误告诉我忘了实现那个功能。我发现问题的唯一方法是在运行时,当最终链接到该库的应用程序因“未定义符号”错误而崩溃时。

我正在寻找一种简单的方法来检查我是否在编译时拥有我需要的所有符号,也许我可以添加到我的 Makefile 中。

我提出的一个解决方案是通过nm -C -U 运行已编译的库,以获取所有未定义引用的解组列表。问题是这也会列出其他库中的所有引用列表,例如 GLibC,当最终应用程序放在一起时,这些引用当然会与这个库一起链接。可以通过我所有的头文件使用nmgrep 的输出,看看是否有任何对应的名称.. 但这似乎很疯狂。当然这不是一个不常见的问题,有更好的解决方法吗?

【问题讨论】:

  • nm -C -u 多次救了我! (注意我系统上的小写-u。)在此处留下此评论,以便下次需要时可以找到它。

标签: c++ linker shared-libraries


【解决方案1】:

查看链接器选项-z defs / --no-undefined。创建共享对象时,如果有未解析的符号会导致链接失败。

如果您使用 gcc 调用链接器,您将使用编译器 -Wl 选项将选项传递给链接器:

gcc -shared ... -Wl,-z,defs

例如,考虑以下文件:

#include <stdio.h>

void forgot_to_define(FILE *fp);

void doit(const char *filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp != NULL)
    {
        forgot_to_define(fp);
        fclose(fp);
    }
}

现在,如果您将其构建到共享对象中,它将成功:

> gcc -shared -fPIC -o libsilly.so silly.c && echo succeeded || echo failed
succeeded

但是如果你添加-z defs,链接会失败并告诉你你缺少的符号:

> gcc -shared -fPIC -o libsilly.so silly.c -Wl,-z,defs && echo succeeded || echo failed
/tmp/cccIwwbn.o: In function `doit':
silly.c:(.text+0x2c): undefined reference to `forgot_to_define'
collect2: ld returned 1 exit status
failed

【讨论】:

  • +1 这个答案可以帮助那些来自 Windows 背景的人,其中 DLLs 外部符号必须在编译时解析。为漂亮的代码 sn-p 走的路!
  • @ShmilTheCat:嗨,我已经尝试将 -Wl,-z,defs 放入我的 make 文件中,但在运行时仍然出现未定义符号错误,但在编译期间却没有。我能做些什么?。在我的场景中,我有两个文件夹,一个在另一个里面,(比如,A/B,即 B 在 A 里面),我可以在“libA.so”中看到几个符号或 B。我不确定 B 中的每个符号是否都在“libA.so”中可用。我如何确保这一点?
  • @ShmilTheCat 要使内容更像 Windows DLL,您可以将 -Bsymbolic 添加到链接器命令行。即使forgot_to_define 现在由于-z 检查而存在于库中,可执行文件仍然可以用自己的定义覆盖它,并且库自己的定义将转到该覆盖; -Bsymbolic 强制使库自己对其函数的定义转到其函数。
  • 仅适用于实际使用的功能。如果我这样做 // forgot_to_define(fp); 它确实 not 报告错误
  • 在 gcc 6.3 中它说:无法识别的命令行选项'-W1,-z,-defs'
【解决方案2】:

在 Linux 上(您似乎正在使用)ldd -r a.out 应该会为您提供您正在寻找的答案。

更新:创建a.out 以进行检查的简单方法:

 echo "int main() { return 0; }" | g++ -xc++ - ./libMySharedLib.so
 ldd -r ./a.out

【讨论】:

  • 这与我在原始帖子中建议的 nm -C -U 几乎完全相同。问题是没有“a.out”应用程序可言,它是一个共享库,所以 ldd -r mylibrary.so 提供了一大堆输出,因为它使用来自其他各种动态库的符号。我特别感兴趣在 my 头文件中定义的符号丢失,而不是外部库。
  • 这个应该被接受,问题是检查未定义符号的简单方法是什么,而不是如何避免未定义符号
【解决方案3】:

测试套件怎么样?您创建链接到所需符号的模拟可执行文件。如果链接失败,说明你的库接口不完整。

【讨论】:

    【解决方案4】:

    我曾经遇到过同样的问题。我正在用 C++ 开发一个组件模型,当然,组件应该在运行时动态加载。我想到了三个解决方案,这就是我应用的解决方案:

    1. 花一些时间来定义一个能够静态编译的构建系统。你会浪费一些时间来设计它,但它会为你节省很多时间来捕捉这些恼人的运行时错误。
    2. 将您的函数分组到众所周知且易于理解的部分中,以便您可以对函数/存根进行分组,以确保每个对应的函数都有其存根。如果您花时间很好地记录它,您也许可以编写一个检查定义的脚本(例如,通过它的 doxygen cmets)并检查相应的 .cpp 文件。
    3. 执行几个加载相同库集的测试可执行文件,并将 RTLD_NOW 标志指定为 dlopen(如果您在 *NIX 下)。他们会提示缺失的符号。

    希望对您有所帮助。

    【讨论】:

    • 在 RTLD_NOW 的行中,是否存在 env var 来强制立即(非惰性)绑定?
    • 是的。这里是 LD_BIND_NOW (libc5; glibc since 2.1.1) 如果设置为非空字符串,则导致动态链接器在程序启动时解析所有符号,而不是将函数调用解析推迟到第一次引用它们时。这在使用调试器时很有用。
    • 这对于 OP 的目的也很有用:LD_WARN (ELF only)(glibc since 2.1.3) 如果设置为非空字符串,警告未解析的符号。
    • Stefano:是的,没错。这些变量是存在的。但是,如果您将动态加载推迟到以后(例如当用户加载模块时),除非您实际编写加载预期(未来)库的测试程序,否则这些变量将无用。
    猜你喜欢
    • 2017-06-11
    • 2010-10-17
    • 1970-01-01
    • 2014-03-06
    • 2011-04-25
    • 2017-08-02
    • 2016-10-16
    • 1970-01-01
    • 2011-02-28
    相关资源
    最近更新 更多