Xcode 26 真机运行崩溃 EXC_BAD_ACCESS map_images_nolock 完美解决方案
问题描述
升级 Xcode 26(26.0 / 26.2)后,项目在真机调试和打包时,App 启动阶段直接崩溃,卡在启动页无法进入主界面。模拟器无法复现(Flutter 混编项目模拟器不支持 release 构建)。
Xcode 崩溃信息:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x10525a7c0)
崩溃调用栈:
libobjc.A.dylib`map_images_nolock
崩溃精确位置在 map_images_nolock 函数内部的 "fix up @protocol references" 阶段,汇编指令为:
asm
-> str x8, [x27, x25, lsl #3] ; 尝试写入 __objc_protorefs 段
控制台同时输出以下警告:
objc[1218]: Class Statistics is implemented in both
.../Frameworks/ffmpegkit.framework/ffmpegkit and
.../Frameworks/ChinaArtGallery_protoc.framework/ChinaArtGallery_protoc.
One of the two will be used. Which one is undefined.
objc[1218]: Class Keychain is implemented in both
.../Frameworks/AliyunPlayer.framework/AliyunPlayer and
.../中华珍宝馆.debug.dylib.
One of the two will be used. Which one is undefined.
环境信息
| 项目 | 版本 |
|---|---|
| Xcode | 26.2 (Build 17C52) |
| 目标设备 | iPhone 真机,iOS 18 |
| 项目类型 | ObjC + Flutter 混编 |
| 依赖管理 | CocoaPods (use_frameworks!) |
| 部署目标 | iOS 13.0 |
根因分析
1. 崩溃本质:写入只读内存
EXC_BAD_ACCESS (code=2) 的 code=2 对应 KERN_PROTECTION_FAILURE,意味着目标内存地址存在,但当前是只读的。
ObjC 运行时在 map_images_nolock 中加载所有 image(framework、dylib、可执行文件)时,会执行 protocol references fixup (协议引用修复),需要将协议引用指针写入 __objc_protorefs section。
2. Xcode 26 链接器行为变化
这是问题的核心。
Xcode 26 的链接器(ld)默认将 __objc_protorefs 等 ObjC 元数据放入 __DATA_CONST segment。__DATA_CONST 在 dyld 完成自身 fixup 后会被标记为只读。
而 iOS 18 设备上的 ObjC 运行时(libobjc.A.dylib)在执行 protocol fixup 时,并不会主动将 __DATA_CONST 改为可写 ,它假设 __objc_protorefs 在可写的 __DATA segment 中。
时序如下:
1. dyld 加载所有 image
2. dyld 将 __DATA_CONST 设为可写,执行 rebase/bind fixup
3. dyld 完成 fixup,将 __DATA_CONST 标记为只读 ← 关键步骤
4. dyld 触发 map_images 通知
5. ObjC 运行时 map_images_nolock 执行 protocol fixup
6. 尝试写入 __DATA_CONST 中的 __objc_protorefs → 💥 CRASH
在 Xcode 16 及更早版本中 ,链接器将 __objc_protorefs 放在 __DATA(始终可写),所以不会崩溃。
3. 为什么只有真机崩溃
模拟器使用 x86_64/arm64 模拟器运行时(来自 Xcode SDK),与真机的 arm64 运行时行为不完全一致。真机上 iOS 18 的 libobjc 对 __DATA_CONST 的内存保护更严格。
4. 重复类加剧问题
虽然重复类(Statistics、Keychain)本身只是警告,但它们会导致运行时需要处理更多的 protocol fixup,增大了触发崩溃的概率。
解决方案
核心修复:添加 -Wl,-no_data_const 链接器标志
该标志告诉 Xcode 26 的链接器不使用 __DATA_CONST segment ,将所有数据(包括 __objc_protorefs)放入可写的 __DATA segment。
关键点:必须同时对主工程和所有 CocoaPods targets 生效。
步骤 1:修改主工程 OTHER_LDFLAGS
在 Xcode 中选择主 target → Build Settings → Other Linker Flags,添加:
-Wl,-no_data_const
或者直接编辑 project.pbxproj,在 Debug 和 Release 的 OTHER_LDFLAGS 中添加:
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-Wl,-no_data_const",
);
步骤 2:修改 Podfile,给所有 Pod targets 添加该标志
由于项目使用 use_frameworks!(动态框架),每个 Pod 都是独立编译链接的 framework。只改主工程不够,必须给每个 Pod target 也加上这个标志。
在 Podfile 的 post_install 中添加:
ruby
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
# Xcode 26: 强制 __objc_protorefs 放入可写的 __DATA 段,避免 map_images_nolock 崩溃
other_ldflags = config.build_settings['OTHER_LDFLAGS'] || ['$(inherited)']
other_ldflags = [other_ldflags] if other_ldflags.is_a?(String)
unless other_ldflags.include?('-Wl,-no_data_const')
other_ldflags << '-Wl,-no_data_const'
end
config.build_settings['OTHER_LDFLAGS'] = other_ldflags
end
end
end
步骤 3:重新安装 Pod 并 Clean Build
bash
pod install
然后在 Xcode 中 Product → Clean Build Folder (Shift+Cmd+K),再真机运行。
排查过程中尝试过的无效方案
| 方案 | 结果 | 原因 |
|---|---|---|
| 清理 DerivedData + 重新 pod install | ❌ 无效 | 不是缓存问题 |
移除 -ObjC 链接器标志 |
❌ 无效 | 问题不在 -ObjC |
添加 -Xlinker -dead_strip -Xlinker -allow_dead_duplicates |
❌ 无效 | 不能解决内存保护问题 |
添加 -Wl,-no_fixup_chains |
❌ 无效 | fixup chains 与此问题无关 |
| 升级第三方 SDK(如 AliPlayerSDK) | ❌ 无效 | 预编译 framework 的 section 布局未变 |
给 protobuf 添加 objc_class_prefix 消除重复类 |
❌ 编译错误过多 | 影响范围太大,不实际 |
只在主工程添加 -Wl,-no_data_const |
❌ 无效 | Pod frameworks 也需要该标志 |
主工程 + 所有 Pod targets 都添加 -Wl,-no_data_const |
✅ 解决 | 所有 image 的 protorefs 都在可写段 |
补充说明
这个方案有副作用吗?
-no_data_const 会让链接器不使用 __DATA_CONST 优化。__DATA_CONST 的本意是让不需要运行时修改的数据页面变为只读,从而:
- 减少 dirty memory(脏页)
- 提高安全性
禁用后,这些数据会放在普通的 __DATA segment,略微增加内存占用,但对 App 功能和性能几乎没有可感知的影响。这是一个合理的 workaround,直到 Apple 在后续 Xcode/iOS 版本中修复这个兼容性问题。
哪些项目容易中招?
- 使用
use_frameworks!(动态框架)的 CocoaPods 项目 - 包含预编译二进制 framework(如 AliPlayerSDK、ffmpegkit 等)
- 存在 ObjC 重复类名的项目(加剧触发概率)
- ObjC + Swift + Flutter 混编项目
如何验证是否是这个问题?
在 Xcode 调试器中确认:
- 崩溃在
libobjc.A.dylib map_images_nolock - 错误码为
EXC_BAD_ACCESS (code=2)(code=2 表示写保护) - 崩溃汇编指令为
str指令(写入操作) - 崩溃阶段在
"IMAGE TIMES: fix up @protocol references"附近
gRPC-Core 编译报错
升级到 Xcode 26 后,如果项目依赖了 gRPC(如 protobuf 生成的服务端代码),可能会遇到 C++ template 语法报错。在 Podfile 的 post_install 中添加:
ruby
if target.name == 'gRPC-Core'
target.build_configurations.each do |config|
flags = config.build_settings['OTHER_CPLUSPLUSFLAGS'] || ['$(inherited)']
flags << '-Wno-missing-template-arg-list-after-template-kw'
config.build_settings['OTHER_CPLUSPLUSFLAGS'] = flags
end
end
完整 Podfile post_install 示例
ruby
post_install do |installer|
# Flutter 相关配置(如有)
flutter_post_install(installer) if defined?(flutter_post_install)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
# Xcode 26: 强制 __objc_protorefs 放入可写 __DATA 段
other_ldflags = config.build_settings['OTHER_LDFLAGS'] || ['$(inherited)']
other_ldflags = [other_ldflags] if other_ldflags.is_a?(String)
unless other_ldflags.include?('-Wl,-no_data_const')
other_ldflags << '-Wl,-no_data_const'
end
config.build_settings['OTHER_LDFLAGS'] = other_ldflags
end
# gRPC-Core Xcode 26 兼容
if target.name == 'gRPC-Core'
target.build_configurations.each do |config|
flags = config.build_settings['OTHER_CPLUSPLUSFLAGS'] || ['$(inherited)']
flags << '-Wno-missing-template-arg-list-after-template-kw'
config.build_settings['OTHER_CPLUSPLUSFLAGS'] = flags
end
end
end
end
总结
Xcode 26 的链接器将 ObjC 协议引用(__objc_protorefs)默认放入只读的 __DATA_CONST segment,而 iOS 18 的 ObjC 运行时在 protocol fixup 阶段假设该内存可写,导致 KERN_PROTECTION_FAILURE 崩溃。
解决方案:给主工程和所有 Pod targets 添加 -Wl,-no_data_const 链接器标志。
希望这篇文章能帮到同样被 Xcode 26 真机崩溃困扰的开发者。如果对你有帮助,请点赞收藏!