[原]调试没有符号的 iOS 应用

说明:

  这里的调试是指使用 lldb 远程调试 iOS 应用

  设置断点是指在 ObjC 方法上设置断点

使用场景:

  1、调试被 strip 了的 iOS 应用

  2、调试被 strip 了的 iOS 系统 dylib

在调试时没有符号的 iOS 应用时,设置断点非常不方便:

  1、App:在没有开启 ASLR 时,需要首先找到方法的地址,然后针对地址设置断点

  2、Dylib:在没有开启 ASLR 时,需要找到dylib的基地址,然后计算偏移

如果开启了 ASLR,设置断点会更麻烦。

一直想解决这个问题,曾经想过的方法:

首先,ObjC 语言是一个相对动态的语言,所以使用class-dump这样的工具,可以 dump 出类信息,函数地址。

另外,DWARF 格式是有公开标准的,

因此,可以通过将 class-dump 的输出信息转换成 DWARF,在调试时动态加载符号。

这个方法我不是第一想到,这个帖子中有详细说明:http://stackoverflow.com/questions/17554070/import-class-dump-info-into-gdb

但是照这个方法进行操作后,发现对 iOS 应用没效果,而且过程繁琐。

后来想,ObjC是通过在C语言之上封装了薄薄的一层(消息特性)而形成的,

所有 ObjC 的方法调用最终会转换为 C 方法调用,

因此,可以通过在对应的 C 函数上设置断点来解决断点设置问题,

而如何得到 C 函数的地址,就依赖于 ObjC 的运行时方法了,主要涉及:

  1、object_getClass

  2、NSSelectorFromString

  3、class_respondsToSelector

  4、class_getMethodImplementation

在解决了在什么位置设置断点的问题后,

接下来需要解决如果在 lldb 中方便的设置断点。

lldb 集成了 Python 脚本引擎,参考:http://lldb.llvm.org/python-reference.html

因此我们可以通过Python脚本扩展 lldb 的调试命令,主要用到如下几个函数:

  1、lldb.debugger

  2、lldb.debugger.GetSelectedTarget()

  3、lldb.debugger.GetSelectedTarget().GetProcess()

  4、lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread()

  5、lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()

  6、lldb.frame.EvaluateExpression

  7、lldb.debugger.HandleCommand

脚本配置方法:

  方法一:在调试控制台执行:command script import bt_objc.py的文件路径

  方法二:将如上命令加入到 ~/.lldbinit,如果文件不存在则可以自己动手创建

脚本内容:

  1 #!/usr/bin/python

  2 

  3 '''

  4 Author: 

  5     Proteas

  6 Date:

  7     2014-03-05

  8 Purpose:

  9     set breakpoint without symbols, for examle: stripped macho

 10 Usage:

 11     add the following line to ~/.lldbinit

 12     command script import ~/.lldb/bt_objc.py

 13 '''

 14 

 15 import lldb

 16 import commands

 17 import shlex

 18 import optparse

 19 import re

 20 

 21 def __lldb_init_module (debugger, dict):

 22     debugger.HandleCommand('command script add -f bt_objc.bt_objc bt_objc')

 23     print 'The "bt_objc" command has been installed'

 24 

 25 def create_command_arguments(command):

 26     return shlex.split(command)

 27     

 28 def is_command_valid(args):

 29     ""

 30     if len(args) == 0:

 31         return False

 32 

 33     arg = args[0]

 34     if len(arg) == 0:

 35         return False

 36 

 37     ret = re.match('^[+-]\[.+ .+\]$', arg) # TODO: more strict

 38     if not ret:

 39         return False

 40 

 41     return True

 42 

 43 def get_class_name(arg):

 44     match = re.search('(?<=\[)[^\[].*[^ ](?= +)', arg) # TODO: more strict

 45     if match:

 46         return match.group(0)

 47     else:

 48         return None

 49 

 50 def get_method_name(arg):

 51     match = re.search('(?<= )[^ ].*[^\]](?=\]+)', arg) # TODO: more strict

 52     if match:

 53         return match.group(0)

 54     else:

 55         return None

 56 

 57 def is_class_method(arg):

 58     if len(arg) == 0:

 59         return False

 60 

 61     if arg[0] == '+':

 62         return True

 63     else:

 64         return False

 65 

 66 def get_selected_frame():

 67     debugger = lldb.debugger

 68     target = debugger.GetSelectedTarget()

 69     process = target.GetProcess()

 70     thread = process.GetSelectedThread()

 71     frame = thread.GetSelectedFrame()

 72 

 73     return frame

 74 

 75 def get_class_method_address(class_name, method_name):

 76     frame = get_selected_frame();

 77     class_addr = frame.EvaluateExpression("(Class)object_getClass((Class)NSClassFromString(@\"%s\"))" % class_name).GetValueAsUnsigned()

 78     if class_addr == 0:

 79         return 0

 80 

 81     sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned()

 82     has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned()

 83     if not has_method:

 84         return 0

 85 

 86     method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr))

 87 

 88     return method_addr.GetValueAsUnsigned()

 89 

 90 def get_instance_method_address(class_name, method_name):

 91     frame = get_selected_frame();

 92     class_addr = frame.EvaluateExpression("(Class)NSClassFromString(@\"%s\")" % class_name).GetValueAsUnsigned()

 93     if class_addr == 0:

 94         return 0

 95 

 96     sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned()

 97     has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned()

 98     if not has_method:

 99         return 0

100 

101     method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr))

102 

103     return method_addr.GetValueAsUnsigned()

104 

105 def bt_objc(debugger, command, result, dict):

106     args = create_command_arguments(command)

107 

108     if not is_command_valid(args):

109         print 'please specify the param, for example: "-[UIView initWithFrame:]"'

110         return

111 

112     arg = args[0]

113     class_name = get_class_name(arg)

114     method_name = get_method_name(arg)

115 

116     address = 0

117     if is_class_method(arg):

118         address = get_class_method_address(class_name, method_name)

119     else:

120         address = get_instance_method_address(class_name, method_name)

121 

122     if address:

123         lldb.debugger.HandleCommand ('breakpoint set --address %x' % address)

124     else:

125         print "fail, please check the arguments"

如上脚本也可以从这个链接下载:https://raw.github.com/Proteas/lldb-scripts/master/bt_objc.py

脚本配置完毕后,可以通过如下命令设置断点:

bt_objc "-[UIView initWithFrame:]"

 

你可能感兴趣的:(ios)