【待校准格式和补充被删除的部分】

【Archive】LLDB - A debugger powered by LLVM

关于LLDB之前有过一次简单的介绍,主要介绍了一些由xcode已经提供了的高效breakpoint设置方法,例如符号断点,objc-exception断点等,也提到了
image等相关概念。

想要学习LLDB,首先要简单了解一下LLVM的结构:

LLVM IR是一个complete code representation。同时是LLVM
Optimizer的唯一接口,所以LLVM的前端这个概念就等价于LLVM IR是什么,LLVM
IR是怎么工作的,以及LLVM IR的规则。

LLVM是一堆库的集合。LLVM被设计为一堆libraries的集合,而不是像GCC一样就是一整个玩意,也不像JVM或者.NET一样不透明,这使我们能够利用丰富的接口创作新的🔧。

LLDB本质上是依附于LLVM的一个工程,使用了LLVM的IR(Clang)作为parser来解析命令,使用LLVM
JIT来转换为target code ,使用LLVM disassembler、LLVM Target来处理calling
convention。

颜色备注:

红色:killer feature

黄色:useful feature

绿色:待评价feature

Frame

frame的概念:

Frame info: 查看当前帧的信息 frame info

Frame recognizer: recognizer是lldb支持外部python API的一个概念。

Frame select: 切换当前栈下的帧 frame select 0

举个例子,我们有个100层嵌套的NSDictionary,每一层的key不尽相同,有一个递归遍历的方法,我们希望停在[key
equalTo:@"1234"]的位置,这个时候我们如果想获得这个key所在的层级,我就可以通过给断点添加frame
select的方法来自动化的递归回去。

Frame variable: 有很多参数,主要是用来格式化字符串等

-s 可以给你标志上这个变量是local,arg,还是global。

-P 当前栈变量的递归打印description的深度

Thread : [th]

Backtrace: 获取某个线程当前的堆栈,也就是一堆frame

thread backtrace 3 获取线程3的堆栈

如果backtrace过长没显示全,或者没捞着啥有用的,可以用 -e 参数试一下

Continue: continue一个指定的线程,如果没有显示指定,默认是所有的线程

Thread continue 1 启动主线程

Thread continue 1 3 启动主线程和3线程

Exception: ...

Info: ...

Jump: 把pc寄存器的值设置到一个新的地址

List: 简要列举各个线程的状态

plan: ...

Return: 从当前帧立刻返回,如果需要返回值,optional yielding

return [-x] -- [<expr>]: 返回一个innermost expression evaluation

return [<expr>]: 返回返回值

Select: 切换当前选中的线程

Step-in: Alias: s

Step-over: Alias:n

Step-inst: instruction level single step. Alias: si

Step-inst-over: stepping over calls. Alias: ni

Step-out: Finish executing the current stack frame and stop after
returning.结束当前栈的调用,在最后一次返回的时候停止。Alias: finish

Step-scripted: 自定义的进行

Until: 直到一个地址或者某一行到达

Expression

Expr的命令就让LLDB像一个交互式命令工具一下,我们可以使用Expr命令Just-In-Time的编译一些代码并执行生效。expr命令的源码

Expr (int) printf ("1234")

Expr unsigned int $foo = 5

Expr -o -- [NSObject new]

Po [NSObject new]

Expr -d 1 -- <expression>

Print the dynamic type of the result of an expresion

Expr -i 0 -- function_with_a_breakpoint()

建议自己做个alias,我的alias是call_withbr

Calling a function so you can stop at a breakpoint in the function.

Expr -- : Alias : Print

Memory

Find:

找到当前进程的某个地址的值

History

给定一个地址,打印出在当前stack
trace中记录过的在这个地址上的所有的alloc和dealloc

Read

读取内存上的值

Region

读取包含某个地址的region信息

Write

直接写内存

Register :

Register read :

Register read --all

Register write :

Register write pc `$pc+8`

Disassemble :

翻译成汇编

Disassemble --frame : 当前栈帧翻译成汇编

之前看大哥们查开启-Oz
编译后frameBuffer内存泄漏问题的时候,应该就是使用这种方法查看的汇编代码,否则整个文件disassemble查找难度较大,信噪比太低。

disassemble --frame --mixed : 汇编和高级语言混合

disassemble --frame --bytes : 把机器码也打印出来

Bugrepot: [bu]

提供详细的当前堆栈的各种信息,比较鸡肋,通常我们调用栈就足够了,bugreport主要是递归展开了【命令:image
show-unwind --address xxxxxxxxxxx】调用栈每一层的汇编代码。

Language: [la]

language objc class-table dump -v
'UIButton'目前只发现这个用来逆向比较有用的,但是系统库的其实可以直接去gg搜,有专门的人dump苹果哈哈哈哈

Process: [pr]

一般我们使用xcode->debug里的attach就可以了

Target :

Target命令主要用来解析符号

对于一个动态库(iOS中的framework,macOS中的dylib),在使用的时候,我们都有一个rebase的过程,但是一个符号在库内的offset是不变的,理论上只要我们给一个动态库rebase到正确的地址,我们就可以拿到crashLog中崩溃栈地址对应的代码位置。

而LLDB为我们提供了根据offset寻找代码位置的能力。

如果我们有一个crashLog,有3个dylib。

crashLog中应该有如下信息:

Binary Images:

0x100000000 - 0x100000ff7 <A866975B-CA1E-3649-98D0-6C5FAA444ECF>
/tmp/a.out

0x7fff83f32000 - 0x7fff83ffefe7 <8CBCF9B9-EBB7-365E-A3FF-2F3850763C6B>
/usr/lib/system/libsystem_c.dylib

0x7fff883db000 - 0x7fff883e3ff7 <62AA0B84-188A-348B-8F9E-3E2DB08DB93C>
/usr/lib/system/libsystem_dnssd.dylib

0x7fff8c0dc000 - 0x7fff8c0f7ff7 <C0535565-35D1-31A7-A744-63D9F10F12A4>
/usr/lib/system/libsystem_kernel.dylib

libsystem_c.dylib的base是0x7fff83f32000,其他同理。

那我们如何找到0x10000ff7对应的代码位置呢?

(lldb) target create --no-dependents --arch x86_64 /tmp/a.out

(lldb) target modules add /usr/lib/system/libsystem_c.dylib

(lldb) target modules add /usr/lib/system/libsystem_dnssd.dylib

(lldb) target modules add /usr/lib/system/libsystem_kernel.dylib

先用target创建 a.out,把这三个动态库add进去

(lldb) target modules load --file a.out 0x100000000

(lldb) target modules load --file libsystem_c.dylib 0x7fff83f32000

(lldb) target modules load --file libsystem_dnssd.dylib 0x7fff883db000

(lldb) target modules load --file libsystem_kernel.dylib 0x7fff8c0dc000

然后用target modules load
这个命令,假装load一下我们的主二进制和dylib,模拟一下程序执行前rebase的过程。

(lldb) image lookup -a 0x00007fff8a1e6d46

(lldb) image lookup -a 0x00007fff84597df0

(lldb) image lookup -a 0x00007fff84598e2a

(lldb) image lookup -a 0x0000000100000f46

大功告成。

这里附送一个链接,理解使用dsym来解析crash堆栈。

Watchpoint

对地址生效的断点

这个玩意是真滴骚,zombie detector我觉得也是用的类似的方法

对地址生效目前我理解有可以做两件事:

检测你调的系统API/堆栈变量操作到底生效没有,见不是特别恰当的例子1

例子1:

//设置系统StatusBar的代码

//但是我遇到了设置完也没用的场景

//进一下汇编


//看到这里有一个testb和jne

//不出所料,在这里直接跳走了,啥也没干

//如果不看汇编

//如果使用的是某个库,我们能捞到某个变量的地址,但是看不到源码,只有一个API,但是出现了不符合预期的结果,我觉得比较有效的一个手段就是

watchpoint set expression 0xabcdef123 //最后跟的是某个系统地址

//通过watch这个地址并不需要进汇编查看汇编代码,如果修改了这个值,那么一定会被这个watchpoint观察到

例子2:

NSArray<MyModel *> *myModels = [NSArray
arrayWithDictionarys:data];

//用后端返回来的dicts生成一个数组,里面元素都是我们的model

//....….

//用一堆二进制了的库干了一堆事or调了别人的API以后

//myModels没了.........

如果你不想一层层跑过去看是谁把你的models给干了,一个简单有效的方式是给myModel这个地址加一个watchpoint~

Breakpoint: [br]

对方法生效的断点。

Breakpoint set --name main: Alias: br s -n main

--file test.c --line 12 : Alias: br s -f test.c -l 12

--method main: Alias: br s -m main。仅用于C++方法。

--name "[NSString stringWithFormat:]" 用名字设置符号断点

--selector <selector-name> 用selector设置符号断点

--condition: 设置断点条件

-E <source-language>: -E objc 设置objc的exception,objc可以换成c什么的

-F <full-name>: C++下就是nameSpace+所有参数,objc就是a full function
prototype with class and selector。

-t <thread-id> : The breakpoint stops only for the thread whose TID
matches this argument.

-s <shlib-name> : 只在指定的动态库上做这个符号断点

-r <regular-expression> : 使用正则打断点:

Br s -r cellforitem

Breakpoint command :

Add <index of breakpoint> : breakpoint add 2
表示给2号断点开始增加stop时的指令

Breakpoint modify:

-G : auto continue

-T <thread-name> : breakpoint stops only for the thread whose name
matches thread-name

-q <queue-name> : The breakpoint stops only for threads in the queue
whose name is given by this argument.

Image : [im]

Image list :列出所有的主执行程序和依赖的共享库

Image lookup --address : 根据一个地址查询信息。

Image lookup -r -n <runc_regex> : 根据正则查询debug symbols

Image lookup -r -s <runc_regex> : 根据正则查询非debug symbols

Image lookup --type <some type> : 使用某种类型名字查询相关信息

(lldb) im loo --type xxCell

Best match found in
/Users/folobe/Library/Developer/Xcode/DerivedData/xxxxxx

Image dump section <module-name> : dump某个模块

Image dump symtab : dump all symbols from main executable and all shared
libraries.

Command :

Command alias :
创建alias,如果想持久化这个alias,就像zsh命令一样,在~下面创建一个.lldbinit,在里面写上command
alias alias-name <raw-input>

Chisel plugin :

Slowanim <mutiple factor>
全局加速或者减速动画,对序列帧lottie同样生效

Alias for : expr [(CALayer *)[[[[UIApplication sharedApplication]
windows] objectAtIndex:0] layer] setSpeed:(CGFloat)%s]

Visualize : 使用一个地址渲染出图片,相当于xcode中的preview

Visualize (CALayer *)

Visualize (UIImage *)

Visualize (UIView *)

lldb使用CGRect等结构体

_tvFeed setFrame:(CGRect){0, 0, 320, 300}

ToTOP