惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
腾讯CDC
博客园_首页
T
Tailwind CSS Blog
月光博客
月光博客
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
SecWiki News
SecWiki News
美团技术团队
P
Privacy International News Feed
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
D
DataBreaches.Net
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cisco Blogs
S
Schneier on Security
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
Forbes - Security
Forbes - Security
D
Docker
T
Tenable Blog
S
Secure Thoughts
雷峰网
雷峰网
S
Security @ Cisco Blogs
T
The Exploit Database - CXSecurity.com
The Cloudflare Blog
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

I'm OWenT

国产大模型(GLM 5.1、Kimi K2.6)真实场景效果和 Coding Plan 额度测试 新版本libatapp的连接管理——从etcd服务发现到拓扑驱动的自动重连 新版本libatbus的设计变更——从树形路由到拓扑驱动 Protobuf又一坑 - C++标准和ABI兼容性 AI真好用-给Blog主题统一加mermaid,chart.js,excalidraw,draw.io的多种引入方式支持 给内网部署Squid-通用HTTP下载缓存 UE使用CodeChecker和clang-tidy生成静态分析报告 找出UE的循环依赖 C++小协程栈和临时变量及作用域的栈溢出问题分析 游戏服务的可观测性能力建设(C++生态) 指标上报的多线程优化和多拉取源点优化 协程(libcopp)的Channel功能和CPU命中率优化 通用RPC代码生成器 实现strong_rc_ptr(比shared_ptr更快的引用计数智能指针) 手夯一个STL allocator和对象内存分析组件 std::condition_variable 的信号丢失问题 踩坑一处(GCC)STL std::async 实现BUG导致的crash问题 GCC 14的一个warning to error BUG 给xresloader(Excel导表工具)增强UE读表支持(包含蓝图,Blueprint) Opentelemetry社区在gRPC的几个链接问题(静态库和动态库混用,musl工具链,符号裁剪) Excel转表工具(xresloader)的新验证器(验证外部Excel和文本数据,唯一性和自定义规则) protobuf v22和gRPC v1.55版本升级的依赖变化和upb适配 xresloader-Excel导表工具链的近期变更汇总 打通游戏服务端框架的C++20协程改造的最后一环 Opentelemetry-cpp的Logs模块标准更新(涉及近期版本:1.8-1.9的BREAK CHANGES) 给cmake-toolset和工具链(curl等)加HTTP/2和HTTP/3支持 又开新坑之 coredns 插件: nftables和filter 关于opentelemetry-cpp社区对于C++ Head Only组件单例和符号可见性的讨论小记 填个转表工具 xresloader 去年的坑(数组尾部裁剪) 集成 upb 和 lua binding 的踩坑小记 libcopp对C++20协程的接入和接口设计 再度优化GCC、LLVM、Clang、libc++、libc++abi等套件的构建脚本 游戏服务的分布式事务优化(二)- 事务管理 游戏服务的分布式事务优化(一)- Write Ahead Log(WAL) 模块 记录一些bazel适配用编译选项 测试现代化硬件C++浮点数性能和一致性 适配Boringssl和OpenSSL 3.0 近期cmake-toolset的一些适配问题 C++20 Text Formatting/fmtlib 适配问题小记 再次重构LLVM+Clang+libcxx+libc++abi+其他相关工具的构建流程 重构基于CMake的构建工具链 新版GCC和LLVM+Clang终于Release啦 折腾一下nftables下的双拨 [C++20] Module partitions和符号交叉引用(声明和实现分离) [Rust] 实现一个线程安全且迭代器可以保存的链表 基于protobuf的代码生成 几个使用protobuf中C++接口的Arena的坑 Amazon Aurora DB存储引擎论文阅读小记 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等) xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等) 在游戏服务器中使用分布式事务 libcopp接入C++20 Coroutine和一些过渡期的设计 libatbus 的大幅优化 nftables初体验 容器配置开发环境小计 PALM Tree - 适合多核并发架构的B+树 - 论文阅读小记 跨平台协程库 - libcopp 简介 C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比) 尝鲜Github Action 一些xresloader(转表工具)的改进 protobuf、flatbuffer、msgpack 针对小数据包的简单对比 协程框架(libcopp) 小幅优化 Excel转表工具(xresloader) 增加protobuf插件功能和集成 UnrealEngine 支持 Anna(支持任意扩展和超高性能的KV数据库系统)阅读笔记 C++20 Coroutine libcopp merge boost.context 1.69.0 Google去中心化分布式系统论文三件套(Percolator、Spanner、F1)读后感 Rust玩具-企业微信机器人通用服务 使用ELK辅助监控开发测试环境服务质量和问题定位 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现 Webpack+vue+boostrap+ejs构建Web版GM工具 Rust的第二次接触-写个小服务器程序 理解和适配AEAD加密套件 atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比 可执行文件压缩 初识Rust 使用restructedtext编写xresloader文档 atframework的etcd模块化重构 C++的backtrace ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装 protobuf-net的动态Message实现 pbc的proto3接入 atgateway内置协议流程优化-加密、算法协商和ECDH 整理一波软件源镜像同步工具+DevOps工具 Blog切换到Hugo libcopp v2的第一波优化完成 libcopp(v2) vs goroutine性能测试 libcopp的线程安全、栈池和merge boost.context 1.64.0 GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本 libatbus的几个藏得很深的bug 用cmake交叉编译到iOS和Android 开源项目得一些小维护 atapp的c binding和c#适配 对象路由系统设计 2016年总结 近期的一个协程流程BUG 重写了llvm+clang+libc++和libc++abi的构建脚本 atsf4g完整游戏工程示例|I'm OWenT atframework基本框架已经完成|I'm OWenT
关于protobuf近期版本(v20/v3.20+)和 gRPC v1.54版本在某些编译环境下的一些链接和编译问题
owent · 2023-06-17 · via I'm OWenT

blog-website

前言

年初的时候我们项目组的构建系统( 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去修复这些问题。

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

#if defined(NDEBUG) || defined(_MSC_VER)
  ~InternalMetadata() {
    if (HasMessageOwnedArenaTag()) {
      delete reinterpret_cast<Arena*>(ptr_ - kMessageOwnedArenaTagMask);
    }
  }
#else
  ~InternalMetadata();
#endif

还有源文件 message_lite.cc#L528

// 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 的代码。

  ~InternalMetadata() {
#if defined(NDEBUG) || defined(_MSC_VER)
    if (HasMessageOwnedArenaTag()) {
      delete reinterpret_cast<Arena*>(ptr_ - kMessageOwnedArenaTagMask);
    }
#else
    CheckedDestruct();
#endif
  }

咋一看好像是没问题了。无论什么情况都有 ~InternalMetadata() 了,但是C++编译器在自动内联一说,😂。某些编译器在编译 .pb.cc 时如果走下面的分支自动内联了,那么就不会生成 ~InternalMetadata() 这个符号。这个类的析构在 MessageLite 这个类中被调用,在生成的 .pb.cc 里是配有被直接调用的。但是某些编译器会生成对它的析构符号的引用(可能也属于编译器的BUG)。 这时候又会导致符号未定义。

我们发现问题的环境是编译iOS版本时,具体编译器版本号忘记了,好像是AppleClang 12或者AppleClang 13。

解决这个问题也很简单,把声明改成 PROTOBUF_NOINLINE ~InternalMetadata() 就行了。 以上问题也可以在我们的构建系统项目中找到Patch文件(还包含少量其他问题的适配):

XXX_default_instance_ 未定义

第二个问题是默认的instance符号未定义的问题。这个问题存在于 v21/v3.21 到目前最新版本 (v23/4.23)。我没有追查更早版本,大概率也有这个问题。 报的错误大致是 "struct XXX YYYY_default_instance_" 符号未定义。触发条件比较多:

  • 需要编译成动态库
  • 默认符号隐藏(Windows默认隐藏,Linux默认可见)
  • 使用 dllexport_decl= 来设置导出符号

在Windows中个,每一个dll和exec都有自己的符号表和堆管理。所以当使用dll时,需要把要导出的符号设置为 __declspec(dllexport)/__attribute__((__dllexport__)), 导入的时候设置为 __declspec((dllimport))/__attribute__((__dllimport__)) 。 而在Linux里,默认是共享且全局可见的。而很多构建系统中会把Windows版本依赖使用静态库,所以很多同学不会碰到这些问题。

有一些更严谨更安全防止符号冲突的方式是吧Linux下符号也通过 __attribute__((visibility("hidden"))) 设为默认隐藏,然后对于要导出的符号设置为 __attribute__((visibility("default"))) 来共享(比如Unreal Engine的构建系统)。这时候通常的做法是提供一个宏,在编译的时候设置PRIVATE定义为 __declspec(__dllimport__)/__attribute__((__dllimport__))/__attribute__((visibility("default"))) 对外传递的PUBLIC定义为 __declspec(dllexport)/__attribute__((__dllexport__))/__attribute__((visibility("default")))

protobuf 生成的代码中,由于 .pb.cc 中存在全局变量,我们也不能允许同一个全局变量在多个动态库中,否则会重复注册和执行构造析构函数。那么为了实现上面的流程,protobuf 提供了一个生成选项 dllexport_decl,用来对要导出的符号指定这个宏。

那么我们再来看一个这个问题对应的生成代码(选项是 --cpp_out=dllexport_decl=TGF_BATTLE_PROTOCOL_API:<OUT DIR>)。 首先对于 .pb.h :

class DBattleAffix;
struct DBattleAffixDefaultTypeInternal;
TGF_BATTLE_PROTOCOL_API extern DBattleAffixDefaultTypeInternal _DBattleAffix_default_instance_;

// ...
class TGF_BATTLE_PROTOCOL_API DBattleAffix final : // ...
{
  // ...
  static inline const DBattleAffix* internal_default_instance() {
    return reinterpret_cast<const DBattleAffix*>(
               &_DBattleAffix_default_instance_);
  }
}

可以看到,有接口引用了 _DBattleAffix_default_instance_ 这个全局变量的地址。那么在 .pb.cc 里:

struct DBattleAffixDefaultTypeInternal {
  PROTOBUF_CONSTEXPR DBattleAffixDefaultTypeInternal()
      : _instance(::_pbi::ConstantInitialized{}) {}
  ~DBattleAffixDefaultTypeInternal() {}
  union {
    DBattleAffix _instance;Bu
  };
};
PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DBattleAffixDefaultTypeInternal _DBattleAffix_default_instance_;

可以看到,.pb.cc 里面并没有设置 TGF_BATTLE_PROTOCOL_API 来导出符号,最终就会导致类似下面这样的链接错误:

7>TGFBattleUtility.lib(BattleAffixAlgorithm.obj): Error LNK2001: unresolved external symbol "struct tgf::DBattleAffixDefaultTypeInternal tgf::_DBattleAffix_default_instance_" (?_DBattleAffix_default_instance_@tgf@@3UDBattleAffixDefaultTypeInternal@1@A)

那么解决方法也很简单,就是在 protoc 的代码里补上这些,(v21和v23版本变化比较大,具体可以参考下面的patch files)

这个issue已经提交到 https://github.com/protocolbuffers/protobuf/issues/13084 并且BUG修复的PR在 https://github.com/protocolbuffers/protobuf/pull/13085 。有兴趣的小伙伴也可以跟进。

gRPC 的链接和编译问题

gRPC 的 v1.54.0 的链接符号问题

我们在使用高版本编译器时,会尽可能使用高版本的STD标准。在C++17以后 constexpr 关键字隐式会被附加 inline (详情参考: https://en.cppreference.com/w/cpp/language/constexpr)。 这意味着这些变量可能不会生成实际的符号。那么问题就来了。我们来看v1.54.0 版本的代码:

首先 src/core/ext/gcp/metadata_query.h :

class MetadataQuery : public InternallyRefCounted<MetadataQuery> {
 public:
  static const char kZoneAttribute[];
  static const char kClusterNameAttribute[];
  static const char kRegionAttribute[];
  static const char kInstanceIdAttribute[];
  static const char kIPv6Attribute[];
// ...

然后 src/core/ext/gcp/metadata_query.cc :

constexpr const char MetadataQuery::kZoneAttribute[] =
    "/computeMetadata/v1/instance/zone";
constexpr const char MetadataQuery::kClusterNameAttribute[] =
    "/computeMetadata/v1/instance/attributes/cluster-name";
constexpr const char MetadataQuery::kRegionAttribute[] =
    "/computeMetadata/v1/instance/region";
constexpr const char MetadataQuery::kInstanceIdAttribute[] =
    "/computeMetadata/v1/instance/id";
constexpr const char MetadataQuery::kIPv6Attribute[] =
    "/computeMetadata/v1/instance/network-interfaces/0/ipv6s";

这时候,某些编译环境下这些变量被inline了以后,就会出现未定义符号的问题。 相关修复放在了 https://github.com/atframework/cmake-toolset/blob/main/ports/grpc/grpc-v1.54.0.patch 。 同时这个BUG在 v1.54.2 里已经被修复。

gRPC 的在部分编译器上的兼容性问题

按照 https://en.cppreference.com/w/cpp/memory/unique_ptr 的要求, std::unqieur_ptr<T, std::default_delete<T>> 中的 T 应该是允许前置声明的。 但是某些编译器或者STL实现的问题,导致开启最高支持标准时 src/core/lib/surface/server.h 里前置申明类型用在 std::unqieur_ptr 时会报 incomplete type 的问题。 临时的处理方式是把它们改成 shared_ptr ,虽然暂时有轻微的性能损失,但是也可以后续等官方更完整的修复。Patch文件在 https://github.com/atframework/cmake-toolset/blob/main/ports/grpc/grpc-v1.54.2.patch

最后

近期碰到的 protobufgRPC 的构建坑也就这么多。后续针对 protobuf v22+ 和 gRPC v1.55+ 还有一些比较重大的变化。导致我们的Patch过程有一个大改造。特别是设计upb的部分。 这部分后续在写分享吧,也欢迎有兴趣的小伙伴交流。