]>
Commit | Line | Data |
---|---|---|
10b92d3b A |
1 | /* |
2 | * Copyright (c) 2017 Apple Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
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 | |
11 | * file. | |
12 | * | |
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. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | ||
25 | #include <string.h> | |
26 | ||
27 | #include <string> | |
28 | #include <map> | |
29 | #include <vector> | |
30 | ||
31 | #include "LaunchCache.h" | |
32 | #include "LaunchCacheFormat.h" | |
33 | ||
34 | #if !DYLD_IN_PROCESS | |
35 | ||
36 | namespace dyld3 { | |
37 | namespace launch_cache { | |
38 | ||
39 | struct Node | |
40 | { | |
41 | std::string value; | |
42 | std::map<std::string, Node> map; | |
43 | std::vector<Node> array; | |
44 | }; | |
45 | ||
46 | static std::string hex(uint64_t value) { | |
47 | char buff[64]; | |
48 | sprintf(buff, "0x%llX", value); | |
49 | return buff; | |
50 | } | |
51 | ||
52 | static std::string hex5(uint64_t value) { | |
53 | char buff[64]; | |
54 | sprintf(buff, "0x%05llX", value); | |
55 | return buff; | |
56 | } | |
57 | ||
58 | static std::string decimal(uint64_t value) { | |
59 | char buff[64]; | |
60 | sprintf(buff, "%llu", value); | |
61 | return buff; | |
62 | } | |
63 | ||
64 | static Node buildImageNode(const Image& image, const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails) | |
65 | { | |
66 | __block Node imageNode; | |
67 | ||
68 | if ( image.isInvalid() ) | |
69 | return imageNode; | |
70 | ||
71 | const ImageGroup group = image.group(); | |
72 | imageNode.map["path"].value = image.path(); | |
73 | __block Node imageAliases; | |
74 | group.forEachAliasOf(group.indexInGroup(image.binaryData()), ^(const char* aliasPath, uint32_t aliasPathHash, bool& stop) { | |
75 | Node anAlias; | |
76 | anAlias.value = aliasPath; | |
77 | imageAliases.array.push_back(anAlias); | |
78 | }); | |
79 | if ( !imageAliases.array.empty() ) | |
80 | imageNode.map["aliases"] = imageAliases; | |
81 | uuid_string_t uuidStr; | |
82 | uuid_unparse(*image.uuid(), uuidStr); | |
83 | imageNode.map["uuid"].value = uuidStr; | |
84 | imageNode.map["has-objc"].value = (image.hasObjC() ? "true" : "false"); | |
85 | imageNode.map["has-weak-defs"].value = (image.hasWeakDefs() ? "true" : "false"); | |
86 | imageNode.map["never-unload"].value = (image.neverUnload() ? "true" : "false"); | |
87 | imageNode.map["platform-binary"].value = (image.isPlatformBinary() ? "true" : "false"); | |
88 | if ( group.groupNum() == 0 ) | |
89 | imageNode.map["overridable-dylib"].value = (image.overridableDylib() ? "true" : "false"); | |
90 | if ( image.cwdMustBeThisDir() ) | |
91 | imageNode.map["cwd-must-be-this-dir"].value = "true"; | |
92 | if ( image.isDiskImage() ) { | |
93 | uint32_t csFileOffset; | |
94 | uint32_t csSize; | |
95 | if ( image.hasCodeSignature(csFileOffset, csSize) ) { | |
96 | imageNode.map["code-sign-location"].map["offset"].value = hex(csFileOffset); | |
97 | imageNode.map["code-sign-location"].map["size"].value = hex(csSize); | |
98 | } | |
99 | uint32_t fpTextOffset; | |
100 | uint32_t fpSize; | |
101 | if ( image.isFairPlayEncrypted(fpTextOffset, fpSize) ) { | |
102 | imageNode.map["fairplay-encryption-location"].map["offset"].value = hex(fpTextOffset); | |
103 | imageNode.map["fairplay-encryption-location"].map["size"].value = hex(fpSize); | |
104 | } | |
105 | if ( image.validateUsingModTimeAndInode() ) { | |
106 | imageNode.map["file-mod-time"].value = hex(image.fileModTime()); | |
107 | imageNode.map["file-inode"].value = hex(image.fileINode()); | |
108 | } | |
109 | else { | |
110 | const uint8_t* cdHash = image.cdHash16(); | |
111 | std::string cdHashStr; | |
112 | cdHashStr.reserve(32); | |
113 | for (int j=0; j < 16; ++j) { | |
114 | uint8_t byte = cdHash[j]; | |
115 | uint8_t nibbleL = byte & 0x0F; | |
116 | uint8_t nibbleH = byte >> 4; | |
117 | if ( nibbleH < 10 ) | |
118 | cdHashStr += '0' + nibbleH; | |
119 | else | |
120 | cdHashStr += 'a' + (nibbleH-10); | |
121 | if ( nibbleL < 10 ) | |
122 | cdHashStr += '0' + nibbleL; | |
123 | else | |
124 | cdHashStr += 'a' + (nibbleL-10); | |
125 | } | |
126 | imageNode.map["file-cd-hash-16"].value = cdHashStr; | |
127 | } | |
128 | imageNode.map["total-vm-size"].value = hex(image.vmSizeToMap()); | |
129 | uint64_t sliceOffset = image.sliceOffsetInFile(); | |
130 | if ( sliceOffset != 0 ) | |
131 | imageNode.map["file-offset-of-slice"].value = hex(sliceOffset); | |
132 | if ( image.hasTextRelocs() ) | |
133 | imageNode.map["has-text-relocs"].value = "true"; | |
134 | image.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop) { | |
135 | Node segInfoNode; | |
136 | segInfoNode.map["file-offset"].value = hex(fileOffset); | |
137 | segInfoNode.map["file-size"].value = hex(fileSize); | |
138 | segInfoNode.map["vm-size"].value = hex(vmSize); | |
139 | segInfoNode.map["permissions"].value = hex(permissions); | |
140 | imageNode.map["mappings"].array.push_back(segInfoNode); | |
141 | }); | |
142 | if ( printFixups ) { | |
143 | image.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool &segStop) { | |
144 | MemoryRange segContent = { nullptr, vmSize }; | |
145 | std::string segName = "segment-" + decimal(segIndex); | |
146 | __block Node segmentFixupsNode; | |
147 | image.forEachFixup(segIndex, segContent, ^(uint64_t segOffset, Image::FixupKind kind, TargetSymbolValue value, bool& stop) { | |
148 | switch ( kind ) { | |
149 | case Image::FixupKind::rebase32: | |
150 | segmentFixupsNode.map[segName].map[hex5(segOffset)].value = "32-bit rebase"; | |
151 | break; | |
152 | case Image::FixupKind::rebase64: | |
153 | segmentFixupsNode.map[segName].map[hex5(segOffset)].value = "64-bit rebase"; | |
154 | break; | |
155 | case Image::FixupKind::rebaseText32 : | |
156 | segmentFixupsNode.map[segName].map[hex5(segOffset)].value = "32-bit text rebase"; | |
157 | break; | |
158 | case Image::FixupKind::bind32: | |
159 | segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("32-bit bind, target=") + value.asString(group); | |
160 | break; | |
161 | case Image::FixupKind::bind64: | |
162 | segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("64-bit bind, target=") + value.asString(group); | |
163 | break; | |
164 | case Image::FixupKind::bindText32 : | |
165 | segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("32-bit text abs bind, target=") + value.asString(group); | |
166 | break; | |
167 | case Image::FixupKind::bindTextRel32 : | |
168 | segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("32-bit text rel bind, target=") + value.asString(group); | |
169 | break; | |
170 | case Image::FixupKind::bindImportJmp32 : | |
171 | segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("32-bit IMPORT JMP rel bind, target=") + value.asString(group); | |
172 | break; | |
173 | } | |
174 | }); | |
175 | if ( segmentFixupsNode.map[segName].map.size() != 0 ) { | |
176 | imageNode.map["fixups"].array.push_back(segmentFixupsNode); | |
177 | } | |
178 | }); | |
179 | } | |
180 | } | |
181 | else { | |
182 | imageNode.map["patch-start-index"].value = decimal(image.patchStartIndex()); | |
183 | imageNode.map["patch-count"].value = decimal(image.patchCount()); | |
184 | } | |
185 | ||
186 | // add dependents | |
187 | image.forEachDependentImage(groupList, ^(uint32_t depIndex, Image depImage, Image::LinkKind kind, bool& stop) { | |
188 | Node depMapNode; | |
189 | depMapNode.map["path"].value = depImage.path(); | |
190 | if ( printDependentsDetails ) { | |
191 | ImageGroup depGroup = depImage.group(); | |
192 | uint32_t indexInGroup = depGroup.indexInGroup(depImage.binaryData()); | |
193 | depMapNode.map["group-index"].value = decimal(depGroup.groupNum()); | |
194 | depMapNode.map["index-in-group"].value = decimal(indexInGroup); | |
195 | } | |
196 | switch ( kind ) { | |
197 | case Image::LinkKind::regular: | |
198 | depMapNode.map["link"].value = "regular"; | |
199 | break; | |
200 | case Image::LinkKind::reExport: | |
201 | depMapNode.map["link"].value = "re-export"; | |
202 | break; | |
203 | case Image::LinkKind::upward: | |
204 | depMapNode.map["link"].value = "upward"; | |
205 | break; | |
206 | case Image::LinkKind::weak: | |
207 | depMapNode.map["link"].value = "weak"; | |
208 | break; | |
209 | } | |
210 | imageNode.map["dependents"].array.push_back(depMapNode); | |
211 | }); | |
212 | // add things to init before this image | |
213 | __block Node initBeforeNode; | |
214 | image.forEachInitBefore(groupList, ^(Image beforeImage) { | |
215 | Node beforeNode; | |
216 | beforeNode.value = beforeImage.path(); | |
217 | imageNode.map["initializer-order"].array.push_back(beforeNode); | |
218 | }); | |
219 | ||
220 | // add initializers | |
221 | image.forEachInitializer(nullptr, ^(const void* initializer) { | |
222 | Node initNode; | |
223 | initNode.value = hex((long)initializer); | |
224 | imageNode.map["initializer-offsets"].array.push_back(initNode); | |
225 | }); | |
226 | ||
227 | // add override info if relevant | |
228 | group.forEachImageRefOverride(groupList, ^(Image standardDylib, Image overrideDylib, bool& stop) { | |
229 | if ( overrideDylib.binaryData() == image.binaryData() ) { | |
230 | imageNode.map["override-of-cached-dylib"].value = standardDylib.path(); | |
231 | } | |
232 | }); | |
233 | ||
234 | // add dtrace info | |
235 | image.forEachDOF(nullptr, ^(const void* section) { | |
236 | Node initNode; | |
237 | initNode.value = hex((long)section); | |
238 | imageNode.map["dof-offsets"].array.push_back(initNode); | |
239 | }); | |
240 | ||
241 | return imageNode; | |
242 | } | |
243 | ||
244 | ||
245 | static Node buildImageGroupNode(const ImageGroup& group, const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails) | |
246 | { | |
247 | Node images; | |
248 | uint32_t imageCount = group.imageCount(); | |
249 | images.array.reserve(imageCount); | |
250 | for (uint32_t i=0; i < imageCount; ++i) { | |
251 | images.array.push_back(buildImageNode(group.image(i), groupList, printFixups, printDependentsDetails)); | |
252 | } | |
253 | return images; | |
254 | } | |
255 | ||
256 | static Node buildClosureNode(const Closure& closure, const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails) | |
257 | { | |
258 | __block Node root; | |
259 | ||
260 | // add env-vars if they exist | |
261 | closure.forEachEnvVar(^(const char* keyEqualValue, bool& stop) { | |
262 | const char* equ = strchr(keyEqualValue, '='); | |
263 | if ( equ != nullptr ) { | |
264 | char key[512]; | |
265 | strncpy(key, keyEqualValue, equ-keyEqualValue); | |
266 | key[equ-keyEqualValue] = '\0'; | |
267 | root.map["env-vars"].map[key].value = equ+1; | |
268 | } | |
269 | }); | |
270 | ||
271 | // add missing files array if they exist | |
272 | closure.forEachMustBeMissingFile(^(const char* path, bool& stop) { | |
273 | Node fileNode; | |
274 | fileNode.value = path; | |
275 | root.map["must-be-missing-files"].array.push_back(fileNode); | |
276 | }); | |
277 | ||
278 | const uint8_t* cdHash = closure.cdHash(); | |
279 | std::string cdHashStr; | |
280 | cdHashStr.reserve(24); | |
281 | for (int i=0; i < 20; ++i) { | |
282 | uint8_t byte = cdHash[i]; | |
283 | uint8_t nibbleL = byte & 0x0F; | |
284 | uint8_t nibbleH = byte >> 4; | |
285 | if ( nibbleH < 10 ) | |
286 | cdHashStr += '0' + nibbleH; | |
287 | else | |
288 | cdHashStr += 'a' + (nibbleH-10); | |
289 | if ( nibbleL < 10 ) | |
290 | cdHashStr += '0' + nibbleL; | |
291 | else | |
292 | cdHashStr += 'a' + (nibbleL-10); | |
293 | } | |
294 | if ( cdHashStr != "0000000000000000000000000000000000000000" ) | |
295 | root.map["cd-hash"].value = cdHashStr; | |
296 | ||
297 | // add uuid of dyld cache this closure requires | |
298 | closure.dyldCacheUUID(); | |
299 | uuid_string_t cacheUuidStr; | |
300 | uuid_unparse(*closure.dyldCacheUUID(), cacheUuidStr); | |
301 | root.map["dyld-cache-uuid"].value = cacheUuidStr; | |
302 | ||
303 | // add top level images | |
304 | Node& rootImages = root.map["root-images"]; | |
305 | uint32_t initImageCount = closure.mainExecutableImageIndex(); | |
306 | rootImages.array.resize(initImageCount+1); | |
307 | for (uint32_t i=0; i <= initImageCount; ++i) { | |
308 | const Image image = closure.group().image(i); | |
309 | uuid_string_t uuidStr; | |
310 | uuid_unparse(*image.uuid(), uuidStr); | |
311 | rootImages.array[i].value = uuidStr; | |
312 | } | |
313 | root.map["initial-image-count"].value = decimal(closure.initialImageCount()); | |
314 | ||
315 | // add images | |
316 | root.map["images"] = buildImageGroupNode(closure.group(), groupList, printFixups, printDependentsDetails); | |
317 | root.map["group-num"].value = decimal(closure.group().groupNum()); | |
318 | ||
319 | if ( closure.mainExecutableUsesCRT() ) | |
320 | root.map["main-offset"].value = hex(closure.mainExecutableEntryOffset()); | |
321 | else | |
322 | root.map["start-offset"].value = hex(closure.mainExecutableEntryOffset()); | |
323 | ||
324 | root.map["libdyld-entry-offset"].value = hex(closure.libdyldVectorOffset()); | |
325 | ||
326 | root.map["restricted"].value = (closure.isRestricted() ? "true" : "false"); | |
327 | ||
328 | root.map["library-validation"].value = (closure.usesLibraryValidation() ? "true" : "false"); | |
329 | ||
330 | __block Node cacheOverrides; | |
331 | closure.group().forEachDyldCacheSymbolOverride(^(uint32_t patchTableIndex, uint32_t imageIndexInClosure, uint32_t imageOffset, bool& stop) { | |
332 | Node patch; | |
333 | patch.map["patch-index"].value = decimal(patchTableIndex); | |
334 | patch.map["replacement"].value = "{closure[" + decimal(imageIndexInClosure) + "]+" + hex(imageOffset) + "}"; | |
335 | cacheOverrides.array.push_back(patch); | |
336 | }); | |
337 | if ( !cacheOverrides.array.empty() ) | |
338 | root.map["dyld-cache-overrides"].array = cacheOverrides.array; | |
339 | ||
340 | return root; | |
341 | } | |
342 | ||
343 | static void indentBy(uint32_t spaces, FILE* out) { | |
344 | for (int i=0; i < spaces; ++i) { | |
345 | fprintf(out, " "); | |
346 | } | |
347 | } | |
348 | ||
349 | static void printJSON(const Node& node, uint32_t indent, FILE* out) | |
350 | { | |
351 | if ( !node.map.empty() ) { | |
352 | fprintf(out, "{"); | |
353 | bool needComma = false; | |
354 | for (const auto& entry : node.map) { | |
355 | if ( needComma ) | |
356 | fprintf(out, ","); | |
357 | fprintf(out, "\n"); | |
358 | indentBy(indent+2, out); | |
359 | fprintf(out, "\"%s\": ", entry.first.c_str()); | |
360 | printJSON(entry.second, indent+2, out); | |
361 | needComma = true; | |
362 | } | |
363 | fprintf(out, "\n"); | |
364 | indentBy(indent, out); | |
365 | fprintf(out, "}"); | |
366 | } | |
367 | else if ( !node.array.empty() ) { | |
368 | fprintf(out, "["); | |
369 | bool needComma = false; | |
370 | for (const auto& entry : node.array) { | |
371 | if ( needComma ) | |
372 | fprintf(out, ","); | |
373 | fprintf(out, "\n"); | |
374 | indentBy(indent+2, out); | |
375 | printJSON(entry, indent+2, out); | |
376 | needComma = true; | |
377 | } | |
378 | fprintf(out, "\n"); | |
379 | indentBy(indent, out); | |
380 | fprintf(out, "]"); | |
381 | } | |
382 | else { | |
383 | fprintf(out, "\"%s\"", node.value.c_str()); | |
384 | } | |
385 | if ( indent == 0 ) | |
386 | fprintf(out, "\n"); | |
387 | } | |
388 | ||
389 | ||
390 | void Image::printAsJSON(const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails, FILE* out) const | |
391 | { | |
392 | Node image = buildImageNode(*this, groupList, printFixups, printDependentsDetails); | |
393 | printJSON(image, 0, out); | |
394 | } | |
395 | ||
396 | void ImageGroup::printAsJSON(const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails, FILE* out) const | |
397 | { | |
398 | Node root; | |
399 | root.map["images"] = buildImageGroupNode(*this, groupList, printFixups, printDependentsDetails); | |
400 | root.map["group-num"].value = decimal(groupNum()); | |
401 | root.map["dylibs-expected-on-disk"].value = (dylibsExpectedOnDisk() ? "true" : "false"); | |
402 | printJSON(root, 0, out); | |
403 | } | |
404 | ||
405 | void ImageGroup::printStatistics(FILE* out) const | |
406 | { | |
407 | __block uint32_t totalRebases = 0; | |
408 | __block uint32_t totalBinds = 0; | |
409 | for (uint32_t i=0; i < imageCount(); ++i) { | |
410 | Image img(image(i)); | |
411 | img.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool &segStop) { | |
412 | MemoryRange segContent = { nullptr, vmSize }; | |
413 | img.forEachFixup(segIndex, segContent, ^(uint64_t segOffset, Image::FixupKind kind, TargetSymbolValue value, bool& stop) { | |
414 | if ( kind == Image::FixupKind::rebase64 ) | |
415 | ++totalRebases; | |
416 | else | |
417 | ++totalBinds; | |
418 | }); | |
419 | }); | |
420 | } | |
421 | ||
422 | fprintf(out, "ImageGroup:\n"); | |
423 | fprintf(out, " image-count: % 5d\n", _binaryData->imagesPoolCount); | |
424 | fprintf(out, " alias-count: % 5d\n", _binaryData->imageAliasCount); | |
425 | fprintf(out, " segments-count: % 5d\n", _binaryData->segmentsPoolCount); | |
426 | fprintf(out, " dependents-count: % 5d\n", _binaryData->dependentsPoolCount); | |
427 | fprintf(out, " targets-count: % 5d\n", _binaryData->targetsPoolCount); | |
428 | fprintf(out, " rebase-count: % 5d\n", totalRebases); | |
429 | fprintf(out, " bind-count: % 5d\n", totalBinds); | |
430 | fprintf(out, " fixups-size: % 8d bytes\n", _binaryData->fixupsPoolSize); | |
431 | fprintf(out, " targets-size: % 8ld bytes\n", _binaryData->targetsPoolCount * sizeof(uint64_t)); | |
432 | fprintf(out, " strings-size: % 8d bytes\n", _binaryData->stringsPoolSize); | |
433 | fprintf(out, " dofs-size: % 8ld bytes\n", _binaryData->dofOffsetPoolCount * sizeof(uint32_t)); | |
434 | fprintf(out, " indirect-groups-size: % 8ld bytes\n", _binaryData->indirectGroupNumPoolCount * sizeof(uint32_t)); | |
435 | } | |
436 | ||
437 | ||
438 | void Closure::printAsJSON(const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails, FILE* out) const | |
439 | { | |
440 | Node root = buildClosureNode(*this, groupList, printFixups, printDependentsDetails); | |
441 | printJSON(root, 0, out); | |
442 | } | |
443 | ||
444 | void Closure::printStatistics(FILE* out) const | |
445 | { | |
446 | fprintf(out, "closure size: %lu\n", size()); | |
447 | group().printStatistics(out); | |
448 | } | |
449 | ||
450 | ||
451 | ||
452 | } // namespace launch_cache | |
453 | } // namespace dyld3 | |
454 | ||
455 | #endif | |
456 | ||
457 |