轻松检查共享库中未解析的符号?


82

我正在编写一个相当大的C ++共享对象库,并且遇到了一个使调试困难的小问题:

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

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

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


3
nm -C -u救了我多次!(请注意-u系统上的小写字母。)在此处保留此注释,以便下次需要时可以找到它。
dpritch '17

Answers:


93

检出链接器选项-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

3
+1这个答案可以帮助那些来自Windows背景的人,他们必须在编译期间解析DLLs外部符号。不错的代码段的方式!
Shmil The Cat 2014年

@ShmilTheCat:嗨,我尝试将-Wl,-z,defs放入我的make文件中,但仍然在运行时出现未定义的符号错误,但在编译过程中却没有。我能做什么?。在我的场景中,我有两个文件夹,一个在另一个文件夹内(例如,A / B,即B在A里面),并且在“ libA.so”中看不到几个符号或B。我不确定B中的每个符号是否在“ libA.so”中可用。我如何确保?
Vinay

@ShmilTheCat若要使事情更像Windows DLL,可以将其添加-Bsymbolic到链接器命令行中。即使forgot_to_define通过-z检查,库中现在存在该可执行文件,它仍然可以使用其自己的定义覆盖它,并且库自己的定义将被覆盖。-Bsymbolic强制执行操作,以使库本身对其功能的定义进入其功能。
卡兹

仅适用于实际使用的功能。如果我做// forgot_to_define(fp);它并没有报告错误
agentsmith

17

在Linux(您似乎正在使用)上,它ldd -r a.out应该可以为您提供所需的确切答案。

UPDATE:创建a.out要检查的简单方法:

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

4
这几乎与我在原始帖子中建议的nm -C -U完全相同。问题是没有“ a.out”应用程序可以说,它是一个共享库,因此ldd -r mylibrary.so提供了完整的输出,因为它使用了来自其他各种动态库的符号。.我特别感兴趣在我的头文件而不是外部库中定义的符号缺失中。
戴维·克拉里奇

1
这个应该被接受,问题是检查未定义符号的简单方法是什么,而不是如何避免未定义符号的方法
workplaylifecycle


3

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

  1. 花一些时间来定义能够静态编译的构建系统。您将花一些时间进行工程设计,但它可以节省许多时间来捕获这些烦人的运行时错误。
  2. 将功能分组为知名和易于理解的部分,以便可以对功能/存根进行分组,以确保每个对应的功能都有其存根。如果您花时间将其很好地记录在文档中,则可以编写一个脚本来检查定义(例如,通过其doxygen注释)并检查相应的.cpp文件。
  3. 执行几个加载相同库的测试可执行文件,并为dlopen指定RTLD_NOW标志(如果您位于* NIX下)。它们将发出信号指示丢失的符号。

希望能有所帮助。


就RTLD_NOW而言,是否存在一个env var来强制立即(非延迟)绑定?
Stefano Borini

1
对。此处为LD_BIND_NOW(libc5;从2.1.1开始为glibc)。如果设置为非空字符串,则使动态链接程序在程序启动时解析所有符号,而不是将函数调用解析推迟到首次引用它们时。使用调试器时,这很有用。
Stefano Borini,2009年

对于OP来说,这也可能是有用的:LD_WARN(仅适用于ELF)(自2.1.3起使用glibc)如果设置为非空字符串,则警告未解析的符号。
Stefano Borini,2009年

Stefano:是的,是的。这些变量存在。但是,如果您将动态加载推迟到以后(例如,当用户加载模块时),这些变量将没有用,除非您实际编写了加载预期的(将来的)库的测试程序。
圣地亚哥塞维利亚
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.