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.

Introduction to iOS Debugging with LLDB.

Being a developer means that many times a day you will end up debugging code, wether is yours or someone else’s. According to this research, we spend 25% of time fixing bugs and 25% making code works, at the end, we spend 50% of our time debugging. But sometimes I found myself spending whole day trying to catch a pesky bug or getting along a new project. More often you will have to debug spaghetti code.

Fortunately Xcode comes with amazing tools to make this task easier: LLDB & Instruments.

LLDB is the default debugger in Xcode (used to be GDB), comes with handy commands, also you can write your own debugging scripts using python and extend it’s functionality.

To help you understand this post I made a test project in github. I’m using Xcode Version 5.1.1 (5B1008).

Start Using LLDB.

Add a breakpoint to FGAppDelegate.

App Delegate breakpoint

Run your app.

Navigate until you reach the breakpoint, you will see the LLDB console.

LLDB Console

 

Print object values.

The command “po” evaluates C/ObjC/C++ expressions in the current program content, using defined variables and variables that are currently in scope. Which means that you will access solely the variables from the current class.

Add a breakpoint in FGViewController.m:40

Breakpoint in class FGViewController

Run the app.
When you hit the breakpoint type:

(lldb) po _person

You will get: nil (because is not yet initialised, press F6 to continue)

(lldb) po _person
<FGPerson:0xc7762c0, ID:0 Name:John Doe>

po is useful to print objects, but if you need to print scalar values use (personID is an int property):

(lldb) p _person.personID
(int) $2 = 0

Also you can use self:

(lldb)po self.view
<UIView: 0xa08c030; frame = (0 0; 320 568); autoresize = RM+BM; layer = <CALayer: 0xa08c090>>

LLDB Console:

po & p commands

NOTE: You can print all the view elements in current view using:
(lldb) po [[[UIApp delegate] window] recursiveDescription]

Breakpoints

Remove previous breakpoint by right click at breakpoint->Delete

Add breakpoints

Add a breakpoint to a class and code line:

(lldb) b FGViewController.m:40

“Breakpoint 2:” = the id for the breakpoint, with this id you can add commands, delete, enable or modify a breakpoint.

Command Options (for more options type “help br” on LLDB prompt):

 s: Sets a breakpoint or set of breakpoints in the executable.
-n: Set the breakpoint by function name.
-S: Set the breakpoint by ObjC selector name.

Add a breakpoint to a class and selector without parameters:

(lldb) br s -n "-[FGViewController setLabels]"

Add a breakpoint to a class and selector with parameters:

(lldb) br s -n "-[FGViewController addPersonHandler:]"

Add a breakpoint to a selector no matter the class:

(lldb) br s -S viewDidLoad

List all current breakpoints; added from LLDB or Xcode:

(lldb) br list

LLDB Console:

Break point command console.

 

NOTE: All breakpoints added through LLDB will never be reflected in Xcode, if you want to create a Symbolic breakpoint that gets reflected in Debugging tab, then follow this.
  1. Open the Breakpoint Navigator
  2. Click on + button
  3. Add Symbolic Breakpoint
  4. In Symbol text box write: -[FGViewController setLabels]
  5. Uncheck “Automatically continue after evaluating”.

br_visual_command

Also you can read more about Xcode breakpoints in the post Xcode Breakpoint Wizardry from Big Nerd Ranch.

Conditional breakpoint

(lldb) b FGResizeViewController.m:53
Breakpoint 2: where = LLDBTest`-[FGResizeViewController resizeSquare:] + 198 at FGResizeViewController.m:53, address = 0x000029d6
(lldb) br mod -c "currentFrame.size.width > 200" 2

Adding a script to a breakpoint

For python scripts you have available these variables:

frame:  a lldb.SBFrame object for the frame which hit breakpoint.
bp_loc: a lldb.SBBreakpointLocation object that represents the breakpoint location that was hit.
dict:   the python session dictionary hit.

Adding a single line Python script that will print the current method for the breakpoint, this is really useful when you get into a new project and you want to trace where you’re passing while using the app:

(lldb) br s -S viewDidLoad
Breakpoint 2: 18 locations.
(lldb) br command add -s python 2 -o "function_name = frame.GetFunctionName(); print '%s' % function_name"

Adding a LLDB script:

(lldb) br command add -s command 2
Enter your debugger command(s). Type 'DONE' to end.
> frame info
> DONE
frame #0: 0x0034bea5 UIKit`-[UIViewController viewDidLoad]

Debugging is one of the main developer’s activities, using the right tools provided by Xcode will help you easy this often tedious task. Now you know how to inspect data without adding NSLogs to your class files and most important, without stopping the app, which will save you a lot of time. Breakpoints are a powerful feature that you can use to stop conditionally  or print more info about what is going on.

In LLDB type

(lldb) help

for more info about all commands available.

Further Reading.

Apple’s official LLDB quick start guide.

LLDB Tutorial.

Getting Started with LLDB from Apple Developer.