2 * Copyright (c) 2017 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
34 #include <sys/syslimits.h>
35 #include <mach-o/arch.h>
36 #include <mach-o/loader.h>
37 #include <mach-o/dyld_priv.h>
38 #include <bootstrap.h>
39 #include <mach/mach.h>
40 #include <dispatch/dispatch.h>
45 #include "DyldSharedCache.h"
46 #include "FileUtils.h"
47 #include "StringUtils.h"
48 #include "ClosureBuilder.h"
49 #include "ClosurePrinter.h"
50 #include "ClosureFileSystemPhysical.h"
51 #include "RootsChecker.h"
53 using dyld3::closure::ImageArray
;
54 using dyld3::closure::Image
;
55 using dyld3::closure::ImageNum
;
56 using dyld3::closure::ClosureBuilder
;
57 using dyld3::closure::LaunchClosure
;
58 using dyld3::closure::DlopenClosure
;
59 using dyld3::closure::PathOverrides
;
63 // mmap() an shared cache file read/only but laid out like it would be at runtime
64 static const DyldSharedCache
* mapCacheFile(const char* path
)
67 if ( ::stat(path
, &statbuf
) ) {
68 fprintf(stderr
, "Error: stat failed for dyld shared cache at %s\n", path
);
72 int cache_fd
= ::open(path
, O_RDONLY
);
74 fprintf(stderr
, "Error: failed to open shared cache file at %s\n", path
);
78 uint8_t firstPage
[4096];
79 if ( ::pread(cache_fd
, firstPage
, 4096, 0) != 4096 ) {
80 fprintf(stderr
, "Error: failed to read shared cache file at %s\n", path
);
83 const dyld_cache_header
* header
= (dyld_cache_header
*)firstPage
;
84 const dyld_cache_mapping_info
* mappings
= (dyld_cache_mapping_info
*)(firstPage
+ header
->mappingOffset
);
85 const dyld_cache_mapping_info
* lastMapping
= &mappings
[header
->mappingCount
- 1];
87 size_t vmSize
= (size_t)(lastMapping
->address
+ lastMapping
->size
- mappings
[0].address
);
89 kern_return_t r
= ::vm_allocate(mach_task_self(), &result
, vmSize
, VM_FLAGS_ANYWHERE
);
90 if ( r
!= KERN_SUCCESS
) {
91 fprintf(stderr
, "Error: failed to allocate space to load shared cache file at %s\n", path
);
94 for (int i
=0; i
< header
->mappingCount
; ++i
) {
95 void* mapped_cache
= ::mmap((void*)(result
+ mappings
[i
].address
- mappings
[0].address
), (size_t)mappings
[i
].size
,
96 PROT_READ
, MAP_FIXED
| MAP_PRIVATE
, cache_fd
, mappings
[i
].fileOffset
);
97 if (mapped_cache
== MAP_FAILED
) {
98 fprintf(stderr
, "Error: mmap() for shared cache at %s failed, errno=%d\n", path
, errno
);
104 return (DyldSharedCache
*)result
;
109 printf("dyld_closure_util program to create or view dyld3 closures\n");
111 printf(" -create_closure <prog-path> # create a closure for the specified main executable\n");
112 printf(" -list_dyld_cache_closures # list all launch closures in the dyld shared cache with size\n");
113 printf(" -list_dyld_cache_dlopen_closures # list all dlopen closures in the dyld shared cache with size\n");
114 printf(" -print_dyld_cache_closure <prog-path> # find closure for specified program in dyld cache and print as JSON\n");
115 printf(" -print_dyld_cache_dylib <dylib-path> # print specified cached dylib as JSON\n");
116 printf(" -print_dyld_cache_dylibs # print all cached dylibs as JSON\n");
117 printf(" -print_dyld_cache_dlopen <path> # print specified dlopen closure as JSON\n");
118 printf(" options:\n");
119 printf(" -cache_file <cache-path> # path to cache file to use (default is current cache)\n");
120 printf(" -build_root <path-prefix> # when building a closure, the path prefix when runtime volume is not current boot volume\n");
121 printf(" -env <var=value> # when building a closure, DYLD_* env vars to assume\n");
122 printf(" -dlopen <path> # for use with -create_closure to simulate that program calling dlopen\n");
123 printf(" -verbose_fixups # for use with -print* options to force printing fixups\n");
124 printf(" -no_at_paths # when building a closure, simulate security not allowing @path expansion\n");
125 printf(" -no_fallback_paths # when building a closure, simulate security not allowing default fallback paths\n");
126 printf(" -allow_insertion_failures # when building a closure, simulate security allowing unloadable DYLD_INSERT_LIBRARIES to be ignored\n");
127 printf(" -force_invalid_cache_version # when building a closure, simulate security the cache version mismatching the builder\n");
130 int main(int argc
, const char* argv
[])
132 const char* cacheFilePath
= nullptr;
133 const char* inputMainExecutablePath
= nullptr;
134 const char* printCacheClosure
= nullptr;
135 const char* printCachedDylib
= nullptr;
136 const char* printOtherDylib
= nullptr;
137 const char* fsRootPath
= nullptr;
138 const char* fsOverlayPath
= nullptr;
139 bool listCacheClosures
= false;
140 bool listCacheDlopenClosures
= false;
141 bool printCachedDylibs
= false;
142 bool verboseFixups
= false;
143 bool allowAtPaths
= true;
144 bool allowFallbackPaths
= true;
145 bool allowInsertionFailures
= false;
146 bool forceInvalidFormatVersion
= false;
147 bool printRaw
= false;
148 std::vector
<const char*> envArgs
;
149 std::vector
<const char*> dlopens
;
150 char fsRootRealPath
[PATH_MAX
];
151 char fsOverlayRealPath
[PATH_MAX
];
158 for (int i
= 1; i
< argc
; ++i
) {
159 const char* arg
= argv
[i
];
160 if ( strcmp(arg
, "-cache_file") == 0 ) {
161 cacheFilePath
= argv
[++i
];
162 if ( cacheFilePath
== nullptr ) {
163 fprintf(stderr
, "-cache_file option requires path to cache file\n");
167 else if ( strcmp(arg
, "-create_closure") == 0 ) {
168 inputMainExecutablePath
= argv
[++i
];
169 if ( inputMainExecutablePath
== nullptr ) {
170 fprintf(stderr
, "-create_closure option requires a path to an executable\n");
174 else if ( strcmp(arg
, "-dlopen") == 0 ) {
175 const char* path
= argv
[++i
];
176 if ( path
== nullptr ) {
177 fprintf(stderr
, "-dlopen option requires a path to a packed closure list\n");
180 dlopens
.push_back(path
);
182 else if ( strcmp(arg
, "-verbose_fixups") == 0 ) {
183 verboseFixups
= true;
185 else if ( strcmp(arg
, "-no_at_paths") == 0 ) {
186 allowAtPaths
= false;
188 else if ( strcmp(arg
, "-no_fallback_paths") == 0 ) {
189 allowFallbackPaths
= false;
191 else if ( strcmp(arg
, "-allow_insertion_failures") == 0 ) {
192 allowInsertionFailures
= true;
194 else if ( strcmp(arg
, "-raw") == 0 ) {
197 else if ( strcmp(arg
, "-fs_root") == 0 ) {
198 fsRootPath
= argv
[++i
];
199 if ( fsRootPath
== nullptr ) {
200 fprintf(stderr
, "-fs_root option requires a path\n");
203 if ( realpath(fsRootPath
, fsRootRealPath
) == nullptr ) {
204 fprintf(stderr
, "-fs_root option requires a real path\n");
207 fsRootPath
= fsRootRealPath
;
209 else if ( strcmp(arg
, "-fs_overlay") == 0 ) {
210 fsOverlayPath
= argv
[++i
];
211 if ( fsOverlayPath
== nullptr ) {
212 fprintf(stderr
, "-fs_overlay option requires a path\n");
215 if ( realpath(fsOverlayPath
, fsOverlayRealPath
) == nullptr ) {
216 fprintf(stderr
, "-fs_root option requires a real path\n");
219 fsOverlayPath
= fsOverlayRealPath
;
221 else if ( strcmp(arg
, "-force_invalid_cache_version") == 0 ) {
222 forceInvalidFormatVersion
= true;
224 else if ( strcmp(arg
, "-list_dyld_cache_closures") == 0 ) {
225 listCacheClosures
= true;
227 else if ( strcmp(arg
, "-list_dyld_cache_dlopen_closures") == 0 ) {
228 listCacheDlopenClosures
= true;
230 else if ( strcmp(arg
, "-print_dyld_cache_closure") == 0 ) {
231 printCacheClosure
= argv
[++i
];
232 if ( printCacheClosure
== nullptr ) {
233 fprintf(stderr
, "-print_dyld_cache_closure option requires a path \n");
237 else if ( strcmp(arg
, "-print_dyld_cache_dylibs") == 0 ) {
238 printCachedDylibs
= true;
240 else if ( strcmp(arg
, "-print_dyld_cache_dylib") == 0 ) {
241 printCachedDylib
= argv
[++i
];
242 if ( printCachedDylib
== nullptr ) {
243 fprintf(stderr
, "-print_dyld_cache_dylib option requires a path \n");
247 else if ( strcmp(arg
, "-print_dyld_cache_dlopen") == 0 ) {
248 printOtherDylib
= argv
[++i
];
249 if ( printOtherDylib
== nullptr ) {
250 fprintf(stderr
, "-print_dyld_cache_dlopen option requires a path \n");
254 else if ( strcmp(arg
, "-env") == 0 ) {
255 const char* envArg
= argv
[++i
];
256 if ( (envArg
== nullptr) || (strchr(envArg
, '=') == nullptr) ) {
257 fprintf(stderr
, "-env option requires KEY=VALUE\n");
260 envArgs
.push_back(envArg
);
263 fprintf(stderr
, "unknown option %s\n", arg
);
268 envArgs
.push_back(nullptr);
271 const DyldSharedCache
* dyldCache
= nullptr;
272 bool dyldCacheIsLive
= true;
273 if ( cacheFilePath
!= nullptr ) {
274 dyldCache
= mapCacheFile(cacheFilePath
);
275 dyldCacheIsLive
= false;
279 dyldCache
= (DyldSharedCache
*)_dyld_get_shared_cache_range(&cacheLength
);
281 dyld3::Platform platform
= dyldCache
->platform();
282 const dyld3::GradedArchs
& archs
= dyld3::GradedArchs::forName(dyldCache
->archName(), true);
284 if ( inputMainExecutablePath
!= nullptr ) {
285 PathOverrides pathOverrides
;
286 pathOverrides
.setFallbackPathHandling(allowFallbackPaths
? dyld3::closure::PathOverrides::FallbackPathMode::classic
: dyld3::closure::PathOverrides::FallbackPathMode::none
);
287 pathOverrides
.setEnvVars(&envArgs
[0], nullptr, nullptr);
288 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 3+dlopens
.size());
289 STACK_ALLOC_ARRAY(dyld3::LoadedImage
, loadedArray
, 1024);
290 imagesArrays
.push_back(dyldCache
->cachedDylibsImageArray());
291 if ( auto others
= dyldCache
->otherOSImageArray() )
292 imagesArrays
.push_back(others
);
294 dyld3::closure::FileSystemPhysical
fileSystem(fsRootPath
, fsOverlayPath
);
295 dyld3::RootsChecker rootsChecker
;
296 ClosureBuilder::AtPath atPathHanding
= allowAtPaths
? ClosureBuilder::AtPath::all
: ClosureBuilder::AtPath::none
;
297 ClosureBuilder
builder(dyld3::closure::kFirstLaunchClosureImageNum
, fileSystem
, rootsChecker
, dyldCache
, dyldCacheIsLive
, archs
, pathOverrides
, atPathHanding
, true, nullptr, platform
, nullptr);
298 if (forceInvalidFormatVersion
)
299 builder
.setDyldCacheInvalidFormatVersion();
301 const LaunchClosure
* mainClosure
= builder
.makeLaunchClosure(inputMainExecutablePath
, allowInsertionFailures
);
302 if ( builder
.diagnostics().hasError() ) {
303 fprintf(stderr
, "dyld_closure_util: %s\n", builder
.diagnostics().errorMessage());
306 ImageNum nextNum
= builder
.nextFreeImageNum();
308 if ( !dlopens
.empty() )
310 imagesArrays
.push_back(mainClosure
->images());
311 dyld3::closure::printClosureAsJSON(mainClosure
, imagesArrays
, verboseFixups
, printRaw
, dyldCache
);
312 ClosureBuilder::buildLoadOrder(loadedArray
, imagesArrays
, mainClosure
);
314 for (const char* path
: dlopens
) {
317 // map unloaded mach-o files for use during closure building
318 for (dyld3::LoadedImage
& li
: loadedArray
) {
319 if ( li
.loadedAddress() != nullptr )
321 if ( li
.image()->inDyldCache() ) {
322 li
.setLoadedAddress((dyld3::MachOLoaded
*)((uint8_t*)dyldCache
+ li
.image()->cacheOffset()));
326 char realerPath
[MAXPATHLEN
];
327 dyld3::closure::LoadedFileInfo loadedFileInfo
= dyld3::MachOAnalyzer::load(diag
, fileSystem
, li
.image()->path(), archs
, builder
.platform(), realerPath
);
328 li
.setLoadedAddress((const dyld3::MachOAnalyzer
*)loadedFileInfo
.fileContent
);
332 ClosureBuilder::AtPath atPathHandingDlopen
= allowAtPaths
? ClosureBuilder::AtPath::all
: ClosureBuilder::AtPath::onlyInRPaths
;
333 ClosureBuilder
dlopenBuilder(nextNum
, fileSystem
, rootsChecker
, dyldCache
, dyldCacheIsLive
, archs
, pathOverrides
, atPathHandingDlopen
, true, nullptr, builder
.platform(), nullptr);
334 if (forceInvalidFormatVersion
)
335 dlopenBuilder
.setDyldCacheInvalidFormatVersion();
337 ImageNum topImageNum
;
338 const DlopenClosure
* dlopenClosure
= dlopenBuilder
.makeDlopenClosure(path
, mainClosure
, loadedArray
, 0, false, false, false, &topImageNum
);
339 if ( dlopenBuilder
.diagnostics().hasError() ) {
340 fprintf(stderr
, "dyld_closure_util: %s\n", dlopenBuilder
.diagnostics().errorMessage());
343 if ( dlopenClosure
== nullptr ) {
344 if ( topImageNum
== 0 ) {
345 fprintf(stderr
, "dyld_closure_util: failed to dlopen %s\n", path
);
348 printf("{\n \"dyld-cache-image-num\": \"0x%04X\"\n}\n", topImageNum
);
351 nextNum
+= dlopenClosure
->images()->imageCount();
352 imagesArrays
.push_back(dlopenClosure
->images());
353 dyld3::closure::printClosureAsJSON(dlopenClosure
, imagesArrays
, verboseFixups
, printRaw
);
354 ClosureBuilder::buildLoadOrder(loadedArray
, imagesArrays
, dlopenClosure
);
357 if ( !dlopens
.empty() )
360 else if ( listCacheClosures
) {
361 dyldCache
->forEachLaunchClosure(^(const char* runtimePath
, const dyld3::closure::LaunchClosure
* closure
) {
362 printf("%6lu %s\n", closure
->size(), runtimePath
);
365 else if ( listCacheDlopenClosures
) {
366 dyldCache
->forEachDlopenImage(^(const char* runtimePath
, const dyld3::closure::Image
* image
) {
367 printf("%6lu %s\n", image
->size(), runtimePath
);
370 else if ( printCacheClosure
) {
371 const dyld3::closure::LaunchClosure
* closure
= dyldCache
->findClosure(printCacheClosure
);
372 if ( closure
!= nullptr ) {
373 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 3);
374 imagesArrays
.push_back(dyldCache
->cachedDylibsImageArray());
375 if ( auto others
= dyldCache
->otherOSImageArray() )
376 imagesArrays
.push_back(others
);
377 imagesArrays
.push_back(closure
->images());
378 dyld3::closure::printClosureAsJSON(closure
, imagesArrays
, verboseFixups
, printRaw
, dyldCache
);
381 fprintf(stderr
, "no closure in cache for %s\n", printCacheClosure
);
384 else if ( printCachedDylibs
) {
385 dyld3::closure::printDyldCacheImagesAsJSON(dyldCache
, verboseFixups
, printRaw
);
387 else if ( printCachedDylib
!= nullptr ) {
388 const dyld3::closure::ImageArray
* dylibs
= dyldCache
->cachedDylibsImageArray();
389 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 2);
390 imagesArrays
.push_back(dylibs
);
392 if ( dylibs
->hasPath(printCachedDylib
, num
) ) {
393 dyld3::closure::printImageAsJSON(dylibs
->imageForNum(num
), imagesArrays
, verboseFixups
, printRaw
, dyldCache
);
396 fprintf(stderr
, "no such image found\n");
399 else if ( printOtherDylib
!= nullptr ) {
400 if ( const dyld3::closure::Image
* image
= dyldCache
->findDlopenOtherImage(printOtherDylib
) ) {
401 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 2);
402 imagesArrays
.push_back(dyldCache
->cachedDylibsImageArray());
403 if ( auto others
= dyldCache
->otherOSImageArray() )
404 imagesArrays
.push_back(others
);
405 dyld3::closure::printImageAsJSON(image
, imagesArrays
, verboseFixups
);
408 fprintf(stderr
, "no such image found\n");