给断点的回调函数传参数
是时候创建Parser
的-c
选项了, 也叫--condition
!
回到BreakAfterRegex.py
文件然后找到generateOptionParser
.在return parser
这行代码之前正确的添加下面的代码:
parser.add_option("-c", "--condition",
action="store",
default=None,
dest="condition",
help="Only stop if the expression matches True. Can
reference return value through 'obj'. Obj-C only.")
现在你应该知道这些代码是做什么的了, 但是下面会做一个快速的总结. 在这里你创建了一个--condition
选项, 它的默认值是none, 并且期待着传进来一个参数, 在help文本里有一些有趣的东西.你表明了你可以通过名字叫obj
的变量引用返回值. 这就意味着你在执行代码的时候会捕获返回值并把obj
赋值给它.
是时候来用一下这个新选项了. 但是等一下...先思考片刻. 你如何把选项的参数传入SBBreakpoint
的回调函数 里呢?
记住, 这些回调函数被称为私有的C++ API, 并且被限制在一个特定的方法签名里.考虑一下下面这个你设置断点的声明:
breakpoint.SetScriptCallbackFunction("BreakAfterRegex.breakpointHandler")
当SBBreakpoint
的回调触发时, 下面的这个函数就会被调用:
def breakpointHandler(frame, bp_loc, dict):
# method contents here
你只有SBFrame, SBBreakpointLocation
和Python内部的dict可以用来传递信息.这个函数怎样才能读到你的OptionParser
实例解析出来的参数并且把它传递给另一个函数呢?函数签名是被锁定的, 仅用来提供这些参数的.
想到几个解决这个问题的方法. 你可以在SBBreakpoint
中搜索替代方法或者类似的类看一下时候有API可以让你传入额外的参数.或者, 你可以试一下继承一下SBBreakpoint
类并添加一些方法来传入条件选项参数, 或者你可以创建一个全局变量来传入解析的选项.如果你真的感到绝望, 你可以试着在运行时用Python的exec
函数动态的创建一个方法.
不幸的是, SBBreakpoint
没有APIs来处理类和回调, 全局变量通常情况下是一个糟糕的想法并且你可能因为多个端点的回调引用了同一个全局变量这样复杂的逻辑而陷入到线程问题中.
继承一个子类是行不通的, 因为Python lldb类动态生成的背后用的是C++的代码, 当你每次尝试访问传入的SBBreakpoint
值时都会得到一个新的实例.此外, 有99%的可能使用exec
是一个非常非常糟糕的想法.
开发者应该做什么呢?
这就意味着你将不得不使用全局变量并处理全局变量的状态.考虑一下下面的情况, 你将选项赋值给全局变量并切创建了SBBreakpoint 1
. 你对SBBreakpoint 2
也做了同样的事情.然而, SBBreakpoint 1
被触发了并切引用了全局变量的回调被执行了.由于SBBreakpoint 2
已经被创建了, 这个选项已经被修改成了与期望的不一样的值.
幸运的是, 这里有一个比使用全局方案更好的替代方案, 而且你会找到解决选项的全局状态的解决方案.
用来替代全局变量的是, 你将会穿件一个Python类, 这个Python类用一个属性来持有被传入的这个选项.
现在去定位那个全局变量的状态: 取代用属性来持有选项的是, 你将用python的dict来持有选项. 断点的一个好处是不管你删除或创建多少次, 每运行一个session
每一个断点都有一个唯一的ID.这就意味着你可以使用断点的ID来作为唯一的key来为断点引用一个特定的选项集合.然后你可以将断点的ID作为key, 而把断点的选项作为value.太酷了, 对吧?
回到BreakAfterRegex.py
文件的顶部, 在import
语句的下面正确的添加下面的代码:
#1
class BarOptions(object):
#2
optdict = {}
#3
@staticmethod
def addOptions(options, breakpoint):
key = str(breakpoint.GetID())
BarOptions.optdict[key] = options
我们一步一步的过一遍:
- 你声明了一个继承自
object
的类BarOptions
.把object
想象成Python中的NSObject
就行了.这个类提供了一些基本的函数. 不继承自某个类在Python是绝对可行的(就像swift一样), 但是Python的某些API让继承自Object变的更友好. - 你声明了一个叫做
optdict
的变量.如果你之前声明过实例变量, 他可能在某个init函数里.鉴于你只会用到这个类变量, 你不必为这个类设置任何初始化函数. - 你同样声明了一个叫做
addOptions
的函数, 它是唯一将选项与断点的ID绑定的函数.
跳到breakAfterRegex
函数, 然后把下面的代码正确的添加到你指定回调函数的代码(SetScriptCallbackFunction函数)之前.
BarOptions.addOptions(options, breakpoint)
在你添加了这行新的代码之后, 创建一个新的函数来判断这个条件. 在BreakAfterRegex.py
的底部添加一个新的函数evaluateCondition
.
def evaluateCondition(debugger, condition):
'''Returns True or False based upon the supplied condition.
You can reference the NSObject through "obj"'''
#1
res = lldb.SBCommandReturnObject()
interpreter = debugger.GetCommandInterpreter()
target = debugger.GetSelectedTarget()
#2
expression = 'expression -lobjc -O -- id obj = ((id){}); ((BOOL)
{})'.format(getRegisterString(target), condition)
interpreter.HandleCommand(expression, res)
#3
if res.GetError():
print(condition)
print('*' * 80 + '\n' + res.GetError() + '\ncondition:' + condition)
return False
elif res.HasResult():
#4
retval = res.GetOutput()
#5
if 'YES' in retval:
return True
#6
return False
下面来解析一下:
- 你创建了一个
SBCommandReturnObject
来处理从condition
参数传进来的代码. - 这会创建并执行传进来的自定义表达式.注意你声明了实例变量
obj
并指定它从返回值里取到的类型为id
. 这可以让你将返回值作为obj
方便的引用. 你提供的表达式将会被指定为Objective-C 的BOOL类型, 这将会输出一个YES或者NO的值. - 你将会判断返回的值, 而且如果这里包含了一个错误, 就将错误打印出来.在这个函数里你明确的返回了一个 False 或者 True, 因为你要用这个返回值决定在执行表达式的时候是停止还是继续.记住, 如果这个函数返回True,
SBBreakpoint
的回调函数breakpointHandler
将会停止执行.如果返回的不是ture执行就会继续. - 如果这里有输出的话将赋值给一个叫做
retval
的变量. - 用这种方法教表达式解析真的让我很痛苦, 因为在下一章你会学到一个更清晰易懂的判断对象的类型
SBValues
. 但是现在, 你将继续使用SBCommandReturnObject
并切比较你期望的输出.如果表达式的判断结果是YES, 然后停止执行. - 如果执行的结果返回的是NO, 然后就继续执行并返回False.
最后一轮代码! 找到breakpointHandler
函数, 在调用thread.StepOut()
的下方添加下面的代码:
#1
key = str(bp_loc.GetBreakpoint().GetID())
#2
options = BarOptions.optdict[key]
#3
if options.condition: #4
condition = shlex.split(options.condition)[0]
#5
return evaluateCondition(debugger, condition)
我们再来解释一下:
-
bp_loc
是SBBreakpointLocation
的一种类型. 这个类让你通过GetBreakpoint
引用初始化SBBreakpoint
.从这里开始, 你就可以使用ID了, ID是一个数字. 因此, 你需要将这个数字转化为一个Python字符串并赋值给key. - 这将会从类的
optdict
属性中取出选项然后赋值给变量options
. - 检查
options
是否是一个non-None引用.如果这里是一个有效的引用, 执行后面的逻辑. - 这将会解析传到命令行中的选项
condition
. 又一次, 我们不得不做一些额外的工作感谢前面提到的posix=False
, 因为它允许我们在选项和参数中使用反斜线和破折号. - 最后, 你调用了你在前面创建的函数
evaluateCondition
. 你返回了函数的返回值, 这个返回值决定了代码是否继续执行.
没有更多的python代码了!保存你的编辑然后回到Xcode中.
再一次, 修改dlopen
符号断点中的第二个action. 这一次, 改为下面的指令:
bar NSURL\(.*init
这一次断点会在NSURLs的初始化方法里触发.这些古怪的语法是必要因为NSURL的大多数初始化方法是这种类型的.
查看控制台中与HTTPS NSURL相关的输出:
看起来这个app有一些Amazon S3 的服务.使用你在
bar
指令中最新创建的condition
选项去暂停执行当NSURL从初始化函数里面返回并且在absoluteString
中包含amazon
的时候.回到
dlopen
符号断点中, 并且修改第二个action为下面的指令:
bar NSURL\(.*init -c '[[obj absoluteString] containsString:@"amazon"]'
屏幕快照 2018-10-10 下午7.06.21.png
构建并运行然后看会发生什么...
执行将会暂停在包含NURL...URL...的具体的某一行, 尽管他暂停在饿了swift环境中, 但是让我们正视现实, 那是一个NSURL实例.
网友评论