LLDB与Python

加入Python模块

首先获取lldb的python模块路径

lldb -P

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

将模块路径写入文件即可

非command中运行

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

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反汇编程序的例子

#!/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中的可以实现回调,比如当某个断点命中时自动执行函数

(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则暂停

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为例,这里加入输出,来观察执行状态

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

执行命令

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

产生输出

__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命令到命令解析器中,函数形式如下

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类实现命令添加

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脚本,可以通过定义

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

该函数在import时执行初始化

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.'

如上述脚本,执行命令

(lldb) command script import ~/script_step.py

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

(lldb) ls /
Applications
Library
Network
System
...

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!