1/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
2 *
3 * Copyright (c) 2009-2012 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 <stdio.h>
26#include <unistd.h>
27#include <sys/stat.h>
28#include <string.h>
29#include <fcntl.h>
30#include <stdlib.h>
31#include <errno.h>
32#include <sys/mman.h>
33#include <sys/syslimits.h>
34#include <mach-o/arch.h>
35#include <mach-o/loader.h>
36
37#include <map>
38
39#include "dsc_iterator.h"
40#include "dyld_cache_format.h"
41#include "Architectures.hpp"
42#include "MachOFileAbstraction.hpp"
43#include "CacheFileAbstraction.hpp"
44
45
46enum Mode {
47	modeNone,
48	modeList,
49	modeMap,
50	modeDependencies,
51	modeSlideInfo,
52	modeLinkEdit,
53	modeInfo
54};
55
56struct Options {
57	Mode		mode;
58	const char*	dependentsOfPath;
59	const void*	mappedCache;
60	bool		printUUIDs;
61	bool		printVMAddrs;
62    bool		printDylibVersions;
63};
64
65struct Results {
66	std::map<uint32_t, const char*>	pageToContent;
67	uint64_t						linkeditBase;
68	bool							dependentTargetFound;
69};
70
71
72
73
74
75void usage() {
76	fprintf(stderr, "Usage: dyld_shared_cache_util -list [ -uuid ] [-vmaddr] | -dependents <dylib-path> [ -versions ] | -linkedit | -map [ shared-cache-file ] | -slide_info | -info\n");
77}
78
79/*
80 * Get the path to the native shared cache for this host
81 */
82static const char* default_shared_cache_path() {
83#if __i386__
84	return MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "i386";
85#elif __x86_64__
86	return MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "x86_64";
87#elif __ARM_ARCH_5TEJ__
88	return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "armv5";
89#elif __ARM_ARCH_6K__
90	return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "armv6";
91#elif __ARM_ARCH_7A__
92	return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "armv7";
93#elif __ARM_ARCH_7F__
94	return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "armv7f";
95#elif __ARM_ARCH_7S__
96	return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "armv7s";
97#elif __ARM_ARCH_7K__
98	return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "armv7k";
99#else
100	#error unsupported architecture
101#endif
102}
103
104typedef void (*segment_callback_t)(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo,
105									const Options& options, Results& results);
106
107
108
109/*
110 * List dependencies from the mach-o header at headerAddr
111 * in the same format as 'otool -L'
112 */
113template <typename A>
114void print_dependencies(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo,
115																	const Options& options, Results& results) {
116	typedef typename A::P		P;
117	typedef typename A::P::E	E;
118
119	if ( strcmp(options.dependentsOfPath, dylibInfo->path) != 0 )
120		return;
121	if ( strcmp(segInfo->name, "__TEXT") != 0 )
122		return;
123
124	const macho_dylib_command<P>* dylib_cmd;
125	const macho_header<P>* mh = (const macho_header<P>*)dylibInfo->machHeader;
126	const macho_load_command<P>* const cmds = (macho_load_command<P>*)((uintptr_t)dylibInfo->machHeader + sizeof(macho_header<P>));
127	const uint32_t cmd_count = mh->ncmds();
128	const macho_load_command<P>* cmd = cmds;
129	for (uint32_t i = 0; i < cmd_count; ++i) {
130		switch ( cmd->cmd() ) {
131			case LC_LOAD_DYLIB:
132			case LC_ID_DYLIB:
133			case LC_REEXPORT_DYLIB:
134			case LC_LOAD_WEAK_DYLIB:
135			case LC_LOAD_UPWARD_DYLIB:
136				dylib_cmd = (macho_dylib_command<P>*)cmd;
137				if ( options.printDylibVersions ) {
138					uint32_t compat_vers = dylib_cmd->compatibility_version();
139					uint32_t current_vers = dylib_cmd->current_version();
140					printf("\t%s", dylib_cmd->name());
141					if ( compat_vers != 0xFFFFFFFF ) {
142						printf("(compatibility version %u.%u.%u, current version %u.%u.%u)\n",
143						   (compat_vers >> 16),
144						   (compat_vers >> 8) & 0xff,
145						   (compat_vers) & 0xff,
146						   (current_vers >> 16),
147						   (current_vers >> 8) & 0xff,
148						   (current_vers) & 0xff);
149					}
150					else {
151						printf("\n");
152					}
153				}
154				else {
155					printf("\t%s\n", dylib_cmd->name());
156				}
157				break;
158		}
159		cmd = (const macho_load_command<P>*)(((uint8_t*)cmd)+cmd->cmdsize());
160	}
161	results.dependentTargetFound = true;
162}
163
164/*
165 * Print out a dylib from the shared cache, optionally including the UUID or unslid load address
166 */
167template <typename A>
168void print_list(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo,
169																		const Options& options, Results& results)
170{
171	if ( strcmp(segInfo->name, "__TEXT") != 0 )
172		return;
173
174	if ( options.printVMAddrs )
175		printf("0x%08llX ", segInfo->address);
176	if ( options.printUUIDs ) {
177		if ( dylibInfo->uuid != NULL ) {
178			const uint8_t* uuid = (uint8_t*)dylibInfo->uuid;;
179			printf("<%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> ",
180    				uuid[0],  uuid[1],  uuid[2],  uuid[3],
181					uuid[4],  uuid[5],  uuid[6],  uuid[7],
182    				uuid[8],  uuid[9],  uuid[10], uuid[11],
183					uuid[12], uuid[13], uuid[14], uuid[15]);
184		}
185	    else
186			printf("<     no uuid in dylib               > ");
187    }
188	if ( dylibInfo->isAlias )
189		printf("[alias] %s\n", dylibInfo->path);
190	else
191		printf("%s\n", dylibInfo->path);
192}
193
194
195
196
197static void add_linkedit(uint32_t pageStart, uint32_t pageEnd, const char* message, Results& results)
198{
199	for (uint32_t p = pageStart; p <= pageEnd; p += 4096) {
200		std::map<uint32_t, const char*>::iterator pos = results.pageToContent.find(p);
201		if ( pos == results.pageToContent.end() ) {
202			results.pageToContent[p] = strdup(message);
203		}
204		else {
205			const char* oldMessage = pos->second;
206			char* newMesssage;
207			asprintf(&newMesssage, "%s, %s", oldMessage, message);
208			results.pageToContent[p] = newMesssage;
209			::free((void*)oldMessage);
210		}
211	}
212}
213
214
215/*
216 * get LINKEDIT info for dylib
217 */
218template <typename A>
219void process_linkedit(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo,
220																			const Options& options, Results& results) {
221	typedef typename A::P		P;
222	typedef typename A::P::E	E;
223	// filter out symlinks
224	if ( dylibInfo->isAlias )
225		return;
226	const macho_header<P>* mh = (const macho_header<P>*)dylibInfo->machHeader;
227	uint32_t ncmds = mh->ncmds();
228	const macho_load_command<P>* const cmds = (macho_load_command<P>*)((long)mh + sizeof(macho_header<P>));
229	const macho_load_command<P>* cmd = cmds;
230	for (uint32_t i = 0; i < ncmds; i++) {
231		if ( cmd->cmd() == LC_DYLD_INFO_ONLY ) {
232			macho_dyld_info_command<P>* dyldInfo = (macho_dyld_info_command<P>*)cmd;
233			char message[1000];
234			const char* shortName = strrchr(dylibInfo->path, '/') + 1;
235			// add export trie info
236			if ( dyldInfo->export_size() != 0 ) {
237				//printf("export_off=0x%X\n", dyldInfo->export_off());
238				uint32_t exportPageOffsetStart = dyldInfo->export_off() & (-4096);
239				uint32_t exportPageOffsetEnd = (dyldInfo->export_off() + dyldInfo->export_size()) & (-4096);
240				sprintf(message, "exports from %s", shortName);
241				add_linkedit(exportPageOffsetStart, exportPageOffsetEnd, message, results);
242			}
243			// add binding info
244			if ( dyldInfo->bind_size() != 0 ) {
245				uint32_t bindPageOffsetStart = dyldInfo->bind_off() & (-4096);
246				uint32_t bindPageOffsetEnd = (dyldInfo->bind_off() + dyldInfo->bind_size()) & (-4096);
247				sprintf(message, "bindings from %s", shortName);
248				add_linkedit(bindPageOffsetStart, bindPageOffsetEnd, message, results);
249			}
250			// add lazy binding info
251			if ( dyldInfo->lazy_bind_size() != 0 ) {
252				uint32_t lazybindPageOffsetStart = dyldInfo->lazy_bind_off() & (-4096);
253				uint32_t lazybindPageOffsetEnd = (dyldInfo->lazy_bind_off() + dyldInfo->lazy_bind_size()) & (-4096);
254				sprintf(message, "lazy bindings from %s", shortName);
255				add_linkedit(lazybindPageOffsetStart, lazybindPageOffsetEnd, message, results);
256			}
257			// add weak binding info
258			if ( dyldInfo->weak_bind_size() != 0 ) {
259				uint32_t weakbindPageOffsetStart = dyldInfo->weak_bind_off() & (-4096);
260				uint32_t weakbindPageOffsetEnd = (dyldInfo->weak_bind_off() + dyldInfo->weak_bind_size()) & (-4096);
261				sprintf(message, "weak bindings from %s", shortName);
262				add_linkedit(weakbindPageOffsetStart, weakbindPageOffsetEnd, message, results);
263			}
264		}
265		cmd = (const macho_load_command<P>*)(((uint8_t*)cmd)+cmd->cmdsize());
266	}
267}
268
269
270/*
271 * Print out a .map file similar to what update_dyld_shared_cache created when the cache file was built
272 */
273template <typename A>
274void print_map(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo, const Options& options, Results& results) {
275	if ( !dylibInfo->isAlias )
276		printf("0x%08llX - 0x%08llX %s %s\n", segInfo->address, segInfo->address + segInfo->fileSize, segInfo->name, dylibInfo->path);
277}
278
279
280static void checkMode(Mode mode) {
281	if ( mode != modeNone ) {
282		fprintf(stderr, "Error: select one of: -list, -dependents, -info, -slide_info, -linkedit, or -map\n");
283		usage();
284		exit(1);
285	}
286}
287
288int main (int argc, const char* argv[]) {
289
290	const char* sharedCachePath = default_shared_cache_path();
291
292 	Options options;
293	options.mode = modeNone;
294    options.printUUIDs = false;
295	options.printVMAddrs = false;
296    options.printDylibVersions = false;
297    options.dependentsOfPath = NULL;
298
299    for (uint32_t i = 1; i < argc; i++) {
300        const char* opt = argv[i];
301        if (opt[0] == '-') {
302            if (strcmp(opt, "-list") == 0) {
303				checkMode(options.mode);
304				options.mode = modeList;
305            }
306			else if (strcmp(opt, "-dependents") == 0) {
307				checkMode(options.mode);
308				options.mode = modeDependencies;
309				options.dependentsOfPath = argv[++i];
310                if ( i >= argc ) {
311                    fprintf(stderr, "Error: option -depdendents requires an argument\n");
312                    usage();
313                    exit(1);
314                }
315            }
316			else if (strcmp(opt, "-linkedit") == 0) {
317				checkMode(options.mode);
318				options.mode = modeLinkEdit;
319			}
320			else if (strcmp(opt, "-info") == 0) {
321				checkMode(options.mode);
322				options.mode = modeInfo;
323			}
324			else if (strcmp(opt, "-slide_info") == 0) {
325				checkMode(options.mode);
326				options.mode = modeSlideInfo;
327			}
328			else if (strcmp(opt, "-map") == 0) {
329				checkMode(options.mode);
330				options.mode = modeMap;
331            }
332			else if (strcmp(opt, "-uuid") == 0) {
333                options.printUUIDs = true;
334            }
335			else if (strcmp(opt, "-versions") == 0) {
336                options.printDylibVersions = true;
337            }
338			else if (strcmp(opt, "-vmaddr") == 0) {
339				options.printVMAddrs = true;
340		    }
341			else {
342                fprintf(stderr, "Error: unrecognized option %s\n", opt);
343                usage();
344                exit(1);
345            }
346        }
347		else {
348            sharedCachePath = opt;
349        }
350    }
351
352	if ( options.mode == modeNone ) {
353		fprintf(stderr, "Error: select one of -list, -dependents, -info, -linkedit, or -map\n");
354		usage();
355		exit(1);
356	}
357
358	if ( options.mode != modeSlideInfo ) {
359		if ( options.printUUIDs && (options.mode != modeList) )
360			fprintf(stderr, "Warning: -uuid option ignored outside of -list mode\n");
361
362		if ( options.printVMAddrs && (options.mode != modeList) )
363			fprintf(stderr, "Warning: -vmaddr option ignored outside of -list mode\n");
364
365		if ( options.printDylibVersions && (options.mode != modeDependencies) )
366			fprintf(stderr, "Warning: -versions option ignored outside of -dependents mode\n");
367
368		if ( (options.mode == modeDependencies) && (options.dependentsOfPath == NULL) ) {
369			fprintf(stderr, "Error: -dependents given, but no dylib path specified\n");
370			usage();
371			exit(1);
372		}
373	}
374
375	struct stat statbuf;
376	if ( ::stat(sharedCachePath, &statbuf) == -1 ) {
377		fprintf(stderr, "Error: stat() failed for dyld shared cache at %s, errno=%d\n", sharedCachePath, errno);
378		exit(1);
379	}
380
381	int cache_fd = ::open(sharedCachePath, O_RDONLY);
382	if ( cache_fd < 0 ) {
383		fprintf(stderr, "Error: open() failed for shared cache file at %s, errno=%d\n", sharedCachePath, errno);
384		exit(1);
385	}
386	options.mappedCache = ::mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, cache_fd, 0);
387	if (options.mappedCache == MAP_FAILED) {
388		fprintf(stderr, "Error: mmap() for shared cache at %s failed, errno=%d\n", sharedCachePath, errno);
389		exit(1);
390	}
391
392	if ( options.mode == modeSlideInfo ) {
393		const dyldCacheHeader<LittleEndian>* header = (dyldCacheHeader<LittleEndian>*)options.mappedCache;
394		if ( header->slideInfoOffset() == 0 ) {
395			fprintf(stderr, "Error: dyld shared cache does not contain slide info\n");
396			exit(1);
397		}
398		const dyldCacheFileMapping<LittleEndian>* mappings = (dyldCacheFileMapping<LittleEndian>*)((char*)options.mappedCache + header->mappingOffset());
399		const dyldCacheFileMapping<LittleEndian>* dataMapping = &mappings[1];
400		uint64_t dataStartAddress = dataMapping->address();
401		uint64_t dataSize = dataMapping->size();
402		const dyldCacheSlideInfo<LittleEndian>* slideInfoHeader = (dyldCacheSlideInfo<LittleEndian>*)((char*)options.mappedCache+header->slideInfoOffset());
403		printf("slide info version=%d\n", slideInfoHeader->version());
404		printf("toc_count=%d, data page count=%lld\n", slideInfoHeader->toc_count(), dataSize/4096);
405		const dyldCacheSlideInfoEntry* entries = (dyldCacheSlideInfoEntry*)((char*)slideInfoHeader + slideInfoHeader->entries_offset());
406		for(int i=0; i < slideInfoHeader->toc_count(); ++i) {
407			printf("0x%08llX: [% 5d,% 5d] ", dataStartAddress + i*4096, i, slideInfoHeader->toc(i));
408			const dyldCacheSlideInfoEntry* entry = &entries[slideInfoHeader->toc(i)];
409			for(int j=0; j < slideInfoHeader->entries_size(); ++j)
410				printf("%02X", entry->bits[j]);
411			printf("\n");
412		}
413	}
414	else if ( options.mode == modeInfo ) {
415		const dyldCacheHeader<LittleEndian>* header = (dyldCacheHeader<LittleEndian>*)options.mappedCache;
416		printf("uuid: ");
417		if ( header->mappingOffset() >= 0x68 ) {
418			const uint8_t* uuid = header->uuid();
419			printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X\n",
420				   uuid[0],  uuid[1],  uuid[2],  uuid[3],
421				   uuid[4],  uuid[5],  uuid[6],  uuid[7],
422				   uuid[8],  uuid[9],  uuid[10], uuid[11],
423				   uuid[12], uuid[13], uuid[14], uuid[15]);
424		}
425		else {
426			printf("n/a\n");
427		}
428		printf("image count: %u\n", header->imagesCount());
429		printf("mappings:\n");
430		const dyldCacheFileMapping<LittleEndian>* mappings = (dyldCacheFileMapping<LittleEndian>*)((char*)options.mappedCache + header->mappingOffset());
431		for (uint32_t i=0; i < header->mappingCount(); ++i) {
432			if ( mappings[i].init_prot() & VM_PROT_EXECUTE )
433				printf("    __TEXT     %lluMB\n", mappings[i].size()/(1024*1024));
434			else if ( mappings[i]. init_prot() & VM_PROT_WRITE )
435				printf("    __DATA      %lluMB\n", mappings[i].size()/(1024*1024));
436			else if ( mappings[i].init_prot() & VM_PROT_READ )
437				printf("    __LINKEDIT  %lluMB\n", mappings[i].size()/(1024*1024));
438		}
439	}
440	else {
441		segment_callback_t callback;
442		if ( strcmp((char*)options.mappedCache, "dyld_v1    i386") == 0 ) {
443			switch ( options.mode ) {
444				case modeList:
445					callback = print_list<x86>;
446					break;
447				case modeMap:
448					callback = print_map<x86>;
449					break;
450				case modeDependencies:
451					callback = print_dependencies<x86>;
452					break;
453				case modeLinkEdit:
454					callback = process_linkedit<x86>;
455					break;
456				case modeNone:
457				case modeInfo:
458				case modeSlideInfo:
459					break;
460			}
461		}
462		else if ( strcmp((char*)options.mappedCache, "dyld_v1  x86_64") == 0 ) {
463			switch ( options.mode ) {
464				case modeList:
465					callback = print_list<x86_64>;
466					break;
467				case modeMap:
468					callback = print_map<x86_64>;
469					break;
470				case modeDependencies:
471					callback = print_dependencies<x86_64>;
472					break;
473				case modeLinkEdit:
474					callback = process_linkedit<x86_64>;
475					break;
476				case modeNone:
477				case modeInfo:
478				case modeSlideInfo:
479					break;
480			}
481		}
482		else if ( (strncmp((char*)options.mappedCache, "dyld_v1   armv", 14) == 0)
483			   || (strncmp((char*)options.mappedCache, "dyld_v1  armv", 13) == 0) ) {
484			switch ( options.mode ) {
485				case modeList:
486					callback = print_list<arm>;
487					break;
488				case modeMap:
489					callback = print_map<arm>;
490					break;
491				case modeDependencies:
492					callback = print_dependencies<arm>;
493					break;
494				case modeLinkEdit:
495					callback = process_linkedit<arm>;
496					break;
497				case modeNone:
498				case modeInfo:
499				case modeSlideInfo:
500					break;
501			}
502		}
503 		else {
504			fprintf(stderr, "Error: unrecognized dyld shared cache magic.\n");
505			exit(1);
506		}
507
508		__block Results results;
509		results.dependentTargetFound = false;
510		int iterateResult = dyld_shared_cache_iterate(options.mappedCache, statbuf.st_size,
511										   ^(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo ) {
512											   (callback)(dylibInfo, segInfo, options, results);
513										   });
514		if ( iterateResult != 0 ) {
515			fprintf(stderr, "Error: malformed shared cache file\n");
516			exit(1);
517		}
518
519		if ( options.mode == modeLinkEdit ) {
520			// dump -linkedit information
521			for (std::map<uint32_t, const char*>::iterator it = results.pageToContent.begin(); it != results.pageToContent.end(); ++it) {
522				printf("0x%08X %s\n", it->first, it->second);
523			}
524		}
525
526		if ( (options.mode == modeDependencies) && options.dependentsOfPath && !results.dependentTargetFound) {
527			fprintf(stderr, "Error: could not find '%s' in the shared cache at\n  %s\n", options.dependentsOfPath, sharedCachePath);
528			exit(1);
529		}
530	}
531	return 0;
532}
533