[原]调试没有符号的 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,如果文件不存在则可以自己动手创建
脚本内容:
#!/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"
如上脚本也可以从这个链接下载:https://raw.github.com/Proteas/lldb-scripts/master/bt_objc.py
脚本配置完毕后,可以通过如下命令设置断点:
bt_objc "-[UIView initWithFrame:]"
[原]调试没有符号的 iOS 应用的更多相关文章
- [原]调试实战——程序CPU占用率飙升,你知道如何快速定位吗?
原调试debugwindbghangprocess explorer 前言 如果我们自己的程序的CPU Usage(CPU占用率)飙升,并且居高不下,很有可能陷入了死循环.你知道怎么快速定位并解决吗? ...
- [原]调试实战——使用windbg调试崩溃在ole32!CStdMarshal::DisconnectSrvIPIDs
原调试debugwindbg崩溃crash 前言 最近程序会不定期崩溃,很是头疼!今晚终于忍无可忍,下决心要干掉它!从之前的几个相关的dump可以猜到是有接口未释放导致的问题,但没有确认到底是哪个接口 ...
- [原]调试实战——使用windbg调试DLL卸载时的死锁
原调试debugwindbg死锁deadlock 前言 最近我们的程序在退出时会卡住,调查发现是在卸载dll时死锁了.大概流程是这样的:我们的dll在加载的时候会创建一个工作线程,在卸载的时候,会设置 ...
- [原]调试实战——使用windbg调试TerminateThread导致的死锁
原调试debugwindbg死锁deadlock 前言 项目里的一个升级程序偶尔会死锁,查看dump后发现是死在了ShellExecuteExW里.经验少,不知道为什么,于是在高端调试论坛里发帖求助, ...
- [原]调试实战——使用windbg调试崩溃在ComFriendlyWaitMtaThreadProc
原调试debugwindbgcrash崩溃COM 前言 这是几年前在项目中遇到的一个崩溃问题,崩溃在了ComFriendlyWaitMtaThreadProc()里,没有源码.耗费了我很大精力,最终通 ...
- [原]调试实战——使用windbg调试excel启动时死锁
原调试debugwindbg死锁deadlock 前言 这是几年前在项目中遇到的一个死锁问题,在博客园发布过.我对之前的笔记进行了整理重新发布于此. 本文假设小伙伴们知道一些基本概念,比如什么是.du ...
- [IOS]从零开始搭建基于Xcode7的IOS开发环境和免开发者帐号真机调试运行第一个IOS程序HelloWorld
首先这篇文章比较长,若想了解Xcode7的免开发者帐号真机调试运行IOS程序的话,直接转到第五部分. 转载请注明原文地址:http://www.cnblogs.com/litou/p/4843772. ...
- iOS——关于创建真机调试证书(发布证书,测试(调试)证书,推送调试证书)、iOS开发者账号申请 请用开发者账号去iTunes connect 查看状态
- iOS开发:创建真机调试证书 分类: ios相关 2015-04-10 10:22 149人阅读 评论(0) 收藏
关于苹果iOS开发,笔者也是从小白过来的,经历过各种困难和坑,其中就有关于开发证书,生产证书,in_house证书,add_Hoc证书申请过程中的问题,以及上架发布问题.今天就着重说一下关于针对于苹果 ...
随机推荐
- kobject_create_and_add
本文来源于源linux 3.14.3版本号/lib/kobject.c文件 /** * kobject_create_and_add - 动态地创建kobject结构和寄存器sysfs * * ...
- vs2012代码段,快捷键,snippet 的使用
这篇还是介绍怎么简单我们编写代码------本想放在上一篇 插件 一起,但是怕搜不到, 大神们就没法给我教更好的方式,所以就另写了一篇 [大家看完后,插件resharp如果能实现这效果,请教 ...
- android如何在代码中设置margin
1.首先看你要设置组件的父容器是什么. 例如,我是FrameLayout其中包括组件.如果调用bottomLayout这是: FrameLayout.LayoutParams lp = new Fra ...
- C# Parse和Convert的区别分析
原文:C# Parse和Convert的区别分析 大家都知道在进行类型转换的时候有连个方法供我们使用就是Convert.to和*.Parse,但是疑问就是什么时候用C 什么时候用P 通俗的解释大家都知 ...
- 通信网Project之——单源单宿最短路问题
最主要的Vertex类: #ifndef VERTEX_H #define VERTEX_H #include <climits> #include <cstddef> #de ...
- C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com
原文:C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | I ...
- 【转】Oracle修改表空间为自动扩展
1.数据文件自动扩展的好处1)不会出现因为没有剩余空间可以利用到数据无法写入2)尽量减少人为的维护3)可以用于重要级别不是很大的数据库中,如测试数据库等 2.数据文件自动扩展的弊端1)如果任其扩大,在 ...
- :link,:visited,:focus,:hover,:active详解
原文::link,:visited,:focus,:hover,:active详解 CSS 又名 层叠样式表,所谓层叠,就是后面的样式会覆盖前面的样式,所以在样式表中,各样式排列的顺序很有讲究. ...
- ArcGIS课程:表面数据转换成矢量数据
虽然TIN (TIN) 和 terrain 数据收集被认为是载体表面.但它们实际上包括基于其他信息元素.并且该信息是在图象点.线或多边形原始格这可能是更实用的公式.在 ArcGIS 在,你可以很容易的 ...
- SQL点滴30—SQL中常用的函数
原文:SQL点滴30-SQL中常用的函数 该文章转载自http://www.cnblogs.com/jiajiayuan/archive/2011/06/16/2082488.html 别人的总结,很 ...