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");
127 int main(int argc
, const char* argv
[])
129 const char* cacheFilePath
= nullptr;
130 const char* inputMainExecutablePath
= nullptr;
131 const char* printCacheClosure
= nullptr;
132 const char* printCachedDylib
= nullptr;
133 const char* printOtherDylib
= nullptr;
134 bool listCacheClosures
= false;
135 bool listCacheDlopenClosures
= false;
136 bool printCachedDylibs
= false;
137 bool verboseFixups
= false;
138 bool allowAtPaths
= true;
139 bool allowFallbackPaths
= true;
140 bool allowInsertionFailures
= false;
141 std::vector
<std::string
> buildtimePrefixes
;
142 std::vector
<const char*> envArgs
;
143 std::vector
<const char*> dlopens
;
150 for (int i
= 1; i
< argc
; ++i
) {
151 const char* arg
= argv
[i
];
152 if ( strcmp(arg
, "-cache_file") == 0 ) {
153 cacheFilePath
= argv
[++i
];
154 if ( cacheFilePath
== nullptr ) {
155 fprintf(stderr
, "-cache_file option requires path to cache file\n");
159 else if ( strcmp(arg
, "-create_closure") == 0 ) {
160 inputMainExecutablePath
= argv
[++i
];
161 if ( inputMainExecutablePath
== nullptr ) {
162 fprintf(stderr
, "-create_closure option requires a path to an executable\n");
166 else if ( strcmp(arg
, "-dlopen") == 0 ) {
167 const char* path
= argv
[++i
];
168 if ( path
== nullptr ) {
169 fprintf(stderr
, "-dlopen option requires a path to a packed closure list\n");
172 dlopens
.push_back(path
);
174 else if ( strcmp(arg
, "-verbose_fixups") == 0 ) {
175 verboseFixups
= true;
177 else if ( strcmp(arg
, "-no_at_paths") == 0 ) {
178 allowAtPaths
= false;
180 else if ( strcmp(arg
, "-no_fallback_paths") == 0 ) {
181 allowFallbackPaths
= false;
183 else if ( strcmp(arg
, "-allow_insertion_failures") == 0 ) {
184 allowInsertionFailures
= true;
186 else if ( strcmp(arg
, "-build_root") == 0 ) {
187 const char* buildRootPath
= argv
[++i
];
188 if ( buildRootPath
== nullptr ) {
189 fprintf(stderr
, "-build_root option requires a path \n");
192 buildtimePrefixes
.push_back(buildRootPath
);
194 else if ( strcmp(arg
, "-list_dyld_cache_closures") == 0 ) {
195 listCacheClosures
= true;
197 else if ( strcmp(arg
, "-list_dyld_cache_dlopen_closures") == 0 ) {
198 listCacheDlopenClosures
= true;
200 else if ( strcmp(arg
, "-print_dyld_cache_closure") == 0 ) {
201 printCacheClosure
= argv
[++i
];
202 if ( printCacheClosure
== nullptr ) {
203 fprintf(stderr
, "-print_dyld_cache_closure option requires a path \n");
207 else if ( strcmp(arg
, "-print_dyld_cache_dylibs") == 0 ) {
208 printCachedDylibs
= true;
210 else if ( strcmp(arg
, "-print_dyld_cache_dylib") == 0 ) {
211 printCachedDylib
= argv
[++i
];
212 if ( printCachedDylib
== nullptr ) {
213 fprintf(stderr
, "-print_dyld_cache_dylib option requires a path \n");
217 else if ( strcmp(arg
, "-print_dyld_cache_dlopen") == 0 ) {
218 printOtherDylib
= argv
[++i
];
219 if ( printOtherDylib
== nullptr ) {
220 fprintf(stderr
, "-print_dyld_cache_dlopen option requires a path \n");
224 else if ( strcmp(arg
, "-env") == 0 ) {
225 const char* envArg
= argv
[++i
];
226 if ( (envArg
== nullptr) || (strchr(envArg
, '=') == nullptr) ) {
227 fprintf(stderr
, "-env option requires KEY=VALUE\n");
230 envArgs
.push_back(envArg
);
233 fprintf(stderr
, "unknown option %s\n", arg
);
238 envArgs
.push_back(nullptr);
241 const DyldSharedCache
* dyldCache
= nullptr;
242 bool dyldCacheIsLive
= true;
243 if ( cacheFilePath
!= nullptr ) {
244 dyldCache
= mapCacheFile(cacheFilePath
);
245 dyldCacheIsLive
= false;
248 #if __MAC_OS_X_VERSION_MIN_REQUIRED && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101300)
249 fprintf(stderr
, "this tool needs to run on macOS 10.13 or later\n");
253 dyldCache
= (DyldSharedCache
*)_dyld_get_shared_cache_range(&cacheLength
);
256 dyld3::Platform platform
= dyldCache
->platform();
257 const char* archName
= dyldCache
->archName();
259 if ( inputMainExecutablePath
!= nullptr ) {
260 PathOverrides pathOverrides
;
261 pathOverrides
.setFallbackPathHandling(allowFallbackPaths
? dyld3::closure::PathOverrides::FallbackPathMode::classic
: dyld3::closure::PathOverrides::FallbackPathMode::none
);
262 pathOverrides
.setEnvVars(&envArgs
[0], nullptr, nullptr);
263 const char* prefix
= ( buildtimePrefixes
.empty() ? nullptr : buildtimePrefixes
.front().c_str());
264 //dyld3::PathOverrides pathStuff(envArgs);
265 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 3+dlopens
.size());
266 STACK_ALLOC_ARRAY(dyld3::LoadedImage
, loadedArray
, 1024);
267 imagesArrays
.push_back(dyldCache
->cachedDylibsImageArray());
268 imagesArrays
.push_back(dyldCache
->otherOSImageArray());
270 dyld3::closure::FileSystemPhysical
fileSystem(prefix
);
271 ClosureBuilder::AtPath atPathHanding
= allowAtPaths
? ClosureBuilder::AtPath::all
: ClosureBuilder::AtPath::none
;
272 ClosureBuilder
builder(dyld3::closure::kFirstLaunchClosureImageNum
, fileSystem
, dyldCache
, dyldCacheIsLive
, pathOverrides
, atPathHanding
, nullptr, archName
, platform
, nullptr);
273 const LaunchClosure
* mainClosure
= builder
.makeLaunchClosure(inputMainExecutablePath
, allowInsertionFailures
);
274 if ( builder
.diagnostics().hasError() ) {
275 fprintf(stderr
, "dyld_closure_util: %s\n", builder
.diagnostics().errorMessage());
278 ImageNum nextNum
= builder
.nextFreeImageNum();
280 if ( !dlopens
.empty() )
282 imagesArrays
.push_back(mainClosure
->images());
283 dyld3::closure::printClosureAsJSON(mainClosure
, imagesArrays
, verboseFixups
);
284 ClosureBuilder::buildLoadOrder(loadedArray
, imagesArrays
, mainClosure
);
286 for (const char* path
: dlopens
) {
289 // map unloaded mach-o files for use during closure building
290 for (dyld3::LoadedImage
& li
: loadedArray
) {
291 if ( li
.loadedAddress() != nullptr )
293 if ( li
.image()->inDyldCache() ) {
294 li
.setLoadedAddress((dyld3::MachOLoaded
*)((uint8_t*)dyldCache
+ li
.image()->cacheOffset()));
298 dyld3::closure::LoadedFileInfo loadedFileInfo
= dyld3::MachOAnalyzer::load(diag
, fileSystem
, li
.image()->path(), archName
, platform
);
299 li
.setLoadedAddress((const dyld3::MachOAnalyzer
*)loadedFileInfo
.fileContent
);
303 ClosureBuilder::AtPath atPathHandingDlopen
= allowAtPaths
? ClosureBuilder::AtPath::all
: ClosureBuilder::AtPath::onlyInRPaths
;
304 ClosureBuilder
dlopenBuilder(nextNum
, fileSystem
, dyldCache
, dyldCacheIsLive
, pathOverrides
, atPathHandingDlopen
, nullptr, archName
, platform
, nullptr);
305 ImageNum topImageNum
;
306 const DlopenClosure
* dlopenClosure
= dlopenBuilder
.makeDlopenClosure(path
, mainClosure
, loadedArray
, 0, false, false, &topImageNum
);
307 if ( dlopenBuilder
.diagnostics().hasError() ) {
308 fprintf(stderr
, "dyld_closure_util: %s\n", dlopenBuilder
.diagnostics().errorMessage());
311 if ( dlopenClosure
== nullptr ) {
312 if ( topImageNum
== 0 ) {
313 fprintf(stderr
, "dyld_closure_util: failed to dlopen %s\n", path
);
316 printf("{\n \"dyld-cache-image-num\": \"0x%04X\"\n}\n", topImageNum
);
319 nextNum
+= dlopenClosure
->images()->imageCount();
320 imagesArrays
.push_back(dlopenClosure
->images());
321 dyld3::closure::printClosureAsJSON(dlopenClosure
, imagesArrays
, verboseFixups
);
322 ClosureBuilder::buildLoadOrder(loadedArray
, imagesArrays
, dlopenClosure
);
325 if ( !dlopens
.empty() )
328 else if ( listCacheClosures
) {
329 dyldCache
->forEachLaunchClosure(^(const char* runtimePath
, const dyld3::closure::LaunchClosure
* closure
) {
330 printf("%6lu %s\n", closure
->size(), runtimePath
);
333 else if ( listCacheDlopenClosures
) {
334 dyldCache
->forEachDlopenImage(^(const char* runtimePath
, const dyld3::closure::Image
* image
) {
335 printf("%6lu %s\n", image
->size(), runtimePath
);
338 else if ( printCacheClosure
) {
339 const dyld3::closure::LaunchClosure
* closure
= dyldCache
->findClosure(printCacheClosure
);
340 if ( closure
!= nullptr ) {
341 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 3);
342 imagesArrays
.push_back(dyldCache
->cachedDylibsImageArray());
343 imagesArrays
.push_back(dyldCache
->otherOSImageArray());
344 imagesArrays
.push_back(closure
->images());
345 dyld3::closure::printClosureAsJSON(closure
, imagesArrays
, verboseFixups
);
348 fprintf(stderr
, "no closure in cache for %s\n", printCacheClosure
);
351 else if ( printCachedDylibs
) {
352 dyld3::closure::printDyldCacheImagesAsJSON(dyldCache
, verboseFixups
);
354 else if ( printCachedDylib
!= nullptr ) {
355 const dyld3::closure::ImageArray
* dylibs
= dyldCache
->cachedDylibsImageArray();
356 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 2);
357 imagesArrays
.push_back(dylibs
);
359 if ( dylibs
->hasPath(printCachedDylib
, num
) ) {
360 dyld3::closure::printImageAsJSON(dylibs
->imageForNum(num
), imagesArrays
, verboseFixups
);
363 fprintf(stderr
, "no such image found\n");
366 else if ( printOtherDylib
!= nullptr ) {
367 if ( const dyld3::closure::Image
* image
= dyldCache
->findDlopenOtherImage(printOtherDylib
) ) {
368 STACK_ALLOC_ARRAY(const ImageArray
*, imagesArrays
, 2);
369 imagesArrays
.push_back(dyldCache
->cachedDylibsImageArray());
370 imagesArrays
.push_back(dyldCache
->otherOSImageArray());
371 dyld3::closure::printImageAsJSON(image
, imagesArrays
, verboseFixups
);
374 fprintf(stderr
, "no such image found\n");