说明:
这里的调试是指使用 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,在调试时动态加载符号。
这个方法我不是第一想到,这个帖子中有详细说明:
d98K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4@1j5h3y4C8L8%4k6W2M7X3k6D9L8%4N6Q4x3X3g2U0L8$3#2Q4x3V1k6I4N6h3g2K6N6r3W2G2L8Y4y4Q4x3V1j5I4y4K6f1#2y4o6l9%4x3q4)9J5c8X3W2E0M7r3!0J5N6q4)9J5k6r3y4D9j5i4y4K6i4K6u0V1k6s2g2E0M7q4)9J5k6r3W2F1k6X3!0Q4x3X3c8A6L8Y4c8G2i4K6u0V1k6$3c8T1
但是照这个方法进行操作后,发现对 iOS 应用没效果,而且过程繁琐。
后来想,ObjC是通过在C语言之上封装了薄薄的一层(消息特性)而形成的,
所有 ObjC 的方法调用最终会转换为 C 方法调用,
因此,可以通过在对应的 C 函数上设置断点来解决断点设置问题,
而如何得到 C 函数的地址,就依赖于 ObjC 的运行时方法了,主要涉及:
1、object_getClass
2、NSSelectorFromString
3、class_respondsToSelector
4、class_getMethodImplementation
在解决了在什么位置设置断点的问题后,
接下来需要解决如果在 lldb 中方便的设置断点。
lldb 集成了 Python 脚本引擎,参考:
9a5K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3I4D9k6r3u0Q4x3X3g2D9L8s2k6E0i4K6u0W2L8%4u0Y4i4K6u0r3M7s2W2@1K9r3!0F1i4K6u0V1M7X3g2X3k6i4u0W2L8X3y4W2i4K6u0W2K9s2c8E0L8l9`.`.
因此我们可以通过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,如果文件不存在则可以自己动手创建
脚本内容:
#!/usr/bin/python
'''
Author:
Proteas
Date:
2014-03-05
Purpose:
set breakpoint without symbols, for examle: stripped macho
Usage:
add the following line to ~/.lldbinit
command script import ~/.lldb/bt_objc.py
'''
import lldb
import commands
import shlex
import optparse
import re
def __lldb_init_module (debugger, dict):
debugger.HandleCommand('command script add -f bt_objc.bt_objc bt_objc')
print 'The "bt_objc" command has been installed'
def create_command_arguments(command):
return shlex.split(command)
def is_command_valid(args):
""
if len(args) == 0:
return False
arg = args[0]
if len(arg) == 0:
return False
ret = re.match('^[+-]\[.+ .+\]$', arg) # TODO: more strict
if not ret:
return False
return True
def get_class_name(arg):
match = re.search('(?<=\[)[^\[].*[^ ](?= +)', arg) # TODO: more strict
if match:
return match.group(0)
else:
return None
def get_method_name(arg):
match = re.search('(?<= )[^ ].*[^\]](?=\]+)', arg) # TODO: more strict
if match:
return match.group(0)
else:
return None
def is_class_method(arg):
if len(arg) == 0:
return False
if arg[0] == '+':
return True
else:
return False
def get_selected_frame():
debugger = lldb.debugger
target = debugger.GetSelectedTarget()
process = target.GetProcess()
thread = process.GetSelectedThread()
frame = thread.GetSelectedFrame()
return frame
def get_class_method_address(class_name, method_name):
frame = get_selected_frame();
class_addr = frame.EvaluateExpression("(Class)object_getClass((Class)NSClassFromString(@\"%s\"))" % class_name).GetValueAsUnsigned()
if class_addr == 0:
return 0
sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned()
has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned()
if not has_method:
return 0
method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr))
return method_addr.GetValueAsUnsigned()
def get_instance_method_address(class_name, method_name):
frame = get_selected_frame();
class_addr = frame.EvaluateExpression("(Class)NSClassFromString(@\"%s\")" % class_name).GetValueAsUnsigned()
if class_addr == 0:
return 0
sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned()
has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned()
if not has_method:
return 0
method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr))
return method_addr.GetValueAsUnsigned()
def bt_objc(debugger, command, result, dict):
args = create_command_arguments(command)
if not is_command_valid(args):
print 'please specify the param, for example: "-[UIView initWithFrame:]"'
return
arg = args[0]
class_name = get_class_name(arg)
method_name = get_method_name(arg)
address = 0
if is_class_method(arg):
address = get_class_method_address(class_name, method_name)
else:
address = get_instance_method_address(class_name, method_name)
if address:
lldb.debugger.HandleCommand ('breakpoint set --address %x' % address)
else:
print "fail, please check the arguments"
如上脚本也可以从这个链接下载:
a79K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5j5i4N6Q4x3X3g2Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6b7M7X3!0@1k6h3q4K6i4K6u0r3L8r3I4V1j5W2)9J5k6s2y4U0M7X3W2H3N6s2y4Q4x3V1k6E0j5i4y4@1k6i4u0Q4x3V1k6T1N6q4)9#2k6X3!0T1K9X3y4Q4x3X3g2H3P5b7`.`.
脚本配置完毕后,可以通过如下命令设置断点:
bt_objc "-[UIView initWithFrame:]"
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课