美文网首页
第二十章:有选项和参数的桥接脚本(一)

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

作者: 股金杂谈 | 来源:发表于2018-10-09 19:50 被阅读17次

    当你在创建一个自定义的调试命令的时候, 你经常在你的命令上用一些选项或者参数. 只有一种方法可以执行一项任务的自定义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.

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/uscghxtx.html