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

Static linking with generated protobufs causes abort

我有一个项目将 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 对象,您可以通过链接 "fix" Linux 上的问题libprotobuf 是静态的,以便如上所述获得两个副本。但这是非常危险的;我不推荐它。

这个错误出现在链接到两个库(LibALibB)的可执行文件的上下文中,这两个库碰巧编译了相同的原型消息,其中 LibB 取决于在 LibA 上并链接到它。

我发现自己遇到了这种情况,因为 LibA 之前不依赖于任何 protobuffer 框架并且 LibB 为这个 in-内部工具应用程序与另一个应用程序进行通信。随着 LibA 的新版本,它需要对另外两个编译各种原型消息的库的新依赖项(LibCLibD)。该问题在 LibCLibD 中同样表现出来,我将讨论 LibC,因为解决方案是相同的。

在加载时,应用程序加载了 LibC,最终它开始加载最上面的模块 LibB,这就是在 LogMessage::Finish() 中 [=30] 中触发中止的时间=].我通过在中止上下文的几层上设置一个断点,发现了谁在进行这种双重加载。这是我正在调用的双重加载原型消息要考虑的相关来源 SomeMessage

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

断点 1:加载 LibC

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 options\n"()  + 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 options\n"()  + 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::err 您会发现以下文本...

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 中删除了那些原始消息。 LibD.

也是如此