修复 macOS 下 Godot-CPP 链接丢失 TLS 符号 (__ZTW) 问题记录

修复 macOS 下 Godot-CPP 链接丢失 TLS 符号 (__ZTW) 问题记录

1. 问题描述 (The Issue)

macOS (Intel x86_64) 环境下,使用 Clang 编译链接 Godot 4.5.1 (Dev) 的 GDExtension 插件时,编译过程成功,但在运行 Godot 加载插件时崩溃,报错如下:

text 复制代码
ERROR: Can't open dynamic library: .../libjrpg_core_sdk.dylib. 
Error: dlopen(...): symbol not found in flat namespace '__ZTWN5godot7Wrapped34_constructing_extension_class_nameE'

环境信息

  • OS: macOS (Intel x86_64)
  • Compiler: Clang (Apple LLVM)
  • Godot Version: 4.5.1 (Master branch)
  • Godot-CPP Version: Master branch
  • Target : template_debug

2. 原因分析 (Root Cause)

  1. 符号含义__ZTW... 是 Itanium C++ ABI 定义的 TLS (Thread Local Storage) Wrapper Function 。对于 thread_local 变量,编译器通常会生成一个包装函数来处理其初始化。
  2. 编译器行为godot-cpp 被编译为静态库 (.a)。在 macOS Clang 编译器中,如果一个 thread_local 变量在静态库内部没有被"显式调用",编译器会为了优化体积而剔除 (Dead Strip) 对应的 Wrapper 函数。
  3. 链接失败 :虽然 godot-cpp 静态库里保留了变量实体(__ZN...,类型为 S),但丢弃了 Wrapper 函数(__ZTW...)。而 GDExtension 插件(动态库)通过头文件引用了这些变量,生成的代码试图调用 Wrapper 函数,导致运行时找不到符号。

3. 修复方案 (The Solution)

由于编译器参数(如 -fvisibility=default__attribute__((used)))无法强制生成这些 Wrapper,我们采取了手动实现 Wrapper 函数并强制导出的方案。

步骤一:修改头文件权限

为了能在 .cpp 文件中手动访问私有变量,需要将 Wrapped 类中的两个 TLS 变量从 private 改为 public

文件 : godot-cpp/include/godot_cpp/classes/wrapped.hpp

cpp 复制代码
class Wrapped {
// ...
// [修改前] 这些变量原本在 private: 区域
// private:
//    _GODOT_CPP_THREAD_LOCAL static const StringName *_constructing_extension_class_name;
//    _GODOT_CPP_THREAD_LOCAL static const GDExtensionInstanceBindingCallbacks *_constructing_class_binding_callbacks;

public: 
    // [修改后] 移到 public: 区域,允许外部访问
    _GODOT_CPP_THREAD_LOCAL static const StringName *_constructing_extension_class_name;
    _GODOT_CPP_THREAD_LOCAL static const GDExtensionInstanceBindingCallbacks *_constructing_class_binding_callbacks;
// ...
};

步骤二:手动实现并导出 Wrapper

在源文件末尾,使用 extern "C" 手动定义符合 macOS 符号命名规则(下划线前缀)的函数。

文件 : godot-cpp/src/classes/wrapped.cpp

在文件最末尾(namespace 大括号之外)添加:

cpp 复制代码
// ================== Patch for macOS TLS Linking Issue ==================
// 手动定义 macOS 链接器缺失的 TLS Wrapper 函数。
// 在 macOS 上,extern "C" 的函数名会自动加一个下划线前缀。
// 我们定义 "_ZTW...",编译器生成 "__ZTW...",正好匹配链接器报错的符号。

extern "C" {

void* __attribute__((visibility("default"))) __attribute__((used)) 
_ZTWN5godot7Wrapped34_constructing_extension_class_nameE() {
    return (void*)&godot::Wrapped::_constructing_extension_class_name;
}

void* __attribute__((visibility("default"))) __attribute__((used)) 
_ZTWN5godot7Wrapped37_constructing_class_binding_callbacksE() {
    return (void*)&godot::Wrapped::_constructing_class_binding_callbacks;
}

}
// =======================================================================

4. 构建配置调整 (Build System)

除了代码修改,构建脚本 (SConstruct) 和编译参数必须严格遵守以下规则:

  1. 统一编译器godot-cpp插件 必须全部强制使用 clang++
    • 参数:use_llvm=yes
    • 环境变量:CC=clang, CXX=clang++
  2. 统一 Target :必须使用 target=template_debug(官方不支持 editor)。
  3. 链接参数 (macOS) :插件链接时必须保留 dynamic_lookup,否则无法找到 Godot 引擎符号。
    • env.Append(LINKFLAGS=["-Wl,-undefined,dynamic_lookup"])

5. 验证方法

编译 godot-cpp 后,使用 nm 命令检查静态库,确认 T (Text/Code) 类型的符号已存在:

bash 复制代码
nm -a bin/libgodot-cpp.macos.template_debug.x86_64.a | grep constructing

成功输出示例:

text 复制代码
... S __ZN5godot7Wrapped34_constructing_extension_class_nameE  (变量实体)
... T __ZTWN5godot7Wrapped34_constructing_extension_class_nameE (Wrapper函数 - 必须有这个T)

此修复方案解决了 godot::Wrapped 类在 macOS Clang 构建下的符号丢失问题,使得 GDExtension 能够正常加载。

相关推荐
无定义_3 小时前
萌新联赛(六)
macos·objective-c·cocoa
风安•辛幸13 小时前
mac上微信最新v4以上版客户端多开教程,亲测可用
macos·微信
weixin_5316708921 小时前
Interlude起来:动作动画和文字通知哪个更适合休息提醒?Mac上有没有用桌宠做动作引导的软件?
人工智能·macos·mac·swift
k4m7v2pz1 天前
Godot 4 仿 agar.io:相机缩放被 max_zoom 卡死,窗口越大球越小的根因与修复
游戏引擎·godot·相机·缩放·clamp·camera2d
FairGuard手游加固1 天前
iOS 游戏加固技术解析:代码混淆、资源加密、内存保护与重签名检测
安全·游戏·macos·unity·ios·objective-c
思盛iOS签名上架1 天前
iOS企业签名原理是什么?
macos·ios·cocoa
Wang's Blog1 天前
Vibe Coding一人即团队系列23: 基于Stitch/Sketch的UI原型1:1复刻工作流
人工智能·macos·ui·sketch
k4m7v2pz2 天前
Godot 4 窗口拉伸黑边与变形:stretch 配置全解析
数码相机·游戏引擎·godot·2d游戏·黑边·stretch·画面比例
空の鱼2 天前
Mac 上 Node 版本管理(brew + nvm 共存方案)
macos·node.js
k4m7v2pz2 天前
Godot 4 仿 agar.io 卡顿优化:用空间网格把 30 万次碰撞检测降到几千次
性能优化·godot·游戏开发·碰撞检测·空间网格