(十三)SB示例 升级版的lookup

1. 升级版的lookup

1.1 创建自动化脚本

这个项目的starter目录中包含了两个Python脚本,这将使您在创建LLDB脚本内容时的生活更轻松。具体如下:

  • generate_new_script.py:接受一个名字,然后帮我们搭建一个脚本的基础骨架。
  • lldbinit.py:此脚本将遍历位于同一目录中的所有脚本(以.py结尾的文件),并尝试将它们加载到LLDB中。此外,如果存在扩展名为txt的文件,LLDB将尝试通过命令导入来加载这些文件的内容。

把这两个文件都放在本章的starter文件夹中,并将它们粘贴到~/lldb/目录中。进入到~/.lldbinit文件并添加以下代码行:

command script import ~/lldb/lldbinit.py
创建lookup命令
 ~> lldb
(lldb) reload_script

如果没有错误的话,我们就可以开始了。

(lldb) __generate_script lookup
Opening "/Users/ycpeng/lldb/lookup.py"...
(lldb) reload_script
(lldb) lookup
Hello! the lookup command is working!
实现lookup命令

前面我们看到lookup命令背后的基础相当简单。“秘密”就是使用SBTargetfindGlobalFunctions。在那之后,只需要进行格式化输出。

我们将继续使用Allocator项目。打开项目,在iPhone模拟器上构建并运行。运行之后,暂停应用程序并启动LLDB。

这个FindGlobalFunctions需要哪些参数?

(lldb) script help(lldb.SBTarget.FindGlobalFunctions)
Help on function FindGlobalFunctions in module lldb:

FindGlobalFunctions(self, name, max_matches, matchtype)
    FindGlobalFunctions(SBTarget self, str const * name, uint32_t max_matches, lldb::MatchType matchtype) -> SBSymbolContextList

因为它是一个Python类,所以可以忽略第一个自参数。名为name的str参数将是我们查询字符串。最大匹配将指定您想要的最大匹配数量。如果使用0,它将返回所有可用的匹配项。matchType参数是一个lldb Python枚举,可以指定执行不同类型的搜索,例如regexnon-regex

因为regex搜索实际上是唯一的方法,所以我们将使用LLDB枚举值lldb.eMatchTypeRegex

其他枚举值可以在这里找到:https://lldb.llvm.org/python_reference/_lldb%27-module.html#eMatchTypeRegex

是时候在lookup.py脚本中实现它了。打开~/lldb/lookup.py。在handle_command末尾找到以下代码:

# Uncomment if you are expecting at least one argument
# clean_command = shlex.split(args[0])[0]
result.AppendMessage('Hello! the lookup command is working!')

替换为

#1
clean_command = shlex.split(args[0])[0]
#2
target = debugger.GetSelectedTarget()
#3
contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
#4
result.AppendMessage(str(contextlist))
  1. 获取传递给脚本的命令的干净版本。
  2. 通过SBDebugger获取SBTarget的实例。
  3. 使用FindGlobalFunctions API,传入clean_command。提供的0表示结果数没有上限,并使用eMatchTypeRegex匹配类型进行正则表达式搜索。
  4. contextlist转换为Python str,然后将其附加到SBCommandReturnObject
(lldb) lookup DSObjectiveCObject
     Module: file = "/Users/ycpeng/Library/Developer/Xcode/DerivedData/Allocator-fnbctbueesgtzzfgksqcimloegwm/Build/Products/Debug-iphonesimulator/Allocator.app/Allocator", arch = "x86_64"
CompileUnit: id = {0x00000000}, file = "/Users/ycpeng/Desktop/Books/Apple_Debugging_and_Reverse_Engineering_v3.0/25-Script Bridging with SBValue & Language Contexts/starter/Allocator/Allocator/DSObjectiveCObject.m", language = "unknown"
   Function: id = {0x100000268}, name = "-[DSObjectiveCObject setLastName:]", range = [0x0000000100002150-0x0000000100002181)
   FuncType: id = {0x100000268}, byte-size = 0, decl = DSObjectiveCObject.h:36, compiler_type = "void (NSString *)"
     Symbol: id = {0x000000db}, range = [0x0000000100002150-0x0000000100002190), name="-[DSObjectiveCObject setLastName:]"
...

得到的输出比image lookup -rn DSObjectiveCObject更糟糕。我们用script研究一下这个执行结果。

(lldb) script k = lldb.target.FindGlobalFunctions('DSObjectiveCObject', 0, lldb.eMatchTypeRegex)

我们可以去查看SBSymbolContextList的文档,或直接查看它的所有方法。

(lldb) gdocumentation SBSymbolContextList
(lldb) script dir(lldb.SBSymbolContextList)
['Append', 'Clear', 'GetContextAtIndex', 'GetDescription', 'GetSize', 'IsValid', '__bool__', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__swig_getmethods__', '__swig_setmethods__', '__weakref__', 'blocks', 'compile_units', 'functions', 'get_block_array', 'get_compile_unit_array', 'get_function_array', 'get_line_entry_array', 'get_module_array', 'get_symbol_array', 'line_entries', 'modules', 'symbols']

我们可以发现两个重要的方法__iter____getitem__,说明我们可以遍历和使用索引访问它。

//等效于script k.__getitem__(0)
(lldb) script k[0]
<lldb.SBSymbolContext; proxy of <Swig Object of type 'lldb::SBSymbolContext *' at 0x10898b6c0> >

(lldb) script print(k[0])
     Module: file = "/Users/ycpeng/Library/Developer/Xcode/DerivedData/Allocator-fnbctbueesgtzzfgksqcimloegwm/Build/Products/Debug-iphonesimulator/Allocator.app/Allocator", arch = "x86_64"
CompileUnit: id = {0x00000000}, file = "/Users/ycpeng/Desktop/Books/Apple_Debugging_and_Reverse_Engineering_v3.0/25-Script Bridging with SBValue & Language Contexts/starter/Allocator/Allocator/DSObjectiveCObject.m", language = "objective-c"
   Function: id = {0x100000268}, name = "-[DSObjectiveCObject setLastName:]", range = [0x0000000100002150-0x0000000100002181)
   FuncType: id = {0x100000268}, byte-size = 0, decl = DSObjectiveCObject.h:36, compiler_type = "void (NSString *)"
     Symbol: id = {0x000000db}, range = [0x0000000100002150-0x0000000100002190), name="-[DSObjectiveCObject setLastName:]"

我们关心的是方法名,如何快速拿到它呢?

(lldb) script print(k[0].symbol.name)
-[DSObjectiveCObject setLastName:]
(lldb) script print(k[0].function.name)
-[DSObjectiveCObject setLastName:]

下面我们将

#3
contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
#4
result.AppendMessage(str(contextlist))

替换为

contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
output = ''
for context in contextlist:
    output += context.symbol.name + '\n\n'
result.AppendMessage(output)

并在LLDB中实验一下

(lldb) reload_script
(lldb) lookup DSObjectiveCObject
-[DSObjectiveCObject setLastName:]

-[DSObjectiveCObject .cxx_destruct]

-[DSObjectiveCObject setFirstName:]

-[DSObjectiveCObject eyeColor]

-[DSObjectiveCObject init]

-[DSObjectiveCObject lastName]

-[DSObjectiveCObject setEyeColor:]

-[DSObjectiveCObject firstName]

输出更加干净了。

现在我想看看这些函数在进程中的位置。我想将一个特定的模块所有函数打印出来,最上面是模块名和命中数量和分割的*

返回lookup.py文件,创建两个新函数。

  • generateFunctionDictionary获取SBBreakpointContextList并生成一个Python字典列表。这个dict将包含每个模块的键。对于dict中的值,将为每个命中的SBSymbolContext提供一个Python列表。

  • generateOutput解析创建的字典,并从OptionParser实例接收到的选项。此方法将返回要打印回控制台的字符串。

首先在lookup.py脚本中的handle_command函数下面实现generateModuleDictionary函数:

def generateModuleDictionary(contextlist):
    mdict = {}
    for context in contextlist: 
        #1
        key = context.module.file.fullpath
        #2
        if not key in mdict:
            mdict[key] = []
        #3
        mdict[key].append(context)
    return mdict
  1. SBSymbolContext中,通过SBModuleSBFileSpecfullPath获取Python字符串,并将其分配给一个名为key的变量。获取完整路径(而不是SBFileSpecbasename属性)很重要,因为可能有多个具有相同basename的模块。
  2. mdict变量将保存找到的所有符号的列表,并按模块拆分。此字典中的键将是模块名称,值将是在该模块中找到的符号数组。检查字典是否已经包含此模块的列表。如果没有,将为此模块键添加一个空白列表。
  3. SBSymbolContext实例添加到此模块的相应列表中。

这个函数下面继续完成generateOutput

def generateOutput(mdict, options, target):
    #1
    output = ''
    separator = '*' * 60 + '\n'
    #2
    for key in mdict:
        #3
        count = len(mdict[key])
        firstItem = mdict[key][0]
        #4
        moduleName = firstItem.module.file.basename
        output += '{0}{1} hits in {2}\n{0}'.format(separator, count, moduleName)
        #5
        for context in mdict[key]:
            query = ''
            query += context.symbol.name
            query += '\n\n'
            output += query
    return output
  1. 输出变量将是包含最终传递给SBCommandReturnObject的所有内容的返回字符串。
  2. 遍历mdict字典中找到的所有键。
  3. 这将获取命中数量和第一个对象(下一步从里面取出模块名)。
  4. 获取模块名。
  5. 遍历Python列表中的所有SBSymbolContext项,并将名称添加到输出变量中。

扩展handle_command函数中的代码,以便可以使用刚刚创建的两个新方法。找到:

output = ''
for context in contextlist:
    output += context.symbol.name + '\n\n'

替换为:

mdict = generateModuleDictionary(contextlist)
output = generateOutput(mdict, options, target)

试一下:

(lldb) reload_script
(lldb) lookup DSObjectiveCObject
************************************************************
8 hits in Allocator
************************************************************
-[DSObjectiveCObject setLastName:]

-[DSObjectiveCObject .cxx_destruct]

-[DSObjectiveCObject setFirstName:]

-[DSObjectiveCObject eyeColor]

-[DSObjectiveCObject init]

-[DSObjectiveCObject lastName]

-[DSObjectiveCObject setEyeColor:]

-[DSObjectiveCObject firstName]

我们现在可以查看OC中有两个参数的所有方法:

(lldb) lookup initWith(\\w+\:){2,2}\]

1.4 为lookup添加选项

  • 向每个查询添加加载地址,展示实际函数在内存中的位置。
  • 仅提供模块摘要。不生成函数名,只列出每个模块的命中数。

配置generateOptionParser

def generateOptionParser():
    usage = "usage: %prog [options] code_to_query"
    parser = optparse.OptionParser(usage=usage, prog="lookup")
    parser.add_option("-l", "--load_address",
                      action="store_true",
                      default=False,
                      dest="load_address",
                      help="Show the load addresses for a particular hit")
    parser.add_option("-s", "--module_summary",
                      action="store_true",
                      default=False,
                      dest="module_summary",
                      help="Only show the amount of queries in the module")
    return parser

您将首先实现--load_address。在generateOutput函数中,找到在SBSymbolContext上迭代的for循环,并替换为:

for context in mdict[key]:
            query = ''
            
            #1
            if options.load_address:
                #2
                start = context.symbol.addr.GetLoadAddress(target)
                end = context.symbol.end_addr.GetLoadAddress(target)
                #3
                startHex = '0x' + format(start, '012x')
                endHex = '0x' + format(end, '012x')
                query += '[{}-{}]\n'.format(startHex, endHex)

            query += context.symbol.name
            query += '\n\n'
            output += query
  1. 判断是否需要加载地址信息。
  2. 符号的addrend_addr中通过GetLoadAddress获取到加载地址。
  3. 使用Python format函数对使用Python long表示开始和结束地址进行格式化。

实验一下:

(lldb) reload_script
(lldb) lookup -l DSObjectiveCObject
************************************************************
8 hits in Allocator
************************************************************
[0x0001056de150-0x0001056de190]
-[DSObjectiveCObject setLastName:]

[0x0001056de190-0x0001056de1e9]
-[DSObjectiveCObject .cxx_destruct]

[0x0001056de0f0-0x0001056de130]
-[DSObjectiveCObject setFirstName:]

[0x0001056de070-0x0001056de090]
-[DSObjectiveCObject eyeColor]

[0x0001056ddf60-0x0001056de070]
-[DSObjectiveCObject init]

[0x0001056de130-0x0001056de150]
-[DSObjectiveCObject lastName]

[0x0001056de090-0x0001056de0d0]
-[DSObjectiveCObject setEyeColor:]

[0x0001056de0d0-0x0001056de0f0]
-[DSObjectiveCObject firstName]

然后在generateOutputmoduleName = firstItem.module.file.basename后面添加:

if options.module_summary:
    output += '{} hits in {}\n'.format(count, moduleName)
    continue

实验一下:

(lldb) reload_script
(lldb) lookup -s viewWillAppear
2 hits in Allocator
1 hits in DocumentManager
54 hits in UIKitCore
4 hits in ShareSheet
6 hits in AVKit
2 hits in WebKit
1 hits in PDFKit
1 hits in GLKit
10 hits in MapKit
27 hits in ContactsUI
5 hits in OnBoardingKit
2 hits in AccessibilityUIUtilities
20 hits in Preferences
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容