Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

22. Debugging Script Bridging
Written by Derek Selander

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You’ve learned the basics of LLDB’s Python script bridging. Now you’re about to embark on the frustrating yet exhilarating world of making full LLDB Python scripts.

As you learn about the classes and methods in the Python lldb module, you’re bound to make false assumptions or simply type incorrect code. In short, you’re going to screw up. Depending on the error, sometimes these scripts fail silently, or they may blow up with an angry stderr.

You need a methodical way to figure out what went wrong in your LLDB script so you don’t pull your hair out. In this chapter, you’ll explore how to inspect your LLDB Python scripts using the Python pdb module, which is used for debugging Python scripts. In addition, you can execute your own “normal” Objective-C, Objective-C++, C or Swift code (or even other languages) within SBDebugger’s (or SBCommandReturnObject’s) HandleCommand method.

In fact, there’s alternative ways to execute non-Python code that you’ll learn about in an upcoming chapter, but for now, you’ll stick to HandleCommand and see how to manage a build time error, or fix a script that produces an incorrect result.

Although it might not seem like it at first, this is the most important chapter in the LLDB Python section, since it will teach you how to explore and debug methods while you’re learning this new Python module. I would have (figuratively?) killed for a chapter like this when I was first learning the Script Bridging module.

Debugging your debugging scripts with pdb

Included in the Python distribution on your system is a Python module named pdb you can use to set breakpoints in a Python script, just like you do with LLDB itself! In addition, pdb has other debugging essential features that let you step into, out of, and over code to inspect potential areas of interest.

You’re going to continue using the script in ~/lldb from the previous chapter. If you haven’t read that chapter yet, copy the from the starter directory into a directory named lldb inside your home directory.

Either way, you should now have a file at ~/lldb/

Open up and navigate to the your_first_command function, replacing it with the following:

def your_first_command(debugger, command, result, internal_dict):
    import pdb; pdb.set_trace()
    print ("hello world")

Note: It’s worth pointing out pdb will not work when you’re debugging Python scripts in Xcode. The Xcode console window will hang once pdb is tracing a script, so you’ll need to do all pdb Python script debugging in a Terminal window.

Save your changes and open a Terminal window to create a new LLDB session. In Terminal, type:


Next, execute the yay command (which is defined in, remember?) like so:

(lldb) yay woot

Execution will stop and you’ll get output similar to the following:

> /Users/derekselander/lldb/
-> print ("hello world")

The LLDB script gave way to pdb. The Python debugger has stopped execution on the print line of code within inside the function your_first_command.

When creating a LLDB command using Python, there are specific parameters expected in the defining Python function. You’ll now explore these parameters, namely debugger, command, and result.

Explore the command argument first, by typing the following into your pdb session:

(Pdb) command

This will dump out the commands you supplied to your yay custom LLDB command. This will always come in the form of a str, even if you have multiple arguments or integers as input. Since there’s no logic to handle any commands, the yay command will silently ignore all input. If you typed in yay woot as indicated earlier, only woot would be spat out as the command.

Next up on the parameter exploration list is the result parameter. Type the following into pdb:

(Pdb) result

This will dump out something similar to the following:

<lldb.SBCommandReturnObject; proxy of <Swig Object of type 'lldb::SBCommandReturnObject *' at 0x110323060> >

This is an instance of SBCommandReturnObject, which is a class the lldb module uses to let you indicate if the execution of an LLDB command was successful. In addition, you can append messages that will be displayed when your command finishes.

Type the following into pdb:

(Pdb) result.AppendMessage("2nd hello world!")

This appends a message which will be shown by LLDB when this command finishes. In this case, once your command finishes executing, 2nd hello world! will be displayed. However, your script is still frozen in time thanks to pdb.

Once your LLDB scripts get more complicated, the SBCommandReturnObject will come into play, but for simple LLDB scripts, it’s not really needed. You’ll explore the SBCommandReturnObject command more later in this chapter.

Finally, onto the debugger parameter. Type the following into pdb:

(Pdb) debugger

This will dump out another object of class SBDebugger, similar to the following:

<lldb.SBDebugger; proxy of <Swig Object of type 'lldb::SBDebugger *' at 0x110067180> >

You explored this class briefly in the previous chapter to help create the LLDB yay command. You’ve already learned one of the most useful commands in SBDebugger: HandleCommand.

Resume execution in pdb. Like LLDB, it has logic to handle a c or continue to resume execution.

Type the following into pdb:

(Pdb) c

You’ll get the following output:

hello world!
2nd hello world!

pdb is great when you need to pause execution in a certain spot to figure out what’s gone wrong. For example, you could have some complicated setup code, and pause in an area where the logic doesn’t seem to be correct.

This is a much more attractive solution than constantly typing script in LLDB to execute one line of Python code at a time.

pdb’s post mortem debugging

Now that you’ve a basic understanding of the process of debugging your scripts, it’s time to throw you into the deep end with an actual LLDB script and see if you can fix it using pdb’s post-mortem debugging features.

lldb -n Photos
(lldb) command script import ~/lldb/
(lldb) help findclass
Syntax: findclass

The `findclass` command will dump all the Objective-C runtime classes it knows about. Alternatively, if you supply an argument for it, it will do a case-sensitive search looking only for the classes that contain the input. 

Usage: findclass  # All Classes
Usage: findclass UIViewController # Only classes that contain UIViewController in name

(lldb) findclass
Traceback (most recent call last):
  File "/Users/derekselander/lldb/", line 40, in findclass
    raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
AssertionError: Uhoh... something went wrong, can you figure it out? :]
(lldb) script import pdb
(lldb) findclass
(lldb) script
> /Users/derekselander/lldb/
-> raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
(Pdb) l 1, 50
def findclass(debugger, command, result, internal_dict):
(Pdb) codeString
'\n    @import Foundation;\n    int numClasses;\n    Class * classes = NULL;\n    classes = NULL;\n    numClasses = objc_getClassList(NULL, 0);\n    NSMutableString *returnString = [NSMutableString string];\n    classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);\n    numClasses = objc_getClassList(classes, numClasses);\n\n    for (int i = 0; i < numClasses; i++) {\n      Class c = classes[i];\n      [returnString appendFormat:@"%s,", class_getName(c)];\n    }\n    free(classes);\n    \n    returnString;\n    '
(Pdb) print codeString
@import Foundation;
int numClasses;
Class * classes = NULL;
classes = NULL;
numClasses = objc_getClassList(NULL, 0);
NSMutableString *returnString = [NSMutableString string];
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);

for (int i = 0; i < numClasses; i++) {
  Class c = classes[i];
  [returnString appendFormat:@"%s,", class_getName(c)];

37    res = lldb.SBCommandReturnObject()
38    debugger.GetCommandInterpreter().HandleCommand("po " ...
39    if res.GetError(): 
40 ->     raise AssertionError("Uhoh... something went wron...
41    elif not res.HasResult():
42        raise AssertionError("There's no result. Womp wom...

(Pdb) print res.GetError()
error: warning: got name from symbols: classes
error: 'objc_getClassList' has unknown return type; cast the call to its declared return type
error: 'objc_getClassList' has unknown return type; cast the call to its declared return type
error: 'class_getName' has unknown return type; cast the call to its declared return type
int objc_getClassList(Class *buffer, int bufferCount);
const char * class_getName(Class cls);
codeString = r'''
@import Foundation;
int numClasses;
Class * classes = NULL;
classes = NULL;
numClasses = (int)objc_getClassList(NULL, 0);
NSMutableString *returnString = [NSMutableString string];
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = (int)objc_getClassList(classes, numClasses);

for (int i = 0; i < numClasses; i++) {
  Class c = classes[i];
  [returnString appendFormat:@"%s,", (char *)class_getName(c)];

(lldb) command script import ~/lldb/
(lldb) findclass
(lldb) findclass ViewController

expression’s Debug Option

As you saw in Chapter 5, “Expression,” LLDB’s expression command has a slew of options available for when LLDB is evaluating code provided to this command. One of these options, overlooked until now, is the --debug option, or more simply -g. If you supply this option to expression, LLDB will evaluate the expression, but the expression will be written to a file and control will stop as soon as execution hits your command.

debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -g -O -- " + codeString, res)
(lldb) command script import ~/lldb/
(lldb) findclass

(lldb) l
(lldb) l
(lldb) gui

How to handle problems

As I alluded to in the introduction to this chapter, you’re going to run into problems when building these scripts. Let’s recap what options you have, depending on the type of problem you encounter when building out these scripts.

Python build errors

When reloading your script, you might encounter something like this:

Python runtime errors or unexpected values

What if your Python script loads just fine, and you don’t get any build errors to the console when reloading — but you receive unexpected output, or your script crashes and you need to further inspect what’s happening?

import pdb; pdb.set_trace()

JIT code build errors

Often, you’re executing actual code inside the process and then return the value back to your Python script. Again, this will be referred to as JIT code throughout the remainder of the book.

JIT code with unexpected results

The final types of errors you could encounter are unexpected results from your JIT code. For example, in the script, what if you didn’t get an expected class? What if you get more hits than you would have expected, searching for a particular query?

debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -g -- " + codeString, res)

Where to go from here?

You’re now equipped to tackle the toughest debugging problems while making your own custom scripts!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now