Xcode高级调试技巧(1)

来源:互联网 发布:微信开发 php java 编辑:程序博客网 时间:2024/05/01 12:00

在苹果的官方文档中列出了我们在调试中能用到的一些命令,我们在这重点讲一些常用的命令

一、基本操作

1.1.视图层次

打印视图层次 po [self.contentView recursiveDescription]

1.2.改变某个取值

int a = 1;//Console expr a=2NSLog(@"实际值: %d", a);

1.3.call 改变view的背景色

call [self.view setBackgroundColor:[UIColor redColor]]

1.4.声明变量

expr int $b=2 或者 e int $b=2//输出  po $b

1.5.打印堆栈

ios中打印堆栈方法是 NSThread callStackSymbols,这里调试的时候有个简单的方法如下

bt  或者  bt all

1.6.更改方法返回值

thread return NO/YES

-(BOOL) returnYES{    //thread return NO ,可以更改函数返回值    return YES;}//方法返回结果为NO  

1.7.多线程异常后查看历史对象的malloc分配历史

先打开Enable Zombie Objects 和 Malloc Stack

(lldb) command script import lldb.macosx.heap(lldb) malloc_info -S 0x7ff7206c3a70 

1.8.寄存器查找对象

曾经遇到过一个问题,[self.tableview reloadData]直接奔溃。这时候tableview其实没有问题,我们要怎么去找问题呢?

找到文件
如上图所示,最后我们通过malloc_info -S 0x00007fe99a629560来查看对象分配在堆里面的具体的地址,随后在左侧打开table的所有变量,输入这个地址即能够看到这个是什么成员。

常见问题-打印无效

上面我们简单的学习了如何使用LLDB命令。但有时我们在使用这些LLDB命令的时候,依然可能会遇到一些问题。不明类型或者类型不匹配

p (void)NSLog(@"%@",[self.view  viewWithTag:1001])    //记住要加void p (CGRect)[self.view frame]  //记住不能写成 self.view.frame,lldb的bug

二、调试进阶

2.1.监听某个方法的调用

设置断点 
如果是自定义的view,比如QQView,想监听frame变化,直接[QQView setFrame:]即可 系统方法就要如图所示,x86_64系统中,rdi表示第一个参数,具体其他平台可看inspecting-obj-c-parameters-in-gdb,里面有详细的说明

$rdi ➡ arg0 (self)$rsi ➡ arg1 (_cmd)$rdx ➡ arg2$rcx ➡ arg3$r8 ➡ arg4$r9 ➡ arg5

2.2.image寻址,找到崩溃行

此时会调用如下代码会崩溃

NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];NSLog(@"%@",arr[2]);2015-06-30 14:47:59.280 QQLLDB[6656:138560] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'*** First throw call stack:(0   CoreFoundation                      0x000000010b2d8c65 __exceptionPreprocess + 1651   libobjc.A.dylib                     0x000000010af71bb7 objc_exception_throw + 452   CoreFoundation                      0x000000010b1cf17e -[__NSArrayI objectAtIndex:] + 1903   QQLLDB                              0x000000010aa404f6 -[ViewController viewDidLoad] + 10304   UIKit                               0x000000010ba75210 -[UIViewController loadViewIfRequired] + 7385   UIKit                               0x000000010ba7540e -[UIViewController view] + 276   UIKit                               0x000000010b9902c9 -[UIWindow 

此时我们要直接找到崩溃的行数,很简单。先找到非系统bug的崩溃地址,如下图所示,显然是第三行QQLLDB,右边对应的地址是0x000000010aa404f6,然后输入image lookup --address 0x000000010aa404f6,即可看到是60行出了bug

image lookup --address 0x000000010aa404f6  Address: QQLLDB[0x00000001000014f6] (QQLLDB.__TEXT.__text + 1030)  Summary: QQLLDB`-[ViewController viewDidLoad] + 1030 at ViewController.m:60

2.3.crash日志分析,读取符号表

1.要知道如何读取符号表,我们得先伪造一份符号表的数据文件出来,代码如下

NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];NSLog(@"%@",arr[2]);

伪造步骤:
1.编译到真机
2.然后进入xcode将这个打开,找到QQLLDB.app.dSYM这个文件,偷偷拷贝一份到桌面
3.然后在product中clean一下工程
4.在真机上打开一个编译进去的程序,会出现图

步骤如下图所示:

xcode工程"
右键打开
包内容 
View Device Logs 
找到文件
之后在命令行操作

 atos -o /Users/tomxiang/Desktop/符号表/QQLLDB.app.dSYM/Contents/Resources/DWARF/QQLLDB -l0x1000e8000 0x1000eebd4    //此时得到结果,告诉我们是ViewController的viewDidLoad的第62行崩溃-[ViewController viewDidLoad] (in QQLLDB) (ViewController.m:62)

2.4观察实例变量的变化

假设你有一个 UIView,不知道为什么它的 _layer 实例变量被重写了 (糟糕)。因为有可能并不涉及到方法,我们不能使用符号断点。相反的,我们想监视什么时候这个地址被写入。

首先,我们需要找到 _layer 这个变量在对象上的相对位置:

(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar*)class_getInstanceVariable([MyView class], "_layer"))(ptrdiff_t) $0 = 8

现在我们知道 ($myView + 8) 是被写入的内存地址:

(lldb) watchpoint set expression -- (int *)$myView + 8Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = wnew value: 0x0000000000000000

这被以 wivar $myView _layer 加入到 Chisel 中。

三、Chisel-facebook开源插件

1.安装方法:

git clone https://github.com/facebook/chisel.git ~/.chiselecho "command script import ~/.chisel/fblldb.py">>~/.lldbinit

安装好后,打开xcode就可以运行调试了

2.基本命令

2.1.预览图片
UIImage *image = [UIImage imageNamed:@"clear"];UIImageView *imageView = [[UIImageView alloc] initWithImage:image];[viewB addSubview:imageView];//此时执行命令之后,图片会被苹果用自带的预览工具显示出来    visualize image
2.2.边框/内容着色

如下所示,打印得到imageView地址之后,然后用border命令将其边框着色unborder取消着色

(lldb) p imageView(UIImageView *) $2 = 0x00007fb8c9b15910(lldb) border 0x00007fb8c9b15910 -c green -w 2

同理,mask是给内容着色 unmask

mask imageView -c green
2.3.关系链的继承
(lldb) pclass imageUIImage    | NSObject
2.4.打印所有属性
`pinternals`这个命令就是打印出来的一个控件(id)类型的内部结构,详细到令人发指!甚至是你自定义的控件中的类型,譬如这个styleView就是我自定义的,内部有个iconView的属性,其中的值它也会打印出来。   (lldb) pinternals image(UIImage) $8 = {      NSObject = {    isa = UIImage  }_imageRef = 0x00007fc1f3330780_scale = 2_imageFlags = { named = 1 imageOrientation = 0 cached = 0 hasPattern = 0 isCIImage = 0 renderingMode = 0 suppressesAccessibilityHairlineThickening = 0 hasDecompressionInfo = 0 }}
2.5.bmessage

如果Chisel

ViewController没有设置viewWillDisappear这个方法,此时我想用断点断下来,可以这样

(lldb) bmessage -[ChiselViewController viewWillDisappear:]Setting a breakpoint at -[UIViewController viewWillDisappear:] with condition (void*)object_getClass((id)$rdi) == 0x0000000100c47060Breakpoint 2: where = UIKit`-[UIViewController viewWillDisappear:], address = 0x0000000101a0b566

Breakpoints

BreakPoint分类

breakpoint也是有分类的,我这里的文章内大致按使用的方式分为了

  • Normal Breakpoint,
  • Exception Breakpoint,
  • OpenGL ES Error breakpoint,
  • Symbolic Breakpoint,
  • Test Failure Breakpoint,
  • WatchPoints。

 

可以按具体的情景使用不同类型的breakpoint,解决问题为根本。

Normal Breakpoint

添加普通断点就不多说了,在源代码的右侧点击一下即可。或者,使用快捷键:command + \ 来添加和删除。这两种方式添加的breakpoints在Xcode上面是可以通过UI看到的。

还有可以通过下面两个LLDB命令直接在运行时添加断点,但是这种方式需要注意的是一方面无法通过UI直接看到断点,另外一方面只存在于本次运行,下一次启动模拟器重新运行的时候,这些断点就不生效了。

blob.png

如上图,通过“br li”命令打印所有的breakpoint,可以看到一共有3个breakpoint,第一个是通过Xcode的UI添加的,后面两个分别是通过下面两个命令添加的:

“breakpoint set -f XXX.m -l XX” 和  “b XXX.m:XX”。

Exception Breakpoint

可以通过下图中Xcode的UI添加Exception Breakpoint。有时候,比如数组越界或者设置一个空对象等问题,都会抛出一个异常,但是这种类型的错误非常难以定位,这个时候就可以使用Exception Breakpoint来进行调试,在异常发生时可以捕捉到并停止程序的执行。OC中的异常是一个常被忽略的地方,但实际上系统框架内这个使用非常广泛,大部分这种错误信息,系统框架都会以异常的形式throw出来,所以善用这种breakpoint的话,我们能大大减少查找错误的时间。

blob.png

例如,当我们添加如下Exception Breakpoint之后(bt 命令后文中会讲解,这个命令的作用是在断点触发时,打印回调栈信息):

blob.png

类似下面这样的数组越界的问题,我们可以很容易就定位到问题所在,不用再毫无头绪找来找去了:

blob.png

当断点暂停执行时,我们可以通过Xcode的UI中查看调用栈信息:

blob.png

或者查看bt命令打印的调用栈信息:

blob.png

还有类似如下的错误可以通过这种断点很容易定位到:

blob.png

,不过这种问题,可以通过使用setValue:forKey:代替来避免。

OpenGL ES Error Breakpoint

同上图中,在Xcode的breakpoint navigator的下部添加按钮,选择”Add OpenGL ES Error Breakpoint”即可。这个breakpoint主要是用来在OpenGL ES发生错误时停止程序的运行。

Symbolic Breakpoint

通过Xcode的UI添加symbolic breakpoint的方式同exception breakpoint,弹出框如下:

blob.png

Symbolic breakpoints 在某个特定的函数或者方法开始执行的时候,暂停程序的执行,通过这种方式添加断点,我们就不需要知道在源文件中添加,也不需要知道断点设置在文件的第几行。

上图中,最主要的设置是Symbol的内容,可以有如下几种:

  • 1. A method name,方法名称,例如 pathsMatchingExtensions: 这样的方法名称,会对所有类的这个方法都起作用。
  • 2. A method of a particular class. 特定类的某个方法。例如 ,[SKLine drawHandlesInView],或者 people::Person::name()
  • 3. A function name。函数名称。例如 ,_objc_msgForward 这样C函数。

另外,也可以通过命令行的方式添加 Symbolic breakpoints。对C函数添加断点:

blob.png

对OC的方法添加断点:

blob.png

常用的这个类型的断点有,objc_exception_throw可以用来代替 Exception Breakpoint,还有一个-[NSObject doesNotRecognizeSelector:] 也比较常用,用于检测方法调用失败。

Test Failure Breakpoint

通过Xcode的UI添加方法同上。这个类型的break point 会在 test assertion 失败的时候暂停程序的执行。

Watchpoints

Watuchpoints是一个用来监听变量的值的变化或者内存地址的变化的工具,发生变化时会在debugger中触发一个暂停。对于那些不知道如何准确跟踪的状态问题,可以利用这个工具来解决。要设置watchpoint的话,在程序运行到stack frame包含有你想观察的变量时,让debugger暂停运行,这个时候变量在当前stack frame的scope内,这个时候才能对该变量设置watchpoint。

你可以在Xcode的GUI中设置watchpoint,在xcode的 Variables View中,把你想观察的变量保留出来,然后右键设置“Watch XXX”。例如下图,观察self的title变量,点击 Watch “_button1ClickCount” 即可。

blob.png

命令行

或者也可以通过命令行来设置watchpoint:watch set variable _button1ClickCount,详细命令可以参考:http://lldb.llvm.org/lldb-gdb.html,有好几种命令可以达到同样的效果。

上面是对变量进行观察,实际上我们可以对任意内存地址进行观察,命令如下:watchpoint set expression — 0x123456,参考:http://stackoverflow.com/questions/21063995/watch-points-on-memory-address

需要注意的是,watchpoint是分类型的,包括read,write或者read_write类型,这个非常容易理解,在读,写或者读写变量或内存的时候,watchpoint是否被触发。read,write或read_write跟着-w参数后面表示类型。另外,命令行中,watchpoint还有一些简写,set简写为s,watch简写为wa,variable简写为v。

下面的示例是来自 http://www.dreamingwish.com/article/lldb-usage-a.html 网站的几个命令:

blob.png

第一个命令是监听_abc4变量的内存地址write的变化,第二个是监听_abc4变量read的变化,第三个是监听_abc3变量read_write的变化。

需要注意的是,通过Xcode的GUI添加的watchpoint为默认类型,即write类型,如果想要添加读写都watch的watchpoint,则只能通过命令行工具进行添加了。

使用watchpoint modify -c ‘(XXX==XX)’,则修改watchpoint之后在某个值的时候才会监听。

编辑选项

BreakPoint Condition

当我们通过Xcode对breakpoint进行编辑时,可以发现normal breakpoint和symbolic breakpoint都有一个”Condition”输入选项,这个的作用很容易理解,只有在设置的condition表达式为YES的情况下这些断点才会起作用。

例如,下图中的breakpoint在判断字符串相等的时候才会停止运行:

blob.png

可以注意到这里使用stringWithUTF8Stirng:方法,原因在于lldb的expression parser有一个bug,不兼容非ASCII字符,需要处理一下才行,否则会报错“An Objective-C constant string's string initializer is not an array”,参考:http://stackoverflow.com/questions/17192505/error-in-breakpoint-condition

更加简单一些的例子就不说了,比如 i == 99之类的简单比较,只要表达式的结果为BOOL类型即可。

Breakpoint Actions

可以看到上面的每种breakpoint编辑选项中基本上都有“Add Action”选项,当breakpoint被触发时,都首先会执行我们设置的这些action,然后我们才能得到控制权,即Xcode上面才会显示程序停止执行的UI。这个Action通过例子比较好理解,我们通过上面那个setObject:forKey:的异常来说明。代码如下:

blob.png

设置Breakpoint:

blob.png

可以看到上图中,我们一共设置了3个action。第一个action,用来打印exception的详细信息,用法参考:http://stackoverflow.com/questions/17238673/xcode-exception-breakpoint-doesnt-print-details-of-the-exception-being-thrown。

第二个action,我们使用shell命令“say”,让电脑发声,把一段文字读出来。

第三个action,我们使用“bt”命令来打印调用栈信息

设置完成之后,当异常发生时,我们会听到电脑发声念上图中的英文,然后在log中可以看到如下信息,第一行是Exception的描述信息,下面是调用堆栈:

blob.png

Continuing after Evaluation

看一下breakpoint的编辑弹窗,我们可以发现有一个 “Automatically continue after evaluation actions” checkbox选项。当我们勾选这个checkbox之后,debugger会执行breakpoint中添加的所有的actions,然后继续执行程序。对于我们来说,除了触发一大堆command并且执行时间很长的情况之外,程序会很快跳过这个breakpoint,所以我们可能根本不会注意到这个breakpoint的存在。所以,这个选项的功能相当于在执行的最后一个action之后,直接输入continue命令继续执行。

有了这个很强大的功能,我们可以直接通过breakpoints来单独对我们的程序进行修改。在某行代码时停止执行,使用”expression”命令来直接修改程序的某个变量设置直接修改UI,然后继续执行。expression / call 配合这个选项的时候,会非常强大,可以很方便实现很多很强大的功能。

例如,我们实现一个如下的功能,把tableview的第一个cell的selectBackgroundView的背景色改为红色:

blob.png

action的内容为“expression [[cell selectedBackgroundView] setBackgroundColor:[UIColor redColor]]”,这里的表达式先不用关心,我们后面LLDB章节会讲到,修改之后,当我们点击cell的时候,cell的背景就会如下图一样变红:

blob.png

使用这种方式,我们在不需要修改一行代码的情况下,只需要通过修改breakpoint,就可以实现对UI的各种调试效果。

 

本博文由博主(iCocos)独立编写或者借鉴别人的好文章进行修改而成,如果不对的地方望指正,谢谢! 如果您还有看到新浪博客关于IOS梦工厂的博文,麻烦请到这里找更完整更清晰的版本,博主已从http://blog.sina.com.cn/s/articlelist_3288975567_0_1.html转移到博客园

参考链接:

1.当异常出现时
2.日志记录CocoaLumberjack
3.在Xcode中调试程序
4.南峰子的技术博客
5.与调试器共舞 - LLDB 的华尔兹
6.官方调试技巧文档
7.inspecting-obj-c-parameters-in-gdb

1 0
原创粉丝点击