0x01 LLDB
Xcode 5发布之后,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器。它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。LLDB为Xcode提供了底层调试环境,其中包括内嵌在Xcode IDE中的位于调试区域的控制面板,在这里我们可以直接调用LLDB命令。
基础语法
[ [...]] [-options [option-value]] [argument [argument...]]
-
command
和subcommand
:LLDB调试命令的名称。 -
action
:执行命令的操作。 -
options
:命令的选项。 -
arguement
:命令的参数。 -
[]
:表示命令是可选的。可有可无。
例:
breakpoint set -n test
command
: breakpoint
表示断点命令
action
: set
表示设置断点
option
: -n
表示根据方法name设置断点
arguement
: test
表示方法名为test
基础命令
- 通过
help
命令查看相关lldb
指令描述
(lldb) help
Debugger commands:
apropos -- List debugger commands related to a word or subject.
breakpoint -- Commands for operating on breakpoints (see 'help b' for
shorthand.)
bugreport -- Commands for creating domain-specific bug reports.
command -- Commands for managing custom LLDB commands.
disassemble -- Disassemble specified instructions in the current
target. Defaults to the current function for the
current thread and stack frame.
expression -- Evaluate an expression on the current thread. Displays
any returned value with LLDB's default formatting.
frame -- Commands for selecting and examing the current thread's
stack frames.
- 通过
apropos
命令获取具体命令的合法参数及含义
(lldb) apropos bt
The following commands may relate to 'bt':
_regexp-bt -- Show the current thread's call stack. Any numeric argument
displays at most that many frames. The argument 'all' displays
all threads.
bt -- Show the current thread's call stack. Any numeric argument
displays at most that many frames. The argument 'all' displays
all threads.
- 一些小指令
c
/continue
: 继续执行
po
:expression命令选项:-O 表示调用对象的discraption方法
up
: 往上走。
n
:单步往下走。将子函数当做整体一步执行。
s
: 单步运行,遇到子函数会进去。
ni
: 单步执行会跳转到指令内部,汇编级别。
finish
:返回上层调用栈。
bt
: 查看当前调用的堆栈信息。
thread return
:回滚。会改变代码执行流程。
frame select 3
:查看某个堆栈信息。
image list
:查看某块列表。
register read
:读取寄存器。
register write
:写入寄存器。
Memory read
:读取内存值。
frame variable
:可以打印当前方法的所有参数变量。frame
表示帧。
(lldb) frame select 3
frame #3: 0x000000010af838e8 UIKitCore`forwardTouchMethod + 353
UIKitCore`forwardTouchMethod:
0x10af838e8 <+353>: movq -0x68(%rbp), %rdi
0x10af838ec <+357>: movq 0x865bc5(%rip), %rbx ; (void *)0x00000001076a0010: objc_release
0x10af838f3 <+364>: callq *%rbx
0x10af838f5 <+366>: movq -0x70(%rbp), %rdi
断点相关
- 通过函数名设置断点
(lldb) breakpoint set -name action1
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.
(lldb) br list
Current breakpoints:
1: name = 'action1', locations = 0 (pending)
- 设置
OC
方法断点。
breakpoint set -n "[ViewController action1:]"
可同时设置多个。就可以同时启用或者禁用某一组。
(lldb)breakpoint set -n "[ViewController action1:]" -n "[ViewController action2:]" -n "[ViewController action3:]
(lldb) br list
Current breakpoints:
1: names = {'[ViewController action1]', '[ViewController action1]', '[ViewController action2]', '[ViewController action2]', '[ViewController action3]', '[ViewController action3]'}, locations = 3, resolved = 3, hit count = 0
1.1: where = LLDB`-[ViewController action1] + 12 at ViewController.m:42, address = 0x000000010a48f60c, resolved, hit count = 0
1.2: where = LLDB`-[ViewController action2] + 12 at ViewController.m:45, address = 0x000000010a48f61c, resolved, hit count = 0
1.3: where = LLDB`-[ViewController action3] + 12 at ViewController.m:48, address = 0x000000010a48f62c, resolved, hit count = 0
- 禁用或者启用某一组。(也可以禁用1.1)
breakpoint disable 1
breakpoint enable 1
- 删除某一组。(只能以组为单位的删除)
breakpoint delete 1
下面这样只能达到禁用效果
breakpoint delete 1.1
- 给项目中所有某个方法设置断点,适合自定义方法。
breakpoint set --selector viewDidLoad
- 给某个文件或者某个方法设置断点
br set --file ViewController.m --selector action1:
- 给系统所有带有某个字符串的方法设置断点。
br set -r xxx
- 删除断点
breakpoint delete
br delete
- 内存断点。类似于
KVO
,可以监听某块内存区域值的变化。
//监听p1对象中的name字段的内存区域。
watchpoint set variable p1->_name
也可以直接表达为具体的地址。
p &p1->_name
watchpoint set expression 0x0000600002d21c68
进阶用法
-
command
指令。通过指令给断点处添加额外指令的实现。
(lldb) breakpoint command add 1
Enter your debugger command(s). Type 'DONE' to end.
> frame variable
> DONE
当截住断点时,便会打印相关的指令。
-
hook
指令。同上,只是针对每个断点生效。每次截住断点,打印配置好的指令。这里指令一般是通用命令。
target stop-hook add -o "frame variable"
补充:
lldb
有一个初始化文件.lldbinit
,通过在文件中配置相关指令,使用lldb
截住的断点都可以执行相关指令。
操作步骤:
1、vim ~/.lldbinit
,进入配置文件。
2、进入编辑模式,将target stop-hook add -o "frame variable"
命令输入其中。保存即可生效。
3、在Xcode
代码中设置断点,截住时便会打印frame variable
的方法参数信息。
0x02 chisel
chisel
是llbd
官方的api,用脚本实现了一些特定的功能。
安装
通过Homebrew进行安装
brew update
brew install chisel
vim ~/.lldbinit
修改lldb
初始化文件,将chisel
的脚本文件的替身地址加入其中
command script import /usr/local/Cellar/chisel/1.8.0/libexec/fblldb.py
重启Xcode,即可使用。
常用命令概览
- 打印视图层级
(lldb) pviews 0x170391b90
>
| >
- 打印视图上级
pviews -u 0x17438fe50
- 打印整个界面的层级关系
pviews
- 查看响应者链
presponder 0x150867780
- 查看按钮绑定的事件
(lldb) pactions 0x150867780
: onFirstViewRegester
- 查看按钮的继承关系
(lldb) pclass 0x150867780
FixTitleColorButton
| UIButton
| | UIControl
| | | UIView
| | | | UIResponder
| | | | | NSObject
- 列出某个对象的方法列表
(lldb) pmethods 0x14f787f20
Class Methods:
No methods were found
- 打印对象的所有属性及属性值。
pinternals 0x136a11200
- 根据控制器的名称找到这个名称当前所有的地址。
(lldb) fvc WCAccountMainLoginViewController
0x136a11200 WCAccountMainLoginViewController
//根据地址显示控制器信息。
(lldb) fvc -v 0x136a11200
Found the owning view controller.
- 快速定位到某个组件。
taplog
后continue
,点击页面的某个控件,便会自动打印控件的相关信息。
(lldb) taplog
Process 2388 resuming
; layer = >
- 闪烁一次控件,方便开发者定位控件。
flicker 0x1378b2880
- 动态查看控件相关信息.
enter
之后,输入相关的指令可查看对应的一些控件信息,并且会给控件更改backgroundColor
来标注控件。
(lldb) vs 0x1378b2880
Use the following and (q) to quit.
(w) move to superview 找到父控件。
(s) move to first subview 找到第一个subview
(a) move to previous sibling 向前移动
(d) move to next sibling 向后移动。
(p) print the hierarchy 打印层级
0x03 DerekSelander
安装
点击DerekSelander下载脚本文件。通过在.lldbinit
中完成配置即可使用。
常见命令
- 查找
UIImageView
的对象。
search UIImageView
<_UINavigationBarBackIndicatorView: 0x1703e0d00; frame = (8 11.5; 13 21); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = >
>
>
>
- 列出某个类所有的方法和属性。(方法后面的地址为
imp
的地址。)
(lldb) methods 0x174897200
:
in WCAccountLoginControlLogic:
Properties:
@property (readonly) unsigned long hash;
@property (readonly) Class superclass;
@property (readonly, copy) NSString* description;
@property (readonly, copy) NSString* debugDescription;
Instance Methods:
- (void) startLogic; (0x102565bdc)
- (void) stopLogic; (0x102566144)
- (void) onOneClickLoginSwitchAccount; (0x1025669d4)
- (void) onOneClickLoginProblem; (0x102566a08)
- (void) onOneClickLoginGoToSecurityCenter; (0x102566a40)
- (void) onOneClickLoginGoToHelpCenter; (0x102566a78)
- 通过内存地址给方法下断点。
(lldb) b -a 0x1025662c0
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol142407$$WeChat, address = 0x00000001025662c0
- 通过
sbt
查看函数调用栈、并得到去符号后的信息。
(lldb)sbt
frame #0 : 0x1025662c0 WeChat`-[WCAccountLoginControlLogic onFirstViewLogin]
frame #1 : 0x187970d34 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 96
frame #2 : 0x1030de4e0 WeChat`___lldb_unnamed_symbol189001$$WeChat ... unresolved womp womp + 460
frame #3 : 0x187959e48 UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 612
0x04 cycript
cycript
是一个允许开发者使用OC
和JS
结合语法查看及修改运行时App内存信息的工具,通过它我们可以注入程序、查看程序界面、调用程序函数验证自己的想法等。同lldb
不同、当开始使用cycript
时它会常驻内存,所以也更方便我们去动态调试。
配置安装
1、在~/.zshrc
文件中链接bash_profile
.
source .bash_profile
2、在官网中下载SDK文件。在bash_profile
中配置dycript
。
3、安装完成,通过cycript
命令即可进入cycript
界面了。
界面调试
通过MonkeyDev运行重签应用,通过查看自动生成的动态库文件,我们可得到对应的端口号。
查看手机IP,通过cycript -r 192.168.0.105:6666
连接手机。当出现如下界面,便可以自由调试了。在这里你可以编写OC
和JS
语言、达到对应用程序的调试。
下面展示一些简单的调试命令:
- 打印当前
UIApplication
的信息。
cy# [UIApplication sharedApplication]
#""
cy# UIApp
#""
- 定义一个变量
keywindow
来保存当前的window
,接着便可以通过变量keywindow
直接打印有关window
的属性。
cy# var keyWindow = UIWindow.keyWindow()
#"; layer = >"
cy# keyWindow.rootViewController
#""
- 通过
#
,查看内存地址的详细信息。
cy# #0x10c165e00
#""
- 拿到某个对象的成员变量的key和value
cy# *#0x10c165e00
只获取key
[i for(i in *#0x157f64480)]
- 查看某个类的层级关系;
#0x157f64480.recursiveDescription()
格式化输出一波:
#0x157f64480.recursiveDescription().toString()
- 查看当前界面所有为某个类的对象
cy# choose(UIButton)
[#">",#">",#">"]
- 动态修改某个控件的值
#0x10b1caa00.text="123123"
"123123"
高级用法:
因为我们使用的monkeyDev
自动链接了两个cy
文件,在文件中实现了对某些方法的自定义函数名以方便我们快捷log。
猴神配置的
cy
文件的链接:
MS.cy
md.cy
下面简单列举以下文件提到的一些命令:
- 打印视图层级和控制器层级。
pviews()
pvcs
- 打印对象所绑定的方法。
cy# pactions(#0x1053163b0)
" showChangeLog:"
- 打印响应链
cy# rp(#0x1053163b0)
`>
>
; layer = >
`
自定义cy
文件,实现自定义命令
通过借鉴猴神的自定义命令的实现,我们可以完成一些常规命令的自定义,方便使用。
1、新建名为dyTest.cy
文件。编写相关命令。
2、定义指令名和方法实现。
//匿名函数自执行表达式
(function(exports){
//这种直接赋值的变量定义适用于不会发生值改变的情况。因为这样在程序加载的时候变量的值就固定了。
APPID = [NSBundle mainBundle].bundleIdentifier,
APPPATH = [NSBundle mainBundle].bundlePath,
//如果值有变化,可借鉴于函数实现的方式。
DyRootvc = function(){
return UIApp.keyWindow.rootViewController;
};
DyKeyWindow = function(){
return UIApp.keyWindow;
};
DyGetCurrentVCFromRootVc = function(rootVC){
var currentVC;
if([rootVC presentedViewController]){
rootVC = [rootVC presentedViewController];
}
if([rootVC isKindOfClass:[UITabBarController class]]){
currentVC = DyGetCurrentVCFromRootVc(rootVC.selectedViewController);
}else if([rootVC isKindOfClass:[UINavigationController class]]){
currentVC = DyGetCurrentVCFromRootVc(rootVC.visibleViewController);
}else{
currentVC = rootVC;
}
return currentVC;
};
DyCurrentVC = function(){
return DyGetCurrentVCFromRootVc(DyRootvc());
};
})(exports);
//匿名函数自执行表达式
3、copy file
文件,保证其运行时能在Frameworks
文件夹中。
4、实践自定义命令。
cy# @import dyTest
{}
cy# APPID
@"com.WeChat.signXcode.dyTest"
cy# APPPATH
@"/private/var/mobile/Containers/Bundle/Application/D4AA77EB-FC58-4189-84CF-114A9EA2BB1E/dyTest.app"
cy# DyRootvc()
#""
cy# DyKeyWindow()
#"; layer = >"
cy# DyGetCurrentVCFromRootVc(#0x14dfd9730)
#""
cy# DyCurrentVC()
#""
参考链接
The LLDB Debugger
chisel-github
MonkeyDev
cycript
DerekSelander