iOS 调试 crash breakpoint EXC_BAD_ACCESS SIGABRT

本文介绍了在Xcode中遇到程序崩溃时的调试技巧,包括使用GDB进行调试、添加全局及符号断点、设置环境变量等方法,帮助开发者快速定位并解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在调试程序的时候,总是碰到crash的bug,而且一追踪就是一些汇编的代码,让人特别疑惑。

一般情况下可以通过增加两天断点来解决此问题,方法介绍如下:

基本上有错误分为以下几种类型:

signal(SIGABRT, MySignalHandler);

signal(SIGILL, MySignalHandler);

signal(SIGSEGV, MySignalHandler);

signal(SIGFPE, MySignalHandler);

signal(SIGBUS, MySignalHandler);

signal(SIGPIPE, MySignalHandler);

SIGABRT和EXC_BAD_ACCESS较为特殊,算是比较好跟进。

SIGABRT是系统报错,在memery warning之后,系统会把程序强制退出,报的就是这个错误。

EXC_BAD_ACCESS 大多数时候是内存提前释放而引起的问题,或者访问的方法不存在引起的。

 

 1. Xcode内置GDB,可以使用GDB调试,调试命令:

1.1 po 命令:为 print object 的缩写,显示对象的文本描述

(lldb) po [$eax class]:输出异常对象的地址


(lldb) po [$eax name]:输出这个异常的名字


(lldb) po [$eax reason]:这个将会输出错误消息:


(lldb)  “po $eax”:对这个对象调用“description”方法和打印出来

“$eax”是cup的一个寄存器。在一个异常的情况下,这个寄存器将会包含一个异常对象的指针。注意:$eax只会在模拟器里面工作,假如你在设备上调试,你将需要使用”$r0″寄存器

1.2 print 命令:有点类似于格式化输出,可以输出对象的不同信息

比如:print (char*)[[dic description] cString]、(lldb) print (int)[label retainCount]

1.3 info 命令:我们可以查看内存地址所在信息

1.4 info line *内存地址:可以获取内存地址所在的代码行相关信息

1.5 show 命令:显示 GDB 相关的信息。如:show version 显示GDB版本信息

1.6 bt: 显示当前进程的函数调用栈的情况;"up num":查看调用的详细信息;down:返回栈列表;l:显示详细代码信息;p:输出数值。

2. 添加全局断点(Add Exception BreakPoint):

2.1 添加步骤:

1. In the bottom-left corner of the breakpoints navigator, click the Add button. 
2. Choose Add Exception Breakpoint. 
3. Choose the type of exception from the Exception pop-up menu. 
4. Choose the phase of the exception handling process at which you want program execution to stop. 
5. Click Done.

2.2 使用场景:

程序因为SIGABRT而crash,想要定位到导致crash的行。


然后右键单击该断点选择move breakpoint to,选择User,即可以在所有项目中起作用


3. 添加符号断点(Add Symbolic BreakPoint):

3.1 断点执行的时机:Symbolic breakpoints stop program execution when a specific function or method starts executing

3.2 添加步骤:

1. Steps In the bottom-left corner of the breakpoint navigator, click the Add button.

2. Choose Add Symbolic Breakpoint. 
3. Enter the symbol name in the Symbol field. 
4. Click Done.

3.3 使用场景:

当想让系统在某个指定条件处中断时,设置相应的断点。

比如:

objc_exception_throw:在系统抛出异常处设置断点。

-[NSException raise]:

还可添加输入malloc_error_break的symbolic断点,以跟踪调试释放了2次的对象


4. 设置NSZombieEnabled、MallocStackLogging、NSAutoreleaseFreedObjectCheckEnabled、NSDebugEnabled:

4.1 设置方法:

1. Product->Edit Scheme...->Run...->EnvironmentVariables. 
2. add NSZombieEnabled,set the value with YES
3. add MallocStackLogging, set the value with YES.

4. add NSAutoreleaseFreedObjectCheckEnabled, set the value with YES.

5. add NSDebugEnabled, set the value with YES.

4.2 使用场景:

主要为了解决EXC_BAD_ACCESS问题,MallocStackLogging用来启用malloc记录(使用方式 malloc_history ${App_PID} ${Object_instance_addr})。

4.3 需要注意的问题

NSZombieEnabled只能在调试的时候使用,千万不要忘记在产品发布的时候去掉,因为NSZombieEnabled不会真正去释放dealloc对象的内存。

5. 重写respondsToSelector方法

5.1 实现方式


#ifdef _FOR_DEBUG_  
-(BOOL) respondsToSelector:(SEL)aSelector {  
printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector) UTF8String]);  
return [super respondsToSelector:aSelector];  
}  
#endif  
5.2 使用方法:
需要在每个object的.m或者.mm文件中加入上面代码(应该可以使用类属实现),并且在other c flags中加入-D  _FOR_DEBUG_(记住请只在Debug  Configuration下加入此标记)。这样当你程序崩溃时,Xcode的console上就会准确地记录了最后运行的object的方法。

参考文章:

1. Xcode GDB 调试:http://blog.youkuaiyun.com/ch_soft/article/details/7005998

2. XCode的一些调试技巧:http://blog.youkuaiyun.com/kesalin/article/details/7222153

3. About the Breakpoint  Navigator:http://developer.apple.com/library/mac/#recipes/xcode_help-breakpoint_navigator/articles/about_breakpoint_navigator.html#//apple_ref/doc/uid/TP40010433-CH6-SW1

4. 当程序崩溃的时候怎么办 part-1:http://article.ityran.com/archives/1006

5. 当程序崩溃的时候怎么办 Part-2:http://article.ityran.com/archives/1143

6. Memory Usage Performance  Guidelines:https://developer.apple.com/library/mac/#documentation /performance/Conceptual/ManagingMemory/ManagingMemory.html#//apple_ref/doc/uid/10000160-SW1

<think>我们正在处理一个关于Swift主线程EXC_BREAKPOINT错误的问题。根据引用[1]和[2],EXC_BREAKPOINT异常通常对应于SIGTRAP信号,它通常由断点、陷阱指令等引起。在Swift中,这可能是由于某些运行时错误(如强制解包nil可选值)导致的。 根据引用[3],我们可以查看崩溃报告中的异常信息,特别是异常类型、位置、线程等信息,以帮助定位问题。 解决方案步骤: 1. 理解EXC_BREAKPOINT在Swift中的常见原因: - 强制解包nil可选值(如:`var a: Int? = nil; let b = a!`) - 数组越界访问 - 类型转换失败(使用`as!`进行强制转换而转换失败) - 代码中显式的断点(如`__builtin_trap()`或`fatalError()`) - 其他运行时错误(如未实现协议方法等) 2. 定位崩溃点: - 查看崩溃日志中的线程回溯(Thread Backtrace),特别是触发异常的线程(通常是主线程),找到对应的代码行。 - 使用Xcode的调试功能,在发生崩溃时,Xcode会停在崩溃发生的位置。 3. 解决方法: - 避免强制解包:使用可选绑定(if let或guard let)或提供默认值。 - 避免数组越界:在访问数组前检查索引是否有效。 - 避免强制类型转换:使用`as?`进行安全转换,并处理转换失败的情况。 - 检查是否有未实现的协议方法:确保实现了所有协议要求的方法。 - 检查代码中是否有`fatalError`或`assertionFailure`的调用,这些会触发`SIGTRAP`。 4. 调试技巧: - 在Xcode中,可以设置异常断点(Exception Breakpoint)来捕获异常发生的位置。 - 使用`po`命令在控制台打印变量值,查看是否有nil值被强制解包。 5. 主线程问题: - 由于错误发生在主线程,通常涉及UI操作。检查在主线程中执行的代码,尤其是与UI相关的代码,看是否有上述错误。 示例:如果是因为强制解包nil可选值,我们可以将: let value = someOptional! 改为: if let value = someOptional { // 使用value } else { // 处理nil的情况 } 或者使用guard语句: guard let value = someOptional else { // 处理nil的情况,比如return或设置默认值 return } // 使用value 6. 使用工具: - 使用Xcode的Address Sanitizer和Thread Sanitizer可以帮助检测内存问题和线程问题。 7. 查看控制台日志: - 在崩溃前,控制台可能会输出错误信息,如“Unexpectedly found nil while unwrapping an Optional value”,这可以帮助定位问题。 8. 如果使用了SwiftUI,注意: - 在SwiftUI中,某些状态更新可能会在非主线程进行,导致UI操作不在主线程。但EXC_BREAKPOINT错误通常不是线程问题,而是代码逻辑错误。不过,确保UI更新在主线程仍然重要。可以使用`DispatchQueue.main.async`来确保代码在主线程执行。 9. 检查第三方库: - 如果使用了第三方库,检查是否在库中触发了错误。同样查看崩溃堆栈。 10. 符号化崩溃日志: - 如果是从设备上获取的崩溃日志,确保已经符号化,以便查看具体的代码位置。 由于问题发生在主线程,我们需要特别关注主线程上执行的代码,尤其是UI更新相关的代码。通过崩溃日志中的堆栈信息,可以定位到具体的代码行,然后检查该行代码是否存在上述问题。 如果用户提供了崩溃日志的线程回溯部分,我们可以进一步分析。但如果没有,以上是通用的解决步骤。 相关问题: 1. 如何设置Xcode的异常断点? 2. 如何避免Swift中的强制解包? 3. Swift中数组越界访问的预防方法? 4. 如何安全地进行类型转换? 5. 如何查看崩溃日志的线程回溯?</think>### Swift 主线程 EXC_BREAKPOINT 错误解决方法 `EXC_BREAKPOINT`(SIGTRAP)是 Swift 运行时特有的崩溃类型,通常由**安全机制触发的陷阱指令**引起,而非代码逻辑错误。当 Swift 检测到未定义行为时会主动中断程序,防止更严重的错误[^2][^3]。以下是常见原因和解决方法: --- #### 一、常见原因及解决方案 1. **强制解包 nil 可选值(最常见)** ```swift let value: Int? = nil print(value!) // 触发 EXC_BREAKPOINT ``` ✅ **解决**: - 使用 `if let` 或 `guard let` 安全解包 - 提供默认值:`value ?? defaultValue` 2. **数组越界访问** ```swift let arr = [1, 2] print(arr[2]) // 索引越界触发崩溃 ``` ✅ **解决**: - 检查索引范围:`if index < arr.count { ... }` - 使用 `indices` 属性:`if arr.indices.contains(index) { ... }` 3. **强制类型转换失败(as!)** ```swift let obj: Any = "Text" let num = obj as! Int // 转换失败触发崩溃 ``` ✅ **解决**: - 使用安全转换 `as?`: ```swift if let num = obj as? Int { ... } ``` 4. **未实现协议必需方法** ```swift protocol MyProtocol { func requiredMethod() } class MyClass: MyProtocol {} // 未实现方法 ``` ✅ **解决**: - 实现协议所有必需方法 - 添加 `@objc` 标记(如协议继承自 `NSObjectProtocol`) 5. **Swift 运行时陷阱函数** - `fatalError()` - `assertionFailure()` - `preconditionFailure()` ✅ **解决**:检查代码中是否有主动调用的断言函数。 --- #### 二、诊断步骤 1. **查看崩溃日志定位代码** 在 Xcode 崩溃报告中: - 找到 `Triggered by Thread: 0`(主线程) - 查看 `Exception Subtype` 和 `Exception Message` - 检查线程回溯中的**最后一行 Swift 代码**[^3] 2. **启用异常断点** Xcode 中操作: ```plaintext [Debug] → [Breakpoints] → [Create Exception Breakpoint] → 类型选择 `Swift Exceptions` ``` 3. **检查控制台输出** Swift 崩溃前通常会打印明确错误,例如: ```plaintext Fatal error: Unexpectedly found nil while unwrapping an Optional value ``` --- #### 三、高级调试技巧 1. **开启 Address Sanitizer** ```plaintext [Product] → [Scheme] → [Edit Scheme] → [Diagnostics] → 勾选 [Address Sanitizer] ``` (检测内存访问错误) 2. **检查线程堆栈** 在崩溃时输入 LLDB 命令: ```plaintext (lldb) thread backtrace // 查看完整调用栈 (lldb) frame select N // 跳转到具体帧(N 为帧编号) ``` 3. **符号化崩溃报告** 如果从设备获取崩溃日志: - 将 `.crash` 文件拖入 Xcode 的 `Devices` 窗口 - 确保工程已编译过相同版本符号文件 --- #### 四、预防措施 ```swift // 1. 使用安全API arr.first // 替代 arr[0] dict["key", default: 0] // 替代 dict["key"]! // 2. 添加防御性检查 precondition(Thread.isMainThread, "必须在主线程执行") // 3. 启用严格编译选项 // Build Settings → 开启以下标记: // - "Swift Compiler - Custom Flags" → `-Xfrontend -enable-ossa-modules` // - "Other Swift Flags" → `-Xfrontend -strict-concurrency=complete` ``` > 提示:`EXC_BREAKPOINT` 本质是 Swift 的**安全防护机制**,修复后能显著提升代码健壮性。若问题持续,检查是否在非主线程操作 UI(虽然通常报 `EXC_BAD_ACCESS`)[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值