跳到主要内容
教程目录

Windows 上真机调试 iOS App,断点、变量与崩溃定位

这篇讲在 Windows 上用 VS Code 调试跑在 iPhone 真机上的 App,从连上手机到定位一次崩溃。适用于 Swift、Objective-C、C / C++ 写的原生代码,Flutter、React Native、Unity 等框架工程的原生部分也是同样的操作。你需要一台 iPhone 和数据线,工程已经能用 KXBuild 编译,KXBuild 的安装见安装页。

连接并信任设备

用数据线把 iPhone 接到电脑上,打开 VS Code 的 KXBuild 视图,「设备」节点会显示手机名、连接方式和系统版本。设备节点显示「点击配置」说明电脑上还没有 Apple 设备驱动,点一下按提示安装,过程中要以管理员身份确认一次。命令行执行 kxdevice doctor 效果相同。

第一次连接要在手机上完成两步设置。

  1. 信任此电脑。解锁手机,在弹窗中点「信任」。用的是充电线或者手机锁屏时弹窗不会出现。
  2. 打开开发者模式。在「设置 → 隐私与安全性 → 开发者模式」中打开,手机会重启一次,重启后按提示确认。这个开关在手机连接过电脑之后才会出现。

同一台手机可能同时出现 USB 和 Wi-Fi 两行,安装和调试选 USB 更稳定。接了多台手机时点设备节点或状态栏选一台,之后 F5 都装到这一台。命令行用下面的命令查看设备,第一列是 UDID。

1kxdevice device list

列表为空时换一根数据线或换个 USB 口,再解锁手机看有没有弹窗。更多情况见设备准备。

第一次调试某个 iOS 版本时会自动下载对应的符号库,有几个 GB,大约十多分钟,进度显示在通知中。只需下载一次,中断后下次继续。下载完成之前断点可能不停,或者只能看到汇编。

用 Debug 配置构建

调试前确认 KXBuild 视图「构建配置」节点显示的是 Debug。Release 配置开启了优化,断点位置会跳,变量也常常显示不出来。证书目录里要有 Apple Development 证书和包含这部手机 UDID 的 Development 描述文件,见证书与签名。

三种启动方式

按场景选一种。

方式 入口 适合的情况
构建并运行 F5、KXBuild 视图标题栏 ▶、target 行内按钮 日常改代码后调试,会构建、签名、安装、启动并附加调试器
运行 「KXBuild: 运行」、target 行内按钮 代码没改,只想重新装一次上一次的构建产物并启动
附加 「KXBuild: 附加到运行中的 App」 App 已经在手机上运行,问题要操作一段时间才出现

构建发生在调试会话开始之前,调试工具栏上的「重启」不会重新编译。改了代码要先停止,再按 F5。

默认按 F5 就能启动,不需要 launch.json。想固定某台设备、某个 target 或构建配置时再写 launch.json,字段说明见运行与调试。

在 Windows 上用 VS Code 断点调试 iPhone 上的 App

设置断点

在源码行号左侧单击就能加断点,命中后使用 VS Code 标准的调试视图,单步、继续、跳出的快捷键和其他语言一样。下面几种断点都在行号左侧右键添加,或者在已有断点上右键「编辑断点」修改。

条件断点

循环里或者被频繁调用的函数,普通断点会停很多次。右键「添加条件断点」,选「表达式」,填一个用源码语言写的条件,只有条件为真时才停。

1index == 42 && item.name == "test"

同一个菜单里选「命中次数」,填 10 表示第 10 次经过时才停,适合排查第 N 次才出现的问题。

日志断点

右键「添加日志点」,填一段消息,经过这一行时把消息打印到调试控制台,程序不停。花括号里的表达式会被替换成当时的值。

1加载第 {index} 项,count = {items.count}

临时想看某个值又不想改代码、重新编译时用它,比加 print 快。

异常断点

调试时展开「运行和调试」视图底部的「断点」面板,会列出 Swift Throw、Objective-C Throw、C++ Throw 等选项,勾选后在异常抛出的位置停下。

排查 Objective-C 的 NSException 崩溃(例如数组越界、给 nil 发不存在的消息)时勾选 Objective-C Throw,停下来的位置就是抛出异常的那一行,比等崩溃后看报告直接得多。

函数断点

在「断点」面板标题栏点「+」,输入函数名,例如 viewDidLoad 或 -[MyViewController viewDidLoad],不用找到源码就能在函数入口停下。

查看变量和调用栈

断点命中后,左侧几个面板分别回答不同的问题。

面板 用途
变量 当前函数的局部变量和 self,对象可以逐层展开
监视 添加表达式,每次停下都自动求值,适合一直盯着的值
调用栈 每个线程的调用链,点一帧就切到那一层查看它的变量

鼠标悬停在源码里的变量上也能看到当前值。停在没有源码的位置时,可以打开汇编视图查看。

在调试控制台执行 LLDB 命令

调试控制台(Ctrl+Shift+Y)可以直接输入 LLDB 命令,下面是最常用的几条。

命令 作用
po self 打印对象的描述,等同于调用 description
p count * 2 求表达式的值并打印类型
v 打印当前帧的所有局部变量,不执行代码,速度快
bt 打印当前线程的调用栈
bt all 打印所有线程的调用栈,排查卡死、死锁时用
thread list 列出所有线程和各自停在哪里
expr self.view.backgroundColor = .red 在运行中修改状态,继续执行后能看到效果

输入的内容被当成表达式而不是命令时,在前面加一个反引号,例如 `bt。

看 App 输出和设备日志

App 里 print、NSLog、os_log 的输出显示在「输出」面板的 KXBuild Console 通道,构建日志在 KXBuild 通道。输出面板用 Ctrl+Shift+U 打开,右上角下拉框切换。

App 没在调试、或者要看系统层面的日志时,用 kxdevice 直接读手机日志。ostrace 只输出 os_log,NSLog 和 Logger 都经过它,比 syslog 干净,还能按子系统和级别过滤。

1kxdevice ostrace --subsystem com.example.myapp --levels error,fault

syslog 输出整机的系统日志,量很大,配合 findstr 过滤。

1kxdevice syslog | findstr MyApp

抓取崩溃报告并定位

App 在手机上崩溃后,系统会生成一份崩溃报告。按 F5 时提示 didn’t start in time,通常也是 App 一启动就崩溃了。先列出手机上的报告,再下载到本地。

1kxdevice crash list MyApp*
2kxdevice crash pull .\crashes MyApp*

下载的 .ips 文件用文本编辑器打开,按下面的顺序看。

  1. 异常类型。EXC_BAD_ACCESS 多是访问了已释放的内存或空指针,EXC_CRASH (SIGABRT) 多是未捕获的异常或断言失败,EXC_BREAKPOINT 在 Swift 里常见于强制解包 nil、数组越界。
  2. Last Exception Backtrace。有这一段说明是 Objective-C 异常,这里的调用栈比崩溃线程的更有用。
  3. 崩溃线程的调用栈。找到第一帧属于你的 App 的位置,那里通常就是出问题的代码。

调用栈里你的 App 只显示地址、没有函数名时,最直接的办法是用 Debug 配置按 F5 复现一次。调试器会停在崩溃的那一行,调用栈和变量都能直接看,Objective-C 异常再配合上面的异常断点。

不想记命令时,执行 kxdevice web 后用浏览器打开 http://localhost:8780,崩溃报告、系统日志、进程列表都能在网页上查看。

附加到已经在运行的 App

有些问题要操作一段时间才出现,重新启动就不好复现。这时让 App 继续运行,把调试器附加上去。

在命令面板运行「KXBuild: 附加到运行中的 App」,会列出手机上正在运行的应用,选一个即可。附加后断点、变量、调试控制台和前面讲的一样。附加的 App 必须是用开发证书签名的,App Store 下载的 App 不能附加。

常见错误

提示 处理
No iOS device selected 在 KXBuild 视图中选一台设备
Developer Mode is off 手机上打开开发者模式并重启
Developer Disk Image is not mounted 重新插拔手机,或运行体检
设备尚未配对 / 信任 解锁手机并点信任
did not report the app as launched in time 解锁手机,检查数据线连接
didn’t start in time App 可能一启动就崩溃,按上一节抓取崩溃报告
断点不停,或只停在汇编上 换成 Debug 配置,等符号库下载完成

签名和安装类的报错见iOS 签名报错与 ipa 安装失败的解决办法,其他错误见排错与 FAQ。