# LLDB Command Reference

Companion to the `debugging-instruments` skill. Commands are typed at the
`(lldb)` prompt in Xcode's console or in a standalone `lldb` session.

## Printing Values: `v`, `p`, `po`

| Command | How it works | Runs code in the target? | Speed |
|---------|--------------|--------------------------|-------|
| `v` | Reads the variable from the frame's memory | No | Fast |
| `p` | Evaluates the expression, formats the result with LLDB's data formatters | Yes | Medium |
| `po` | Evaluates the expression, then asks the object for its description | Yes | Slow |

`po` shows what `CustomDebugStringConvertible` (`debugDescription`) returns.
`p` shows the static type followed by the formatted value.

`v` understands subscripts and member paths:

```text
(lldb) v invoices[0]
(lldb) v self.store.selection
```

Default to `v`. Switch to `po` when you want the type's own description, and
to `p` when you want to see the type as well.

## Registers and Raw Memory

```text
(lldb) register read                 # every register
(lldb) register read x0 x1           # two ARM64 argument registers
(lldb) memory read 0x16fdfe2a0
(lldb) memory read --size 1 --format x --count 32 0x16fdfe2a0   # 32 hex bytes
```

## Breakpoint Recipes

```text
(lldb) br set -f InvoiceStore.swift -l 57 -c "invoice.total < 0"
(lldb) br set -f InvoiceStore.swift -l 57 --one-shot true     # removed after the first hit
(lldb) br set -n "Billing.InvoiceStore.reload()"              # fully qualified Swift name
(lldb) br set -r "Billing\..*\.deinit"                         # every deinit in a module
```

Attach actions to a breakpoint:

```text
(lldb) br command add 1
> po "reload hit at \(Date())"
> bt
> continue
> DONE
```

### Logpoints

Print on every hit without stopping:

```text
(lldb) br set -f InvoiceStore.swift -l 57
(lldb) breakpoint modify --auto-continue true 1
(lldb) br command add 1
> po invoice.number
> DONE
```

This is the command-line form of Xcode's "Log Message" action with
"Automatically continue after evaluating actions" checked.

### Housekeeping

```text
(lldb) br list
(lldb) br enable 1
(lldb) br modify --ignore-count 5 1    # skip the first five hits
(lldb) br delete 1
(lldb) br delete             # no id: deletes every breakpoint (asks first)
```

## Expressions

Swift expressions can contain closures and several statements:

```text
(lldb) e invoices.filter { $0.isOverdue }.count
(lldb) e let summary = store.summary(); print(summary)
(lldb) expression -l swift -- import Foundation
(lldb) expression -l swift -- self.navigationItem.title = "Debug"
```

Objective-C context is handy for UIKit internals:

```text
(lldb) expression -l objc -- (void)[[UIApplication sharedApplication] _performMemoryWarning]
(lldb) expression -l objc -- (BOOL)[(id)0x10a3c4f00 isKindOfClass:[UIView class]]
(lldb) expression -l objc -- (void)[0x10a3c4f00 recursiveDescription]
```

The first line calls a private method to simulate a memory warning; use it only
while debugging. In Simulator, Debug > Simulate Memory Warning does the same.
`recursiveDescription` prints the view tree under the given view.

### Changing the UI while paused

```text
(lldb) expression -l swift -- self.statusLabel.text = "Paid"
(lldb) expression -l swift -- self.statusLabel.backgroundColor = UIColor.systemTeal
(lldb) expression -l objc -- (void)[CATransaction flush]
```

### Calling functions

```text
(lldb) expression self.store.clearCache()
(lldb) expression UserDefaults.standard.set(true, forKey: "showOnboarding")
(lldb) expression NotificationCenter.default.post(name: .init("SessionExpired"), object: nil)
```

## Watchpoints

```text
(lldb) w set v self.balance -w read_write      # reads and writes
(lldb) w set e -- 0x600003d04f10               # raw address
```

Apple Silicon offers four hardware watchpoints. Spend them carefully and delete
the ones you no longer need.

## Moving Through Threads and Frames

```text
(lldb) bt 5                  # top five frames only
(lldb) frame info            # where am I
(lldb) frame variable        # every variable in this frame
(lldb) up                    # one frame toward the caller
(lldb) down                  # one frame back toward the callee
(lldb) thread return         # leave the current function now
(lldb) thread return false   # leave a Bool-returning function with false
```

`thread return` skips the rest of a function. Use it to step past a line that
would crash or to try the other branch of a decision without rebuilding.

## Memory Inspection

```text
(lldb) memory read --size 4 --format x --count 8 0x600003d04f10   # eight 4-byte hex words
(lldb) memory read -f s 0x600003d04f10             # a C string
(lldb) memory find 0x600003d04000 0x600003d05000 -e "DEADBEEF"
(lldb) image lookup -a 0x0000000104a1c3b8          # symbol at this address
(lldb) image lookup -n InvoiceStore.reload         # address of this symbol
(lldb) image list                                  # loaded images
```

Inspecting Swift values at runtime:

```text
(lldb) e print(type(of: store.current))
(lldb) e dump(store.current)
(lldb) e Mirror(reflecting: store.current).children.map { $0.label }
```

## Type Summaries

Short summaries make `v` and `p` output easier to read. Put them in
`~/.lldbinit`:

```text
type summary add -s "(${var.latitude}, ${var.longitude})" CLLocationCoordinate2D
type summary add -s "ref+${var._time}" Foundation.Date
type summary add -s "#${var.number} ${var.total}" Billing.Invoice
```

The `Date` line prints the raw seconds since the reference date
(`timeIntervalSinceReferenceDate`); the stored field name can change between
toolchains, so check it with `v -R` first.

## Python Scripting

One-liners:

```text
(lldb) script import os
(lldb) script print(os.getpid())
(lldb) script print(lldb.debugger.GetSelectedTarget().GetProcess().GetNumThreads())
```

A custom command in `~/lldb_commands/window_tree.py`:

```python
import lldb

def window_tree(debugger, command, result, internal_dict):
    frame = (debugger.GetSelectedTarget()
                     .GetProcess()
                     .GetSelectedThread()
                     .GetSelectedFrame())
    options = lldb.SBExpressionOptions()
    options.SetLanguage(lldb.eLanguageTypeObjC)
    value = frame.EvaluateExpression(
        "[[[UIApplication sharedApplication] keyWindow] recursiveDescription]",
        options)
    result.AppendMessage(value.GetObjectDescription() or "no key window")

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand(
        "command script add -f window_tree.window_tree window_tree")
```

Load it from `~/.lldbinit` with
`command script import ~/lldb_commands/window_tree.py`, then type
`window_tree`.

## Symbolic Breakpoints Worth Keeping

| Symbol | Stops on |
|--------|----------|
| `UIViewAlertForUnsatisfiableConstraints` | Auto Layout constraint conflicts |
| `swift_willThrow` | Every Swift `throw` |
| `malloc_error_break` | Heap corruption detected by malloc |
| `_UITraitCollectionChangeObserverNotify` | Trait collection changes |
| `objc_exception_throw` | Every Objective-C exception |
| `-[UIApplication _performMemoryWarning]` | Simulated memory warnings |

`NSInternalInconsistencyException` is an exception name, not a symbol, so a
symbolic breakpoint on it never fires. Catch Foundation assertion failures with
an exception breakpoint (`br set -E objc`, or Xcode's Exception Breakpoint) or
with `objc_exception_throw`, then read the reason with `po $arg1`.

## `.lldbinit`

```text
command alias -- swiftfound e -l swift -- import Foundation
command alias pjson expression -l swift -- print(String(decoding: try! JSONSerialization.data(withJSONObject: %1, options: [.prettyPrinted, .sortedKeys]), as: UTF8.self))
type summary add -x "^.*RawRepresentable$" -s "${var.rawValue}"
settings set target.language swift
settings set frame-format "#${frame.index} ${function.name}{ @ ${line.file.basename}:${line.number}}\n"
```

- `command alias` gives a long command a short name.
- `pjson` pretty-prints any JSON-compatible object passed as its argument
  through `JSONSerialization` with `.prettyPrinted`; `%1` is the argument.
- The `-x` flag makes the summary apply to every type whose name matches the
  regex.
- `frame-format` controls how each frame line looks in `bt` and when stepping.
