debugging-lldb-macos-debugging

Comprehensive guide to LLDB debugger for macOS/iOS development. Covers LLDB vs GDB differences, Swift/Objective-C debugging, Xcode integration, command-line usage, breakpoint expressions, watchpoints, Python scripting, and performance improvements (50x faster step-over in 2025).

LLDB macOS Debugging

Last Updated: 2025-10-26

Overview

LLDB is the default debugger for macOS, iOS, and Apple platforms. It's the backend for Xcode's debugger and provides superior Swift/Objective-C debugging compared to GDB. As of 2025, LLDB features 50x faster step-over performance for optimized Swift code.

Core Concepts

LLDB vs GDB

| Feature | GDB | LLDB | |---------|-----|------| | Swift debugging | Limited | Native | | Objective-C | Basic | Excellent | | macOS integration | Poor | Native | | Xcode backend | No | Yes | | Command syntax | Abbreviated | Structured | | Performance (2025) | Baseline | 50x faster step-over |

LLDB Architecture

Debuggee (App/Binary)
  ↓
debugserver (macOS/iOS)
  ↓
LLDB (debugger)
  ↓
Xcode UI / Command Line

Installation & Setup

Verify Installation

# Check LLDB version
lldb --version
# lldb-1600.0.0  (2025 version)

# Check Xcode Command Line Tools
xcode-select -p

# Install if missing
xcode-select --install

Command-Line Launch

# Launch LLDB with binary
lldb ./my_program

# Launch with arguments
lldb -- ./my_program arg1 arg2

# Attach to running process
lldb -p <pid>
lldb -n ProcessName

# Load and run immediately
lldb -o "run" ./my_program

Command Syntax: LLDB vs GDB

Structured Commands

LLDB uses structured syntax: <command> <subcommand> [options]

GDB                         →  LLDB
────────────────────────────────────────────────
break main                  →  breakpoint set -n main
break file.c:42             →  breakpoint set -f file.c -l 42
continue                    →  continue (same)
step                        →  thread step-in
next                        →  thread step-over
finish                      →  thread step-out
backtrace                   →  thread backtrace
print var                   →  expr var / p var
run                         →  process launch / run
attach <pid>                →  process attach -p <pid>
info breakpoints            →  breakpoint list

Aliases

LLDB provides GDB-compatible aliases:

(lldb) run          # Alias for process launch
(lldb) r            # Short form
(lldb) step         # Alias for thread step-in
(lldb) s            # Short form
(lldb) next         # Alias for thread step-over
(lldb) n            # Short form
(lldb) continue     # Same as GDB
(lldb) c            # Short form

Breakpoints

Setting Breakpoints

Function breakpoints:

# Swift function
(lldb) breakpoint set -n MyClass.myMethod
(lldb) br s -n MyClass.myMethod
(lldb) b MyClass.myMethod

# Objective-C method
(lldb) breakpoint set -n "-[MyClass myMethod:]"
(lldb) b -[MyClass myMethod:]

# C function
(lldb) breakpoint set -n main
(lldb) b main

# Regex breakpoint
(lldb) breakpoint set -r 'MyClass.*'
(lldb) rb 'calculate.*'

File and line breakpoints:

# Break at file:line
(lldb) breakpoint set -f ViewController.swift -l 42
(lldb) br s -f ViewController.swift -l 42
(lldb) b ViewController.swift:42

# Break in current file
(lldb) breakpoint set -l 100

Conditional breakpoints:

# Break if condition true
(lldb) breakpoint set -n process_data -c 'count > 100'
(lldb) br s -n myMethod -c 'userName == "admin"'

# Modify existing breakpoint
(lldb) breakpoint modify -c 'index >= 5' 1

Managing Breakpoints

# List all breakpoints
(lldb) breakpoint list
(lldb) br l

# Disable/enable breakpoint
(lldb) breakpoint disable 1
(lldb) breakpoint enable 1

# Delete breakpoint
(lldb) breakpoint delete 1
(lldb) br del 1

# Delete all breakpoints
(lldb) breakpoint delete

# Ignore breakpoint N times
(lldb) breakpoint modify -i 10 1

Watchpoints

Monitor memory changes:

# Watch variable
(lldb) watchpoint set variable myVariable
(lldb) watch set var myVariable

# Watch memory address
(lldb) watchpoint set expression -- 0x7ffeefbff5b0
(lldb) watch set expr -- 0x7ffeefbff5b0

# Watch with condition
(lldb) watchpoint set variable count
(lldb) watchpoint modify -c 'count > 100' 1

# List watchpoints
(lldb) watchpoint list

# Delete watchpoint
(lldb) watchpoint delete 1

Program Control

Running Programs

# Launch program
(lldb) process launch
(lldb) run
(lldb) r

# Launch with arguments
(lldb) process launch -- arg1 arg2
(lldb) run arg1 arg2

# Set arguments before launch
(lldb) settings set target.run-args arg1 arg2
(lldb) run

# Launch with environment variables
(lldb) env VAR=value
(lldb) run

# Launch with input redirection
(lldb) process launch -i input.txt

Stepping Through Code

# Step into (enter functions)
(lldb) thread step-in
(lldb) step
(lldb) s

# Step over (don't enter functions)
(lldb) thread step-over
(lldb) next
(lldb) n

# Step out (finish current function)
(lldb) thread step-out
(lldb) finish

# Step one instruction
(lldb) thread step-inst
(lldb) stepi
(lldb) si

# Continue until line
(lldb) thread until 50

# Continue execution
(lldb) process continue
(lldb) continue
(lldb) c

Performance: 50x Faster Step-Over (2025)

Context: As of 2025, LLDB step-over is 50x faster for optimized Swift code compared to 2020 versions.

Usage:

# Fast step-over in Swift (2025 LLDB)
(lldb) thread step-over  # 50x faster than older LLDB

# Enable performance optimizations
(lldb) settings set target.skip-prologue true
(lldb) settings set target.process.optimization-warnings false

Benchmark (2025 data):

Stepping through 1000 lines of optimized Swift:
- LLDB 2020: ~25 seconds
- LLDB 2025: ~0.5 seconds (50x improvement)

Stack Inspection

Backtraces

# Full backtrace
(lldb) thread backtrace
(lldb) bt

# Backtrace with arguments
(lldb) bt all

# Backtrace all threads
(lldb) thread backtrace all

# Limited backtrace
(lldb) thread backtrace -c 10

Frame Navigation

# Show current frame
(lldb) frame info

# Select frame
(lldb) frame select 2
(lldb) f 2

# Move up/down
(lldb) up
(lldb) down

# Show frame variables
(lldb) frame variable
(lldb) fr v

# Show specific variable
(lldb) frame variable count
(lldb) fr v count

Variable Inspection

Expression Evaluation

# Evaluate expression
(lldb) expression count + 1
(lldb) expr count + 1
(lldb) p count + 1

# Assign variable
(lldb) expr count = 42

# Call function
(lldb) expr -l swift -- print("Debug message")
(lldb) po print("Debug message")  # Swift

# Objective-C call
(lldb) expr -l objc -- (void)[self printDebugInfo]
(lldb) po [self description]

Swift Debugging

Print Swift objects:

# Print description (CustomStringConvertible)
(lldb) po myObject

# Print detailed info
(lldb) frame variable -D myObject
(lldb) fr v -D myObject

# Print type
(lldb) type lookup MyClass

Swift-specific commands:

# Import Swift module
(lldb) expr import MyFramework

# Swift REPL (exit with :quit)
(lldb) repl

Objective-C Debugging

Print Objective-C objects:

# Print description
(lldb) po myObject
(lldb) expr -O -- myObject

# Call method
(lldb) po [myArray count]
(lldb) po [myString uppercaseString]

# Print ivar
(lldb) po myObject->_privateIvar

Memory Examination

# Read memory
(lldb) memory read 0x7ffeefbff5b0
(lldb) x 0x7ffeefbff5b0

# Read with format
(lldb) memory read -f x -c 10 0x7ffeefbff5b0  # 10 hex values
(lldb) x/10xw 0x7ffeefbff5b0  # GDB syntax also works

# Read string
(lldb) memory read -f s 0x100003f80
(lldb) x/s 0x100003f80

# Write memory (dangerous!)
(lldb) memory write 0x7ffeefbff5b0 0x42

Xcode Integration

Xcode Debugger Console

Access LLDB console in Xcode:

  1. Run app (Cmd+R)
  2. Pause execution (breakpoint or pause button)
  3. View > Debug Area > Activate Console (Cmd+Shift+Y)
  4. Type LLDB commands in console

Common Xcode workflows:

# In Xcode console, all LLDB commands work

# Print variable
(lldb) po self.userName

# Modify variable
(lldb) expr self.count = 10

# Continue
(lldb) continue

# Force unwrap Swift optional
(lldb) po myOptional!

# Break on exception
(lldb) breakpoint set -E swift
(lldb) br s -E objc  # Objective-C exceptions

Symbolic Breakpoints in Xcode

  1. Debug Navigator (Cmd+7)
  2. Click + → Symbolic Breakpoint
  3. Symbol: MyClass.myMethod or -[MyClass myMethod:]
  4. Condition: count > 10
  5. Action: Log message, run script, etc.

View Debugging

Xcode View Debugger (3D UI hierarchy):

  1. Run app
  2. Debug > View Debugging > Capture View Hierarchy
  3. Inspect view properties in inspector

LLDB view commands:

# Print view hierarchy (UIKit)
(lldb) po UIApplication.shared.keyWindow?.rootViewController?.view.recursiveDescription()

# Print responder chain
(lldb) po [UIResponder.firstResponder]

# Modify view
(lldb) expr ((UIView *)0x7f8a3d4050f0).backgroundColor = UIColor.red

Advanced Features

Python Scripting

Load Python script:

(lldb) command script import ~/lldb_scripts/my_script.py

my_script.py:

import lldb

def hello_command(debugger, command, result, internal_dict):
    """Print hello message."""
    print(f"Hello, {command}!")

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f my_script.hello_command hello')
    print("Custom commands loaded")

Swift pretty printer:

import lldb

def swift_array_summary(valobj, internal_dict):
    """Custom summary for Swift Array."""
    count = valobj.GetChildAtIndex(0).GetValueAsUnsigned()
    return f"Array with {count} elements"

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('type summary add -F my_script.swift_array_summary "Swift.Array"')

.lldbinit Customization

~/.lldbinit (global) or ./.lldbinit (project):

# Settings
settings set target.skip-prologue false
settings set target.process.extra-startup-command QSetLogging:bitmask=LOG_ALL;

# Aliases
command alias bfl breakpoint set -f %1 -l %2
command alias bp breakpoint set -n %1

# Auto-import scripts
command script import ~/lldb_scripts/swift_helpers.py

# Break on Swift errors
breakpoint set -E swift

# Stop on Objective-C exceptions
breakpoint set -E objc

Custom Commands

# Create alias
(lldb) command alias bfl breakpoint set -f %1 -l %2

# Use alias
(lldb) bfl ViewController.swift 42

# Create regex command
(lldb) command regex jump 's/(.+)/thread jump --by %1/'

# Use regex
(lldb) jump 5  # Jump forward 5 lines

Remote Debugging

iOS Device Debugging

Xcode handles this automatically, but for manual setup:

# Connect device via USB
# Start debugserver on device (requires jailbreak or Xcode)

# On Mac:
(lldb) platform select remote-ios
(lldb) platform connect connect://localhost:6666
(lldb) file /path/to/MyApp.app/MyApp
(lldb) run

Remote macOS Debugging

Start debugserver on remote Mac:

# Remote Mac
/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/debugserver localhost:2345 ./my_program

Connect from local Mac:

# Local Mac
(lldb) platform select remote-macosx
(lldb) platform connect connect://192.168.1.100:2345
(lldb) continue

Swift-Specific Debugging

Swift Error Handling

# Break on Swift error throw
(lldb) breakpoint set -E swift
(lldb) br s -E swift

# Break on specific error type
(lldb) breakpoint set -E swift -O "MyError"

Swift Type Inspection

# Inspect type metadata
(lldb) type lookup MyStruct

# Dump type layout
(lldb) type lookup --show-layout MyClass

# Print protocol conformances
(lldb) expr -l swift -- import Foundation
(lldb) expr -l swift -- print(type(of: myObject))

Swift REPL

# Launch Swift REPL
(lldb) repl

# In REPL:
1> import Foundation
2> let url = URL(string: "https://example.com")
3> print(url)
4> :quit

Objective-C-Specific Debugging

Method Breakpoints

# Instance method
(lldb) breakpoint set -n "-[MyClass myMethod:]"
(lldb) b -[MyClass myMethod:]

# Class method
(lldb) breakpoint set -n "+[MyClass classMethod]"
(lldb) b +[MyClass classMethod]

# All methods in class
(lldb) breakpoint set -r '\[MyClass .*\]'

Message Tracing

# Trace all messages to object
(lldb) expr (void)instrumentObjcMessageSends(YES)

# Check /tmp/msgSends-<pid> for log

Performance Optimization

Faster Debugging (2025 Best Practices)

# Enable fast step-over (2025 feature)
(lldb) settings set target.skip-prologue true

# Reduce output verbosity
(lldb) settings set thread.format "thread #${thread.index}: tid = ${thread.id}\n"

# Disable auto-summary for large collections
(lldb) type summary delete "Swift.Array"

# Use hardware breakpoints (limited, but faster)
(lldb) breakpoint set -n main -H

Benchmarking LLDB Performance

# Profile LLDB command execution
time lldb -b -o "run" -o "bt" -o "quit" ./my_program

# Enable LLDB logging
(lldb) log enable lldb all
(lldb) log enable gdb-remote packets

Core Dump Analysis (macOS)

Enable Core Dumps

# macOS: core dumps disabled by default
ulimit -c unlimited

# Set core file location
sudo sysctl -w kern.corefile=/tmp/core.%P

# Check settings
ulimit -c
sysctl kern.corefile

Load Core Dump

# Load core dump
lldb -c /tmp/core.12345 ./my_program

# Or within LLDB:
(lldb) target create ./my_program
(lldb) target core /tmp/core.12345

# Analyze
(lldb) bt
(lldb) frame info
(lldb) thread backtrace all

Multi-threaded Debugging

Thread Commands

# List threads
(lldb) thread list

# Select thread
(lldb) thread select 2

# Backtrace all threads
(lldb) thread backtrace all

# Apply command to all threads
(lldb) thread apply all bt

# Step only current thread
(lldb) thread step-over

Common Workflows

Debugging Swift Crashes

# Run until crash
(lldb) run

# Examine crash
(lldb) thread backtrace
(lldb) frame variable
(lldb) po self

# Check for force-unwrap crash
(lldb) fr v myOptional  # Check if nil

Debugging UI Issues

# Print view hierarchy
(lldb) po UIApplication.shared.keyWindow?.rootViewController?.view.recursiveDescription()

# Modify view background
(lldb) expr myView.backgroundColor = .red

# Continue to see change
(lldb) continue

Debugging Memory Issues

Use Instruments instead of LLDB:

LLDB memory debugging:

# Print retain count (Objective-C)
(lldb) po [myObject retainCount]

# Print all instances of class
(lldb) expr -l objc -- (void)[NSClassFromString(@"MyClass") instancesRespondToSelector:@selector(description)]

Anti-Patterns

Common Mistakes

❌ NEVER: Use GDB for Swift/Objective-C debugging
   → LLDB has native support, GDB doesn't

❌ NEVER: Ignore 50x faster step-over (2025 LLDB)
   → Update Xcode for performance gains

❌ NEVER: Debug optimized Swift without -Onone
   → Variables optimized out, inaccurate stepping

❌ NEVER: Use `po` for non-printable types
   → Use `frame variable` instead

❌ NEVER: Attach to system processes without SIP disabled
   → macOS System Integrity Protection blocks this

❌ NEVER: Modify memory in production apps
   → Undefined behavior, crashes

Best Practices

✅ ALWAYS: Use LLDB for macOS/iOS development
✅ ALWAYS: Update Xcode for latest LLDB features (50x perf)
✅ ALWAYS: Use `po` for Swift/ObjC objects, `fr v` for primitives
✅ ALWAYS: Set symbolic breakpoints in Xcode for convenience
✅ ALWAYS: Use Instruments for memory/performance debugging
✅ ALWAYS: Save .lldbinit for project-specific commands

Related Skills


Summary

LLDB is the standard debugger for macOS/iOS. Key capabilities:

  1. Swift/Objective-C: Native debugging with po, expression evaluation
  2. Performance: 50x faster step-over for optimized Swift (2025)
  3. Xcode integration: Seamless debugging in Xcode with console access
  4. Python scripting: Custom commands, pretty printers
  5. Remote debugging: iOS devices, remote Macs
  6. Structured syntax: breakpoint set, thread step-in, process launch

Quick start:

lldb ./my_program
(lldb) breakpoint set -n main
(lldb) run
(lldb) thread step-over
(lldb) po myVariable
(lldb) thread backtrace

Master LLDB for efficient debugging on Apple platforms.