如果你在最近一次 Release Archive 的构建日志里看到这么一行:
Upload Symbols Failed:The archive did not include a dSYM for the LiveKitWebRTC.framework with the UUID ...
先冷静一下:这不是证书过期,也不是 Firebase 配置写错,更不是 LiveKit SDK 本身“上传失败”。这是 Crashlytics 在告诉你,它在归档包里没找到 LiveKitWebRTC.framework 对应的 dSYM 文件。UU 大概率是 UUID 被截断后的写法,真实日志里会是一段十六进制字符串。错误的核心是符号表缺失,不是上传通道出问题。
这篇文章我会把这件事拆开讲清楚:这个告警是怎么产生的、怎么用命令确认 dSYM 是否真的丢了、以及针对 LiveKitWebRTC.framework 这种预编译二进制依赖,有哪些靠谱的修复路线。如果你正在做 iOS 音视频应用、用了 LiveKit 做实时通信,同时又接了 Crashlytics 或 Firebase,这篇文章基本就是给你写的。
1. 这个报错到底卡在哪一环:先读懂 Crashlytics 的 dSYM 上传逻辑
1.1 报错里的 UUID 是给谁看的
先从最基础的概念说起。
dSYM 是 Debug Symbols 的缩写,是 Xcode 在编译链接后生成的一个 debug symbol bundle,里面存着 Mach-O 二进制里函数地址到函数名的映射关系。崩溃上报平台拿到一段十六进制栈地址后,必须拿到相同 UUID 的 dSYM,才能把地址翻译成-[ClassName methodName:]这种可读的调用栈。
UUID 在这里不是普通设备标识,而是 Mach-O 二进制在链接时生成的一个唯一 ID。每次编译,只要代码或编译参数有变化,UUID 就会变。Crashlytics 的 upload-symbols 脚本会上传整个 dSYM 集合,并在后台建立“UUID -> dSYM”的索引。等用户崩溃日志传上来,后台根据崩溃二进制里记录的 UUID,找到对应的 dSYM,然后做符号还原。
所以这句报错翻译成人话就是:
Crashlytics 在归档包的 dSYMs 目录里,没有找到一个 UUID 和 LiveKitWebRTC.framework 匹配的 dSYM。
它没有说“你的网络坏了”,也没有说“你的签名不对”,只是告诉你:这个 framework 的崩溃以后无法符号化。
1.2 为什么二进制框架最容易触发这条告警
你可能会有疑问:为什么主 App 的 dSYM 好好的,偏偏 LiveKitWebRTC.framework 会缺?
原因在于 LiveKitWebRTC.framework 不是 Xcode 在你项目里编译出来的。它属于预编译二进制依赖,通常以 CocoaPods 的 vendored_frameworks 方式,或者 Swift Package Manager 的 binaryTarget 方式集成到项目里。
对于 Xcode 来说,这个 framework 就是一个“外来文件”。Archive 的时候,Xcode 会为自己编译的 target 生成 dSYM,但不会去为外部下载的二进制框架重新生成 dSYM。这些第三方二进制有没有 dSYM,完全取决于对方发布时有没有附带。
LiveKit 底层依赖的是 WebRTC,WebRTC 本身编译极其重,官方大概率不会把所有版本都附上 dSYM,或者至少在某些 release 里没有完整附带。于是就会出现:App 的 dSYM 都在,唯独 LiveKitWebRTC.framework 的 dSYM 缺失。
如果你用的是纯源码集成的 SDK,一般不会有这个问题,因为源码参与编译,Xcode 会按DEBUG_INFORMATION_FORMAT的设置生成 dSYM。但 LiveKitWebRTC 这种重量级预编译框架,恰恰是 dSYM 缺失的高发区。
2. 先用三条命令确认 dSYM 是否真的缺失
遇到这种报错,我建议先别急着找解决方案,先把现场信息确认完整。很多时候,你看到的报错只是“其中一个 UUID 缺失”,但实际缺失的可能不止一个。用下面的命令查一遍,能让你对问题边界有准确判断。
2.1 检查归档包里的 dSYM 列表
首先找到你的 xcarchive 文件。最直接的方式是打开 Xcode Organizer,选中最近这次 Archive,右键“Show in Finder”。
然后在终端里进入这个 xcarchive,查看 dSYMs 目录:
cd ~/Library/Developer/Xcode/Archives/2025-01-20/你的App\ 2025-01-20\ 10.00.00.xcarchive ls -la dSYMs/正常情况下,你会看到:
你的App.app.dSYM 一些Pods依赖.framework.dSYM ...如果在列表里找不到LiveKitWebRTC.framework.dSYM,那这个告警就是真实的。
也可以用 find 更粗暴地搜一遍:
find "$HOME/Library/Developer/Xcode/Archives" -name "LiveKitWebRTC.framework.dSYM" 2>/dev/null这条命令会把你所有历史归档里包含 LiveKitWebRTC dSYM 的路径都列出来。如果一条都没有,说明你从来没有拿到过它的 dSYM。
2.2 直接读取二进制 UUID 做一次硬核对
光看有没有 dSYM 还不够。有时候你有 dSYM,但 UUID 和 framework 不匹配,上传了一样没用。所以第二步是直接读取 LiveKitWebRTC.framework 的 UUID。
先找到你工程里实际链接的 LiveKitWebRTC.framework,然后执行:
xcrun dwarfdump --uuid /path/to/LiveKitWebRTC.framework/LiveKitWebRTC输出大概长这样:
arm64 UUID: A1B2C3D4-1234-5678-9ABC-DEF012345678 /path/to/LiveKitWebRTC.framework/LiveKitWebRTC如果这是一个 fat framework 或 xcframework 里的某一段 slice,可能还会输出多个架构、多个 UUID。
然后再看报错日志里提到的那串 UUID,和这里的输出对一下。一致,说明 Crashlytics 说的就是当前这个二进制;不一致,可能是你 Archive 时的 framework 版本和本地当前版本不一致,需要去翻对应版本的产物。
拿到这个 UUID 之后,如果你能找到一个疑似可用的 dSYM,再验证一下 dSYM 的 UUID:
xcrun dwarfdump --uuid /path/to/LiveKitWebRTC.framework.dSYM/Contents/Resources/DWARF/LiveKitWebRTC两边 UUID 必须完全一致,这个 dSYM 才有意义。
2.3 一个容易漏掉的细节:同一个 framework 可能有多个 UUID
LiveKitWebRTC 如果以 xcframework 形式提供,里面通常有多个 slice,比如:
- ios-arm64
- ios-arm64_x86_64-simulator
- ios-arm64_x86_64
不同 slice 的 Mach-O 是不同编译产物,UUID 自然不同。Archive 真机包时,upload-symbols 主要关心 device slice 的 dSYM;但如果用模拟器跑性能测试或做 CI 预编译,simulator slice 的 dSYM 缺失同样会刷告警。
所以排查时要看全,不要只看某一个路径。用这条命令可以遍历整个 xcframework 里的所有二进制 UUID:
find /path/to/LiveKitWebRTC.xcframework -name "LiveKitWebRTC" -type f -exec xcrun dwarfdump --uuid {} \;3. 针对 LiveKitWebRTC.framework 的三套修复方案
确认缺失之后,接下来就是怎么补。根据我的经验,修复方案按优先级排列大致如下:先找官方 dSYM,再考虑自己构建,最后才是评估是否忽略。
3.1 方案一:从官方发布包补齐 dSYM(首选)
这个方案最省事,但很多人不知道,或者下载时没注意。
LiveKitWebRTC 的 release 页面或对应版本的发布包里,部分版本会附带 dSYM 文件,可能在单独的dSYMs/目录里,也可能直接以LiveKitWebRTC.framework.dSYM形式放在下载包里。如果你能找到和当前 framework 同版本的 dSYM,直接把它复制到 xcarchive 的 dSYMs 目录里:
cp -R /path/to/downloaded/LiveKitWebRTC.framework.dSYM "$ARCHIVE_DIR/dSYMs/"然后手动重跑一次 Crashlytics 上传脚本:
"$PODS_ROOT/FirebaseCrashlytics/upload-symbols" \ -gsp "$SRCROOT/GoogleService-Info.plist" \ -p ios "$ARCHIVE_DIR/dSYMs"如果日志里出现类似Successfully uploaded symbols的内容,说明 Crashlytics 已经接受这个 dSYM。
注意:复制 dSYM 之前一定用dwarfdump --uuid验证版本,别随便拿一个同名文件硬塞进去,UUID 不匹配的话上传一万遍也没用。
3.2 方案二:用源码重新编译带 DWARF 的 framework
如果官方没有提供 dSYM,或者提供的版本和你当前用的版本对不上,那就只能自己构建。
LiveKitWebRTC 本质是 WebRTC 的 LiveKit 封装产物,如果官方公开了源码工程,你可以用xcodebuild自己编译一个带 dSYM 的版本。以 webRTC 源码工程为例,基本命令是:
xcodebuild build \ -project WebRTC.xcodeproj \ -scheme WebRTC \ -configuration Release \ -destination 'generic/platform=iOS' \ -derivedDataPath ./build \ CODE_SIGNING_ALLOWED=NO \ DEBUG_INFORMATION_FORMAT=dwarf-with-dsym \ ENABLE_DEBUG_DWARF=YES关键参数是DEBUG_INFORMATION_FORMAT=dwarf-with-dsym。有些 Release 配置默认是dwarf,也就是只保留 DWARF 调试信息但不生成 dSYM bundle。只有显式指定成dwarf-with-dsym,Xcode 才会在编译结束后生成 .dSYM。
构建完成后:
find ./build -name "LiveKitWebRTC.framework.dSYM" -type d然后把生成好的 dSYM 复制到归档包,再走一遍上传脚本。
我得提醒一句:WebRTC 这种级别的项目,完整编译一次非常耗时,磁盘占用也可能超过 10GB。如果只是想要一个 dSYM,付出的时间成本并不低。所以这个方案通常只适合两种情况:一是团队本来就有维护 WebRTC 源码工程的能力;二是官方长期不提供 dSYM,而你又确实需要分析 LiveKit 内部的崩溃。
3.3 方案三:判断这个告警值不值得管
如果 LiveKitWebRTC.framework 的 dSYM 只是缺失,但你的业务并不关心它内部的符号化,那有一个很务实的选项:忽略它。
很多人看到“Upload Symbols Failed”就紧张,其实这个告警并不会直接让上传流程失败,也不会让 App 发布流程中断。它影响的只是 LiveKitWebRTC 这个 framework 的崩溃栈可读性。你的主 App、其他源码 SDK 的崩溃符号化,完全不受影响。
但这里有个禁忌:不要在 upload-symbols 脚本后面直接加|| true或者|| echo "ignore"。我见过不止一个团队,为了消掉一条 dSYM 告警,把整整一行脚本吞掉。结果某天 Firebase 服务端临时抖动,upload-symbols 真的失败时,CI 照样绿,等到需要查崩溃符号时才发现这一批全没传上去。
更好的做法是:保留脚本的失败退出码,只在日志里标记“已知缺失 dSYM”。这个思路下一章我会具体展开。
4. 写一个 Run Script:在 Crashlytics 上传前把缺失 dSYM 变成显式输出
4.1 脚本逻辑与关键命令
如果你的项目是长期工程,后面还会持续升级 LiveKit SDK,那每次 Archive 都靠人眼盯日志不现实。我建议在 upload-symbols 之前加一个 Run Script 阶段,专门检查已知的第三方 framework 是否有 dSYM。
脚本内容如下:
#!/bin/bash set -uo pipefail REQUIRED_FRAMEWORKS=( "LiveKitWebRTC" ) DSYM_ROOT="${DWARF_DSYM_FOLDER_PATH:-${BUILT_PRODUCTS_DIR}}" MISSING_COUNT=0 if [ ! -d "$DSYM_ROOT" ]; then echo "::error:: dSYM directory not found: $DSYM_ROOT" exit 1 fi for fw in "${REQUIRED_FRAMEWORKS[@]}"; do if ! find "$DSYM_ROOT" -maxdepth 2 -name "${fw}.framework.dSYM" | grep -q .; then echo "::error:: Missing dSYM for ${fw} under ${DSYM_ROOT}" MISSING_COUNT=$((MISSING_COUNT + 1)) fi done if [ "$MISSING_COUNT" -gt 0 ]; then echo "::error:: Found ${MISSING_COUNT} missing dSYM(s). Fix before upload or expect unsymbolicated third-party stacks." if [ "${FAIL_ON_MISSING_DSYM:-0}" = "1" ]; then exit 1 fi fi把这个脚本放在 Xcode 的 Target -> Build Phases 里,新增一个 Run Script Phase,放在 Crashlytics 的[CP]upload-symbols 脚本之前。
默认情况下,FAIL_ON_MISSING_DSYM没有设置,脚本只输出 error 日志但不阻断构建。这样 Archive 流程不会因为你还没补齐 dSYM 就中断。等你想强制团队修复时,在 CI 环境变量里加FAIL_ON_MISSING_DSYM=1,就能让缺失 dSYM 直接变成构建失败。
这里有个细节值得说:脚本里的find -maxdepth 2是为了匹配dSYMs/LiveKitWebRTC.framework.dSYM这种两层结构,一般够用。如果你把 dSYM 放在更深的层级,可以去掉-maxdepth限制。
4.2 上传前如何确认 Crashlytics 真的收到了 dSYM
脚本检查完之后,upload-symbols 阶段还是要仔细看日志。正常的成功日志通常包含:
Running upload-symbols... Uploading symbols for <UUID> Successfully uploaded symbols如果只是看到Upload Symbols Failed后面跟了一串 dSYM 缺失告警,但没有真正的 error,说明 Crashlytics 还在继续处理其他 dSYM。你可以把这一阶段的完整日志复制下来,留作排查依据。
还有一种常见做法:在 upload-symbols 命令里用-d参数指定 dSYM 目录。这点在 FirebaseCrashlytics 文档里也有,目的就是避免 Xcode 默认的DWARF_DSYM_FOLDER_PATH和实际目录不一致。对于手动补 dSYM 的场景,尤其建议显式传路径。
4.3 SPM 接入时脚本路径差异
如果你的项目用的是 Swift Package Manager,而不是 CocoaPods,upload-symbols 脚本的路径会不一样。它不在 Pods 目录里,而是位于 Firebase SDK 的 checkout 目录下:
DerivedData/.../SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols这个路径在不同机器上不稳定,所以脚本里最好用find去定位:
UPLOAD_SYMBOLS=$(find "$BUILD_DIR" -name upload-symbols -type f 2>/dev/null | head -n 1)找到后,再执行上传。dSYM 检查脚本则不用变,因为DWARF_DSYM_FOLDER_PATH是 Xcode 环境变量,不管用哪种依赖管理方式都会正确指向归档包的 dSYMs 目录。
5. 其他容易和 dSYM 缺失混淆的报错
5.1 自己 App 的 dSYM 缺失:和第三方二进制是两码事
我在团队里经常看到有人把 LiveKitWebRTC 的 dSYM 告警,和“主 App dSYM 未生成”混淆。这其实是两类问题。
如果你的主 App 也提示 dSYM 缺失,第一反应应该是查 Build Settings:
- 找到
Debug Information Format - Release 下必须选择
DWARF with dSYM File
如果这个值被设置成DWARF,那么 Release 构建不会生成 .dSYM,无论你用不用 LiveKit,Crashlytics 都会缺符号表。
而 LiveKitWebRTC 这种预编译框架,即使你的主项目设置完全正确,它缺 dSYM 和大家都不矛盾。因为 Xcode 不会为“不是由项目编译产生的二进制”生成 dSYM。
5.2 XCFramework 和 SPM 的 dSYM 打包方式
随着 SPM 越来越普及,LiveKitWebRTC 也有不少版本是通过 binaryTarget 分发的。SPM 里如果要用 xcframework 分发,打包时最好把 dSYM 一起塞进去。
正确的结构大致是这样:
LiveKitWebRTC.xcframework/ Info.plist ios-arm64/ LiveKitWebRTC.framework/ dSYMs/ LiveKitWebRTC.framework.dSYM/ ios-arm64_x86_64-simulator/ LiveKitWebRTC.framework/ dSYMs/ LiveKitWebRTC.framework.dSYM/在创建 xcframework 时,可以通过-debug-symbols参数显式指定 dSYM 路径:
xcodebuild -create-xcframework \ -framework build/ios-arm64/LiveKitWebRTC.framework \ -debug-symbols build/ios-arm64/LiveKitWebRTC.framework.dSYM \ -framework build/ios-arm64_x86_64-simulator/LiveKitWebRTC.framework \ -debug-symbols build/ios-arm64_x86_64-simulator/LiveKitWebRTC.framework.dSYM \ -output LiveKitWebRTC.xcframework这样生成出来的 xcframework,被 SPM 引用后,Archive 时 dSYM 就有机会被 Xcode 和 Crashlytics 识别到。很多团队打包 SPM 二进制时只丢 framework,不丢 dSYM,这就是告警反复出现的原因。
5.3 不要把“符号上传成功”和“崩溃已符号化”画等号
还有一个认知误区:看到 upload-symbols 说成功,就觉得所有崩溃都能符号化。其实上传成功只能说明“当前这次提交的 dSYM 已经被后台记录”,但用户崩溃发生时的 App 版本、UUID 是否和你上传的 dSYM 匹配,是另一回事。
所以正确流程是:每次 Release 出包后,保留好对应的 xcarchive,不要删。手动测试几次崩溃,或者让 QA 故意触发几个可控崩溃,确认 Crashlytics 后台看到的调用栈已经符号化,再放心发版。
6. 我自己的处理节奏:先确认,再决定要不要补
如果你现在正好站在这个告警面前,我的建议是按这个节奏走:
先在终端跑一遍dwarfdump --uuid,确认告警里的 UUID 和当前 framework 的 UUID 一致。然后去 LiveKitWebRTC 的发布包里找同版本 dSYM。找不到就评估这个 framework 的崩溃栈对你的价值。如果 LiveKit 是核心链路,崩溃栈全地址不可接受,那就花时间用源码构建带 dSYM 的版本。如果不是核心链路,就在日志里保留告警,继续发布。
我个人更倾向于在项目里长期留着第四节那个检查脚本,但是默认不阻断构建。因为 LiveKit 升级频率不低,每次升级都可能伴随 dSYM 的加入或缺失,脚本至少能在 Archive 时把“哪个版本缺 dSYM”这件事明确暴露出来。以后再看到Upload Symbols Failed,你脑子里就不会再是一场虚惊,而是一套清晰的排查路径。