Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Fourth Edition · iOS 16, macOS 13.3 · Swift 5.8, Python 3 · Xcode 14

Section I: Beginning LLDB Commands

Section 1: 10 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

21. Debugging Script Bridging
Written by Walter Tyree

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, Swift, or even other languages code 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 essential debugging features that let you step into, out of, and over code to inspect potential areas of interest. If you’ve decided to set up VS Code or vim with plugins, after finishing this chapter, be sure to explore how they interact with pdb as well. Any fancy IDE you use for Python will include some integration with pdb.

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

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

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

def your_first_command(debugger, command, result, internal_dict):
    breakpoint()
    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 outside of Xcode.

Save your changes and open a Terminal window and type the following to create a new LLDB session:

lldb

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

(lldb) yay woot

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

> /Users/wtyree/lldb/helloworld.py(3)your_first_command()
-> print ("hello world")
(Pdb)

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

When creating an 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. Since pdb stopped inside of the function, those parameters are currently in scope and are assigned values.

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

(Pdb) command

This dumps out the commands you supplied to your yay custom LLDB command. This always comes 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 silently ignores all input. If you typed in yay woot as indicated earlier, only woot would appear 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 to display when your command finishes.

Type the following into pdb:

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

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

As 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 an 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 this 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/findclass.py
(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/wtyree/lldb/findclass.py", 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 pdb.pm()
> /Users/<username>/lldb/findclass.py(71)findclass()
-> raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
(Pdb)
(Pdb) l 32, 83
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)];
}
free(classes);

returnString;    
68    res = lldb.SBCommandReturnObject()
69    debugger.GetCommandInterpreter().HandleCommand("po " ...
70    if res.GetError():
71 ->     raise AssertionError("Uhoh... something went wron...
72    elif not res.HasResult():
73        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);
numClasses = objc_getClassList(NULL, 0);
numClasses = (int)objc_getClassList(NULL, 0);
numClasses = objc_getClassList(classes, numClasses);
numClasses = (int)objc_getClassList(classes, numClasses);
  [returnString appendFormat:@"%s,", class_getName(c)];
  [returnString appendFormat:@"%s,", (char *)class_getName(c)];
(lldb) command script import ~/lldb/findclass.py
(lldb) findclass
(lldb) findclass ViewController

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?

breakpoint()

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.

Key Points

  • Add import pdb; pdb.set_trace() to set a breakpoint in your Python script.
  • For Python scripts that handle errors and throw exceptions, the script pdb.pm() command can pause execution just before an exception is thrown.
  • When inspecting variables with pdb use the print(<the thing>) to get nicely formatted output.
  • When using command script import to reload a Python command script, LLDB might say it didn’t realod the script, but it really did.
  • Using an IDE that knows Python will help to avoid Python build errors before command script import.

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