前言
年初的时候我们项目组的构建系统( 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 编译器在自动内联一说,