在共享库中查找引用未定义符号的源文件

4
我有一个共享库(ELF格式,我想是这样的),它是从C++源代码编译而成的,且库是以调试模式构建的。
鉴于该库的未定义符号,我想确定它来自哪个源文件(或目标文件)。可能非常容易在库的调试版本中完成此操作。
由于我只关心库所包含的源文件,因此递归grep不是一个选项。 未定义的符号可能来自外部头文件,因此搜索库本身的源代码将找不到任何内容。

3
递归搜索源代码。 - stark
请问您能否详细说明一下您的答案?我认为可以使用readelf或ldd来实现这个目的。 - Alexey
@stark 感谢您的评论,但源代码本身可能不包含该符号 - 该符号可能来自外部头文件。 - Alexey
头文件不会导致那个错误,只有对符号的使用才会。 - stark
@stark 不,可能没有直接使用符号 - 的情况,例如,在源代码中使用了宏。在预处理期间,该宏将被替换为该符号。 - Alexey
未定义符号错误是链接时错误,通常是缺少库。谷歌一下符号名称,你可能会找到哪个库。 - Michael Surette
1个回答

3
你共享的带有调试信息的库引用了一个未定义的外部变量,就像我将要构建的示例一样:
foo.cpp
 namespace bar {
     extern int undefined;
 };

 int foo()
 {
     return bar::undefined;
 }

我将未定义的符号放入一个命名空间中,只是为了得到一个在链接器中被名称修饰的情况,因为你谈论的是C++。

编译并链接,附加调试信息:

 $ g++ -shared -g -fPIC -o libfoo.so foo.cpp

这是在库的符号表中的原始内容:

 $ nm --undefined-only libfoo.so | grep undefined
                  U _ZN3bar9undefinedE

并进行了反编译:

 $ nm -C --undefined-only libfoo.so | grep undefined
                  U bar::undefined

现在,如果我们转储调试信息,我们会看到这个:
$ readelf --debug-dump=info libfoo.so
Contents of the .debug_info section:

  Compilation Unit @ offset 0x0:
   Length:        0x6d (32-bit)
   Version:       4
   Abbrev Offset: 0x0
   Pointer Size:  8
 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
    <c>   DW_AT_producer    : (indirect string, offset: 0x0): GNU C++14 7.3.0 -mtune=generic -march=x86-64 -g -fPIC -fstack-protector-strong
    <10>   DW_AT_language    : 4    (C++)
    <11>   DW_AT_name        : (indirect string, offset: 0x8f): foo.cpp
    <15>   DW_AT_comp_dir    : (indirect string, offset: 0x74): /home/imk/develop/so/scrap
    <19>   DW_AT_low_pc      : 0x5ba
    <21>   DW_AT_high_pc     : 0xf
    <29>   DW_AT_stmt_list   : 0x0
 <1><2d>: Abbrev Number: 2 (DW_TAG_namespace)
    <2e>   DW_AT_name        : bar
    <32>   DW_AT_decl_file   : 1
    <33>   DW_AT_decl_line   : 1
    <34>   DW_AT_sibling     : <0x48>
 <2><38>: Abbrev Number: 3 (DW_TAG_variable)
    <39>   DW_AT_name        : (indirect string, offset: 0x6a): undefined
    <3d>   DW_AT_decl_file   : 1
    <3e>   DW_AT_decl_line   : 2
    <3f>   DW_AT_linkage_name: (indirect string, offset: 0x57): _ZN3bar9undefinedE
    <43>   DW_AT_type        : <0x48>
    <47>   DW_AT_external    : 1
    <47>   DW_AT_declaration : 1
 <2><47>: Abbrev Number: 0
 <1><48>: Abbrev Number: 4 (DW_TAG_base_type)
    <49>   DW_AT_byte_size   : 4
    <4a>   DW_AT_encoding    : 5    (signed)
    <4b>   DW_AT_name        : int
 <1><4f>: Abbrev Number: 5 (DW_TAG_subprogram)
    <50>   DW_AT_external    : 1
    <50>   DW_AT_name        : foo
    <54>   DW_AT_decl_file   : 1
    <55>   DW_AT_decl_line   : 5
    <56>   DW_AT_linkage_name: (indirect string, offset: 0x4f): _Z3foov
    <5a>   DW_AT_type        : <0x48>
    <5e>   DW_AT_low_pc      : 0x5ba
    <66>   DW_AT_high_pc     : 0xf
    <6e>   DW_AT_frame_base  : 1 byte block: 9c     (DW_OP_call_frame_cfa)
    <70>   DW_AT_GNU_all_call_sites: 1
 <1><70>: Abbrev Number: 0

我们的符号_ZN3bar9undefinedE是由第一个(也是唯一一个)为libfoo.so编译的编译单元中的入口<2>描述的。它的链接名称由以下记录给出:

<3f>   DW_AT_linkage_name: (indirect string, offset: 0x57): _ZN3bar9undefinedE

因此,要获取引用了bar::undefined的源文件名,我们需要执行以下操作:

从调试信息中提取所有类似于以下行块:

 ...Compilation Unit...
 ...
 ...
 ..._ZN3bar9undefinedE...

然后从它们中提取所有类似的块:

 ...DW_TAG_compile_unit...
 ...
 ...DW_AT_comp_dir...

然后从这些块中,打印出最后两行。以下是一种方法 - 很可能不是最专业的方法 - 来实现:

$ readelf --debug-dump=info libfoo.so | awk '/Compilation Unit/, /_ZN3bar9undefinedE/' | awk '/DW_TAG_compile_unit/,/DW_AT_comp_dir/' | grep -B1 'DW_AT_comp_dir' 
    <11>   DW_AT_name        : (indirect string, offset: 0x8f): foo.cpp
    <15>   DW_AT_comp_dir    : (indirect string, offset: 0x74): /home/imk/develop/so/scrap

我们得到了1个命中(当然,因为只编译了一个源文件),告诉我们_ZN3bar9undefinedE,也就是bar::undefinedfoo.cpp中被引用,这个文件是在构建目录/home/imk/develop/so/scrap中编译的。


1
谢谢您的回答!但是由于某些原因,该符号在readelf --debug-dump=info输出中不存在。尽管它存在于nm --undefined-only输出中。 - Alexey
看起来库可能不包含此依赖项所需的完整DWARF调试信息。或者在你的情况下,可能是我的awk操作有误。Awk程序员的道路上到处都是香蕉皮。如果没有构建您的库,我无法提供更多帮助。 - Mike Kinghan
我已经对整个readelf --debug-dump=info输出进行了grep操作 - 它不包含该符号。看起来是缺少调试信息的问题。 - Alexey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接