dyld-832.7.1.tar.gz
[apple/dyld.git] / dyld3 / shared-cache / mrm_shared_cache_builder.cpp
1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
2 *
3 * Copyright (c) 2017 Apple Inc. All rights reserved.
4 *
5 * @APPLE_LICENSE_HEADER_START@
6 *
7 * This file contains Original Code and/or Modifications of Original Code
8 * as defined in and that are subject to the Apple Public Source License
9 * Version 2.0 (the 'License'). You may not use this file except in
10 * compliance with the License. Please obtain a copy of the License at
11 * http://www.opensource.apple.com/apsl/ and read it before using this
12 * file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19 * Please see the License for the specific language governing rights and
20 * limitations under the License.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25 #include "mrm_shared_cache_builder.h"
26 #include "SharedCacheBuilder.h"
27 #include "ClosureFileSystem.h"
28 #include "FileUtils.h"
29 #include "JSONReader.h"
30 #include <pthread.h>
31 #include <memory>
32 #include <vector>
33 #include <map>
34 #include <sys/stat.h>
35
36 #if __has_include(<Cambria/SharedCache.h>)
37 #include <Cambria/SharedCache.h>
38 asm(".linker_option \"-lcambria_sharedcache\"");
39 asm(".linker_option \"-lcodedirectory_static\"");
40 #endif
41
42 static const uint64_t kMinBuildVersion = 1; //The minimum version BuildOptions struct we can support
43 static const uint64_t kMaxBuildVersion = 2; //The maximum version BuildOptions struct we can support
44
45 static const uint32_t MajorVersion = 1;
46 static const uint32_t MinorVersion = 2;
47
48 namespace dyld3 {
49 namespace closure {
50
51 struct FileInfo {
52 const char* path;
53 const uint8_t* data;
54 const uint64_t length;
55 FileFlags flags;
56 uint64_t mtime;
57 uint64_t inode;
58 };
59
60 class FileSystemMRM : public FileSystem {
61 public:
62 FileSystemMRM() : FileSystem() { }
63
64 bool getRealPath(const char possiblePath[MAXPATHLEN], char realPath[MAXPATHLEN]) const override {
65 Diagnostics diag;
66 std::string resolvedPath = symlinkResolver.realPath(diag, possiblePath);
67 if (diag.hasError()) {
68 diag.verbose("MRM error: %s\n", diag.errorMessage().c_str());
69 diag.clearError();
70 return false;
71 }
72
73 // FIXME: Should we only return real paths of files which point to macho's? For now that is what we are doing
74 auto it = fileMap.find(resolvedPath);
75 if (it == fileMap.end())
76 return false;
77
78 memcpy(realPath, resolvedPath.c_str(), std::min((size_t)MAXPATHLEN, resolvedPath.size() + 1));
79 return true;
80 }
81
82 bool loadFile(const char* path, LoadedFileInfo& info, char realerPath[MAXPATHLEN], void (^error)(const char* format, ...)) const override {
83 Diagnostics diag;
84 std::string resolvedPath = symlinkResolver.realPath(diag, path);
85 if (diag.hasError()) {
86 diag.verbose("MRM error: %s\n", diag.errorMessage().c_str());
87 diag.clearError();
88 return false;
89 }
90
91 auto it = fileMap.find(resolvedPath);
92 if (it == fileMap.end())
93 return false;
94
95 if (resolvedPath == path)
96 realerPath[0] = '\0';
97 else
98 memcpy(realerPath, resolvedPath.c_str(), std::min((size_t)MAXPATHLEN, resolvedPath.size() + 1));
99
100 // The file exists at this exact path. Lets use it!
101 const FileInfo& fileInfo = files[it->second];
102
103 info.fileContent = fileInfo.data;
104 info.fileContentLen = fileInfo.length;
105 info.sliceOffset = 0;
106 info.sliceLen = fileInfo.length;
107 info.isOSBinary = true;
108 info.inode = fileInfo.inode;
109 info.mtime = fileInfo.mtime;
110 info.unload = nullptr;
111 info.path = path;
112 return true;
113 }
114
115 void unloadFile(const LoadedFileInfo& info) const override {
116 if (info.unload)
117 info.unload(info);
118 }
119
120 void unloadPartialFile(LoadedFileInfo& info, uint64_t keepStartOffset, uint64_t keepLength) const override {
121 // Note we don't actually unload the data here, but we do want to update the offsets for other data structures to track where we are
122 info.fileContent = (const void*)((char*)info.fileContent + keepStartOffset);
123 info.fileContentLen = keepLength;
124 }
125
126 bool fileExists(const char* path, uint64_t* inode=nullptr, uint64_t* mtime=nullptr,
127 bool* issetuid=nullptr, bool* inodesMatchRuntime = nullptr) const override {
128 Diagnostics diag;
129 std::string resolvedPath = symlinkResolver.realPath(diag, path);
130 if (diag.hasError()) {
131 diag.verbose("MRM error: %s\n", diag.errorMessage().c_str());
132 diag.clearError();
133 return false;
134 }
135
136 auto it = fileMap.find(resolvedPath);
137 if (it == fileMap.end())
138 return false;
139
140 // The file exists at this exact path. Lets use it!
141 const FileInfo& fileInfo = files[it->second];
142 if (inode)
143 *inode = fileInfo.inode;
144 if (mtime)
145 *mtime = fileInfo.mtime;
146 if (issetuid)
147 *issetuid = false;
148 if (inodesMatchRuntime)
149 *inodesMatchRuntime = false;
150 return true;
151 }
152
153 // MRM file APIs
154 bool addFile(const char* path, uint8_t* data, uint64_t size, Diagnostics& diag, FileFlags fileFlags) {
155 auto iteratorAndInserted = fileMap.insert(std::make_pair(path, files.size()));
156 if (!iteratorAndInserted.second) {
157 diag.error("Already have content for path: '%s'", path);
158 return false;
159 }
160
161 symlinkResolver.addFile(diag, path);
162 if (diag.hasError())
163 return false;
164
165 // on iOS, inode is just a placeholder
166 // Note its safe to just use the index here as we only compare it during closure building
167 // and never record it in the closures
168 uint64_t inode = files.size() + 1;
169 uint64_t mtime = 0;
170
171 files.push_back((FileInfo){ path, data, size, fileFlags, mtime, inode });
172 return true;
173 }
174
175 bool addSymlink(const char* fromPath, const char* toPath, Diagnostics& diag) {
176 symlinkResolver.addSymlink(diag, fromPath, toPath);
177 return !diag.hasError();
178 }
179
180 void forEachFileInfo(std::function<void(const char* path, FileFlags fileFlags)> lambda) {
181 for (const FileInfo& fileInfo : files)
182 lambda(fileInfo.path, fileInfo.flags);
183 }
184
185 size_t fileCount() const {
186 return files.size();
187 }
188
189 std::vector<DyldSharedCache::FileAlias> getResolvedSymlinks(Diagnostics& diag) {
190 return symlinkResolver.getResolvedSymlinks(diag);
191 }
192
193 private:
194 std::vector<FileInfo> files;
195 std::map<std::string, uint64_t> fileMap;
196 SymlinkResolver symlinkResolver;
197 };
198
199 } // namespace closure
200 } // namespace dyld3
201
202 struct BuildInstance {
203 std::unique_ptr<DyldSharedCache::CreateOptions> options;
204 std::unique_ptr<SharedCacheBuilder> builder;
205 std::vector<CacheBuilder::InputFile> inputFiles;
206 std::vector<const char*> errors;
207 std::vector<const char*> warnings;
208 std::vector<std::string> errorStrings; // Owns the data for the errors
209 std::vector<std::string> warningStrings; // Owns the data for the warnings
210 uint8_t* cacheData = nullptr;
211 uint64_t cacheSize = 0;
212 std::string jsonMap;
213 std::string macOSMap; // For compatibility with update_dyld_shared_cache's .map file
214 std::string macOSMapPath; // Owns the string for the path
215 std::string cdHash; // Owns the data for the cdHash
216 std::string cdHashType; // Owns the data for the cdHashType
217 std::string uuid; // Owns the data for the uuid
218 };
219
220 struct BuildFileResult {
221 std::string path;
222 const uint8_t* data;
223 uint64_t size;
224 };
225
226 struct TranslationResult {
227 const uint8_t* data;
228 size_t size;
229 std::string cdHash;
230 std::string path;
231 bool bufferWasMalloced;
232 };
233
234 struct MRMSharedCacheBuilder {
235 MRMSharedCacheBuilder(const BuildOptions_v1* options);
236 const BuildOptions_v1* options;
237 dyld3::closure::FileSystemMRM fileSystem;
238
239 std::string dylibOrderFileData;
240 std::string dirtyDataOrderFileData;
241 void* objcOptimizationsFileData;
242 size_t objcOptimizationsFileLength;
243
244 // An array of builders and their options as we may have more than one builder for a given device variant.
245 std::vector<BuildInstance> builders;
246
247 // The paths in all of the caches
248 // We keep this here to own the std::string path data
249 std::map<std::string, std::unordered_set<const BuildInstance*>> dylibsInCaches;
250
251 // The results from all of the builders
252 // We keep this in a vector to own the data.
253 std::vector<FileResult*> fileResults;
254 std::vector<FileResult> fileResultStorage;
255 std::vector<std::pair<uint64_t, bool>> fileResultBuffers;
256
257 // The results from all of the builders
258 // We keep this in a vector to own the data.
259 std::vector<CacheResult*> cacheResults;
260 std::vector<CacheResult> cacheResultStorage;
261
262 #if __has_include(<Cambria/SharedCache.h>)
263 // Storage for translated shared caches
264 std::vector<TranslationResult> translationResults;
265 #endif
266
267 // The files to remove. These are in every copy of the caches we built
268 std::vector<const char*> filesToRemove;
269
270 std::vector<const char*> errors;
271 std::vector<std::string> errorStorage;
272 pthread_mutex_t lock;
273
274 enum State {
275 AcceptingFiles,
276 Building,
277 FinishedBuilding
278 };
279
280 State state = AcceptingFiles;
281
282 void runSync(void (^block)()) {
283 pthread_mutex_lock(&lock);
284 block();
285 pthread_mutex_unlock(&lock);
286 }
287
288 __attribute__((format(printf, 2, 3)))
289 void error(const char* format, ...) {
290 va_list list;
291 va_start(list, format);
292 Diagnostics diag;
293 diag.error(format, list);
294 va_end(list);
295
296 errorStorage.push_back(diag.errorMessage());
297 errors.push_back(errorStorage.back().data());
298 }
299 };
300
301 MRMSharedCacheBuilder::MRMSharedCacheBuilder(const BuildOptions_v1* options)
302 : options(options)
303 , lock(PTHREAD_MUTEX_INITIALIZER)
304 , objcOptimizationsFileData(nullptr)
305 , objcOptimizationsFileLength(0)
306 {
307
308 }
309
310 void validiateBuildOptions(const BuildOptions_v1* options, MRMSharedCacheBuilder& builder) {
311 if (options->version < kMinBuildVersion) {
312 builder.error("Builder version %llu is less than minimum supported version of %llu", options->version, kMinBuildVersion);
313 }
314 if (options->version > kMaxBuildVersion) {
315 builder.error("Builder version %llu is greater than maximum supported version of %llu", options->version, kMaxBuildVersion);
316 }
317 if (!options->updateName) {
318 builder.error("updateName must not be null");
319 }
320 if (!options->deviceName) {
321 builder.error("deviceName must not be null");
322 }
323 switch (options->disposition) {
324 case Disposition::Unknown:
325 case Disposition::InternalDevelopment:
326 case Disposition::Customer:
327 case Disposition::InternalMinDevelopment:
328 break;
329 default:
330 builder.error("unknown disposition value");
331 break;
332 }
333 switch (options->platform) {
334 case Platform::unknown:
335 builder.error("platform must not be unknown");
336 break;
337 case Platform::macOS:
338 case Platform::iOS:
339 case Platform::tvOS:
340 case Platform::watchOS:
341 case Platform::bridgeOS:
342 case Platform::iOSMac:
343 case Platform::iOS_simulator:
344 case Platform::tvOS_simulator:
345 case Platform::watchOS_simulator:
346 break;
347 default:
348 builder.error("unknown platform value");
349 break;
350 }
351 if (!options->archs) {
352 builder.error("archs must not be null");
353 }
354 if (!options->numArchs) {
355 builder.error("numArchs must not be 0");
356 }
357 }
358
359 void getVersion(uint32_t *major, uint32_t *minor) {
360 *major = MajorVersion;
361 *minor = MinorVersion;
362 }
363
364 struct MRMSharedCacheBuilder* createSharedCacheBuilder(const BuildOptions_v1* options) {
365 MRMSharedCacheBuilder* builder = new MRMSharedCacheBuilder(options);
366
367 // Check the option struct values are valid
368 validiateBuildOptions(options, *builder);
369
370 return builder;
371 }
372
373 bool addFile(struct MRMSharedCacheBuilder* builder, const char* path, uint8_t* data, uint64_t size, FileFlags fileFlags) {
374 __block bool success = false;
375 builder->runSync(^() {
376 if (builder->state != MRMSharedCacheBuilder::AcceptingFiles) {
377 builder->error("Cannot add file: '%s' as we have already started building", path);
378 return;
379 }
380 size_t pathLength = strlen(path);
381 if (pathLength == 0) {
382 builder->error("Empty path");
383 return;
384 }
385 if (pathLength >= MAXPATHLEN) {
386 builder->error("Path is too long: '%s'", path);
387 return;
388 }
389 if (data == nullptr) {
390 builder->error("Data cannot be null for file: '%s'", path);
391 return;
392 }
393 switch (fileFlags) {
394 case NoFlags:
395 case MustBeInCache:
396 case ShouldBeExcludedFromCacheIfUnusedLeaf:
397 case RequiredClosure:
398 break;
399 case DylibOrderFile:
400 builder->dylibOrderFileData = std::string((char*)data, size);
401 success = true;
402 return;
403 case DirtyDataOrderFile:
404 builder->dirtyDataOrderFileData = std::string((char*)data, size);
405 success = true;
406 return;
407 case ObjCOptimizationsFile:
408 builder->objcOptimizationsFileData = data;
409 builder->objcOptimizationsFileLength = size;
410 success = true;
411 return;
412 default:
413 builder->error("unknown file flags value");
414 break;
415 }
416 Diagnostics diag;
417 if (!builder->fileSystem.addFile(path, data, size, diag, fileFlags)) {
418 builder->errorStorage.push_back(diag.errorMessage());
419 builder->errors.push_back(builder->errorStorage.back().data());
420 return;
421 }
422 success = true;
423 });
424 return success;
425 }
426
427 bool addSymlink(struct MRMSharedCacheBuilder* builder, const char* fromPath, const char* toPath) {
428 __block bool success = false;
429 builder->runSync(^() {
430 if (builder->state != MRMSharedCacheBuilder::AcceptingFiles) {
431 builder->error("Cannot add file: '%s' as we have already started building", fromPath);
432 return;
433 }
434 size_t pathLength = strlen(fromPath);
435 if (pathLength == 0) {
436 builder->error("Empty path");
437 return;
438 }
439 if (pathLength >= MAXPATHLEN) {
440 builder->error("Path is too long: '%s'", fromPath);
441 return;
442 }
443 Diagnostics diag;
444 if (!builder->fileSystem.addSymlink(fromPath, toPath, diag)) {
445 builder->errorStorage.push_back(diag.errorMessage());
446 builder->errors.push_back(builder->errorStorage.back().data());
447 return;
448 }
449 success = true;
450 });
451 return success;
452 }
453
454 static DyldSharedCache::LocalSymbolsMode platformExcludeLocalSymbols(Platform platform) {
455 switch (platform) {
456 case Platform::unknown:
457 case Platform::macOS:
458 return DyldSharedCache::LocalSymbolsMode::keep;
459 case Platform::iOS:
460 case Platform::tvOS:
461 case Platform::watchOS:
462 case Platform::bridgeOS:
463 return DyldSharedCache::LocalSymbolsMode::unmap;
464 case Platform::iOSMac:
465 case Platform::iOS_simulator:
466 case Platform::tvOS_simulator:
467 case Platform::watchOS_simulator:
468 return DyldSharedCache::LocalSymbolsMode::keep;
469 }
470 }
471
472 static DyldSharedCache::LocalSymbolsMode excludeLocalSymbols(const BuildOptions_v1* options) {
473 if ( options->version >= 2 ) {
474 const BuildOptions_v2* v2 = (const BuildOptions_v2*)options;
475 if ( v2->optimizeForSize )
476 return DyldSharedCache::LocalSymbolsMode::strip;
477 }
478
479 // Old build options always use the platform default
480 return platformExcludeLocalSymbols(options->platform);
481 }
482
483 static bool optimizeDyldDlopens(const BuildOptions_v1* options) {
484 // Old builds always default to dyld3 optimisations
485 if ( options->version < 2 ) {
486 return true;
487 }
488
489 // If we want to optimize for size instead of speed, then disable dyld3 dlopen closures
490 const BuildOptions_v2* v2 = (const BuildOptions_v2*)options;
491 return !v2->optimizeForSize;
492 }
493
494 static DyldSharedCache::CodeSigningDigestMode platformCodeSigningDigestMode(Platform platform) {
495 switch (platform) {
496 case Platform::unknown:
497 case Platform::macOS:
498 case Platform::iOS:
499 case Platform::tvOS:
500 return DyldSharedCache::SHA256only;
501 case Platform::watchOS:
502 return DyldSharedCache::Agile;
503 case Platform::bridgeOS:
504 case Platform::iOSMac:
505 case Platform::iOS_simulator:
506 case Platform::tvOS_simulator:
507 case Platform::watchOS_simulator:
508 return DyldSharedCache::SHA256only;
509 }
510 }
511
512 static bool platformIsForSimulator(Platform platform) {
513 switch (platform) {
514 case Platform::unknown:
515 case Platform::macOS:
516 case Platform::iOS:
517 case Platform::tvOS:
518 case Platform::watchOS:
519 case Platform::bridgeOS:
520 case Platform::iOSMac:
521 return false;
522 case Platform::iOS_simulator:
523 case Platform::tvOS_simulator:
524 case Platform::watchOS_simulator:
525 return true;
526 }
527 }
528
529 static const char* dispositionName(Disposition disposition) {
530 switch (disposition) {
531 case Disposition::Unknown:
532 return "";
533 case Disposition::InternalDevelopment:
534 return "Internal";
535 case Disposition::Customer:
536 return "Customer";
537 case Disposition::InternalMinDevelopment:
538 return "InternalMinDevelopment";
539 }
540 }
541
542 // This is a JSON file containing the list of classes for which
543 // we should try to build IMP caches.
544 dyld3::json::Node parseObjcOptimizationsFile(Diagnostics& diags, const void* data, size_t length) {
545 return dyld3::json::readJSON(diags, data, length);
546 }
547
548 bool runSharedCacheBuilder(struct MRMSharedCacheBuilder* builder) {
549 __block bool success = false;
550 builder->runSync(^() {
551 if (builder->state != MRMSharedCacheBuilder::AcceptingFiles) {
552 builder->error("Builder has already been run");
553 return;
554 }
555 builder->state = MRMSharedCacheBuilder::Building;
556 if (builder->fileSystem.fileCount() == 0) {
557 builder->error("Cannot run builder with no files");
558 }
559
560 __block Diagnostics diag;
561 std::vector<DyldSharedCache::FileAlias> aliases = builder->fileSystem.getResolvedSymlinks(diag);
562 if (diag.hasError()) {
563 diag.verbose("Symlink resolver error: %s\n", diag.errorMessage().c_str());
564 }
565
566 if (!builder->errors.empty()) {
567 builder->error("Skipping running shared cache builder due to previous errors");
568 return;
569 }
570
571 __block std::vector<SharedCacheBuilder::InputFile> inputFiles;
572 builder->fileSystem.forEachFileInfo(^(const char* path, FileFlags fileFlags) {
573 SharedCacheBuilder::InputFile::State state = SharedCacheBuilder::InputFile::Unset;
574 switch (fileFlags) {
575 case FileFlags::NoFlags:
576 state = SharedCacheBuilder::InputFile::Unset;
577 break;
578 case FileFlags::MustBeInCache:
579 state = SharedCacheBuilder::InputFile::MustBeIncluded;
580 break;
581 case FileFlags::ShouldBeExcludedFromCacheIfUnusedLeaf:
582 state = SharedCacheBuilder::InputFile::MustBeExcludedIfUnused;
583 break;
584 case FileFlags::RequiredClosure:
585 state = SharedCacheBuilder::InputFile::MustBeIncluded;
586 break;
587 case FileFlags::DylibOrderFile:
588 case FileFlags::DirtyDataOrderFile:
589 case FileFlags::ObjCOptimizationsFile:
590 builder->error("Order files should not be in the file system");
591 return;
592 }
593 inputFiles.emplace_back((SharedCacheBuilder::InputFile){ path, state });
594 });
595
596 auto addCacheConfiguration = ^(bool isOptimized) {
597 for (uint64_t i = 0; i != builder->options->numArchs; ++i) {
598 // HACK: Skip i386 for macOS
599 if ( (builder->options->platform == Platform::macOS) && (strcmp(builder->options->archs[i], "i386") == 0 ) )
600 continue;
601 auto options = std::make_unique<DyldSharedCache::CreateOptions>((DyldSharedCache::CreateOptions){});
602 const char *cacheSuffix = (isOptimized ? "" : ".development");
603 if ( builder->options->platform == Platform::macOS )
604 cacheSuffix = "";
605 std::string runtimePath = (builder->options->platform == Platform::macOS) ? MACOSX_MRM_DYLD_SHARED_CACHE_DIR : IPHONE_DYLD_SHARED_CACHE_DIR;
606 options->outputFilePath = runtimePath + "dyld_shared_cache_" + builder->options->archs[i] + cacheSuffix;
607 options->outputMapFilePath = options->outputFilePath + ".json";
608 options->archs = &dyld3::GradedArchs::forName(builder->options->archs[i]);
609 options->platform = (dyld3::Platform)builder->options->platform;
610 options->localSymbolMode = excludeLocalSymbols(builder->options);
611 options->optimizeStubs = isOptimized;
612 options->optimizeDyldDlopens = optimizeDyldDlopens(builder->options);
613 options->optimizeDyldLaunches = true;
614 options->codeSigningDigestMode = platformCodeSigningDigestMode(builder->options->platform);
615 options->dylibsRemovedDuringMastering = true;
616 options->inodesAreSameAsRuntime = false;
617 options->cacheSupportsASLR = true;
618 options->forSimulator = platformIsForSimulator(builder->options->platform);
619 options->isLocallyBuiltCache = builder->options->isLocallyBuiltCache;
620 options->verbose = builder->options->verboseDiagnostics;
621 options->evictLeafDylibsOnOverflow = true;
622 options->loggingPrefix = std::string(builder->options->deviceName) + dispositionName(builder->options->disposition) + "." + builder->options->archs[i] + cacheSuffix;
623 options->dylibOrdering = parseOrderFile(builder->dylibOrderFileData);
624 options->dirtyDataSegmentOrdering = parseOrderFile(builder->dirtyDataOrderFileData);
625 options->objcOptimizations = parseObjcOptimizationsFile(diag, builder->objcOptimizationsFileData, builder->objcOptimizationsFileLength);
626
627 auto cacheBuilder = std::make_unique<SharedCacheBuilder>(*options.get(), builder->fileSystem);
628 builder->builders.emplace_back((BuildInstance) { std::move(options), std::move(cacheBuilder), inputFiles });
629 }
630 };
631
632 // Enqueue a cache for each configuration
633 switch (builder->options->disposition) {
634 case Disposition::Unknown:
635 case Disposition::InternalDevelopment:
636 // HACK: MRM for the mac should only get development, even if it requested both
637 if (builder->options->platform == Platform::macOS) {
638 addCacheConfiguration(false);
639 } else {
640 addCacheConfiguration(false);
641 addCacheConfiguration(true);
642 }
643 break;
644 case Disposition::Customer:
645 addCacheConfiguration(true);
646 break;
647 case Disposition::InternalMinDevelopment:
648 addCacheConfiguration(false);
649 break;
650 }
651
652 // FIXME: This step can run in parallel.
653 for (auto& buildInstance : builder->builders) {
654 SharedCacheBuilder* cacheBuilder = buildInstance.builder.get();
655 cacheBuilder->build(buildInstance.inputFiles, aliases);
656
657 // First put the warnings in to a vector to own them.
658 buildInstance.warningStrings.reserve(cacheBuilder->warnings().size());
659 for (const std::string& warning : cacheBuilder->warnings())
660 buildInstance.warningStrings.push_back(warning);
661
662 // Then copy to a vector to reference the owner
663 buildInstance.warnings.reserve(buildInstance.warningStrings.size());
664 for (const std::string& warning : buildInstance.warningStrings)
665 buildInstance.warnings.push_back(warning.c_str());
666
667 if (!cacheBuilder->errorMessage().empty()) {
668 // First put the errors in to a vector to own them.
669 buildInstance.errorStrings.push_back(cacheBuilder->errorMessage());
670
671 // Then copy to a vector to reference the owner
672 buildInstance.errors.reserve(buildInstance.errorStrings.size());
673 for (const std::string& error : buildInstance.errorStrings)
674 buildInstance.errors.push_back(error.c_str());
675 }
676
677 if (cacheBuilder->errorMessage().empty()) {
678 cacheBuilder->writeBuffer(buildInstance.cacheData, buildInstance.cacheSize);
679 buildInstance.jsonMap = cacheBuilder->getMapFileJSONBuffer(builder->options->deviceName);
680 if ( buildInstance.options->platform == dyld3::Platform::macOS ) {
681 // For compatibility with update_dyld_shared_cache, put a .map file next to the shared cache
682 buildInstance.macOSMap = cacheBuilder->getMapFileBuffer();
683 buildInstance.macOSMapPath = buildInstance.options->outputFilePath + ".map";
684 }
685 buildInstance.cdHash = cacheBuilder->cdHashFirst();
686 buildInstance.uuid = cacheBuilder->uuid();
687 switch (buildInstance.options->codeSigningDigestMode) {
688 case DyldSharedCache::SHA256only:
689 buildInstance.cdHashType = "sha256";
690 break;
691 case DyldSharedCache::SHA1only:
692 buildInstance.cdHashType = "sha1";
693 break;
694 case DyldSharedCache::Agile:
695 buildInstance.cdHashType = "sha1";
696 break;
697 }
698
699 // Track the dylibs which were included in this cache
700 cacheBuilder->forEachCacheDylib(^(const std::string &path) {
701 builder->dylibsInCaches[path.c_str()].insert(&buildInstance);
702 });
703 cacheBuilder->forEachCacheSymlink(^(const std::string &path) {
704 builder->dylibsInCaches[path.c_str()].insert(&buildInstance);
705 });
706 }
707 // Free the cache builder now so that we don't keep too much memory resident
708 cacheBuilder->deleteBuffer();
709 buildInstance.builder.reset();
710 }
711
712 #if __has_include(<Cambria/SharedCache.h>)
713 // Only build the cambria caches if we have x86_64(h) and at least one other arch
714 bool gotX86Cache = false;
715 bool gotOtherCache = false;
716 for (uint64_t i = 0; i != builder->options->numArchs; ++i) {
717 const char* arch = builder->options->archs[i];
718 if ( !strcmp(arch, "x86_64") || !strcmp(arch, "x86_64h") ) {
719 gotX86Cache = true;
720 continue;
721 }
722 gotOtherCache = true;
723 }
724
725 if ( (builder->options->platform == Platform::macOS) && gotX86Cache && gotOtherCache ) {
726 // Now create an Aot shared cache for any applicable caches
727 for (const auto& buildInstance : builder->builders) {
728 // Only convert the x86_64 cache
729 if ( buildInstance.options->archs != &dyld3::GradedArchs::x86_64)
730 continue;
731
732 // Skip failed caches
733 if (!buildInstance.errors.empty())
734 continue;
735
736 const uint8_t* aotCacheData;
737 size_t aotCacheSize;
738 std::string cdHashStr;
739
740 // Translate this shared cache file.
741 cambria::TranslationOptions options;
742
743 #if !defined(RC_HIDE_J274) || !RC_HIDE_J274
744 // <rdar://problem/63767839> Don't generate a Tonga shared cache translation in the seed trains
745 cambria::translate_shared_cache(options, buildInstance.cacheData, buildInstance.cacheSize,
746 &aotCacheData, &aotCacheSize, &cdHashStr);
747
748 builder->translationResults.push_back({
749 .data = aotCacheData,
750 .size = aotCacheSize,
751 .cdHash = cdHashStr,
752 .path = std::string(cambria::kAotSharedCachePath),
753 .bufferWasMalloced = false
754 });
755 #endif
756
757 // Now translate again for aruba based systems
758 options.is_aruba_shared_cache = true;
759 cambria::translate_shared_cache(options, buildInstance.cacheData, buildInstance.cacheSize,
760 &aotCacheData, &aotCacheSize, &cdHashStr);
761
762 bool bufferWasMalloced = false;
763 #if !defined(RC_HIDE_J274) || !RC_HIDE_J274
764 // For GM we don't need to ship the Aruba AOT any more. It will be converted on the fly
765 // for the DTK. We'll just put a 0-byte placeholder instead and record the cdHash
766 vm_deallocate(mach_task_self(), (vm_address_t)aotCacheData, aotCacheSize);
767 aotCacheData = (uint8_t*)calloc(1, 1);
768 aotCacheSize = 0;
769 bufferWasMalloced = true;
770 #endif
771
772 builder->translationResults.push_back({
773 .data = aotCacheData,
774 .size = aotCacheSize,
775 .cdHash = cdHashStr,
776 .path = std::string(cambria::kAotSharedCachePathAruba),
777 .bufferWasMalloced = bufferWasMalloced
778 });
779 }
780
781 // Convert translation results into File results
782 for (auto& translationResult : builder->translationResults) {
783 FileResult cacheFileResult;
784 cacheFileResult.version = 1;
785 cacheFileResult.behavior = AddFile;
786 cacheFileResult.path = translationResult.path.c_str();
787 cacheFileResult.data = translationResult.data;
788 cacheFileResult.size = translationResult.size;
789 cacheFileResult.hashArch = "x86_64";
790 cacheFileResult.hashType = "sha256";
791 cacheFileResult.hash = translationResult.cdHash.c_str();
792
793 builder->fileResultBuffers.push_back({ builder->fileResultStorage.size(), translationResult.bufferWasMalloced });
794 builder->fileResultStorage.emplace_back(cacheFileResult);
795 }
796 }
797 #endif
798
799 // Now that we have run all of the builds, collect the results
800 // First push file results for each of the shared caches we built
801 for (auto& buildInstance : builder->builders) {
802 CacheResult cacheBuildResult;
803 cacheBuildResult.version = 1;
804 cacheBuildResult.loggingPrefix = buildInstance.options->loggingPrefix.c_str();
805 cacheBuildResult.deviceConfiguration = buildInstance.options->loggingPrefix.c_str();
806 cacheBuildResult.warnings = buildInstance.warnings.empty() ? nullptr : buildInstance.warnings.data();
807 cacheBuildResult.numWarnings = buildInstance.warnings.size();
808 cacheBuildResult.errors = buildInstance.errors.empty() ? nullptr : buildInstance.errors.data();
809 cacheBuildResult.numErrors = buildInstance.errors.size();
810 cacheBuildResult.uuidString = buildInstance.uuid.c_str();
811 cacheBuildResult.mapJSON = buildInstance.jsonMap.c_str();
812
813 builder->cacheResultStorage.emplace_back(cacheBuildResult);
814
815 if (!buildInstance.errors.empty())
816 continue;
817
818 FileResult cacheFileResult;
819 cacheFileResult.version = 1;
820 cacheFileResult.path = buildInstance.options->outputFilePath.c_str();
821 cacheFileResult.behavior = AddFile;
822 cacheFileResult.data = buildInstance.cacheData;
823 cacheFileResult.size = buildInstance.cacheSize;
824 cacheFileResult.hashArch = buildInstance.options->archs->name();
825 cacheFileResult.hashType = buildInstance.cdHashType.c_str();
826 cacheFileResult.hash = buildInstance.cdHash.c_str();
827
828 builder->fileResultBuffers.push_back({ builder->fileResultStorage.size(), true });
829 builder->fileResultStorage.emplace_back(cacheFileResult);
830
831 // Add a file result for the .map file
832 if ( !buildInstance.macOSMap.empty() ) {
833 FileResult cacheFileResult;
834 cacheFileResult.version = 1;
835 cacheFileResult.path = buildInstance.macOSMapPath.c_str();
836 cacheFileResult.behavior = AddFile;
837 cacheFileResult.data = (const uint8_t*)buildInstance.macOSMap.data();
838 cacheFileResult.size = buildInstance.macOSMap.size();
839 cacheFileResult.hashArch = buildInstance.options->archs->name();
840 cacheFileResult.hashType = buildInstance.cdHashType.c_str();
841 cacheFileResult.hash = buildInstance.cdHash.c_str();
842
843 builder->fileResultStorage.emplace_back(cacheFileResult);
844 }
845 }
846
847 // Copy from the storage to the vector we can return to the API.
848 for (auto &fileResult : builder->fileResultStorage)
849 builder->fileResults.push_back(&fileResult);
850 for (auto &cacheResult : builder->cacheResultStorage)
851 builder->cacheResults.push_back(&cacheResult);
852
853
854 // Add entries to tell us to remove all of the dylibs from disk which are in every cache.
855 const size_t numCaches = builder->builders.size();
856 for (const auto& dylibAndCount : builder->dylibsInCaches) {
857 const char* pathToRemove = dylibAndCount.first.c_str();
858
859 if ( builder->options->platform == Platform::macOS ) {
860 // macOS has to leave the simulator support binaries on disk
861 if ( strcmp(pathToRemove, "/usr/lib/system/libsystem_kernel.dylib") == 0 )
862 continue;
863 if ( strcmp(pathToRemove, "/usr/lib/system/libsystem_platform.dylib") == 0 )
864 continue;
865 if ( strcmp(pathToRemove, "/usr/lib/system/libsystem_pthread.dylib") == 0 )
866 continue;
867 }
868
869 if (dylibAndCount.second.size() == numCaches) {
870 builder->filesToRemove.push_back(pathToRemove);
871 } else {
872 // File is not in every cache, so likely has perhaps only x86_64h slice
873 // but we built both x86_64 and x86_64h caches.
874 // We may still delete it if its in all caches it's eligible for, ie, we
875 // assume the cache builder knows about all possible arch's on the system and
876 // can delete anything it knows can't run
877 bool canDeletePath = true;
878 for (auto& buildInstance : builder->builders) {
879 if ( dylibAndCount.second.count(&buildInstance) != 0 )
880 continue;
881 // This builder didn't get this image. See if the image was ineligible
882 // based on slide, ie, that dyld at runtime couldn't load this anyway, so
883 // so removing it from disk won't hurt
884 Diagnostics loaderDiag;
885 const dyld3::GradedArchs* archs = buildInstance.options->archs;
886 dyld3::Platform platform = buildInstance.options->platform;
887 char realerPath[MAXPATHLEN];
888 dyld3::closure::LoadedFileInfo fileInfo = dyld3::MachOAnalyzer::load(loaderDiag, builder->fileSystem,
889 pathToRemove, *archs, platform, realerPath);
890 if ( (platform == dyld3::Platform::macOS) && loaderDiag.hasError() ) {
891 // Try again with iOSMac
892 loaderDiag.clearError();
893 fileInfo = dyld3::MachOAnalyzer::load(loaderDiag, builder->fileSystem,
894 pathToRemove, *archs, dyld3::Platform::iOSMac, realerPath);
895 }
896
897 // We don't need the file content now, as we only needed to know if this file could be loaded
898 builder->fileSystem.unloadFile(fileInfo);
899
900 if ( loaderDiag.hasError() || (fileInfo.fileContent == nullptr) ) {
901 // This arch/platform combination couldn't load this path, so we can remove it
902 continue;
903 }
904
905 // This arch was compatible, so the dylib was rejected from this cache for some other reason, eg,
906 // cache overflow. We need to keep it on-disk
907 canDeletePath = false;
908 break;
909 }
910 if ( canDeletePath )
911 builder->filesToRemove.push_back(pathToRemove);
912 }
913 }
914
915 // Quit if we had any errors.
916 for (auto& buildInstance : builder->builders) {
917 if (!buildInstance.errors.empty())
918 return;
919 }
920
921 builder->state = MRMSharedCacheBuilder::FinishedBuilding;
922 success = true;
923 });
924 return success;
925 }
926
927 const char* const* getErrors(const struct MRMSharedCacheBuilder* builder, uint64_t* errorCount) {
928 if (builder->errors.empty())
929 return nullptr;
930 *errorCount = builder->errors.size();
931 return builder->errors.data();
932 }
933
934 const struct FileResult* const* getFileResults(struct MRMSharedCacheBuilder* builder, uint64_t* resultCount) {
935 if (builder->fileResults.empty())
936 return nullptr;
937 *resultCount = builder->fileResults.size();
938 return builder->fileResults.data();
939 }
940
941 const struct CacheResult* const* getCacheResults(struct MRMSharedCacheBuilder* builder, uint64_t* resultCount) {
942 if (builder->cacheResults.empty())
943 return nullptr;
944 *resultCount = builder->cacheResults.size();
945 return builder->cacheResults.data();
946 }
947
948 const char* const* getFilesToRemove(const struct MRMSharedCacheBuilder* builder, uint64_t* fileCount) {
949 if (builder->filesToRemove.empty())
950 return nullptr;
951 *fileCount = builder->filesToRemove.size();
952 return builder->filesToRemove.data();
953 }
954
955 void destroySharedCacheBuilder(struct MRMSharedCacheBuilder* builder) {
956 for (auto &indexAndIsDataMalloced : builder->fileResultBuffers) {
957 FileResult& fileResult = builder->fileResultStorage[indexAndIsDataMalloced.first];
958 if (indexAndIsDataMalloced.second) {
959 free((void*)fileResult.data);
960 } else {
961 vm_deallocate(mach_task_self(), (vm_address_t)fileResult.data, fileResult.size);
962 }
963 fileResult.data = nullptr;
964 }
965 delete builder;
966 }