Published on

Advanced Debugging Techniques: LLDB Scripting

Authors

Overview

Debugging can be a tedious task, especially when dealing with large codebases or complex data structures. Xcode's LLDB (Low-Level Debugger) provides powerful scripting capabilities that can help you automate repetitive tasks, inspect deeply nested objects, or even manipulate variables on the fly. In this tutorial, we'll explore how to level up your debugging with LLDB scripting, making your workflow faster and more efficient.

Prerequisites

  • Basic understanding of Xcode and LLDB.
  • Familiarity with debugging and setting breakpoints.
  • Knowledge of Python (optional but beneficial).

Why Use LLDB Scripting?

LLDB scripting allows you to:

  • Automate repetitive debugging tasks.
  • Create custom commands tailored to your workflow.
  • Inspect and manipulate objects that are hard to view with standard breakpoints.
  • Speed up the debugging process by writing scripts for common scenarios.

1. Getting Started with LLDB

LLDB is the default debugger in Xcode, and you interact with it through the debug console. You can use LLDB commands directly or script them using Python for more advanced control.

Accessing LLDB in Xcode

  1. Set a breakpoint in your code.
  2. Run your app. When the breakpoint is hit, the debug console will open.
  3. You can now enter LLDB commands directly into the console.

Basic LLDB Commands

Here are some essential LLDB commands to get started:

  • po <expression>: Print the object using Objective-C syntax.
  • print <expression> or p <expression>: Print variables or expressions.
  • bt: Print the current call stack.
  • frame variable or fr v: Print all local variables in the current frame.

2. Writing Custom LLDB Commands

LLDB allows you to define custom commands that can simplify complex debugging tasks. For example, you can create a command to print nested data structures, modify variables, or automate repetitive actions.

Creating a Simple LLDB Command

To create a custom LLDB command, you'll use the command script add functionality, typically with Python scripts.

Example: Creating a Command to Print Deeply Nested JSON

# Save this script as print_json.py

import lldb

def print_json(debugger, command, result, internal_dict):
    '''
    Command to print a deeply nested JSON object.
    Usage: print_json <variable_name>
    '''
    target = debugger.GetSelectedTarget()
    process = target.GetProcess()
    frame = process.GetSelectedThread().GetSelectedFrame()
    variable = frame.FindVariable(command.strip())

    if not variable.IsValid():
        print("Invalid variable name.")
        return

    print_object(variable, 0)

def print_object(variable, indent):
    indent_str = ' ' * indent
    if variable.GetTypeName() == 'NSDictionary':
        for key, value in variable.items():
            print(f"{indent_str}{key}:")
            print_object(value, indent + 4)
    else:
        print(f"{indent_str}{variable}")

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f print_json.print_json print_json')

Using the Command in Xcode:

  1. Load the script:
    command script import /path/to/print_json.py
    
  2. Use the command:
    print_json myVariable
    

3. Manipulating Variables on the Fly

Sometimes, you may want to change a variable's value during debugging to test different scenarios without modifying your code.

Changing Variable Values

Use the expression command to change variables:

(lldb) expr myVariable = 42

You can also manipulate more complex structures directly:

(lldb) expr myArray[0] = @("New Value")

4. Automating Tasks with Breakpoints and Scripts

You can attach scripts to breakpoints, which is especially useful for debugging complex flows where you want to automate specific tasks when certain conditions are met.

Attaching Scripts to Breakpoints

  1. Create a breakpoint at the desired line.
  2. Right-click on the breakpoint and select Edit Breakpoint.
  3. In the Actions section, click Add Action and choose Debugger Command.
  4. Enter your custom LLDB command, like print_json myVariable.

Alternatively, you can directly attach a Python script:

(lldb) breakpoint command add 1
(lldb) script print_json(myVariable)

5. Advanced LLDB Tips

  • Aliases: Create aliases for frequently used commands to save time:

    command alias pj po json.parse
    
  • Conditional Breakpoints: Only break when specific conditions are met:

    breakpoint set --name viewDidLoad --condition (self.myVariable == nil)
    
  • Watchpoints: Monitor when a variable’s value changes, useful for tracking down memory bugs:

    watchpoint set variable myVariable
    

Conclusion

LLDB scripting is a powerful tool in the hands of developers looking to streamline their debugging process. By creating custom commands and leveraging LLDB's scripting capabilities, you can save time, reduce errors, and uncover bugs that are hard to spot with standard breakpoints. Experiment with these techniques and start building your custom LLDB toolkit today!

Next Steps

Keep pushing the boundaries of what you can achieve with debugging! What part of your debugging workflow would benefit most from automation next?