与生成的protobufs的静态链接会导致中止

Static linking with generated protobufs causes abort

本文关键字:静态 protobufs 链接      更新时间:2023-10-16

我有一个项目,它将c++生成的protobuf序列化程序编译到一个静态库中。可执行文件链接到此库,.so(.dll(也链接。可执行文件稍后加载.so文件。当这种情况发生时,我得到:

[libprotobuf ERROR /mf-toolchain/src/protobuf-3.0.0-beta-1/src/google/protobuf/descriptor_database.cc:57] File already exists in database: mri.proto
[libprotobuf FATAL /mf-toolchain/src/protobuf-3.0.0-beta-1/src/google/protobuf/descriptor.cc:1128] CHECK failed: generated_database_->Add(encoded_file_descriptor, size): 
terminate called after throwing an instance of 'google::protobuf::FatalException'
what(): CHECK failed: generated_database_->Add(encoded_file_descriptor, size): 
Aborted (core dumped)

为了清楚起见,我有一个静态库A,它链接到程序p和共享库S。后来,p加载了S,我得到了上面的错误。

我在Stack Overflow和google上看到了类似的错误,但我很确定我只是针对库进行链接,而不是重新编译源代码。据我所知,这应该意味着汇编的数据是相同的。

另请注意:这个问题只发生在Linux上。这在Windows和OS X上运行良好。

问题是静态库包含一个文件mri.pb.cc,该文件在其全局初始化程序中,将类型描述符注册到libprotobuf维护的全局描述符数据库中。因为静态库被加载到程序中两次,所以这个初始化器运行了两次,但因为进程中只有一个libprotobuf副本,所以两个初始化器都注册到同一个全局数据库中,并且它检测到冲突。

要解决这个问题,您需要将静态库更改为共享库,主程序和动态加载的库都依赖于此。

我不知道你为什么在Windows或OSX上看到不同的行为。我的最佳猜测是,在这些平台上,您实际上将libprotobuf的两个独立副本链接到程序中——一个在主可执行文件中,另一个在动态加载库中。因此,描述符数据库有两个副本,并且没有冲突。然而,你可能会在这里看到更微妙的问题。如果在主程序和动态加载的模块之间传输protobuf对象指针(不进行序列化,然后再次解析(,那么最终可能会有一个由库的一个副本创建但与另一个副本(因此是描述符数据库的不同副本(一起使用的protobuf目标,这将混淆库并导致奇怪的事情发生。

或者,如果从未通过边界传递protobuf对象,则可以通过静态链接libprotobuf在Linux上"修复"问题,以便获得如上所述的两个副本。但这是相当危险的;我不推荐它。

这个错误出现在链接到两个库(LibALibB(的可执行文件的上下文中,这两个库恰好编译了同一个proto消息,其中LibB依赖于LibA并链接到它。

我发现自己遇到了这种情况,因为LibA以前不依赖于任何protobuffer框架,LibB为这个内部工具应用程序构建了一整套相关的proto消息,以便与另一个应用程序通信。随着LibA的新发布,它需要对另外两个编译各种原型消息的库(LibCLibD(进行新的依赖。这个问题从LibCLibD都同样表现出来,我将讨论LibC,因为解决方案是相同的。

在加载时,应用程序加载了LibC,最终加载了最上面的模块LibB,这时common.cc中的LogMessage::Finish()将触发中止。我通过在中止上下文的几个级别上设置一个断点来发现是谁在进行这种双重加载。以下是我称之为SomeMessage…的双加载原型消息的相关来源

void LibC_AddDesc_SomeMessage_2eproto() {
  static bool already_here = false; // <--- Breakpoint Set Here
  if (already_here) return;
  already_here = true;

突破点命中1:加载伦敦银行同业拆借利率

LibC.dll!MyNamespace::LibC_AddDesc_SomeMessage_2eproto()  Line 415  C++
LibC.dll!MyNamespace::LibC_AddDesc_ParentMessage_2eproto()  Line 388    C++
LibC.dll!MyNamespace::StaticDescriptorInitializer_ParentMessage_2eproto::StaticDescriptorInitializer_ParentMessage_2eproto()  Line 494  C++
LibC.dll!MyNamespace::`dynamic initializer for 'static_descriptor_initializer_ParentMessage_2eproto_''()  Line 495 + 0x21 bytes C++
msvcr100d.dll!_initterm()  + 0x2c bytes 
LibC.dll!_CRT_INIT(void * hDllHandle, unsigned long dwReason, void * lpreserved)  Line 289  C
LibC.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved)  Line 506 + 0x13 bytes   C
LibC.dll!_DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved)  Line 477 C
ntdll.dll!LdrpRunInitializeRoutines()  + 0x1e8 bytes    
ntdll.dll!LdrpInitializeProcess()  - 0x14c9 bytes   
ntdll.dll!string "Enabling heap debug optionsn"()  + 0x29a99 bytes 
ntdll.dll!LdrInitializeThunk()  + 0xe bytes 

在加载LibC的过程中,我可以看到断点被击中两次,静态变量already_here从false设置为true,并保持为true,将跳过此消息的注册。

突破点命中2:加载LibB

当这个库尝试加载时,变量already_here将被重新初始化为false,我们将继续尝试第二次注册这个消息,这触发了中止。

LibB.dll!MyNamespace::LibC_AddDesc_SomeMessage_2eproto()  Line 415  C++
LibB.dll!MyNamespace::LibC_AddDesc_ParentMessage_2eproto()  Line 388    C++
LibB.dll!MyNamespace::LibC_AddDesc_FullMessage_2eproto()  Line 219  C++
LibB.dll!MyNamespace::StaticDescriptorInitializer_FullMessage_2eproto::StaticDescriptorInitializer_FullMessage_2eproto()  Line 358  C++
LibB.dll!MyNamespace::`dynamic initializer for 'static_descriptor_initializer_FullMessage_2eproto_''()  Line 359 + 0x21 bytes   C++
msvcr100d.dll!_initterm()  + 0x2c bytes 
LibB.dll!_CRT_INIT(void * hDllHandle, unsigned long dwReason, void * lpreserved)  Line 289  C
LibB.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved)  Line 506 + 0x13 bytes   C
LibB.dll!_DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved)  Line 477 C
ntdll.dll!LdrpRunInitializeRoutines()  + 0x1e8 bytes    
ntdll.dll!LdrpInitializeProcess()  - 0x14c9 bytes   
ntdll.dll!string "Enabling heap debug optionsn"()  + 0x29a99 bytes 
ntdll.dll!LdrInitializeThunk()  + 0xe bytes 

我们会在中止线的stubs/common.cc中结束

void LogMessage::Finish() {
  bool suppress = false;
  if (level_ != LOGLEVEL_FATAL) {
    InitLogSilencerCountOnce();
    MutexLock lock(log_silencer_count_mutex_);
    suppress = internal::log_silencer_count_ > 0;
  }
  if (!suppress) {
    internal::log_handler_(level_, filename_, line_, message_);
  }
  if (level_ == LOGLEVEL_FATAL) {
    abort(); // <----- runtime crash!
  }
}

对于std::呃,你会发现以下文本。。。

libprotobuf ERROR descriptor_database.cc:57] File already exists in database: SomeMessage.proto
libprotobuf FATAL descriptor.cc:860] CHECK failed: generated_database_->Add(encoded_file_descriptor, size):

解决方案很简单,我打开了LibC项目,搜索pb,并从LibB中删除了这些原始消息。CCD_ 24也是如此。

我也遇到了这个问题,刚刚解决了它。我忘了链接pthread。链接pthread后,问题消失了。在这里发帖以防其他人错过。