前言
LLDB是搭配LLVM的一个调试工具,就如同GDB之于GCC。我们日常开发中几乎时时刻刻和它在打交道。下面简单地从多个方面来了解以及使用LLDB:
- 1、BreakPoint和WatchPoint;
- 2、程序控制;
- 3、线程状态和栈帧状态;
其中BreakPoint也就是我们常说的断点,它是我们日常调试中使用过最多的能力,其作用自不必多言;WatchPoint相对来说用的就很少了,我们可以称之为“观察点”。我们可以通过添加观察点来关注我们指定变量的变化。
程序控制就是指的在我们调试过程中的单步执行、继续执行、进入子过程内部以及从子过程内部跳出。
线程状态和栈帧状态则是查看当前线程的执行状态,包括过程调用、变量信息以及寄存器状态等等。
下面提及的所有命令,如果不清楚如何使用都可以使用 help xxx 形式来进行查询:
help breakpoint
help watchpoint
help memory
...
程序启动和控制相关
程序启动大致可以分为两种,第一种是在启动lldb的时候指定要debug的程序;第二种是在启动lldb之后,再明确指定要启动的程序:
/// 第一种启动lldb的时候指定待调试的程序
➜ lldb lldb_demo
/// 第二种先启动lldb,然后指定要启动的调试程序
➜ lldb
(lldb) run /Users/.../db_demo 1 2 3
/// 第三种创建不同的target,在lldb里面随意的启动
➜ lldb
(lldb) target create /Users/xxx/Library/Developer/Xcode/DerivedData/lldb_demo-bcdczbxhxshevwakdquvexpupggc/Build/Products/Debug/lldb_demo
Current executable set to '/Users/xxx/Library/Developer/Xcode/DerivedData/lldb_demo-bcdczbxhxshevwakdquvexpupggc/Build/Products/Debug/lldb_demo' (x86_64).
(lldb) target list
Current targets:
* target #0: /Users/xxx/Library/Developer/Xcode/DerivedData/lldb_demo-bcdczbxhxshevwakdquvexpupggc/Build/Products/Debug/lldb_demo ( arch=x86_64-apple-macosx11.5.0, platform=host )
(lldb) r 0
Process 30082 launched: '/Users/xxx/Library/Developer/Xcode/DerivedData/lldb_demo-bcdczbxhxshevwakdquvexpupggc/Build/Products/Debug/lldb_demo' (x86_64)
一个小提示:我们在target create的时候需要知道某一个路径的话,就需要lldb里面执行shell相关的指令。可以使用如下方式:
platform shell xxxx。当然也可以使用command alias为platform shell取一个别名。
下表中的内容简单的列举了一下相关的指令:
类别 | 描述 | GDB | LLDB | 备注 |
---|---|---|---|---|
程序启动 | 启动指定进程 | run/r <args> xxx | process launch -- <args>、run/r <args> xxx | args启动过程中需要的参数,对应main函数中的参数 |
展示传入的参数 | show args | settings show target.run-args | 展示我们设置的参数 | |
设置环境变量 | set env DEBUG 1 | env DEBUG=1 | 设置环境变量 | |
程序控制 | 源码级别进入执行 | step/s | thread step-in | 单步执行到指定过程的内部(比如子函数调用时,进入子函数内部) |
指令级别进入执行 | stepi/si | thread step-inst | ||
源码级别跳过执行 | next/n | thread step-over | ||
指令级别跳过执行 | nexti/ni | hread step-inst-over | ||
结束执行 | finish | thread step-out |
///####################################### 展示传入的参数
(lldb) settings show target.run-args
target.run-args (array of strings) =
[0]: "/Users/xxx/Library/Developer/Xcode/DerivedData/lldb_demo-bcdczbxhxshevwakdquvexpupggc/Build/Products/Debug/lldb_demo"
[1]: "1"
[2]: "2"
[3]: "3"
BreakPoint和WatchPoint
下面列出了我们调试过程中需要设置断点和观察点的方法,以及编辑断点和观察点的命令。
类别 | 描述 | GDB | LLDB | 备注 |
---|---|---|---|---|
断点相关 | 基于函数名称设置断点 | break main | breakpoint set --name main | |
基于代码文件以及某一行设置断点 | break Controller.cpp:11 | breakpoint set --file Controller.cpp --line 11 | ||
基于方法名设置断点 | breakpoint set --method pfb_lldb::Controller::increase | |||
基于Objective C相关断点设置 | breakpoint set --name "-[Model setController:]"\breakpoint set --selector count | |||
获取当前设置的断点信息 | info break | breakpoint list | ||
删除指定断点 | delete 1 | breakpoint delete 1 | 这里命令后面带的数字,即某一断点在breakpoint list里面的序号 | |
使指定断点失效 | disable 1 | breakpoint disable 1 | ||
使指定断点生效 | enable 1 | breakpoint enable 1 | ||
观察点相关 | 设置观察点 | watch ctlptr->len | watchpoint set variable ctlptr->len | 这里命令后面带的数字,即某一观察点在watchpoint list里面的序号 |
查看所有观察点 | info break | watchpoint list | ||
删除观察点 | delete 1 | watchpoint delete 1 |
///####################################### 基于C++方法设置断点
(lldb) breakpoint set --method pfb_lldb::Controller::increase
///####################################### 获取当前所有的断点
(lldb) breakpoint list
Current breakpoints:
1: name = '-[Model setController:]', locations = 1
1.1: where = lldb_demo`-[Model setController:] + 20 at Model.mm:14:5, address = lldb_demo[0x0000000100003de4], unresolved, hit count = 0
2: name = 'main', locations = 10
2.1: where = lldb_demo`main + 25 at main.mm:14:22, address = lldb_demo[0x0000000100003b49], unresolved, hit count = 0
2.2: where = Foundation`-[NSBlockOperation main], address = Foundation[0x00007fff21177335], unresolved, hit count = 0
2.3: where = Foundation`-[NSThread main], address = Foundation[0x00007fff2118e53d], unresolved, hit count = 0
2.4: where = Foundation`-[NSFilesystemItemRemoveOperation main], address = Foundation[0x00007fff2118f067], unresolved, hit count = 0
2.5: where = Foundation`-[NSInvocationOperation main], address = Foundation[0x00007fff211a34a8], unresolved, hit count = 0
2.6: where = Foundation`-[NSOperation main], address = Foundation[0x00007fff211a3c97], unresolved, hit count = 0
2.7: where = Foundation`-[NSFilesystemItemMoveOperation main], address = Foundation[0x00007fff211e2a9f], unresolved, hit count = 0
2.8: where = Foundation`-[NSDirectoryTraversalOperation main], address = Foundation[0x00007fff2122aa96], unresolved, hit count = 0
2.9: where = Foundation`-[_NSBarrierOperation main], address = Foundation[0x00007fff212ef78b], unresolved, hit count = 0
2.10: where = Security`Security::OSXCode::main(), address = Security[0x00007fff22450154], unresolved, hit count = 0
3: name = 'pfb_lldb::Controller::increase', locations = 1
3.1: where = lldb_demo`pfb_lldb::Controller::increase() + 12 at Controller.cpp:11:8, address = lldb_demo[0x0000000100003d9c], unresolved, hit count = 0
///####################################### 设置观察点
(lldb) watchpoint set variable ctlptr->len
Watchpoint created: Watchpoint 1: addr = 0x100704080 size = 4 state = enabled type = w
declare @ '/Users/xxx/WorkStation/iOS/exec/lldb_demo/lldb_demo/main.mm:17'
watchpoint spec = 'ctlptr->len'
new value: 0
变量和表达式执行
使用expr我们可以在debug的时候去执行相关的表达式,以达到运行期间修改的目的。比如在运行时我们想要修改某一个View的背景色,那么我们就可以使用expr来达到这个目的,而不需要重新编译执行。
类别 | 描述 | GDB | LLDB | 备注 |
---|---|---|---|---|
变量 | 查看当前帧变量 | info args | frame variable | |
展示局部变量 | info locals | frame variable --no-args | 局部变量,即非外部传入的变量 | |
打印指定变量 | p bar | p bar | ||
执行表达式 | print (int) printf ("Print nine: %d.", 4 + 5) | expr (int) printf ("Print nine: %d.", 4 + 5) | ||
打印OC相关的对象 | po [SomeClass returnAnObject] | po [SomeClass returnAnObject]\expr -o -- [SomeClass returnAnObject] |
///####################################### 查看当前变量
(lldb) frame variable
(int) argc = 1
(const char **) argv = 0x00007ffeefbff4d0
(NSRunLoop *) loop = 0x0000000100405d80
(pfb_lldb::Controller *) ctlptr = 0x0000000100704080
(Model *) mdl = nil
///####################################### 查看当前局部变量
(lldb) frame variable --no-args
(NSRunLoop *) loop = 0x0000000100405d80
(pfb_lldb::Controller *) ctlptr = 0x0000000100704080
(Model *) mdl = nil
///####################################### 执行表达式相关
(lldb) expr ctlptr->len = 10
(int) $0 = 10
(lldb) expr ctlptr->len++
(int) $1 = 10
(lldb) po ctlptr->len
11
(lldb) expr -o -- ctlptr->len
11
线程状态
其中bt是我们使用的最多的一个命令,它可以打印出当前线程的调用堆栈。
类别 | 描述 | GDB | LLDB | 备注 |
---|---|---|---|---|
Thread State | 列举程序中的线程信息 | info threads | thread list | |
选中某个线程 | thread 1 | thread select 1 | 序号为thread list中的编号 | |
当前线程调用栈 | bt | thread backtrace | ||
所有线程调用栈 | thread apply all bt | bt all | ||
当前线程调用栈前2帧 | bt 2 | thread backtrace -c 2 | ||
选中某第1帧 | frame 1 | frame select 1 |
(lldb) thread list
Process 50356 stopped
* thread #1: tid = 0x50aac7, 0x0000000100003c1b lldb_demo`main(argc=1, argv=0x00007ffeefbff4d0) at main.mm:18:9, queue = 'com.apple.main-thread', stop reason = step over
thread #2: tid = 0x50afa5, 0x00007fff2050f420 libsystem_pthread.dylib`start_wqthread
thread #3: tid = 0x50e012, 0x00007fff22d7fcd2 libsystem_notify.dylib`___lldb_unnamed_symbol29$$libsystem_notify.dylib, queue = 'com.apple.root.user-initiated-qos'
///####################################### 调用堆栈
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
* frame #0: 0x0000000100003d9c lldb_demo`pfb_lldb::Controller::increase(this=0x0000000100704080) at Controller.cpp:11:8
frame #1: 0x0000000100003c24 lldb_demo`main(argc=1, argv=0x00007ffeefbff4d0) at main.mm:18:17
frame #2: 0x00007fff2052ef3d libdyld.dylib`start + 1
///####################################### 调用堆栈前2帧
(lldb) bt 2
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
* frame #0: 0x0000000100003d9c lldb_demo`pfb_lldb::Controller::increase(this=0x0000000100704080) at Controller.cpp:11:8
frame #1: 0x0000000100003c24 lldb_demo`main(argc=1, argv=0x00007ffeefbff4d0) at main.mm:18:17
/// 选中指定帧
(lldb) frame select 1
frame #1: 0x0000000100003c24 lldb_demo`main(argc=1, argv=0x00007ffeefbff4d0) at main.mm:18:17
15 NSRunLoop *loop = [NSRunLoop currentRunLoop];
16 [loop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
17 Controller *ctlptr = new Controller();
-> 18 ctlptr->increase();
19 Model *mdl = [Model new];
20 [mdl setController:ctlptr];
21 delete ctlptr;
内存和寄存器
当我们发生了bad access/类型异常等等错误,为了定位到底发生了什么我们就需要去查看对应地址上的内存值。
类别 | 描述 | GDB | LLDB | 备注 |
---|---|---|---|---|
内存相关 | 读取指定地址的内存值 | x/10xw 0x0000000100704080 | x/10xw 0x0000000100704080 |
上表中和内存读取相关的指令格式中存在一个紧跟着反斜杠的10bw内容,这里我简单解释一下他们的作用:
- N(Number):需要读取的个数,比如这里是10;
- F(Format):我们读取的格式,比如这里的b。它和我们平时使用printf格式一样:‘x’(十六进制), ‘d’, ‘u’, ‘o’, ‘t’, ‘a’, ‘c’, ‘f’, ‘s’, ‘m’;
- U(Unit):上面提到的个数,那么U就是决定每个的展示内容的大小:
b Bytes:字节
h Halfwords (2 bytes):半字
w Words (4 bytes):字
g Giant words (8 bytes):巨字
(lldb) frame variable --no-args
(NSRunLoop *) loop = 0x0000000100405d80
(pfb_lldb::Controller *) ctlptr = 0x0000000100704080
(Model *) mdl = nil
(lldb) x/10xw 0x0000000100704080
0x100704080: 0x0000000b 0x00000000 0x00000000 0x00000000
0x100704090: 0x00000000 0x00000000 0x00000000 0x00000000
0x1007040a0: 0x00000000 0x00000000
在进行DEBUG的时候获取寄存器的值,对我们定位问题有着至关重要的作用。对于通用目的寄存器来说,排查问题比较大的帮助的有bp/fp、sp用于确定当前栈的区域;ip/pc用于确定当前执行指令;lr寄存器保存函数调用返回地址等等。
类别 | 描述 | GDB | LLDB | 备注 |
---|---|---|---|---|
寄存器相关 | 展示当前线程通用目的寄存器 | info registers | register read | |
展示所有寄存器的值 | info all-registers | register read --all | ||
修改指定寄存器的值 | register write rax 123 |
(lldb) register read
General Purpose Registers:
rbx = 0x0000000000000000
rbp = 0x00007ffeefbff4b0
rsp = 0x00007ffeefbff430
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100003c24 lldb_demo`main + 244 at main.mm:18:17
13 registers were unavailable.
(lldb) register read --all
General Purpose Registers:
rbx = 0x0000000000000000
rbp = 0x00007ffeefbff4b0
rsp = 0x00007ffeefbff430
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100003c24 lldb_demo`main + 244 at main.mm:18:17
ebx = 0x00000000
ebp = 0xefbff420
esp = 0xefbff420
sp = 0xf420
61 registers were unavailable.
Floating Point Registers:
50 registers were unavailable.
Exception State Registers:
3 registers were unavailable.
可执行文件和共享库相关查询
我们在调试的时候用到image的场景不是特别常见,它主要用于调试的时候查看有哪些镜像、以及这些镜像的起始地址和结束地址。用于对比发生异常之后指令是位于哪个镜像。
类别 | 描述 | GDB | LLDB |
---|---|---|---|
可执行文件和共享库 | 展示当前可执行文件以及所依赖的所有库 | info shared | image list |
查找指定地址相关信息 | info symbol 0x1ec4 | image lookup --address 0x1ec4 | |
dump所有的sections | maintenance info sections | image dump sections | |
dump指定image的sections | image dump sections a.out | ||
dump指定image的符号表 | image dump symtab lldb_demo |
(lldb) image lookup --address 0x0000000100003b30
Address: lldb_demo[0x0000000100003b30] (lldb_demo.__TEXT.__text + 0)
Summary: lldb_demo`main at main.mm:12
(lldb) image dump section lldb_demo
Sections for '/Users/wangwang/Library/Developer/Xcode/DerivedData/lldb_demo-bcdczbxhxshevwakdquvexpupggc/Build/Products/Debug/lldb_demo' (x86_64):
SectID Type Load Address Perm File Off. File Size Flags Section Name
---------- ---------------- --------------------------------------- ---- ---------- ---------- ---------- ----------------------------
0x00000100 container [0x0000000000000000-0x0000000100000000)* --- 0x00000000 0x00000000 0x00000000 lldb_demo.__PAGEZERO
0x00000200 container [0x0000000100000000-0x0000000100004000) r-x 0x00000000 0x00004000 0x00000000 lldb_demo.__TEXT
0x00000001 code [0x0000000100003b30-0x0000000100003e3e) r-x 0x00003b30 0x0000030e 0x80000400 lldb_demo.__TEXT.__text
0x00000002 code [0x0000000100003e3e-0x0000000100003e74) r-x 0x00003e3e 0x00000036 0x80000408 lldb_demo.__TEXT.__stubs
0x00000003 code [0x0000000100003e74-0x0000000100003eca) r-x 0x00003e74 0x00000056 0x80000400 lldb_demo.__TEXT.__stub_helper
0x00000004 regular [0x0000000100003ecc-0x0000000100003f08) r-x 0x00003ecc 0x0000003c 0x00000000 lldb_demo.__TEXT.__gcc_except_tab
0x00000005 data-cstr [0x0000000100003f08-0x0000000100003f5c) r-x 0x00003f08 0x00000054 0x00000002 lldb_demo.__TEXT.__objc_methname
0x00000006 data-cstr [0x0000000100003f5c-0x0000000100003f61) r-x 0x00003f5c 0x00000005 0x00000002 lldb_demo.__TEXT.__cstring
0x00000007 data-cstr [0x0000000100003f61-0x0000000100003f67) r-x 0x00003f61 0x00000006 0x00000002 lldb_demo.__TEXT.__objc_classname
0x00000008 data-cstr [0x0000000100003f67-0x0000000100003f95) r-x 0x00003f67 0x0000002e 0x00000002 lldb_demo.__TEXT.__objc_methtype
0x00000009 compact-unwind [0x0000000100003f98-0x0000000100003ff4) r-x 0x00003f98 0x0000005c 0x00000000 lldb_demo.__TEXT.__unwind_info
0x00000300 container [0x0000000100004000-0x0000000100008000) rw- 0x00004000 0x00004000 0x00000010 lldb_demo.__DATA_CONST
0x0000000a data-ptrs [0x0000000100004000-0x0000000100004028) rw- 0x00004000 0x00000028 0x00000006 lldb_demo.__DATA_CONST.__got
0x0000000b data-ptrs [0x0000000100004028-0x0000000100004030) rw- 0x00004028 0x00000008 0x10000000 lldb_demo.__DATA_CONST.__objc_classlist
0x0000000c regular [0x0000000100004030-0x0000000100004038) rw- 0x00004030 0x00000008 0x00000000 lldb_demo.__DATA_CONST.__objc_imageinfo
0x00000400 container [0x0000000100008000-0x000000010000c000) rw- 0x00008000 0x00004000 0x00000000 lldb_demo.__DATA
0x0000000d data-ptrs [0x0000000100008000-0x0000000100008048) rw- 0x00008000 0x00000048 0x00000007 lldb_demo.__DATA.__la_symbol_ptr
0x0000000e data-ptrs [0x0000000100008048-0x0000000100008168) rw- 0x00008048 0x00000120 0x00000000 lldb_demo.__DATA.__objc_const
0x0000000f data-cstr-ptr [0x0000000100008168-0x0000000100008190) rw- 0x00008168 0x00000028 0x10000005 lldb_demo.__DATA.__objc_selrefs
0x00000010 data-ptrs [0x0000000100008190-0x00000001000081a8) rw- 0x00008190 0x00000018 0x10000000 lldb_demo.__DATA.__objc_classrefs
0x00000011 regular [0x00000001000081a8-0x00000001000081b0) rw- 0x000081a8 0x00000008 0x00000000 lldb_demo.__DATA.__objc_ivar
0x00000012 data-ptrs [0x00000001000081b0-0x0000000100008200) rw- 0x000081b0 0x00000050 0x00000000 lldb_demo.__DATA.__objc_data
0x00000013 data [0x0000000100008200-0x0000000100008208) rw- 0x00008200 0x00000008 0x00000000 lldb_demo.__DATA.__data
0x00000500 container [0x000000010000c000-0x0000000100014000) r-- 0x0000c000 0x00005c10 0x00000000 lldb_demo.__LINKEDIT
使用Python脚本搭配LLDB
使用Python脚本的好处就是可以避免人工来做重复的事情,比如命中某个断点之后我们需要修改一些内存、寄存器、或者代码的执行路径等等。都是可以基于Python脚本来进行的。其大致步骤有:
- 编写python脚本文件;
- 导入脚本文件;
- 导入脚本内部的命令(这一步可以没有);
其中最主要的是第一步,毕竟导入文件和导入命令就两个lldb命令的事儿。因此主要来看看脚本文件的编写。
Python脚本编写
由于是编写基于LLDB的脚本,所以我们必须先导入lldb这个module(这是LLDB API 包含在一个名为 lldb 的 python 模块中,完整的API见LLDB Python API ):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import lldb
其中我们可能会用到的一些变量包括有(下面表格里面所有提到的内容都可以通过 script help(lldb.xxxx) 来获取详细地帮助信息):
变量 | 类型 | 类 | 描述 |
---|---|---|---|
lldb.debugger | lldb.SBDebugger | SBTarget.GetDebugger | 调试器 |
lldb.target | lldb.SBTarget | SBDebugger.GetSelectedTarget | 我们在启动lldb之后,可以通过target create 来创建不同的target。然后对应run来指定要运行的target |
lldb.process | lldb.SBProcess | SBTarget.GetProcess | 进程 |
lldb.thread | lldb.SBThread | SBProcess.GetSelectedThread | 线程 |
lldb.frame | lldb.SBFrame | SBThread.GetSelectedFrame | 调用帧 |
(lldb) script
Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
>>> print(lldb.debugger)
Debugger (instance: "debugger_1", id: 1)
>>> print(lldb.process)
SBProcess: pid = 30104, state = stopped, threads = 1, executable = lldb_demo
>>> print(lldb.thread)
thread #1: tid = 0x61552d, 0x0000000100003b49 lldb_demo`main(argc=2, argv=0x00007ffeefbff468) at main.mm:14:22, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
>>> print(lldb.frame)
frame #0: 0x0000000100003b49 lldb_demo`main(argc=2, argv=0x00007ffeefbff468) at main.mm:14:22
>>> print(lldb.target)
lldb_demo
这些是我们在lldb内嵌的python解释器中执行相关的代码得到的结论。
现在我们来编写Python脚本。首先是创建一个Target,我们可以根据debugger来创建:
def pfbCreateTarget():
# triple = "x86_64-apple-macosx"
# platform_name = None
# add_dependents = False
path = os.getcwd() + "/lldb_demo"
demo_target = lldb.debugger.CreateTargetWithFileAndArch (path, lldb.LLDB_ARCH_DEFAULT)
return demo_target
创建好了target,那肯定是要执行这个target的。这里会返回一个process:
def pfbRunTarget(target):
process = target.LaunchSimple(None, None, os.getcwd())
return process
程序默认执行是不会暂停的,我们可以通过脚本来增加指定的断点:
def pfbCreateMainBreakpoint(target):
breakpoint = target.BreakpointCreateByName("main", None)
breakpoint.enable = True
return breakpoint
到这里我们先让这个脚本能RUN起来,那么第一步肯定是要让我们这两个函数有地方调用。在LLDB中给我们提供了一个函数入口__lldb_init_module:
def __lldb_init_module(debugger, internal_dict):
print("LLDB INIT MODULE")
pfbTarget = pfbCreateTarget()
print(pfbTarget)
pfbMainBreakPoint = pfbCreateMainBreakpoint(pfbTarget)
pfbProcess = pfbRunTarget(pfbTarget)
将脚本添加到lldb中:
(lldb) command script import ~/WorkStation/exec/llvm/lldb/script/python/search_fmp.py
可以得到如下结果,程序在指定的位置(断点处)暂停了:
如果是我们需要在脚本文件中处理断点相关的debug操作的话,LLDB给我们提供断点被执行时的回调函数。默认是breakpoint_function_wrapper。但是我们可以在执行指令的时候通过--python-function指定我们需要处理的函数(指定的函数参数必须要包含frame、bp_loc):
def breakpoint_function_wrapper(frame, bp_loc, extra_args, internal_dict):
# Your code goes here
print("breakpoint_function_wrapper1 begin:")
print(frame)
print("breakpoint_function_wrapper1 end")
return True
'''
-----------------
私有函数
-----------------
'''
def pfbEnableBreakpointHook(target):
# breakpoint command add -s python xxx --python-function search_fmp.breakpoint_function_wrapper
bnums = target.GetNumBreakpoints()
print("Enable Breakpoint nums: " + str(bnums))
for i in range(0, bnums):
brkpt = target.GetBreakpointAtIndex(i)
cmd = "breakpoint command add -s python " + str(brkpt.id) + " --python-function search_fmp.breakpoint_function_wrapper"
print("Enable Breakpoint: " + cmd)
lldb.debugger.HandleCommand(cmd)
针对函数原型中提到的几个参数,下面做了一下简单的解释:
参数名称 | 类型 | 备注 |
---|---|---|
frame | lldb.SBFrame | 断点被命中的当前堆栈帧 |
bp_loc | lldb.SBBreakpointLocation | 命中的断点位置 |
extra_args | lldb.SBStructuredData | 暂未使用 |
internal_dict | dict | python字典对象 |
得到的结论类似于这样:
使用Python脚本给LLDB新增指令
就像我在本文开头描述的那样,我们需要在lldb里面使用shell的命令需要加一个platform shell的前缀。显然这么麻烦的事情,并不是我们程序员要干的事儿。需求有了,那就来实现一下!
首先是定义我们需要使用的指令,这里我就以最常用的ls和pwd来举例:
def ls(debugger, command, exe_ctx, result, internal_dict):
ret = subprocess.check_output(["ls", "-l"])
print(ret)
def pwd(debugger, command, exe_ctx, result, internal_dict):
ret = subprocess.check_output(["pwd"])
print(ret)
def pfbShell(debugger, command, result, dict):
command_args = shlex.split(command)
ret = subprocess.check_output(command_args)
print(ret)
在定义了对应的函数之后,我们需要将这些函数添加到command中:
debugger.HandleCommand("command script add -f search_fmp.pfbShell pfb")
debugger.HandleCommand("command script add -f search_fmp.pwd pwd")
debugger.HandleCommand("command script add -f search_fmp.ls ls")
Python脚本搭配LLDB还有很多的玩法,我就列出了简单的使用场景。
完整的代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# https://lldb.llvm.org/use/python-reference.html
import lldb
# python2 command, python3 subprocess
import subprocess
import optparse
import shlex
import os
'''
-----------------
LLDB 回调时机
-----------------
'''
# debugger: lldb.SBDebugger 调试器对象
def __lldb_init_module(debugger, internal_dict):
print("LLDB INIT MODULE")
pfbTarget = pfbCreateTarget()
pfbMainBreakPoint = pfbCreateMainBreakpoint(pfbTarget)
pfbEnableBreakpointHook(pfbTarget)
pfbProcess = pfbRunTarget(pfbTarget)
# lldb.target
# debugger.HandleCommand("")
debugger.HandleCommand("command script add -f search_fmp.pfbShell pfb")
debugger.HandleCommand("command script add -f search_fmp.pwd pwd")
debugger.HandleCommand("command script add -f search_fmp.ls ls")
# frame: lldb.SBFrame 断点被命中的当前堆栈帧
# bp_loc: lldb.SBBreakpointLocation 命中的断点位置
# extra_args: lldb.SBStructuredData
# internal_dict: dict
def breakpoint_function_wrapper(frame, bp_loc, extra_args, internal_dict):
# Your code goes here
print("breakpoint_function_wrapper1 begin:")
print(frame)
print("breakpoint_function_wrapper1 end")
return True
'''
-----------------
command
-----------------
'''
def ls(debugger, command, exe_ctx, result, internal_dict):
ret = subprocess.check_output(["ls", "-l"])
print(ret)
def pwd(debugger, command, exe_ctx, result, internal_dict):
ret = subprocess.check_output(["pwd"])
print(ret)
def pfbShell(debugger, command, result, dict):
command_args = shlex.split(command)
ret = subprocess.check_output(command_args)
print(ret)
'''
-----------------
私有函数
-----------------
'''
def pfbEnableBreakpointHook(target):
# breakpoint command add -s python xxx --python-function search_fmp.breakpoint_function_wrapper
bnums = target.GetNumBreakpoints()
print("Enable Breakpoint nums: " + str(bnums))
for i in range(0, bnums):
brkpt = target.GetBreakpointAtIndex(i)
cmd = "breakpoint command add -s python " + str(brkpt.id) + " --python-function search_fmp.breakpoint_function_wrapper"
print("Enable Breakpoint: " + cmd)
lldb.debugger.HandleCommand(cmd)
def pfbCreateMainBreakpoint(target):
breakpoint = target.BreakpointCreateByName("main", None)
breakpoint.enable = True
return breakpoint
def pfbCreateTarget():
triple = "x86_64-apple-macosx11.5.0"
platform_name = None
add_dependents = False
path = os.getcwd() + "/lldb_demo"
print("Target Create: " + path)
# https://lldb.llvm.org/use/symbolication.html#using-python-api-to-symbolicate
target = lldb.debugger.CreateTarget(path, triple, platform_name, add_dependents, lldb.SBError())
#target = lldb.debugger.CreateTargetWithFileAndArch (path, lldb.LLDB_ARCH_DEFAULT)
return target
def pfbRunTarget(target):
process = target.LaunchSimple(None, None, os.getcwd())
return process