【问题标题】:Segmentation fault using shared library使用共享库的分段错误
【发布时间】:2010-11-13 19:02:32
【问题描述】:

我有一个与 cpp/h 文件关联的共享库(即 libXXX.so)。 它们包含许多函数指针(指向 .so 函数入口点)和一个将这些函数包装为所述类的方法的类。

即:.h 文件:

typedef void* handle;
/* wrapper functions */
handle okUsbFrontPanel_Construct();
void okUsbFrontPanel_Destruct(handle hnd);

/* wrapper class */
class okCUsbFrontPanel
{
public:
  handle h;
public:
  okCUsbFrontPanel();
  ~okCUsbFrontPanel();
};

.cpp 文件

/* class methods */
okCUsbFrontPanel::okCUsbFrontPanel()
  { h=okUsbFrontPanel_Construct(); }
okCUsbFrontPanel::~okCUsbFrontPanel()
  { okUsbFrontPanel_Destruct(h); }
/* function pointers */
typedef handle  (*OKUSBFRONTPANEL_CONSTRUCT_FN) (void);
typedef void    (*OKUSBFRONTPANEL_DESTRUCT_FN)  (handle);
OKUSBFRONTPANEL_CONSTRUCT_FN    _okUsbFrontPanel_Construct = NULL;
OKUSBFRONTPANEL_DESTRUCT_FN _okUsbFrontPanel_Destruct = NULL;
/* load lib function */
Bool LoadLib(char *libname){
  void *hLib = dlopen(libname, RTLD_NOW);
  if(hLib){
    _okUsbFrontPanel_Construct = ( OKUSBFRONTPANEL_CONSTRUCT_FN ) dlsym(hLib, "okUsbFrontPanel_Construct");
    _okUsbFrontPanel_Destruct = ( OKUSBFRONTPANEL_DESTRUCT_FN ) dlsym( hLib, "okUsbFrontPanel_Destruct" );
  }
}
/* construct function */
handle okUsbFrontPanel_Construct(){
  if (_okUsbFrontPanel_Construct){
    handle h = (*_okUsbFrontPanel_Construct)(); //calls function pointer
    return h;
  }
  return(NULL);
}

void okUsbFrontPanel_Destruct(handle hnd)
{
  if (_okUsbFrontPanel_Destruct)
    (*_okUsbFrontPanel_Destruct)(hnd);
}

然后我有另一个共享库(由我自己制作)调用:

LoadLib("libXXX.so");
okCusbFrontPanel *device = new okCusbFrontPanel();

导致分段错误。 分段错误似乎发生在

handle h = (*_okUsbFrontPanel_Construct)();

但有一个奇怪的行为:一旦我到达

(*_okUsbFrontPanel_Construct)(); 

我得到了 okUsbFrontPanel_Construct() 的递归。

有人知道吗?

编辑:这是通过 gdb 运行获得的回溯。

#0  0x007590b0 in _IO_new_do_write () from /lib/tls/libc.so.6
#1  0x00759bb8 in _IO_new_file_overflow () from /lib/tls/libc.so.6
#2  0x0075a83d in _IO_new_file_xsputn () from /lib/tls/libc.so.6
#3  0x00736db7 in vfprintf () from /lib/tls/libc.so.6
#4  0x0073ecd0 in printf () from /lib/tls/libc.so.6
#5  0x02cb68ca in okCUsbFrontPanel (this=0x9d0ae28) at okFrontPanelDLL.cpp:167
#6  0x03cac343 in okUsbFrontPanel_Construct () from /opt/atlas/tdaq/tdaq-02-00-00/installed/i686-slc4-gcc34-dbg/lib/libokFrontPanel.so
#7  0x02cb8f36 in okUsbFrontPanel_Construct () at okFrontPanelDLL.cpp:1107
#8  0x02cb68db in okCUsbFrontPanel (this=0x9d0ade8) at okFrontPanelDLL.cpp:169
#9  0x03cac343 in okUsbFrontPanel_Construct () from /opt/atlas/tdaq/tdaq-02-00-00/installed/i686-slc4-gcc34-dbg/lib/libokFrontPanel.so
#10 0x02cb8f36 in okUsbFrontPanel_Construct () at okFrontPanelDLL.cpp:1107
#11 0x02cb68db in okCUsbFrontPanel (this=0x9d0ada8) at okFrontPanelDLL.cpp:169
#12 0x03cac343 in okUsbFrontPanel_Construct () from /opt/atlas/tdaq/tdaq-02-00-00/installed/i686-slc4-gcc34-dbg/lib/libokFrontPanel.so
#13 0x02cb8f36 in okUsbFrontPanel_Construct () at okFrontPanelDLL.cpp:1107

等等…… 恕我直言,由于某种堆栈溢出,我遇到了段错误。递归调用太多,出错了..

顺便说一句,我使用的是 Scientific Linux 4 发行版(基于 RH4)。

编辑2:

用于函数 okUsbFrontPanel_Construct 输出的 libokFrontPanel.so 的 objdump:

00009316 <okUsbFrontPanel_Construct>:
9316:   55                      push   ebp  
9317:   89 e5                   mov    ebp,esp
9319:   56                      push   esi
931a:   53                      push   ebx
931b:   83 ec 30                sub    esp,0x30
931e:   e8 44 f4 ff ff          call   8767 <__i686.get_pc_thunk.bx>
9323:   81 c3 dd bd 00 00       add    ebx,0xbddd
9329:   c7 04 24 38 00 00 00    mov    DWORD PTR [esp],0x38
9330:   e8 93 ec ff ff          call   7fc8 <_Znwj@plt>
9335:   89 45 e4                mov    DWORD PTR [ebp-28],eax
9338:   8b 45 e4                mov    eax,DWORD PTR [ebp-28]
933b:   89 04 24                mov    DWORD PTR [esp],eax
933e:   e8 65 ed ff ff          call   80a8 <_ZN16okCUsbFrontPanelC1Ev@plt>
9343:   8b 45 e4                mov    eax,DWORD PTR [ebp-28]
9346:   89 45 f4                mov    DWORD PTR [ebp-12],eax
9349:   8b 45 f4                mov    eax,DWORD PTR [ebp-12]
934c:   89 45 e0                mov    DWORD PTR [ebp-32],eax
934f:   eb 1f                   jmp    9370 <okUsbFrontPanel_Construct+0x5a>
9351:   89 45 dc                mov    DWORD PTR [ebp-36],eax
9354:   8b 75 dc                mov    esi,DWORD PTR [ebp-36]
9357:   8b 45 e4                mov    eax,DWORD PTR [ebp-28]
935a:   89 04 24                mov    DWORD PTR [esp],eax
935d:   e8 d6 f2 ff ff          call   8638 <_ZdlPv@plt>
9362:   89 75 dc                mov    DWORD PTR [ebp-36],esi
9365:   8b 45 dc                mov    eax,DWORD PTR [ebp-36]
9368:   89 04 24                mov    DWORD PTR [esp],eax
936b:   e8 a8 f0 ff ff          call   8418 <_Unwind_Resume@plt>
9370:   8b 45 e0                mov    eax,DWORD PTR [ebp-32]
9373:   83 c4 30                add    esp,0x30
9376:   5b                      pop    ebx
9377:   5e                      pop    esi
9378:   5d                      pop    ebp
9379:   c3                      ret    

在 933e 确实有一个对 <_zn16okcusbfrontpanelc1ev> 的调用。这个调用是否与我的 .cpp 中的调用混淆了?

【问题讨论】:

  • 您的代码中的 XXX 是否不同?
  • 我重读的次数越多,您的问题就越令人困惑。您的两个来源是 libXXX.so 的一部分,还是“另一个共享库(我自己制作)”的一部分?如果是前者,那么答案是显而易见的。
  • 这两个来源不是 libXXX.so 的一部分。它们是其他共享库的一部分。
  • 你在哪个平台上?您的描述暗示了一个损坏的 dlsym(),但这很难相信。我建议您制作一个简化的测试用例来演示相同的问题,并编辑您的问题以确切地显示代码是什么、如何编译以及您观察到的内容。

标签: c++ memory-management shared-libraries segmentation-fault


【解决方案1】:

既然您已经发布了GDB 输出,那么您的问题就很清楚了。

您在libokFrontPanel.solibLoadLibrary.so 中定义了相同的符号(因为没有更好的名称——当它们被正确命名时,所以更容易解释),并且 导致了无限递归。

在 UNIX 上(与 Windows 不同)默认情况下,来自所有共享库(和主可执行文件)的所有全局符号都进入单个“加载器符号名称空间”。

除其他外,这意味着如果您在主可执行文件中定义mallocmalloc 将被所有共享库调用,包括libc(即使libc有自己的malloc 定义)。

所以,这就是发生的事情:在 libLoadLibrary.so 中,您定义了 okCUsbFrontPanel 构造函数。我断言在libokFrontPanel.so 中也有该确切符号的定义。 所有对该构造函数的调用(默认情况下)转到第一个定义(动态加载程序首先观察到的那个),即使libokFrontPanel.so 的创建者并不打算发生这种情况。循环是(以相同的顺序GDB 打印它们——最里面的框架在顶部):

 #1 okCUsbFrontPanel () at okFrontPanelDLL.cpp:169
 #3 okUsbFrontPanel_Construct () from libokFrontPanel.so
 #2 okUsbFrontPanel_Construct () at okFrontPanelDLL.cpp:1107
 #1 okCUsbFrontPanel () at okFrontPanelDLL.cpp:169

#3 对构造函数的调用旨在转到符号#4 -- okCUsbFrontPanel 构造函数inside libokFrontPanel.so。相反,它进入了libLoadLibrary.so 中之前看到的定义:你“抢占”了符号#4,从而形成了一个无限递归循环。

道德:不要在多个库中定义相同的符号,除非您了解运行时加载程序决定哪些符号引用绑定到哪些定义的规则。

编辑:回答问题的“EDIT2”:
是的,从okUsbFrontPanel_Construct_ZN16okCUsbFrontPanelC1Ev 的调用将转到okFrontPanelDLL.cpp 中该方法的定义。 检查objdump -d okFrontPanelDLL.o 可能很有启发性

【讨论】:

  • 我没有定义任何符号:这些名称/类由 FPGA 制造商提供。我无法在 libokFrontPanel.so 中更改名称:我应该将名称更改为 okFrontPanelDLL.cpp 中的 okUsbFrontPanel_Construct () 还是 okFrontPanelDLL.cpp:169 处的 okCUsbFrontPanel () ??
  • 是的,我知道 libokFrontPanel.so 是第 3 方代码。您应该更改代码以避免符号名称冲突:同时更改 okCUsbFrontPanel 和 okUsbFrontPanel_Construct。后一种更改不是绝对必要的,但您应该尽可能避免 all 名称冲突。
  • 非常感谢,成功了!!最后一件事我无法解释:这个问题发生在我在一个巨大的基于插件的程序中使用共享库(我自己制作的)时。我尝试制作一个简单的 main.cpp,它使用我的共享库(在更改符号之前)并且一切正常。我认为这个基于插件的大型程序可能使用与标准程序不同的方式来解析符号。有什么猜测吗?
【解决方案2】:

与 Norman Ramsey 所说的相反,诊断段错误的首选工具是 GDB,而不是 valgrind
后者仅对某些类型的段错误有用(主要是这些与堆损坏有关;这里似乎不是这种情况)。

我的水晶球说您的dlopen() 失败(如果/当这种情况发生时,您应该打印dlerror()!),并且您的_okUsbFrontPanel_Construct 仍然是NULL。在GDB 中,您将立即能够判断该猜测是否正确。

我的猜测与您“递归到 okUsbFrontPanel_Construct()”的说法相矛盾。但是,如果您不查看GDB,您怎么知道您得到了这样的递归?

【讨论】:

  • dlopen 没有失败..我确实有一个 dlerror() 打印但我从发布的代码中删除了它以缩短它一点..
  • 我说我“得到一个 okUsbFrontPanel_Construct() 的递归”,因为如果我在那个函数中放一个 printf,即。“正在输入等等。”我得到了很多 Entering ... Entering .. .
【解决方案3】:

诊断段错误的首选工具是valgrind。如果您滥用指针或内存,valgrind 会发现问题并在段错误发生之前为您提供堆栈跟踪。在常见问题解答中,valgrind 声称只要您不致电 dlclose(),就可以处理共享库。

如果你以前从未使用过 valgrind,我想你会惊讶于它的简单和强大。您只需使用“valgrind”作为命令行的第一个单词,它就会发现您的内存错误。好东西! Vladislav Vyshemirsky 的博客上有一个short example session

【讨论】:

  • 我已经听说过 Valgrind,但我遇到了一个棘手的情况:我的应用程序本身就是一个共享库。它是由我无法控制的程序加载的。有没有办法附加 valgrind到已经启动的程序?
  • 只要能从命令行运行程序,在valgrind下就可以运行。如果其他程序没有调试符号,你从 valgrind 获得的信息可能不是很有帮助,但是---我没有尝试在那种模式下使用它。
猜你喜欢
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-12
  • 2014-12-25
相关资源
最近更新 更多