xcode调试之断点调试及动态输出

   在开发App的过程中,需要反复的修改和优化我们的代码,特别是当程序出现bug,需要快速的找出错误的原因,进行修改,以保证程序的正确执行。
断点调试,是最常用最简单的一种调试方式,可以一步步跟踪程序执行的流程,得到变量的值,快速的找到错误的原因。

1、断点的基本操作

  一,断点的设置 

 断点的操作非常简单,下面通过几张图来介绍一下如何添加、删除、编辑以及使用断点。设置断点也可以通过快捷键(command+"\")在需要设置断点的代码处




二,断点的自定义

(1)在你设置断点的地方,右击该断点,会弹出一个栏,选择Edit Breakpoint,可以对断点进行自定义设置

(2)点击Edit Breakpoint选项后,弹出如下图


蓝色对勾:蓝色对勾后面表示,当前断点所处的位置将对勾抹去,表示该断点失效。(代码行数显示你可以在菜单栏Xcode->Preferences->TestEditing->勾选Line Numbers,将代码行数字显示出来。)

Condition:指的是条件表达式,该项允许我们对断点生效设置条件,表示当满足某一特定条件的前提下,该断点才生效。(该条件的录入,不能够识别预处理的宏定义,也不能识别断点作用域之外的变量和方法)。
Ignore忽略次数。它指定了在断点生效,应用暂停之前,代码忽略断点的次数。你如果希望应用运行一段时间后断点才生效,那么就可以使用这个选项。比如说在调试某一循环体的时候。
Action:动作。它表示当断点生效时,Xcode作出反应后的行为动作。点击右边的Add Action选项会弹出如图下菜单。



图中所示红色方框中的选项,可以让你指定那一种动作。默认的是Debugger Command。还有以下几种动作供选择,下面逐一介绍。

1.AppleScript
它是苹果提供的一种脚本语言,用来执行一些预先指定的行为。选中该选项,将会出现如下图所示的AppleScript语言的输入框。

我在输入框中输入了相关的打印信息,它的意思是弹出一个显示“Hello World!”的对话框。点击Compile按钮后,如果没有错误,会显示成功信息。而点击Test按钮,会测试运行效果,如下图



至于红色方框中的内容是三种特殊符号相对应的定义。

符号标记 定义
@expression@ LLDB表达式
%B 断点的名称
%H 遇到该断点的次数
2.Capture GPU Frame
这个功能用于当断点生效时,捕获GPU当前所绘制的帧。该功能是辅助图形调试的。
3.Debugger Command
默认的选项,可以让断点执行LLDB调试命令。
4.Log Message
使用Log命令可以生成消息队列,将相关的消息输出到控制台上,还有一个Speak Message选项,可以播报消息。
5.Shell Command
该动作接收一个命令文件和参数列表。如下图

命令文件必须是一个可执行的二进制程序或者脚本。可以复制粘贴输入路径,也可以点击Choose按钮选择具体文件。
参数通过空格表示分割,也可以在两个@字符之间包含LLDB表达式。
一般情况下,Xcode会异步执行Shell Command,也就是说,Shell Command 和调试器将会同步执行。如果希望调试器在Shell Command命令完成后运行,则可以勾选下面的Wait until done选项。
6.Sound
动作会在断点被触发时,弹出声音提示。

2、全局断点

  设置全局断点(异常断点),当遇到错误,Debug程序会自动定位到栈底信息,即跳到出错代码所在行。

 

Exception:选项可以让你选择响应Objective-C对象抛出的异常,也可以选择响应C++对象抛出的异常。
Break:则是选择断点所接收的异常,是接收“Throw”语句抛出的异常还是Catch语句的。

3、条件断点

  设置条件断点,当满足条件的时候,才触发断点,适合用于循环结构中,可以准确的定位到某次循环。



4、符号断点

  符号断点可以中断指定函数的调用,也可以定位到出现异常的代码处,并打印异常信息。

一,设置方法

 

二,通过 ObjC_exception_throw 定位到异常抛出的位置


三,unrecognized selector send to instancd 快速定位

在Symbolic中填写如下方法签名  -[NSObject(NSObject)  doesNotRecognizeSelector:]   


四,添加指定的方法为断点,比如添加一个 viewDidLoad Symbol


如果你要添加某个特定类的实例方法,可以用 -[类名 实例方法名]。类方法是+[类名   方法名] 


5.OpenGL ES错误断点(OpenGL ES Error BreakPoint)

  这个断点的作用和异常断点类似,只不过这个断点只有在openGL ES错误发生的时候才会触发。

6、NSLog输出

  在调试的过程中,经常通过NSLog在控制台输出需要的信息。NSLog输出比较消耗系统资源,输出的数据也可能会暴露出App里的保密信息,所在在发布正式版本之前必须把所有的NSLog输出都屏蔽掉。

  NSLog除了输出基本信息,对于结构体也可以一次性输出。



7、动态输出(lldb)

  在程序的调试过程中,除了通过断点调试在Xcode下方查看变量的值,也可以通过NSLog在控制台输出想要的信息。使用NSLog输出需要在运行前就把想要输出的信息写好,如果有改变,需要重新编译运行,效率非常低。

  LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。

        GDB to LLDB 参考是一个非常好的调试器可用命令的总览 当然你也可以通过在输入台,键入Help命令,查看相关的调试命令

     Help

  它会列举出所有的命令。如果你忘记了一个命令是做什么的,或者想知道更多的话,你可以通过 help <command> 来了解更多细节,例如 help print 或者 help thread。如果你甚至忘记了help 命令是做什么的,你可以试试 help help。

例如:

lldb  help


lldb  help print


 print 

   就是打印值    LLDB 实际上会作前缀匹配。所以你也可以使用 prin,pri,或者 p。但你不能使用 pr,因为 LLDB 不能消除和 process 的歧义 
   你可能还注意到了,结果中有个 $0。实际上你可以使用它来指向这个结果。试试 print $0 + 7,你会看到 106。任何以美元符开头的东西都是存在于 LLDB 的命名    空间的,它们是为了帮助你进行调试而存在的。

   

1>print ,po 与expression的关系
考虑一个有意思的表达式:p  count = 18。如果我们运行这条命令,然后打印 count 的内容。我们将看到它的结果与expression   count = 18 一样。
expression 不同的是,print 命令不需要参数。比如 e   -h +17 中,你很难区分到底是以-h 为标识,仅仅执行+17 呢,还是要计算17h 的差值。连字符号确实很让人困惑,你或许得不到自己想要的结果。
幸运的是,解决方案很简单。用 -- 来表征标识的结束,以及输入的开始。如果想要-h 作为标识,就用e -h -- +17,如果想计算它们的差值,就使用e -- -h +17。因为一般来说不使用标识的情况比较多,所以 e -- 就有了一个简写的方式,那就是print
输入 help print,然后向下滚动,你会发现:
'print' is an abbreviation for 'expression --'.   
(print是 `expression --` 的缩写)

打印对象
尝试输入
p objects
输出会有点啰嗦
(NSString *) $7 = 0x0000000104da4040 @"red balloons"
如果我们尝试打印结构更复杂的对象,结果甚至会更糟
(lldb) p   @[ @"foo", @"bar" ]
(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 objects" 

实际上,我们想看的是对象的 description 方法的结果。我么需要使用 -O (字母 O,而不是数字 0) 标志告诉 expression 命令以 对象 (Object) 的方式来打印结果。
(lldb) e -O -- $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)

幸运的是,e -o -- 有也有个别名,那就是po (print object 的缩写),我们可以使用它来进行简化:

(lldb) po $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)
(lldb) po @"lunar"
lunar
(lldb) p @"lunar"
(NSString *) $13 = 0x00007fdb9d0003b0 @"lunar"
2>打印变量
可以给 print 指定不同的打印格式。它们都是以 print/<fmt> 或者简化的 p/<fmt> 格式书写。下面是一些例子:
默认的格式
(lldb) p 16
16
十六进制:
(lldb) p/x 16
0x10
二进制 (t 代表 two):
(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000
你也可以使用 p/c 打印字符,或者 p/s 打印以空终止的字符串 (译者注:以 '\0' 结尾的字符串)。

打印对象(po)

     (lldb) po anObj 
     (lldb) po 0x0715aa40 

  po用于打印输出对象信息。使用动态指令需要与断点配合使用,这样就能够动态的打印输出程序执行到某个断点时的信息。



打印表达式(expr)

expo:可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。LLDB 实际上会作前缀匹配。所以你也可以使用 e

     (lldb) expr 5+2 
     (lldb) e aString = @"aNewValue" 

打印调用堆栈(bt)

打印调用堆栈,加all可打印所有thread的堆栈。

查询地址(image)

image 命令可用于寻址,有多个组合命令。比较实用的用法是用于寻找栈地址对应的代码位置。 下面我写了一段代码

NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
NSLog(@"%@",arr[2]);
这段代码有明显的错误,程序运行这段代码后会抛出下面的异常:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
*** First throw call stack:(
  0   CoreFoundation                      0x0000000101951495 __exceptionPreprocess + 165
  1   libobjc.A.dylib                     0x00000001016b099e objc_exception_throw + 43
  2   CoreFoundation                      0x0000000101909e3f -[__NSArrayI objectAtIndex:] + 175
  3   ControlStyleDemo                    0x0000000100004af8 -[RootViewController viewDidLoad] + 312
  4   UIKit                               0x000000010035359e -[UIViewController loadViewIfRequired] + 562
  5   UIKit                               0x0000000100353777 -[UIViewController view] + 29
  6   UIKit                               0x000000010029396b -[UIWindow addRootViewControllerViewIfPossible] + 58
  7   UIKit                               0x0000000100293c70 -[UIWindow _setHidden:forced:] + 282
  8   UIKit                               0x000000010029cffa -[UIWindow makeKeyAndVisible] + 51
  9   ControlStyleDemo                    0x00000001000045e0 -[AppDelegate application:didFinishLaunchingWithOptions:] + 672
  10  UIKit                               0x00000001002583d9 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 264
  11  UIKit                               0x0000000100258be1 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1605
  12  UIKit                               0x000000010025ca0c -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 660
  13  UIKit                               0x000000010026dd4c -[UIApplication handleEvent:withNewEvent:] + 3189
  14  UIKit                               0x000000010026e216 -[UIApplication sendEvent:] + 79
  15  UIKit                               0x000000010025e086 _UIApplicationHandleEvent + 578
  16  GraphicsServices                    0x0000000103aca71a _PurpleEventCallback + 762
  17  GraphicsServices                    0x0000000103aca1e1 PurpleEventCallback + 35
  18  CoreFoundation                      0x00000001018d3679 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
  19  CoreFoundation                      0x00000001018d344e __CFRunLoopDoSource1 + 478
  20  CoreFoundation                      0x00000001018fc903 __CFRunLoopRun + 1939
  21  CoreFoundation                      0x00000001018fbd83 CFRunLoopRunSpecific + 467
  22  UIKit                               0x000000010025c2e1 -[UIApplication _run] + 609
  23  UIKit                               0x000000010025de33 UIApplicationMain + 1010
  24  ControlStyleDemo                    0x0000000100006b73 main + 115
  25  libdyld.dylib                       0x0000000101fe95fd start + 1
  26  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
现在,我们怀疑出错的地址是0x0000000100004af8(可以根据执行文件名判断,或者最小的栈地址)。为了进一步精确定位,我们可以输入以下的命令:
image  lookup  -- address  0x0000000100004af8
命令执行后返回:
Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53
我们可以看到,出错的位置是RootViewController.m的第53行。

详情请考参考这里

8、僵尸模式

    1. 什么是 EXC_BAD_ACCESS?

    简单的说就是,你向一个已经释放的对象发送消息。在c和Objective-c中,我们对内存的操作都是通过指针完成的,不是直接对内存进行操作指针中存储的是内存地址,通过内存地址,我们就能对该存储区域进行操作。但是,当该存储器区域不再映射到您的应用时,或者换句话说,该内存区域在你认为使用的时候却没有使用,该内存区域是无法访问的。 这时内核会抛出一个异常( EXC ),表明你的应用程序不能访问该存储器区域(BAD ACCESS)。一般情况下,EXC_BAD_ACCESS是由被损坏的指针引起的。比如野指针,内存泄漏等等

    2.为什么要了解EXC_BAD_ACCESS

Xcode可以把那些已经release掉得对象,变成“僵尸”,当我们访问一个Zombie对象时,Xcode可以告诉我们正在访问的对象是一个不应该存在的对象了,你的应用程序将会由于EXC_BAD_ACCESS而崩溃。因为Xcode知道这个对象是什么,所以可以让我们知道这个对象在哪里,以及这是什么时候发生的。而Xcode这种能力就是通过僵尸模式来体现的

    3.僵尸调试模式

单击左上角的Edit Scheme,并选中Edit Scheme。


在左侧选中Run ,在上方打开 Diagnostics选项。要启用僵尸对象,勾选 Enable Zombie Objects选框。


如果你现在遇到EXC_BAD_ACCESS ,在Xcode的控制台输出,告诉你该从哪里查找问题。看看下面的例子输出。 
2016-07-28 06:31:55.501 Debug[2371:1379247] -[ChildViewController respondsToSelector:] message sent to deallocated instance 0x17579780
这条消息对于定位问题有很好的提示作用。但是很多时候,只有这条提示是不够的,我们需要更多的提示来帮助定位问题,这时候再加入 MallocStackLogging 来启用malloc记录。




  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值