lldb 脚本 - 断点写出(breakpoint write) 修复

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lldb 脚本 - 断点写出(breakpoint write) 修复相关的知识,希望对你有一定的参考价值。

参考技术A lldb 有断点序列化的功能,即写出到文件和从文件读取:

然而默认的写出存在 bug(我是在安卓 arm/thumb 环境下发现和处理这些问题的,对于存在多个 location 的断点,可能会出错,不过此情况较少,暂时未去处理)。

lldb 永远不会写入 so 的名称。

断点写出时无法保存正确的断点偏移值,默认在 “AddressOffset” 字段会写入 section 偏移,然而却没有字段保存 section 相对于库的偏移。

可以看到 84125(0x1489D) 并不是相对于 libc.so 的偏移且无其他任何偏移值可计算该断点位置,也未见写入 libc.so 的名称;

插件将 AddressOffset 直接修改为相对于 so 的偏移,且增加了 so 的名称;

breakpoints && lldb  && chisel 的使用

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直接看到断点。另外一方面仅仅存在于本次执行,下一次启动模拟器又一次执行的时候,这些断点就不生效了。
技术分享
如上图,通过“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的话,我们能大大降低查找错误的时间。

技术分享

比如,当我们加入例如以下Exception Breakpoint之后(bt 命令后文中会解说。这个命令的作用是在断点触发时。打印回调栈信息):
技术分享
类似以下这种数组越界的问题。我们能够非常easy就定位到问题所在。不用再毫无头绪找来找去了:
技术分享

当断点暂停运行时,我们能够通过Xcode的UI中查看调用栈信息:
技术分享
或者查看bt命令打印的调用栈信息:
技术分享


还有类似例如以下的错误能够通过这样的断点非常easy定位到:
技术分享,只是这样的问题,能够通过使用setValue:forKey:取代来避免。


OpenGL ES Error Breakpoint

同上图中,在Xcode的breakpoint navigator的下部加入button。选择”Add OpenGL ES Error Breakpoint”就可以。

这个breakpoint主要是用来在OpenGL ES错误发生时停止程序的执行。



Symbolic Breakpoint

通过Xcode的UI加入symbolic breakpoint的方式同exception breakpoint,弹出框例如以下:
技术分享
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函数加入断点:

技术分享
对OC的方法加入断点:
技术分享

经常使用的这个类型的断点有,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” 就可以。
技术分享

命令行

或者也能够通过命令行来设置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类型,这个很easy理解,在读,写或者读写变量或内存的时候,watchpoint是否被触发。

read,write或read_write跟着-w參数后面表示类型。

另外。命令行中。watchpoint另一些简写,set简写为s。watch简写为wa,variable简写为v。

以下的演示样例是来自 http://www.dreamingwish.com/article/lldb-usage-a.html 站点的几个命令:
技术分享
第一个命令是监听_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”输入选项,这个的作用非常easy理解,仅仅有在设置的condition表达式为YES的情况下这些断点才会起作用。

比如,下图中的breakpoint在推断字符串相等的时候才会停止执行:
技术分享
能够注意到这里使用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:的异常来说明。代码例如以下:
技术分享

设置Breakpoint:
技术分享
能够看到上图中。我们一共设置了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的描写叙述信息,以下是调用堆栈:
技术分享


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的背景色改为红色:
技术分享
action的内容为“expression [[cell selectedBackgroundView] setBackgroundColor:[UIColor redColor]]”,这里的表达式先不用关心。我们后面LLDB章节会讲到,改动之后,当我们点击cell的时候,cell的背景就会例如以下图一样变红:
技术分享
使用这样的方式,我们在不须要改动一行代码的情况下,仅仅须要通过改动breakpoint,就能够实现对UI的各种调试效果。


參考:


LLDB


经常使用命令

help

直接输入help命令,列出全部可用的commands。
使用 help <command> 来获得某个command的详细使用方法

expression

简写形式:expr / e。在执行调试时执行表达式,用来直接改动程序的变量的值,或者使用这个命令声明一个变量对象。

比如:改动变量的值:expression count = 20
或者声明一个新变量a:
技术分享

expression命令能够带有參数。但也会带来一些问题。比如: e -h +17 命令,这个命令就easy产生混淆,究竟-h是參数flag,然后+17是输入变量。还是说要计算 -h+17 表达式的终于值。LLDB提供的解决方案很easy,使用“ -- ”来指定命令參数的终止,命令输入的開始。比如,e -h -- +17表示前文中命令的第一个解释,e -- -h+17 表示表示前文中命令的第二个解释。


print

简写形式:prin / pri / p。可是不能用pr表示。由于会和process混淆。实际上。你会发现。lldb对于命令的简称,是头部匹配方式,仅仅要不相互混淆,能够任意简称某个命令。

实际上,假设在console中输入“help print”,就会得到’print’ is an abbreviation for ‘expression --‘ 这句话,也就是说print实际上就相当于 expression -- 。

这里就非常easy理解以下命令是怎样工作的。p _lastPoiID=20。这行命令表达式会被运行。

技术分享

打印一个对象的话,使用 po 命令,使用expression表示的话就是 expression -O -- XXX

使用 print/<fmt> 或者 p/<fmt> 来格式化打印变量,能够參考相应的格式列表:https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html。p/x使用十六进制打印一个变量,p/t 使用二进制打印整数 ,p/c 打印字符,p/s 打印c字符串。

既然我们能够print对象和简单类型,而且在debugger中通过expression命令直接改动他们。那么我们能够通过使用一些变量来减轻我们的工作量。前面讲过我们能够直接使用expression命令来声明变量。但须要注意的是,新声明的变量必须以 $ 符号開始。
技术分享
最后一个命令报错了,是由于LLDB没法确定结果的类型。须要强制转换一下来告诉LLDB。
技术分享

call

调用命令。类似expression,一般用户不须要打印结果或者无返回值的地方

bt

打印调用堆栈信息,使用 bt all命令能够打印出全部thread的调用栈信息
比方说:call [self.view setBackgroundColor:[UIColor redColor]]。使用这个命令来设置view controller的背景色为红色

image

image命令可用于寻址。有多个组合命令。比較有用的使用方法是用于寻找栈地址相应的代码位置。具体使用方法參考:http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/http://blog.csdn.net/hursing/article/details/8745334

常见问题

对中文的兼容

前面文章中讲过,有中文时必须使用 [NSString stringWithUTF8String:] 方法,原因在于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

类型的问题

出现类型不确定或类型不匹配的时候。就会报错。这个时候必须强制转换才干够。

比方前文中获取indexPath的row的方法的表达式:(int)[indexPath row],或者类似以下这样的干脆无返回值 p (void)NSLog(@"%@",[self.view  viewWithTag:1001])。或者上文中的 p (char)[[$array objectAtIndex:$a] characterAtIndex:0],这些场景中都须要明白输出的类型。


找不到方法

注意,使用系统框架对象的属性时不能使用dot语法。比方下图中的问题。
技术分享
改成例如以下的格式才行:
技术分享


Chisel


基于LLDB对Python插件的支持,http://lldb.llvm.org/python-reference.html。facebook开发开源了一套LLDB命令库。https://github.com/facebook/chisel,里面包括了非常多非常有意思的命令工具。

安装方式非常easy。使用brew工具。具体參考官方站点,不多说。


安装之后,使用help命令。在以下能够 user-defined commands 能够找到这个框架提供的一些自己定义命令。Chisel提供了许多很实用的命令行工具,调试UI的时候很方便,详细能够參考官方站点。



參考





























































































以上是关于lldb 脚本 - 断点写出(breakpoint write) 修复的主要内容,如果未能解决你的问题,请参考以下文章

breakpoints &amp;&amp; lldb &#160;&amp;&amp; chisel 的使用

如何通过 LLDB 命令行添加断点操作?

lldb 断点命令在 XCode 8 中不起作用

lldb通过c ++ api未解析的断点

每当添加断点时 LLDB 就会崩溃

如何在 objc 类的 init 中设置 lldb 断点