Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
</TestAction>
<LaunchAction
buildConfiguration = "SwiftUIDebug"
customLLDBInitFile = "$(SRCROOT)/../Scripts/LLDB/.lldbinit-swiftui"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
</TestAction>
<LaunchAction
buildConfiguration = "SwiftUIDebug"
customLLDBInitFile = "$(SRCROOT)/../Scripts/LLDB/.lldbinit-swiftui"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
</TestAction>
<LaunchAction
buildConfiguration = "SwiftUIDebug"
customLLDBInitFile = "$(SRCROOT)/../Scripts/LLDB/.lldbinit-swiftui"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
16 changes: 12 additions & 4 deletions Example/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ let targets: [Target] = [

// MARK: - Schemes

let swiftUIDisplayListLLDBInitFile: Path =
"../Scripts/LLDB/.lldbinit-swiftui"

func scheme(
name: String,
target: TargetReference,
Expand All @@ -342,7 +345,8 @@ func scheme(
.testableTarget(target: "OpenSwiftUIUITests", parallelization: .enabled),
],
includeTestAction: Bool = true,
runArguments: Arguments = launchArguments
runArguments: Arguments = launchArguments,
customLLDBInitFile: Path? = nil
) -> Scheme {
.scheme(
name: name,
Expand All @@ -358,6 +362,7 @@ func scheme(
: nil,
runAction: .runAction(
configuration: debugConfiguration,
customLLDBInitFile: customLLDBInitFile,
executable: .executable(target),
arguments: runArguments
),
Expand All @@ -382,7 +387,8 @@ let schemes: [Scheme] = [
name: "SUI_Example",
target: "Example",
debugConfiguration: swiftUIDebug,
releaseConfiguration: swiftUIRelease
releaseConfiguration: swiftUIRelease,
customLLDBInitFile: swiftUIDisplayListLLDBInitFile
),
scheme(
name: "OSUI_HostingExample",
Expand All @@ -398,7 +404,8 @@ let schemes: [Scheme] = [
debugConfiguration: swiftUIDebug,
releaseConfiguration: swiftUIRelease,
testableTargets: [],
runArguments: swiftUIHostingLaunchArguments
runArguments: swiftUIHostingLaunchArguments,
customLLDBInitFile: swiftUIDisplayListLLDBInitFile
),
scheme(
name: "OSUI_TestingHost",
Expand All @@ -412,7 +419,8 @@ let schemes: [Scheme] = [
target: "TestingHost",
debugConfiguration: swiftUIDebug,
releaseConfiguration: swiftUIRelease,
includeTestAction: false
includeTestAction: false,
customLLDBInitFile: swiftUIDisplayListLLDBInitFile
),
.scheme(
name: "OSUI_UITests",
Expand Down
1 change: 1 addition & 0 deletions Scripts/LLDB/.lldbinit-swiftui
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
command script import --relative-to-command-file swiftui_displaylist_minimal_description.py
77 changes: 77 additions & 0 deletions Scripts/LLDB/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# SwiftUI Display-List LLDB Commands

`swiftui_displaylist_minimal_description.py` redirects SwiftUI's full
display-list description to its minimal description while debugging.

SwiftUI normally checks `SWIFTUI_PRINT_TREE` and then calls `description`.
The two getters use the same Swift calling convention and return the same type:

```text
$s7SwiftUI11DisplayListV11descriptionSSvg
$s7SwiftUI11DisplayListV18minimalDescriptionSSvg
```

While the process is paused, the script replaces the first arm64 instruction
of `description` with a direct branch to `minimalDescription`. SwiftUI retains
its existing environment-variable check, output prefix, and printing schedule;
only the display-list payload changes from the full form to the minimal form.
No breakpoint is required, so Xcode does not stop for each printed tree.

## Usage

Enable `SWIFTUI_PRINT_TREE=1` in the Xcode Scheme before launching the app.
SwiftUI caches this setting, so changing it after launch requires a relaunch.

Run the app until SwiftUICore is loaded, pause it, then import and enable the
hook:

```text
(lldb) command script import <path-to-checkout>/Scripts/LLDB/swiftui_displaylist_minimal_description.py
(lldb) swiftui-display-list-minimal enable
```

The `SUI_Example`, `SUI_HostingExample`, and `SUI_TestingHost` schemes import
the script automatically through the adjacent `.lldbinit-swiftui` file; for
those schemes, only the `enable` command is needed after pausing.

Continue execution. Tree output now uses `minimalDescription`, for example:

```text
View 0x... at Time(...):
(DL(I:...))
```

Inspect or remove the hook while the process is paused:

```text
(lldb) swiftui-display-list-minimal status
(lldb) swiftui-display-list-minimal disable
```

`disable` restores the exact instruction bytes that were present before
`enable`. A process restart also restores the original SwiftUICore mapping.

## Existing description breakpoints

Xcode can persist script-created breakpoints while dropping their Python
actions. Such a breakpoint stops in `DisplayList.description` and then executes
the full getter when continued.

When enabling the hook, the script temporarily disables any breakpoint location
already resolved at the `description` entry. It restores those locations when
the hook is disabled. A stale symbolic breakpoint can also be removed once from
Xcode's Breakpoint navigator.

## Scope

- The script has been validated against SwiftUI 6.5.4 on an arm64 iOS 18.5
Simulator.
- The hook currently supports arm64 and arm64e targets. It validates instruction
alignment and the direct-branch range before changing memory.
- The getters are private implementation details and may change between OS
releases. Missing or ambiguous symbols leave the process unchanged.
- While enabled, every direct call to `DisplayList.description` is redirected,
not only calls originating from `SWIFTUI_PRINT_TREE`.
- The change exists only in the debugged process. It does not modify the
SwiftUICore binary on disk.
- Disable the hook before detaching LLDB if the process will remain alive.
Loading
Loading