Xcode 26 真机运行崩溃 EXC_BAD_ACCESS map_images_nolock 完美解决方案

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. 重复类加剧问题

虽然重复类(StatisticsKeychain)本身只是警告,但它们会导致运行时需要处理更多的 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 也加上这个标志。

Podfilepost_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 调试器中确认:

  1. 崩溃在 libobjc.A.dylib map_images_nolock
  2. 错误码为 EXC_BAD_ACCESS (code=2)(code=2 表示写保护)
  3. 崩溃汇编指令为 str 指令(写入操作)
  4. 崩溃阶段在 "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 真机崩溃困扰的开发者。如果对你有帮助,请点赞收藏!

相关推荐
用户21816970493017 小时前
Flutter ClipPath
flutter
用户21816970493019 小时前
Flutter 折叠组件 ExpansionTile ExpansionPanelList
flutter
终端安全笔记1 天前
iOS 27 给了租赁一个新工具,但它只认受监督的设备
android·网络·安全·ios
大熊猫侯佩1 天前
独立 App 首发完成 iPhone Duo 展开态适配
ios·swiftui·swift
光影少年1 天前
setImmediate 和 setTimeout(0) 的区别
android·前端·react.js·ios·前端框架
2501_916008891 天前
SSE 和 gRPC 流式接口如何抓包,调试 SSE 与 gRPC 流式接口
网络协议·计算机网络·网络安全·ios·adb·https·udp
不羁的木木2 天前
给鸿蒙 App 增加用系统应用打开文件的能力 —— open_app_file 的鸿蒙使用指南
flutter·harmonyos
HouWan2 天前
Flutter: MediaQuery.of(context) 为什么可能拖慢页面?
android·flutter·ios
用户38034165882972 天前
Broadcast Extension 里的 Live Activity:一次跨进程妥协
ios·音视频开发
匠测AI说2 天前
XCUITest 自动化全解:同进程白盒、XCTest 架构、自动同步机制与 iOS 端选型指南
测试工具·ios·自动化