Skip to content

fix(dyld): correct class-dump rsBase on iOS 15 (deadlock + bogus selectors)#1197

Draft
Lessica wants to merge 1 commit into
blacktop:masterfrom
Lessica:fix/dyld-class-dump-rsbase-ios15
Draft

fix(dyld): correct class-dump rsBase on iOS 15 (deadlock + bogus selectors)#1197
Lessica wants to merge 1 commit into
blacktop:masterfrom
Lessica:fix/dyld-class-dump-rsbase-ios15

Conversation

@Lessica
Copy link
Copy Markdown

@Lessica Lessica commented Apr 28, 2026

Summary

Fix class-dump on iOS 15 (and earlier) shared caches. Refs #577.

On iOS 15 DSCs the previous logic broke in two ways:

  1. rsBase (the relative-method-list base) was only computed when the
    objc-opt header version was >= 16. iOS 15 caches still use the legacy
    ObjcOptT optimization header, so rsBase stayed at 0 and every
    relative method-list selector resolved to a garbage address — class-dump
    output looked fine but every selector name was wrong.
  2. GetPartialMacho resolves rsBase via GetOptimizations. For
    libobjc.A.dylib itself this re-enters the optimization lookup
    (getOptimizationsOldgetLibObjCimage.GetPartialMacho on the
    same image) and deadlocks on f.objcOptOnce.

Changes:

  • pkg/dyld/image.go: compute rsBase unconditionally from the
    optimization's relative-method-list base; only add SharedRegionStart
    when the optimization is the LargeSharedCache ObjCOptimizationHeader
    (which stores the base as a cache-relative offset). Apply the same
    correction in both GetMacho and GetPartialMacho. In GetPartialMacho
    also skip the optimization lookup when the image being parsed is
    libobjc.A.dylib, breaking the re-entrancy.
  • pkg/dyld/objc.go: persist objcOptRoAddr in getOptimizationsOld so
    the legacy code path resolves the correct __objc_opt_ro section
    address.

Testing

ipsw dyld class-dump --all <DSC> against the system DSC of each release:

iOS Build Result
15.2 19C57 OK (no deadlock, selectors verified)
16.0 20A362 OK (regression check)
17.0 21A329 OK
18.0 22A3351 OK
26.2 23C55 OK

Spot-checked selector strings on iOS 15 against the on-device classes —
all correct after the fix (random garbage before).

AI Assistance

GitHub Copilot (Claude) helped narrow down the deadlock by tracing the
objcOptOnce re-entry path and drafted the early-return for the
libobjc.A.dylib self-reference case. I personally reviewed every code
change, ran the five-version class-dump --all matrix above, and
verified the resulting selectors against on-device class metadata before
opening this PR.

The previous logic gated rsBase computation on objc opt version >= 16,
which skipped the case where the cache uses the legacy ObjcOptT
optimization header (typical for iOS 15 shared caches). As a result,
class-dump produced relative method lists with a zero base address and
emitted bogus selector offsets.

Compute rsBase unconditionally from the optimization's relative method
lists base address, and only add SharedRegionStart when the optimization
is the new LargeSharedCache *ObjCOptimizationHeader* (which stores the
base as a cache-relative offset). Apply the same correction to both the
full GetMacho path and the GetPartialMacho path. Also persist
objcOptRoAddr in getOptimizationsOld so the legacy code path resolves
the correct __objc_opt_ro section address.

Additionally, GetPartialMacho must not call GetOptimizations when the
partial macho being parsed is libobjc.A.dylib itself: on iOS 15 the
optimization lookup falls back to getOptimizationsOld() which calls
getLibObjC() -> image.GetPartialMacho() on the very same libobjc image,
re-entering f.objcOptOnce and deadlocking. Skip the rsBase resolution in
that self-referential case (libobjc's own selectors do not require it
at the partial-macho stage).
Copilot AI review requested due to automatic review settings April 28, 2026 18:53
@Lessica Lessica marked this pull request as draft April 28, 2026 18:55
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes incorrect Objective-C relative selector resolution and a sync.Once re-entrancy deadlock when running class-dump against iOS 15 (and earlier) dyld shared caches.

Changes:

  • Compute rsBase (relative method-list base) for all objc optimization header versions and only apply SharedRegionStart for the LargeSharedCache ObjCOptimizationHeader format.
  • Prevent a deadlock in GetPartialMacho by skipping optimization lookup when parsing libobjc.A.dylib itself.
  • Persist __objc_opt_ro section address (objcOptRoAddr) in the legacy optimization path.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
pkg/dyld/objc.go Stores __objc_opt_ro VM address so legacy optimization offsets can be resolved correctly.
pkg/dyld/image.go Fixes rsBase computation for iOS 15 legacy headers and avoids objcOptOnce re-entrancy deadlock for libobjc.A.dylib.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Lessica
Copy link
Copy Markdown
Author

Lessica commented Apr 28, 2026

This PR cannot be safely merged. This is because I do not fully understand the AI-generated fix, even though it resolves the issue I encountered: successfully completing class-dump on iOS 15 and earlier versions without causing a deadlock.

I created this PR simply to help others who are having trouble with this. I hope the author can help improve this PR. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants