关于protobuf近期版本(v20/v3.20+)和 gRPC v1.54版本在某些编译环境下的一些链接和编译问题

2023-10-21 15:53:24 浏览数 (3)

前言

年初的时候我们项目组的构建系统( cmake-toolset )里把 protobuf 升级到了 v20/v3.20 版本, gRPC 也升级到了 v1.54 版本。然而这两个版本在Linux的ELF ABI和MacOS的Macho ABI下都出现了一些符号未定义的问题(当然也包含Android和iOS)。 这些问题也不仅限于 protobuf v20/v3.20 和 gRPC v1.54,后续的版本有些修复了,有些没有。在官方完全修复之前,我们自己打了一些patch去修复这些问题。

protobuf 的链接和符号问题

InternalMetadata::~InternalMetadata() 未定义

protobuf的问题主要分两组,第一组报的是 InternalMetadata::~InternalMetadata() 未定义。这个问题存在于 v20/v3.20 和 v21/v3.21 。v22/v4.22 版本已修复(v22/v4.22在构建和依赖上有个很大的变化,后面再写分享说明)。这个问题的issue也可以见于 https://github.com/protocolbuffers/protobuf/issues/9947 。

其本质原因可以先看 v20/v3.20 版本的 metadata_lite.h#L73

代码语言:javascript复制
#if defined(NDEBUG) || defined(_MSC_VER)
  ~InternalMetadata() {
    if (HasMessageOwnedArenaTag()) {
      delete reinterpret_cast<Arena*>(ptr_ - kMessageOwnedArenaTagMask);
    }
  }
#else
  ~InternalMetadata();
#endif

还有源文件 message_lite.cc#L528

代码语言:javascript复制
// Non-inline implementations of InternalMetadata routines
#if defined(NDEBUG) || defined(_MSC_VER)
// for opt and MSVC builds, the destructor is defined in the header.
#else
// This is moved out of the header because the GOOGLE_DCHECK produces a lot of code.
InternalMetadata::~InternalMetadata() {
  if (HasMessageOwnedArenaTag()) {
    GOOGLE_DCHECK(!HasUnknownFieldsTag());
    delete reinterpret_cast<Arena*>(ptr_ - kMessageOwnedArenaTagMask);
  }
}
#endif

这里的本意是在非Debug模式下这个析构写头文件里,这样某些编译器和编译选项可以被自动内连,可以优化掉。而调试模式下有额外的检查走本地的符号。 但是这里的问题是,我们经常会在编译依赖库采用Release模式,而使用者可能处于Debug模式。这就意味着编译 protobuf 的时候是可能被优化掉而没有这个符号的。 但是使用者认为有这个符号,最终链接失败。

这个问题在 protobuf v21.4/v3.21.4 版本里进行了部分修复,但是某些编译环境还是有问题。

我们先来看 protobuf v21/v3.21 版本里 metadata_lite.h#L81 的代码。

代码语言:javascript复制
  ~InternalMetadata() {
#if defined(NDEBUG) || defined(_MSC_VER)
    if (HasMessageOwnedArenaTag()) {
      delete reinterpret_cast<Arena*>(ptr_ - kMessageOwnedArenaTagMask);
    }
#else
    CheckedDestruct();
#endif
  }

咋一看好像是没问题了。无论什么情况都有 ~InternalMetadata() 了,但是C 编译器在自动内联一说,

0 人点赞