LLDB与Python

加入Python模块

首先获取lldb的python模块路径

1
lldb -P

在Python的site-packages路径下加入.pth文件,比如Extra.pth

将模块路径写入文件即可

非command中运行

可以直接通过接口获得一个调试器的实例

1
lldb.debugger = lldb.SBDebugger.Create()

lldb.py提供的基本接口有以下几种:

lldb.SBDebugger : 代表调试器实例的类,拥有命令行解析和所有调试目标

lldb.SBTarget : 代表当前调试目标

lldb.SBProcess : 代表当前调试目标的执行进程,管理进程并且可访问全部内存空间

lldb.SBThread : 代表当前选择的线程,并且管理全部栈帧。及时当前Target停止仍然可以被选择

lldb.SBFrame : 代表某一栈帧,管理所属的局部变量和寄存器快照

https://lldb.llvm.org/python-reference.html 的最后有一个Python脚本使用lldb反汇编程序的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/python

import lldb
import os

def disassemble_instructions(insts):
for i in insts:
print i

# Set the path to the executable to debug
exe = "./a.out"

# Create a new debugger instance
debugger = lldb.SBDebugger.Create()

# When we step or continue, don't return from the function until the process
# stops. Otherwise we would have to handle the process events ourselves which, while doable is
#a little tricky. We do this by setting the async mode to false.
debugger.SetAsync (False)

# Create a target from a file and arch
print "Creating a target for '%s'" % exe

target = debugger.CreateTargetWithFileAndArch (exe, lldb.LLDB_ARCH_DEFAULT)

if target:
# If the target is valid set a breakpoint at main
main_bp = target.BreakpointCreateByName ("main", target.GetExecutable().GetFilename());

print main_bp

# Launch the process. Since we specified synchronous mode, we won't return
# from this function until we hit the breakpoint at main
process = target.LaunchSimple (None, None, os.getcwd())

# Make sure the launch went ok
if process:
# Print some simple process info
state = process.GetState ()
print process
if state == lldb.eStateStopped:
# Get the first thread
thread = process.GetThreadAtIndex (0)
if thread:
# Print some simple thread info
print thread
# Get the first frame
frame = thread.GetFrameAtIndex (0)
if frame:
# Print some simple frame info
print frame
function = frame.GetFunction()
# See if we have debug info (a function)
if function:
# We do have a function, print some info for the function
print function
# Now get all instructions for this function and print them
insts = function.GetInstructions(target)
disassemble_instructions (insts)
else:
# See if we have a symbol in the symbol table for where we stopped
symbol = frame.GetSymbol();
if symbol:
# We do have a symbol, print some info for the symbol
print symbol

可以看出这些主要类的相互调用关系

回调函数

在lldb的command中的可以实现回调,比如当某个断点命中时自动执行函数

1
2
3
4
5
6
7
8
9
10
11
(lldb) breakpoint set --func-regex <regular-expression>
(lldb) breakpoint command add -s python 1
Enter your Python command(s). Type 'DONE' to end.
def function (frame, bp_loc, internal_dict):
"""frame: the lldb.SBFrame for the location at which you stopped
bp_loc: an lldb.SBBreakpointLocation for the breakpoint location information
internal_dict: an LLDB support object not to be used"""
name = frame.GetFunctionName()
print "function name: %s" % name
return False
DONE

函数的返回值决定是否将控制权还给用户,返回False则不还给用户继续执行,返回其他包括None则暂停

1
2
3
4
Process 2447 launched: '/Users/penguin/Test/a.out' (x86_64)
function name: func100
function name: c100
9Process 2447 exited with status = 0 (0x00000000)

CUSTOM STEPPING LOGIC

这是一个比较深奥复杂的功能。lldb的step是由一个“thread plans”的栈驱动的。每当有step的动作出现,就有一个新的plan被压入栈中,完成后被弹出。

当plan入栈后,lldb会通过“询问”该plan来决定一些功能,比如是否中断,“询问”的方式是调用约定的接口,常用的接口如下

should_step:

栈底的plan是stepping控制器,意味着当进程暂停时该plan被询问是否自由的执行(running freely),或者在当前线程单步(instruction-single-step)

explains_stop :

每个plan都会被询问,第一个声明这个stop的获得处理权利。例如在该地址有一个断点,如果脚本explains这个stop,并且should_stop返回false,这个断点将被跳过,因为处理权归脚本,而脚本不中断。这也是为什么step-over返回控制权的原因,因为它的explains一直返回false

should_stop:

a) 确定是否完成工作。如果完成那么通过调用SetPlanComplete来指示

b) 通过返回True或者False来确定是否将控制权交给用户

c) 如果没有完成,将建立任何需要的机器在下次thread继续的时候,False情况下设置SetPlanComplete为False会exit当前thread

is_stale:

轮询完成不管是否停下或者什么都没做,所有的plans将被访问is_stale,如果是他们将从栈中被弹出

以文档的中 http://llvm.org/svn/llvm-project/lldb/trunk/examples/python/scripted_step.py 第一个SimpleStep为例,这里加入输出,来观察执行状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class SimpleStep:

def __init__(self, thread_plan, dict):
self.thread_plan = thread_plan
self.start_address = thread_plan.GetThread().GetFrameAtIndex(0).GetPC()
print "__init__ start_address:"
print hex(self.start_address)

def explains_stop(self, event):
# We are stepping, so if we stop for any other reason, it isn't
# because of us.
print 'explains_stop'
if self.thread_plan.GetThread().GetStopReason() == lldb.eStopReasonTrace:
print 'true'
return True
else:
return False

def should_stop(self, event):
print 'should_stop'
cur_pc = self.thread_plan.GetThread().GetFrameAtIndex(0).GetPC()
print hex(cur_pc)
if cur_pc < self.start_address or cur_pc >= self.start_address + 20:
print 'true'
self.thread_plan.SetPlanComplete(True)
return True
else:
print 'false'
return False

def should_step(self):
print 'should_step'
return True

执行命令

1
2
(lldb) command script import ~/script_step.py
(lldb) thread step-scripted -C script_step.SimpleStep

产生输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
__init__ start_address:
0x100000f30
should_step
should_stop
0x100000f31
false
should_step
explains_stop
true
should_stop
0x100000f34
false
should_step
explains_stop
true
should_stop
0x100000f38
false
should_step
explains_stop
true
should_stop
0x100000f3d
false
should_step
explains_stop
true
should_stop
0x100000f44
true

should_step返回True,所以之后都是单步执行(可以修改为False试试,程序跑飞了)

然后explains_stop宣布占据这个单步执行导致的中断

should_stop根据条件决定是否将程序的控制权交给user,直观的感受就是程序停下来了

增加命令

Python函数可以增加新的LLDB命令到命令解析器中,函数形式如下

1
2
3
def command_function(debugger, command, result, internal_dict):
"""This command takes a lot of options and does many fancy things"""
# Your code goes here

第一个参数是debugger实例,第二个参数是命令参数,

第三个参数为lldb.SBCommandReturnObjec类型,包含命令执行结果信息

最后是嵌入的脚本的集合

也可以使用Python类实现命令添加

1
2
3
4
5
6
7
8
9
class CommandObjectType:
def __init__(self, debugger, session_dict):
this call should initialize the command with respect to the command interpreter for the passed-in debugger
def __call__(self, debugger, command, exe_ctx, result):
this is the actual bulk of the command, akin to Python command functions
def get_short_help(self):
this call should return the short help text for this command[1]
def get_long_help(self):
this call should return the long help text for this command[1]

对于一个Python脚本,可以通过定义

1
2
def __lldb_init_module(debugger, internal_dict):
# Command Initialization code goes here

该函数在import时执行初始化

1
2
3
4
5
6
7
8
def ls(debugger, command, result, internal_dict):
ret = commands.getoutput('/bin/ls %s' % command)
print >>result, (ret)

# And the initialization code to add your commands
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f script_step.ls ls')
print 'The "ls" python command has been installed and is ready for use.'

如上述脚本,执行命令

1
(lldb) command script import ~/script_step.py

就可以直接在LLDB中使用命令ls

1
2
3
4
5
6
(lldb) ls /
Applications
Library
Network
System
...