我已经找到了一种分析调试这个问题的方法。就我而言,我正在为较旧的 ABI 进行交叉编译,因此 apt-get 不是一个选项,我正在手动编译所有依赖项。
首先让我们来看看这个问题究竟是什么。在 Google GFlags 库中,标志是通过全局对象声明的。当全局对象的构造函数运行时,它调用 GFlags 库来注册该命令行标志。如果全局构造函数多次运行(由于包含它的库的多个版本被加载到内存中),则 GFlags 注册方法会因错误而终止。
GLog 与此有什么关系?好吧,GLog 使用 GFlags,并且它具有全局声明的标志对象。即使 GFlags 已正确链接,如果 GLog 库被多次加载,您也会收到指向 GLog 中 logging.cc 的错误。
听起来很乱,呵呵。即使 GLog 和 GFlags 在大多数情况下被链接为共享,如果另一个库链接到静态版本或其他版本,kaboom。
幸运的是,如果您愿意深入研究一些棘手的符号分析,我们可以使用 GDB 和其他工具调试此问题。首先,当 Python 解释器尝试导入 caffe 时,您需要在 GDB 上运行它:
gdb --args python -c 'import caffe'
现在,运行程序一次,以便 GDB 可以获取它导入的所有库:
(gdb) r
现在,我们可以在函数 ( FlagRegistry::RegisterFlag()
) 中打印错误消息的位置设置断点,然后再次运行它。请注意,此行号来自我的 GFlags 版本(2.2.2),您可能需要查看 GFlags 版本的源代码并获取行号。
(gdb) break gflags.c:728
(gdb) r
希望 GDB 应该在错误的第一个实例上中断(如果没有,请检查 gflags 是否已使用调试符号构建)。查看回溯:
(gdb) bt
#0 google::(anonymous namespace)::FlagRegistry::RegisterFlag (this=0xa33b30, flag=0x1249d20) at dev/gflags-2.2.2/src/gflags.cc:728
#1 0x00007ffff0f3247a in _GLOBAL__sub_I_logging.cc () from prefix/lib/libcaffe2.so
#2 0x00007ffff7de76ca in call_init (l=<optimized out>, argc=argc@entry=3, argv=argv@entry=0x7fffffffdb08, env=env@entry=0x7fffffffdb28) at dl-init.c:72
#3 0x00007ffff7de77db in call_init (env=0x7fffffffdb28, argv=0x7fffffffdb08, argc=3, l=<optimized out>) at dl-init.c:30
#4 _dl_init (main_map=main_map@entry=0xd9c2a0, argc=3, argv=0x7fffffffdb08, env=0x7fffffffdb28) at dl-init.c:120
#5 0x00007ffff7dec8f2 in dl_open_worker (a=a@entry=0x7fffffffcf70) at dl-open.c:575
#6 0x00007ffff7de7574 in _dl_catch_error (objname=objname@entry=0x7fffffffcf60, errstring=errstring@entry=0x7fffffffcf68, mallocedp=mallocedp@entry=0x7fffffffcf5f,
operate=operate@entry=0x7ffff7dec4e0 <dl_open_worker>, args=args@entry=0x7fffffffcf70) at dl-error.c:187
#7 0x00007ffff7debdb9 in _dl_open (file=0x9aee70 "prefix/lib/python2.7/site-packages/caffe2/python/caffe2_pybind11_state.so", mode=-2147483646,
caller_dlopen=0x51bb39 <_PyImport_GetDynLoadFunc+233>, nsid=-2, argc=<optimized out>, argv=<optimized out>, env=0x7fffffffdb28) at dl-open.c:660
#8 0x00007ffff75ecf09 in dlopen_doit (a=a@entry=0x7fffffffd1a0) at dlopen.c:66
#9 0x00007ffff7de7574 in _dl_catch_error (objname=0xabf9f0, errstring=0xabf9f8, mallocedp=0xabf9e8, operate=0x7ffff75eceb0 <dlopen_doit>, args=0x7fffffffd1a0) at dl-error.c:187
#10 0x00007ffff75ed571 in _dlerror_run (operate=operate@entry=0x7ffff75eceb0 <dlopen_doit>, args=args@entry=0x7fffffffd1a0) at dlerror.c:163
#11 0x00007ffff75ecfa1 in __dlopen (file=<optimized out>, mode=<optimized out>) at dlopen.c:87
#12 0x000000000051bb39 in _PyImport_GetDynLoadFunc ()
<snip>
好吧,有很多事情要处理,但让我们专注于真正重要的那一行:
#1 0x00007ffff0f3247a in _GLOBAL__sub_I_logging.cc () from prefix/lib/libcaffe2.so
这是对 logging.cc(它是 GLog 的一部分)中全局变量的构造函数的调用。如您所见,此调用位于 libcaffe2.so 中,这意味着 GLog 已静态链接到 libcaffe2.so [我使用的是 caffe2,但此过程对两者应该相同]。
然后,您可以设置断点google::(anonymous namespace)::FlagRegistry::RegisterFlag
并从头开始重新运行程序。查看对 RegisterFlag() 的每次调用,并找出第一次注册此特定标志的位置。如果提供标志的库是共享库,那么它应该只从该 .so 文件中注册,而不是其他任何地方。
要确认诊断,您可以使用
nm <library> | grep _GLOBAL__sub_I_logging.cc
检查库文件中的该初始化函数。一旦你找到了罪魁祸首,你就需要重建它,这样它就不会静态链接到 GFlags/GLog。