本文所实现的功能已经合入到 chisel的开发分支上面。 欢迎follow我的github https://github.com/sunbohong
为了避免浪费各位读者的时间,请在阅读本文前先思考以下问题:
- 你是否经常使用Xcode的断点功能?
- Xcode的断点功能好用吗?
- 如果给Xcode批量添加启用&禁用断点功能,是否会提高你的工作效率?
如果都是NO,那么,请先阅读 https://objccn.io/issue-19-2/ 后再回来阅读本文章。相信我,掌握甚至精通 lldb 能够快速的提供你的生产力,提高生活品质。
如果以上问题都是YES,那么,欢迎继续阅读以下内容。
阅读本文需要以下技能:
- 对 Python 有基本的了解
- 对 Xcode 的断点功能有基本的了解(相关的文档见下方的参考链接)
通过本文,希望大家可以了解以下内容:
- 通过 lldb 相关 API,构建自己的效率工具(Python脚本)
LLDB
LLDB 是一个开源调试器,它已经被内置在 Xcode 程序中。 如下图所示,位于主窗口的底部,名为Conseole的窗口就是用于和 lldb 交互的区域。
断点
首先,我们通过以下操作,对 lldb 有基本的了解。
- 创建一个程序
- 在
viewDidLoad
处添加添加断点 - 运行程序,并使程序停在断点处
- 在 Console 区域输入
po self
并回车 - 观察输出结果
当程序暂停后(通过断点或者手动点击暂停按钮),Console 区域就会进入 lldb 模式。
代码语言:javascript复制po self
po self
是指把 self 当做一个对象进行打印,类似的还有 p self
等命令。
ps.通过 help
命令,可以打印所有的可用命令。
pss.通过 help po
命令,可以打印该命令的用法。
Chisel
Chisel 是一个 Python 脚本集合,建议读者自行阅读 https://objccn.io/issue-19-2/ 后再看下面的部分
再看LLDB
LLDB 的调试接口本质上是一个C 共享库,在 Mac 系统上,它被打包为 LLDB.framework(正常情况下,它存在 /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework
路径下),在类unix 系统上,它是 lldb.so.
这些调试接口可以在 lldb 的脚本解释器内直接使用,或者可以被引入 lldb.py 模块 的Python脚本 使用。
LLDB 本身支持用户自定义命令,比如通过脚本可以自定义一个pviews
命令,该命令可以打印APP所有的视图。
ps.该命令已经在 Chisel 中实现。
lldb脚本入门
首先,我们先通过一个非常简单的脚本,构造一个自定义命令。
- 在 ~/ls.py 位置创建一个脚本,内容如下:
#!/usr/bin/python
import lldb
import commands
import optparse
import shlex
def ls(debugger, command, result, internal_dict):
print >>result, (commands.getoutput('/bin/ls %s' % command))
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f ls.ls ls')
print 'The "ls" python command has been installed and is ready for use.'
- 在lldb中载入脚本
(lldb) command script import ~/ls.py
- 执行命令
(lldb) ls ./
Applications
Users
...
到此为止,我们已经成功的实现了一个自定义的命令。
OK,让我们重新解释一下上面的代码。
代码语言:javascript复制command script import ~/ls.py
command
是 lldb 用于管理自定义命令的一个入口。
command script import
可以导入一个自定义的脚本文件。
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f ls.ls ls')
print 'The "ls" python command has been installed and is ready for use.'
脚本文件被导入时,并且 def __lldb_init_module(debugger, internal_dict):
方法会被检测到时,它会被自动调用。我们可以在这里一次性实现多个自定义命令。
debugger
是 lldb.SBDebugger
的一个实例,代表了当前的调试器对象。
internal_dict
包含了当前脚本会话的变量和方法。
HandleCommand
是一个实例方法,通过它,我们可以在 Python 脚本里面,调用lldb的方法。比如,这里的 command script add -f ls.py ls
command script add -f ls.py ls
的含义是“声明一个自定义的命令 ls
,这个命令的实现是 ls.py
”。
command script add -f 函数名 自定义命令名
-f
代表后面跟着一个函数名,类似还有 -c
,代表一个 Python 类。
def ls(debugger, command, result, internal_dict):
print >>result, (commands.getoutput('/bin/ls %s' % command))
debugger
上面已经讲过,不再赘述。
command
是一个字符串,是我们命令的参数,通过情况下,我们可以使用 shlex
模块的 shlex.split(command)
命令切割处理。当然,本例直接透传就可以了。
result
是 lldb.SBCommandReturnObject
的实例。
internal_dict
上面已经讲过,不再赘述。
这个函数是我们自定义命令的核心,它通过调用 Python 模块commands
的 getoutput
方法,获取 ls
命令的输出结果,并打印到结果中。
批量管理断点
通过上面的介绍,相信读者很容易实现一个批量管理断点的自定义命令。 这里简单介绍一下我的思路。
- 注册两个自定义命令作为入口,
benable
&bdisable
通过一个函数实现状态的控制 def switchBreakpointState(expression,on):
- 遍历断点和 location,当符合要求时,切换断点和 location 的状态。
lldb 模块的常用变量
lldb 提供以下常用变量(类似全局变量) | 类 |
---|---|
lldb.debugger | lldb.SBDebugger |
lldb.target | lldb.SBTarget |
lldb.process | lldb.SBProcess |
lldb.thread | lldb.SBThread |
lldb.frame | lldb.SBFrame |
完整源码
代码语言:javascript复制#!/usr/bin/python
import lldb
import commands
import optparse
import shlex
import re
def ls(debugger, command, result, internal_dict):
print >>result, (commands.getoutput('/bin/ls %s' % command))
def switchBreakpointState(expression,on):
print str(expression)
print str(on)
expression_pattern = re.compile(r'{}'.format(expression),re.I)
print str(expression_pattern)
target = lldb.debugger.GetSelectedTarget()
print str(target)
for breakpoint in target.breakpoint_iter():
if breakpoint.IsEnabled() != on and (expression_pattern.search(str(breakpoint))):
print str(breakpoint)
breakpoint.SetEnabled(on)
for location in breakpoint:
if location.IsEnabled() != on and (expression_pattern.search(str(location)) or expression == hex(location.GetAddress()) ):
print str(location)
location.SetEnabled(on)
def benable(debugger, command, result, internal_dict):
switchBreakpointState(str(command),True)
def bdisable(debugger, command, result, internal_dict):
switchBreakpointState(str(command),False)
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f ls.ls ls')
debugger.HandleCommand('command script add -f ls.benable benable')
debugger.HandleCommand('command script add -f ls.bdisable bdisable')
print 'The python commands has been installed and is ready for use.'
参考文档
- lldb命令
- Xcode 断点文档
- LLDB Python Reference
- 与调试器共舞 – LLDB 的华尔兹
- http://ios.jobbole.com/81794/
- https://objccn.io/issue-19-2/