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

20. Hello, Script Bridging
Written by Walter Tyree

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

LLDB has several ways you can use to create your own customized commands. The first way is through the easy-to-use command alias you saw in Chapter 9, “Persisting & Customizing Commands”. This command simply creates an alias for a static command. While easy to implement, it really only allowed you to execute commands with no input.

After that came command regex, which let you specify a regular expression to capture input then apply it to a command. You learned about this command in Chapter 10, “Regex Commands”. This command works well when you want to feed input to an LLDB command, but it was inconvenient to execute multiline commands and supplying multiple, optional parameters could get really messy.

Next up in the tradeoff between convenience and complexity is LLDB’s script bridging. With script bridging, you can do nearly anything you like. Script bridging is a Python interface LLDB uses to help extend the debugger to accomplish your wildest debugging dreams.

However, there’s a cost to the script bridging interface. It has a steep learning curve, and the documentation, to put it professionally, sucks. Fortunately, you’ve got this book in your hands to help guide you through learning script bridging. Once you’ve a grasp on LLDB’s Python module, you can do some very cool (and excitingly dangerous!) things.

Credit Where Credit’s Due

Before we officially begin talking about script bridging, I want to bring up one Python script that has blown my mind. If it wasn’t for this script, this book would not be in your hands.


This is the script that made me take a deep dive into learning LLDB. I’ve never had a mental butt-kicking as good as I did trying to initially understand what was happening in this code.

This script has it all: finding stack traces for malloc’d objects (malloc_info -s), getting all instances of a particular subclass of NSObject (obj_refs -O), finding all pointers to a particular reference in memory (ptr_refs), finding C strings in memory (cstr_ref).

You can load the contents of this script into any LLDB session, and use its functions, with the following LLDB command:

(lldb) command script import lldb.macosx.heap

Sadly, this script has fallen a bit out of functionality as the compiler has changed, while this code has not, rendering several of its components unusable.

When you’re done reading this section, I would strongly encourage you to attempt to understand the contents of this script. You can learn a lot from it.

Ok, now back to our regularly scheduled, reading program…

Python 101

As mentioned, LLDB’s script bridge is a Python interface to the debugger. This means you can load and execute Python scripts in LLDB. In those Python scripts, you include the lldb module to interface with the debugger to obtain information such as the arguments to a custom command.

(lldb) script print (sys.version)
python3 --version
>>> import sys
>>> print (sys.version)

Playing Around in Python

If you are unfamiliar with Python, this section will help you get familiar with the language quickly. If you’re already knowledgeable about Python, feel free to jump to the next section.

>>> h = "hello world"
>>> h
'hello world'
>>> h.split()
['hello', 'world']
var h: [Any] = []
>>> h.split(" ").__class__
<type 'list'>
>>> h.__class__
<type 'str'>
>>> help (str)
>>> help (str.split)
Help on method_descriptor:

split(self, /, sep=None, maxsplit=-1)
  Return a list of the words in the string, using sep as the delimiter string.

    The delimiter according which to split the string.
    None (the default value) means split according to any whitespace,
    and discard empty strings from the result.
    Maximum number of splits to do.
    -1 (the default value) means no limit.
>>> h.split(" ", 0)
>>> def test(a):
...   print(a + " world!")
>>> test("hello")

Creating Your First LLDB Python Script

From here on out, you’ll be creating all your LLDB Python scripts in the ~/lldb directory. If you want to have them in a different directory, every time I say ~/lldb, you’ll need to invoke your “mental symlink” to whatever directory you’ve decided to use.

mkdir ~/lldb
nano ~/lldb/
def your_first_command(debugger, command, result, internal_dict):
  print ("hello world!")
(lldb) command script import ~/lldb/
(lldb) script import helloworld
(lldb) script dir(helloworld)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'your_first_command']
(lldb) command script add -f helloworld.your_first_command yay
(lldb) yay

Setting Up Commands Efficiently

Once the high of creating a custom function in script bridging has worn off, you’ll come to realize you don’t want to type this stuff each time you start LLDB. You want those commands to be there ready for you as soon as LLDB starts.

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f helloworld.your_first_command yay')
(lldb) script help(lldb.SBDebugger.HandleCommand)
HandleCommand(self, command)
  HandleCommand(SBDebugger self, char const * command)
command script import ~/lldb/
(lldb) yay
hello world!

Key Points

  • MacOS and the embedded lldb use Python3, which is not Python 2. Be mindful of what version someone is referencing in blog posts and online tutorials.
  • Run lldb and type script print(sys.version) to check which version of Python LLDB is using.
  • In Terminal, use python3 --version to check which version of Python your system is using by default.
  • Python uses whitespace instead of braces to denote different scopes in code.
  • In the Python REPL (run python3 in a terminal) type help (<whatever>) to view the online documentation for a keyword or object.
  • This book will assume you’re putting all of your scripts in a ~/lldb folder on your system.
  • In a .py file, create a __lldb_init_module function to load your commands into lldb sessions automatically.

Where to Go From Here?

If you don’t feel comfortable with Python, now is the time to start brushing up on it. If you have past development experience, you’ll find Python to be a fun and friendly language to learn. It’s a great language for quickly building other tools to help with everyday programming tasks.

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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now