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