当你在创建一个自定义的调试命令的时候, 你经常在你的命令上用一些选项或者参数. 只有一种方法可以执行一项任务的自定义LLDB命令是让人厌恶的只会一招的小马驹.
在这一章中, 你将会学习如何传一个可选的参数作为你自定义命令的参数来修改你自定义LLDB脚本中的功能和逻辑.
你将会继续使用你在前面的章节中创建的bar(break-after-regex)命令工作. 在这一章中, 你将会通过在你的脚本中添加逻辑处理选项来完成bar
命令.
在这一章的结尾, bar
命令将会有逻辑来处理下面的可选参数:
• Non-regular expression search: 使用-n或者--non_regex选项将会导致bar
命令用一个非正在表达式断点搜索. 这个选项不会带任何额外的参数.
• Filter by module: 使用-m或者--module选项将会仅仅只搜索特定模块中的断点. 这个选项期望有一个额外的指明模块名字的参数.
• Stop on condition: 使用-c或者--condition选项, 在步出当前函数之后bar
命令将会执行给定的条件. 如果为True
, 执行将会停止. 如果为False
, 执行将会继续. 这个选项期望一个额外的将会被执行而且是作为一个Objective-C
BOOL执行的字符串代码作为参数.
这一章将会是内容稠密但又很有趣的一章. 确保你手中已经有了一杯咖啡!
设置
如果你已经读过了之前的章节而且你的bar
命令是可以工作的, 然后你可以继续使用那个脚本并且可以忽略这一部分内容. 否则, 打开本章资源中的start文件夹, 然后复制BreakAfterRegex.py 文件到~/lldb文件夹中. 确保你的~/.lldbinit文件中已经有了在前面的章节中添加的下面这行内容:
command script import ~/lldb/BreakAfterRegex.py
如果你对于这个名师是否成功的加载到LLDB中有什么困惑, 只需要在终端中简单的新开一个LLDB实例:
lldb
然后查看bar
的文档:
(lldb) help bar
如果你得到了一个错误, 它就没有成功的加载到LLDB中; 但是如果你的到了文档字符串, 那就表明成功的加载到了LLDB中.
RWDevCon项目
在本章中, 你将会使用一个叫做RWDevcon的APP. 它是一个直播APP, 可以在APP Store中下载https://itunes.apple.com/us/app/rwdevcon-the-tutorial-conference/id958625272?mt=8.
这个APP是RWDevcon引用的同伴APP, https://www.rwdevcon.com/, 这是一个查看在Ray Wenderlich生气之前你可以拍多少下Ray Wenderlich的肩膀. 尝试运行它!我个人的最好值是37!
在这个项目中, 我已经建了一个分支
84167c68
, 这个分支可以starter文件夹中找到. 然而, 你可以在https://github.com/raywenderlich/RWDevCon-App这个网站上找到更早的版本.找到
start
文件夹然后打开, 构建, 然后运行这个应用程序. 看一个了解一下这个项目.这里不需要浏览任何的源代码. 在
bar
命令的帮助下, 你可以浏览只能断点查询到的不同的有趣的事物.但是在我们做之前, 让我们先讨论一下如何让
bar
命令变得更加强大.
Python模块optparse
LLDB Python脚本中最可爱的事情是你可以发挥Python的所有力量以及它所有的模块.
在Python 2.7中在解析现象和参数时这里有三个值得查看的非常相关的模块: getopt, getopt, 和argparse. getopt
是一种地级别的而optparse
是正在淘汰的路上因为它在Python 2.7之后的版本被废弃了. 不幸的是argparse
在设计的时候几乎是和Python的sys.argv
一起使用的-- sys.argv是不能在Python 的LLDB命令脚本中使用的.这就意味着optparse
将会是你的go-to
选项. Facebook的Chisel, Apple自己自定义的LLDB脚本, 而我全部使用这个模块. 因此, 它有一点是事实上的解析参数的标准.
optparse
模块可以让你定义一个OptionParser类型的实例, 一个负责解析所有参数的类. 在这个类工作的时候, 你需要声明你的命令支持的参数和选项. 这产生了一种情况因为可选的参数可能为那个特定的选项带有额外的值或者没带额外的值.
看一个简单的例子. 思考下面的代码:
some_command woot -b 34 -a "hello world"
这个命令的名字叫做some_command
. 但是传到这个命令里的参数和选项是什么呢?
如果你没有给这个解析器任何的上下文, 然后这个句子就可能是模棱两可的. 这个解析器不知道-b
或者-a
选项是否应该带一个参数. 例如, 这个解析器可能会认为这个命令传入了三个参数:[woot
, 34
, hello world
], 和两个没有参数的选项-b
, -a
. 然儿, 如果解析器期望-b
或者-a
带一个参数, 解析器就会将[woot
作为参数, 34
作为-b
的参数而hello world
作为-a
的参数.
让我们更深入到optparse
函数中, 然后看一看我们如何使用它来处理类似的情况.
添加没有参数的选项
伴随着你需要告诉解析器哪些参数是期望的, 是时候添加你的第一个可以修改bar
命令功能的选项来应用到没有使用正则表达式的SBBreakpoint
, 而不是使用一个普通的正则表达式.
这个参数将会通过一个Python的boolean
值返回, 这个选项不需要参数. 存在的这个选项就是你需要检测的boolean
值的所有信息. 如果参数存在, 然后它的值就会是True
. 否则, 它的值就是False
.
有些脚本的作者会为这个boolean值设计一个指明必要的参数的boolean
选项而且这个选项的默认值可能是True
也可能是False
如果这个选项没有赋值的话.
例如, 下面的命令带了一个选项, -f没有带参数:
some_command -f
这将会被转换为:
some_command -f1
这真不是我的风格. 但是如果你正在为广泛的用户写脚本的话你可能要考虑这种设计方式, 因为它给了用户更详细的目的.
好了, 闲聊够了. 让我们去实现这个解析器的内容吧.
打开BreakAfterRegex.py然后在这个文件的顶部添加下面的import
语句:
import optparse
import shlex
optparse
模块包含OptionParser
类, OptionParser
类可以解析指令中额外输入的任何内容.
shlex
模块有一个好用的小Python函数, 这个函数可以方便的分割参数,并将参数应用到您的指令中, 同时保持字符串不变.
例如, 思考下面的Python代码:
import shlex
command = '"hello world" "2nd parameter" 34'
shlex.split(command)
这将会产生下面的输出:
['hello world', '2nd parameter', '34']
这里返回一个python的list, 元素内容是解析后的python的string.
但是在你去用split
方法之前, 你需要创建一个解析器.
在BreakAfterRegex.py
文件的底部, 添加如下代码:
def generateOptionParser():
'''Gets the return register as a string for lldb
based upon the hardware
'''
usage = "usage: %prog [options] breakpoint_query\n" +\
"Use 'bar -h' for option desc"
#1
parser = optparse.OptionParser(usage=usage, prog='bar')
#2
parser.add_option("-n", "--non_regex",
#3
action="store_true",
#4
default=False,
#5
dest="non_regex",
#6
help="Use a non-regex breakpoint instead")
#7
return parser
让我们一个参数一个参数的解释一下:
- (编号为#1的这一行)你创建了
OptionParser
参数, 然后给他传了一个usage
参数和prog
参数. 如果你搞砸了或者给了parser
一个它不知道怎么处理的参数, usage就会被显示.prog
选项是用来定位程序名字的.因为它解决了奇怪的小问题, 这个小问题会让你运行-h
或-help
来获取所有支持的自定义命令, 所以我总是合并它. 如果prog
参数不在这里,-h
命令就无法正常的工作. 这是生活中的一个小谜团. - (编号为#2的)这一行, 给
parser
添加了-n
和-- non_regex
参数. - (编号为#3的这一行)
action
参数告知了, 这段程序运行完之后执行什么操作. "store_true"这个选项的应用表明parser
会存储Python Boolean True. - (编号为#4的这一行)表明如果
default
没有被明确的传入值,那么他的默认初始值将会是false. - (编号为#5的这一行)
dest
参数用来确定OptionParser
解析你的输入时你给出的属性的名字.举个列子, 思考下面的代码, 这段代码在指令中解析了一个带有选项和参数的python 字符串.
command_args = shlex.split(command)
(options, args) = parser.parse_args(command_args)
options.non_regex
正如你稍后会看到的, parse_args
方法产生了一个python的tuple
,这个tuple
包含两个list
其中一个list
包含的是options(叫做options), 另一个list
包含的是arguments(叫做args).现在options
变量将会包含non_regex
属性.
- (编号为#6的这一行)
help
将会给你帮助文档. 你可以用--help
选项获取所有的参数和他们的信息.例如, 当这些被正确的设置到bar
指令中之后, 你所要做的就是通过输入bar -h
就能查看包含所有选项以及选项的作用的列表. - (编号为#7的这一行)在你创建了
OptionParser
, 并且添加了-n
选项之后, 你需要返回OptionParser
.
你刚才创建了一个方法, 这个方法可以生成OptionParser
实例, 这个实例你需要开始去解析那些参数.现在是时候来用下刚才学的知识了.
让我们跳回breakAfterRegex
函数的开始, 移除下面这两行代码:
target = debugger.GetSelectedTarget()
breakpoint = target.BreakpointCreateByRegex(command)
然后在它们的位置上添加下面的代码:
'''Creates a regular expression breakpoint and adds it.
Once the breakpoint is hit, control will step out of the
current function and print the return value. Useful for
stopping on getter/accessor/initialization methods
'''
#1
command = command.replace('\\', '\\\\')
#2
command_args = shlex.split(command, posix=False)
#3
parser = generateOptionParser()
#4
try:
#5
(options, args) = parser.parse_args(command_args)
except:
result.SetError(parser.usage)
return
target = debugger.GetSelectedTarget()
#6
clean_command = shlex.split(args[0])[0]
#7
if options.non_regex:
breakpoint = target.BreakpointCreateByName(clean_command)
else:
breakpoint = target.BreakpointCreateByRegex(
clean_command)
# The rest remains unchanged
确保你的缩进是正确的.这些应该缩进两个空格, 因为它们全部都是函数的一部分.
下面是这些代码所做的事情:
- 当把你的输入给
OptionParser
解析的时候, 它会把slashes
作为逃避字符解释. 例如, \' 会作为 '解释, 这就意味着在你的命令中需要规避所有的反斜杠字符. - 正如你在前面几章中学到的内容, 传入到你的自定义lldb脚本中的指令参数是一个python字符串. 你会透过这个变量进入
shlex.split
方法去获取一个包含python strs的python list.此外, 这里就是那个帮助对付任何包含特殊字符串(比如:破折号)的输入posix=False
的作用. 否则,OptionParser
会错误的假定那是一个被传入的选项. 这很重要因为Objective-C在实例方法中有破折号, 因此你不会希望破折号被错误的解释为一个选项. - 使用刚才新创建的
generateOptionParser
函数, 你创建了一个解析器来处理命令行的输入. - 解析输入可能很容易出错. Python通常会抛出异常来处理错误. 如果
optparse
发生了错误并抛出了异常, 不要吃惊. 如果你没有在你的脚本中捕获异常, lldb就会停止工作, 进程也会收到重创!因此, 在解析过程中包含一个try-except
代码块去防止lldb在出现不合理的输入的时候出现假死. -
OptionParser
类有一个parse_args
方法. 你要把command_args
变量传入到这个方法里, 并且接收一个元组作为返回值. 这个元组由两个值组成: 一个值是options
,options
由所有的选项参数组成(现在只有non_regex
这一个选项). 另外一个值args
由 parser解析出来的输入值组成. - 你将捕获的第一个参数赋值给
clean_command
变量. 还记得第二行提到的posix=False
吗?那个逻辑将会持有你捕获到的用圆括号的语法保护的参数. 如果你没有将posix
设置为false
, 你也可以用args[0]代替, 但是你会由于不能在正则表达式中使用反斜杠语法而丧失正则表达式的强大功能. - 你已经用掉了第一个选项!你正在检查选项
non_regex
的真实性, 如果是true, 你将会执行在SBTarget
中的BreakpointCreateByName
去产生一个非正则表达式断点. 如果non_regex
的值是false(你给generateOptionParser
函数传入的default
的默认值), 然后你的脚本就会使用正则表达式搜索. 再说一次, 你所需要做的就是在bar
指令的输入中添加-n
来使non_regex
为true.