第二十章:有选项和参数的桥接脚本(一)

当你在创建一个自定义的调试命令的时候, 你经常在你的命令上用一些选项或者参数. 只有一种方法可以执行一项任务的自定义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-CBOOL执行的字符串代码作为参数.
这一章将会是内容稠密但又很有趣的一章. 确保你手中已经有了一杯咖啡!

设置

如果你已经读过了之前的章节而且你的bar命令是可以工作的, 然后你可以继续使用那个脚本并且可以忽略这一部分内容. 否则, 打开本章资源中的start文件夹, 然后复制BreakAfterRegex.py 文件到~/lldb文件夹中. 确保你的~/.lldbinit文件中已经有了在前面的章节中添加的下面这行内容:

command script import ~/lldb/BreakAfterRegex.py

如果你对于这个名师是否成功的加载到LLDB中有什么困惑, 只需要在终端中简单的新开一个LLDB实例:

lldb

然后查看bar的文档:

(lldb) help bar
图片.png

如果你得到了一个错误, 它就没有成功的加载到LLDB中; 但是如果你的到了文档字符串, 那就表明成功的加载到了LLDB中.

RWDevCon项目

在本章中, 你将会使用一个叫做RWDevcon的APP. 它是一个直播APP, 可以在APP Store中下载https://itunes.apple.com/us/app/rwdevcon-the-tutorial-conference/id958625272?mt=8.

图片.png

这个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. (编号为#1的这一行)你创建了OptionParser参数, 然后给他传了一个usage 参数和prog参数. 如果你搞砸了或者给了parser一个它不知道怎么处理的参数, usage就会被显示.prog选项是用来定位程序名字的.因为它解决了奇怪的小问题, 这个小问题会让你运行-h-help来获取所有支持的自定义命令, 所以我总是合并它. 如果prog参数不在这里, -h命令就无法正常的工作. 这是生活中的一个小谜团.
  2. (编号为#2的)这一行, 给parser添加了-n-- non_regex参数.
  3. (编号为#3的这一行)action参数告知了, 这段程序运行完之后执行什么操作. "store_true"这个选项的应用表明parser 会存储Python Boolean True.
  4. (编号为#4的这一行)表明如果default没有被明确的传入值,那么他的默认初始值将会是false.
  5. (编号为#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属性.

  1. (编号为#6的这一行)help将会给你帮助文档. 你可以用--help选项获取所有的参数和他们的信息.例如, 当这些被正确的设置到bar指令中之后, 你所要做的就是通过输入bar -h就能查看包含所有选项以及选项的作用的列表.
  2. (编号为#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

确保你的缩进是正确的.这些应该缩进两个空格, 因为它们全部都是函数的一部分.
下面是这些代码所做的事情:

  1. 当把你的输入给OptionParser解析的时候, 它会把slashes作为逃避字符解释. 例如, \' 会作为 '解释, 这就意味着在你的命令中需要规避所有的反斜杠字符.
  2. 正如你在前面几章中学到的内容, 传入到你的自定义lldb脚本中的指令参数是一个python字符串. 你会透过这个变量进入shlex.split方法去获取一个包含python strs的python list.此外, 这里就是那个帮助对付任何包含特殊字符串(比如:破折号)的输入posix=False的作用. 否则, OptionParser会错误的假定那是一个被传入的选项. 这很重要因为Objective-C在实例方法中有破折号, 因此你不会希望破折号被错误的解释为一个选项.
  3. 使用刚才新创建的generateOptionParser 函数, 你创建了一个解析器来处理命令行的输入.
  4. 解析输入可能很容易出错. Python通常会抛出异常来处理错误. 如果optparse发生了错误并抛出了异常, 不要吃惊. 如果你没有在你的脚本中捕获异常, lldb就会停止工作, 进程也会收到重创!因此, 在解析过程中包含一个try-except代码块去防止lldb在出现不合理的输入的时候出现假死.
  5. OptionParser类有一个parse_args方法. 你要把command_args变量传入到这个方法里, 并且接收一个元组作为返回值. 这个元组由两个值组成: 一个值是options, options 由所有的选项参数组成(现在只有non_regex这一个选项). 另外一个值args由 parser解析出来的输入值组成.
  6. 你将捕获的第一个参数赋值给clean_command变量. 还记得第二行提到的posix=False吗?那个逻辑将会持有你捕获到的用圆括号的语法保护的参数. 如果你没有将posix设置为false, 你也可以用args[0]代替, 但是你会由于不能在正则表达式中使用反斜杠语法而丧失正则表达式的强大功能.
  7. 你已经用掉了第一个选项!你正在检查选项non_regex的真实性, 如果是true, 你将会执行在SBTarget中的BreakpointCreateByName去产生一个非正则表达式断点. 如果non_regex的值是false(你给generateOptionParser函数传入的default的默认值), 然后你的脚本就会使用正则表达式搜索. 再说一次, 你所需要做的就是在bar指令的输入中添加-n来使non_regex为true.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容