llvm学习(十八):lldb脚本demo
从零开始的 lldb 脚本开发。系列第四篇(估计是最后一篇),介绍一些常用情景。
简易测试
根据之前的介绍,有 IDE 加持查阅 API 会很方便,但是每次都要改 python 文件、重新加载、查看效果,非常费劲。lldb 支持交互式的 python Interpreter,输入 script 即可进入,并且自动绑定下面 lldb 这几个变量,可以快速测试 API 的效果。
- lldb.debugger
- lldb.target
- lldb.process
- lldb.thread
- lldb.frame
举例:测试 lldb.frame.regs 的输出效果。
1 | b main |
1 | print(lldb.frame.regs) |
访问寄存器
访问PC、SP
PC、SP是最常用的寄存器,断点触发的情况下有效,断点触发时可以获取到栈帧 lldb.SBFrame
。断点触发时注册 stop-hook
可以拿到 lldb.SBExecutionContext
,否则就只能通过 lldb.debugger
一层一层去找。
示范如下:
1 | frame:lldb.SBFrame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedFrame() |
访问通用寄存器
同样,lldb.SBFrame
信息里包含全部寄存器的信息,lldb.SBFrame.GetRegisters
返回 lldb.SBValueList
,lldb 将寄存器分为了三类(可能更多或更少),General Purpose Registers
、Floating Point Registers
、Exception State Registers
,每类都是一个 lldb.SBValue
,里面存放数个寄存器条目,底层使用 c++ 指针。
python 的 API 只提供一个迭代器,这个设计下,使用列表、没有使用字典,导致直接访问指定名字的寄存器非常麻烦,例如访问 rax,需要使用如下的代码:
1 | frame:lldb.SBFrame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedFrame() |
或者使用 python 高阶函数
1 | frame:lldb.SBFrame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedFrame() |
访问内存
可以通过 lldb.SBTarget
或者 lldb.SBProcess
访问内存,二者 API 是不一致的,前者 API 需要构造,后者的 API 更加友好。例如有:
- ReadMemory
- WriteMemory
- ReadCStringFromMemory
- ReadUnsignedFromMemory
- ReadPointerFromMemory
举例:
1 | error = lldb.SBError() |
执行lldb命令
单纯执行命令、不考虑执行情况和返回值的话,可以直接使用 lldb.SBDebugger.HandleCommand
。
需要获取执行情况和返回值,需要用到 lldb.SBDebugger.GetCommandInterpreter
拿到 SBCommandInterpreter
,调用 lldb.SBCommandInterpreter.HandleCommand
1 | res = lldb.SBCommandReturnObject() |
处理错误
lldb 抛出异常的情况较少,提供的 API 经常需要传递新建的 lldb.SBError
用来检测 API 是否运行异常,一般情况下,SBError
success 时,API 返回真实的数据,SBError
fail 时,API 返回一个 None。
实际开发中,需要注意处理异常的 case,例如上面的访问内存就有用到 lldb.SBError
。
小demo
效果:使用 demo.py 文件,每次断点断下来时,打印 sp 的值。
1 | command script import /tmp/demo.py |
1 | from typing import Dict |
2021年12月24日注:该API从lldb12开始才支持python。https://github.com/llvm/llvm-project/commit/b65966cff65bfb66de59621347ffd97238d3f645
结语
lldb 整体的设计是非常优雅的,结构合理,兼容性好,开发中最大的困难就是对 API 的不够熟悉,很多功能不知道要调用什么 API,明明自带的命令有这种功能,但却找不到对应的 API。然后就得去翻源码找线索,整个过程比较消耗精力和消耗耐心。