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"
52 using dyld3::closure::ImageArray
;
53 using dyld3::closure::Image
;
54 using dyld3::closure::ImageNum
;
55 using dyld3::closure::ClosureBuilder
;
56 using dyld3::closure::LaunchClosure
;
57 using dyld3::closure::DlopenClosure
;
58 using dyld3::closure::PathOverrides
;
62 // mmap() an shared cache file read/only but laid out like it would be at runtime
63 static const DyldSharedCache
* mapCacheFile(const char* path
)
66 if ( ::stat(path
, &statbuf
) ) {
67 fprintf(stderr
, "Error: stat failed for dyld shared cache at %s\n", path
);
71 int cache_fd
= ::open(path
, O_RDONLY
);
73 fprintf(stderr
, "Error: failed to open shared cache file at %s\n", path
);
77 uint8_t firstPage
[4096];
78 if ( ::pread(cache_fd
, firstPage
, 4096, 0) != 4096 ) {
79 fprintf(stderr
, "Error: failed to read shared cache file at %s\n", path
);
82 const dyld_cache_header
* header
= (dyld_cache_header
*)firstPage
;
83 const dyld_cache_mapping_info
* mappings
= (dyld_cache_mapping_info
*)(firstPage
+ header
->mappingOffset
);
85 size_t vmSize
= (size_t)(mappings
[2].address
+ mappings
[2].size
- mappings
[0].address
);
87 kern_return_t r
= ::vm_allocate(mach_task_self(), &result
, vmSize
, VM_FLAGS_ANYWHERE
);
88 if ( r
!= KERN_SUCCESS
) {
89 fprintf(stderr
, "Error: failed to allocate space to load shared cache file at %s\n", path
);
92 for (int i
=0; i
< 3; ++i
) {
93 void* mapped_cache
= ::mmap((void*)(result
+ mappings
[i
].address
- mappings
[0].address
), (size_t)mappings
[i
].size
,
94 PROT_READ
, MAP_FIXED
| MAP_PRIVATE
, cache_fd
, mappings
[i
].fileOffset
);
95 if (mapped_cache
== MAP_FAILED
) {
96 fprintf(stderr
, "Error: mmap() for shared cache at %s failed, errno=%d\n", path
, errno
);
102 return (DyldSharedCache
*)result
;
107 printf("dyld_closure_util program to create or view dyld3 closures\n");
109 printf(" -create_closure <prog-path> # create a closure for the specified main executable\n");
110 printf(" -list_dyld_cache_closures # list all launch closures in the dyld shared cache with size\n");
111 printf(" -list_dyld_cache_dlopen_closures # list all dlopen closures in the dyld shared cache with size\n");
112 printf(" -print_dyld_cache_closure <prog-path> # find closure for specified program in dyld cache and print as JSON\n");
113 printf(" -print_dyld_cache_dylib <dylib-path> # print specified cached dylib as JSON\n");
114 printf(" -print_dyld_cache_dylibs # print all cached dylibs as JSON\n");
115 printf(" -print_dyld_cache_dlopen <path> # print specified dlopen closure as JSON\n");
116 printf(" options:\n");
117 printf(" -cache_file <cache-path> # path to cache file to use (default is current cache)\n");
118 printf(" -build_root <path-prefix> # when building a closure, the path prefix when runtime volume is not current boot volume\n");
119 printf(" -env <var=value> # when building a closure, DYLD_* env vars to assume\n");
120 printf(" -dlopen <path> # for use with -create_closure to simulate that program calling dlopen\n");
121 printf(" -verbose_fixups # for use with -print* options to force printing fixups\n");
122 printf(" -no_at_paths # when building a closure, simulate security not allowing @path expansion\n");
123 printf(" -no_fallback_paths # when building a closure, simulate security not allowing default fallback paths\n");
124 printf(" -allow_insertion_failures # when building a closure, simulate security allowing unloadable DYLD_INSERT_LIBRARIES to be ignored\n");
125 printf(" -force_invalid_cache_version # when building a closure, simulate security the cache version mismatching the builder\n");
128 int main(int argc
, const char* argv
[])
130 const char* cacheFilePath
= nullptr;
131 const char* inputMainExecutablePath
= nullptr;
132 const char* printCacheClosure
= nullptr;
133 const char* printCachedDylib
= nullptr;
134 const char* printOtherDylib
= nullptr;
135 const char* fsRootPath
= nullptr;
136 const char* fsOverlayPath
= nullptr;
137 bool listCacheClosures
= false;
138 bool listCacheDlopenClosures
= false;
139 bool printCachedDylibs
= false;
140 bool verboseFixups
= false;
141 bool allowAtPaths
= true;
142 bool allowFallbackPaths
= true;
143 bool allowInsertionFailures
= false;
144 bool forceInvalidFormatVersion
= false;
145 bool printRaw
= false;
146 std::vector
<const char*> envArgs
;
147 std::vector
<const char*> dlopens
;
148 char fsRootRealPath
[PATH_MAX
];
149 char fsOverlayRealPath
[PATH_MAX
];
156 for (int i
= 1; i
< argc
; ++i
) {
157 const char* arg
= argv
[i
];
158 if ( strcmp(arg
, "-cache_file") == 0 ) {
159 cacheFilePath
= argv
[++i
];
160 if ( cacheFilePath
== nullptr ) {
161 fprintf(stderr
, "-cache_file option requires path to cache file\n");
165 else if ( strcmp(arg
, "-create_closure") == 0 ) {
166 inputMainExecutablePath
= argv
[++i
];
167 if ( inputMainExecutablePath
== nullptr ) {
168 fprintf(stderr
, "-create_closure option requires a path to an executable\n");
172 else if ( strcmp(arg
, "-dlopen") == 0 ) {
173 const char* path
= argv
[++i
];
174 if ( path
== nullptr ) {
175 fprintf(stderr
, "-dlopen option requires a path to a packed closure list\n");
178 dlopens
.push_back(path
);
180 else if ( strcmp(arg
, "-verbose_fixups") == 0 ) {
181 verboseFixups
= true;
183 else if ( strcmp(arg
, "-no_at_paths") == 0 ) {
184 allowAtPaths
= false;
186 else if ( strcmp(arg
, "-no_fallback_paths") == 0 ) {
187 allowFallbackPaths
= false;
189 else if ( strcmp(arg
, "-allow_insertion_failures") == 0 ) {
190 allowInsertionFailures
= true;
192 else if ( strcmp(arg
, "-raw") == 0 ) {
195 else if ( strcmp(arg
, "-fs_root") == 0 ) {
196 fsRootPath
= argv
[++i
];
197 if ( fsRootPath
== nullptr ) {
198 fprintf(stderr
, "-fs_root option requires a path\n");
201 if ( realpath(fsRootPath
, fsRootRealPath
) == nullptr ) {
202 fprintf(stderr
, "-fs_root option requires a real path\n");
205 fsRootPath
= fsRootRealPath
;
207 else if ( strcmp(arg
, "-fs_overlay") == 0 ) {
208 fsOverlayPath
= argv
[++i
];
209 if ( fsOverlayPath
== nullptr ) {
210 fprintf(stderr
, "-fs_overlay option requires a path\n");
213 if ( realpath(fsOverlayPath
, fsOverlayRealPath
) == nullptr ) {
214 fprintf(stderr
, "-fs_root option requires a real path\n");
217 fsOverlayPath
= fsOverlayRealPath
;
219 else if ( strcmp(arg
, "-force_invalid_cache_version") == 0 ) {
220 forceInvalidFormatVersion
= true;
222 else if ( strcmp(arg
, "-list_dyld_cache_closures") == 0 ) {
223 listCacheClosures
= true;
225 else if ( strcmp(arg
, "-list_dyld_cache_dlopen_closures") == 0 ) {
226 listCacheDlopenClosures
= true;
228 else if ( strcmp(arg
, "-print_dyld_cache_closure") == 0 ) {
229 printCacheClosure
= argv
[++i
];
230 if ( printCacheClosure
== nullptr ) {
231 fprintf(stderr
, "-print_dyld_cache_closure option requires a path \n");
235 else if ( strcmp(arg
, "-print_dyld_cache_dylibs") == 0 ) {
236 printCachedDylibs
= true;
238 else if ( strcmp(arg
, "-print_dyld_cache_dylib") == 0 ) {
239 printCachedDylib
= argv
[++i
];
240 if ( printCachedDylib
== nullptr ) {
241 fprintf(stderr
, "-print_dyld_cache_dylib option requires a path \n");
245 else if ( strcmp(arg
, "-print_dyld_cache_dlopen") == 0 ) {
246 printOtherDylib
= argv
[++i
];
247 if ( printOtherDylib
== nullptr ) {
248 fprintf(stderr
, "-print_dyld_cache_dlopen option requires a path \n");
252 else if ( strcmp(arg
, "-env") == 0 ) {
253 const char* envArg
= argv
[++i
];
254 if ( (envArg
== nullptr) || (strchr(envArg
, '=') == nullptr) ) {
255 fprintf(stderr
, "-env option requires KEY=VALUE\n");
258 envArgs
.push_back(envArg
);
261 fprintf(stderr
, "unknown option %s\n", arg
);
266 envArgs
.push_back(nullptr);
269 const DyldSharedCache
* dyldCache
= nullptr;
270 bool dyldCacheIsLive
= true;
271 if ( cacheFilePath
!= nullptr ) {
272 dyldCache
= mapCacheFile(cacheFilePath
);
273 dyldCacheIsLive
= false;
276 #if __MAC_OS_X_VERSION_MIN_REQUIRED && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101300)
277 fprintf(stderr
, "this tool needs to run on macOS 10.13 or later\n");
281 dyldCache
= (DyldSharedCache
*)_dyld_get_shared_cache_range(&cacheLength
);
284 dyld3::Platform platform
= dyldCache
->platform();
285 const dyld3::GradedArchs
& archs
= dyld3::GradedArchs::forName(dyldCache
->archName(), true);
287 if ( inputMainExecutablePath
!= nullptr ) {
288 PathOverrides pathOverrides
;
289 pathOverrides
.setFallbackPathHandling(allowFallbackPaths
? dyld3::closure::PathOverrides::FallbackPathMode::classic
: dyld3::closure::PathOverrides::FallbackPathMode::none
);
290 pathOverrides
.setEnvVars(&envArgs
[0], nullptr, nullptr);
291 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 3+dlopens
.size());
292 STACK_ALLOC_ARRAY(dyld3::LoadedImage
, loadedArray
, 1024);
293 imagesArrays
.push_back(dyldCache
->cachedDylibsImageArray());
294 imagesArrays
.push_back(dyldCache
->otherOSImageArray());
296 dyld3::closure::FileSystemPhysical
fileSystem(fsRootPath
, fsOverlayPath
);
297 ClosureBuilder::AtPath atPathHanding
= allowAtPaths
? ClosureBuilder::AtPath::all
: ClosureBuilder::AtPath::none
;
298 ClosureBuilder
builder(dyld3::closure::kFirstLaunchClosureImageNum
, fileSystem
, dyldCache
, dyldCacheIsLive
, archs
, pathOverrides
, atPathHanding
, true, nullptr, platform
, nullptr);
299 if (forceInvalidFormatVersion
)
300 builder
.setDyldCacheInvalidFormatVersion();
302 const LaunchClosure
* mainClosure
= builder
.makeLaunchClosure(inputMainExecutablePath
, allowInsertionFailures
);
303 if ( builder
.diagnostics().hasError() ) {
304 fprintf(stderr
, "dyld_closure_util: %s\n", builder
.diagnostics().errorMessage());
307 ImageNum nextNum
= builder
.nextFreeImageNum();
309 if ( !dlopens
.empty() )
311 imagesArrays
.push_back(mainClosure
->images());
312 dyld3::closure::printClosureAsJSON(mainClosure
, imagesArrays
, verboseFixups
, printRaw
, dyldCache
);
313 ClosureBuilder::buildLoadOrder(loadedArray
, imagesArrays
, mainClosure
);
315 for (const char* path
: dlopens
) {
318 // map unloaded mach-o files for use during closure building
319 for (dyld3::LoadedImage
& li
: loadedArray
) {
320 if ( li
.loadedAddress() != nullptr )
322 if ( li
.image()->inDyldCache() ) {
323 li
.setLoadedAddress((dyld3::MachOLoaded
*)((uint8_t*)dyldCache
+ li
.image()->cacheOffset()));
327 char realerPath
[MAXPATHLEN
];
328 dyld3::closure::LoadedFileInfo loadedFileInfo
= dyld3::MachOAnalyzer::load(diag
, fileSystem
, li
.image()->path(), archs
, platform
, realerPath
);
329 li
.setLoadedAddress((const dyld3::MachOAnalyzer
*)loadedFileInfo
.fileContent
);
333 ClosureBuilder::AtPath atPathHandingDlopen
= allowAtPaths
? ClosureBuilder::AtPath::all
: ClosureBuilder::AtPath::onlyInRPaths
;
334 ClosureBuilder
dlopenBuilder(nextNum
, fileSystem
, dyldCache
, dyldCacheIsLive
, archs
, pathOverrides
, atPathHandingDlopen
, true, nullptr, platform
, nullptr);
335 if (forceInvalidFormatVersion
)
336 dlopenBuilder
.setDyldCacheInvalidFormatVersion();
338 ImageNum topImageNum
;
339 const DlopenClosure
* dlopenClosure
= dlopenBuilder
.makeDlopenClosure(path
, mainClosure
, loadedArray
, 0, false, false, false, &topImageNum
);
340 if ( dlopenBuilder
.diagnostics().hasError() ) {
341 fprintf(stderr
, "dyld_closure_util: %s\n", dlopenBuilder
.diagnostics().errorMessage());
344 if ( dlopenClosure
== nullptr ) {
345 if ( topImageNum
== 0 ) {
346 fprintf(stderr
, "dyld_closure_util: failed to dlopen %s\n", path
);
349 printf("{\n \"dyld-cache-image-num\": \"0x%04X\"\n}\n", topImageNum
);
352 nextNum
+= dlopenClosure
->images()->imageCount();
353 imagesArrays
.push_back(dlopenClosure
->images());
354 dyld3::closure::printClosureAsJSON(dlopenClosure
, imagesArrays
, verboseFixups
, printRaw
);
355 ClosureBuilder::buildLoadOrder(loadedArray
, imagesArrays
, dlopenClosure
);
358 if ( !dlopens
.empty() )
361 else if ( listCacheClosures
) {
362 dyldCache
->forEachLaunchClosure(^(const char* runtimePath
, const dyld3::closure::LaunchClosure
* closure
) {
363 printf("%6lu %s\n", closure
->size(), runtimePath
);
366 else if ( listCacheDlopenClosures
) {
367 dyldCache
->forEachDlopenImage(^(const char* runtimePath
, const dyld3::closure::Image
* image
) {
368 printf("%6lu %s\n", image
->size(), runtimePath
);
371 else if ( printCacheClosure
) {
372 const dyld3::closure::LaunchClosure
* closure
= dyldCache
->findClosure(printCacheClosure
);
373 if ( closure
!= nullptr ) {
374 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 3);
375 imagesArrays
.push_back(dyldCache
->cachedDylibsImageArray());
376 imagesArrays
.push_back(dyldCache
->otherOSImageArray());
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 imagesArrays
.push_back(dyldCache
->otherOSImageArray());
404 dyld3::closure::printImageAsJSON(image
, imagesArrays
, verboseFixups
);
407 fprintf(stderr
, "no such image found\n");