Loading...
dyld3/shared-cache/OptimizerObjC.cpp dyld-655.1.1 dyld-733.8
--- dyld/dyld-655.1.1/dyld3/shared-cache/OptimizerObjC.cpp
+++ dyld/dyld-733.8/dyld3/shared-cache/OptimizerObjC.cpp
@@ -36,6 +36,11 @@
 #include "FileAbstraction.hpp"
 #include "MachOFileAbstraction.hpp"
 #include "MachOLoaded.h"
+#include "MachOAnalyzer.h"
+
+#ifndef MH_HAS_OBJC
+  #define MH_HAS_OBJC            0x40000000
+#endif
 
 // Scan a C++ or Swift length-mangled field.
 static bool scanMangledField(const char *&string, const char *end, 
@@ -124,13 +129,13 @@
     uint64_t vmAddrForOnDiskVMAddr(uint64_t vmaddr) {
         if ( _chainedFixups ) {
             dyld3::MachOLoaded::ChainedFixupPointerOnDisk ptr;
-            ptr.raw = vmaddr;
-            assert(ptr.authRebase.bind == 0);
-            if ( ptr.authRebase.auth ) {
-                vmaddr = _cacheUnslideAddr + ptr.authRebase.target;
+            ptr.raw64 = vmaddr;
+            assert(ptr.arm64e.authRebase.bind == 0);
+            if ( ptr.arm64e.authRebase.auth ) {
+                vmaddr = _cacheUnslideAddr + ptr.arm64e.authRebase.target;
             }
             else {
-                vmaddr = ptr.plainRebase.signExtendedTarget();
+                vmaddr = ptr.arm64e.unpackTarget();
             }
         }
         return vmaddr;
@@ -283,6 +288,14 @@
         return (pint_t)element->second;
     }
 
+    void visitCoalescedStrings(const CacheBuilder::CacheCoalescedText& coalescedText) {
+        const CacheBuilder::CacheCoalescedText::StringSection& methodNames = coalescedText.getSectionData("__objc_methname");
+        for (const auto& stringAndOffset : methodNames.stringsToOffsets) {
+            uint64_t vmAddr = methodNames.bufferVMAddr + stringAndOffset.second;
+            _selectorStrings[stringAndOffset.first.data()] = vmAddr;
+        }
+    }
+
     objc_opt::string_map& strings() { 
         return _selectorStrings;
     }
@@ -341,11 +354,13 @@
 private:
     typedef typename P::uint_t pint_t;
 
-    objc_opt::string_map    _protocolNames;
-    objc_opt::protocol_map  _protocols;
-    size_t                  _protocolCount;
-    size_t                  _protocolReferenceCount;
-    Diagnostics&            _diagnostics;
+    objc_opt::string_map                                _protocolNames;
+    objc_opt::legacy_protocol_map                       _protocols;
+    objc_opt::protocol_map                              _protocolsAndHeaders;
+    size_t                                              _protocolCount;
+    size_t                                              _protocolReferenceCount;
+    Diagnostics&                                        _diagnostics;
+    HeaderInfoOptimizer<P, objc_header_info_ro_t<P>>&   _hInfos;
 
     friend class ProtocolReferenceWalker<P, ProtocolOptimizer<P>>;
 
@@ -360,8 +375,8 @@
 
 public:
 
-    ProtocolOptimizer(Diagnostics& diag)
-        : _protocolCount(0), _protocolReferenceCount(0), _diagnostics(diag) {
+    ProtocolOptimizer(Diagnostics& diag, HeaderInfoOptimizer<P, objc_header_info_ro_t<P>>& hinfos)
+        : _protocolCount(0), _protocolReferenceCount(0), _diagnostics(diag), _hInfos(hinfos) {
     }
 
     void addProtocols(ContentAccessor* cache, const macho_header<P>* header)
@@ -378,13 +393,17 @@
                     _diagnostics.error("objc protocol is too big");
                     return;
                 }
-
                 uint64_t name_vmaddr = cache->vmAddrForContent((void*)name);
                 uint64_t proto_vmaddr = cache->vmAddrForContent(proto);
                 _protocolNames.insert(objc_opt::string_map::value_type(name, name_vmaddr));
-                _protocols.insert(objc_opt::protocol_map::value_type(name, proto_vmaddr));
+                _protocols.insert(objc_opt::legacy_protocol_map::value_type(name, proto_vmaddr));
                 _protocolCount++;
             }
+
+            // Note down which header this protocol came from.  We'll fill in the proto_vmaddr here later
+            // once we've chosen a single definition for the protocol with this name.
+            uint64_t hinfo_vmaddr = cache->vmAddrForContent(_hInfos.hinfoForHeader(cache, header));
+            _protocolsAndHeaders.insert(objc_opt::class_map::value_type(name, std::pair<uint64_t, uint64_t>(0, hinfo_vmaddr)));
         }
     }
 
@@ -405,9 +424,7 @@
             return "libobjc's read-write section is too small (metadata not optimized)";
         }
 
-        for (objc_opt::protocol_map::iterator iter = _protocols.begin();
-             iter != _protocols.end();
-             ++iter)
+        for (auto iter = _protocols.begin(); iter != _protocols.end(); ++iter)
         {
             objc_protocol_t<P>* oldProto = (objc_protocol_t<P>*)
                 cache->contentForVMAddr(iter->second);
@@ -450,12 +467,19 @@
                 proto->setDemangledName(cache, roName, _diagnostics);
             }
             proto->setFixedUp();
+            proto->setIsCanonical();
 
             // Redirect the protocol table at our new object.
             iter->second = cache->vmAddrForContent(proto);
 
             // Add new rebase entries.
             proto->addPointers(cache, aslrTracker);
+        }
+
+        // Now that we've chosen the canonical protocols, set the duplicate headers to
+        // point to their protocols.
+        for (auto iter = _protocolsAndHeaders.begin(); iter != _protocolsAndHeaders.end(); ++iter) {
+            iter->second.first = _protocols[iter->first];
         }
         
         return NULL;
@@ -467,13 +491,17 @@
         refs.walk(cache, header);
     }
 
-    objc_opt::string_map& protocolNames() { 
+    objc_opt::string_map& protocolNames() {
         return _protocolNames;
     }
 
-    objc_opt::protocol_map& protocols() { 
+    objc_opt::legacy_protocol_map& protocols() {
         return _protocols;
-   }
+    }
+
+    objc_opt::protocol_map& protocolsAndHeaders() {
+        return _protocolsAndHeaders;
+    }
 
     size_t protocolCount() const { return _protocolCount; }
     size_t protocolReferenceCount() const { return _protocolReferenceCount; }
@@ -487,11 +515,87 @@
         return 100;
 }
 
+template <typename P>
+void addObjcSegments(Diagnostics& diag, DyldSharedCache* cache, const mach_header* libobjcMH,
+                     uint8_t* objcReadOnlyBuffer, uint64_t objcReadOnlyBufferSizeAllocated,
+                     uint8_t* objcReadWriteBuffer, uint64_t objcReadWriteBufferSizeAllocated,
+                     uint32_t objcRwFileOffset)
+{
+    // validate there is enough free space to add the load commands
+    const dyld3::MachOAnalyzer* libobjcMA = ((dyld3::MachOAnalyzer*)libobjcMH);
+    uint32_t freeSpace = libobjcMA->loadCommandsFreeSpace();
+    const uint32_t segSize = sizeof(macho_segment_command<P>);
+    if ( freeSpace < 2*segSize ) {
+        diag.warning("not enough space in libojbc.dylib to add load commands for objc optimization regions");
+        return;
+    }
+
+    // find location of LINKEDIT LC_SEGMENT load command, we need to insert new segments before it
+    __block uint8_t* linkeditSeg = nullptr;
+    libobjcMA->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) {
+        if ( strcmp(info.segName, "__LINKEDIT") == 0 )
+            linkeditSeg = (uint8_t*)libobjcMH + info.loadCommandOffset;
+    });
+    if ( linkeditSeg == nullptr ) {
+        diag.warning("__LINKEDIT not found in libojbc.dylib");
+        return;
+    }
+
+    // move load commands to make room to insert two new ones before LINKEDIT segment load command
+    uint8_t* endOfLoadCommands = (uint8_t*)libobjcMH + sizeof(macho_header<P>) + libobjcMH->sizeofcmds;
+    uint32_t remainingSize = (uint32_t)(endOfLoadCommands - linkeditSeg);
+    memmove(linkeditSeg+2*segSize, linkeditSeg, remainingSize);
+
+    // insert new segments
+    macho_segment_command<P>* roSeg = (macho_segment_command<P>*)(linkeditSeg);
+    macho_segment_command<P>* rwSeg = (macho_segment_command<P>*)(linkeditSeg+sizeof(macho_segment_command<P>));
+    roSeg->set_cmd(macho_segment_command<P>::CMD);
+    roSeg->set_cmdsize(segSize);
+    roSeg->set_segname("__OBJC_RO");
+    roSeg->set_vmaddr(cache->unslidLoadAddress() + objcReadOnlyBuffer - (uint8_t*)cache);
+    roSeg->set_vmsize(objcReadOnlyBufferSizeAllocated);
+    roSeg->set_fileoff(objcReadOnlyBuffer - (uint8_t*)cache);
+    roSeg->set_filesize(objcReadOnlyBufferSizeAllocated);
+    roSeg->set_maxprot(VM_PROT_READ);
+    roSeg->set_initprot(VM_PROT_READ);
+    roSeg->set_nsects(0);
+    roSeg->set_flags(0);
+    rwSeg->set_cmd(macho_segment_command<P>::CMD);
+    rwSeg->set_cmdsize(segSize);
+    rwSeg->set_segname("__OBJC_RW");
+    rwSeg->set_vmaddr(cache->unslidLoadAddress() + objcReadWriteBuffer - (uint8_t*)cache);
+    rwSeg->set_vmsize(objcReadWriteBufferSizeAllocated);
+    rwSeg->set_fileoff(objcRwFileOffset);
+    rwSeg->set_filesize(objcReadWriteBufferSizeAllocated);
+    rwSeg->set_maxprot(VM_PROT_WRITE|VM_PROT_READ);
+    rwSeg->set_initprot(VM_PROT_WRITE|VM_PROT_READ);
+    rwSeg->set_nsects(0);
+    rwSeg->set_flags(0);
+
+    // update mach_header to account for new load commands
+    macho_header<P>* mh = (macho_header<P>*)libobjcMH;
+    mh->set_sizeofcmds(mh->sizeofcmds() + 2*segSize);
+    mh->set_ncmds(mh->ncmds()+2);
+
+    // fix up table at start of dyld cache that has pointer into install name for libobjc
+    dyld_cache_image_info* images = (dyld_cache_image_info*)((uint8_t*)cache + cache->header.imagesOffset);
+    uint64_t libobjcUnslidAddress = cache->unslidLoadAddress() + ((uint8_t*)libobjcMH - (uint8_t*)cache);
+    for (uint32_t i=0; i < cache->header.imagesCount; ++i) {
+        if ( images[i].address == libobjcUnslidAddress ) {
+            images[i].pathFileOffset += (2*segSize);
+            break;
+        }
+    }
+}
+
 
 template <typename P>
 void doOptimizeObjC(DyldSharedCache* cache, bool forProduction, CacheBuilder::ASLR_Tracker& aslrTracker,
-                    CacheBuilder::LOH_Tracker& lohTracker,
-                    const std::map<void*, std::string>& missingWeakImports, Diagnostics& diag)
+                    CacheBuilder::LOH_Tracker& lohTracker, const CacheBuilder::CacheCoalescedText& coalescedText,
+                    const std::map<void*, std::string>& missingWeakImports, Diagnostics& diag,
+                    uint8_t* objcReadOnlyBuffer, uint64_t objcReadOnlyBufferSizeUsed, uint64_t objcReadOnlyBufferSizeAllocated,
+                    uint8_t* objcReadWriteBuffer, uint64_t objcReadWriteBufferSizeAllocated,
+                    uint32_t objcRwFileOffset)
 {
     typedef typename P::E           E;
     typedef typename P::uint_t      pint_t;
@@ -509,15 +613,15 @@
     //
     // Find libobjc's empty sections and build list of images with objc metadata
     //
+    __block const mach_header*      libobjcMH = nullptr;
     __block const macho_section<P> *optROSection = nullptr;
-    __block const macho_section<P> *optRWSection = nullptr;
     __block const macho_section<P> *optPointerListSection = nullptr;
     __block std::vector<const macho_header<P>*> objcDylibs;
     cache->forEachImage(^(const mach_header* machHeader, const char* installName) {
         const macho_header<P>* mh = (const macho_header<P>*)machHeader;
         if ( strstr(installName, "/libobjc.") != nullptr ) {
+            libobjcMH = (mach_header*)mh;
             optROSection = mh->getSection("__TEXT", "__objc_opt_ro");
-            optRWSection = mh->getSection("__DATA", "__objc_opt_rw");
             optPointerListSection = mh->getSection("__DATA", "__objc_opt_ptrs");
         }
         if ( mh->getSection("__DATA", "__objc_imageinfo") || mh->getSection("__OBJC", "__image_info") ) {
@@ -529,23 +633,21 @@
         diag.warning("libobjc's read-only section missing (metadata not optimized)");
         return;
     }
-    if ( optRWSection == nullptr ) {
-        diag.warning("libobjc's read/write section missing (metadata not optimized)");
-        return;
-    }
     if ( optPointerListSection == nullptr ) {
         diag.warning("libobjc's pointer list section missing (metadata not optimized)");
         return;
     }
-
-    uint8_t* optROData = (uint8_t*)cacheAccessor.contentForVMAddr(optROSection->addr());
+    // point optROData into space allocated in dyld cache
+    uint8_t* optROData = objcReadOnlyBuffer + objcReadOnlyBufferSizeUsed;
+    size_t optRORemaining = objcReadOnlyBufferSizeAllocated - objcReadOnlyBufferSizeUsed;
+    *((uint32_t*)optROData) = objc_opt::VERSION;
     if ( optROData == nullptr ) {
         diag.warning("libobjc's read-only section has bad content");
         return;
     }
-    size_t optRORemaining = optROSection->size();
-    uint8_t* optRWData = (uint8_t*)cacheAccessor.contentForVMAddr(optRWSection->addr());
-    size_t optRWRemaining = optRWSection->size();
+
+    uint8_t* optRWData = objcReadWriteBuffer;
+    size_t optRWRemaining = objcReadWriteBufferSizeAllocated;
     if (optRORemaining < headerSize) {
         diag.warning("libobjc's read-only section is too small (metadata not optimized)");
         return;
@@ -581,7 +683,7 @@
     //
     // First the RO header info
     // log("writing out %d RO dylibs at offset %d", (uint32_t)objcDylibs.size(), (uint32_t)(optROSection->size() - optRORemaining));
-    uint64_t hinfoROVMAddr = optROSection->addr() + optROSection->size() - optRORemaining;
+    uint64_t hinfoROVMAddr = cacheAccessor.vmAddrForContent(optROData);
     HeaderInfoOptimizer<P, objc_header_info_ro_t<P>> hinfoROOptimizer;
     const char* err = hinfoROOptimizer.init((uint32_t)objcDylibs.size(), optROData, optRORemaining);
     if (err) {
@@ -596,7 +698,7 @@
 
     // Then the RW header info
     // log("writing out %d RW dylibs at offset %d", (uint32_t)objcDylibs.size(), (uint32_t)(optRWSection->size() - optRWRemaining));
-    uint64_t hinfoRWVMAddr = (uint64_t)optRWSection->addr() + (uint64_t)optRWSection->size() - optRWRemaining;
+    uint64_t hinfoRWVMAddr = cacheAccessor.vmAddrForContent(optRWData);
     HeaderInfoOptimizer<P, objc_header_info_rw_t<P>> hinfoRWOptimizer;
     err = hinfoRWOptimizer.init((uint32_t)objcDylibs.size(), optRWData, optRWRemaining);
     if (err) {
@@ -621,6 +723,39 @@
     ObjCSelectorUniquer<P> uniq(&cacheAccessor);
     std::vector<const macho_header<P>*> sizeSortedDylibs = objcDylibs;
     std::sort(sizeSortedDylibs.begin(), sizeSortedDylibs.end(),  [](const macho_header<P>* lmh, const macho_header<P>* rmh) -> bool {
+        // Sort a select few heavy hitters first.
+        auto getPriority = [](const char* installName) -> int {
+            if (!strcmp(installName, "/usr/lib/libobjc.A.dylib"))
+                return 0;
+            if (!strcmp(installName, "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation") ||
+                !strcmp(installName, "/System/Library/Frameworks/Foundation.framework/Foundation"))
+                return 1;
+            if (!strcmp(installName, "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation") ||
+                !strcmp(installName, "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"))
+                return 2;
+            // Note we don't sort iOSMac UIKitCore early as we want iOSMac after macOS.
+            if (!strcmp(installName, "/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore"))
+                return 3;
+            if (!strcmp(installName, "/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit"))
+                return 4;
+            if (!strcmp(installName, "/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork") ||
+                !strcmp(installName, "/System/Library/Frameworks/CFNetwork.framework/CFNetwork"))
+                return 5;
+            return INT_MAX;
+        };
+
+        // Sort by priority first
+        int priorityA = getPriority(((const dyld3::MachOFile*)lmh)->installName());
+        int priorityB = getPriority(((const dyld3::MachOFile*)rmh)->installName());
+        if (priorityA != priorityB)
+            return priorityA < priorityB;
+
+        // Sort mac before iOSMac
+        bool isIOSMacA = strncmp(((const dyld3::MachOFile*)lmh)->installName(), "/System/iOSSupport/", 19) == 0;
+        bool isIOSMacB = strncmp(((const dyld3::MachOFile*)rmh)->installName(), "/System/iOSSupport/", 19) == 0;
+        if (isIOSMacA != isIOSMacB)
+            return !isIOSMacA;
+        
         const macho_section<P>* lSection = lmh->getSection("__TEXT", "__objc_methname");
         const macho_section<P>* rSection = rmh->getSection("__TEXT", "__objc_methname");
         uint64_t lSelectorSize = (lSection ? lSection->size() : 0);
@@ -628,7 +763,12 @@
         return lSelectorSize > rSelectorSize;
     });
 
+    auto alignPointer = [](uint8_t* ptr) -> uint8_t* {
+        return (uint8_t*)(((uintptr_t)ptr + 0x7) & ~0x7);
+    };
+
     SelectorOptimizer<P, ObjCSelectorUniquer<P> > selOptimizer(uniq);
+    selOptimizer.visitCoalescedStrings(coalescedText);
     for (const macho_header<P>* mh : sizeSortedDylibs) {
         LegacySelectorUpdater<P, ObjCSelectorUniquer<P>>::update(&cacheAccessor, mh, uniq);
         selOptimizer.optimize(&cacheAccessor, mh);
@@ -637,7 +777,7 @@
     diag.verbose("  uniqued  %6lu selectors\n", uniq.strings().size());
     diag.verbose("  updated  %6lu selector references\n", uniq.count());
 
-    uint64_t seloptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining;
+    uint64_t seloptVMAddr = cacheAccessor.vmAddrForContent(optROData);
     objc_opt::objc_selopt_t *selopt = new(optROData) objc_opt::objc_selopt_t;
     err = selopt->write(seloptVMAddr, optRORemaining, uniq.strings());
     if (err) {
@@ -645,6 +785,7 @@
         return;
     }
     optROData += selopt->size();
+    optROData = alignPointer(optROData);
     optRORemaining -= selopt->size();
     uint32_t seloptCapacity = selopt->capacity;
     uint32_t seloptOccupied = selopt->occupied;
@@ -658,20 +799,24 @@
     // 
     // Detect classes that have missing weak-import superclasses.
     // 
-    // Production only. Development cache does not do this: a replacement 
-    // library could omit a class at runtime that was present during 
-    // cache construction.
+    // Production shared caches don't support roots so we can set this and know
+    // there will definitely not be missing weak superclasses at runtime.
+    // Development shared caches can set this bit as the objc runtime only trusts
+    // this bit if there are no roots at runtime.
     // 
     // This is SAFE: the binaries themselves are unmodified.
-    bool noMissingWeakSuperclasses = false; // dev cache can't promise otherwise
+    WeakClassDetector<P> weakopt;
+    bool noMissingWeakSuperclasses = weakopt.noMissingWeakSuperclasses(&cacheAccessor,
+                                                                       missingWeakImports,
+                                                                       sizeSortedDylibs);
+
     if (forProduction) {
-        WeakClassDetector<P> weakopt;
-        noMissingWeakSuperclasses = 
-            weakopt.noMissingWeakSuperclasses(&cacheAccessor, missingWeakImports, sizeSortedDylibs);
-
-        // Shared cache does not currently support unbound weak references. 
-        // Here we assert that there are none. If support is added later then 
+        // Shared cache does not currently support unbound weak references.
+        // Here we assert that there are none. If support is added later then
         // this assertion needs to be removed and this path needs to be tested.
+        // FIXME: The internal cache also isn't going to notice that an on-disk
+        // dylib could resolve a weak bind from the shared cache.  Should we just
+        // error on all caches, regardless of dev/customer?
         if (!noMissingWeakSuperclasses) {
             diag.error("Some Objective-C class has a superclass that is "
                        "weak-import and missing from the cache.");
@@ -691,7 +836,7 @@
 
     diag.verbose("  recorded % 6ld classes\n", classes.classNames().size());
 
-    uint64_t clsoptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining;
+    uint64_t clsoptVMAddr = cacheAccessor.vmAddrForContent(optROData);
     objc_opt::objc_clsopt_t *clsopt = new(optROData) objc_opt::objc_clsopt_t;
     err = clsopt->write(clsoptVMAddr, optRORemaining, 
                         classes.classNames(), classes.classes(), false);
@@ -700,6 +845,7 @@
         return;
     }
     optROData += clsopt->size();
+    optROData = alignPointer(optROData);
     optRORemaining -= clsopt->size();
     size_t duplicateCount = clsopt->duplicateCount();
     uint32_t clsoptCapacity = clsopt->capacity;
@@ -732,7 +878,7 @@
     // This is SAFE: no protocol references are updated yet
     // This must be done AFTER updating method lists.
 
-    ProtocolOptimizer<P> protocolOptimizer(diag);
+    ProtocolOptimizer<P> protocolOptimizer(diag, hinfoROOptimizer);
     for (const macho_header<P>* mh : sizeSortedDylibs) {
         protocolOptimizer.addProtocols(&cacheAccessor, mh);
     }
@@ -750,24 +896,29 @@
         return;
     }
 
-    uint64_t protocoloptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining;
-    objc_opt::objc_protocolopt_t *protocolopt = new (optROData) objc_opt::objc_protocolopt_t;
-    err = protocolopt->write(protocoloptVMAddr, optRORemaining, 
-                             protocolOptimizer.protocolNames(), 
-                             protocolOptimizer.protocols(), true);
+    // Align the buffer again.  The new protocols may have added an odd number of name characters
+    optROData = alignPointer(optROData);
+
+    // New protocol table which tracks loaded images.
+    uint64_t protocoloptVMAddr = cacheAccessor.vmAddrForContent(optROData);
+    objc_opt::objc_protocolopt2_t *protocolopt = new (optROData) objc_opt::objc_protocolopt2_t;
+    err = protocolopt->write(protocoloptVMAddr, optRORemaining,
+                             protocolOptimizer.protocolNames(),
+                             protocolOptimizer.protocolsAndHeaders(), false);
     if (err) {
         diag.warning("%s", err);
         return;
     }
     optROData += protocolopt->size();
+    optROData = alignPointer(optROData);
     optRORemaining -= protocolopt->size();
     uint32_t protocoloptCapacity = protocolopt->capacity;
     uint32_t protocoloptOccupied = protocolopt->occupied;
     protocolopt->byteswap(E::little_endian), protocolopt = NULL;
 
     diag.verbose("  protocol table occupancy %u/%u (%u%%)\n",
-               protocoloptOccupied, protocoloptCapacity, 
-               (unsigned)(protocoloptOccupied/(double)protocoloptCapacity*100));
+                 protocoloptOccupied, protocoloptCapacity,
+                 (unsigned)(protocoloptOccupied/(double)protocoloptCapacity*100));
 
 
     // Redirect protocol references to the uniqued protocols.
@@ -815,25 +966,32 @@
     }
 
 
-    // Success. Update RO header last.
-    E::set32(optROHeader->flags, headerFlags);
-    E::set32(optROHeader->selopt_offset, (uint32_t)(seloptVMAddr - optROSection->addr()));
-    E::set32(optROHeader->clsopt_offset, (uint32_t)(clsoptVMAddr - optROSection->addr()));
-    E::set32(optROHeader->protocolopt_offset, (uint32_t)(protocoloptVMAddr - optROSection->addr()));
-    E::set32(optROHeader->headeropt_ro_offset, (uint32_t)(hinfoROVMAddr - optROSection->addr()));
-    E::set32(optROHeader->headeropt_rw_offset, (uint32_t)(hinfoRWVMAddr - optROSection->addr()));
+    // Success. Update __objc_opt_ro section in libobjc.dylib to contain offsets to generated optimization structures
+    objc_opt::objc_opt_t* libROHeader = (objc_opt::objc_opt_t *)cacheAccessor.contentForVMAddr(optROSection->addr());
+    E::set32(libROHeader->flags, headerFlags);
+    E::set32(libROHeader->selopt_offset, (uint32_t)(seloptVMAddr - optROSection->addr()));
+    E::set32(libROHeader->clsopt_offset, (uint32_t)(clsoptVMAddr - optROSection->addr()));
+    E::set32(libROHeader->unused_protocolopt_offset, 0);
+    E::set32(libROHeader->headeropt_ro_offset, (uint32_t)(hinfoROVMAddr - optROSection->addr()));
+    E::set32(libROHeader->headeropt_rw_offset, (uint32_t)(hinfoRWVMAddr - optROSection->addr()));
+    E::set32(libROHeader->protocolopt_offset, (uint32_t)(protocoloptVMAddr - optROSection->addr()));
 
     // Log statistics.
-    size_t roSize = optROSection->size() - optRORemaining;
-    size_t rwSize = optRWSection->size() - optRWRemaining;
-    diag.verbose("  %lu/%llu bytes (%d%%) used in libobjc read-only optimization section\n",
-                  roSize, optROSection->size(), percent(roSize, optROSection->size()));
-    diag.verbose("  %lu/%llu bytes (%d%%) used in libobjc read/write optimization section\n",
-                  rwSize, optRWSection->size(), percent(rwSize, optRWSection->size()));
+    size_t roSize = objcReadOnlyBufferSizeAllocated - optRORemaining;
+    size_t rwSize = objcReadWriteBufferSizeAllocated - optRWRemaining;
+    diag.verbose("  %lu/%llu bytes (%d%%) used in shared cache read-only optimization region\n",
+                  roSize, objcReadOnlyBufferSizeAllocated, percent(roSize, objcReadOnlyBufferSizeAllocated));
+    diag.verbose("  %lu/%llu bytes (%d%%) used in shared cache read/write optimization region\n",
+                  rwSize, objcReadWriteBufferSizeAllocated, percent(rwSize, objcReadWriteBufferSizeAllocated));
     diag.verbose("  wrote objc metadata optimization version %d\n", objc_opt::VERSION);
+
+    // Add segments to libobjc.dylib that cover cache builder allocated r/o and r/w regions
+    addObjcSegments<P>(diag, cache, libobjcMH, objcReadOnlyBuffer, objcReadOnlyBufferSizeAllocated, objcReadWriteBuffer, objcReadWriteBufferSizeAllocated, objcRwFileOffset);
+
 
     // Now that objc has uniqued the selector references, we can apply the LOHs so that ADRP/LDR -> ADRP/ADD
     if (forProduction) {
+        const bool logSelectors = false;
         uint64_t lohADRPCount = 0;
         uint64_t lohLDRCount = 0;
 
@@ -862,7 +1020,8 @@
 
                         if (pass == 0) {
                             if ( (newPage21 > 2097151) || (newPage21 < -2097151) ) {
-                                diag.verbose("Out of bounds ADRP selector reference target\n");
+                                if (logSelectors)
+                                    fprintf(stderr, "Out of bounds ADRP selector reference target\n");
                                 instructions.clear();
                                 break;
                             }
@@ -881,13 +1040,15 @@
                         if (pass == 0) {
                             if ( (instruction & 0xC0C00000) != 0xC0400000 ) {
                                 // Not a load, or dest reg isn't xN, or uses sign extension
-                                diag.verbose("Bad LDR for selector reference optimisation\n");
+                                if (logSelectors)
+                                    fprintf(stderr, "Bad LDR for selector reference optimisation\n");
                                 instructions.clear();
                                 break;
                             }
                             if ( (instruction & 0x04000000) != 0 ) {
                                 // Loading a float
-                                diag.verbose("Bad LDR for selector reference optimisation\n");
+                                if (logSelectors)
+                                    fprintf(stderr, "Bad LDR for selector reference optimisation\n");
                                 instructions.clear();
                                 break;
                             }
@@ -912,12 +1073,14 @@
                     if ( (instruction & 0xFFC00000) == 0x91000000 ) {
                         // ADD imm12
                         // We don't support ADDs.
-                        diag.verbose("Bad ADD for selector reference optimisation\n");
+                        if (logSelectors)
+                            fprintf(stderr, "Bad ADD for selector reference optimisation\n");
                         instructions.clear();
                         break;
                     }
 
-                    diag.verbose("Unknown instruction for selref optimisation\n");
+                    if (logSelectors)
+                        fprintf(stderr, "Unknown instruction for selref optimisation\n");
                     instructions.clear();
                     break;
                 }
@@ -941,10 +1104,36 @@
 
 void CacheBuilder::optimizeObjC()
 {
+    uint32_t objcRwFileOffset = (uint32_t)((_objcReadWriteBuffer - _readWriteRegion.buffer) + _readWriteRegion.cacheFileOffset);
     if ( _archLayout->is64 )
-        doOptimizeObjC<Pointer64<LittleEndian>>((DyldSharedCache*)_readExecuteRegion.buffer, _options.optimizeStubs, _aslrTracker, _lohTracker, _missingWeakImports, _diagnostics);
+        doOptimizeObjC<Pointer64<LittleEndian>>((DyldSharedCache*)_readExecuteRegion.buffer, _options.optimizeStubs, _aslrTracker, _lohTracker,
+                                                _coalescedText, _missingWeakImports,
+                                                _diagnostics, _objcReadOnlyBuffer, _objcReadOnlyBufferSizeUsed, _objcReadOnlyBufferSizeAllocated,
+                                                _objcReadWriteBuffer, _objcReadWriteBufferSizeAllocated, objcRwFileOffset);
     else
-        doOptimizeObjC<Pointer32<LittleEndian>>((DyldSharedCache*)_readExecuteRegion.buffer, _options.optimizeStubs, _aslrTracker, _lohTracker, _missingWeakImports, _diagnostics);
+        doOptimizeObjC<Pointer32<LittleEndian>>((DyldSharedCache*)_readExecuteRegion.buffer, _options.optimizeStubs, _aslrTracker, _lohTracker,
+                                                _coalescedText, _missingWeakImports,
+                                                _diagnostics, _objcReadOnlyBuffer, _objcReadOnlyBufferSizeUsed, _objcReadOnlyBufferSizeAllocated,
+                                                _objcReadWriteBuffer, _objcReadWriteBufferSizeAllocated, objcRwFileOffset);
 }
 
-
+static uint32_t hashTableSize(uint32_t maxElements, uint32_t perElementData)
+{
+    uint32_t elementsWithPadding = maxElements*11/10; // if close to power of 2, perfect hash may fail, so don't get within 10% of that
+    uint32_t powTwoCapacity = 1 << (32 - __builtin_clz(elementsWithPadding - 1));
+    uint32_t headerSize = 4*(8+256);
+    return headerSize + powTwoCapacity/2 + powTwoCapacity + powTwoCapacity*perElementData;
+}
+
+// The goal here is to allocate space in the dyld shared cache (while it is being laid out) that will contain
+// the objc structures that previously were in the __objc_opt_ro section.
+uint32_t CacheBuilder::computeReadOnlyObjC(uint32_t selRefCount, uint32_t classDefCount, uint32_t protocolDefCount)
+{
+    return 0xA000 + hashTableSize(selRefCount, 5) + hashTableSize(classDefCount, 12) + hashTableSize(protocolDefCount, 8);
+}
+
+// Space to replace the __objc_opt_rw section.
+uint32_t CacheBuilder::computeReadWriteObjC(uint32_t imageCount, uint32_t protocolDefCount)
+{
+    return 8*imageCount + protocolDefCount*12*(_archLayout->is64 ? 8 : 4);
+}