forked from nygard/class-dump
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathclass-dump.m
More file actions
1246 lines (1135 loc) · 63.6 KB
/
class-dump.m
File metadata and controls
1246 lines (1135 loc) · 63.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// -*- mode: ObjC -*-
// This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
// Copyright (C) 1997-2019 Steve Nygard.
#include <stdio.h>
#include <libc.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <mach-o/arch.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#import "CDClassDump.h"
#import "CDFindMethodVisitor.h"
#import "CDClassDumpVisitor.h"
#import "CDMultiFileVisitor.h"
#import "CDFile.h"
#import "CDMachOFile.h"
#import "CDFatFile.h"
#import "CDFatArch.h"
#import "CDSearchPathState.h"
#import "CDMachOWriter.h"
#import "CDDyldCache.h"
#import "CDLCFilesetEntry.h"
#import "CDLoadCommand.h"
#import "CDCPlusPlusDumper.h"
#import "CDSwiftDumper.h"
#import "CDDecompiler.h"
void print_usage(void)
{
fprintf(stderr,
"class-dump %s\n"
"Usage: class-dump [options] <mach-o-file>\n"
"\n"
" where options are:\n"
" -a show instance variable offsets\n"
" -A show implementation addresses\n"
" --arch <arch> choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64, armv6, armv7, armv7s, arm64)\n"
" -C <regex> only display classes matching regular expression\n"
" -f <str> find string in method name\n"
" -H generate header files in current directory, or directory specified with -o\n"
" -I sort classes, categories, and protocols by inheritance (overrides -s)\n"
" -o <dir> output directory used for -H\n"
" -r recursively expand frameworks and fixed VM shared libraries\n"
" -s sort classes and categories by name\n"
" -S sort methods by name\n"
" -t suppress header in output, for testing\n"
" --list-arches list the arches in the file, then exit\n"
" --sdk-ios specify iOS SDK version (will look for /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk\n"
" or /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk)\n"
" --sdk-mac specify Mac OS X version (will look for /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX<version>.sdk\n"
" or /Developer/SDKs/MacOSX<version>.sdk)\n"
" --sdk-root specify the full SDK root path (or use --sdk-ios/--sdk-mac for a shortcut)\n"
" --show-mach-header dump the Mach-O header and exit\n"
" --show-load-commands dump the Mach-O load commands and exit\n"
" --lipo-info list architectures in a fat archive and exit\n"
" --thin <arch> extract a single architecture (with --out FILE)\n"
" --out <file> output path for write/extract operations\n"
" --id <name> set LC_ID_DYLIB (must fit in original space)\n"
" --change OLD,NEW change LC_LOAD_DYLIB / LC_REEXPORT_DYLIB / etc. matching OLD\n"
" --rpath OLD,NEW change LC_RPATH matching OLD\n"
" --add-rpath <path> add LC_RPATH (uses load command region slack)\n"
" --delete-rpath <path>delete LC_RPATH\n"
" --strip-codesig remove LC_CODE_SIGNATURE and trailing signature data\n"
" --dsc-info print dyld_shared_cache header info\n"
" --dsc-list-images list all images in a dyld_shared_cache\n"
" --list-fileset list LC_FILESET_ENTRY entries (kernelcache)\n"
" --extract-fileset NAME --out FILE\n"
" extract a fileset entry by name (raw slice)\n"
" --dsc-extract DIR extract every dylib from a dyld_shared_cache to DIR\n"
" (uses Apple's dsc_extractor.bundle from Xcode)\n"
" --with-cache FILE use a dyld_shared_cache file to resolve selectors and\n"
" type strings when class-dumping cache-extracted dylibs\n"
" --cpp dump C++ classes (from LC_SYMTAB Itanium-mangled symbols)\n"
" --swift dump Swift extensions/types (from LC_SYMTAB mangled symbols,\n"
" demangled via libswiftCore swift_demangle)\n"
" --dsc-class-dump CACHE_OR_DIR --out OUTDIR\n"
" extract every dylib from a cache (or use already-extracted\n"
" dir) and class-dump each into OUTDIR/<install-path>/\n"
" (combine with --cpp and/or --swift to additionally write\n"
" C++ .h files and Swift .swift files per image)\n"
" --scan-dir DIR recursively scan DIR for Mach-O/dylib files and feed their\n"
" Objective-C type encodings into a shared type pool so that\n"
" struct/union/protocol references in the primary binary get\n"
" resolved to fuller definitions across the binary set\n"
" (repeatable; pool images themselves are not emitted)\n"
" --auto-scan also scan the input file's containing directory as a\n"
" --scan-dir pool source (excluding the input itself)\n"
" --decompile run Ghidra's headless decompiler over each extracted\n"
" or class-dumped binary, writing a pseudo-C .c file\n"
" next to the .h output (requires Ghidra; honors\n"
" $GHIDRA_HOME or searches /Applications/ghidra*, ~/ghidra*,\n"
" /opt/ghidra*, /opt/homebrew/Caskroom/ghidra/*).\n"
" Hooks into --dsc-class-dump, --dsc-extract, and\n"
" --extract-fileset; also runs on a plain class-dump.\n"
" --decompile-swift like --decompile, but emits a <binary>.swift file\n"
" containing only Swift-mangled functions, with their\n"
" names demangled via Ghidra's Swift demangler\n"
" (NOTE: output is pseudo-C, not real Swift source).\n"
" Can be combined with --decompile.\n"
,
CLASS_DUMP_VERSION
);
}
#define CD_OPT_ARCH 1
#define CD_OPT_LIST_ARCHES 2
#define CD_OPT_VERSION 3
#define CD_OPT_SDK_IOS 4
#define CD_OPT_SDK_MAC 5
#define CD_OPT_SDK_ROOT 6
#define CD_OPT_HIDE 7
#define CD_OPT_SHOW_LC 8
#define CD_OPT_SHOW_HEADER 9
#define CD_OPT_OUT 20
#define CD_OPT_SET_ID 21
#define CD_OPT_CHANGE 22
#define CD_OPT_RPATH_CHG 23
#define CD_OPT_RPATH_ADD 24
#define CD_OPT_RPATH_DEL 25
#define CD_OPT_STRIP_SIG 26
#define CD_OPT_THIN 27
#define CD_OPT_LIPO_INFO 28
#define CD_OPT_DSC_INFO 30
#define CD_OPT_DSC_IMAGES 31
#define CD_OPT_FILESET_LS 32
#define CD_OPT_FILESET_EX 33
#define CD_OPT_DSC_EXTRACT 34
#define CD_OPT_WITH_CACHE 35
#define CD_OPT_CPP 36
#define CD_OPT_DSC_DUMPALL 37
#define CD_OPT_SWIFT 38
#define CD_OPT_SCAN_DIR 39
#define CD_OPT_AUTO_SCAN 40
#define CD_OPT_DECOMPILE 41
#define CD_OPT_DECOMPILE_SWIFT 42
int main(int argc, char *argv[])
{
@autoreleasepool {
NSString *searchString;
BOOL shouldGenerateSeparateHeaders = NO;
BOOL shouldListArches = NO;
BOOL shouldPrintVersion = NO;
CDArch targetArch;
BOOL hasSpecifiedArch = NO;
NSString *outputPath;
NSMutableSet *hiddenSections = [NSMutableSet set];
int ch;
BOOL errorFlag = NO;
struct option longopts[] = {
{ "show-ivar-offsets", no_argument, NULL, 'a' },
{ "show-imp-addr", no_argument, NULL, 'A' },
{ "match", required_argument, NULL, 'C' },
{ "find", required_argument, NULL, 'f' },
{ "generate-multiple-files", no_argument, NULL, 'H' },
{ "sort-by-inheritance", no_argument, NULL, 'I' },
{ "output-dir", required_argument, NULL, 'o' },
{ "recursive", no_argument, NULL, 'r' },
{ "sort", no_argument, NULL, 's' },
{ "sort-methods", no_argument, NULL, 'S' },
{ "arch", required_argument, NULL, CD_OPT_ARCH },
{ "list-arches", no_argument, NULL, CD_OPT_LIST_ARCHES },
{ "suppress-header", no_argument, NULL, 't' },
{ "version", no_argument, NULL, CD_OPT_VERSION },
{ "sdk-ios", required_argument, NULL, CD_OPT_SDK_IOS },
{ "sdk-mac", required_argument, NULL, CD_OPT_SDK_MAC },
{ "sdk-root", required_argument, NULL, CD_OPT_SDK_ROOT },
{ "hide", required_argument, NULL, CD_OPT_HIDE },
{ "show-load-commands", no_argument, NULL, CD_OPT_SHOW_LC },
{ "show-mach-header", no_argument, NULL, CD_OPT_SHOW_HEADER },
{ "out", required_argument, NULL, CD_OPT_OUT },
{ "id", required_argument, NULL, CD_OPT_SET_ID },
{ "change", required_argument, NULL, CD_OPT_CHANGE },
{ "rpath", required_argument, NULL, CD_OPT_RPATH_CHG },
{ "add-rpath", required_argument, NULL, CD_OPT_RPATH_ADD },
{ "delete-rpath", required_argument, NULL, CD_OPT_RPATH_DEL },
{ "strip-codesig", no_argument, NULL, CD_OPT_STRIP_SIG },
{ "thin", required_argument, NULL, CD_OPT_THIN },
{ "lipo-info", no_argument, NULL, CD_OPT_LIPO_INFO },
{ "dsc-info", no_argument, NULL, CD_OPT_DSC_INFO },
{ "dsc-list-images", no_argument, NULL, CD_OPT_DSC_IMAGES },
{ "list-fileset", no_argument, NULL, CD_OPT_FILESET_LS },
{ "extract-fileset", required_argument, NULL, CD_OPT_FILESET_EX },
{ "dsc-extract", required_argument, NULL, CD_OPT_DSC_EXTRACT },
{ "with-cache", required_argument, NULL, CD_OPT_WITH_CACHE },
{ "cpp", no_argument, NULL, CD_OPT_CPP },
{ "dsc-class-dump", required_argument, NULL, CD_OPT_DSC_DUMPALL },
{ "swift", no_argument, NULL, CD_OPT_SWIFT },
{ "scan-dir", required_argument, NULL, CD_OPT_SCAN_DIR },
{ "auto-scan", no_argument, NULL, CD_OPT_AUTO_SCAN },
{ "decompile", no_argument, NULL, CD_OPT_DECOMPILE },
{ "decompile-swift", no_argument, NULL, CD_OPT_DECOMPILE_SWIFT },
{ NULL, 0, NULL, 0 },
};
BOOL shouldShowLoadCommands = NO;
BOOL shouldShowMachHeader = NO;
NSString *writeOutPath = nil;
NSString *newDylibID = nil;
NSString *changeOldPath = nil;
NSString *changeNewPath = nil;
NSString *rpathOldPath = nil;
NSString *rpathNewPath = nil;
NSMutableArray<NSString *> *addRPaths = [NSMutableArray array];
NSMutableArray<NSString *> *deleteRPaths = [NSMutableArray array];
BOOL shouldStripCodesig = NO;
NSString *thinArch = nil;
BOOL shouldLipoInfo = NO;
BOOL shouldDscInfo = NO;
BOOL shouldDscListImages = NO;
BOOL shouldListFileset = NO;
NSString *extractFilesetName = nil;
NSString *dscExtractDir = nil;
BOOL shouldDumpCpp = NO;
BOOL shouldDumpSwift = NO;
NSString *dscDumpAllInput = nil;
NSMutableArray<NSString *> *scanDirs = [NSMutableArray array];
BOOL shouldAutoScan = NO;
BOOL shouldDecompile = NO;
BOOL shouldDecompileSwift = NO;
if (argc == 1) {
print_usage();
exit(0);
}
CDClassDump *classDump = [[CDClassDump alloc] init];
while ( (ch = getopt_long(argc, argv, "aAC:f:HIo:rRsSt", longopts, NULL)) != -1) {
switch (ch) {
case CD_OPT_ARCH: {
NSString *name = [NSString stringWithUTF8String:optarg];
targetArch = CDArchFromName(name);
if (targetArch.cputype != CPU_TYPE_ANY)
hasSpecifiedArch = YES;
else {
fprintf(stderr, "Error: Unknown arch %s\n\n", optarg);
errorFlag = YES;
}
break;
}
case CD_OPT_LIST_ARCHES:
shouldListArches = YES;
break;
case CD_OPT_VERSION:
shouldPrintVersion = YES;
break;
case CD_OPT_SDK_IOS: {
NSString *root = [NSString stringWithUTF8String:optarg];
//NSLog(@"root: %@", root);
NSString *str;
if ([[NSFileManager defaultManager] fileExistsAtPath: @"/Applications/Xcode.app"]) {
str = [NSString stringWithFormat:@"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS%@.sdk", root];
} else if ([[NSFileManager defaultManager] fileExistsAtPath: @"/Developer"]) {
str = [NSString stringWithFormat:@"/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS%@.sdk", root];
}
classDump.sdkRoot = str;
break;
}
case CD_OPT_SDK_MAC: {
NSString *root = [NSString stringWithUTF8String:optarg];
//NSLog(@"root: %@", root);
NSString *str;
if ([[NSFileManager defaultManager] fileExistsAtPath: @"/Applications/Xcode.app"]) {
str = [NSString stringWithFormat:@"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX%@.sdk", root];
} else if ([[NSFileManager defaultManager] fileExistsAtPath: @"/Developer"]) {
str = [NSString stringWithFormat:@"/Developer/SDKs/MacOSX%@.sdk", root];
}
classDump.sdkRoot = str;
break;
}
case CD_OPT_SDK_ROOT: {
NSString *root = [NSString stringWithUTF8String:optarg];
//NSLog(@"root: %@", root);
classDump.sdkRoot = root;
break;
}
case CD_OPT_SHOW_LC:
shouldShowLoadCommands = YES;
break;
case CD_OPT_SHOW_HEADER:
shouldShowMachHeader = YES;
break;
case CD_OPT_OUT:
writeOutPath = [NSString stringWithUTF8String:optarg];
break;
case CD_OPT_SET_ID:
newDylibID = [NSString stringWithUTF8String:optarg];
break;
case CD_OPT_CHANGE: {
NSString *arg = [NSString stringWithUTF8String:optarg];
NSRange comma = [arg rangeOfString:@","];
if (comma.location == NSNotFound) {
fprintf(stderr, "class-dump: --change expects OLD,NEW\n");
errorFlag = YES;
break;
}
changeOldPath = [arg substringToIndex:comma.location];
changeNewPath = [arg substringFromIndex:comma.location + 1];
break;
}
case CD_OPT_RPATH_CHG: {
NSString *arg = [NSString stringWithUTF8String:optarg];
NSRange comma = [arg rangeOfString:@","];
if (comma.location == NSNotFound) {
fprintf(stderr, "class-dump: --rpath expects OLD,NEW\n");
errorFlag = YES;
break;
}
rpathOldPath = [arg substringToIndex:comma.location];
rpathNewPath = [arg substringFromIndex:comma.location + 1];
break;
}
case CD_OPT_RPATH_ADD:
[addRPaths addObject:[NSString stringWithUTF8String:optarg]];
break;
case CD_OPT_RPATH_DEL:
[deleteRPaths addObject:[NSString stringWithUTF8String:optarg]];
break;
case CD_OPT_STRIP_SIG:
shouldStripCodesig = YES;
break;
case CD_OPT_THIN:
thinArch = [NSString stringWithUTF8String:optarg];
break;
case CD_OPT_LIPO_INFO:
shouldLipoInfo = YES;
break;
case CD_OPT_DSC_INFO:
shouldDscInfo = YES;
break;
case CD_OPT_DSC_IMAGES:
shouldDscListImages = YES;
break;
case CD_OPT_FILESET_LS:
shouldListFileset = YES;
break;
case CD_OPT_FILESET_EX:
extractFilesetName = [NSString stringWithUTF8String:optarg];
break;
case CD_OPT_DSC_EXTRACT:
dscExtractDir = [NSString stringWithUTF8String:optarg];
break;
case CD_OPT_CPP:
shouldDumpCpp = YES;
break;
case CD_OPT_DSC_DUMPALL:
dscDumpAllInput = [NSString stringWithUTF8String:optarg];
break;
case CD_OPT_SWIFT:
shouldDumpSwift = YES;
break;
case CD_OPT_SCAN_DIR:
[scanDirs addObject:[NSString stringWithUTF8String:optarg]];
break;
case CD_OPT_AUTO_SCAN:
shouldAutoScan = YES;
break;
case CD_OPT_DECOMPILE:
shouldDecompile = YES;
break;
case CD_OPT_DECOMPILE_SWIFT:
shouldDecompileSwift = YES;
break;
case CD_OPT_WITH_CACHE: {
NSString *cachePath = [NSString stringWithUTF8String:optarg];
NSData *cacheData = [NSData dataWithContentsOfFile:cachePath
options:NSDataReadingMappedAlways
error:NULL];
if (cacheData == nil) {
fprintf(stderr, "class-dump: cannot read cache %s\n", optarg);
errorFlag = YES;
break;
}
CDDyldCache *cache = [[CDDyldCache alloc] initWithData:cacheData];
if (cache == nil) {
fprintf(stderr, "class-dump: %s is not a dyld_shared_cache\n", optarg);
errorFlag = YES;
break;
}
classDump.backingCache = cache;
break;
}
case CD_OPT_HIDE: {
NSString *str = [NSString stringWithUTF8String:optarg];
if ([str isEqualToString:@"all"]) {
[hiddenSections addObject:@"structures"];
[hiddenSections addObject:@"protocols"];
} else {
[hiddenSections addObject:str];
}
break;
}
case 'a':
classDump.shouldShowIvarOffsets = YES;
break;
case 'A':
classDump.shouldShowMethodAddresses = YES;
break;
case 'C': {
NSError *error;
NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithUTF8String:optarg]
options:(NSRegularExpressionOptions)0
error:&error];
if (regularExpression != nil) {
classDump.regularExpression = regularExpression;
} else {
fprintf(stderr, "class-dump: Error with regular expression: %s\n\n", [[error localizedFailureReason] UTF8String]);
errorFlag = YES;
}
// Last one wins now.
break;
}
case 'f': {
searchString = [NSString stringWithUTF8String:optarg];
break;
}
case 'H':
shouldGenerateSeparateHeaders = YES;
break;
case 'I':
classDump.shouldSortClassesByInheritance = YES;
break;
case 'o':
outputPath = [NSString stringWithUTF8String:optarg];
break;
case 'r':
classDump.shouldProcessRecursively = YES;
break;
case 's':
classDump.shouldSortClasses = YES;
break;
case 'S':
classDump.shouldSortMethods = YES;
break;
case 't':
classDump.shouldShowHeader = NO;
break;
case '?':
default:
errorFlag = YES;
break;
}
}
if (errorFlag) {
print_usage();
exit(2);
}
if (shouldPrintVersion) {
printf("class-dump %s compiled %s\n", CLASS_DUMP_VERSION, __DATE__ " " __TIME__);
exit(0);
}
// Fail-fast: if --decompile / --decompile-swift was requested but
// Ghidra cannot be found, tell the user now rather than after the
// dump has produced its other output.
if (shouldDecompile || shouldDecompileSwift) {
NSString *gh = [CDDecompiler findGhidraHome];
if (gh == nil) {
fprintf(stderr, "class-dump: --decompile%s: Ghidra not found.\n%s\n",
shouldDecompileSwift ? "-swift" : "",
[[CDDecompiler installHint] UTF8String]);
exit(1);
}
fprintf(stderr, "class-dump: decompile: using Ghidra at %s\n", [gh UTF8String]);
}
if (dscDumpAllInput) {
if (writeOutPath == nil) {
fprintf(stderr, "class-dump: --dsc-class-dump requires --out DIR\n");
exit(1);
}
NSFileManager *fm = [NSFileManager defaultManager];
BOOL isDir = NO;
if (![fm fileExistsAtPath:dscDumpAllInput isDirectory:&isDir]) {
fprintf(stderr, "class-dump: %s does not exist\n", [dscDumpAllInput UTF8String]);
exit(1);
}
NSString *extractedDir = dscDumpAllInput;
CDDyldCache *bulkCache = classDump.backingCache;
if (!isDir) {
// Treat as a cache file: extract first to a temp dir, then walk.
NSString *tmp = [NSTemporaryDirectory() stringByAppendingPathComponent:
[@"class-dump-dsc-" stringByAppendingString:[[NSUUID UUID] UUIDString]]];
if (![fm createDirectoryAtPath:tmp withIntermediateDirectories:YES attributes:nil error:NULL]) {
fprintf(stderr, "class-dump: cannot create %s\n", [tmp UTF8String]);
exit(1);
}
static NSString * const kBundleSearchPaths[] = {
@"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle",
@"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/lib/dsc_extractor.bundle",
@"/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/usr/lib/dsc_extractor.bundle",
@"/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/usr/lib/dsc_extractor.bundle",
@"/Applications/Xcode.app/Contents/Developer/Platforms/XROS.platform/usr/lib/dsc_extractor.bundle",
};
void *bundle = NULL;
for (size_t i = 0; i < sizeof(kBundleSearchPaths)/sizeof(kBundleSearchPaths[0]); i++) {
if ([fm fileExistsAtPath:kBundleSearchPaths[i]]) {
bundle = dlopen([kBundleSearchPaths[i] fileSystemRepresentation], RTLD_LAZY);
if (bundle) break;
}
}
if (bundle == NULL) {
fprintf(stderr, "class-dump: dsc_extractor.bundle not found\n");
exit(1);
}
int (*extract)(const char *, const char *, void (^)(unsigned, unsigned)) =
dlsym(bundle, "dyld_shared_cache_extract_dylibs_progress");
if (extract == NULL) {
fprintf(stderr, "class-dump: dyld_shared_cache_extract_dylibs_progress not found\n");
exit(1);
}
fprintf(stderr, "class-dump: extracting cache to %s\n", [tmp UTF8String]);
__block unsigned last = 0;
int rc = extract([dscDumpAllInput fileSystemRepresentation],
[tmp fileSystemRepresentation],
^(unsigned cur, unsigned total) {
if (cur == total || cur - last >= 100 || cur == 1) {
fprintf(stderr, "\rclass-dump: extract %u/%u", cur, total);
fflush(stderr);
last = cur;
}
});
fprintf(stderr, "\n");
if (rc != 0) {
fprintf(stderr, "class-dump: extraction failed (rc=%d)\n", rc);
exit(1);
}
extractedDir = tmp;
// Use the cache itself as backing for cross-image resolution.
if (bulkCache == nil) {
NSData *cdata = [NSData dataWithContentsOfFile:dscDumpAllInput
options:NSDataReadingMappedAlways
error:NULL];
if (cdata) bulkCache = [[CDDyldCache alloc] initWithData:cdata];
}
}
if (![fm fileExistsAtPath:writeOutPath]) {
[fm createDirectoryAtPath:writeOutPath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// Walk extractedDir for Mach-O dylibs and class-dump each.
NSDirectoryEnumerator *en = [fm enumeratorAtPath:extractedDir];
unsigned processed = 0, succeeded = 0, failed = 0;
for (NSString *rel in en) {
NSString *full = [extractedDir stringByAppendingPathComponent:rel];
NSDictionary *attrs = [en fileAttributes];
if (![[attrs fileType] isEqualToString:NSFileTypeRegular]) continue;
if ([attrs fileSize] < 4) continue;
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:full];
NSData *head = [fh readDataOfLength:4];
[fh closeFile];
if ([head length] != 4) continue;
uint32_t magic;
memcpy(&magic, [head bytes], 4);
if (magic != MH_MAGIC && magic != MH_MAGIC_64
&& magic != MH_CIGAM && magic != MH_CIGAM_64
&& magic != FAT_MAGIC && magic != FAT_CIGAM
&& magic != FAT_MAGIC_64 && magic != FAT_CIGAM_64) continue;
processed++;
if (processed % 50 == 0) {
fprintf(stderr, "\rclass-dump: dumped %u/? ok=%u fail=%u", processed, succeeded, failed);
fflush(stderr);
}
@autoreleasepool {
CDClassDump *cd = [[CDClassDump alloc] init];
if (bulkCache) cd.backingCache = bulkCache;
CDSearchPathState *sp = [[CDSearchPathState alloc] init];
sp.executablePath = [full stringByDeletingLastPathComponent];
cd.searchPathState.executablePath = sp.executablePath;
CDFile *file = [CDFile fileWithContentsOfFile:full searchPathState:cd.searchPathState];
if (file == nil) { failed++; continue; }
CDArch arch;
if (![file bestMatchForLocalArch:&arch]) { failed++; continue; }
cd.targetArch = arch;
NSError *err = nil;
if (![cd loadFile:file error:&err]) { failed++; continue; }
NSString *outSub = [writeOutPath stringByAppendingPathComponent:rel];
[fm createDirectoryAtPath:outSub withIntermediateDirectories:YES attributes:nil error:NULL];
@try {
[cd processObjectiveCData];
[cd registerTypes];
CDMultiFileVisitor *v = [[CDMultiFileVisitor alloc] init];
v.classDump = cd;
cd.typeController.delegate = v;
v.outputPath = outSub;
[cd recursivelyVisit:v];
if (shouldDumpCpp || shouldDumpSwift) {
CDMachOFile *mf = [cd.machOFiles lastObject];
if (mf) {
if (shouldDumpCpp) {
NSError *e = nil;
if (![CDCPlusPlusDumper writeHeadersForMachOFile:mf toDirectory:outSub error:&e]) {
fprintf(stderr, "class-dump: cpp dump for %s failed: %s\n",
[rel UTF8String], [[e localizedDescription] UTF8String]);
}
}
if (shouldDumpSwift) {
NSError *e = nil;
if (![CDSwiftDumper writeHeadersForMachOFile:mf toDirectory:outSub error:&e]) {
fprintf(stderr, "class-dump: swift dump for %s failed: %s\n",
[rel UTF8String], [[e localizedDescription] UTF8String]);
}
}
}
}
if (shouldDecompile) {
NSString *cOut = [outSub stringByAppendingPathComponent:
[[full lastPathComponent] stringByAppendingPathExtension:@"c"]];
NSError *de = nil;
if (![CDDecompiler decompileMachOAtPath:full toPath:cOut error:&de]) {
fprintf(stderr, "class-dump: decompile %s failed: %s\n",
[rel UTF8String], [[de localizedFailureReason] UTF8String]);
}
}
if (shouldDecompileSwift) {
NSString *sOut = [outSub stringByAppendingPathComponent:
[[full lastPathComponent] stringByAppendingPathExtension:@"swift"]];
NSError *de = nil;
if (![CDDecompiler decompileSwiftMachOAtPath:full toPath:sOut error:&de]) {
fprintf(stderr, "class-dump: decompile-swift %s failed: %s\n",
[rel UTF8String], [[de localizedFailureReason] UTF8String]);
}
}
succeeded++;
} @catch (NSException *e) {
failed++;
}
}
}
fprintf(stderr, "\nclass-dump: dumped %u images (ok=%u fail=%u) into %s\n",
processed, succeeded, failed, [writeOutPath UTF8String]);
exit(0);
}
BOOL hasWriteOp = (newDylibID || changeOldPath || rpathOldPath ||
[addRPaths count] || [deleteRPaths count] ||
shouldStripCodesig || thinArch);
if (optind < argc && dscExtractDir) {
NSString *cachePath = [NSString stringWithFileSystemRepresentation:argv[optind]];
// dyld_shared_cache_extract_dylibs_progress lives in dsc_extractor.bundle
// (shipped with Xcode). It works on caches for any platform.
static NSString * const kBundleSearchPaths[] = {
@"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle",
@"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/lib/dsc_extractor.bundle",
@"/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/usr/lib/dsc_extractor.bundle",
@"/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/usr/lib/dsc_extractor.bundle",
@"/Applications/Xcode.app/Contents/Developer/Platforms/XROS.platform/usr/lib/dsc_extractor.bundle",
};
void *bundle = NULL;
NSFileManager *fm = [NSFileManager defaultManager];
for (size_t i = 0; i < sizeof(kBundleSearchPaths)/sizeof(kBundleSearchPaths[0]); i++) {
if ([fm fileExistsAtPath:kBundleSearchPaths[i]]) {
bundle = dlopen([kBundleSearchPaths[i] fileSystemRepresentation], RTLD_LAZY);
if (bundle) break;
}
}
if (bundle == NULL) {
fprintf(stderr, "class-dump: dsc_extractor.bundle not found in any Xcode platform; install Xcode\n");
exit(1);
}
int (*extract)(const char *, const char *, void (^)(unsigned, unsigned)) =
dlsym(bundle, "dyld_shared_cache_extract_dylibs_progress");
if (extract == NULL) {
fprintf(stderr, "class-dump: dyld_shared_cache_extract_dylibs_progress not found in dsc_extractor.bundle\n");
exit(1);
}
if (![fm fileExistsAtPath:dscExtractDir]) {
NSError *e = nil;
if (![fm createDirectoryAtPath:dscExtractDir withIntermediateDirectories:YES attributes:nil error:&e]) {
fprintf(stderr, "class-dump: cannot create %s: %s\n",
[dscExtractDir UTF8String], [[e localizedDescription] UTF8String]);
exit(1);
}
}
__block unsigned lastReported = 0;
int rc = extract([cachePath fileSystemRepresentation],
[dscExtractDir fileSystemRepresentation],
^(unsigned cur, unsigned total) {
if (cur == total || cur - lastReported >= 50 || cur == 1) {
fprintf(stderr, "\rclass-dump: extracting %u/%u", cur, total);
fflush(stderr);
lastReported = cur;
}
});
fprintf(stderr, "\n");
if (rc != 0) {
fprintf(stderr, "class-dump: dyld_shared_cache_extract_dylibs_progress failed (rc=%d)\n", rc);
exit(1);
}
if (shouldDecompile || shouldDecompileSwift) {
NSDirectoryEnumerator *den = [fm enumeratorAtPath:dscExtractDir];
unsigned dcDone = 0, dcFail = 0, swDone = 0, swFail = 0;
for (NSString *rel in den) {
@autoreleasepool {
NSString *full = [dscExtractDir stringByAppendingPathComponent:rel];
NSDictionary *attrs = [den fileAttributes];
if (![[attrs fileType] isEqualToString:NSFileTypeRegular]) continue;
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:full];
NSData *head = [fh readDataOfLength:4];
[fh closeFile];
if ([head length] != 4) continue;
uint32_t magic;
memcpy(&magic, [head bytes], 4);
if (magic != MH_MAGIC && magic != MH_MAGIC_64
&& magic != MH_CIGAM && magic != MH_CIGAM_64) continue;
if (shouldDecompile) {
NSString *cOut = [full stringByAppendingPathExtension:@"c"];
NSError *de = nil;
if ([CDDecompiler decompileMachOAtPath:full toPath:cOut error:&de]) dcDone++;
else {
dcFail++;
fprintf(stderr, "class-dump: decompile %s failed: %s\n",
[rel UTF8String], [[de localizedFailureReason] UTF8String]);
}
}
if (shouldDecompileSwift) {
NSString *sOut = [full stringByAppendingPathExtension:@"swift"];
NSError *de = nil;
if ([CDDecompiler decompileSwiftMachOAtPath:full toPath:sOut error:&de]) swDone++;
else {
swFail++;
fprintf(stderr, "class-dump: decompile-swift %s failed: %s\n",
[rel UTF8String], [[de localizedFailureReason] UTF8String]);
}
}
if ((dcDone + dcFail + swDone + swFail) % 20 == 0) {
fprintf(stderr, "\rclass-dump: decompiled c=%u/%u swift=%u/%u",
dcDone, dcDone + dcFail, swDone, swDone + swFail);
fflush(stderr);
}
}
}
fprintf(stderr, "\nclass-dump: decompile finished: c ok=%u fail=%u, swift ok=%u fail=%u\n",
dcDone, dcFail, swDone, swFail);
}
exit(0);
}
if (optind < argc && (shouldDscInfo || shouldDscListImages)) {
NSString *arg = [NSString stringWithFileSystemRepresentation:argv[optind]];
NSData *fileData = [NSData dataWithContentsOfFile:arg
options:NSDataReadingMappedAlways
error:NULL];
if (fileData == nil) {
fprintf(stderr, "class-dump: cannot read %s\n", [arg UTF8String]);
exit(1);
}
CDDyldCache *cache = [[CDDyldCache alloc] initWithData:fileData];
if (cache == nil) {
fprintf(stderr, "class-dump: %s is not a dyld_shared_cache\n", [arg UTF8String]);
exit(1);
}
if (shouldDscInfo) {
printf("magic: %s\n", [cache.magic UTF8String]);
printf("mappings: %u (offset 0x%x)\n", cache.mappingCount, cache.mappingOffset);
printf("images: %lu\n", (unsigned long)[cache.images count]);
printf("layout: %s\n", cache.usesLegacyImageTable ? "legacy" : "modern");
if (cache.platform) printf("platform: %u\n", cache.platform);
}
if (shouldDscListImages) {
for (CDDyldCacheImageInfo *img in cache.images) {
printf("0x%016llx %s\n", img.address, [img.path UTF8String]);
}
}
exit(0);
}
if (optind < argc && (shouldListFileset || extractFilesetName)) {
NSString *arg = [NSString stringWithFileSystemRepresentation:argv[optind]];
NSData *fileData = [NSData dataWithContentsOfFile:arg
options:NSDataReadingMappedAlways
error:NULL];
if (fileData == nil) {
fprintf(stderr, "class-dump: cannot read %s\n", [arg UTF8String]);
exit(1);
}
CDSearchPathState *sp = [[CDSearchPathState alloc] init];
sp.executablePath = [arg stringByDeletingLastPathComponent];
id parsed = [CDFile fileWithContentsOfFile:arg searchPathState:sp];
CDMachOFile *macho = nil;
if ([parsed isKindOfClass:[CDMachOFile class]]) macho = parsed;
else if ([parsed isKindOfClass:[CDFatFile class]]) {
CDArch a;
if ([parsed bestMatchForLocalArch:&a]) macho = [parsed machOFileWithArch:a];
}
if (macho == nil) {
fprintf(stderr, "class-dump: not a Mach-O\n");
exit(1);
}
NSMutableArray<CDLCFilesetEntry *> *entries = [NSMutableArray array];
for (CDLoadCommand *lc in macho.loadCommands) {
if ([lc isKindOfClass:[CDLCFilesetEntry class]]) [entries addObject:(CDLCFilesetEntry *)lc];
}
if ([entries count] == 0) {
fprintf(stderr, "class-dump: no LC_FILESET_ENTRY load commands found\n");
exit(1);
}
if (shouldListFileset) {
NSArray *sorted = [entries sortedArrayUsingComparator:^NSComparisonResult(CDLCFilesetEntry *a, CDLCFilesetEntry *b) {
if (a.fileoff < b.fileoff) return NSOrderedAscending;
if (a.fileoff > b.fileoff) return NSOrderedDescending;
return NSOrderedSame;
}];
for (CDLCFilesetEntry *e in sorted) {
printf("vmaddr 0x%016llx fileoff 0x%016llx %s\n",
e.vmaddr, e.fileoff, [e.entryID UTF8String]);
}
}
if (extractFilesetName) {
if (writeOutPath == nil) {
fprintf(stderr, "class-dump: --extract-fileset requires --out\n");
exit(1);
}
CDLCFilesetEntry *target = nil;
for (CDLCFilesetEntry *e in entries) {
if ([e.entryID isEqualToString:extractFilesetName]) { target = e; break; }
}
if (target == nil) {
fprintf(stderr, "class-dump: fileset entry '%s' not found\n", [extractFilesetName UTF8String]);
exit(1);
}
// Naive extraction: copy from this entry's fileoff to the next entry's fileoff.
// This works for typical kernelcache layouts where entries are contiguous;
// it does NOT rebase per-segment fileoff fields, so the resulting Mach-O
// segments still reference offsets relative to the original cache.
uint64_t end = [fileData length];
for (CDLCFilesetEntry *e in entries) {
if (e.fileoff > target.fileoff && e.fileoff < end) end = e.fileoff;
}
if (target.fileoff >= [fileData length]) {
fprintf(stderr, "class-dump: fileset entry fileoff 0x%llx out of bounds\n", target.fileoff);
exit(1);
}
NSData *slice = [fileData subdataWithRange:NSMakeRange((NSUInteger)target.fileoff,
(NSUInteger)(end - target.fileoff))];
if (![slice writeToFile:writeOutPath atomically:YES]) {
fprintf(stderr, "class-dump: cannot write %s\n", [writeOutPath UTF8String]);
exit(1);
}
fprintf(stderr, "class-dump: extracted %llu bytes to %s (raw slice; segment file offsets not rebased)\n",
end - target.fileoff, [writeOutPath UTF8String]);
if (shouldDecompile) {
NSString *cOut = [writeOutPath stringByAppendingPathExtension:@"c"];
NSError *de = nil;
if ([CDDecompiler decompileMachOAtPath:writeOutPath toPath:cOut error:&de]) {
fprintf(stderr, "class-dump: decompiled to %s\n", [cOut UTF8String]);
} else {
fprintf(stderr, "class-dump: decompile failed: %s\n",
[[de localizedFailureReason] UTF8String]);
}
}
if (shouldDecompileSwift) {
NSString *sOut = [writeOutPath stringByAppendingPathExtension:@"swift"];
NSError *de = nil;
if ([CDDecompiler decompileSwiftMachOAtPath:writeOutPath toPath:sOut error:&de]) {
fprintf(stderr, "class-dump: swift decompiled to %s\n", [sOut UTF8String]);
} else {
fprintf(stderr, "class-dump: decompile-swift failed: %s\n",
[[de localizedFailureReason] UTF8String]);
}
}
}
exit(0);
}
if (optind < argc && (hasWriteOp || shouldLipoInfo)) {
NSString *arg = [NSString stringWithFileSystemRepresentation:argv[optind]];
NSString *executablePath = [arg executablePathForFilename] ?: arg;
NSData *fileData = [NSData dataWithContentsOfFile:executablePath];
if (fileData == nil) {
fprintf(stderr, "class-dump: cannot read %s\n", [executablePath UTF8String]);
exit(1);
}
if (shouldLipoInfo) {
NSArray *archs = [CDMachOWriter architecturesInData:fileData];
printf("%s\n", [[archs componentsJoinedByString:@" "] UTF8String]);
exit(0);
}
BOOL hasMutateOp = (newDylibID || changeOldPath || rpathOldPath ||
[addRPaths count] || [deleteRPaths count] || shouldStripCodesig);
// For thin extraction with no other mutations, write the slice and exit.
if (thinArch && !hasMutateOp) {
if (writeOutPath == nil) {
fprintf(stderr, "class-dump: --thin requires --out\n");
exit(1);
}
NSError *err = nil;
NSData *slice = [CDMachOWriter thinSliceForArch:thinArch fromFatData:fileData error:&err];
if (slice == nil) {
fprintf(stderr, "class-dump: %s\n", [[err localizedDescription] UTF8String]);
exit(1);
}
if (![slice writeToFile:writeOutPath atomically:YES]) {
fprintf(stderr, "class-dump: cannot write %s\n", [writeOutPath UTF8String]);
exit(1);
}
exit(0);
}
// Mutating operations: require thin input.
NSMutableData *workingData = nil;
if (thinArch) {
NSError *err = nil;
NSData *slice = [CDMachOWriter thinSliceForArch:thinArch fromFatData:fileData error:&err];
if (slice == nil) {
fprintf(stderr, "class-dump: %s\n", [[err localizedDescription] UTF8String]);
exit(1);
}
workingData = [slice mutableCopy];
} else {
NSArray *archs = [CDMachOWriter architecturesInData:fileData];
if ([archs count] != 1) {
fprintf(stderr, "class-dump: input is fat (%s); use --thin <arch> first\n",
[[archs componentsJoinedByString:@" "] UTF8String]);
exit(1);
}
workingData = [fileData mutableCopy];
}