Introduction to LLDB Python scripting.

One of the most unknown features of LLDB is that you could extend it’s functionality using Python. LLDB comes with a Python Library you can use through a script bridge interface, wether you call it from lldb command line or add debugging functionality to a Python program.

But why learn to write LLDB Python scripts? after all we could set a breakpoint, inspect data using preview or po command, right?, when comes to debugging, nothing is sufficient and writing your own commands to debug issues in your project will save you time and improve code’s quality. Also you can share scripts to your development team, so all get benefits from a common debugging library.

Suppose you are hired to maintain an existing app, as is usual, you will be provided with the architecture document, class diagrams, user stories or all you need to understand what is going on (please note the sarcasm). A quick look to the project shows you it uses the widely spread Spaghetti Architecture, where you have two or more components doing the same, not clear path to navigate trough the app and you get the sense this will be a nightmare to maintain. Can set tons of breakpoints or you could write a LLDB python script to print the execution’s frame trace to understand what’s going on.

For this blog post I used:

  1. OS X 10.9.2
  2. Xcode 5.1.1
  3. Python 2.7
  4. LLDBTest project

Defining the function.

1. Import the LLDB Python bridge library and define the function.

import lldb 

def command_name(debugger, command, result, internal_dict): 

Parameter description:

debugger: Is the primordial object that creates targets and provides access to them. It also manages the overall debugging experiences.

command:A python string containing all arguments for your command.

result: Represents a container which holds the result from command execution. It works with SBCommandInterpreter.HandleCommand() to encapsulate the result of command execution.

internal_dict: The dictionary for the current embedded script session which contains all variables and functions.

2. Add the code to print the frame trace:

def print_frame(debugger, command, result, internal_dict):
    target = debugger.GetSelectedTarget()
    process = target.GetProcess()
    thread = process.GetSelectedThread()

    for frame in thread:
            print >>result, str(frame)

3. Define how should be used as LLDB command:

def __lldb_init_module(debugger, internal_dict):  
debugger.HandleCommand('command script add -f print_command.print_frame print_frame')  
print 'The "print_frame" python command has been installed and is ready for use.'  

Import description:

command script add -f: LLDB command to import a script.

print_command: file name

print_frame: function name

print_frame: how we will invoke the function in Xcode

4. Save this file as print_command.py

Importing the command into Xcode.

I have stored my LLDB scripts in ~/lldb folder.

NOTE: Do this every time you start the debugger:
(lldb) command script import ~/lldb/print_command.py
The "print_frame" python command has been installed and is ready for use.

To print the stack frame every time viewDidload selector is fired:

(lldb) br s -S viewDidLoad  
Breakpoint 3: 18 locations.  

Add our Python LLDB command to breakpoint “3”:

(lldb) br command add 3  
Enter your debugger command(s).  Type 'DONE' to end.  
> print_frame  
> DONE  

 

Download print_command file.
Import print command.

Adding parameters to the script.

While this is simple, is not quite useful as we will print all frame trace, no matter the class invoked, let’s modify our script to pass a parameter for filtering.

1. Add support libraries:

import commands
import optparse
import shlex

2. Add a command parser function:

    def create_print_frame_options():
        usage = "usage: %prog [options]"
    description='''This command uses two parameters to filter frame printing.
        '''  
    parser = optparse.OptionParser(description=description, prog='print_frame',usage=usage)
    parser.add_option('-p', '--prefix', type='string', dest='prefix', help='Class prefix to filter.')
    parser.add_option('-m', '--message', type='string', dest='message', help='Message to print at the begining.')

    return parser

Options description:

-p: single char parameter flag.

--prefix: parameter flag description.

type: python variable type.

dest: variable name in python.

help: message to display in help command.

3. Modify print_frame function, add:


command_args = shlex.split(command)
parser = create_print_frame_options()

try:
    (options, args) = parser.parse_args(command_args)

except:
    # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit
    result.SetError ("option parsing failed")
    return

if options.message:
    print >>result, "************************** Tracing: %s %s" % (options.message," **************************")

if options.prefix:
  for frame in thread:
    if str(frame.GetFunctionName()).find(options.prefix) != -1:
      print >>result, str(frame)

else:
  for frame in thread:
    print >>result, str(frame)

4. Save the file as print_command_options.py, don’t forget to modify __lldb_init_module with the new function file.

5. In Xcode hit a breakpoint and import the new command:

(lldb) command script import ~/lldb/print_command_options.py
The "print_frame" python command has been installed and is ready for use.

To print the filtered stack frame every time viewDidload selector is fired:

(lldb) br s -S viewDidLoad
Breakpoint 3: 18 locations.

Add our modified Python LLDB command to breakpoint “3”:

(lldb) br command add 3  
Enter your debugger command(s).  Type 'DONE' to end.  
> print_frame -p FG -m viewDidLoad  
> DONE  

Navigate the app, you will stop each time viewDidLoad gets fired and get the frame trace.

 

Download print_command_options file.

Importing print command with options.

If you have doubts about how to use LLDB commands, please read my previous post Introduction to iOS Debugging With LLDB.

To definitively import the command into Xcode, create or modify a file called ~/.lldbinit, add this line:
command script import ~/lldb/print_command_options.py

LLDB Python scripts is an excellent tool to extend debugging capabilities to your project, found bugs faster and improve code’s quality. Now you know the basis of how to create your own scripts.

Further Learning.

Facebook has released chisel a collection of useful LLDB Python scripts.

RealMac’s Software how to disassemble a block in LLDB.

LLDB’s official python scripts examples.

Python optparse tutorial.