乐温州网站建设,wordpress js代码插件下载,网站维护费一年多少钱,中国软件是外包吗原文网址#xff1a;http://www.cnblogs.com/Twisted-Fate/p/4760156.html 今天博主有一些Bug调试的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步. Xcode的Bug调试方法大概有以下几种: 1.断点,全局断点,条件断点配合Nslog找出Bug 2.静态分析工具:Analyze,静态检测内… 原文网址http://www.cnblogs.com/Twisted-Fate/p/4760156.html 今天博主有一些Bug调试的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步. Xcode的Bug调试方法大概有以下几种: 1.断点,全局断点,条件断点配合Nslog找出Bug 2.静态分析工具:Analyze,静态检测内存 3.动态分析工具:Profile,其中最常用的是Leaks(检测内存泄露)和Allocations(检测内存溢出) 4.gdb调试:Xcode4.0之前的调试工具 5.lldb调试:Xcode4.0之后的调试工具 今天就和大家分享一下如何通过lldb,成为Bug解决达人 LLDB LLDB 是一个有着 REPL 的特性和 C ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它你可以查看变量的值执行自定的指令并且按照你所认为合适的步骤来操作程序的进展。 与此同时让我们以在调试器中打印变量来开始我们的旅程吧。 基础 这里有一个简单的小程序它会打印一个字符串。注意断点已经被加在第 8 行。断点可以通过点击 Xcode 的源码窗口的侧边槽进行创建。 程序会在这一行停止运行并且控制台会被打开允许我们和调试器交互。那我们应该打些什么呢 help 最简单命令是 help它会列举出所有的命令。如果你忘记了一个命令是做什么的或者想知道更多的话你可以通过 help command 来了解更多细节例如 help print 或者 help thread。如果你甚至忘记了 help 命令是做什么的你可以试试 help help。不过你如果知道这么做那就说明你大概还没有忘光这个命令。? print 打印值很简单只要试试 print 命令: LLDB 实际上会作前缀匹配。所以你也可以使用 prinpri或者 p。但你不能使用 pr因为 LLDB 不能消除和 process 的歧义 (幸运的是 p 并没有歧义)。 你可能还注意到了结果中有个 $0。实际上你可以使用它来指向这个结果。试试 print $0 7你会看到 106。任何以美元符开头的东西都是存在于 LLDB 的命名空间的它们是为了帮助你进行调试而存在的。 expression 如果想改变一个值怎么办你或许会猜 modify。其实这时候我们要用到的是 expression 这个方便的命令。 这不仅会改变调试器中的值实际上它改变了程序中的值。这时候继续执行程序将会打印 42 red balloons。神奇吧。 注意从现在开始我们将会偷懒分别以 p 和 e 来代替 print 和 expression。 什么是 print 命令 考虑一个有意思的表达式p count 18。如果我们运行这条命令然后打印 count 的内容。我们将看到它的结果与 expression count 18 一样。 和 expression 不同的是print 命令不需要参数。比如 e -h 17 中你很难区分到底是以 -h 为标识仅仅执行 17 呢还是要计算 17 和 h 的差值。连字符号确实很让人困惑你或许得不到自己想要的结果。 幸运的是解决方案很简单。用 -- 来表征标识的结束以及输入的开始。如果想要 -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 打印变量 可以给 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 结尾的字符串)。 这里是格式的完整清单。 变量 现在你已经可以打印对象和简单类型并且知道如何使用 expression 命令在调试器中修改它们了。现在让我们使用一些变量来减少输入量。就像你可以在 C 语言中用 int a 0 来声明一个变量一样你也可以在 LLDB 中做同样的事情。不过为了能使用声明的变量变量必须以美元符开头。 (lldb) e int $a 2
(lldb) p $a * 19
38 (lldb) e NSArray *$array [ Saturday, Sunday, Monday ] (lldb) p [$array count] 2 (lldb) po [[$array objectAtIndex:0] uppercaseString] SATURDAY (lldb) p [[$array objectAtIndex:$a] characterAtIndex:0] error: no known method -characterAtIndex:; cast the message send to the methods return type error: 1 errors parsing expression 悲剧了LLDB 无法确定涉及的类型 (译者注返回的类型)。这种事情常常发生给个说明就好了 (lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0] M (lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0] 77 变量使调试器变的容易使用得多想不到吧? 流程控制 当你通过 Xcode 的源码编辑器的侧边槽 (或者通过下面的方法) 插入一个断点程序到达断点时会就会停止运行。 调试条上会出现四个你可以用来控制程序的执行流程的按钮。 从左到右四个按钮分别是continuestep overstep intostep out。 第一个continue 按钮会取消程序的暂停允许程序正常执行 (要么一直执行下去要么到达下一个断点)。在 LLDB 中你可以使用 process continue 命令来达到同样的效果它的别名为 continue或者也可以缩写为 c。 第二个step over 按钮会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用那么就不会跳进这个函数而是会执行这个函数然后继续。LLDB 则可以使用 thread step-overnext或者 n 命令。 如果你确实想跳进一个函数调用来调试或者检查程序的执行情况那就用第三个按钮step in或者在LLDB中使用 thread step instep或者 s 命令。注意当前行不是函数调用时next 和 step 效果是一样的。 大多数人知道 cn 和 s但是其实还有第四个按钮step out。如果你曾经不小心跳进一个函数但实际上你想跳过它常见的反应是重复的运行 n 直到函数返回。其实这种情况step out 按钮是你的救世主。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止。 例子 考虑下面一段程序 假如我们运行程序让它停止在断点然后执行下面一些列命令 p i
n
s
p i
finish
p i
frame info这里frame info 会告诉你当前的行数和源码文件以及其他一些信息查看 help framehelp thread 和 help process来获得更多信息。这一串命令的结果会是什么看答案之前请先想一想。 (lldb) p i
(int) $0 99 (lldb) n 2014-11-22 10:49:26.445 DebuggerDance[60182:4832768] 101 is odd! (lldb) s (lldb) p i (int) $2 110 (lldb) finish 2014-11-22 10:49:35.978 DebuggerDance[60182:4832768] 110 is even! (lldb) p i (int) $4 99 (lldb) frame info frame #0: 0x000000010a53bcd4 DebuggerDancemain 68 at main.m:17 它始终在 17 行的原因是 finish 命令一直运行到 isEven() 函数的 return然后立刻停止。注意即使它还在 17 行其实这行已经被执行过了。 Thread Return 调试时还有一个很棒的函数可以用来控制程序流程thread return 。它有一个可选参数在执行时它会把可 选参数加载进返回寄存器里然后立刻执行返回命令跳出当前栈帧。这意味这函数剩余的部分不会被执行。这会给 ARC 的引用计数造成一些问题或者会使函数内的清理部分失效。但是在函数的开头执行这个命令是个非常好的隔离这个函数伪造返回值的方式 。 让我们稍微修改一下上面代码段并运行 p i
s
thread return NO
n
p even0
frame info看答案前思考一下。下面是答案 (lldb) p i
(int) $0 99 (lldb) s (lldb) thread return NO (lldb) n (lldb) p even0 (BOOL) $2 NO (lldb) frame info frame #0: 0x00000001009a5cc4 DebuggerDancemain 52 at main.m:17 断点 我们都把断点作为一个停止程序运行检查当前状态追踪 bug 的方式。但是如果我们改变和断点交互的方式很多事情都变成可能。 断点允许控制程序什么时候停止然后允许命令的运行。 想象把断点放在函数的开头然后用 thread return 命令重写函数的行为然后继续。想象一下让这个过程自动化听起来不错不是吗 管理断点 Xcode 提供了一系列工具来创建和管理断点。我们会一个个看过来并介绍 LLDB 中等价的命令 (是的你可以在调试器内部添加断点)。 在 Xcode 的左侧面板有一组按钮。其中一个看起来像断点。点击它打开断点导航这是一个可以快速管理所有断点的面板。 在这里你可以看到所有的断点 - 在 LLDB 中通过 breakpoint list (或者 br li) 命令也做同样的事儿。你也可以点击单个断点来开启或关闭 - 在 LLDB 中使用 breakpoint enable breakpointID 和 breakpoint disable breakpointID (lldb) br li
Current breakpoints:
1: file /Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m, line 16, locations 1, resolved 1, hit count 1 1.1: where DebuggerDancemain 27 at main.m:16, address 0x000000010a3f6cab, resolved, hit count 1 (lldb) br dis 1 1 breakpoints disabled. (lldb) br li Current breakpoints: 1: file /Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m, line 16, locations 1 Options: disabled 1.1: where DebuggerDancemain 27 at main.m:16, address 0x000000010a3f6cab, unresolved, hit count 1 (lldb) br del 1 1 breakpoints deleted; 0 breakpoint locations disabled. (lldb) br li No breakpoints currently set. 创建断点 在上面的例子中我们通过在源码页面器的滚槽 16 上点击来创建断点。你可以通过把断点拖拽出滚槽然后释放鼠标来删除断点 (消失时会有一个非常可爱的噗的一下的动画)。你也可以在断点导航页选择断点然后按下删除键删除。 要在调试器中创建断点可以使用 breakpoint set 命令。 (lldb) breakpoint set -f main.m -l 16
Breakpoint 1: where DebuggerDancemain 27 at main.m:16, address 0x000000010a3f6cab 也可以使用缩写形式 br。虽然 b 是一个完全不同的命令 (_regexp-break 的缩写)但恰好也可以实现和上面同样的效果。 (lldb) b main.m:17
Breakpoint 2: where DebuggerDancemain 52 at main.m:17, address 0x000000010a3f6cc4 也可以在一个符号 (C 语言函数) 上创建断点而完全不用指定哪一行 (lldb) b isEven
Breakpoint 3: where DebuggerDanceisEven 16 at main.m:4, address 0x000000010a3f6d00 (lldb) br s -F isEven Breakpoint 4: where DebuggerDanceisEven 16 at main.m:4, address 0x000000010a3f6d00 这些断点会准确的停止在函数的开始。Objective-C 的方法也完全可以 (lldb) breakpoint set -F -[NSArray objectAtIndex:]
Breakpoint 5: where CoreFoundation-[NSArray objectAtIndex:], address 0x000000010ac7a950 (lldb) b -[NSArray objectAtIndex:] Breakpoint 6: where CoreFoundation-[NSArray objectAtIndex:], address 0x000000010ac7a950 (lldb) breakpoint set -F [NSSet setWithObject:] Breakpoint 7: where CoreFoundation[NSSet setWithObject:], address 0x000000010abd3820 (lldb) b [NSSet setWithObject:] Breakpoint 8: where CoreFoundation[NSSet setWithObject:], address 0x000000010abd3820 如果想在 Xcode 的UI上创建符号断点你可以点击断点栏左侧的 按钮。 然后选择第三个选项 这时会出现一个弹出框你可以在里面添加例如 -[NSArray objectAtIndex:] 这样的符号断点。这样每次调用这个函数的时候程序都会停止不管是你调用还是苹果调用。 如果你 Xcode 的 UI 上右击任意断点然后选择 Edit Breakpoint 的话会有一些非常诱人的选择。 这里断点已经被修改为只有当 i 是 99 的时候才会停止。你也可以使用 ignore 选项来告诉断点最初的 n 次调用 (并且条件为真的时候) 的时候不要停止。 接下来介绍 Add Action 按钮... 断点行为 (Action) 上面的例子中你或许想知道每一次到达断点的时候 i 的值。我们可以使用 p i 作为断点行为。这样每次到达断点的时候都会自动运行这个命令。 你也可以添加多个行为可以是调试器命令shell 命令也可以是更直接的打印 可以看到它打印 i然后大声念出那个句子接着打印了自定义的表达式。 下面是在 LLDB 而不是 Xcode 的 UI 中做这些的时候看起来的样子。 (lldb) breakpoint set -F isEven
Breakpoint 1: where DebuggerDanceisEven 16 at main.m:4, address 0x00000001083b5d00
(lldb) breakpoint modify -c i 99 1 (lldb) breakpoint command add 1 Enter your debugger command(s). Type DONE to end. p i DONE (lldb) br li 1 1: name isEven, locations 1, resolved 1, hit count 0 Breakpoint commands: p i Condition: i 99 1.1: where DebuggerDanceisEven 16 at main.m:4, address 0x00000001083b5d00, resolved, hit count 0 接下来说说自动化。 赋值后继续运行 看编辑断点弹出窗口的底部你还会看到一个选项 Automatically continue after evaluation actions. 。它仅仅是一个选择框但是却很强大。选中它调试器会运行你所有的命令然后继续运行。看起来就像没有执行任何断点一样 (除非断点太多运行需要一段时间拖慢了你的程序)。 这个选项框的效果和让最后断点的最后一个行为是 continue 一样。选框只是让这个操作变得更简单。调试器的输出是 (lldb) breakpoint set -F isEven
Breakpoint 1: where DebuggerDanceisEven 16 at main.m:4, address 0x00000001083b5d00
(lldb) breakpoint command add 1 Enter your debugger command(s). Type DONE to end. continue DONE (lldb) br li 1 1: name isEven, locations 1, resolved 1, hit count 0 Breakpoint commands: continue 1.1: where DebuggerDanceisEven 16 at main.m:4, address 0x00000001083b5d00, resolved, hit count 0 执行断点后自动继续运行允许你完全通过断点来修改程序你可以在某一行停止运行一个 expression 命令来改变变量然后继续运行。 例子 想想所谓的打印调试技术吧不要这么做 NSLog(%, whatIsInsideThisThing);而是用个打印变量的断点替换 log 语句然后继续运行。 也不要 int calculateTheTrickyValue {return 9;/*Figure this out later....
}而是加一个使用 thread return 9 命令的断点然后让它继续运行。 符号断点加上 action 真的很强大。你也可以在你朋友的 Xcode 工程上添加一些断点并且加上大声朗读某些东西的 action。看看他们要花多久才能弄明白发生了什么。? 完全在调试器内运行 在开始舞蹈之前还有一件事要看一看。实际上你可以在调试器中执行任何 C/Objective-C/C/Swift 的命令。唯一的缺点就是不能创建新函数... 这意味着不能创建新的类block函数有虚拟函数的 C 类等等。除此之外它都可以做。 我们可以申请分配一些字节 (lldb) e char *$str (char *)malloc(8)
(lldb) e (void)strcpy($str, munkeys)
(lldb) e $str[1] o (char) $0 o (lldb) p $str (char *) $str 0x00007fd04a900040 monkeys 我们可以查看内存 (使用 x 命令)来看看新数组中的四个字节 (lldb) x/4c $str
0x7fd04a900040: monk我们也可以去掉 3 个字节 (x 命令需要斜引号因为它只有一个内存地址的参数而不是表达式使用 help x 来获得更多信息) (lldb) x/1w $str 3
0x7fd04a900043: keys 做完了之后一定不要忘了释放内存这样才不会内存泄露。(哈虽然这是调试器用到的内存) (lldb) e (void)free($str)让我们起舞 现在我们已经知道基本的步调了是时候开始跳舞并玩一些疯狂的事情了。我曾经写过一篇 NSArray 深度探究的博客。这篇博客用了很多 NSLog 语句但实际上我的所有探索都是在调试器中完成的。看看你能不能弄明白怎么做的这会是一个有意思的练习。 不用断点调试 程序运行时Xcode 的调试条上会出现暂停按钮而不是继续按钮 点击按钮会暂停 app (这会运行 process interrupt 命令因为 LLDB 总是在背后运行)。这会让你可以访问调试器但看起来可以做的事情不多因为在当前作用域没有变量也没有特定的代码让你看。 这就是有意思的地方。如果你正在运行 iOS app你可以试试这个 (因为全局变量是可访问的) (lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
UIWindow: 0x7f82b1fa8140; frame (0 0; 320 568); gestureRecognizers NSArray: 0x7f82b1fa92d0; layer UIWindowLayer: 0x7f82b1fa8400 | UIView: 0x7f82b1d01fd0; frame (0 0; 320 568); autoresize WH; layer CALayer: 0x7f82b1e2e0a0 你可以看到整个层次。Chisel 中 pviews 就是这么实现的。 更新UI 有了上面的输出我们可以获取这个 view (lldb) e id $myView (id)0x7f82b1d01fd0然后在调试器中改变它的背景色 (lldb) e (void)[$myView setBackgroundColor:[UIColor blueColor]]但是只有程序继续运行之后才会看到界面的变化。因为改变的内容必须被发送到渲染服务中然后显示才会被更新。 渲染服务实际上是一个另外的进程 (被称作 backboardd)。这就是说即使我们正在调试的内容所在的进程被打断了backboardd也还是继续运行着的。 这意味着你可以运行下面的命令而不用继续运行程序 (lldb) e (void)[CATransaction flush]即使你仍然在调试器中UI 也会在模拟器或者真机上实时更新。Chisel 为此提供了一个别名叫做 caflush这个命令被用来实现其他的快捷命令例如 hide viewshow view 以及其他很多命令。所有 Chisel 的命令都有文档所以安装后随意运行 help show 来看更多信息。 Push 一个 View Controller 想象一个以 UINavigationController 为 root ViewController 的应用。你可以通过下面的命令轻松地获取它 (lldb) e id $nvc [[[UIApplication sharedApplication] keyWindow] rootViewController]然后 push 一个 child view controller: (lldb) e id $vc [UIViewController new]
(lldb) e (void)[[$vc view] setBackgroundColor:[UIColor yellowColor]]
(lldb) e (void)[$vc setTitle:Yay!] (lldb) e (void)[$nvc pushViewContoller:$vc animated:YES] 最后运行下面的命令 (lldb) caflush // e (void)[CATransaction flush]navigation Controller 就会立刻就被 push 到你眼前。 查找按钮的 target 想象你在调试器中有一个 $myButton 的变量可以是创建出来的也可以是从 UI 上抓取出来的或者是你停止在断点时的一个局部变量。你想知道按钮按下的时候谁会接收到按钮发出的 action。非常简单 (lldb) po [$myButton allTargets]
{(MagicEventListener: 0x7fb58bd2e240
)}
(lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0] __NSArrayM 0x7fb58bd2aa40( _handleTap: ) 现在你或许想在它发生的时候加一个断点。在 -[MagicEventListener _handleTap:] 设置一个符号断点就可以了在 Xcode 和 LLDB 中都可以然后你就可以点击按钮并停在你所希望的地方了。 观察实例变量的变化 假设你有一个 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 8
Watchpoint created: Watchpoint 3: addr 0x7fa554231340 size 8 state enabled type w new value: 0x0000000000000000 这被以 wivar $myView _layer 加入到 Chisel 中。 非重写方法的符号断点 假设你想知道 -[MyViewController viewDidAppear:] 什么时候被调用。如果这个方法并没有在MyViewController 中实现而是在其父类中实现的该怎么办呢试着设置一个断点会出现以下结果 (lldb) b -[MyViewController viewDidAppear:]
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.因为 LLDB 会查找一个符号但是实际在这个类上却找不到所以断点也永远不会触发。你需要做的是为断点设置一个条件 [self isKindOfClass:[MyViewController class]]然后把断点放在 UIViewController 上。正常情况下这样设置一个条件可以正常工作。但是这里不会因为我们没有父类的实现。 viewDidAppear: 是苹果实现的方法因此没有它的符号在方法内没有 self 。如果想在符号断点上使用 self你必须知道它在哪里 (它可能在寄存器上也可能在栈上在 x86 上你可以在 $esp4 找到它)。但是这是很痛苦的因为现在你必须至少知道四种体系结构 (x86x86-64armv7armv64)。想象你需要花多少时间去学习命令集以及它们每一个的调用约定然后正确的写一个在你的超类上设置断点并且条件正确的命令。幸运的是这个在 Chisel 被解决了。这被成为 bmessage (lldb) bmessage -[MyViewController viewDidAppear:]
Setting a breakpoint at -[UIViewController viewDidAppear:] with condition (void*)object_getClass((id)$rdi) 0x000000010e2f4d28 Breakpoint 1: where UIKit-[UIViewController viewDidAppear:], address 0x000000010e11533c LLDB 和 Python LLDB 有内建的完整的 Python 支持。在LLDB中输入 script会打开一个 Python REPL。你也可以输入一行 python 语句作为 script 命令 的参数这可以运行 python 语句而不进入REPL (lldb) script import os
(lldb) script os.system(open http://www.objc.io/) 这样就允许你创造各种酷的命令。把下面的语句放到文件 ~/myCommands.py 中 def caflushCommand(debugger, command, result, internal_dict):debugger.HandleCommand(e (void)[CATransaction flush]) 然后再 LLDB 中运行 command script import ~/myCommands.py或者把这行命令放在 /.lldbinit 里这样每次进入 LLDB 时都会自动运行。Chisel 其实就是一个 Python 脚本的集合这些脚本拼接 (命令) 字符串 然后让 LLDB 执行。很简单不是吗 紧握调试器这一武器 LLDB 可以做的事情很多。大多数人习惯于使用 ppons 和 c 但实际上除此之外LLDB 可以做的还有很多。掌握所有的命令 (实际上并不是很多)会让你在揭示代码运行时的运行状态寻找 bug强制执行特定的运行路径时获得更大的能力。你甚至可以构建简单的交互原型 - 比如要是现在以 modal 方式弹出一个 View Controller 会怎么样使用调试器一试便知。 打开 LLDB输入 help看一看列举的命令。你尝试过多少用了多少 但愿 NSLog 看起来不再那么吸引你 转载于:https://www.cnblogs.com/Cheetah-yang/p/4764534.html