关闭

iOS调试技巧

740人阅读 评论(0) 收藏 举报
分类:

转载自:http://www.hotobear.com/

iOS调试技巧(1)——断点

iOS调试技巧(2)——堆栈
iOS调试技巧(3)—— Attach to Process with Xcode

iOS调试技巧(4)—— LLDB指令


一:断点

断点是最基本的调试技巧之一。很长一段时间内,我都觉得断点毫无技巧可言,随着接触愈深,才发现自己真是“too native sometimes native”!

如何在Xcode里面加断点,以及Step over、Step into等等就不啰嗦了,直接说说其他用法。

自定义断点

Xcode中,断点右键,选择“Edit Breakpoint…”,可以看见如下的视图:

breakpioint-1

  1. Condition:条件断点,用来对付遍历或者循环相当有效
  2. Ignore:忽略前多少次的断点
  3. Action:断点时执行某种操作。右面的“+”“-”可以添加删除其他操作。点开下拉框,一共有六种操作可以选择
    • Debugger Command:Debugger指令,提示可以填入“po foo”之类的指令。Debugger指令高深莫测,具体等以后总结GDB和LLDB时再详细讲讲
    • Log Message:日志输出,一些特殊的日志规则也都有提示。(日志输出可以选择语音哦,识别率很高声音很性感哦)
    • Shell Command:Shell指令,没有具体尝试过,估计对于自动化测试很有效果
    • Sound:声音,和语音日志输出一样,就是用来调戏用的
    • 其他两种,目前没有应用场景,先占位
  4. Options:勾选可以选择执行后自动跳过。话说自动跳过的断点有什么用,其实是用来配合上一项“Action”的,这两项配合得好的话,必杀技无疑了

更多类型的断点

断点的导航视图(快捷键Command+6),详细标明了程序里的断点信息。除了第一种之外,还有两种:异常断点和符号断点,如下图。具体可在左下角的“+”添加。

breakpioint-2

  • 异常断点(Exception Breakpoint)

假如我们执行下面的代码:

NSArray *array = [[NSArray alloc] init];

NSObject *i = [array objectAtIndex:0];

NSLog(@"%@", i);

[array release];

会直接崩溃到main函数数并报异常。虽说可以知道是什么异常,但没法立刻知道具体是哪里抛出了异常,这时,添加一个异常断点就十分有效了。 

添加一个异常断点。“Break”选择“On Throw”,再次运行代码,就可以在第二句代码处抛出异常时断点。

  • 符号断点(Symbolic Breakpoint)

假设此时需要断点在非自己的代码的函数里面,这一项就发挥作用了。

breakpioint-3

  1. Symbol:符号,假如断不到点,可以先确定下是否格式写错了
  2. Module:模块,限制范围用的
  3. 其他选项的和之前的相同

GDB和LLDB

学好GDB和LLDB很重要,在控制台调试程序,给人的感觉就如同大家都在用IDE写代码的时候,你在用记事本敲代码。

GDB和LLDB有很多断点类的指令,具体指令可以看这里,就不解释含义了。

可以说下我的某次应用场景:某种操作下,UIWebView的弹出键盘被莫名其妙地缩回去了。代码里面所有的显示调用resignFirstResponder都查遍了,没有发现可疑的地方,可以断定,是UIWebView内部的操作导致键盘缩回了。
breakpioint-4
如图,断上所有有关resignFirstResponder的调用函数,这时就可以根据断点查出,到底那些代码,引起了网页键盘缩回。



二:堆栈


断点生效之后,按Command+5,可以快速跳到堆栈的导航视图,如下图:

stack-1

  1. Thread 1是主线程,也就是常说的UI线程,能够看到的是一个runloop开始到断点之间的堆栈调用的函数符号,按照调用顺序排列,最底部是从main函数向UIApplication发出的runloop开始的调用,一般来说基本没什么参考意义
  2. 不同的库和framework的堆栈用不同的颜色来表示,蓝色的人头表示的是非系统库的堆栈,紫色的杯子就是UIKit的东西啦
  3. 看得见函数名称的,说明拥有符号表,没有呢就靠猜吧,当然控制台lldb也是可以打印的

主线程被卡住是非常常见的场景,具体表现就是程序不响应任何的UI交互。这时按下调试的暂停按钮,查看堆栈,就可以看到是到底是死锁、死循环还是死等,导致UI线程被卡住。

下面这张图,做过浏览器都不陌生。主要是Thread 4 WebThread抛出的和UI线程进行同步的堆栈现场。Thread 4 WebThread堆栈顶部有着很明显的wait痕迹。这就是所谓的iOS上面webkit的多线程模型——wait until done。

QQ20131026-1@2x


三:Attach to Process with Xcode


断点调试的前提,是gdb或者lldb能够attach上被调试的程序。

Xcode自带lldb和gdb(Xcode 5.0去除了gdb),特别是lldb,对于Objective-C的调试很是方便;同时作为官方IDE,Xcode的图形化界面对于断点和堆栈也很是直观。所以若条件许可,利用Xcode的lldb来调试程序最为方便。

get-task-allow

Code signing Entitlements中有一项很重要的key值,就是get-task-allow,作用如下:

get-task-allow, when signed into an application, allows other processes (like the debugger) to attach to your app. Distribution profiles require that this value be turned off, while development profiles require this value to be turned on (otherwise Xcode would never be able to launch and attach to your app).

也就是说,get-task-allow决定了这个app能否被Xcode调试。一般来说,上架了的程序,这个key值为空,默认为NO。

模拟器

理所当然,既然能build进模拟器,那就说明这个app能够被调试。那么就大胆地启动app,在Xcode菜单里面选择

Debug -> Attach to Process -> [process name]。

选项中会列出Mac OS上所有的进程,有点多,按顺序找找有了。

而对于模拟器上的系统程序(如Safari),默认也都是可调试的,如下图:

Attach Safari sim

真机

(以下内容涉及app crack)

真机上的app,若不是自己build进去的,基本没有调试的权限。原因在于get-task-allow没有开启。若要开启,步骤如下:

以MobileSafari为例。

  1. 在越狱iOS设备上,利用iOS系统文件管理工具(itools,ifunbox),在Applications/MobileSafari.app目录下,找到MobileSafari这个执行文件,拷贝到Mac上。利用ldid将MobileSafari的code sign导出:
    ldid -e MobileSafari >> MobileSafari.xml
  2. 打开MobileSafari.xml细心看看,好多的私有key。不过不必天真,Code signing Entitlements若出现了私有key,是不允许上架的。在MobileSafari.xml添加get-task-allow这个key,并赋值为true,保存退出。
  3. 对MobileSafari进行重签名:
    ldid -SMobileSafari.xml ./MobileSafari 
  4. 将MobileSafari重新拷贝回iOS设备,声明下权限:
    chmod 755 ./MobileSafari 
  5. 启动Safari,Debug -> Attach to Process -> [process name]。
  6. 成功Attach上去之后,点击暂停按钮,在控制台执行指令
    po [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]

打印出App的名称,可以看到Safari,如下图:

Attach Safari (1)

测试环境和工具

  • Xcode 5.0
  • 越狱的iPhone,iOS6.1
  • ifunbox

参考

xcode中的get-task-allow有什么用?


四:LLDB指令



基本操作

 指令  作用
 c  continue,进程继续运行
 r  run,进程重新运行
 kill  run,kill进程
 s  step-in
 n  step-over
 finish  step-out

 

 断点

 指令  作用
 b  打印所有断点
 b -[NSString stringWithFormat:]  Objective-C函数断点
 b objc_msgSend  C/C++函数断点
 b resignFirstResponder  对所有含有resignFirstResponder的函数进行断点
 br del 1  删除某个断点(标号可以通过「打印所有断点」取得)

 

 堆栈

 指令  作用
 bt  打印当前线程堆栈
 bt all  打印所有线程堆栈
 up  跳到上一个调用栈
 down  跳到下一个调用栈

 

 p 和 po

p指令可以打印结构体以及基本类型,有时会出现异常,尝试类型强转,如 p (int)a 。

除了p指令外,LLDB增加了po,如 po self ,用来打印一个OC对象的description。

又如下面这句:

po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]

– [UIView recursiveDescription] 可以用来打印一个UIView的层次结构。在Reveal还没有出来之前,这个方法是用来快速查看UIView的层次结构。

事实上,p 和 po 指令是用来执行代码,并输出返回值结果。用来打印输出只是返回结果罢了。所以,在断点处输入

po [self.view setBackgroundColor:[UIColor whiteColor]]

是可以实时生效的。

 

image

image指令可以操作符号相关的东西。

NSArray *array = [[NSArray alloc] init];
NSObject *i = [array objectAtIndex:0];

比如说上面的代码,运行时崩溃,控制台会输入如下:

QQ20141228-2@2x

通过控制台输出,可以猜测崩溃在我们的程序「test」中的某处(上图标红处),此时通过指令

image lookup −a 0x00000001036d58e7

可以看出,程序崩溃在ViewController.m的第38行。QQ20141228-4@2x

另外,还有一个非常好用的指令,可以用来打印和某关键字相关的符号,如下:

image lookup −r −n UIWebView

 

参考

The LLDB Debugger





0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:29120次
    • 积分:791
    • 等级:
    • 排名:千里之外
    • 原创:50篇
    • 转载:19篇
    • 译文:0篇
    • 评论:0条