From 9f83892a2b93eb5239bb24af5371fef9407632b4 Mon Sep 17 00:00:00 2001 From: Apple Date: Thu, 22 Sep 2016 17:56:16 +0000 Subject: [PATCH] dyld-421.1.tar.gz --- .clang-format | 13 + bin/set-alt-dyld | 2 +- configs/dyld.xcconfig | 5 - configs/libdyld.xcconfig | 6 +- doc/man/man1/dyld.1 | 6 +- doc/man/man1/update_dyld_shared_cache.1 | 25 +- dyld.xcodeproj/project.pbxproj | 1013 ++++- include/mach-o/dyld_images.h | 15 +- include/mach-o/dyld_priv.h | 149 +- include/mach-o/dyld_process_info.h | 135 + include/objc-shared-cache.h | 61 +- .../AdjustForNewSegmentLocation.cpp | 1059 +++++ interlinked-dylibs/BindAllImages.cpp | 761 ++++ interlinked-dylibs/CodeSigningTypes.h | 130 + interlinked-dylibs/FileCache.cpp | 234 + interlinked-dylibs/Logging.cpp | 257 ++ interlinked-dylibs/Logging.h | 132 + interlinked-dylibs/MachOProxy.cpp | 254 ++ interlinked-dylibs/MachOProxy.h | 66 + interlinked-dylibs/Manifest.h | 208 + interlinked-dylibs/Manifest.mm | 740 +++ interlinked-dylibs/MultiCacheBuilder.h | 48 + interlinked-dylibs/MultiCacheBuilder.mm | 441 ++ .../ObjC1Abstraction.hpp | 119 +- interlinked-dylibs/ObjC2Abstraction.hpp | 1217 +++++ interlinked-dylibs/OptimizerBranches.cpp | 1472 ++++++ interlinked-dylibs/OptimizerBranches.h | 36 + interlinked-dylibs/OptimizerLinkedit.cpp | 1185 +++++ interlinked-dylibs/OptimizerObjC.cpp | 826 ++++ interlinked-dylibs/SharedCache.cpp | 1623 +++++++ interlinked-dylibs/Trie.hpp | 498 ++ .../dyld_shared_cache_builder.mm | 387 ++ interlinked-dylibs/mega-dylib-utils.h | 255 ++ .../multi_dyld_shared_cache_builder.mm | 289 ++ .../update_dyld_shared_cache.mm | 448 ++ .../update_dyld_shared_cache_compat.cpp | 309 ++ launch-cache/CacheFileAbstraction.hpp | 214 +- launch-cache/MachOFileAbstraction.hpp | 7 +- launch-cache/MachOLayout.hpp | 8 +- launch-cache/MachORebaser.hpp | 2 + launch-cache/MachOTrie.hpp | 12 +- launch-cache/ObjCModernAbstraction.hpp | 1234 ----- launch-cache/dsc_extractor.cpp | 32 +- launch-cache/dyld_cache_format.h | 164 +- launch-cache/dyld_shared_cache_util.cpp | 309 +- launch-cache/update_dyld_shared_cache.cpp | 4033 ----------------- src/ImageLoader.cpp | 367 +- src/ImageLoader.h | 142 +- src/ImageLoaderMachO.cpp | 785 +++- src/ImageLoaderMachO.h | 53 +- src/ImageLoaderMachOClassic.cpp | 47 +- src/ImageLoaderMachOClassic.h | 10 +- src/ImageLoaderMachOCompressed.cpp | 525 +-- src/ImageLoaderMachOCompressed.h | 15 +- src/ImageLoaderMegaDylib.cpp | 1144 +++++ src/ImageLoaderMegaDylib.h | 255 ++ src/dyld.cpp | 1871 ++++++-- src/dyld.exp | 2 +- src/dyld.h | 27 +- src/dyld.order | 26 +- src/dyldAPIs.cpp | 390 +- src/dyldAPIsInLibSystem.cpp | 246 +- src/dyldInitialization.cpp | 2 +- src/dyldNew.cpp | 2 +- src/dyldStartup.s | 25 +- src/dyldSyscallInterface.h | 17 +- src/dyld_gdb.cpp | 91 +- src/dyld_process_info.cpp | 648 +++ src/dyld_process_info_internal.h | 135 + src/dyld_process_info_notify.cpp | 310 ++ src/glue.c | 204 +- src/threadLocalHelpers.s | 1 + src/threadLocalVariables.c | 23 +- testing/README.txt | 47 + testing/build_tests.py | 199 + testing/nocr/execserver.defs | 1 + testing/nocr/nocr.1 | 26 + testing/nocr/nocr.c | 189 + testing/run_all_dyld_tests.py | 114 + testing/task_for_pid_entitlement.plist | 10 + .../test-cases/NSAddImage-basic.dtest/main.c | 28 + .../test-cases/NSAddImage-basic.dtest/zzz.c | 1 + testing/test-cases/dlopen-basic.dtest/foo.c | 5 + testing/test-cases/dlopen-basic.dtest/main.c | 48 + .../dlopen-framework-fallback.dtest/main.c | 35 + .../test-cases/dlopen-signing.dtest/dylib.c | 3 + .../test-cases/dlopen-signing.dtest/main.c | 48 + .../dyld_abort_payload.dtest/defSymbol.c | 5 + .../dyld_abort_payload.dtest/emptyMain.c | 7 + .../test-cases/dyld_abort_payload.dtest/foo.c | 5 + .../dyld_abort_payload.dtest/main.c | 173 + .../dyld_abort_payload.dtest/useSymbol.c | 8 + .../dyld_process_info.dtest/linksWithCF.c | 8 + .../test-cases/dyld_process_info.dtest/main.c | 177 + .../dyld_process_info_notify.dtest/foo.c | 4 + .../dyld_process_info_notify.dtest/main.c | 294 ++ .../dyld_process_info_notify.dtest/target.c | 22 + .../dylib-re-export-old-format.dtest/bar.c | 5 + .../dylib-re-export-old-format.dtest/foo.c | 3 + .../dylib-re-export-old-format.dtest/main.c | 26 + .../test-cases/dylib-re-export.dtest/bar.c | 5 + .../test-cases/dylib-re-export.dtest/foo.c | 3 + .../test-cases/dylib-re-export.dtest/main.c | 26 + .../test-cases/dylib-static-link.dtest/foo.c | 3 + .../dylib-static-link.dtest/missing.c | 12 + .../dylib-static-link.dtest/present.c | 28 + .../dylib-static-weak-link.dtest/foo.c | 3 + .../dylib-static-weak-link.dtest/missing.c | 19 + .../dylib-static-weak-link.dtest/present.c | 33 + testing/test-cases/interpose-weak.dtest/foo.c | 12 + .../interpose-weak.dtest/interposer.c | 15 + .../test-cases/interpose-weak.dtest/main.c | 70 + .../test-cases/restrict-search.dtest/foo.c | 5 + .../test-cases/restrict-search.dtest/main.c | 54 + .../thread-local-variables.dtest/foo.c | 8 + .../thread-local-variables.dtest/main.c | 115 + .../coreSymbolication-notify/Makefile | 8 +- unit-tests/test-cases/crt-apple/main.c | 10 +- unit-tests/test-cases/dlopen-sandbox/Makefile | 44 + unit-tests/test-cases/dlopen-sandbox/base.sb | 9 + unit-tests/test-cases/dlopen-sandbox/foo.c | 7 + unit-tests/test-cases/dlopen-sandbox/main.c | 46 + .../dyld_is_memory_immutable/Makefile | 21 + .../test-cases/dyld_is_memory_immutable/bar.c | 7 + .../test-cases/dyld_is_memory_immutable/foo.c | 6 + .../dyld_is_memory_immutable/main.c | 93 + .../dyld_shared_cache_iterate_text/Makefile | 37 + .../dyld_shared_cache_iterate_text/main.c | 73 + .../test-cases/image-state-change/main.c | 55 +- 129 files changed, 23285 insertions(+), 6835 deletions(-) create mode 100644 .clang-format create mode 100644 include/mach-o/dyld_process_info.h create mode 100644 interlinked-dylibs/AdjustForNewSegmentLocation.cpp create mode 100644 interlinked-dylibs/BindAllImages.cpp create mode 100644 interlinked-dylibs/CodeSigningTypes.h create mode 100644 interlinked-dylibs/FileCache.cpp create mode 100644 interlinked-dylibs/Logging.cpp create mode 100644 interlinked-dylibs/Logging.h create mode 100644 interlinked-dylibs/MachOProxy.cpp create mode 100644 interlinked-dylibs/MachOProxy.h create mode 100644 interlinked-dylibs/Manifest.h create mode 100644 interlinked-dylibs/Manifest.mm create mode 100644 interlinked-dylibs/MultiCacheBuilder.h create mode 100644 interlinked-dylibs/MultiCacheBuilder.mm rename launch-cache/ObjCLegacyAbstraction.hpp => interlinked-dylibs/ObjC1Abstraction.hpp (55%) create mode 100644 interlinked-dylibs/ObjC2Abstraction.hpp create mode 100644 interlinked-dylibs/OptimizerBranches.cpp create mode 100644 interlinked-dylibs/OptimizerBranches.h create mode 100644 interlinked-dylibs/OptimizerLinkedit.cpp create mode 100644 interlinked-dylibs/OptimizerObjC.cpp create mode 100644 interlinked-dylibs/SharedCache.cpp create mode 100644 interlinked-dylibs/Trie.hpp create mode 100644 interlinked-dylibs/dyld_shared_cache_builder.mm create mode 100644 interlinked-dylibs/mega-dylib-utils.h create mode 100644 interlinked-dylibs/multi_dyld_shared_cache_builder.mm create mode 100644 interlinked-dylibs/update_dyld_shared_cache.mm create mode 100644 interlinked-dylibs/update_dyld_shared_cache_compat.cpp delete mode 100644 launch-cache/ObjCModernAbstraction.hpp delete mode 100644 launch-cache/update_dyld_shared_cache.cpp create mode 100644 src/ImageLoaderMegaDylib.cpp create mode 100644 src/ImageLoaderMegaDylib.h create mode 100644 src/dyld_process_info.cpp create mode 100644 src/dyld_process_info_internal.h create mode 100644 src/dyld_process_info_notify.cpp create mode 100755 testing/README.txt create mode 100755 testing/build_tests.py create mode 100644 testing/nocr/execserver.defs create mode 100644 testing/nocr/nocr.1 create mode 100644 testing/nocr/nocr.c create mode 100755 testing/run_all_dyld_tests.py create mode 100644 testing/task_for_pid_entitlement.plist create mode 100644 testing/test-cases/NSAddImage-basic.dtest/main.c create mode 100644 testing/test-cases/NSAddImage-basic.dtest/zzz.c create mode 100644 testing/test-cases/dlopen-basic.dtest/foo.c create mode 100644 testing/test-cases/dlopen-basic.dtest/main.c create mode 100644 testing/test-cases/dlopen-framework-fallback.dtest/main.c create mode 100644 testing/test-cases/dlopen-signing.dtest/dylib.c create mode 100644 testing/test-cases/dlopen-signing.dtest/main.c create mode 100644 testing/test-cases/dyld_abort_payload.dtest/defSymbol.c create mode 100644 testing/test-cases/dyld_abort_payload.dtest/emptyMain.c create mode 100644 testing/test-cases/dyld_abort_payload.dtest/foo.c create mode 100644 testing/test-cases/dyld_abort_payload.dtest/main.c create mode 100644 testing/test-cases/dyld_abort_payload.dtest/useSymbol.c create mode 100644 testing/test-cases/dyld_process_info.dtest/linksWithCF.c create mode 100644 testing/test-cases/dyld_process_info.dtest/main.c create mode 100644 testing/test-cases/dyld_process_info_notify.dtest/foo.c create mode 100644 testing/test-cases/dyld_process_info_notify.dtest/main.c create mode 100644 testing/test-cases/dyld_process_info_notify.dtest/target.c create mode 100644 testing/test-cases/dylib-re-export-old-format.dtest/bar.c create mode 100644 testing/test-cases/dylib-re-export-old-format.dtest/foo.c create mode 100644 testing/test-cases/dylib-re-export-old-format.dtest/main.c create mode 100644 testing/test-cases/dylib-re-export.dtest/bar.c create mode 100644 testing/test-cases/dylib-re-export.dtest/foo.c create mode 100644 testing/test-cases/dylib-re-export.dtest/main.c create mode 100644 testing/test-cases/dylib-static-link.dtest/foo.c create mode 100644 testing/test-cases/dylib-static-link.dtest/missing.c create mode 100644 testing/test-cases/dylib-static-link.dtest/present.c create mode 100644 testing/test-cases/dylib-static-weak-link.dtest/foo.c create mode 100644 testing/test-cases/dylib-static-weak-link.dtest/missing.c create mode 100644 testing/test-cases/dylib-static-weak-link.dtest/present.c create mode 100644 testing/test-cases/interpose-weak.dtest/foo.c create mode 100644 testing/test-cases/interpose-weak.dtest/interposer.c create mode 100644 testing/test-cases/interpose-weak.dtest/main.c create mode 100644 testing/test-cases/restrict-search.dtest/foo.c create mode 100644 testing/test-cases/restrict-search.dtest/main.c create mode 100644 testing/test-cases/thread-local-variables.dtest/foo.c create mode 100644 testing/test-cases/thread-local-variables.dtest/main.c create mode 100644 unit-tests/test-cases/dlopen-sandbox/Makefile create mode 100644 unit-tests/test-cases/dlopen-sandbox/base.sb create mode 100644 unit-tests/test-cases/dlopen-sandbox/foo.c create mode 100644 unit-tests/test-cases/dlopen-sandbox/main.c create mode 100644 unit-tests/test-cases/dyld_is_memory_immutable/Makefile create mode 100644 unit-tests/test-cases/dyld_is_memory_immutable/bar.c create mode 100644 unit-tests/test-cases/dyld_is_memory_immutable/foo.c create mode 100644 unit-tests/test-cases/dyld_is_memory_immutable/main.c create mode 100644 unit-tests/test-cases/dyld_shared_cache_iterate_text/Makefile create mode 100644 unit-tests/test-cases/dyld_shared_cache_iterate_text/main.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1d7b0fd --- /dev/null +++ b/.clang-format @@ -0,0 +1,13 @@ +Language: Cpp +BasedOnStyle: WebKit + +AlignConsecutiveDeclarations: true +AlignOperands: false +AlignTrailingComments: true +IndentWidth: 4 + +Standard: Cpp11 + +UseTab: Never + +SortIncludes: false diff --git a/bin/set-alt-dyld b/bin/set-alt-dyld index 032bcf9..19ae128 100755 --- a/bin/set-alt-dyld +++ b/bin/set-alt-dyld @@ -16,7 +16,7 @@ foreach $arg (@ARGV) my $in = ; close IN or die $!; - if($in =~ s{/usr/lib/dyld}{/usr/lib/dyle}) + if($in =~ s{/usr/lib/dyld}{/usr/local/dy}) { open OUT, ">$arg" or die $!; print OUT $in; diff --git a/configs/dyld.xcconfig b/configs/dyld.xcconfig index fc7bcdc..13863b8 100644 --- a/configs/dyld.xcconfig +++ b/configs/dyld.xcconfig @@ -1,10 +1,5 @@ ALIGNMENT[arch=armv7s] = -Wl,-segalign,0x4000 -BASE_ADDRESS[arch=armv7*] = 0x1fe00000 -BASE_ADDRESS[arch=arm64] = 0x120000000 -BASE_ADDRESS[arch=i386] = 0x8fe00000 -BASE_ADDRESS[arch=x86_64] = 0x7fff5fc00000 - ENTRY[sdk=*simulator*] = -Wl,-e,_start_sim ENTRY[sdk=iphoneos*] = -Wl,-e,__dyld_start ENTRY[sdk=macosx*] = -Wl,-e,__dyld_start diff --git a/configs/libdyld.xcconfig b/configs/libdyld.xcconfig index 756db3d..d3c6d1c 100644 --- a/configs/libdyld.xcconfig +++ b/configs/libdyld.xcconfig @@ -1,7 +1,7 @@ -LIBSYSTEM_LIBS[sdk=*simulator*] = -Wl,-upward-lsystem_sim_platform -Wl,-upward-lsystem_malloc -Wl,-upward-lsystem_c -Wl,-upward-lsystem_sim_pthread -Wl,-upward-lxpc -Wl,-upward-lsystem_blocks -Wl,-upward-lsystem_sim_kernel -LIBSYSTEM_LIBS[sdk=iphoneos*] = -Wl,-upward-lsystem_platform -Wl,-upward-lsystem_malloc -Wl,-upward-lsystem_c -Wl,-upward-lsystem_pthread -Wl,-upward-lxpc -Wl,-upward-lsystem_blocks -Wl,-upward-lsystem_kernel -LIBSYSTEM_LIBS[sdk=macosx*] = -Wl,-upward-lsystem_platform -Wl,-upward-lsystem_malloc -Wl,-upward-lsystem_c -Wl,-upward-lsystem_pthread -Wl,-upward-lxpc -Wl,-upward-lsystem_blocks -Wl,-upward-lsystem_kernel +LIBSYSTEM_LIBS[sdk=*simulator*] = -Wl,-upward-lsystem_sim_platform -Wl,-upward-lsystem_malloc -Wl,-upward-lsystem_c -Wl,-upward-lsystem_sim_pthread -Wl,-upward-lxpc -Wl,-upward-lsystem_blocks -Wl,-upward-lsystem_sim_kernel -Wl,-upward-lsystem_sandbox -Wl,-upward-ldispatch +LIBSYSTEM_LIBS[sdk=iphoneos*] = -Wl,-upward-lsystem_platform -Wl,-upward-lsystem_malloc -Wl,-upward-lsystem_c -Wl,-upward-lsystem_pthread -Wl,-upward-lxpc -Wl,-upward-lsystem_blocks -Wl,-upward-lsystem_kernel -Wl,-upward-lsystem_sandbox -Wl,-upward-ldispatch +LIBSYSTEM_LIBS[sdk=macosx*] = -Wl,-upward-lsystem_platform -Wl,-upward-lsystem_malloc -Wl,-upward-lsystem_c -Wl,-upward-lsystem_pthread -Wl,-upward-lxpc -Wl,-upward-lsystem_blocks -Wl,-upward-lsystem_kernel -Wl,-upward-lsystem_sandbox -Wl,-upward-ldispatch INSTALL_PATH = /usr/lib/system diff --git a/doc/man/man1/dyld.1 b/doc/man/man1/dyld.1 index 50729a7..4c9e61d 100644 --- a/doc/man/man1/dyld.1 +++ b/doc/man/man1/dyld.1 @@ -217,7 +217,11 @@ lazily bound at the time of their first call. Right before the process's main() is called, dyld prints out information about how dyld spent its time. Useful for analyzing launch performance. .TP -.B DYLD_DISABLE_DOFS +.B DYLD_PRINT_STATISTICS_DETAILS +Right before the process's main() is called, dyld prints out detailed information about how +dyld spent its time. Useful for analyzing launch performance. +.TP +.B DYLD_DISABLE_DOFS Causes dyld not register dtrace static probes with the kernel. .TP .B DYLD_PRINT_INITIALIZERS diff --git a/doc/man/man1/update_dyld_shared_cache.1 b/doc/man/man1/update_dyld_shared_cache.1 index 9d524ad..7161fc5 100644 --- a/doc/man/man1/update_dyld_shared_cache.1 +++ b/doc/man/man1/update_dyld_shared_cache.1 @@ -1,4 +1,4 @@ -.Dd Oct 10, 2008 +.Dd June 23, 2016 .Dt update_dyld_shared_cache 1 .Os Darwin .Sh NAME @@ -11,12 +11,8 @@ .Op Fl arch Ar arch .Op Fl force .Op Fl debug -.Op Fl sort_by_name -.Op Fl universal_boot +.Op Fl universal_boot .Op Fl verify -.Op Fl dylib_list Ar file -.Op Fl iPhone -.Op Fl cache_dir Ar dir .Sh DESCRIPTION .Nm update_dyld_shared_cache ensures that dyld's shared cache is up-to-date. This tool is normally @@ -79,27 +75,12 @@ This option will cause to regenerated the shared cache files even if they appear to be already up-to-date. .It Fl debug This option prints out additional information about the work being done. -.It Fl sort_by_name -By default -.Nm update_dyld_shared_cache -assigns a random start address to each mach-o image in the cache. -This option causes the start addresses to be chosen in path order, thus subsequent runs will -produce the same address layout which can help reproduce some bugs. .It Fl universal_boot -This option can only be used running on an machine with an Intel processor. It builds caches -that can be used when booting on both 32-bit and 64-bit machines. -.It Fl dylib_list Ar file -Instead of scanning /var/db/dyld/shared_region_roots/, this option provides a file that contains -a list of the dylibs to use when building the shared cache file. +This option builds caches for all machines. .It Fl verify Will regenerate a shared cache in-memory that matches the randomization of the existing shared cache file. Then instead of writing the cache file, it compares the in-memory cache file to the on disk version and reports any differences. -.It Fl iPhone -indicates that cache is not for the current Mac OS X, but for rather for an iPhone -.It Fl cache_dir Ar directory -This option specifies the directory in which to create the cache file(s). If not specified, -the cache file(s) are created in the standard location (e.g. var/db/dyld/) of the root partition. .El .Sh FILES .Tp diff --git a/dyld.xcodeproj/project.pbxproj b/dyld.xcodeproj/project.pbxproj index e17ce86..89a7c1f 100644 --- a/dyld.xcodeproj/project.pbxproj +++ b/dyld.xcodeproj/project.pbxproj @@ -7,6 +7,22 @@ objects = { /* Begin PBXAggregateTarget section */ + 37A0AD0A1C15FFF500731E50 /* update_dyld_shared_cache */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 37A0AD0B1C15FFF500731E50 /* Build configuration list for PBXAggregateTarget "update_dyld_shared_cache" */; + buildPhases = ( + ); + dependencies = ( + 37A0AD1B1C16004600731E50 /* PBXTargetDependency */, + 37A0AD131C16003600731E50 /* PBXTargetDependency */, + 37A0AD151C16003600731E50 /* PBXTargetDependency */, + 37A0AD171C16003600731E50 /* PBXTargetDependency */, + 37A0AD191C16003600731E50 /* PBXTargetDependency */, + 37A0AD111C16003600731E50 /* PBXTargetDependency */, + ); + name = update_dyld_shared_cache; + productName = update_dyld_shared_cache; + }; F908134211D3ED0B00626CC1 /* libdyld */ = { isa = PBXAggregateTarget; buildConfigurationList = F908135211D3ED9000626CC1 /* Build configuration list for PBXAggregateTarget "libdyld" */; @@ -33,14 +49,70 @@ dependencies = ( F9ED4CA70630A78A00DF4E74 /* PBXTargetDependency */, F9ED4CA90630A78A00DF4E74 /* PBXTargetDependency */, - F93937380A94FB6A00070A07 /* PBXTargetDependency */, + 37A0AD0F1C16000F00731E50 /* PBXTargetDependency */, ); name = all; productName = all; }; + F9F6F4271C1FB0A700BD8FED /* dyld_tests */ = { + isa = PBXAggregateTarget; + buildConfigurationList = F9F6F42A1C1FB0A700BD8FED /* Build configuration list for PBXAggregateTarget "dyld_tests" */; + buildPhases = ( + F9F6F42B1C1FB0AE00BD8FED /* build */, + ); + dependencies = ( + F97FF3661C237F97000ACDD2 /* PBXTargetDependency */, + ); + name = dyld_tests; + productName = dyld_tests; + }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 3703A1141B38C1B300ADBA7F /* AdjustForNewSegmentLocation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6D1A367093001E839B /* AdjustForNewSegmentLocation.cpp */; }; + 3703A1161B38C1B300ADBA7F /* BindAllImages.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6E1A367093001E839B /* BindAllImages.cpp */; }; + 3703A1171B38C1B300ADBA7F /* OptimizerObjC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E19031A3FC1A9008DB73F /* OptimizerObjC.cpp */; }; + 3703A1181B38C1B300ADBA7F /* OptimizerBranches.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E18FF1A3FC1A9008DB73F /* OptimizerBranches.cpp */; }; + 3703A1191B38C1B300ADBA7F /* OptimizerLinkedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E19011A3FC1A9008DB73F /* OptimizerLinkedit.cpp */; }; + 3703A11A1B38C1B300ADBA7F /* MultiCacheBuilder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 371D29811B2F53C8000BBE48 /* MultiCacheBuilder.mm */; }; + 3703A11B1B38C1B300ADBA7F /* FileCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6F1A367093001E839B /* FileCache.cpp */; }; + 3703A11C1B38C1B300ADBA7F /* SharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE701A367093001E839B /* SharedCache.cpp */; }; + 3703A1261B38C22900ADBA7F /* dyld_shared_cache_builder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3703A1251B38C22900ADBA7F /* dyld_shared_cache_builder.mm */; }; + 370C6E541BDEF08000387223 /* libspindump.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 370C6E531BDEF08000387223 /* libspindump.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; + 370E5F421CC06CF8000158F2 /* Logging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 370E5F401CC06CF8000158F2 /* Logging.cpp */; }; + 370E5F431CC06CF8000158F2 /* Logging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 370E5F401CC06CF8000158F2 /* Logging.cpp */; }; + 370E5F441CC06CF8000158F2 /* Logging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 370E5F401CC06CF8000158F2 /* Logging.cpp */; }; + 371D29821B2F53C8000BBE48 /* MultiCacheBuilder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 371D29811B2F53C8000BBE48 /* MultiCacheBuilder.mm */; }; + 3733C9071BD98F6800420392 /* dsc_iterator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */; }; + 3733C9081BD98F6900420392 /* dsc_iterator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */; }; + 3733C9091BD98F6A00420392 /* dsc_iterator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */; }; + 375E6F441C59DEFF001BB760 /* MachOProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 376ABDB71C5930E7009F0011 /* MachOProxy.cpp */; }; + 376ABDB61C592CC0009F0011 /* Metabom.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 376ED1D71C46F2710051DD54 /* Metabom.framework */; }; + 376ABDBA1C5930E7009F0011 /* MachOProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 376ABDB71C5930E7009F0011 /* MachOProxy.cpp */; }; + 376ABDBB1C5930E7009F0011 /* MachOProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 376ABDB71C5930E7009F0011 /* MachOProxy.cpp */; }; + 376ED1D81C46F2710051DD54 /* Metabom.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 376ED1D71C46F2710051DD54 /* Metabom.framework */; }; + 377685F51AC4B27D00026E6C /* AdjustForNewSegmentLocation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6D1A367093001E839B /* AdjustForNewSegmentLocation.cpp */; }; + 377685F61AC4B27D00026E6C /* BindAllImages.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6E1A367093001E839B /* BindAllImages.cpp */; }; + 377685F71AC4B27D00026E6C /* OptimizerObjC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E19031A3FC1A9008DB73F /* OptimizerObjC.cpp */; }; + 377685F81AC4B27D00026E6C /* OptimizerBranches.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E18FF1A3FC1A9008DB73F /* OptimizerBranches.cpp */; }; + 377685F91AC4B27D00026E6C /* OptimizerLinkedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E19011A3FC1A9008DB73F /* OptimizerLinkedit.cpp */; }; + 377685FA1AC4B27D00026E6C /* FileCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6F1A367093001E839B /* FileCache.cpp */; }; + 377685FB1AC4B27D00026E6C /* SharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE701A367093001E839B /* SharedCache.cpp */; }; + 377686041AC4B2ED00026E6C /* multi_dyld_shared_cache_builder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 377686031AC4B2ED00026E6C /* multi_dyld_shared_cache_builder.mm */; }; + 378EE3B11BE88C47001C99FB /* Bom.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37F7A5961BB363820039043A /* Bom.framework */; }; + 378EE3B21BE88C4A001C99FB /* Bom.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37F7A5961BB363820039043A /* Bom.framework */; }; + 37BF1D761B6168150048BC27 /* Manifest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37BF1D731B6168150048BC27 /* Manifest.mm */; }; + 37BF1D771B6168150048BC27 /* Manifest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37BF1D731B6168150048BC27 /* Manifest.mm */; }; + 37F7A5951BB362CA0039043A /* update_dyld_shared_cache.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E93E291AAFB0B10080E640 /* update_dyld_shared_cache.mm */; }; + 37F7A5981BB364130039043A /* Manifest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37BF1D731B6168150048BC27 /* Manifest.mm */; }; + 37F7A5991BB364130039043A /* MultiCacheBuilder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 371D29811B2F53C8000BBE48 /* MultiCacheBuilder.mm */; }; + 37F7A59A1BB3642F0039043A /* FileCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6F1A367093001E839B /* FileCache.cpp */; }; + 37F7A59B1BB3642F0039043A /* SharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE701A367093001E839B /* SharedCache.cpp */; }; + 37F7A59C1BB364530039043A /* OptimizerBranches.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E18FF1A3FC1A9008DB73F /* OptimizerBranches.cpp */; }; + 37F7A59D1BB364530039043A /* OptimizerLinkedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E19011A3FC1A9008DB73F /* OptimizerLinkedit.cpp */; }; + 37F7A59E1BB364530039043A /* OptimizerObjC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98E19031A3FC1A9008DB73F /* OptimizerObjC.cpp */; }; + 37F7A59F1BB364530039043A /* AdjustForNewSegmentLocation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6D1A367093001E839B /* AdjustForNewSegmentLocation.cpp */; }; + 37F7A5A01BB364530039043A /* BindAllImages.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D0FE6E1A367093001E839B /* BindAllImages.cpp */; }; F908134C11D3ED6200626CC1 /* dyld.h in usr|include|mach-o */ = {isa = PBXBuildFile; fileRef = F9ED4CEA0630A80600DF4E74 /* dyld.h */; }; F908134D11D3ED6200626CC1 /* dyld_images.h in usr|include|mach-o */ = {isa = PBXBuildFile; fileRef = F98D274C0AA79D7400416316 /* dyld_images.h */; }; F908135911D3FA8700626CC1 /* dlfcn.h in usr|include */ = {isa = PBXBuildFile; fileRef = F99EE6AE06B48D4200BF1992 /* dlfcn.h */; }; @@ -57,11 +129,14 @@ F908136D11D3FB3A00626CC1 /* dyld.3 in usr|share|man|man3 */ = {isa = PBXBuildFile; fileRef = EF799FF0070D27BB00F78484 /* dyld.3 */; }; F908136E11D3FB3A00626CC1 /* dlopen_preflight.3 in usr|share|man|man3 */ = {isa = PBXBuildFile; fileRef = F9E572000A66EF41007D9BE9 /* dlopen_preflight.3 */; }; F913FADA0630A8AE00B7AE9D /* dyldAPIsInLibSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F913FAD90630A8AE00B7AE9D /* dyldAPIsInLibSystem.cpp */; }; - F93666E0163B4C42002ECADA /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F93666DF163B4C42002ECADA /* CoreFoundation.framework */; }; - F93666E2163B4C58002ECADA /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F93666E1163B4C58002ECADA /* Security.framework */; }; - F93937470A94FC4700070A07 /* update_dyld_shared_cache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F93937460A94FC4700070A07 /* update_dyld_shared_cache.cpp */; }; + F9280B7B1AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9280B791AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp */; }; F94DB9040F0A9B1700323715 /* ImageLoaderMachOClassic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F94DB9000F0A9B1700323715 /* ImageLoaderMachOClassic.cpp */; }; F94DB9050F0A9B1700323715 /* ImageLoaderMachOCompressed.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F94DB9020F0A9B1700323715 /* ImageLoaderMachOCompressed.cpp */; settings = {COMPILER_FLAGS = "-O3"; }; }; + F95090E51C5AD1E80031F81D /* dyld_process_info.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F95090E41C5AD1B30031F81D /* dyld_process_info.cpp */; }; + F958D4771C7FCE6700A0B199 /* dyld_process_info_notify.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F958D4761C7FCD4A00A0B199 /* dyld_process_info_notify.cpp */; }; + F97FF3601C236408000ACDD2 /* execserver.defs in Sources */ = {isa = PBXBuildFile; fileRef = F97FF35E1C236402000ACDD2 /* execserver.defs */; settings = {ATTRIBUTES = (Server, ); }; }; + F97FF3611C23640C000ACDD2 /* nocr.c in Sources */ = {isa = PBXBuildFile; fileRef = F97FF35F1C236402000ACDD2 /* nocr.c */; }; + F97FF3641C237F68000ACDD2 /* nocr.1 in install man page */ = {isa = PBXBuildFile; fileRef = F97FF3631C237F5C000ACDD2 /* nocr.1 */; }; F98C78F00F7C02E8006257D2 /* dsc_iterator.h in usr|local|include|mach-o */ = {isa = PBXBuildFile; fileRef = F9F2A56F0F7AEEE300B7C9EB /* dsc_iterator.h */; }; F99B8E630FEC11B400701838 /* dyld_shared_cache_util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F99B8E620FEC11B400701838 /* dyld_shared_cache_util.cpp */; }; F99B8EA30FEC1C4200701838 /* dsc_iterator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */; }; @@ -89,6 +164,7 @@ F9ED4CE50630A7F100DF4E74 /* stub_binding_helper.s in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CD50630A7F100DF4E74 /* stub_binding_helper.s */; }; F9F256360639DBCC00A7427D /* dyldLock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CCC0630A7F100DF4E74 /* dyldLock.cpp */; }; F9F2A5700F7AEEE300B7C9EB /* dsc_iterator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */; }; + F9FF8C161C69B080009F8A53 /* dyld_process_info.h in usr|local|include|mach-o */ = {isa = PBXBuildFile; fileRef = F95090D01C5AB89A0031F81D /* dyld_process_info.h */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -99,6 +175,7 @@ isEditable = 1; outputFiles = ( ); + script = ""; }; F921D317070376A6000D1056 /* PBXBuildRule */ = { isa = PBXBuildRule; @@ -107,6 +184,7 @@ isEditable = 1; outputFiles = ( ); + script = ""; }; F921D318070376B0000D1056 /* PBXBuildRule */ = { isa = PBXBuildRule; @@ -115,6 +193,7 @@ isEditable = 1; outputFiles = ( ); + script = ""; }; F921D31E070376F1000D1056 /* PBXBuildRule */ = { isa = PBXBuildRule; @@ -135,54 +214,82 @@ /* End PBXBuildRule section */ /* Begin PBXContainerItemProxy section */ - F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */ = { + 37A0AD0E1C16000F00731E50 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F9ED4C9E0630A76B00DF4E74; - remoteInfo = libdyld.dylib; + remoteGlobalIDString = 37A0AD0A1C15FFF500731E50; + remoteInfo = update_dyld_shared_cache; }; - F93937370A94FB6A00070A07 /* PBXContainerItemProxy */ = { + 37A0AD101C16003600731E50 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; remoteGlobalIDString = F93937310A94FAF700070A07; - remoteInfo = update_dyld_shared_cache; + remoteInfo = update_dyld_shared_cache_tool; }; - F99B8E9F0FEC195800701838 /* PBXContainerItemProxy */ = { + 37A0AD121C16003600731E50 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F99B8E550FEC10F600701838; - remoteInfo = dyld_shared_cache_util; + remoteGlobalIDString = F9F2A5580F7AEE9800B7C9EB; + remoteInfo = libdsc; }; - F99B8EB10FEC220C00701838 /* PBXContainerItemProxy */ = { + 37A0AD141C16003600731E50 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F9D1001114D8D0BA00099D91; + remoteInfo = dsc_extractor; + }; + 37A0AD161C16003600731E50 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 377685F21AC4B27D00026E6C; + remoteInfo = multi_dyld_shared_cache_builder; + }; + 37A0AD181C16003600731E50 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3703A1111B38C1B300ADBA7F; + remoteInfo = dyld_shared_cache_builder; + }; + 37A0AD1A1C16004600731E50 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; remoteGlobalIDString = F99B8E550FEC10F600701838; remoteInfo = dyld_shared_cache_util; }; - F9B4D77F12AD9736000605A6 /* PBXContainerItemProxy */ = { + F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F9F2A5580F7AEE9800B7C9EB; - remoteInfo = libdsc; + remoteGlobalIDString = F9ED4C9E0630A76B00DF4E74; + remoteInfo = libdyld.dylib; }; - F9CE330A120F40EA0098B590 /* PBXContainerItemProxy */ = { + F97FF3651C237F97000ACDD2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F9F2A5580F7AEE9800B7C9EB; - remoteInfo = libdsc; + remoteGlobalIDString = F97FF3551C23638F000ACDD2; + remoteInfo = nocr; }; - F9D1004614D8D91100099D91 /* PBXContainerItemProxy */ = { + F99B8EB10FEC220C00701838 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F9D1001114D8D0BA00099D91; - remoteInfo = dsc_extractor; + remoteGlobalIDString = F99B8E550FEC10F600701838; + remoteInfo = dyld_shared_cache_util; + }; + F9B4D77F12AD9736000605A6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F9F2A5580F7AEE9800B7C9EB; + remoteInfo = libdsc; }; F9ED4CA60630A78A00DF4E74 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -201,6 +308,24 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 3703A1201B38C1B300ADBA7F /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; + 377685FE1AC4B27D00026E6C /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; F908135111D3ED9000626CC1 /* usr|include|mach-o */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; @@ -232,6 +357,7 @@ files = ( F908135D11D3FACD00626CC1 /* dyld-interposing.h in usr|local|include|mach-o */, F908135E11D3FACD00626CC1 /* dyld_cache_format.h in usr|local|include|mach-o */, + F9FF8C161C69B080009F8A53 /* dyld_process_info.h in usr|local|include|mach-o */, F908135F11D3FACD00626CC1 /* dyld_gdb.h in usr|local|include|mach-o */, F908136011D3FACD00626CC1 /* dyld_priv.h in usr|local|include|mach-o */, ); @@ -266,6 +392,17 @@ name = "usr|share|man|man3"; runOnlyForDeploymentPostprocessing = 1; }; + F97FF3541C23638F000ACDD2 /* install man page */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(INSTALL_PATH_PREFIX)/usr/share/man/man1"; + dstSubfolderSpec = 0; + files = ( + F97FF3641C237F68000ACDD2 /* nocr.1 in install man page */, + ); + name = "install man page"; + runOnlyForDeploymentPostprocessing = 1; + }; F98C78D10F7C00EA006257D2 /* usr|local|include|mach-o */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; @@ -303,8 +440,23 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 834A90AB0E1D85D600555761 /* ObjCLegacyAbstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ObjCLegacyAbstraction.hpp; sourceTree = ""; }; - 834A90AC0E1D85D600555761 /* ObjCModernAbstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ObjCModernAbstraction.hpp; sourceTree = ""; }; + 3703A1241B38C1B300ADBA7F /* dyld_shared_cache_builder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dyld_shared_cache_builder; sourceTree = BUILT_PRODUCTS_DIR; }; + 3703A1251B38C22900ADBA7F /* dyld_shared_cache_builder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = dyld_shared_cache_builder.mm; sourceTree = ""; usesTabs = 0; }; + 370C6E531BDEF08000387223 /* libspindump.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libspindump.dylib; path = usr/lib/libspindump.dylib; sourceTree = SDKROOT; }; + 370E5F401CC06CF8000158F2 /* Logging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Logging.cpp; sourceTree = ""; usesTabs = 0; }; + 370E5F411CC06CF8000158F2 /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = ""; usesTabs = 0; }; + 371D29811B2F53C8000BBE48 /* MultiCacheBuilder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MultiCacheBuilder.mm; sourceTree = ""; usesTabs = 0; }; + 371D29831B30E587000BBE48 /* MultiCacheBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiCacheBuilder.h; sourceTree = ""; usesTabs = 0; }; + 374DDAE11AC0A0F70097CFF0 /* Trie.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Trie.hpp; sourceTree = ""; }; + 376ABDB71C5930E7009F0011 /* MachOProxy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MachOProxy.cpp; sourceTree = ""; usesTabs = 0; }; + 376ABDB81C5930E7009F0011 /* MachOProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MachOProxy.h; sourceTree = ""; usesTabs = 0; }; + 376ED1D71C46F2710051DD54 /* Metabom.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metabom.framework; path = AppleInternal/Library/Frameworks/Metabom.framework; sourceTree = SDKROOT; }; + 377686021AC4B27D00026E6C /* multi_dyld_shared_cache_builder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = multi_dyld_shared_cache_builder; sourceTree = BUILT_PRODUCTS_DIR; }; + 377686031AC4B2ED00026E6C /* multi_dyld_shared_cache_builder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = multi_dyld_shared_cache_builder.mm; sourceTree = ""; usesTabs = 0; }; + 37BF1D731B6168150048BC27 /* Manifest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Manifest.mm; sourceTree = ""; usesTabs = 0; }; + 37BF1D741B6168150048BC27 /* Manifest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Manifest.h; sourceTree = ""; usesTabs = 0; }; + 37E93E291AAFB0B10080E640 /* update_dyld_shared_cache.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = update_dyld_shared_cache.mm; sourceTree = ""; usesTabs = 0; }; + 37F7A5961BB363820039043A /* Bom.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Bom.framework; path = System/Library/PrivateFrameworks/Bom.framework; sourceTree = SDKROOT; }; EF799FE9070D27BB00F78484 /* dyld.1 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.man; name = dyld.1; path = doc/man/man1/dyld.1; sourceTree = SOURCE_ROOT; }; EF799FEB070D27BB00F78484 /* dladdr.3 */ = {isa = PBXFileReference; explicitFileType = text.man; fileEncoding = 30; name = dladdr.3; path = doc/man/man3/dladdr.3; sourceTree = SOURCE_ROOT; }; EF799FEC070D27BB00F78484 /* dlclose.3 */ = {isa = PBXFileReference; explicitFileType = text.man; fileEncoding = 30; name = dlclose.3; path = doc/man/man3/dlclose.3; sourceTree = SOURCE_ROOT; }; @@ -315,8 +467,8 @@ F90C540A1A82D78000558E8C /* update_dyld_shared_cache_entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = update_dyld_shared_cache_entitlements.plist; sourceTree = ""; }; F913FAD90630A8AE00B7AE9D /* dyldAPIsInLibSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = dyldAPIsInLibSystem.cpp; path = src/dyldAPIsInLibSystem.cpp; sourceTree = ""; }; F918691408B16D2500E0F9DB /* dyld-interposing.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = "dyld-interposing.h"; path = "include/mach-o/dyld-interposing.h"; sourceTree = ""; }; - F93666DF163B4C42002ECADA /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = ""; }; - F93666E1163B4C58002ECADA /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = ""; }; + F9280B791AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoaderMegaDylib.cpp; path = src/ImageLoaderMegaDylib.cpp; sourceTree = ""; }; + F9280B7A1AB9DCA000B18AEC /* ImageLoaderMegaDylib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMegaDylib.h; path = src/ImageLoaderMegaDylib.h; sourceTree = ""; }; F93937320A94FAF700070A07 /* update_dyld_shared_cache */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = update_dyld_shared_cache; sourceTree = BUILT_PRODUCTS_DIR; }; F939373E0A94FC4700070A07 /* Architectures.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Architectures.hpp; sourceTree = ""; }; F939373F0A94FC4700070A07 /* CacheFileAbstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = CacheFileAbstraction.hpp; sourceTree = ""; }; @@ -324,21 +476,35 @@ F93937410A94FC4700070A07 /* FileAbstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = FileAbstraction.hpp; sourceTree = ""; }; F93937430A94FC4700070A07 /* MachOFileAbstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = MachOFileAbstraction.hpp; sourceTree = ""; }; F93937440A94FC4700070A07 /* MachOLayout.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = MachOLayout.hpp; sourceTree = ""; }; - F93937460A94FC4700070A07 /* update_dyld_shared_cache.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = update_dyld_shared_cache.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 1; }; F94DB9000F0A9B1700323715 /* ImageLoaderMachOClassic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoaderMachOClassic.cpp; path = src/ImageLoaderMachOClassic.cpp; sourceTree = ""; }; F94DB9010F0A9B1700323715 /* ImageLoaderMachOClassic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMachOClassic.h; path = src/ImageLoaderMachOClassic.h; sourceTree = ""; }; F94DB9020F0A9B1700323715 /* ImageLoaderMachOCompressed.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoaderMachOCompressed.cpp; path = src/ImageLoaderMachOCompressed.cpp; sourceTree = ""; }; F94DB9030F0A9B1700323715 /* ImageLoaderMachOCompressed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMachOCompressed.h; path = src/ImageLoaderMachOCompressed.h; sourceTree = ""; }; + F95090D01C5AB89A0031F81D /* dyld_process_info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dyld_process_info.h; path = "include/mach-o/dyld_process_info.h"; sourceTree = ""; usesTabs = 0; }; + F95090E41C5AD1B30031F81D /* dyld_process_info.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dyld_process_info.cpp; path = src/dyld_process_info.cpp; sourceTree = ""; usesTabs = 0; }; + F958D4751C7FCD4A00A0B199 /* dyld_process_info_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dyld_process_info_internal.h; path = src/dyld_process_info_internal.h; sourceTree = ""; }; + F958D4761C7FCD4A00A0B199 /* dyld_process_info_notify.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dyld_process_info_notify.cpp; path = src/dyld_process_info_notify.cpp; sourceTree = ""; }; F95C95160E994796007B7CB8 /* MachOTrie.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MachOTrie.hpp; sourceTree = ""; }; F971DD131A4A0E0700BBDD52 /* base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = base.xcconfig; sourceTree = ""; }; F971DD141A4A0E0700BBDD52 /* dyld.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = dyld.xcconfig; sourceTree = ""; }; F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = libdyld.xcconfig; sourceTree = ""; }; F971DD161A4A0E0700BBDD52 /* update_dyld_shared_cache.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = update_dyld_shared_cache.xcconfig; sourceTree = ""; }; F976F548127B90F8004BA2A5 /* dyld.order */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dyld.order; path = src/dyld.order; sourceTree = ""; }; + F97FF3561C23638F000ACDD2 /* nocr */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = nocr; sourceTree = BUILT_PRODUCTS_DIR; }; + F97FF3581C23638F000ACDD2 /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = ""; }; + F97FF35E1C236402000ACDD2 /* execserver.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = execserver.defs; path = testing/nocr/execserver.defs; sourceTree = ""; }; + F97FF35F1C236402000ACDD2 /* nocr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = nocr.c; path = testing/nocr/nocr.c; sourceTree = ""; }; + F97FF3631C237F5C000ACDD2 /* nocr.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = nocr.1; path = ../../../testing/nocr/nocr.1; sourceTree = ""; }; F981BB8B170FC24400A686D6 /* dyldSyscallInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dyldSyscallInterface.h; path = src/dyldSyscallInterface.h; sourceTree = ""; }; F98935B90A9A412B00FB6228 /* MachOBinder.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = MachOBinder.hpp; sourceTree = ""; }; F98935BA0A9A412B00FB6228 /* MachORebaser.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = MachORebaser.hpp; sourceTree = ""; }; F98D274C0AA79D7400416316 /* dyld_images.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = dyld_images.h; path = "include/mach-o/dyld_images.h"; sourceTree = ""; }; + F98E18FF1A3FC1A9008DB73F /* OptimizerBranches.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OptimizerBranches.cpp; sourceTree = ""; usesTabs = 0; }; + F98E19001A3FC1A9008DB73F /* OptimizerBranches.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OptimizerBranches.h; sourceTree = ""; usesTabs = 0; }; + F98E19011A3FC1A9008DB73F /* OptimizerLinkedit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OptimizerLinkedit.cpp; sourceTree = ""; usesTabs = 0; }; + F98E19031A3FC1A9008DB73F /* OptimizerObjC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OptimizerObjC.cpp; sourceTree = ""; usesTabs = 0; }; + F98E190E1A40D167008DB73F /* ObjC1Abstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ObjC1Abstraction.hpp; sourceTree = ""; usesTabs = 0; }; + F98E190F1A40D167008DB73F /* ObjC2Abstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ObjC2Abstraction.hpp; sourceTree = ""; usesTabs = 0; }; F99B8E620FEC11B400701838 /* dyld_shared_cache_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dyld_shared_cache_util.cpp; sourceTree = ""; }; F99B8E670FEC121100701838 /* dyld_shared_cache_util */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dyld_shared_cache_util; sourceTree = BUILT_PRODUCTS_DIR; }; F99DE0361AAE4F0400669496 /* libdyld_data_symbols.dirty */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = libdyld_data_symbols.dirty; path = src/libdyld_data_symbols.dirty; sourceTree = ""; }; @@ -351,13 +517,19 @@ F9AC7E930B7BB67700FEB38B /* version.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = version.c; sourceTree = BUILT_PRODUCTS_DIR; }; F9AFEA3216F15CE300CB5161 /* start_glue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = start_glue.h; path = src/start_glue.h; sourceTree = ""; }; F9B01E3D0739ABDE00CF981B /* dyld.exp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.exports; name = dyld.exp; path = src/dyld.exp; sourceTree = SOURCE_ROOT; }; - F9C69EFD14EC8ABF009CAE2E /* objc-shared-cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-shared-cache.h"; path = "include/objc-shared-cache.h"; sourceTree = ""; }; + F9C69EFD14EC8ABF009CAE2E /* objc-shared-cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-shared-cache.h"; path = "include/objc-shared-cache.h"; sourceTree = ""; usesTabs = 0; }; F9CE30781208F1B50098B590 /* dsc_extractor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dsc_extractor.cpp; sourceTree = ""; }; F9CE30791208F1B50098B590 /* dsc_extractor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dsc_extractor.h; sourceTree = ""; }; + F9D0FE6D1A367093001E839B /* AdjustForNewSegmentLocation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AdjustForNewSegmentLocation.cpp; sourceTree = ""; usesTabs = 0; }; + F9D0FE6E1A367093001E839B /* BindAllImages.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BindAllImages.cpp; sourceTree = ""; usesTabs = 0; }; + F9D0FE6F1A367093001E839B /* FileCache.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FileCache.cpp; sourceTree = ""; usesTabs = 0; }; + F9D0FE701A367093001E839B /* SharedCache.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SharedCache.cpp; sourceTree = ""; usesTabs = 0; }; + F9D0FE711A367093001E839B /* mega-dylib-utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "mega-dylib-utils.h"; sourceTree = ""; usesTabs = 0; }; F9D1001214D8D0BA00099D91 /* dsc_extractor.bundle */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = dsc_extractor.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; F9D238D90A9E19A0002B55C7 /* update_dyld_shared_cache.1 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.man; path = update_dyld_shared_cache.1; sourceTree = ""; }; F9D49CCB1458B95200F86ADD /* start_glue.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = start_glue.s; path = src/start_glue.s; sourceTree = ""; }; F9E572000A66EF41007D9BE9 /* dlopen_preflight.3 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = dlopen_preflight.3; sourceTree = ""; }; + F9E7AD981BE47CE30025D311 /* CodeSigningTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodeSigningTypes.h; sourceTree = ""; }; F9ED4C980630A76000DF4E74 /* dyld */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dyld; sourceTree = BUILT_PRODUCTS_DIR; }; F9ED4C9F0630A76B00DF4E74 /* libdyld.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libdyld.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F9ED4CC60630A7F100DF4E74 /* dyld_gdb.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = dyld_gdb.cpp; path = src/dyld_gdb.cpp; sourceTree = SOURCE_ROOT; }; @@ -382,15 +554,40 @@ F9F2A5590F7AEE9800B7C9EB /* libdsc.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libdsc.a; sourceTree = BUILT_PRODUCTS_DIR; }; F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dsc_iterator.cpp; sourceTree = ""; }; F9F2A56F0F7AEEE300B7C9EB /* dsc_iterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dsc_iterator.h; sourceTree = ""; }; + F9F6F4261C1FAF8000BD8FED /* testing */ = {isa = PBXFileReference; lastKnownFileType = folder; path = testing; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 3703A11D1B38C1B300ADBA7F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 376ABDB61C592CC0009F0011 /* Metabom.framework in Frameworks */, + 378EE3B21BE88C4A001C99FB /* Bom.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 377685FD1AC4B27D00026E6C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 376ED1D81C46F2710051DD54 /* Metabom.framework in Frameworks */, + 378EE3B11BE88C47001C99FB /* Bom.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F93937300A94FAF700070A07 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F93666E2163B4C58002ECADA /* Security.framework in Frameworks */, - F93666E0163B4C42002ECADA /* CoreFoundation.framework in Frameworks */, + 370C6E541BDEF08000387223 /* libspindump.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F97FF3531C23638F000ACDD2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -432,6 +629,7 @@ isa = PBXGroup; children = ( EF799FE9070D27BB00F78484 /* dyld.1 */, + F97FF3631C237F5C000ACDD2 /* nocr.1 */, F9D238D90A9E19A0002B55C7 /* update_dyld_shared_cache.1 */, ); name = man1; @@ -457,8 +655,6 @@ isa = PBXGroup; children = ( F90C540A1A82D78000558E8C /* update_dyld_shared_cache_entitlements.plist */, - F93666E1163B4C58002ECADA /* Security.framework */, - F93666DF163B4C42002ECADA /* CoreFoundation.framework */, F939373E0A94FC4700070A07 /* Architectures.hpp */, F939373F0A94FC4700070A07 /* CacheFileAbstraction.hpp */, F93937400A94FC4700070A07 /* dyld_cache_format.h */, @@ -468,9 +664,6 @@ F98935BA0A9A412B00FB6228 /* MachORebaser.hpp */, F98935B90A9A412B00FB6228 /* MachOBinder.hpp */, F95C95160E994796007B7CB8 /* MachOTrie.hpp */, - 834A90AB0E1D85D600555761 /* ObjCLegacyAbstraction.hpp */, - 834A90AC0E1D85D600555761 /* ObjCModernAbstraction.hpp */, - F93937460A94FC4700070A07 /* update_dyld_shared_cache.cpp */, F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */, F9F2A56F0F7AEEE300B7C9EB /* dsc_iterator.h */, F9CE30781208F1B50098B590 /* dsc_extractor.cpp */, @@ -489,16 +682,61 @@ F971DD161A4A0E0700BBDD52 /* update_dyld_shared_cache.xcconfig */, ); path = configs; + sourceTree = SOURCE_ROOT; + }; + F97FF3571C23638F000ACDD2 /* nocr */ = { + isa = PBXGroup; + children = ( + F97FF3581C23638F000ACDD2 /* main.c */, + ); + path = nocr; sourceTree = ""; }; + F9D0FE6C1A367093001E839B /* interlinked-dylibs */ = { + isa = PBXGroup; + children = ( + 370C6E531BDEF08000387223 /* libspindump.dylib */, + 376ED1D71C46F2710051DD54 /* Metabom.framework */, + 37F7A5961BB363820039043A /* Bom.framework */, + 374DDAE11AC0A0F70097CFF0 /* Trie.hpp */, + F9E7AD981BE47CE30025D311 /* CodeSigningTypes.h */, + F98E18FF1A3FC1A9008DB73F /* OptimizerBranches.cpp */, + F98E19001A3FC1A9008DB73F /* OptimizerBranches.h */, + F98E19011A3FC1A9008DB73F /* OptimizerLinkedit.cpp */, + F98E19031A3FC1A9008DB73F /* OptimizerObjC.cpp */, + F98E190F1A40D167008DB73F /* ObjC2Abstraction.hpp */, + F9D0FE6D1A367093001E839B /* AdjustForNewSegmentLocation.cpp */, + F98E190E1A40D167008DB73F /* ObjC1Abstraction.hpp */, + F9D0FE6E1A367093001E839B /* BindAllImages.cpp */, + F9D0FE6F1A367093001E839B /* FileCache.cpp */, + F9D0FE701A367093001E839B /* SharedCache.cpp */, + 376ABDB81C5930E7009F0011 /* MachOProxy.h */, + 376ABDB71C5930E7009F0011 /* MachOProxy.cpp */, + 37BF1D741B6168150048BC27 /* Manifest.h */, + 37BF1D731B6168150048BC27 /* Manifest.mm */, + 371D29831B30E587000BBE48 /* MultiCacheBuilder.h */, + 371D29811B2F53C8000BBE48 /* MultiCacheBuilder.mm */, + 37E93E291AAFB0B10080E640 /* update_dyld_shared_cache.mm */, + 377686031AC4B2ED00026E6C /* multi_dyld_shared_cache_builder.mm */, + 3703A1251B38C22900ADBA7F /* dyld_shared_cache_builder.mm */, + F9D0FE711A367093001E839B /* mega-dylib-utils.h */, + 370E5F411CC06CF8000158F2 /* Logging.h */, + 370E5F401CC06CF8000158F2 /* Logging.cpp */, + ); + path = "interlinked-dylibs"; + sourceTree = SOURCE_ROOT; + }; F9ED4C870630A72200DF4E74 = { isa = PBXGroup; children = ( + F9F6F4261C1FAF8000BD8FED /* testing */, F971DD121A4A0E0700BBDD52 /* configs */, F9ED4CBB0630A7AA00DF4E74 /* src */, F9ED4CC30630A7BE00DF4E74 /* doc */, F9ED4CBE0630A7B100DF4E74 /* include */, + F97FF3571C23638F000ACDD2 /* nocr */, F9ED4C990630A76000DF4E74 /* Products */, + F9D0FE6C1A367093001E839B /* interlinked-dylibs */, F939373D0A94FC4700070A07 /* launch-cache */, ); indentWidth = 4; @@ -515,6 +753,9 @@ F9F2A5590F7AEE9800B7C9EB /* libdsc.a */, F99B8E670FEC121100701838 /* dyld_shared_cache_util */, F9D1001214D8D0BA00099D91 /* dsc_extractor.bundle */, + 377686021AC4B27D00026E6C /* multi_dyld_shared_cache_builder */, + 3703A1241B38C1B300ADBA7F /* dyld_shared_cache_builder */, + F97FF3561C23638F000ACDD2 /* nocr */, ); name = Products; sourceTree = ""; @@ -522,6 +763,8 @@ F9ED4CBB0630A7AA00DF4E74 /* src */ = { isa = PBXGroup; children = ( + F97FF35E1C236402000ACDD2 /* execserver.defs */, + F97FF35F1C236402000ACDD2 /* nocr.c */, F9ED4CC60630A7F100DF4E74 /* dyld_gdb.cpp */, F9ED4CC70630A7F100DF4E74 /* dyld.cpp */, F9ED4CC80630A7F100DF4E74 /* dyld.h */, @@ -547,6 +790,8 @@ F94DB9010F0A9B1700323715 /* ImageLoaderMachOClassic.h */, F94DB9020F0A9B1700323715 /* ImageLoaderMachOCompressed.cpp */, F94DB9030F0A9B1700323715 /* ImageLoaderMachOCompressed.h */, + F9280B791AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp */, + F9280B7A1AB9DCA000B18AEC /* ImageLoaderMegaDylib.h */, F99DE0361AAE4F0400669496 /* libdyld_data_symbols.dirty */, F9ED4CD50630A7F100DF4E74 /* stub_binding_helper.s */, F9B01E3D0739ABDE00CF981B /* dyld.exp */, @@ -555,6 +800,9 @@ F9A221E60F3A6D7C00D15F73 /* dyldLibSystemGlue.c */, F9A6D6E2116F9DF20051CC16 /* threadLocalVariables.c */, F9A6D70B116FBBD10051CC16 /* threadLocalHelpers.s */, + F958D4751C7FCD4A00A0B199 /* dyld_process_info_internal.h */, + F958D4761C7FCD4A00A0B199 /* dyld_process_info_notify.cpp */, + F95090E41C5AD1B30031F81D /* dyld_process_info.cpp */, ); name = src; sourceTree = ""; @@ -562,6 +810,7 @@ F9ED4CBE0630A7B100DF4E74 /* include */ = { isa = PBXGroup; children = ( + F95090D01C5AB89A0031F81D /* dyld_process_info.h */, F918691408B16D2500E0F9DB /* dyld-interposing.h */, F98D274C0AA79D7400416316 /* dyld_images.h */, F9ED4CE80630A80600DF4E74 /* dyld_gdb.h */, @@ -584,9 +833,45 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - F93937310A94FAF700070A07 /* update_dyld_shared_cache */ = { + 3703A1111B38C1B300ADBA7F /* dyld_shared_cache_builder */ = { isa = PBXNativeTarget; - buildConfigurationList = F93937340A94FB2900070A07 /* Build configuration list for PBXNativeTarget "update_dyld_shared_cache" */; + buildConfigurationList = 3703A1211B38C1B300ADBA7F /* Build configuration list for PBXNativeTarget "dyld_shared_cache_builder" */; + buildPhases = ( + 3703A1121B38C1B300ADBA7F /* make dyld_cache_config.h */, + 3703A1131B38C1B300ADBA7F /* Sources */, + 3703A11D1B38C1B300ADBA7F /* Frameworks */, + 3703A1201B38C1B300ADBA7F /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = dyld_shared_cache_builder; + productName = update_os_interlinked_dylib; + productReference = 3703A1241B38C1B300ADBA7F /* dyld_shared_cache_builder */; + productType = "com.apple.product-type.tool"; + }; + 377685F21AC4B27D00026E6C /* multi_dyld_shared_cache_builder */ = { + isa = PBXNativeTarget; + buildConfigurationList = 377685FF1AC4B27D00026E6C /* Build configuration list for PBXNativeTarget "multi_dyld_shared_cache_builder" */; + buildPhases = ( + 377685F31AC4B27D00026E6C /* make dyld_cache_config.h */, + 377685F41AC4B27D00026E6C /* Sources */, + 377685FD1AC4B27D00026E6C /* Frameworks */, + 377685FE1AC4B27D00026E6C /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = multi_dyld_shared_cache_builder; + productName = update_os_interlinked_dylib; + productReference = 377686021AC4B27D00026E6C /* multi_dyld_shared_cache_builder */; + productType = "com.apple.product-type.tool"; + }; + F93937310A94FAF700070A07 /* update_dyld_shared_cache_tool */ = { + isa = PBXNativeTarget; + buildConfigurationList = F93937340A94FB2900070A07 /* Build configuration list for PBXNativeTarget "update_dyld_shared_cache_tool" */; buildPhases = ( F91083C91702592700831889 /* create dyld_cache_config.h */, F939372F0A94FAF700070A07 /* Sources */, @@ -597,15 +882,29 @@ buildRules = ( ); dependencies = ( - F99B8EA00FEC195800701838 /* PBXTargetDependency */, - F9CE330B120F40EA0098B590 /* PBXTargetDependency */, - F9D1004714D8D91100099D91 /* PBXTargetDependency */, ); - name = update_dyld_shared_cache; + name = update_dyld_shared_cache_tool; productName = update_dyld_shared_cache; productReference = F93937320A94FAF700070A07 /* update_dyld_shared_cache */; productType = "com.apple.product-type.tool"; }; + F97FF3551C23638F000ACDD2 /* nocr */ = { + isa = PBXNativeTarget; + buildConfigurationList = F97FF35C1C23638F000ACDD2 /* Build configuration list for PBXNativeTarget "nocr" */; + buildPhases = ( + F97FF3521C23638F000ACDD2 /* Sources */, + F97FF3531C23638F000ACDD2 /* Frameworks */, + F97FF3541C23638F000ACDD2 /* install man page */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = nocr; + productName = nocr; + productReference = F97FF3561C23638F000ACDD2 /* nocr */; + productType = "com.apple.product-type.tool"; + }; F99B8E550FEC10F600701838 /* dyld_shared_cache_util */ = { isa = PBXNativeTarget; buildConfigurationList = F99B8E5D0FEC10F800701838 /* Build configuration list for PBXNativeTarget "dyld_shared_cache_util" */; @@ -702,7 +1001,18 @@ F9ED4C8B0630A72300DF4E74 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0440; + LastUpgradeCheck = 0630; + TargetAttributes = { + 37A0AD0A1C15FFF500731E50 = { + CreatedOnToolsVersion = 7.0; + }; + F97FF3551C23638F000ACDD2 = { + CreatedOnToolsVersion = 7.1; + }; + F9F6F4271C1FB0A700BD8FED = { + CreatedOnToolsVersion = 7.1; + }; + }; }; buildConfigurationList = F9D8C7E9087B087300E93EFB /* Build configuration list for PBXProject "dyld" */; compatibilityVersion = "Xcode 3.2"; @@ -720,18 +1030,55 @@ projectRoot = ""; targets = ( F9ED4C920630A73900DF4E74 /* all */, + 37A0AD0A1C15FFF500731E50 /* update_dyld_shared_cache */, F9ED4C970630A76000DF4E74 /* dyld */, F908134211D3ED0B00626CC1 /* libdyld */, F9ED4C9E0630A76B00DF4E74 /* libdyld.dylib */, - F93937310A94FAF700070A07 /* update_dyld_shared_cache */, + F93937310A94FAF700070A07 /* update_dyld_shared_cache_tool */, F9F2A5580F7AEE9800B7C9EB /* libdsc */, F99B8E550FEC10F600701838 /* dyld_shared_cache_util */, F9D1001114D8D0BA00099D91 /* dsc_extractor */, + 377685F21AC4B27D00026E6C /* multi_dyld_shared_cache_builder */, + 3703A1111B38C1B300ADBA7F /* dyld_shared_cache_builder */, + F9F6F4271C1FB0A700BD8FED /* dyld_tests */, + F97FF3551C23638F000ACDD2 /* nocr */, ); }; /* End PBXProject section */ /* Begin PBXShellScriptBuildPhase section */ + 3703A1121B38C1B300ADBA7F /* make dyld_cache_config.h */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "make dyld_cache_config.h"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/dyld_cache_config.h", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/bash; + shellScript = "echo \"\" > ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\nif [ -z \"${ARM_SDK}\" ]; then\n # if iOS SDK not available, use MacOSX SDK\n ARM_SDK=`xcodebuild -sdk macosx.internal -version Path`\nfi\n\nSHARED_REGION_FILE=\"${ARM_SDK}/usr/include/mach/shared_region.h\"\n\n\nif [ -r \"${SHARED_REGION_FILE}\" ]; then\n echo -n \"#define ARM_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\nelse\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${SHARED_REGION_FILE}'\"\n exit 1\nfi\n\nif [ -r \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\nif [ -r \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\n\n"; + showEnvVarsInLog = 0; + }; + 377685F31AC4B27D00026E6C /* make dyld_cache_config.h */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "make dyld_cache_config.h"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/dyld_cache_config.h", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/bash; + shellScript = "echo \"\" > ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\nif [ -z \"${ARM_SDK}\" ]; then\n # if iOS SDK not available, use MacOSX SDK\n ARM_SDK=`xcodebuild -sdk macosx.internal -version Path`\nfi\n\nSHARED_REGION_FILE=\"${ARM_SDK}/usr/include/mach/shared_region.h\"\n\n\nif [ -r \"${SHARED_REGION_FILE}\" ]; then\n echo -n \"#define ARM_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\nelse\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${SHARED_REGION_FILE}'\"\n exit 1\nfi\n\nif [ -r \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\nif [ -r \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\n\n"; + showEnvVarsInLog = 0; + }; F907E2490FA6469000BFEDBD /* install iPhone file */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; @@ -760,7 +1107,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; - shellScript = "echo \"\" > ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\nif [ -n \"${ARM_SDK}\" ]; then\n if [ -r \"${ARM_SDK}/usr/include/mach/shared_region.h\" ]; then\n echo -n \"#define ARM_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM[ \\t]/ { print $3;}' \"${ARM_SDK}/usr/include/mach/shared_region.h\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM[ \\t]/ { print $3;}' \"${ARM_SDK}/usr/include/mach/shared_region.h\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64/ { print $3;}' \"${ARM_SDK}/usr/include/mach/shared_region.h\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64/ { print $3;}' \"${ARM_SDK}/usr/include/mach/shared_region.h\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n else\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${ARM_SDK}/usr/include/mach/shared_region.h'\"\n exit 1\n fi\nelse\n\tif [ -z ${RC_PURPLE} ]; then \n\t\techo \"#define ARM_SHARED_REGION_START 0x20000000 \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\t\techo \"#define ARM_SHARED_REGION_SIZE 0x20000000 \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\t\techo \"#define ARM64_SHARED_REGION_START 0x180000000 \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\t\techo \"#define ARM64_SHARED_REGION_SIZE 0x20000000 \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n else\n echo \"ERROR: env var ARM_SDK not defined\"\n exit 1\n fi\nfi\n\n"; + shellScript = "echo \"\" > ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\nif [ -z \"${ARM_SDK}\" ]; then\n # if iOS SDK not available, use MacOSX SDK\n ARM_SDK=`xcodebuild -sdk macosx.internal -version Path`\nfi\n\nSHARED_REGION_FILE=\"${ARM_SDK}/usr/include/mach/shared_region.h\"\n\n\nif [ -r \"${SHARED_REGION_FILE}\" ]; then\n echo -n \"#define ARM_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\nelse\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${SHARED_REGION_FILE}'\"\n exit 1\nfi\n\n"; showEnvVarsInLog = 0; }; F9213B3F18BFC9CB001CB6E8 /* simulator entitlement */ = { @@ -839,14 +1186,90 @@ shellScript = "# link with all .a files in /usr/local/lib/dyld\nls -1 ${SDKROOT}/usr/local/lib/dyld/*.a > ${DERIVED_SOURCES_DIR}/archives.txt \n\n# link with crash report archive if it exists\nif [ -f ${SDKROOT}/usr/local/lib/libCrashReporterClient.a ]\nthen\n echo \\\"${SDKROOT}/usr/local/lib/libCrashReporterClient.a\\\" >> ${DERIVED_SOURCES_DIR}/archives.txt \nfi\n\n"; showEnvVarsInLog = 0; }; + F9F6F42B1C1FB0AE00BD8FED /* build */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputPaths = ( + ); + name = build; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/testing/build_tests.py && cp ${SRCROOT}/testing/run_all_dyld_tests.py ${DSTROOT}/AppleInternal/CoreOS/tests/dyld/\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 3703A1131B38C1B300ADBA7F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3703A1141B38C1B300ADBA7F /* AdjustForNewSegmentLocation.cpp in Sources */, + 370E5F441CC06CF8000158F2 /* Logging.cpp in Sources */, + 376ABDBB1C5930E7009F0011 /* MachOProxy.cpp in Sources */, + 3703A1161B38C1B300ADBA7F /* BindAllImages.cpp in Sources */, + 3703A1171B38C1B300ADBA7F /* OptimizerObjC.cpp in Sources */, + 3703A1181B38C1B300ADBA7F /* OptimizerBranches.cpp in Sources */, + 3703A1191B38C1B300ADBA7F /* OptimizerLinkedit.cpp in Sources */, + 3733C9081BD98F6900420392 /* dsc_iterator.cpp in Sources */, + 3703A11A1B38C1B300ADBA7F /* MultiCacheBuilder.mm in Sources */, + 3703A1261B38C22900ADBA7F /* dyld_shared_cache_builder.mm in Sources */, + 3703A11B1B38C1B300ADBA7F /* FileCache.cpp in Sources */, + 3703A11C1B38C1B300ADBA7F /* SharedCache.cpp in Sources */, + 37BF1D771B6168150048BC27 /* Manifest.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 377685F41AC4B27D00026E6C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 377685F51AC4B27D00026E6C /* AdjustForNewSegmentLocation.cpp in Sources */, + 370E5F431CC06CF8000158F2 /* Logging.cpp in Sources */, + 376ABDBA1C5930E7009F0011 /* MachOProxy.cpp in Sources */, + 377686041AC4B2ED00026E6C /* multi_dyld_shared_cache_builder.mm in Sources */, + 377685F61AC4B27D00026E6C /* BindAllImages.cpp in Sources */, + 377685F71AC4B27D00026E6C /* OptimizerObjC.cpp in Sources */, + 377685F81AC4B27D00026E6C /* OptimizerBranches.cpp in Sources */, + 3733C9071BD98F6800420392 /* dsc_iterator.cpp in Sources */, + 377685F91AC4B27D00026E6C /* OptimizerLinkedit.cpp in Sources */, + 371D29821B2F53C8000BBE48 /* MultiCacheBuilder.mm in Sources */, + 377685FA1AC4B27D00026E6C /* FileCache.cpp in Sources */, + 377685FB1AC4B27D00026E6C /* SharedCache.cpp in Sources */, + 37BF1D761B6168150048BC27 /* Manifest.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F939372F0A94FAF700070A07 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F93937470A94FC4700070A07 /* update_dyld_shared_cache.cpp in Sources */, + 37F7A59C1BB364530039043A /* OptimizerBranches.cpp in Sources */, + 370E5F421CC06CF8000158F2 /* Logging.cpp in Sources */, + 375E6F441C59DEFF001BB760 /* MachOProxy.cpp in Sources */, + 37F7A59D1BB364530039043A /* OptimizerLinkedit.cpp in Sources */, + 37F7A59E1BB364530039043A /* OptimizerObjC.cpp in Sources */, + 37F7A59F1BB364530039043A /* AdjustForNewSegmentLocation.cpp in Sources */, + 37F7A5A01BB364530039043A /* BindAllImages.cpp in Sources */, + 3733C9091BD98F6A00420392 /* dsc_iterator.cpp in Sources */, + 37F7A59A1BB3642F0039043A /* FileCache.cpp in Sources */, + 37F7A59B1BB3642F0039043A /* SharedCache.cpp in Sources */, + 37F7A5981BB364130039043A /* Manifest.mm in Sources */, + 37F7A5991BB364130039043A /* MultiCacheBuilder.mm in Sources */, + 37F7A5951BB362CA0039043A /* update_dyld_shared_cache.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F97FF3521C23638F000ACDD2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F97FF3601C236408000ACDD2 /* execserver.defs in Sources */, + F97FF3611C23640C000ACDD2 /* nocr.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -879,6 +1302,7 @@ F9ED4CDA0630A7F100DF4E74 /* dyldExceptions.c in Sources */, F9ED4CD60630A7F100DF4E74 /* dyld_gdb.cpp in Sources */, F9ED4CE00630A7F100DF4E74 /* glue.c in Sources */, + F9280B7B1AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp in Sources */, F9ED4CE10630A7F100DF4E74 /* ImageLoader.cpp in Sources */, F9ED4CE30630A7F100DF4E74 /* ImageLoaderMachO.cpp in Sources */, F9ED4CE50630A7F100DF4E74 /* stub_binding_helper.s in Sources */, @@ -898,6 +1322,8 @@ F913FADA0630A8AE00B7AE9D /* dyldAPIsInLibSystem.cpp in Sources */, F9A6D6E4116F9DF20051CC16 /* threadLocalVariables.c in Sources */, F9A6D70C116FBBD10051CC16 /* threadLocalHelpers.s in Sources */, + F95090E51C5AD1E80031F81D /* dyld_process_info.cpp in Sources */, + F958D4771C7FCE6700A0B199 /* dyld_process_info_notify.cpp in Sources */, F9D49CCC1458B95200F86ADD /* start_glue.s in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -914,20 +1340,50 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - F908134811D3ED1A00626CC1 /* PBXTargetDependency */ = { + 37A0AD0F1C16000F00731E50 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F9ED4C9E0630A76B00DF4E74 /* libdyld.dylib */; - targetProxy = F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */; + target = 37A0AD0A1C15FFF500731E50 /* update_dyld_shared_cache */; + targetProxy = 37A0AD0E1C16000F00731E50 /* PBXContainerItemProxy */; + }; + 37A0AD111C16003600731E50 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F93937310A94FAF700070A07 /* update_dyld_shared_cache_tool */; + targetProxy = 37A0AD101C16003600731E50 /* PBXContainerItemProxy */; + }; + 37A0AD131C16003600731E50 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F9F2A5580F7AEE9800B7C9EB /* libdsc */; + targetProxy = 37A0AD121C16003600731E50 /* PBXContainerItemProxy */; }; - F93937380A94FB6A00070A07 /* PBXTargetDependency */ = { + 37A0AD151C16003600731E50 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F93937310A94FAF700070A07 /* update_dyld_shared_cache */; - targetProxy = F93937370A94FB6A00070A07 /* PBXContainerItemProxy */; + target = F9D1001114D8D0BA00099D91 /* dsc_extractor */; + targetProxy = 37A0AD141C16003600731E50 /* PBXContainerItemProxy */; }; - F99B8EA00FEC195800701838 /* PBXTargetDependency */ = { + 37A0AD171C16003600731E50 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 377685F21AC4B27D00026E6C /* multi_dyld_shared_cache_builder */; + targetProxy = 37A0AD161C16003600731E50 /* PBXContainerItemProxy */; + }; + 37A0AD191C16003600731E50 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3703A1111B38C1B300ADBA7F /* dyld_shared_cache_builder */; + targetProxy = 37A0AD181C16003600731E50 /* PBXContainerItemProxy */; + }; + 37A0AD1B1C16004600731E50 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F99B8E550FEC10F600701838 /* dyld_shared_cache_util */; - targetProxy = F99B8E9F0FEC195800701838 /* PBXContainerItemProxy */; + targetProxy = 37A0AD1A1C16004600731E50 /* PBXContainerItemProxy */; + }; + F908134811D3ED1A00626CC1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F9ED4C9E0630A76B00DF4E74 /* libdyld.dylib */; + targetProxy = F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */; + }; + F97FF3661C237F97000ACDD2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F97FF3551C23638F000ACDD2 /* nocr */; + targetProxy = F97FF3651C237F97000ACDD2 /* PBXContainerItemProxy */; }; F99B8EB20FEC220C00701838 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -939,16 +1395,6 @@ target = F9F2A5580F7AEE9800B7C9EB /* libdsc */; targetProxy = F9B4D77F12AD9736000605A6 /* PBXContainerItemProxy */; }; - F9CE330B120F40EA0098B590 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F9F2A5580F7AEE9800B7C9EB /* libdsc */; - targetProxy = F9CE330A120F40EA0098B590 /* PBXContainerItemProxy */; - }; - F9D1004714D8D91100099D91 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F9D1001114D8D0BA00099D91 /* dsc_extractor */; - targetProxy = F9D1004614D8D91100099D91 /* PBXContainerItemProxy */; - }; F9ED4CA70630A78A00DF4E74 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F9ED4C970630A76000DF4E74 /* dyld */; @@ -962,6 +1408,216 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 3703A1221B38C1B300ADBA7F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(APPLE_INTERNAL_LIBRARY_DIR)/Frameworks", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + USER_HEADER_SEARCH_PATHS = "../launch-cache/"; + VALID_ARCHS = "armv6 armv7 x86_64 x86_64h"; + }; + name = Debug; + }; + 3703A1231B38C1B300ADBA7F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(APPLE_INTERNAL_LIBRARY_DIR)/Frameworks", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + USER_HEADER_SEARCH_PATHS = "../launch-cache/"; + VALID_ARCHS = "armv6 armv7 x86_64 x86_64h"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 377686001AC4B27D00026E6C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)/AppleInternal/Library/Frameworks", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + PRODUCT_NAME = multi_dyld_shared_cache_builder; + SDKROOT = macosx.internal; + STRIP_INSTALLED_PRODUCT = NO; + USER_HEADER_SEARCH_PATHS = "../launch-cache/"; + VALID_ARCHS = "armv6 armv7 x86_64 x86_64h"; + }; + name = Debug; + }; + 377686011AC4B27D00026E6C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)/AppleInternal/Library/Frameworks", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + PRODUCT_NAME = multi_dyld_shared_cache_builder; + SDKROOT = macosx.internal; + STRIP_INSTALLED_PRODUCT = NO; + USER_HEADER_SEARCH_PATHS = "../launch-cache/"; + VALID_ARCHS = "armv6 armv7 x86_64 x86_64h"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 37A0AD0C1C15FFF500731E50 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 37A0AD0D1C15FFF500731E50 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; F908134311D3ED0C00626CC1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -987,10 +1643,15 @@ F93937350A94FB2900070A07 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + ); GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -1004,15 +1665,11 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; - OTHER_CPLUSPLUSFLAGS = ( - "-std=c++11", - "-stdlib=libc++", - "$(OTHER_CFLAGS)", - ); + OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = "-stdlib=libc++"; PRODUCT_NAME = update_dyld_shared_cache; SDKROOT = macosx.internal; - VALID_ARCHS = "x86_64 i386"; + VALID_ARCHS = x86_64; }; name = Debug; }; @@ -1020,41 +1677,136 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD161A4A0E0700BBDD52 /* update_dyld_shared_cache.xcconfig */; buildSettings = { - ARCHS = x86_64; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + ); GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = s; GCC_THREADSAFE_STATICS = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; - OTHER_CPLUSPLUSFLAGS = ( - "-std=c++11", - "-stdlib=libc++", - "$(OTHER_CFLAGS)", - ); + OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = "-stdlib=libc++"; PRODUCT_NAME = update_dyld_shared_cache; SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; - VALID_ARCHS = "x86_64 i386"; + VALID_ARCHS = x86_64; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; + F97FF35A1C23638F000ACDD2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(DERIVED_SOURCES_DIR)/**", + "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders", + ); + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + }; + name = Debug; + }; + F97FF35B1C23638F000ACDD2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(DERIVED_SOURCES_DIR)/**", + "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders", + ); + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + }; + name = Release; + }; F99B8E580FEC10F600701838 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/OSX10.10.xctoolchain/usr/include, + "$(SRCROOT)/interlinked-dylibs/", + ); INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; PRODUCT_NAME = dyld_shared_cache_util; }; @@ -1064,10 +1816,16 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_DYNAMIC_NO_PIC = NO; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/OSX10.10.xctoolchain/usr/include, + "$(SRCROOT)/interlinked-dylibs/", + ); INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; PRODUCT_NAME = dyld_shared_cache_util; SKIP_INSTALL = NO; @@ -1078,6 +1836,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DYLIB_COMPATIBILITY_VERSION = ""; DYLIB_CURRENT_VERSION = ""; @@ -1103,6 +1862,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_COMPATIBILITY_VERSION = ""; @@ -1131,6 +1891,8 @@ ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_EMPTY_BODY = YES; + CODE_SIGN_IDENTITY = "-"; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; @@ -1143,6 +1905,7 @@ GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SHADOW = YES; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; GCC_WARN_UNINITIALIZED_AUTOS = NO; HEADER_SEARCH_PATHS = ( @@ -1156,7 +1919,6 @@ "$(OTHER_CFLAGS)", ); OTHER_LDFLAGS = ( - "-Wl,-seg1addr,$(BASE_ADDRESS)", "@$(DERIVED_SOURCES_DIR)/archives.txt", "-nostdlib", "-Wl,-dylinker", @@ -1180,7 +1942,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD141A4A0E0700BBDD52 /* dyld.xcconfig */; buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_EMPTY_BODY = YES; + CODE_SIGN_IDENTITY = "-"; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1191,6 +1956,7 @@ GCC_PREPROCESSOR_DEFINITIONS = "DYLD_VERSION=$(RC_ProjectSourceVersion)"; GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + GCC_WARN_SHADOW = YES; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; HEADER_SEARCH_PATHS = ( ./include, @@ -1198,13 +1964,13 @@ ); LD_GENERATE_MAP_FILE = YES; ORDER_FILE = "$(SRCROOT)/src/dyld.order"; + OTHER_CFLAGS = ""; "OTHER_CFLAGS[arch=armv6]" = "-mthumb"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", ); OTHER_LDFLAGS = ( - "-Wl,-seg1addr,$(BASE_ADDRESS)", "@$(DERIVED_SOURCES_DIR)/archives.txt", "-nostdlib", "-Wl,-dylinker", @@ -1212,6 +1978,8 @@ "-stdlib=libc++", "$(ALIGNMENT)", "$(ENTRY)", + "-Wl,-no_data_const", + "-Wl,-section_order,__DATA,__all_image_info:__nl_symbol_ptr:__got:__const:__crash_info:__data:__common:__bss", ); SDKROOT = macosx.internal; STRIPFLAGS = "-S"; @@ -1230,16 +1998,23 @@ baseConfigurationReference = F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_WARN_EMPTY_BODY = YES; + COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; EXECUTABLE_PREFIX = lib; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; INSTALLHDRS_COPY_PHASE = YES; - OTHER_CFLAGS = "-mno-global-merge"; + OTHER_CFLAGS = ""; OTHER_LDFLAGS = ( "-nostdlib", "$(LIBSYSTEM_LIBS)", @@ -1261,6 +2036,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */; buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_WARN_EMPTY_BODY = YES; + COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; DEAD_CODE_STRIPPING = YES; @@ -1271,10 +2050,14 @@ GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; INSTALLHDRS_COPY_PHASE = YES; - OTHER_CFLAGS = "-mno-global-merge"; + OTHER_CFLAGS = ""; OTHER_CPLUSPLUSFLAGS = ( "-fno-exceptions", "$(OTHER_CFLAGS)", @@ -1333,6 +2116,9 @@ baseConfigurationReference = F971DD131A4A0E0700BBDD52 /* base.xcconfig */; buildSettings = { CLANG_CXX_LIBRARY = "compiler-default"; + ONLY_ACTIVE_ARCH = NO; + SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx watchos appletvos"; }; name = Debug; }; @@ -1341,6 +2127,8 @@ baseConfigurationReference = F971DD131A4A0E0700BBDD52 /* base.xcconfig */; buildSettings = { CLANG_CXX_LIBRARY = "compiler-default"; + SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx watchos appletvos"; }; name = Release; }; @@ -1348,6 +2136,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; @@ -1366,6 +2155,7 @@ F9F2A55B0F7AEE9900B7C9EB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; @@ -1391,9 +2181,50 @@ }; name = Release; }; + F9F6F4281C1FB0A700BD8FED /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + F9F6F4291C1FB0A700BD8FED /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 3703A1211B38C1B300ADBA7F /* Build configuration list for PBXNativeTarget "dyld_shared_cache_builder" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3703A1221B38C1B300ADBA7F /* Debug */, + 3703A1231B38C1B300ADBA7F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 377685FF1AC4B27D00026E6C /* Build configuration list for PBXNativeTarget "multi_dyld_shared_cache_builder" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 377686001AC4B27D00026E6C /* Debug */, + 377686011AC4B27D00026E6C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 37A0AD0B1C15FFF500731E50 /* Build configuration list for PBXAggregateTarget "update_dyld_shared_cache" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 37A0AD0C1C15FFF500731E50 /* Debug */, + 37A0AD0D1C15FFF500731E50 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F908135211D3ED9000626CC1 /* Build configuration list for PBXAggregateTarget "libdyld" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1403,7 +2234,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F93937340A94FB2900070A07 /* Build configuration list for PBXNativeTarget "update_dyld_shared_cache" */ = { + F93937340A94FB2900070A07 /* Build configuration list for PBXNativeTarget "update_dyld_shared_cache_tool" */ = { isa = XCConfigurationList; buildConfigurations = ( F93937350A94FB2900070A07 /* Debug */, @@ -1412,6 +2243,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F97FF35C1C23638F000ACDD2 /* Build configuration list for PBXNativeTarget "nocr" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F97FF35A1C23638F000ACDD2 /* Debug */, + F97FF35B1C23638F000ACDD2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F99B8E5D0FEC10F800701838 /* Build configuration list for PBXNativeTarget "dyld_shared_cache_util" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1475,6 +2315,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F9F6F42A1C1FB0A700BD8FED /* Build configuration list for PBXAggregateTarget "dyld_tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F9F6F4281C1FB0A700BD8FED /* Debug */, + F9F6F4291C1FB0A700BD8FED /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = F9ED4C8B0630A72300DF4E74 /* Project object */; diff --git a/include/mach-o/dyld_images.h b/include/mach-o/dyld_images.h index ba287f9..25e4afc 100644 --- a/include/mach-o/dyld_images.h +++ b/include/mach-o/dyld_images.h @@ -32,6 +32,7 @@ extern "C" { #endif + /* * Beginning in Mac OS X 10.4, this is how gdb discovers which mach-o images are loaded in a process. * @@ -88,6 +89,8 @@ enum { dyld_error_kind_none=0, dyld_error_kind_symbol_missing=4 }; +/* internal limit */ +#define DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT 8 struct dyld_all_image_infos { uint32_t version; /* 1 in Mac OS X 10.4 and 10.5 */ @@ -124,8 +127,16 @@ struct dyld_all_image_infos { uintptr_t sharedCacheSlide; /* the following field is only in version 13 (Mac OS X 10.9, iOS 7.0) and later */ uint8_t sharedCacheUUID[16]; - /* the following field is only in version 14 (Mac OS X 10.9, iOS 7.0) and later */ - uintptr_t reserved[16]; + /* the following field is only in version 15 (macOS 10.12, iOS 10.0) and later */ + uintptr_t sharedCacheBaseAddress; + uint64_t infoArrayChangeTimestamp; + const char* dyldPath; + mach_port_t notifyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; +#if __LP64__ + uintptr_t reserved[13-(DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT/2)]; +#else + uintptr_t reserved[12-DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; +#endif }; diff --git a/include/mach-o/dyld_priv.h b/include/mach-o/dyld_priv.h index 7708927..db108fd 100644 --- a/include/mach-o/dyld_priv.h +++ b/include/mach-o/dyld_priv.h @@ -46,9 +46,7 @@ extern int _dyld_func_lookup(const char* dyld_func_name, void **address); extern void _dyld_fork_child(); -// -// Possible state changes for which you can register to be notified -// +// DEPRECATED enum dyld_image_states { dyld_image_state_mapped = 10, // No batch notification for this @@ -60,25 +58,30 @@ enum dyld_image_states dyld_image_state_terminated = 60 // Only single notification for this }; -// -// Callback that provides a bottom-up array of images -// For dyld_image_state_[dependents_]mapped state only, returning non-NULL will cause dyld to abort loading all those images -// and append the returned string to its load failure error message. dyld does not free the string, so -// it should be a literal string or a static buffer -// +// DEPRECATED typedef const char* (*dyld_image_state_change_handler)(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]); + + +typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]); +typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh); +typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh); + + // -// Register a handler to be called when any image changes to the requested state. -// If 'batch' is true, the callback is called with an array of all images that are in the requested state sorted by dependency. -// If 'batch' is false, the callback is called with one image at a time as each image transitions to the the requested state. -// During the call to this function, the handler may be called back with existing images and the handler should -// not return a string, since there is no load to abort. In batch mode, existing images at or past the request -// state supplied in the callback. In non-batch mode, the callback is called for each image exactly in the -// requested state. +// Note: only for use by objc runtime +// Register handlers to be called when objc images are mapped, unmapped, and initialized. +// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section. +// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to +// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(), +// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call, +// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called +// initializers in that image. This is when objc calls any +load methods in that image. // -extern void -dyld_register_image_state_change_handler(enum dyld_image_states state, bool batch, dyld_image_state_change_handler handler); +void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, + _dyld_objc_notify_init init, + _dyld_objc_notify_unmapped unmapped); + // @@ -138,7 +141,7 @@ extern intptr_t _dyld_get_image_slide(const struct mach_header* mh); // Exists in Mac OS X 10.4 and later through _dyld_func_lookup() // Exists in Mac OS X 10.6 and later through libSystem.dylib // -const struct dyld_all_image_infos* _dyld_get_all_image_infos(); +const struct dyld_all_image_infos* _dyld_get_all_image_infos() __attribute__((deprecated)); @@ -189,6 +192,7 @@ extern const struct mach_header* dyld_image_header_containing_address(const void #define DYLD_MACOSX_VERSION_10_9 0x000A0900 #define DYLD_MACOSX_VERSION_10_10 0x000A0A00 #define DYLD_MACOSX_VERSION_10_11 0x000A0B00 +#define DYLD_MACOSX_VERSION_10_12 0x000A0C00 #define DYLD_IOS_VERSION_2_0 0x00020000 #define DYLD_IOS_VERSION_2_1 0x00020100 @@ -209,7 +213,21 @@ extern const struct mach_header* dyld_image_header_containing_address(const void #define DYLD_IOS_VERSION_8_0 0x00080000 #define DYLD_IOS_VERSION_8_1 0x00080100 #define DYLD_IOS_VERSION_8_2 0x00080200 +#define DYLD_IOS_VERSION_8_3 0x00080300 +#define DYLD_IOS_VERSION_8_4 0x00080400 #define DYLD_IOS_VERSION_9_0 0x00090000 +#define DYLD_IOS_VERSION_9_1 0x00090100 +#define DYLD_IOS_VERSION_9_2 0x00090200 +#define DYLD_IOS_VERSION_9_3 0x00090300 +#define DYLD_IOS_VERSION_10_0 0x000A0000 + + +#define DYLD_WATCHOS_VERSION_1_0 0x00010000 +#define DYLD_WATCHOS_VERSION_2_0 0x00020000 +#define DYLD_WATCHOS_VERSION_2_1 0x00020100 +#define DYLD_WATCHOS_VERSION_2_2 0x00020200 +#define DYLD_WATCHOS_VERSION_3_0 0x00030000 + // @@ -240,6 +258,14 @@ extern uint32_t dyld_get_program_sdk_version(); extern uint32_t dyld_get_program_sdk_watch_os_version(); // __WATCHOS_AVAILABLE(2.0); +// Watch OS only. +// This finds the Watch min OS version that the main executable was built to run on. +// Note: dyld_get_program_min_os_version() returns the iOS equivalent (e.g. 9.0) +// whereas this returns the raw watchOS version (e.g. 2.0). +// Exists in Watch OS 3.0 and later +extern uint32_t dyld_get_program_min_watch_os_version(); // __WATCHOS_AVAILABLE(3.0); + + // // This finds the min OS version a binary was built to run on. // Returns zero on error, or if no min OS recorded in binary. @@ -306,6 +332,91 @@ struct dyld_interpose_tuple { extern void dyld_dynamic_interpose(const struct mach_header* mh, const struct dyld_interpose_tuple array[], size_t count); +struct dyld_shared_cache_dylib_text_info { + uint64_t version; // current version 1 + // following fields all exist in version 1 + uint64_t loadAddressUnslid; + uint64_t textSegmentSize; + uuid_t dylibUuid; + const char* path; // pointer invalid at end of iterations +}; +typedef struct dyld_shared_cache_dylib_text_info dyld_shared_cache_dylib_text_info; + +// +// Given the UUID of a dyld shared cache file, this function will attempt to locate the cache +// file and if found iterate all images, returning info about each one. Returns 0 on success. +// +// Exists in Mac OS X 10.11 and later +// iOS 9.0 and later +extern int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callback)(const dyld_shared_cache_dylib_text_info* info)); + + +// +// Given the UUID of a dyld shared cache file, and a NULL terminated array of extra directory paths to search, +// this function will scan the standard and extra directories looking for a cache file that matches the UUID +// and if found iterate all images, returning info about each one. Returns 0 on success. +// +// Exists in Mac OS X 10.12 and later +// iOS 10.0 and later +extern int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extraSearchDirs[], void (^callback)(const dyld_shared_cache_dylib_text_info* info)); + + +// +// Returns if the specified address range is in a dyld owned memory +// that is mapped read-only and will never be unloaded. +// +// Exists in Mac OS X 10.12 and later +// iOS 10.0 and later +extern bool _dyld_is_memory_immutable(const void* addr, size_t length); + + +// +// Finds the UUID (from LC_UUID load command) of given image. +// Returns false if LC_UUID is missing or mach_header is malformed. +// +// Exists in Mac OS X 10.12 and later +// Exists in iOS 10.0 and later +extern bool _dyld_get_image_uuid(const struct mach_header* mh, uuid_t uuid); + + +// +// Gets the UUID of the dyld shared cache in the current process. +// Returns false if there is no dyld shared cache in use by the processes. +// +// Exists in Mac OS X 10.12 and later +// Exists in iOS 10.0 and later +extern bool _dyld_get_shared_cache_uuid(uuid_t uuid); + + + +// +// When dyld must terminate a process because of a required dependent dylib +// could not be loaded or a symbol is missing, dyld calls abort_with_reason() +// using one of the following error codes. +// +#define DYLD_EXIT_REASON_DYLIB_MISSING 1 +#define DYLD_EXIT_REASON_DYLIB_WRONG_ARCH 2 +#define DYLD_EXIT_REASON_DYLIB_WRONG_VERSION 3 +#define DYLD_EXIT_REASON_SYMBOL_MISSING 4 +#define DYLD_EXIT_REASON_CODE_SIGNATURE 5 +#define DYLD_EXIT_REASON_FILE_SYSTEM_SANDBOX 6 +#define DYLD_EXIT_REASON_MALFORMED_MACHO 7 +#define DYLD_EXIT_REASON_OTHER 9 + +// +// When it has more information about the termination, dyld will use abort_with_payload(). +// The payload is a dyld_abort_payload structure. The fixed fields are offsets into the +// payload for the corresponding string. If the offset is zero, that string is not available. +// +struct dyld_abort_payload { + uint32_t version; // first version is 1 + uint32_t flags; // 0x00000001 means dyld terminated at launch, backtrace not useful + uint32_t targetDylibPathOffset; // offset in payload of path string to dylib that could not be loaded + uint32_t clientPathOffset; // offset in payload of path string to image requesting dylib + uint32_t symbolOffset; // offset in payload of symbol string that could not be found + // string data +}; +typedef struct dyld_abort_payload dyld_abort_payload; #if __cplusplus } diff --git a/include/mach-o/dyld_process_info.h b/include/mach-o/dyld_process_info.h new file mode 100644 index 0000000..67bf25d --- /dev/null +++ b/include/mach-o/dyld_process_info.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2016 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#ifndef _DYLD_PROCESS_INFO_ +#define _DYLD_PROCESS_INFO_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// +// Beginning in iOS 10.0 and Mac OS X 10.12, this is how lldb figures out mach-o binaries are in a process: +// +// When attaching to an existing process, lldb uses _dyld_process_info_create() to get the current list of images +// in a process, then falls into the start up case. +// +// When starting a process, lldb starts the process suspended, finds the "_dyld_debugger_notification" symbol in +// dyld, sets a break point on it, then resumes the process. Dyld will call _dyld_debugger_notification() with +// a list of images that were just added or removed from the process. Dyld calls this function before running +// any initializers in the image, so the debugger will have a chance to set break points in the image. +// + +enum dyld_notify_mode { dyld_notify_adding=0, dyld_notify_removing=1, dyld_notify_remove_all=2 }; + +void _dyld_debugger_notification(enum dyld_notify_mode, unsigned long count, uint64_t machHeaders[]); + + +struct dyld_process_cache_info { + uuid_t cacheUUID; // UUID of cache used by process + uint64_t cacheBaseAddress; // load address of dyld shared cache + bool noCache; // process is running without a dyld cache + bool privateCache; // process is using a private copy of its dyld cache +}; +typedef struct dyld_process_cache_info dyld_process_cache_info; + +enum { + dyld_process_state_not_started = 0x00, // process is suspended, dyld has not started running yet + dyld_process_state_dyld_initialized = 0x10, // dyld has initialzed itself + dyld_process_state_terminated_before_inits = 0x20, // process was terminated due missing library or symbol before it got to main() + dyld_process_state_libSystem_initialized = 0x30, // dyld has run libSystem's initializer + dyld_process_state_running_initializers = 0x40, // dyld is running other initializers + dyld_process_state_program_running = 0x50, // dyld has finished and jumped into main() + dyld_process_state_dyld_terminated = 0x60 // process was terminated by dyld post-main (e.g. bad lazying binding info) +}; + +struct dyld_process_state_info { + uint64_t timestamp; // mach_absolute_time of last time dyld change to image list + uint32_t imageCount; // number of images currently loaded into process + uint32_t initialImageCount; // number of images statically loaded into process (before any dlopen() calls) + uint8_t dyldState; // one of dyld_process_state_* values +}; +typedef struct dyld_process_state_info dyld_process_state_info; + + +typedef const struct dyld_process_info_base* dyld_process_info; + +// +// Generate a dyld_process_info object for specified task. +// +// The timestamp parameter is an optimization to not spend the time to gather all the image information +// if the process image list has not changed since the last call. If timestamp is zero, this function +// always gathers the full process info. If timestamp is non-zero, this function will check if the target +// task's image list has changed since that time. If is has not changed, the function returns NULL and +// kern_return_t is KERN_SUCCESS. If it has changed, the function gathers the full image info. +// The kernelError parameter can be NULL for clients that don't care why it failed. +// +extern dyld_process_info _dyld_process_info_create(task_t task, uint64_t timestamp, kern_return_t* kernelError); + +// retain/release dyld_process_info for specified task +extern void _dyld_process_info_release(dyld_process_info info); +extern void _dyld_process_info_retain(dyld_process_info info); + +// fill in struct with basic info about dyld in the process +extern void _dyld_process_info_get_state(dyld_process_info info, dyld_process_state_info* stateInfo); + +// fill in struct with info about dyld cache in use by process +extern void _dyld_process_info_get_cache(dyld_process_info info, dyld_process_cache_info* cacheInfo); + +// iterate all images in process +extern void _dyld_process_info_for_each_image(dyld_process_info info, void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)); + +// iterate all segments in an image +extern void _dyld_process_info_for_each_segment(dyld_process_info info, uint64_t machHeaderAddress, void (^callback)(uint64_t segmentAddress, uint64_t segmentSize, const char* segmentName)); + + + + +typedef const struct dyld_process_info_notify_base* dyld_process_info_notify; + +// +// Request notifications if image list changes in target process. Each time a load or unload happens in the target taks, +// the notify block will be called in this process. If the process exits, the notifyExit block will be called. +// If the notifications cannot be set up, this function will return NULL, and the reason in the kernError parameter. +// The kernelError parameter can be NULL for clients that don't care why it failed. +// If you want to stop receiving notifications, call _dyld_process_info_notify_release(). +// +extern dyld_process_info_notify _dyld_process_info_notify(task_t task, dispatch_queue_t queue, + void (^notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path), + void (^notifyExit)(), + kern_return_t* kernelError); + +// stop notifications and invalid dyld_process_info_notify object +extern void _dyld_process_info_notify_release(dyld_process_info_notify object); +extern void _dyld_process_info_notify_retain(dyld_process_info_notify object); + + +#ifdef __cplusplus +} +#endif + +#endif /* _DYLD_PROCESS_INFO_ */ diff --git a/include/objc-shared-cache.h b/include/objc-shared-cache.h index d2a27df..ef5c109 100644 --- a/include/objc-shared-cache.h +++ b/include/objc-shared-cache.h @@ -158,7 +158,7 @@ static perfect_hash make_perfect(const string_map& strings); // Precomputed perfect hash table of strings. // Base class for precomputed selector table and class table. -// Edit objc-sel-table.s and OPT_INITIALIZER if you change this structure. +// Edit objc-sel-table.s if you change this structure. struct objc_stringhash_t { uint32_t capacity; uint32_t occupied; @@ -334,7 +334,7 @@ struct objc_stringhash_t { // Precomputed selector table. -// Edit objc-sel-table.s and OPT_INITIALIZER if you change this structure. +// Edit objc-sel-table.s if you change this structure. struct objc_selopt_t : objc_stringhash_t { const char *get(const char *key) const { @@ -346,7 +346,7 @@ struct objc_selopt_t : objc_stringhash_t { }; // Precomputed class list. -// Edit objc-sel-table.s and OPT_INITIALIZER if you change these structures. +// Edit objc-sel-table.s if you change these structures. struct objc_classheader_t { objc_stringhash_offset_t clsOffset; @@ -489,7 +489,7 @@ struct objc_clsopt_t : objc_stringhash_t { continue; } - uint32_t count = classes.count(c->first); + uint32_t count = (uint32_t)classes.count(c->first); if (count == 1) { // only one class with this name @@ -632,24 +632,36 @@ struct objc_protocolopt_t : objc_stringhash_t { // Precomputed image list. -struct objc_headeropt_t; +struct objc_headeropt_ro_t; + +// Precomputed image list. +struct objc_headeropt_rw_t; // Precomputed class list. struct objc_clsopt_t; // Edit objc-sel-table.s if you change this value. -enum { VERSION = 13 }; +// lldb and Symbolication read these structures. Inform them of any changes. +enum { VERSION = 15 }; + +// Values for objc_opt_t::flags +enum : uint32_t { + IsProduction = (1 << 0), // never set in development cache + NoMissingWeakSuperclasses = (1 << 1), // never set in development cache +}; // Top-level optimization structure. -// Edit objc-sel-table.s and OPT_INITIALIZER if you change this structure. +// Edit objc-sel-table.s if you change this structure. struct alignas(alignof(void*)) objc_opt_t { uint32_t version; + uint32_t flags; int32_t selopt_offset; - int32_t headeropt_offset; + int32_t headeropt_ro_offset; int32_t clsopt_offset; int32_t protocolopt_offset; + int32_t headeropt_rw_offset; - const objc_selopt_t* selopt() const { + const objc_selopt_t* selopt() const { if (selopt_offset == 0) return NULL; return (objc_selopt_t *)((uint8_t *)this + selopt_offset); } @@ -658,9 +670,9 @@ struct alignas(alignof(void*)) objc_opt_t { return (objc_selopt_t *)((uint8_t *)this + selopt_offset); } - struct objc_headeropt_t* headeropt() const { - if (headeropt_offset == 0) return NULL; - return (struct objc_headeropt_t *)((uint8_t *)this + headeropt_offset); + struct objc_headeropt_ro_t* headeropt_ro() const { + if (headeropt_ro_offset == 0) return NULL; + return (struct objc_headeropt_ro_t *)((uint8_t *)this + headeropt_ro_offset); } struct objc_clsopt_t* clsopt() const { @@ -672,25 +684,16 @@ struct alignas(alignof(void*)) objc_opt_t { if (protocolopt_offset == 0) return NULL; return (objc_protocolopt_t *)((uint8_t *)this + protocolopt_offset); } + + struct objc_headeropt_rw_t* headeropt_rw() const { + if (headeropt_rw_offset == 0) return NULL; + return (struct objc_headeropt_rw_t *)((uint8_t *)this + headeropt_rw_offset); + } }; // sizeof(objc_opt_t) must be pointer-aligned STATIC_ASSERT(sizeof(objc_opt_t) % sizeof(void*) == 0); -// Initializer for empty opt of type uint32_t[]. -#define X8(x) x, x, x, x, x, x, x, x -#define X64(x) X8(x), X8(x), X8(x), X8(x), X8(x), X8(x), X8(x), X8(x) -#define X256(x) X64(x), X64(x), X64(x), X64(x) -#define OPT_INITIALIZER { \ - /* objc_opt_t */ \ - objc_opt::VERSION, 16, 0, 0, \ - /* objc_selopt_t */ \ - 4, 4, 63, 3, 0, 0, 0,0, X256(0), 0, 0, 16, 16, 16, 16 \ - /* no objc_headeropt_t */ \ - /* no objc_clsopt_t */ \ - /* no objc_protocolopt_t */ \ -} - // List of offsets in libobjc that the shared cache optimization needs to use. template @@ -1039,8 +1042,8 @@ static void initnorm(key *keys, ub4 nkeys, ub4 alen, ub4 blen, ub4 smax, ub8 sal for (i = 0; i < nkeys; i++) { key *mykey = keys+i; ub8 hash = lookup8(mykey->name_k, mykey->len_k, salt); - mykey->a_k = (loga > 0) ? hash>>(UB8BITS-loga) : 0; - mykey->b_k = (blen > 1) ? hash&(blen-1) : 0; + mykey->a_k = (loga > 0) ? (ub4)(hash >> (UB8BITS-loga)) : 0; + mykey->b_k = (blen > 1) ? (hash & (blen-1)) : 0; } } @@ -1412,7 +1415,7 @@ static void getkeys(key **keys, ub4 *nkeys, const string_map& strings) mykey->len_k = (ub4)strlen(s->first); } *keys = buf; - *nkeys = strings.size(); + *nkeys = (ub4)strings.size(); } diff --git a/interlinked-dylibs/AdjustForNewSegmentLocation.cpp b/interlinked-dylibs/AdjustForNewSegmentLocation.cpp new file mode 100644 index 0000000..f03abc6 --- /dev/null +++ b/interlinked-dylibs/AdjustForNewSegmentLocation.cpp @@ -0,0 +1,1059 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include "mega-dylib-utils.h" +#include "Logging.h" +#include "MachOFileAbstraction.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "Trie.hpp" +#include "dyld_cache_config.h" + +#if !NEW_CACHE_FILE_FORMAT + #include "CacheFileAbstraction.hpp" +#endif + +#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE + #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 +#endif + + +namespace { + +template +class Adjustor { +public: + Adjustor(void* cacheBuffer, macho_header

* mh, const std::vector& segNewStartAddresses, + const std::vector& segCacheFileOffset, const std::vector& segCacheFileSizes); + void adjustImageForNewSegmentLocations(std::vector& pointersForASLR); + +private: + void adjustReferencesUsingInfoV2(std::vector& pointersForASLR); + void adjustReference(uint32_t kind, uint8_t* mappedAddr, uint64_t fromNewAddress, uint64_t toNewAddress, int64_t adjust, int64_t targetSlide, + uint64_t imageStartAddress, uint64_t imageEndAddress, std::vector& pointersForASLR, uint32_t*& lastMappedAddr32, + uint32_t& lastKind, uint64_t& lastToNewAddress); + void adjustDataPointers(std::vector& pointersForASLR); + void slidePointer(int segIndex, uint64_t segOffset, uint8_t type, std::vector& pointersForASLR); + void adjustSymbolTable(); + void adjustExportsTrie(std::vector& newTrieBytes); + void rebuildLinkEdit(); + void adjustCode(); + void adjustInstruction(uint8_t kind, uint64_t cacheOffset, uint64_t codeToDataDelta); + void rebuildLinkEditAndLoadCommands(); + uint64_t slideForOrigAddress(uint64_t addr); + + typedef typename P::uint_t pint_t; + typedef typename P::E E; + + void* _cacheBuffer; + macho_header

* _mh; + const uint8_t* _linkeditBias = nullptr; + int64_t _linkeditAdjust = 0; + unsigned _linkeditSegIndex = 0; + bool _maskPointers = false; + bool _splitSegInfoV2 = false; + const char* _installName = nullptr; + macho_symtab_command

* _symTabCmd = nullptr; + macho_dysymtab_command

* _dynSymTabCmd = nullptr; + macho_dyld_info_command

* _dyldInfo = nullptr; + macho_linkedit_data_command

* _splitSegInfoCmd = nullptr; + macho_linkedit_data_command

* _functionStartsCmd = nullptr; + macho_linkedit_data_command

* _dataInCodeCmd = nullptr; + std::vector _segOrigStartAddresses; + std::vector _segNewStartAddresses; + std::vector _segCacheOffsets; + std::vector _segCacheSizes; + std::vector _segSlides; + std::vector*> _segCmds; +}; + +template +Adjustor

::Adjustor(void* cacheBuffer, macho_header

* mh, const std::vector& segNewStartAddresses, + const std::vector& segCacheFileOffsets, const std::vector& segCacheFileSizes) + : _mh(mh), _cacheBuffer(cacheBuffer), _segNewStartAddresses(segNewStartAddresses), + _segCacheOffsets(segCacheFileOffsets), _segCacheSizes(segCacheFileSizes) +{ + macho_segment_command

* segCmd; + const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + const uint32_t cmd_count = mh->ncmds(); + const macho_load_command

* cmd = cmds; + unsigned segIndex = 0; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd()) { + case LC_ID_DYLIB: + _installName = ((macho_dylib_command

*)cmd)->name(); + break; + case LC_SYMTAB: + _symTabCmd = (macho_symtab_command

*)cmd; + break; + case LC_DYSYMTAB: + _dynSymTabCmd = (macho_dysymtab_command

*)cmd; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + _dyldInfo = (macho_dyld_info_command

*)cmd; + break; + case LC_SEGMENT_SPLIT_INFO: + _splitSegInfoCmd = (macho_linkedit_data_command

*)cmd; + break; + case LC_FUNCTION_STARTS: + _functionStartsCmd = (macho_linkedit_data_command

*)cmd; + break; + case LC_DATA_IN_CODE: + _dataInCodeCmd = (macho_linkedit_data_command

*)cmd; + break; + case macho_segment_command

::CMD: + segCmd = (macho_segment_command

*)cmd; + _segCmds.push_back(segCmd); + _segOrigStartAddresses.push_back(segCmd->vmaddr()); + _segSlides.push_back(_segNewStartAddresses[segIndex] - segCmd->vmaddr()); + if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { + _linkeditAdjust = segCacheFileOffsets[segIndex] - segCmd->fileoff(); + _linkeditBias = (uint8_t*)cacheBuffer + _linkeditAdjust; + _linkeditSegIndex = segIndex; + } + ++segIndex; + break; + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } + _maskPointers = (P::E::get32(mh->cputype()) == CPU_TYPE_ARM64); + if ( _splitSegInfoCmd != NULL ) { + const uint8_t* infoStart = &_linkeditBias[_splitSegInfoCmd->dataoff()]; + _splitSegInfoV2 = (*infoStart == DYLD_CACHE_ADJ_V2_FORMAT); + } +} + +template +void Adjustor

::adjustImageForNewSegmentLocations(std::vector& pointersForASLR) +{ + if ( _splitSegInfoV2 ) { + adjustReferencesUsingInfoV2(pointersForASLR); + } + else { + adjustDataPointers(pointersForASLR); + adjustCode(); + } + adjustSymbolTable(); + rebuildLinkEditAndLoadCommands(); +} + +template +uint64_t Adjustor

::slideForOrigAddress(uint64_t addr) +{ + for (unsigned i=0; i < _segOrigStartAddresses.size(); ++i) { + if ( (_segOrigStartAddresses[i] <= addr) && (addr < (_segOrigStartAddresses[i]+_segCmds[i]->vmsize())) ) + return _segSlides[i]; + } + // On arm64, high nibble of pointers can have extra bits + if ( _maskPointers && (addr & 0xF000000000000000) ) { + return slideForOrigAddress(addr & 0x0FFFFFFFFFFFFFFF); + } + terminate("slide not known for dylib address 0x%llX in %s", addr, _installName); +} + +template +void Adjustor

::rebuildLinkEditAndLoadCommands() +{ + // Exports trie is only data structure in LINKEDIT that might grow + std::vector newTrieBytes; + adjustExportsTrie(newTrieBytes); + + // Remove: code signature, rebase info, code-sign-dirs, split seg info + uint32_t bindOffset = 0; + uint32_t bindSize = _dyldInfo->bind_size(); + uint32_t lazyBindOffset = bindOffset + bindSize; + uint32_t lazyBindSize = _dyldInfo->lazy_bind_size(); + uint32_t weakBindOffset = lazyBindOffset + lazyBindSize; + uint32_t weakBindSize = _dyldInfo->weak_bind_size(); + uint32_t exportOffset = weakBindOffset + weakBindSize; + uint32_t exportSize = (uint32_t)newTrieBytes.size(); + uint32_t splitSegInfoOffset = exportOffset + exportSize; + uint32_t splitSegInfosSize = (_splitSegInfoCmd ? _splitSegInfoCmd->datasize() : 0); + uint32_t funcStartsOffset = splitSegInfoOffset + splitSegInfosSize; + uint32_t funcStartsSize = (_functionStartsCmd ? _functionStartsCmd->datasize() : 0); + uint32_t dataInCodeOffset = funcStartsOffset + funcStartsSize; + uint32_t dataInCodeSize = (_dataInCodeCmd ? _dataInCodeCmd->datasize() : 0); + uint32_t symbolTableOffset = dataInCodeOffset + dataInCodeSize; + uint32_t symbolTableSize = _symTabCmd->nsyms() * sizeof(macho_nlist

); + uint32_t indirectTableOffset = symbolTableOffset + symbolTableSize; + uint32_t indirectTableSize = _dynSymTabCmd->nindirectsyms() * sizeof(uint32_t); + uint32_t symbolStringsOffset = indirectTableOffset + indirectTableSize; + uint32_t symbolStringsSize = _symTabCmd->strsize(); + uint32_t newLinkEditSize = symbolStringsOffset + symbolStringsSize; + + size_t linkeditBufferSize = align(_segCmds[_linkeditSegIndex]->vmsize(), 12); + if ( linkeditBufferSize < newLinkEditSize ) { + terminate("LINKEDIT overflow in %s", _installName); + } + + uint32_t linkeditStartOffset = (uint32_t)_segCacheOffsets[_linkeditSegIndex]; + uint8_t* newLinkeditBufer = (uint8_t*)::calloc(linkeditBufferSize, 1); + if ( bindSize ) + memcpy(&newLinkeditBufer[bindOffset], &_linkeditBias[_dyldInfo->bind_off()], bindSize); + if ( lazyBindSize ) + memcpy(&newLinkeditBufer[lazyBindOffset], &_linkeditBias[_dyldInfo->lazy_bind_off()], lazyBindSize); + if ( weakBindSize ) + memcpy(&newLinkeditBufer[weakBindOffset], &_linkeditBias[_dyldInfo->weak_bind_off()], weakBindSize); + if ( exportSize ) + memcpy(&newLinkeditBufer[exportOffset], &newTrieBytes[0], exportSize); + if ( splitSegInfosSize ) + memcpy(&newLinkeditBufer[splitSegInfoOffset], &_linkeditBias[_splitSegInfoCmd->dataoff()], splitSegInfosSize); + if ( funcStartsSize ) + memcpy(&newLinkeditBufer[funcStartsOffset], &_linkeditBias[_functionStartsCmd->dataoff()], funcStartsSize); + if ( dataInCodeSize ) + memcpy(&newLinkeditBufer[dataInCodeOffset], &_linkeditBias[_dataInCodeCmd->dataoff()], dataInCodeSize); + if ( symbolTableSize ) + memcpy(&newLinkeditBufer[symbolTableOffset], &_linkeditBias[_symTabCmd->symoff()], symbolTableSize); + if ( indirectTableSize ) + memcpy(&newLinkeditBufer[indirectTableOffset], &_linkeditBias[_dynSymTabCmd->indirectsymoff()], indirectTableSize); + if ( symbolStringsSize ) + memcpy(&newLinkeditBufer[symbolStringsOffset], &_linkeditBias[_symTabCmd->stroff()], symbolStringsSize); + + memcpy((uint8_t*)_cacheBuffer+linkeditStartOffset, newLinkeditBufer, newLinkEditSize); + ::bzero((uint8_t*)_cacheBuffer+linkeditStartOffset+newLinkEditSize, linkeditBufferSize-newLinkEditSize); + ::free(newLinkeditBufer); + + // updates load commands and removed ones no longer needed + macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)_mh + sizeof(macho_header

)); + uint32_t cmd_count = _mh->ncmds(); + const macho_load_command

* cmd = cmds; + const unsigned origLoadCommandsSize = _mh->sizeofcmds(); + unsigned bytesRemaining = origLoadCommandsSize; + unsigned removedCount = 0; + unsigned segIndex = 0; + for (uint32_t i = 0; i < cmd_count; ++i) { + macho_symtab_command

* symTabCmd; + macho_dysymtab_command

* dynSymTabCmd; + macho_dyld_info_command

* dyldInfo; + macho_linkedit_data_command

* functionStartsCmd; + macho_linkedit_data_command

* dataInCodeCmd; + macho_linkedit_data_command

* splitSegInfoCmd; + macho_segment_command

* segCmd; + macho_routines_command

* routinesCmd; + macho_dylib_command

* dylibIDCmd; + uint32_t cmdSize = cmd->cmdsize(); + int32_t segFileOffsetDelta; + bool remove = false; + switch ( cmd->cmd() ) { + case LC_ID_DYLIB: + dylibIDCmd = (macho_dylib_command

*)cmd; + dylibIDCmd->set_timestamp(2); // match what static linker sets in LC_LOAD_DYLIB + break; + case LC_SYMTAB: + symTabCmd = (macho_symtab_command

*)cmd; + symTabCmd->set_symoff(linkeditStartOffset+symbolTableOffset); + symTabCmd->set_stroff(linkeditStartOffset+symbolStringsOffset); + break; + case LC_DYSYMTAB: + dynSymTabCmd = (macho_dysymtab_command

*)cmd; + dynSymTabCmd->set_indirectsymoff(linkeditStartOffset+indirectTableOffset); + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + dyldInfo = (macho_dyld_info_command

*)cmd; + dyldInfo->set_rebase_off(0); + dyldInfo->set_rebase_size(0); + dyldInfo->set_bind_off(bindSize ? linkeditStartOffset+bindOffset : 0); + dyldInfo->set_bind_size(bindSize); + dyldInfo->set_weak_bind_off(weakBindSize ? linkeditStartOffset+weakBindOffset : 0); + dyldInfo->set_weak_bind_size(weakBindSize); + dyldInfo->set_lazy_bind_off(lazyBindSize ? linkeditStartOffset+lazyBindOffset : 0); + dyldInfo->set_lazy_bind_size(lazyBindSize); + dyldInfo->set_export_off(exportSize ? linkeditStartOffset+exportOffset : 0); + dyldInfo->set_export_size(exportSize); + break; + case LC_FUNCTION_STARTS: + functionStartsCmd = (macho_linkedit_data_command

*)cmd; + functionStartsCmd->set_dataoff(linkeditStartOffset+funcStartsOffset); + break; + case LC_DATA_IN_CODE: + dataInCodeCmd = (macho_linkedit_data_command

*)cmd; + dataInCodeCmd->set_dataoff(linkeditStartOffset+dataInCodeOffset); + break; + case macho_routines_command

::CMD: + routinesCmd = (macho_routines_command

*)cmd; + routinesCmd->set_init_address(routinesCmd->init_address()+slideForOrigAddress(routinesCmd->init_address())); + break; + case macho_segment_command

::CMD: + segCmd = (macho_segment_command

*)cmd; + segFileOffsetDelta = (int32_t)(_segCacheOffsets[segIndex] - segCmd->fileoff()); + segCmd->set_vmaddr(_segNewStartAddresses[segIndex]); + segCmd->set_vmsize(_segCacheSizes[segIndex]); + segCmd->set_fileoff(_segCacheOffsets[segIndex]); + segCmd->set_filesize(_segCacheSizes[segIndex]); + if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) + segCmd->set_vmsize(linkeditBufferSize); + if ( segCmd->nsects() > 0 ) { + macho_section

* const sectionsStart = (macho_section

*)((uint8_t*)segCmd + sizeof(macho_segment_command

)); + macho_section

* const sectionsEnd = §ionsStart[segCmd->nsects()]; + for (macho_section

* sect=sectionsStart; sect < sectionsEnd; ++sect) { + sect->set_addr(sect->addr() + _segSlides[segIndex]); + if ( sect->offset() != 0 ) + sect->set_offset(sect->offset() + segFileOffsetDelta); + } + } + ++segIndex; + break; + case LC_RPATH: + warning("dyld shared cache does not support LC_RPATH found in %s", _installName); + remove = true; + break; + case LC_SEGMENT_SPLIT_INFO: + splitSegInfoCmd = (macho_linkedit_data_command

*)cmd; + splitSegInfoCmd->set_dataoff(linkeditStartOffset+splitSegInfoOffset); + break; + case LC_CODE_SIGNATURE: + case LC_DYLIB_CODE_SIGN_DRS: + remove = true; + break; + default: + break; + } + macho_load_command

* nextCmd = (macho_load_command

*)(((uint8_t*)cmd)+cmdSize); + if ( remove ) { + ::memmove((void*)cmd, (void*)nextCmd, bytesRemaining); + ++removedCount; + } + else { + bytesRemaining -= cmdSize; + cmd = nextCmd; + } + } + // zero out stuff removed + ::bzero((void*)cmd, bytesRemaining); + // update header + _mh->set_ncmds(cmd_count-removedCount); + _mh->set_sizeofcmds(origLoadCommandsSize-bytesRemaining); + _mh->set_flags(_mh->flags() | 0x80000000); +} + + +template +void Adjustor

::adjustSymbolTable() +{ + macho_nlist

* symbolTable = (macho_nlist

*)&_linkeditBias[_symTabCmd->symoff()]; + + // adjust global symbol table entries + macho_nlist

* lastExport = &symbolTable[_dynSymTabCmd->iextdefsym()+_dynSymTabCmd->nextdefsym()]; + for (macho_nlist

* entry = &symbolTable[_dynSymTabCmd->iextdefsym()]; entry < lastExport; ++entry) { + if ( (entry->n_type() & N_TYPE) == N_SECT ) + entry->set_n_value(entry->n_value() + slideForOrigAddress(entry->n_value())); + } + + // adjust local symbol table entries + macho_nlist

* lastLocal = &symbolTable[_dynSymTabCmd->ilocalsym()+_dynSymTabCmd->nlocalsym()]; + for (macho_nlist

* entry = &symbolTable[_dynSymTabCmd->ilocalsym()]; entry < lastLocal; ++entry) { + if ( (entry->n_sect() != NO_SECT) && ((entry->n_type() & N_STAB) == 0) ) + entry->set_n_value(entry->n_value() + slideForOrigAddress(entry->n_value())); + } +} + +template +void Adjustor

::slidePointer(int segIndex, uint64_t segOffset, uint8_t type, std::vector& pointersForASLR) +{ + pint_t* mappedAddrP = (pint_t*)((uint8_t*)_cacheBuffer + _segCacheOffsets[segIndex] + segOffset); + uint32_t* mappedAddr32 = (uint32_t*)mappedAddrP; + pint_t valueP; + uint32_t value32; + switch ( type ) { + case REBASE_TYPE_POINTER: + valueP = (pint_t)P::getP(*mappedAddrP); + P::setP(*mappedAddrP, valueP + slideForOrigAddress(valueP)); + pointersForASLR.push_back(mappedAddrP); + break; + + case REBASE_TYPE_TEXT_ABSOLUTE32: + value32 = P::E::get32(*mappedAddr32); + P::E::set32(*mappedAddr32, value32 + (uint32_t)slideForOrigAddress(value32)); + break; + + case REBASE_TYPE_TEXT_PCREL32: + // general text relocs not support + default: + terminate("unknown rebase type 0x%02X in %s", type, _installName); + } +} + + +static bool isThumbMovw(uint32_t instruction) +{ + return ( (instruction & 0x8000FBF0) == 0x0000F240 ); +} + +static bool isThumbMovt(uint32_t instruction) +{ + return ( (instruction & 0x8000FBF0) == 0x0000F2C0 ); +} + +static uint16_t getThumbWord(uint32_t instruction) +{ + uint32_t i = ((instruction & 0x00000400) >> 10); + uint32_t imm4 = (instruction & 0x0000000F); + uint32_t imm3 = ((instruction & 0x70000000) >> 28); + uint32_t imm8 = ((instruction & 0x00FF0000) >> 16); + return ((imm4 << 12) | (i << 11) | (imm3 << 8) | imm8); +} + +static uint32_t setThumbWord(uint32_t instruction, uint16_t word) { + uint32_t imm4 = (word & 0xF000) >> 12; + uint32_t i = (word & 0x0800) >> 11; + uint32_t imm3 = (word & 0x0700) >> 8; + uint32_t imm8 = word & 0x00FF; + return (instruction & 0x8F00FBF0) | imm4 | (i << 10) | (imm3 << 28) | (imm8 << 16); +} + +static bool isArmMovw(uint32_t instruction) +{ + return (instruction & 0x0FF00000) == 0x03000000; +} + +static bool isArmMovt(uint32_t instruction) +{ + return (instruction & 0x0FF00000) == 0x03400000; +} + +static uint16_t getArmWord(uint32_t instruction) +{ + uint32_t imm4 = ((instruction & 0x000F0000) >> 16); + uint32_t imm12 = (instruction & 0x00000FFF); + return (imm4 << 12) | imm12; +} + +static uint32_t setArmWord(uint32_t instruction, uint16_t word) { + uint32_t imm4 = (word & 0xF000) >> 12; + uint32_t imm12 = word & 0x0FFF; + return (instruction & 0xFFF0F000) | (imm4 << 16) | imm12; +} + +template +void Adjustor

::adjustReference(uint32_t kind, uint8_t* mappedAddr, uint64_t fromNewAddress, uint64_t toNewAddress, + int64_t adjust, int64_t targetSlide, uint64_t imageStartAddress, uint64_t imageEndAddress, + std::vector& pointersForASLR, uint32_t*& lastMappedAddr32, uint32_t& lastKind, uint64_t& lastToNewAddress) +{ + uint64_t value64; + uint64_t* mappedAddr64; + uint32_t value32; + uint32_t* mappedAddr32; + uint32_t instruction; + int64_t offsetAdjust; + int64_t delta; + switch ( kind ) { + case DYLD_CACHE_ADJ_V2_DELTA_32: + mappedAddr32 = (uint32_t*)mappedAddr; + value32 = P::E::get32(*mappedAddr32); + delta = (int32_t)value32; + delta += adjust; + if ( (delta > 0x80000000) || (-delta > 0x80000000) ) + terminate("DYLD_CACHE_ADJ_V2_DELTA_32 can't be adjust by 0x%016llX in %s", adjust, _installName); + P::E::set32(*mappedAddr32, (int32_t)delta); + break; + case DYLD_CACHE_ADJ_V2_POINTER_32: + mappedAddr32 = (uint32_t*)mappedAddr; + if ( toNewAddress != (E::get32(*mappedAddr32) + targetSlide) ) + terminate("bad DYLD_CACHE_ADJ_V2_POINTER_32 value not as expected at address 0x%llX in %s", fromNewAddress, _installName); + E::set32(*mappedAddr32, (uint32_t)toNewAddress); + pointersForASLR.push_back(mappedAddr); + break; + case DYLD_CACHE_ADJ_V2_POINTER_64: + mappedAddr64 = (uint64_t*)mappedAddr; + if ( toNewAddress != (E::get64(*mappedAddr64) + targetSlide) ) + terminate("bad DYLD_CACHE_ADJ_V2_POINTER_64 value not as expected at address 0x%llX in %s", fromNewAddress, _installName); + E::set64(*mappedAddr64, toNewAddress); + pointersForASLR.push_back(mappedAddr); + break; + case DYLD_CACHE_ADJ_V2_DELTA_64: + mappedAddr64 = (uint64_t*)mappedAddr; + value64 = P::E::get64(*mappedAddr64); + E::set64(*mappedAddr64, value64 + adjust); + break; + case DYLD_CACHE_ADJ_V2_IMAGE_OFF_32: + if ( adjust == 0 ) + break; + mappedAddr32 = (uint32_t*)mappedAddr; + value32 = P::E::get32(*mappedAddr32); + value64 = toNewAddress - imageStartAddress; + if ( value64 > imageEndAddress ) + terminate("DYLD_CACHE_ADJ_V2_IMAGE_OFF_32 can't be adjust to 0x%016llX in %s", toNewAddress, _installName); + P::E::set32(*mappedAddr32, (uint32_t)value64); + break; + break; + case DYLD_CACHE_ADJ_V2_ARM64_ADRP: + mappedAddr32 = (uint32_t*)mappedAddr; + instruction = P::E::get32(*mappedAddr32); + if ( (instruction & 0x9F000000) == 0x90000000 ) { + int64_t pageDistance = ((toNewAddress & ~0xFFF) - (fromNewAddress & ~0xFFF)); + int64_t newPage21 = pageDistance >> 12; + if ( (newPage21 > 2097151) || (newPage21 < -2097151) ) + terminate("DYLD_CACHE_ADJ_V2_ARM64_ADRP can't be adjusted that far in %s", _installName); + instruction = (instruction & 0x9F00001F) | ((newPage21 << 29) & 0x60000000) | ((newPage21 << 3) & 0x00FFFFE0); + P::E::set32(*mappedAddr32, instruction); + } + else { + // ADRP instructions are sometimes optimized to other instructions (e.g. ADR) after the split-seg-info is generated + } + break; + case DYLD_CACHE_ADJ_V2_ARM64_OFF12: + mappedAddr32 = (uint32_t*)mappedAddr; + instruction = P::E::get32(*mappedAddr32); + offsetAdjust = (adjust & 0xFFF); + if ( offsetAdjust == 0 ) + break; + if ( (instruction & 0x3B000000) == 0x39000000 ) { + // LDR/STR imm12 + if ( offsetAdjust != 0 ) { + uint32_t encodedAddend = ((instruction & 0x003FFC00) >> 10); + uint32_t newAddend = 0; + switch ( instruction & 0xC0000000 ) { + case 0x00000000: + if ( (instruction & 0x04800000) == 0x04800000 ) { + if ( offsetAdjust & 0xF ) + terminate("can't adjust off12 scale=16 instruction by %lld bytes at mapped address=%p in %s", offsetAdjust, mappedAddr, _installName); + if ( encodedAddend*16 >= 4096 ) + terminate("off12 scale=16 instruction points outside its page at mapped address=%p in %s", mappedAddr, _installName); + newAddend = (encodedAddend + offsetAdjust/16) % 256; + } + else { + // scale=1 + newAddend = (encodedAddend + (int32_t)offsetAdjust) % 4096; + } + break; + case 0x40000000: + if ( offsetAdjust & 1 ) + terminate("can't adjust off12 scale=2 instruction by %lld bytes at mapped address=%p in %s", offsetAdjust, mappedAddr, _installName); + if ( encodedAddend*2 >= 4096 ) + terminate("off12 scale=2 instruction points outside its page at mapped address=%p in %s", mappedAddr, _installName); + newAddend = (encodedAddend + offsetAdjust/2) % 2048; + break; + case 0x80000000: + if ( offsetAdjust & 3 ) + terminate("can't adjust off12 scale=4 instruction by %lld bytes at mapped address=%p in %s", offsetAdjust, mappedAddr, _installName); + if ( encodedAddend*4 >= 4096 ) + terminate("off12 scale=4 instruction points outside its page at mapped address=%p in %s", mappedAddr, _installName); + newAddend = (encodedAddend + offsetAdjust/4) % 1024; + break; + case 0xC0000000: + if ( offsetAdjust & 7 ) + terminate("can't adjust off12 scale=8 instruction by %lld bytes at mapped address=%p in %s", offsetAdjust, mappedAddr, _installName); + if ( encodedAddend*8 >= 4096 ) + terminate("off12 scale=8 instruction points outside its page at mapped address=%p in %s", mappedAddr, _installName); + newAddend = (encodedAddend + offsetAdjust/8) % 512; + break; + } + uint32_t newInstruction = (instruction & 0xFFC003FF) | (newAddend << 10); + P::E::set32(*mappedAddr32, newInstruction); + } + } + else if ( (instruction & 0xFFC00000) == 0x91000000 ) { + // ADD imm12 + if ( instruction & 0x00C00000 ) + terminate("ADD off12 uses shift at mapped address=%p in %s", mappedAddr, _installName); + uint32_t encodedAddend = ((instruction & 0x003FFC00) >> 10); + uint32_t newAddend = (encodedAddend + offsetAdjust) & 0xFFF; + uint32_t newInstruction = (instruction & 0xFFC003FF) | (newAddend << 10); + P::E::set32(*mappedAddr32, newInstruction); + } + else if ( instruction != 0xD503201F ) { + // ignore imm12 instructions optimized into a NOP, but warn about others + terminate("unknown off12 instruction 0x%08X at 0x%0llX in %s", instruction, fromNewAddress, _installName); + } + break; + case DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT: + mappedAddr32 = (uint32_t*)mappedAddr; + // to update a movw/movt pair we need to extract the 32-bit they will make, + // add the adjust and write back the new movw/movt pair. + if ( lastKind == kind ) { + if ( lastToNewAddress == toNewAddress ) { + uint32_t instruction1 = P::E::get32(*lastMappedAddr32); + uint32_t instruction2 = P::E::get32(*mappedAddr32); + if ( isThumbMovw(instruction1) && isThumbMovt(instruction2) ) { + uint16_t high = getThumbWord(instruction2); + uint16_t low = getThumbWord(instruction1); + uint32_t full = high << 16 | low; + full += adjust; + instruction1 = setThumbWord(instruction1, full & 0xFFFF); + instruction2 = setThumbWord(instruction2, full >> 16); + } + else if ( isThumbMovt(instruction1) && isThumbMovw(instruction2) ) { + uint16_t high = getThumbWord(instruction1); + uint16_t low = getThumbWord(instruction2); + uint32_t full = high << 16 | low; + full += adjust; + instruction2 = setThumbWord(instruction2, full & 0xFFFF); + instruction1 = setThumbWord(instruction1, full >> 16); + } + else { + terminate("two DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT in a row but not paried in %s", _installName); + } + P::E::set32(*lastMappedAddr32, instruction1); + P::E::set32(*mappedAddr32, instruction2); + kind = 0; + } + else { + terminate("two DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT in a row but target different addresses in %s", _installName); + } + } + break; + case DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT: + mappedAddr32 = (uint32_t*)mappedAddr; + // to update a movw/movt pair we need to extract the 32-bit they will make, + // add the adjust and write back the new movw/movt pair. + if ( lastKind == kind ) { + if ( lastToNewAddress == toNewAddress ) { + uint32_t instruction1 = P::E::get32(*lastMappedAddr32); + uint32_t instruction2 = P::E::get32(*mappedAddr32); + if ( isArmMovw(instruction1) && isArmMovt(instruction2) ) { + uint16_t high = getArmWord(instruction2); + uint16_t low = getArmWord(instruction1); + uint32_t full = high << 16 | low; + full += adjust; + instruction1 = setArmWord(instruction1, full & 0xFFFF); + instruction2 = setArmWord(instruction2, full >> 16); + } + else if ( isArmMovt(instruction1) && isArmMovw(instruction2) ) { + uint16_t high = getArmWord(instruction1); + uint16_t low = getArmWord(instruction2); + uint32_t full = high << 16 | low; + full += adjust; + instruction2 = setArmWord(instruction2, full & 0xFFFF); + instruction1 = setArmWord(instruction1, full >> 16); + } + else { + terminate("two DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT in a row but not paired in %s", _installName); + } + P::E::set32(*lastMappedAddr32, instruction1); + P::E::set32(*mappedAddr32, instruction2); + kind = 0; + } + else { + terminate("two DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT in a row but target different addresses in %s", _installName); + } + } + break; + case DYLD_CACHE_ADJ_V2_ARM64_BR26: + case DYLD_CACHE_ADJ_V2_THUMB_BR22: + case DYLD_CACHE_ADJ_V2_ARM_BR24: + // nothing to do with calls to stubs + break; + default: + terminate("unknown split seg kind=%d in %s", kind, _installName); + } + lastKind = kind; + lastToNewAddress = toNewAddress; + lastMappedAddr32 = mappedAddr32; +} + +template +void Adjustor

::adjustReferencesUsingInfoV2(std::vector& pointersForASLR) +{ + static const bool log = false; + + const uint8_t* infoStart = &_linkeditBias[_splitSegInfoCmd->dataoff()]; + const uint8_t* infoEnd = &infoStart[_splitSegInfoCmd->datasize()]; + if ( *infoStart++ != DYLD_CACHE_ADJ_V2_FORMAT ) + terminate("malformed split seg info in %s", _installName); + + // build section arrays of slide and mapped address for each section + std::vector sectionSlides; + std::vector sectionNewAddress; + std::vector sectionMappedAddress; + sectionSlides.reserve(16); + sectionNewAddress.reserve(16); + sectionMappedAddress.reserve(16); + // section index 0 refers to mach_header + sectionMappedAddress.push_back((uint8_t*)_cacheBuffer + _segCacheOffsets[0]); + sectionSlides.push_back(_segSlides[0]); + sectionNewAddress.push_back(_segNewStartAddresses[0]); + // section 1 and later refer to real sections + unsigned sectionIndex = 0; + for (unsigned segmentIndex=0; segmentIndex < _segCmds.size(); ++segmentIndex) { + macho_segment_command

* segCmd = _segCmds[segmentIndex]; + macho_section

* const sectionsStart = (macho_section

*)((char*)segCmd + sizeof(macho_segment_command

)); + macho_section

* const sectionsEnd = §ionsStart[segCmd->nsects()]; + for(macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { + sectionMappedAddress.push_back((uint8_t*)_cacheBuffer + _segCacheOffsets[segmentIndex] + sect->addr() - segCmd->vmaddr()); + sectionSlides.push_back(_segSlides[segmentIndex]); + sectionNewAddress.push_back(_segNewStartAddresses[segmentIndex] + sect->addr() - segCmd->vmaddr()); + if (log) { + fprintf(stderr, " %s/%s, sectIndex=%d, mapped at=%p\n", + sect->segname(), sect->sectname(), sectionIndex, sectionMappedAddress.back()); + } + ++sectionIndex; + } + } + + // Whole :== FromToSection+ + // FromToSection :== ToOffset+ + // ToOffset :== FromOffset+ + // FromOffset :== + const uint8_t* p = infoStart; + uint64_t sectionCount = read_uleb128(p, infoEnd); + for (uint64_t i=0; i < sectionCount; ++i) { + uint32_t* lastMappedAddr32 = NULL; + uint32_t lastKind = 0; + uint64_t lastToNewAddress = 0; + uint64_t fromSectionIndex = read_uleb128(p, infoEnd); + uint64_t toSectionIndex = read_uleb128(p, infoEnd); + uint64_t toOffsetCount = read_uleb128(p, infoEnd); + uint64_t fromSectionSlide = sectionSlides[fromSectionIndex]; + uint64_t fromSectionNewAddress = sectionNewAddress[fromSectionIndex]; + uint8_t* fromSectionMappedAddress = sectionMappedAddress[fromSectionIndex]; + uint64_t toSectionSlide = sectionSlides[toSectionIndex]; + uint64_t toSectionNewAddress = sectionNewAddress[toSectionIndex]; + if (log) printf(" from sect=%lld (mapped=%p), to sect=%lld (new addr=0x%llX):\n", fromSectionIndex, fromSectionMappedAddress, toSectionIndex, toSectionNewAddress); + uint64_t toSectionOffset = 0; + for (uint64_t j=0; j < toOffsetCount; ++j) { + uint64_t toSectionDelta = read_uleb128(p, infoEnd); + uint64_t fromOffsetCount = read_uleb128(p, infoEnd); + toSectionOffset += toSectionDelta; + for (uint64_t k=0; k < fromOffsetCount; ++k) { + uint64_t kind = read_uleb128(p, infoEnd); + if ( kind > 12 ) + terminate("bad kind value (%llu) in %s", kind, _installName); + uint64_t fromSectDeltaCount = read_uleb128(p, infoEnd); + uint64_t fromSectionOffset = 0; + for (uint64_t l=0; l < fromSectDeltaCount; ++l) { + uint64_t delta = read_uleb128(p, infoEnd); + fromSectionOffset += delta; + int64_t deltaAdjust = toSectionSlide - fromSectionSlide; + //if (log) printf(" kind=%lld, from offset=0x%0llX, to offset=0x%0llX, adjust=0x%llX, targetSlide=0x%llX\n", kind, fromSectionOffset, toSectionOffset, deltaAdjust, toSectionSlide); + uint8_t* fromMappedAddr = fromSectionMappedAddress + fromSectionOffset; + uint64_t toNewAddress = toSectionNewAddress + toSectionOffset; + uint64_t fromNewAddress = fromSectionNewAddress + fromSectionOffset; + uint64_t imageStartAddress = sectionNewAddress.front(); + uint64_t imageEndAddress = sectionNewAddress.back(); + if ( toSectionIndex != 255 ) + adjustReference((uint32_t)kind, fromMappedAddr, fromNewAddress, toNewAddress, deltaAdjust, toSectionSlide, imageStartAddress, imageEndAddress, pointersForASLR, lastMappedAddr32, lastKind, lastToNewAddress); + } + } + } + } + +} + +template +void Adjustor

::adjustDataPointers(std::vector& pointersForASLR) +{ + const uint8_t* p = &_linkeditBias[_dyldInfo->rebase_off()]; + const uint8_t* end = &p[_dyldInfo->rebase_size()]; + + uint8_t type = 0; + int segIndex = 0; + uint64_t segOffset = 0; + uint64_t count; + uint64_t skip; + bool done = false; + while ( !done && (p < end) ) { + uint8_t immediate = *p & REBASE_IMMEDIATE_MASK; + uint8_t opcode = *p & REBASE_OPCODE_MASK; + ++p; + switch (opcode) { + case REBASE_OPCODE_DONE: + done = true; + break; + case REBASE_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segIndex = immediate; + segOffset = read_uleb128(p, end); + break; + case REBASE_OPCODE_ADD_ADDR_ULEB: + segOffset += read_uleb128(p, end); + break; + case REBASE_OPCODE_ADD_ADDR_IMM_SCALED: + segOffset += immediate*sizeof(pint_t); + break; + case REBASE_OPCODE_DO_REBASE_IMM_TIMES: + for (int i=0; i < immediate; ++i) { + slidePointer(segIndex, segOffset, type, pointersForASLR); + segOffset += sizeof(pint_t); + } + break; + case REBASE_OPCODE_DO_REBASE_ULEB_TIMES: + count = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + slidePointer(segIndex, segOffset, type, pointersForASLR); + segOffset += sizeof(pint_t); + } + break; + case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: + slidePointer(segIndex, segOffset, type, pointersForASLR); + segOffset += read_uleb128(p, end) + sizeof(pint_t); + break; + case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(p, end); + skip = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + slidePointer(segIndex, segOffset, type, pointersForASLR); + segOffset += skip + sizeof(pint_t); + } + break; + default: + terminate("unknown rebase opcode 0x%02X in %s", opcode, _installName); + } + } +} + + +template +void Adjustor

::adjustInstruction(uint8_t kind, uint64_t cacheOffset, uint64_t codeToDataDelta) +{ + uint8_t* fixupLoc = (uint8_t*)_cacheBuffer + cacheOffset; + uint32_t* fixupLoc32 = (uint32_t*)fixupLoc; + uint64_t* fixupLoc64 = (uint64_t*)fixupLoc; + uint32_t instruction; + uint32_t value32; + uint64_t value64; + + switch (kind) { + case 1: // 32-bit pointer (including x86_64 RIP-rel) + value32 = P::E::get32(*fixupLoc32); + value32 += codeToDataDelta; + P::E::set32(*fixupLoc32, value32); + break; + case 2: // 64-bit pointer + value64 = P::E::get64(*fixupLoc64); + value64 += codeToDataDelta; + P::E::set64(*fixupLoc64, value64); + break; + case 4: // only used for i386, a reference to something in the IMPORT segment + break; + case 5: // used by thumb2 movw + instruction = P::E::get32(*fixupLoc32); + // slide is always a multiple of 4096, so only top 4 bits of lo16 will ever need adjusting + value32 = (instruction & 0x0000000F) + ((uint32_t)codeToDataDelta >> 12); + instruction = (instruction & 0xFFFFFFF0) | (value32 & 0x0000000F); + P::E::set32(*fixupLoc32, instruction); + break; + case 6: // used by ARM movw + instruction = P::E::get32(*fixupLoc32); + // slide is always a multiple of 4096, so only top 4 bits of lo16 will ever need adjusting + value32 = ((instruction & 0x000F0000) >> 16) + ((uint32_t)codeToDataDelta >> 12); + instruction = (instruction & 0xFFF0FFFF) | ((value32 <<16) & 0x000F0000); + P::E::set32(*fixupLoc32, instruction); + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + // used by thumb2 movt (low nibble of kind is high 4-bits of paired movw) + { + instruction = P::E::get32(*fixupLoc32); + assert((instruction & 0x8000FBF0) == 0x0000F2C0); + // extract 16-bit value from instruction + uint32_t i = ((instruction & 0x00000400) >> 10); + uint32_t imm4 = (instruction & 0x0000000F); + uint32_t imm3 = ((instruction & 0x70000000) >> 28); + uint32_t imm8 = ((instruction & 0x00FF0000) >> 16); + uint32_t imm16 = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8; + // combine with codeToDataDelta and kind nibble + uint32_t targetValue = (imm16 << 16) | ((kind & 0xF) << 12); + uint32_t newTargetValue = targetValue + (uint32_t)codeToDataDelta; + // construct new bits slices + uint32_t imm4_ = (newTargetValue & 0xF0000000) >> 28; + uint32_t i_ = (newTargetValue & 0x08000000) >> 27; + uint32_t imm3_ = (newTargetValue & 0x07000000) >> 24; + uint32_t imm8_ = (newTargetValue & 0x00FF0000) >> 16; + // update instruction to match codeToDataDelta + uint32_t newInstruction = (instruction & 0x8F00FBF0) | imm4_ | (i_ << 10) | (imm3_ << 28) | (imm8_ << 16); + P::E::set32(*fixupLoc32, newInstruction); + } + break; + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + // used by arm movt (low nibble of kind is high 4-bits of paired movw) + { + instruction = P::E::get32(*fixupLoc32); + // extract 16-bit value from instruction + uint32_t imm4 = ((instruction & 0x000F0000) >> 16); + uint32_t imm12 = (instruction & 0x00000FFF); + uint32_t imm16 = (imm4 << 12) | imm12; + // combine with codeToDataDelta and kind nibble + uint32_t targetValue = (imm16 << 16) | ((kind & 0xF) << 12); + uint32_t newTargetValue = targetValue + (uint32_t)codeToDataDelta; + // construct new bits slices + uint32_t imm4_ = (newTargetValue & 0xF0000000) >> 28; + uint32_t imm12_ = (newTargetValue & 0x0FFF0000) >> 16; + // update instruction to match codeToDataDelta + uint32_t newInstruction = (instruction & 0xFFF0F000) | (imm4_ << 16) | imm12_; + P::E::set32(*fixupLoc32, newInstruction); + } + break; + case 3: // used for arm64 ADRP + instruction = P::E::get32(*fixupLoc32); + if ( (instruction & 0x9F000000) == 0x90000000 ) { + // codeToDataDelta is always a multiple of 4096, so only top 4 bits of lo16 will ever need adjusting + value64 = ((instruction & 0x60000000) >> 17) | ((instruction & 0x00FFFFE0) << 9); + value64 += codeToDataDelta; + instruction = (instruction & 0x9F00001F) | ((value64 << 17) & 0x60000000) | ((value64 >> 9) & 0x00FFFFE0); + P::E::set32(*fixupLoc32, instruction); + } + break; + default: + break; + } +} + +template +void Adjustor

::adjustCode() +{ + // find compressed info on how code needs to be updated + const uint8_t* infoStart = &_linkeditBias[_splitSegInfoCmd->dataoff()]; + const uint8_t* infoEnd = &infoStart[_splitSegInfoCmd->datasize()];; + + // This encoding only works if all data segments slide by the same amount + uint64_t codeToDataDelta = _segSlides[1] - _segSlides[0]; + + // compressed data is: [ [uleb128-delta]+ <0> ] + <0> + for (const uint8_t* p = infoStart; (*p != 0) && (p < infoEnd);) { + uint8_t kind = *p++; + uint64_t cacheOffset = _segCacheOffsets[0]; + while (uint64_t delta = read_uleb128(p, infoEnd)) { + cacheOffset += delta; + adjustInstruction(kind, cacheOffset, codeToDataDelta); + } + } +} + + +template +void Adjustor

::adjustExportsTrie(std::vector& newTrieBytes) +{ + // if no export info, nothing to adjust + if ( _dyldInfo->export_size() == 0 ) + return; + + // since export info addresses are offsets from mach_header, everything in __TEXT is fine + // only __DATA addresses need to be updated + const uint8_t* start = &_linkeditBias[_dyldInfo->export_off()]; + const uint8_t* end = &start[_dyldInfo->export_size()]; + std::vector originalExports; + if ( !ExportInfoTrie::parseTrie(start, end, originalExports) ) { + terminate("malformed exports trie in %s", _installName); + } + + std::vector newExports; + newExports.reserve(originalExports.size()); + uint64_t baseAddress = _segOrigStartAddresses[0]; + uint64_t baseAddressSlide = slideForOrigAddress(baseAddress); + for (auto& entry: originalExports) { + // remove symbols used by the static linker only + if ( (strncmp(entry.name.c_str(), "$ld$", 4) == 0) + || (strncmp(entry.name.c_str(), ".objc_class_name",16) == 0) + || (strncmp(entry.name.c_str(), ".objc_category_name",19) == 0) ) { + continue; + } + // adjust symbols in slid segments + if ( (entry.info.flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) != EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE ) + entry.info.address += (slideForOrigAddress(entry.info.address + baseAddress) - baseAddressSlide); + newExports.push_back(entry); + } + + // rebuild export trie + newTrieBytes.reserve(_dyldInfo->export_size()); + + ExportInfoTrie(newExports).emit(newTrieBytes); + // align + while ( (newTrieBytes.size() % sizeof(pint_t)) != 0 ) + newTrieBytes.push_back(0); +} + + +} // anonymous namespace + + +void SharedCache::adjustImageForNewSegmentLocations(const std::vector& segNewStartAddresses, + const std::vector& segCacheFileOffsets, + const std::vector& segCacheFileSizes, + std::vector& pointersForASLR) +{ + void* mh = (uint8_t*)_buffer.get() + segCacheFileOffsets[0]; + switch ( _arch.arch ) { + case CPU_TYPE_ARM: + case CPU_TYPE_I386: + { + if ( LittleEndian::get32(*(uint32_t*)mh) != MH_MAGIC ) + return; + Adjustor> adjustor32(_buffer.get(), (macho_header>*)mh, segNewStartAddresses, segCacheFileOffsets, segCacheFileSizes); + adjustor32.adjustImageForNewSegmentLocations(pointersForASLR); + } + break; + case CPU_TYPE_X86_64: + case CPU_TYPE_ARM64: + { + if ( LittleEndian::get32(*(uint32_t*)mh) != MH_MAGIC_64 ) + return; + Adjustor> adjustor64(_buffer.get(), (macho_header>*)mh, segNewStartAddresses, segCacheFileOffsets, segCacheFileSizes); + adjustor64.adjustImageForNewSegmentLocations(pointersForASLR); + } + break; + default: + terminate("unsupported arch 0x%08X", _arch.arch); + } +} + + + + + diff --git a/interlinked-dylibs/BindAllImages.cpp b/interlinked-dylibs/BindAllImages.cpp new file mode 100644 index 0000000..8bd9e6e --- /dev/null +++ b/interlinked-dylibs/BindAllImages.cpp @@ -0,0 +1,761 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include "mega-dylib-utils.h" +#include "MachOFileAbstraction.hpp" +#include "Trie.hpp" +#include "Logging.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dyld_cache_config.h" + +#if !NEW_CACHE_FILE_FORMAT + #include "CacheFileAbstraction.hpp" +#endif + +#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE + #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 +#endif + + +namespace { + +template +class BindInfo { +public: + BindInfo(void* cacheBuffer, macho_header

* mh); + + void setReExports(const std::unordered_map*>& dylibPathToBindInfo); + void setDependentDylibs(const std::unordered_map*>& dylibPathToBindInfo); + void bind(const std::unordered_map*>& dylibPathToBindInfo, std::vector& pointersForASLR); + + static void bindAllImagesInCache(void* cacheBuffer, const std::unordered_map& dylibPathToMachHeader, std::vector& pointersForASLR); + + void addExportsToGlobalMap(std::unordered_map*>& reverseMap); + +private: + typedef typename P::uint_t pint_t; + typedef typename P::E E; + + struct SymbolInfo { + SymbolInfo() { } + pint_t address = 0; + bool isResolver = false; + bool isAbsolute = false; + bool isSymbolReExport = false; + bool isThreadLocal = false; + int reExportDylibIndex = 0; + std::string reExportName; + }; + + void bindImmediates(const std::unordered_map*>& dylibPathToBindInfo, std::vector& pointersForASLR); + void bindLazyPointers(const std::unordered_map*>& dylibPathToBindInfo, std::vector& pointersForASLR); + + void bindLocation(uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, int libraryOrdinal, + int64_t addend, const char* symbolName, bool lazyPointer, bool weakImport, + const std::unordered_map*>& dylibPathToBindInfo, std::vector& pointersForASLR); + + bool findExportedSymbolAddress(const char* symbolName, const std::unordered_map*>& dylibPathToBindInfo, + pint_t* address, BindInfo

** foundIn, bool* isResolverSymbol, bool* isAbsolute); + pint_t findBlessedLazyPointerFor(const std::string& resolverSymbolName); + void switchStubToUseSharedLazyPointer(const std::string& resolverSymbolName, pint_t lpVMAddr); + void switchArmStubsLazyPointer(uint8_t* stubMappedAddress, pint_t stubVMAddress, uint32_t stubSize, pint_t lpVMAddr); + void switchArm64StubsLazyPointer(uint8_t* stubMappedAddress, pint_t stubVMAddress, uint32_t stubSize, pint_t lpVMAddr); + + typedef std::unordered_map*>> ResolverClientsMap; + typedef std::unordered_map ResolverToBlessedLazyPointerMap; + + void* _cacheBuffer; + macho_header

* _mh; + const uint8_t* _linkeditBias; + const char* _installName; + const macho_symtab_command

* _symTabCmd; + const macho_dysymtab_command

* _dynSymTabCmd; + const macho_dyld_info_command

* _dyldInfo; + std::vector _dependentPaths; + std::vector _segSizes; + std::vector _segCacheOffsets; + std::vector*>_segCmds; + std::unordered_map _exports; + std::vector _reExportedDylibNames; + std::vector*> _reExportedDylibs; + std::vector*> _dependentDylibs; + pint_t _baseAddress; + ResolverClientsMap _resolverClients; + ResolverToBlessedLazyPointerMap _resolverBlessedMap; +}; + + +template +BindInfo

::BindInfo(void* cacheBuffer, macho_header

* mh) + : _cacheBuffer(cacheBuffer), _mh(mh), _linkeditBias((uint8_t*)cacheBuffer), _symTabCmd(nullptr), _dynSymTabCmd(nullptr), _dyldInfo(nullptr), _baseAddress(0) +{ + macho_segment_command

* segCmd; + macho_dylib_command

* dylibCmd; + const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + const uint32_t cmd_count = mh->ncmds(); + unsigned segIndex = 0; + const macho_load_command

* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd()) { + case LC_ID_DYLIB: + dylibCmd = (macho_dylib_command

*)cmd; + _installName = dylibCmd->name(); + break; + case LC_SYMTAB: + _symTabCmd = (macho_symtab_command

*)cmd; + break; + case LC_DYSYMTAB: + _dynSymTabCmd = (macho_dysymtab_command

*)cmd; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + _dyldInfo = (macho_dyld_info_command

*)cmd; + break; + case LC_REEXPORT_DYLIB: + dylibCmd = (macho_dylib_command

*)cmd; + _dependentPaths.push_back(dylibCmd->name()); + _reExportedDylibNames.push_back(dylibCmd->name()); + break; + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + dylibCmd = (macho_dylib_command

*)cmd; + _dependentPaths.push_back(dylibCmd->name()); + break; + case macho_segment_command

::CMD: + segCmd = (macho_segment_command

*)cmd; + _segCmds.push_back(segCmd); + _segSizes.push_back(segCmd->vmsize()); + _segCacheOffsets.push_back(segCmd->fileoff()); + if ( segIndex == 0 ) + _baseAddress = (pint_t)segCmd->vmaddr(); + ++segIndex; + break; + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } + + // if no export info, no _exports map to build + if ( _dyldInfo->export_size() == 0 ) + return; + + std::vector exports; + const uint8_t* exportsStart = &_linkeditBias[_dyldInfo->export_off()]; + const uint8_t* exportsEnd = &exportsStart[_dyldInfo->export_size()]; + if ( !ExportInfoTrie::parseTrie(exportsStart, exportsEnd, exports) ) { + terminate("malformed exports trie in %s", _installName); + } + + for(const ExportInfoTrie::Entry& entry : exports) { + _exports[entry.name].address = (pint_t)entry.info.address + _baseAddress; + switch ( entry.info.flags & EXPORT_SYMBOL_FLAGS_KIND_MASK ) { + case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: + if ( (entry.info.flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) ) { + _exports[entry.name].isResolver = true; + } + if ( entry.info.flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { + SymbolInfo& info = _exports[entry.name]; + info.isSymbolReExport = true; + info.reExportDylibIndex = (int)entry.info.other; + if ( !entry.info.importName.empty()) + info.reExportName = entry.info.importName; + } + break; + case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: + _exports[entry.name].isThreadLocal = true; + break; + case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: + _exports[entry.name].isAbsolute = true; + _exports[entry.name].address = (pint_t)entry.info.address; + break; + default: + terminate("non-regular symbol binding not supported for %s in %s", entry.name.c_str(), _installName); + break; + } + } + +} + +template +void BindInfo

::bind(const std::unordered_map*>& dylibPathToBindInfo, std::vector& pointersForASLR) +{ + bindImmediates(dylibPathToBindInfo, pointersForASLR); + bindLazyPointers(dylibPathToBindInfo, pointersForASLR); + // weak bind info is processed at launch time +} + + +template +void BindInfo

::setReExports(const std::unordered_map*>& dylibPathToBindInfo) +{ + for (const std::string& depName : _reExportedDylibNames) { + auto pos = dylibPathToBindInfo.find(depName); + if ( pos == dylibPathToBindInfo.end() ) { + terminate("can't find re-exported dylib '%s' needed by '%s'", depName.c_str(), _installName); + } + _reExportedDylibs.push_back(pos->second); + } +} + +template +void BindInfo

::setDependentDylibs(const std::unordered_map*>& dylibPathToBindInfo) +{ + for (const std::string& depName : _dependentPaths) { + auto pos = dylibPathToBindInfo.find(depName); + if ( pos == dylibPathToBindInfo.end() ) { + terminate("can't find dependent dylib '%s' needed by '%s'", depName.c_str(), _installName); + } + _dependentDylibs.push_back(pos->second); + } +} + + +template +void BindInfo

::bindImmediates(const std::unordered_map*>& dylibPathToBindInfo, std::vector& pointersForASLR) +{ + const uint8_t* p = &_linkeditBias[_dyldInfo->bind_off()]; + const uint8_t* end = &p[_dyldInfo->bind_size()]; + + uint8_t type = 0; + uint64_t segmentOffset = 0; + uint8_t segmentIndex = 0; + const char* symbolName = NULL; + int libraryOrdinal = 0; + int64_t addend = 0; + uint64_t count; + uint64_t skip; + bool weakImport = false; + bool done = false; + while ( !done && (p < end) ) { + uint8_t immediate = *p & BIND_IMMEDIATE_MASK; + uint8_t opcode = *p & BIND_OPCODE_MASK; + ++p; + switch (opcode) { + case BIND_OPCODE_DONE: + done = true; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: + libraryOrdinal = immediate; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + libraryOrdinal = (int)read_uleb128(p, end); + break; + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + // the special ordinals are negative numbers + if ( immediate == 0 ) + libraryOrdinal = 0; + else { + int8_t signExtended = BIND_OPCODE_MASK | immediate; + libraryOrdinal = signExtended; + } + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 ); + symbolName = (char*)p; + while (*p != '\0') + ++p; + ++p; + break; + case BIND_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + addend = read_sleb128(p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segmentIndex = immediate; + segmentOffset = read_uleb128(p, end); + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + segmentOffset += read_uleb128(p, end); + break; + case BIND_OPCODE_DO_BIND: + bindLocation(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, false, weakImport, dylibPathToBindInfo, pointersForASLR); + segmentOffset += sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + bindLocation(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, false, weakImport, dylibPathToBindInfo, pointersForASLR); + segmentOffset += read_uleb128(p, end) + sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + bindLocation(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, false, weakImport, dylibPathToBindInfo, pointersForASLR); + segmentOffset += immediate*sizeof(pint_t) + sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(p, end); + skip = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + bindLocation(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, false, weakImport, dylibPathToBindInfo, pointersForASLR); + segmentOffset += skip + sizeof(pint_t); + } + break; + default: + terminate("bad bind opcode 0x%02X in %s", *p, _installName); + } + } + +} + +template +void BindInfo

::bindLazyPointers(const std::unordered_map*>& dylibPathToBindInfo, std::vector& pointersForASLR) +{ + const uint8_t* p = &_linkeditBias[_dyldInfo->lazy_bind_off()]; + const uint8_t* end = &p[_dyldInfo->lazy_bind_size()]; + + uint8_t type = BIND_TYPE_POINTER; + uint64_t segmentOffset = 0; + uint8_t segmentIndex = 0; + const char* symbolName = NULL; + int libraryOrdinal = 0; + int64_t addend = 0; + bool weakImport = false; + while ( p < end ) { + uint8_t immediate = *p & BIND_IMMEDIATE_MASK; + uint8_t opcode = *p & BIND_OPCODE_MASK; + ++p; + switch (opcode) { + case BIND_OPCODE_DONE: + // this opcode marks the end of each lazy pointer binding + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: + libraryOrdinal = immediate; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + libraryOrdinal = (int)read_uleb128(p, end); + break; + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + // the special ordinals are negative numbers + if ( immediate == 0 ) + libraryOrdinal = 0; + else { + int8_t signExtended = BIND_OPCODE_MASK | immediate; + libraryOrdinal = signExtended; + } + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 ); + symbolName = (char*)p; + while (*p != '\0') + ++p; + ++p; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + addend = read_sleb128(p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segmentIndex = immediate; + segmentOffset = read_uleb128(p, end); + break; + case BIND_OPCODE_DO_BIND: + bindLocation(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, true, weakImport, dylibPathToBindInfo, pointersForASLR); + segmentOffset += sizeof(pint_t); + break; + case BIND_OPCODE_SET_TYPE_IMM: + case BIND_OPCODE_ADD_ADDR_ULEB: + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + default: + terminate("bad lazy bind opcode 0x%02X in %s", opcode, _installName); + } + } + +} + + +template +void BindInfo

::bindLocation(uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, int libraryOrdinal, + int64_t addend, const char* symbolName, bool lazyPointer, bool weakImport, + const std::unordered_map*>& dylibPathToBindInfo, std::vector& pointersForASLR) +{ + //printf("bindLocation: seg=%d, segOffset=0x%08llX, type=%d, lib=%d, addend=%lld, symbol=%s\n", segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName); + if ( segmentIndex > _segSizes.size() ) + terminate("bad segment index in bind info in %s", _installName); + + if ( segmentOffset > _segSizes[segmentIndex] ) + terminate("bad segment offset in bind info in %s", _installName); + + BindInfo

* targetBinder = nullptr; + std::string depName; + switch ( libraryOrdinal ) { + case BIND_SPECIAL_DYLIB_FLAT_LOOKUP: + terminate("dynamic lookup linkage not allowed in dyld shared cache in %s", _installName); + break; + + case BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE: + terminate("linkage to main executable not allowed in dyld shared cache in %s", _installName); + break; + + case BIND_SPECIAL_DYLIB_SELF: + targetBinder = this; + break; + + default: + if ( libraryOrdinal < 0 ) + terminate("bad mach-o binary, special library ordinal not allowd in dyld shared cache in %s", _installName); + if ( (unsigned)libraryOrdinal > _dependentPaths.size() ) + terminate("bad mach-o binary, library ordinal too big in %s", _installName); + depName = _dependentPaths[libraryOrdinal-1]; + auto pos = dylibPathToBindInfo.find(depName); + if ( pos != dylibPathToBindInfo.end() ) + targetBinder = pos->second; + break; + } + + pint_t targetSymbolAddress; + bool isResolverSymbol = false; + bool isAbsolute = false; + BindInfo

* foundIn; + if ( weakImport && (targetBinder == nullptr) ) { + targetSymbolAddress = 0; + foundIn = nullptr; + } + else { + if (targetBinder == nullptr) + terminate("could not bind symbol '%s' used in '%s' because installname '%s' not found", symbolName, _installName, depName.c_str()); + if ( ! targetBinder->findExportedSymbolAddress(symbolName, dylibPathToBindInfo, &targetSymbolAddress, &foundIn, &isResolverSymbol, &isAbsolute) ) + terminate("could not bind symbol '%s' used in: %s expected in: %s", symbolName, _installName, targetBinder->_installName); + } + + //if ( isResolverSymbol ) + // fprintf(stderr, "found resolver based symbol '%s' in %s\n", symbolName, targetBinder->_installName); + // don't bind lazy pointers to resolvers in shared cache + if ( lazyPointer && isResolverSymbol ) { + // instead find common lazy pointer that can be re-used by all clients + pint_t lpVMAddr = targetBinder->findBlessedLazyPointerFor(symbolName); + // switch stub to use shared lazy pointer to reduce dirty pages + this->switchStubToUseSharedLazyPointer(symbolName, lpVMAddr); + return; + } + + + // do actual update + uint8_t* mappedAddr = (uint8_t*)_cacheBuffer + _segCacheOffsets[segmentIndex] + segmentOffset; + pint_t* mappedAddrP = (pint_t*)mappedAddr; + pint_t newValue = (pint_t)(targetSymbolAddress + addend); + switch ( type ) { + case BIND_TYPE_POINTER: + // only write new value if it will change it + // this reduces pages dirtied + if ( P::getP(*mappedAddrP) != newValue ) + P::setP(*mappedAddrP, newValue); + break; + + case BIND_TYPE_TEXT_ABSOLUTE32: + case BIND_TYPE_TEXT_PCREL32: + terminate("text relocs not supported for shared cache binding in %s", _installName); + break; + + default: + terminate("bad bind type (%d) in %s", type, _installName); + } + if ( !isAbsolute ) + pointersForASLR.push_back(mappedAddr); +} + + +template +bool BindInfo

::findExportedSymbolAddress(const char* symbolName, const std::unordered_map*>& dylibPathToBindInfo, + pint_t* address, BindInfo

** foundIn, bool* isResolverSymbol, bool* isAbsolute) +{ + auto pos = _exports.find(symbolName); + if ( pos != _exports.end() ) { + if ( pos->second.isSymbolReExport ) { + const char* importName = symbolName; + if ( !pos->second.reExportName.empty() ) + importName = pos->second.reExportName.c_str(); + std::string& depPath = _dependentPaths[pos->second.reExportDylibIndex-1]; + auto pos2 = dylibPathToBindInfo.find(depPath); + if ( pos2 != dylibPathToBindInfo.end() ) { + BindInfo

* reExportFrom = pos2->second; + return reExportFrom->findExportedSymbolAddress(importName, dylibPathToBindInfo, address, foundIn, isResolverSymbol, isAbsolute); + } + else { + verboseLog("findExportedSymbolAddress(%s) => ???\n", symbolName); + } + } + *address = pos->second.address; + *foundIn = this; + *isResolverSymbol = pos->second.isResolver; + *isAbsolute = pos->second.isAbsolute; + //verboseLog("findExportedSymbolAddress(%s) => 0x0%llX\n", symbolName, (uint64_t)*address); + return true; + } + + for (BindInfo

* dep : _reExportedDylibs) { + if ( dep->findExportedSymbolAddress(symbolName, dylibPathToBindInfo, address, foundIn, isResolverSymbol, isAbsolute) ) + return true; + } + return false; +} + +template +void BindInfo

::addExportsToGlobalMap(std::unordered_map*>& reverseMap) +{ + for (const auto& expEntry : _exports) { + const std::string& symName = expEntry.first; + auto pos = reverseMap.find(symName); + if ( pos == reverseMap.end() ) { + reverseMap[symName] = this; + } + else { + BindInfo

* other = pos->second; + if ( expEntry.second.isSymbolReExport ) + continue; + if ( other->_exports[symName].isSymbolReExport ) + continue; + //warning("symbol '%s' exported from %s and %s\n", symName.c_str(), this->_installName, other->_installName); + } + } +} + +template +typename P::uint_t BindInfo

::findBlessedLazyPointerFor(const std::string& resolverSymbolName) +{ + static const bool log = false; + + // check if this has already been looked up + auto pos1 = _resolverBlessedMap.find(resolverSymbolName); + if ( pos1 != _resolverBlessedMap.end() ) { + return pos1->second; + } + + // if this symbol is re-exported from another dylib, look there + bool thisDylibImplementsResolver = false; + auto pos = _exports.find(resolverSymbolName); + if ( pos != _exports.end() ) { + const SymbolInfo& info = pos->second; + if ( info.isSymbolReExport ) { + std::string reImportName = resolverSymbolName; + if ( !info.reExportName.empty() ) + reImportName = info.reExportName; + if ( info.reExportDylibIndex > _dependentDylibs.size() ) { + warning("dylib index for re-exported symbol %s too large (%d) in %s", resolverSymbolName.c_str(), info.reExportDylibIndex, _installName); + } + else { + BindInfo

* reExportedFrom = _dependentDylibs[info.reExportDylibIndex-1]; + if ( log ) verboseLog( "following re-export of %s in %s, to %s in %s", resolverSymbolName.c_str(), _installName, reImportName.c_str(), reExportedFrom->_installName); + pint_t lp = reExportedFrom->findBlessedLazyPointerFor(reImportName); + if ( lp != 0 ) { + _resolverBlessedMap[resolverSymbolName] = lp; + return lp; + } + } + } + if ( info.isResolver ) + thisDylibImplementsResolver = true; + } + + // lookup in lazy pointer section + if ( thisDylibImplementsResolver ) { + const uint32_t* const indirectTable = (uint32_t*)&_linkeditBias[_dynSymTabCmd->indirectsymoff()]; + const macho_nlist

* const symbolTable = (macho_nlist

*)(&_linkeditBias[_symTabCmd->symoff()]); + const char* symbolStringPool = (char*)(&_linkeditBias[_symTabCmd->stroff()]); + + for (const macho_segment_command

* seg : _segCmds) { + const macho_section

* const sectionsStart = (macho_section

*)((uint8_t*)seg + sizeof(macho_segment_command

)); + const macho_section

* const sectionsEnd = §ionsStart[seg->nsects()]; + for (const macho_section

* sect=sectionsStart; sect < sectionsEnd; ++sect) { + uint8_t sectionType = sect->flags() & SECTION_TYPE; + if ( sectionType == S_LAZY_SYMBOL_POINTERS) { + uint32_t elementCount = (uint32_t)(sect->size() / sizeof(pint_t)); + const uint32_t indirectTableOffset = sect->reserved1(); + pint_t vmlocation = (pint_t)sect->addr(); + for (uint32_t j=0; j < elementCount; ++j, vmlocation += sizeof(pint_t)) { + uint32_t symbolIndex = E::get32(indirectTable[indirectTableOffset + j]); + switch ( symbolIndex ) { + case INDIRECT_SYMBOL_ABS: + case INDIRECT_SYMBOL_LOCAL: + break; + default: + const macho_nlist

* aSymbol = &symbolTable[symbolIndex]; + const char* aName = &symbolStringPool[aSymbol->n_strx()]; + if ( resolverSymbolName == aName) { + if ( log ) verboseLog("found shared lazy pointer at 0x%llX for %s in %s in %s", (uint64_t)vmlocation, aName, sect->sectname(), _installName); + _resolverBlessedMap[resolverSymbolName] = vmlocation; + return vmlocation; + } + break; + } + } + } + } + } + } + + if ( log ) verboseLog( "not found shared lazy pointer for %s in %s, checking re-export dylibs", resolverSymbolName.c_str(), _installName); + for (BindInfo

* reExportedDylib : _reExportedDylibs ) { + pint_t result = reExportedDylib->findBlessedLazyPointerFor(resolverSymbolName); + if ( result != 0 ) { + _resolverBlessedMap[resolverSymbolName] = result; + return result; + } + } + + if ( log ) verboseLog( "NOT found shared lazy pointer for %s in %s", resolverSymbolName.c_str(), _installName); + return 0; +} + +template +void BindInfo

::switchStubToUseSharedLazyPointer(const std::string& resolverSymbolName, pint_t lpVMAddr) +{ + // find named stub + const uint32_t* const indirectTable = (uint32_t*)&_linkeditBias[_dynSymTabCmd->indirectsymoff()]; + const macho_nlist

* const symbolTable = (macho_nlist

*)(&_linkeditBias[_symTabCmd->symoff()]); + const char* symbolStringPool = (char*)(&_linkeditBias[_symTabCmd->stroff()]); + for (const macho_segment_command

* seg : _segCmds) { + macho_section

* const sectionsStart = (macho_section

*)((char*)seg + sizeof(macho_segment_command

)); + macho_section

* const sectionsEnd = §ionsStart[seg->nsects()]; + for(macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { + if ( ((sect->flags() & SECTION_TYPE) == S_SYMBOL_STUBS) && (sect->size() != 0) ) { + pint_t stubsVMStart = (pint_t)sect->addr(); + uint8_t* stubsMappingStart = ((uint8_t*)_cacheBuffer) + sect->offset(); + const uint32_t indirectTableOffset = sect->reserved1(); + const uint32_t stubSize = sect->reserved2(); + uint32_t elementCount = (uint32_t)(sect->size() / stubSize); + pint_t stubVMAddr = stubsVMStart; + uint8_t* stubMappedAddr = stubsMappingStart; + for (uint32_t j=0; j < elementCount; ++j, stubMappedAddr += stubSize, stubVMAddr += stubSize) { + uint32_t symbolIndex = E::get32(indirectTable[indirectTableOffset + j]); + switch ( symbolIndex ) { + case INDIRECT_SYMBOL_ABS: + case INDIRECT_SYMBOL_LOCAL: + break; + default: + { + const macho_nlist

* aSymbol = &symbolTable[symbolIndex]; + const char* stubName = &symbolStringPool[aSymbol->n_strx()]; + if ( resolverSymbolName == stubName ) { + switch (_mh->cputype()) { + case CPU_TYPE_ARM: + switchArmStubsLazyPointer(stubMappedAddr, stubVMAddr, stubSize, lpVMAddr); + break; + default: + //warning("shared resolver lazy pointer to %s not implemented for this arch", resolverSymbolName.c_str()); + break; + } + } + } + break; + } + } + } + } + } +} + +template +void BindInfo

::switchArmStubsLazyPointer(uint8_t* stubMappedAddress, pint_t stubVMAddress, uint32_t stubSize, pint_t lpVMAddr) +{ + if ( stubSize != 16 ) { + warning("could not optimize ARM stub to resolver function in %s because it is wrong size\n", _installName); + return; + } + uint32_t* instructions = (uint32_t*)stubMappedAddress; + if ( (E::get32(instructions[0]) != 0xe59fc004) + || (E::get32(instructions[1]) != 0xe08fc00c) + || (E::get32(instructions[2]) != 0xe59cf000) + ) { + warning("could not optimize ARM stub to resolver function in %s because instructions are not as expected", _installName); + return; + } + // last .long in stub is: lazyPtr - (stub+8) + // alter to point to more optimal lazy pointer + uint32_t betterOffset = (uint32_t)(lpVMAddr - (stubVMAddress + 12)); + E::set32(instructions[3], betterOffset); +} + + +template +void BindInfo

::bindAllImagesInCache(void* cacheBuffer, const std::unordered_map& dylibPathToMachHeader, std::vector& pointersForASLR) +{ + // build BindInfo object for each dylib + std::unordered_map*, BindInfo

*> headersToBindInfo; + std::unordered_map*> dylibPathToBindInfo; + for (const auto& entry: dylibPathToMachHeader) { + macho_header

* mh = (macho_header

*)entry.second; + if ( headersToBindInfo.count(mh) == 0 ) + headersToBindInfo[mh] = new BindInfo

(cacheBuffer, mh); + dylibPathToBindInfo[entry.first] = headersToBindInfo[mh]; + } + + // chain re-exported dylibs + for (const auto& entry: headersToBindInfo) { + entry.second->setDependentDylibs(dylibPathToBindInfo); + entry.second->setReExports(dylibPathToBindInfo); + } + + // bind each dylib + for (const auto& entry: headersToBindInfo) { + entry.second->bind(dylibPathToBindInfo, pointersForASLR); + } + + // look for exported symbol collisions + std::unordered_map*> reverseMap; + for (const auto& entry: headersToBindInfo) { + entry.second->addExportsToGlobalMap(reverseMap); + } + + // clean up + for (const auto& entry: headersToBindInfo) { + delete entry.second; + } +} + + +} // anonymous namespace + + +void SharedCache::bindAllImagesInCache(const std::unordered_map& dylibPathToMachHeader, std::vector& pointersForASLR) +{ + switch ( _arch.arch ) { + case CPU_TYPE_ARM: + case CPU_TYPE_I386: + BindInfo>::bindAllImagesInCache(_buffer.get(), dylibPathToMachHeader, pointersForASLR); + break; + case CPU_TYPE_X86_64: + case CPU_TYPE_ARM64: + BindInfo>::bindAllImagesInCache(_buffer.get(), dylibPathToMachHeader, pointersForASLR); + break; + default: + terminate("unsupported arch 0x%08X", _arch.arch); + } +} + + + diff --git a/interlinked-dylibs/CodeSigningTypes.h b/interlinked-dylibs/CodeSigningTypes.h new file mode 100644 index 0000000..e4a4049 --- /dev/null +++ b/interlinked-dylibs/CodeSigningTypes.h @@ -0,0 +1,130 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _CODE_SIGNING_TYPES_ +#define _CODE_SIGNING_TYPES_ + +#include +#include + + +// +// Magic numbers used by Code Signing +// +enum { + CSMAGIC_REQUIREMENT = 0xfade0c00, // single Requirement blob + CSMAGIC_REQUIREMENTS = 0xfade0c01, // Requirements vector (internal requirements) + CSMAGIC_CODEDIRECTORY = 0xfade0c02, // CodeDirectory blob + CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, // embedded form of signature data + CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, // multi-arch collection of embedded signatures + CSMAGIC_BLOBWRAPPER = 0xfade0b01, // used for the cms blob +}; + +enum { + CS_PAGE_SIZE = 4096, + + CS_HASHTYPE_SHA1 = 1, + CS_HASHTYPE_SHA256 = 2, + CS_HASHTYPE_SHA256_TRUNCATED = 3, + + CS_HASH_SIZE_SHA1 = 20, + CS_HASH_SIZE_SHA256 = 32, + CS_HASH_SIZE_SHA256_TRUNCATED = 20, + + CSSLOT_CODEDIRECTORY = 0, + CSSLOT_INFOSLOT = 1, + CSSLOT_REQUIREMENTS = 2, + CSSLOT_RESOURCEDIR = 3, + CSSLOT_APPLICATION = 4, + CSSLOT_ENTITLEMENTS = 5, + CSSLOT_CMS_SIGNATURE = 0x10000, + + kSecCodeSignatureAdhoc = 2 +}; + + + +// +// Structure of a SuperBlob +// +struct CS_BlobIndex { + uint32_t type; // type of entry + uint32_t offset; // offset of entry +}; + +struct CS_SuperBlob { + uint32_t magic; // magic number + uint32_t length; // total length of SuperBlob + uint32_t count; // number of index entries following + CS_BlobIndex index[]; // (count) entries + // followed by Blobs in no particular order as indicated by offsets in index +}; + +// +// C form of a CodeDirectory. +// +struct CS_CodeDirectory { + uint32_t magic; // magic number (CSMAGIC_CODEDIRECTORY) */ + uint32_t length; // total length of CodeDirectory blob + uint32_t version; // compatibility version + uint32_t flags; // setup and mode flags + uint32_t hashOffset; // offset of hash slot element at index zero + uint32_t identOffset; // offset of identifier string + uint32_t nSpecialSlots; // number of special hash slots + uint32_t nCodeSlots; // number of ordinary (code) hash slots + uint32_t codeLimit; // limit to main image signature range + uint8_t hashSize; // size of each hash in bytes + uint8_t hashType; // type of hash (cdHashType* constants) + uint8_t platform; // platform identifier; zero if not platform binary + uint8_t pageSize; // log2(page size in bytes); 0 => infinite + uint32_t spare2; // unused (must be zero) + // Version 0x20100 or later + uint32_t scatterOffset; // offset of optional scatter vector + // followed by dynamic content as located by offset fields above +}; + +struct CS_Blob { + uint32_t magic; // magic number + uint32_t length; // total length of blob +}; + +struct CS_RequirementsBlob { + uint32_t magic; // magic number + uint32_t length; // total length of blob + uint32_t data; // zero for dyld shared cache +}; + + +struct CS_Scatter { + uint32_t count; // number of pages; zero for sentinel (only) + uint32_t base; // first page number + uint64_t targetOffset; // byte offset in target + uint64_t spare; // reserved (must be zero) +}; + + +#endif // _CODE_SIGNING_TYPES_ + + + diff --git a/interlinked-dylibs/FileCache.cpp b/interlinked-dylibs/FileCache.cpp new file mode 100644 index 0000000..3ccb9a1 --- /dev/null +++ b/interlinked-dylibs/FileCache.cpp @@ -0,0 +1,234 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +#include "mega-dylib-utils.h" +#include "MachOFileAbstraction.hpp" +#include "Trie.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dyld_cache_config.h" + +#include "OptimizerBranches.h" + +#include "CacheFileAbstraction.hpp" + +#include "mega-dylib-utils.h" +#include "Logging.h" + + +//#include +extern "C" int rootless_check_trusted(const char *path); +extern "C" int rootless_check_trusted_fd(int fd) __attribute__((weak_import)); + +static bool rootlessEnabled; +static dispatch_once_t onceToken; + +bool isProtectedBySIP(const std::string& path, int fd) +{ + bool isProtected = false; + // Check to make sure file system protections are on at all + dispatch_once(&onceToken, ^{ + rootlessEnabled = csr_check(CSR_ALLOW_UNRESTRICTED_FS); + }); + if (!rootlessEnabled) + return false; +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 + if ( (fd != -1) && (&rootless_check_trusted_fd != NULL) ) + isProtected = (rootless_check_trusted_fd(fd) == 0); + else +#endif + if ( &rootless_check_trusted != NULL ) + isProtected = (rootless_check_trusted(path.c_str()) == 0); + return isProtected; +} + + +std::string toolDir() +{ + char buffer[PATH_MAX]; + uint32_t bufsize = PATH_MAX; + int result = _NSGetExecutablePath(buffer, &bufsize); + if ( result == 0 ) { + std::string path = buffer; + size_t pos = path.rfind('/'); + if ( pos != std::string::npos ) + return path.substr(0,pos+1); + } + warning("tool directory not found"); + return "/tmp/"; +} + +std::string baspath(const std::string& path) +{ + std::string::size_type slash_pos = path.rfind("/"); + if (slash_pos != std::string::npos) { + slash_pos++; + return path.substr(slash_pos); + } else { + return path; + } +} + +std::string dirpath(const std::string& path) +{ + std::string::size_type slash_pos = path.rfind("/"); + if (slash_pos != std::string::npos) { + slash_pos++; + return path.substr(0, slash_pos); + } else { + char cwd[MAXPATHLEN]; + (void)getcwd(cwd, MAXPATHLEN); + return cwd; + } +} + +std::string normalize_absolute_file_path(const std::string &path) { + std::vector components; + std::vector processed_components; + std::stringstream ss(path); + std::string retval; + std::string item; + + while (std::getline(ss, item, '/')) { + components.push_back(item); + } + + if (components[0] == ".") { + retval = "."; + } + + for (auto& component : components) { + if (component.empty() || component == ".") + continue; + else if (component == ".." && processed_components.size()) + processed_components.pop_back(); + else + processed_components.push_back(component); + } + + for (auto & component : processed_components) { + retval = retval + "/" + component; + } + + return retval; +} + +FileCache fileCache; + +FileCache::FileCache(void) { + cache_queue = dispatch_queue_create("com.apple.dyld.cache.cache", DISPATCH_QUEUE_SERIAL); +} + + +void FileCache::preflightCache(const std::unordered_set& paths) { + for (auto &path : paths) { + preflightCache(path); + } +} + +void FileCache::preflightCache(const std::string& path) +{ + cacheBuilderDispatchAsync(cache_queue, [=] { + std::string normalizedPath = normalize_absolute_file_path(path); + if (entries.count(normalizedPath) == 0) { + fill(normalizedPath); + } + }); +} + +std::tuple FileCache::cacheLoad(const std::string path) { + std::string normalizedPath = normalize_absolute_file_path(path); + cacheBuilderDispatchSync(cache_queue, [=] { + if ( entries.count(normalizedPath) == 0 ) + fill(normalizedPath); + }); + + return entries[normalizedPath]; +} + + +//FIXME error handling +void FileCache::fill(const std::string& path) { + struct stat stat_buf; + + int fd = ::open(path.c_str(), O_RDONLY, 0); + if ( fd == -1 ) { + verboseLog("can't open file '%s', errno=%d", path.c_str(), errno); + entries[path] = std::make_tuple((uint8_t*)(-1), stat_buf, false); + return; + } + + if ( fstat(fd, &stat_buf) == -1) { + verboseLog("can't stat open file '%s', errno=%d", path.c_str(), errno); + entries[path] = std::make_tuple((uint8_t*)(-1), stat_buf, false); + ::close(fd); + return; + } + + if ( stat_buf.st_size < 4096 ) { + verboseLog("file too small '%s'", path.c_str()); + entries[path] = std::make_tuple((uint8_t*)(-1), stat_buf, false); + ::close(fd); + return; + } + + bool rootlessProtected = isProtectedBySIP(path, fd); + + void* buffer_ptr = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buffer_ptr == MAP_FAILED) { + verboseLog("mmap() for shared cache at %s failed, errno=%d", path.c_str(), errno); + entries[path] = std::make_tuple((uint8_t*)(-1), stat_buf, false); + ::close(fd); + return; + } + + entries[path] = std::make_tuple((uint8_t*)buffer_ptr, stat_buf, rootlessProtected); + ::close(fd); +} + + + + + + diff --git a/interlinked-dylibs/Logging.cpp b/interlinked-dylibs/Logging.cpp new file mode 100644 index 0000000..0f744ef --- /dev/null +++ b/interlinked-dylibs/Logging.cpp @@ -0,0 +1,257 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2016 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include + +#include +#include "mega-dylib-utils.h" + +#include "Logging.h" + +#define MAX_LOG_STR_LEN (512) +//const char* kDispatchQueueLogNameKey = "kDispatchQueueLogNameKey"; +const char* kDispatchWarningArrayKey = "kDispatchWarningArrayKey"; + +static dispatch_queue_t log_queue; +static dispatch_once_t logQueueInit = 0; +static dispatch_queue_t unique_queue; +static dispatch_once_t uniqueQueueInit = 0; + +static uint32_t verbose = 0; +static bool returnNonZeroIfTerminateCalled = false; +static bool terminateCalled = false; + +static const char* warningPrefix = "WARNING: "; +static const char* errorPrefix = "ERROR: "; + +LoggingContext::LoggingContext(const std::string& N) + : _name(N) + , _tainted(false) +{ +} + +LoggingContext::LoggingContext(const std::string& N, WarningTargets T) + : _name(N) + , _warnings(T) + , _tainted(false) +{ +} + +void LoggingContext::taint() +{ + _tainted = true; +} + +bool LoggingContext::isTainted() +{ + return _tainted; +} + +const std::string& LoggingContext::name() +{ + return _name; +} + +const WarningTargets& LoggingContext::targets() +{ + return _warnings; +} + + + +pthread_key_t getLoggingContextKey(void) { + static pthread_key_t logContextKey; + static dispatch_once_t logContextToken; + dispatch_once(&logContextToken, ^{ + pthread_key_create(&logContextKey, nullptr); + }); + return logContextKey; +} + + +void setLoggingContext(std::shared_ptr& context) +{ + pthread_setspecific(getLoggingContextKey(), (void*)&context); + + if (context && !context->name().empty()) { + pthread_setname_np(context->name().substr(0, MAXTHREADNAMESIZE-1).c_str()); + } +} + +std::shared_ptr getLoggingContext() +{ + if (void* val = pthread_getspecific(getLoggingContextKey())) + return *((std::shared_ptr*)val); + return nullptr; +} + +void runBody(void* Ctx) +{ + std::unique_ptr> + Body(reinterpret_cast*>(Ctx)); + (*Body)(); +} + +static dispatch_queue_t getLogQueue() +{ + dispatch_once(&logQueueInit, ^{ + log_queue = dispatch_queue_create("com.apple.dyld.cache.logging", DISPATCH_QUEUE_SERIAL); + }); + return log_queue; +} + +void setVerbose(bool level) +{ + verbose = level; +} + +void setWarnAnErrorPrefixes(const char* warn, const char* err) +{ + warningPrefix = warn; + errorPrefix = err; +} + +void setReturnNonZeroOnTerminate() +{ + returnNonZeroIfTerminateCalled = true; +} + +void queued_print(FILE* __restrict fd, const char* str) +{ + const char* qstr = strdup(str); + + dispatch_async(getLogQueue(), ^{ + (void)fprintf(fd, "%s", qstr); + free((void*)qstr); + }); +} + +#define VLOG(header) \ + va_list list; \ + va_start(list, format); \ + char temp[MAX_LOG_STR_LEN]; \ + vsprintf(temp, format, list); \ + auto ctx = getLoggingContext(); \ + char temp2[MAX_LOG_STR_LEN]; \ + if (ctx && !ctx->name().empty()) { \ + snprintf(temp2, MAX_LOG_STR_LEN, "[%s] %s%s\n", ctx->name().c_str(), header, \ + temp); \ + } else { \ + snprintf(temp2, MAX_LOG_STR_LEN, "%s%s\n", header, temp); \ + } \ + queued_print(stderr, temp2); \ + va_end(list); + +void log(const char* __restrict format, ...) +{ + VLOG(""); +} + +void verboseLog(const char* format, ...) +{ + if (verbose) { + VLOG(""); + } +} + +static std::set warnings; + +void warning(const char* format, ...) +{ + dispatch_once(&uniqueQueueInit, ^{ + unique_queue = dispatch_queue_create("com.apple.dyld.cache.logging", DISPATCH_QUEUE_SERIAL); + }); + + va_list list; + va_start(list, format); + char temp[MAX_LOG_STR_LEN]; + vsprintf(temp, format, list); + char* blockTemp = strdup(temp); + + auto ctx = getLoggingContext(); + if (ctx) { + for (auto& target : ctx->targets().second) { + ctx->targets().first->configurations[target.first].architectures[target.second].results.warnings.push_back(blockTemp); + } + } + + dispatch_sync(unique_queue, ^{ + if (warnings.count(blockTemp) == 0) { + warnings.insert(blockTemp); + } + + free(blockTemp); + }); + + va_end(list); +} + +void terminate(const char* format, ...) +{ + VLOG(errorPrefix); + + terminateCalled = true; + + if (ctx) { + // We are a work in a logging context, throw + throw std::string(temp); + } else { + // We are in general handing, let the loggging queue darain and exit + dispatch_sync(getLogQueue(), ^{ + for (auto& warning : warnings) { + (void)fprintf(stderr, "%s%s\n", warningPrefix, warning.c_str()); + } + if ( returnNonZeroIfTerminateCalled ) { + exit(1); + } + else { + time_t endtime = time(0); + (void)fprintf(stderr, "Finished: %s\n", asctime(localtime(&endtime))); + (void)fprintf(stderr, "Exiting\n"); + exit(0); + } + }); + } + + // clang can't reason out that we won't hit this due to the dispatch_sync in the exit path + __builtin_unreachable(); +} + +void dumpLogAndExit(bool logFinishTime) +{ + dispatch_async(getLogQueue(), ^{ + for (auto& warning : warnings) { + (void)fprintf(stderr, "%s%s\n", warningPrefix, warning.c_str()); + } + if ( logFinishTime ) { + time_t endtime = time(0); + (void)fprintf(stderr, "Finished: %s\n", asctime(localtime(&endtime))); + (void)fprintf(stderr, "Exiting\n"); + } + exit(returnNonZeroIfTerminateCalled && terminateCalled ? 1 : 0); + }); +} diff --git a/interlinked-dylibs/Logging.h b/interlinked-dylibs/Logging.h new file mode 100644 index 0000000..db4c992 --- /dev/null +++ b/interlinked-dylibs/Logging.h @@ -0,0 +1,132 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2016 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef LOGGING_H +#define LOGGING_H + +#include +#include +#include "mega-dylib-utils.h" + +void verboseLog(const char* format, ...) __printflike(1, 2); +void log(const char* __restrict format, ...) __printflike(1, 2); +void alwaysLog(const char* __restrict format, ...) __printflike(1, 2); + +void warning(const char* format, ...) __printflike(1, 2); +void terminate(const char* format, ...) __printflike(1, 2) __attribute__((noreturn)); +void dumpLogAndExit(bool logFinishTime=true); + +void setVerbose(bool level); +void setReturnNonZeroOnTerminate(); +void setWarnAnErrorPrefixes(const char* warn, const char* err); + +struct LoggingContext { + LoggingContext(const std::string& N); + LoggingContext(const std::string& N, WarningTargets T); + void taint(); + bool isTainted(); + const std::string& name(); + const WarningTargets& targets(); + +private: + const std::string _name; + const WarningTargets _warnings; + bool _tainted; +}; + +void setLoggingContext(std::shared_ptr& context); +std::shared_ptr getLoggingContext(); + +/* Okay, so this gets tricky + * Naively, what we are doing is stashing some information in pthread specific + * variables so that the logging system can pick it up and tag messages with it, + * but without us having to track it all through the entire cache builder code base. + * Additionally, we use the presence of that information to determine if we are in + * the root logging context (where terminate() calls are fatal), or a thread context + * (where they should just throw so the thread fails, logs an error, and cleans up its + * state. + * + * The problem is that we need that context to follow our blocks when we switch threads. + * We achieve that by wrapping dispatch_(a)sync with our own calls that setup try{} blocks + * around the executing lambda, that way it is always safe to throw in a named context + * name. We also use those wrappers to copy the context between the threads using + * the closures as glue. Finally, we set a taint variable that can back propgate to stop + * the execution of any furthur blocks related to a context that has thrown. + * + * This is exposed in the header because we need it for the templates to work, but aside from + * cacheBuilderDispatchAsync() and friends nothing here should be used directly. + */ + +void runBody(void* Ctx); + +pthread_key_t getLoggingContextKey(void); + +template +std::function* heapSafe(BodyFtor&& Body, std::shared_ptr context) +{ + auto retval = new std::function([ B = std::move(Body), context ]() mutable { + if (!context || !context->isTainted()) { + + void* oldCtx = pthread_getspecific(getLoggingContextKey()); + setLoggingContext(context); + try { + B(); + } catch (std::string exception) { + WarningTargets warningTargets = context->targets(); + for (auto& target : warningTargets.second) { + warningTargets.first->configurations[target.first].architectures[target.second].results.failure = exception; + } + if (context) { + context->taint(); + } + } catch (...) { + if (context) { + context->taint(); + } + } + pthread_setspecific(getLoggingContextKey(), oldCtx); + } + }); + return retval; +} + +template +void cacheBuilderDispatchAsync(dispatch_queue_t queue, BodyFtor&& Body) +{ + dispatch_async_f(queue, heapSafe(Body, getLoggingContext()), runBody); +} + +template +void cacheBuilderDispatchGroupAsync(dispatch_group_t group, dispatch_queue_t queue, BodyFtor&& Body) +{ + dispatch_group_async_f(group, queue, heapSafe(Body, getLoggingContext()), runBody); +} + +template +void cacheBuilderDispatchSync(dispatch_queue_t queue, BodyFtor&& Body) +{ + dispatch_sync_f(queue, heapSafe(Body, getLoggingContext()), runBody); +} + +#endif /* LOGGING_H */ diff --git a/interlinked-dylibs/MachOProxy.cpp b/interlinked-dylibs/MachOProxy.cpp new file mode 100644 index 0000000..e1923f8 --- /dev/null +++ b/interlinked-dylibs/MachOProxy.cpp @@ -0,0 +1,254 @@ +// +// DylibProxy.cpp +// dyld +// +// Created by Louis Gerbarg on 1/27/16. +// +// + +#include +#include + +#include "mega-dylib-utils.h" +#include "Logging.h" + +#include "MachOProxy.h" + +namespace { +std::map mapMachOFile( const std::string& path ) { + std::map retval; + const uint8_t* p = (uint8_t*)( -1 ); + struct stat stat_buf; + bool rootless; + + std::tie( p, stat_buf, rootless ) = fileCache.cacheLoad( path ); + + if ( p == (uint8_t*)( -1 ) ) { + return retval; + } + + // if fat file, process each architecture + const fat_header* fh = (fat_header*)p; + const mach_header* mh = (mach_header*)p; + if ( OSSwapBigToHostInt32( fh->magic ) == FAT_MAGIC ) { + // Fat header is always big-endian + const fat_arch* slices = (const fat_arch*)( (char*)fh + sizeof( fat_header ) ); + const uint32_t sliceCount = OSSwapBigToHostInt32( fh->nfat_arch ); + for ( uint32_t i = 0; i < sliceCount; ++i ) { + // FIXME Should we validate the fat header matches the slices? + ArchPair arch( OSSwapBigToHostInt32( slices[i].cputype ), OSSwapBigToHostInt32( slices[i].cpusubtype ) ); + uint32_t fileOffset = OSSwapBigToHostInt32( slices[i].offset ); + const mach_header* th = (mach_header*)(p+fileOffset); + if ( ( OSSwapLittleToHostInt32( th->magic ) == MH_MAGIC ) || ( OSSwapLittleToHostInt32( th->magic ) == MH_MAGIC_64 ) ) { + uint32_t fileSize = static_cast( stat_buf.st_size ); + retval[stringForArch( arch )] = new MachOProxy( path, stat_buf.st_ino, stat_buf.st_mtime, fileOffset, fileSize, rootless ); + } + } + } else if ( ( OSSwapLittleToHostInt32( mh->magic ) == MH_MAGIC ) || ( OSSwapLittleToHostInt32( mh->magic ) == MH_MAGIC_64 ) ) { + ArchPair arch( OSSwapLittleToHostInt32( mh->cputype ), OSSwapLittleToHostInt32( mh->cpusubtype ) ); + uint32_t fileOffset = OSSwapBigToHostInt32( 0 ); + uint32_t fileSize = static_cast( stat_buf.st_size ); + retval[stringForArch( arch )] = new MachOProxy( path, stat_buf.st_ino, stat_buf.st_mtime, fileOffset, fileSize, rootless ); + } else { + // warning( "file '%s' is not contain requested a MachO", path.c_str() ); + } + return retval; +} +} + +template +std::string MachOProxy::machoParser(bool ignoreUncacheableDylibsInExecutables) +{ + const uint8_t* buffer = getBuffer(); + bool hasSplitSegInfo = false; + bool hasDylidInfo = false; + const macho_header

* mh = (const macho_header

*)buffer; + const macho_symtab_command

* symTab = nullptr; + const macho_dysymtab_command

* dynSymTab = nullptr; + const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + const uint32_t cmd_count = mh->ncmds(); + const macho_load_command

* cmd = cmds; + _dylib = (mh->filetype() == MH_DYLIB); + _executable = (mh->filetype() == MH_EXECUTE); + if (mh->filetype() == MH_DYLIB_STUB) { + return "stub dylib"; + } + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd()) { + case LC_ID_DYLIB: { + macho_dylib_command

* dylib = (macho_dylib_command

*)cmd; + if (dylib->name()[0] != '/') { + if (strncmp(dylib->name(), "@rpath", 6) == 0) + return "@rpath cannot be used in -install_name for OS dylibs"; + else + return "-install_name is not an absolute path"; + } + installName = dylib->name(); + installNameOffsetInTEXT = (uint32_t)((uint8_t*)cmd - buffer) + dylib->name_offset(); + } break; + case LC_UUID: { + const macho_uuid_command

* uuidCmd = (macho_uuid_command

*)cmd; + ::memcpy(uuid, uuidCmd->uuid(), sizeof(uuid_t)); + } break; + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: { + macho_dylib_command

* dylib = (macho_dylib_command

*)cmd; + std::string depName = dylib->name(); + if ( _executable && ignoreUncacheableDylibsInExecutables && !has_prefix(depName, "/usr/lib/") && !has_prefix(depName, "/System/Library/") ) { + // in update_dyld_shared_cache don't warn if root executable links with something not eligible for shared cache + break; + } + else if ( depName[0] != '/' ) { + return "linked against a dylib whose -install_name was non-absolute (e.g. @rpath)"; + } + dependencies.insert(depName); + } break; + case macho_segment_command

::CMD: { + const macho_segment_command

* segCmd = (macho_segment_command

*)cmd; + MachOProxy::Segment seg; + seg.name = segCmd->segname(); + seg.size = align(segCmd->vmsize(), 12); + seg.diskSize = (uint32_t)segCmd->filesize(); + seg.fileOffset = (uint32_t)segCmd->fileoff(); + seg.protection = segCmd->initprot(); + if (segCmd->nsects() > 0) { + seg.p2align = 0; + const macho_section

* const sectionsStart = (macho_section

*)((uint8_t*)segCmd + sizeof(macho_segment_command

)); + const macho_section

* const sectionsLast = §ionsStart[segCmd->nsects() - 1]; + const macho_section

* const sectionsEnd = §ionsStart[segCmd->nsects()]; + for (const macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { + if (sect->align() > seg.p2align) + seg.p2align = sect->align(); + } + seg.sizeOfSections = sectionsLast->addr() + sectionsLast->size() - segCmd->vmaddr(); + } else { + seg.p2align = 12; + } + segments.push_back(seg); + } break; + case LC_SEGMENT_SPLIT_INFO: + hasSplitSegInfo = true; + break; + case LC_SYMTAB: + symTab = (macho_symtab_command

*)cmd; + break; + case LC_DYSYMTAB: + dynSymTab = (macho_dysymtab_command

*)cmd; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + hasDylidInfo = true; + break; + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd) + cmd->cmdsize()); + } + + if (!_dylib) { + return ""; + } + + if (!hasDylidInfo) { + return "built for old OS"; + } + + if ((mh->flags() & MH_TWOLEVEL) == 0) { + return "built with -flat_namespace"; + } + + if (!hasSplitSegInfo) { + bool inUsrLib = (installName.size() > 9) && (installName.substr(0, 9) == "/usr/lib/"); + bool inSystemLibrary = (installName.size() > 16) && (installName.substr(0, 16) == "/System/Library/"); + if (!inUsrLib && !inSystemLibrary) { + return "-install_name not /usr/lib/* or /System/Library/*"; + } + return "no shared region info"; + } + + if ((symTab == nullptr) && (dynSymTab == nullptr)) { + return "no symbol table"; + } + + if (installName.empty()) { + return "dylib missing install name"; + } + + // scan undefines looking for invalid ordinals + const macho_nlist

* symbolTable = (macho_nlist

*)((uint8_t*)mh + symTab->symoff()); + const uint32_t startUndefs = dynSymTab->iundefsym(); + const uint32_t endUndefs = startUndefs + dynSymTab->nundefsym(); + for (uint32_t i = startUndefs; i < endUndefs; ++i) { + uint8_t ordinal = GET_LIBRARY_ORDINAL(symbolTable[i].n_desc()); + if (ordinal == DYNAMIC_LOOKUP_ORDINAL) { + return "built with '-undefined dynamic_lookup'"; + } else if (ordinal == EXECUTABLE_ORDINAL) { + return "built with -bundle_loader"; + } + } + + return ""; +} + +const bool MachOProxy::isDylib() +{ + return _dylib; +} + +const bool MachOProxy::isExecutable() +{ + return _executable; +} + +std::map MachOProxy::findDylibInfo(const std::string& path, bool warnOnProblems, bool ignoreUncacheableDylibsInExecutables) { + std::map slices = mapMachOFile( path ); + std::vector errorSlices; + + for ( auto& slice : slices ) { + std::string errorMessage; + verboseLog( "analyzing file '%s'", path.c_str() ); + switch ( archForString( slice.first ).arch ) { + case CPU_TYPE_ARM: + case CPU_TYPE_I386: + errorMessage = slice.second->machoParser>(ignoreUncacheableDylibsInExecutables); + break; + case CPU_TYPE_X86_64: + case CPU_TYPE_ARM64: + errorMessage = slice.second->machoParser>(ignoreUncacheableDylibsInExecutables); + break; + default: + errorMessage = "unsupported arch '" + slice.first + "'"; + break; + } + + if ( !errorMessage.empty() ) { + if ( warnOnProblems ) + warning( "%s (%s)", errorMessage.c_str(), path.c_str() ); + errorSlices.push_back( slice.first ); + } + } + + for ( const auto& slice : errorSlices ) { + slices.erase( slice ); + } + + return slices; +} + +const uint8_t* MachOProxy::getBuffer() { + const uint8_t* p = (uint8_t*)( -1 ); + struct stat stat_buf; + bool rootless; + std::tie(p, stat_buf,rootless) = fileCache.cacheLoad(path); + return p + fatFileOffset; +} + +bool MachOProxy::addAlias( const std::string& alias ) { + if (!has_prefix(alias, "/usr/lib/") && !has_prefix(alias, "/System/Library/")) + return false; + if ( alias != installName && installNameAliases.count( alias ) == 0 ) { + installNameAliases.insert( alias ); + return true; + } + return false; +} diff --git a/interlinked-dylibs/MachOProxy.h b/interlinked-dylibs/MachOProxy.h new file mode 100644 index 0000000..7a7f620 --- /dev/null +++ b/interlinked-dylibs/MachOProxy.h @@ -0,0 +1,66 @@ +// +// DylibProxy.h +// dyld +// +// Created by Louis Gerbarg on 1/27/16. +// +// + +#ifndef MachOProxy_h +#define MachOProxy_h + +#include +#include +#include +#include +#include + +#include + +struct MachOProxy { + MachOProxy(const std::string& p, ino_t i, time_t t, uint32_t o, uint32_t s, bool r) : + path(p), fatFileOffset(o), fileSize(s), lastModTime(t), inode(i), installNameOffsetInTEXT(0), rootlessProtected(r) { + bzero( &uuid, sizeof( uuid_t ) ); + } + + struct Segment { + std::string name; + uint64_t size; + uint64_t sizeOfSections; + uint32_t diskSize; + uint32_t fileOffset; + uint8_t p2align; + uint8_t protection; + }; + + const std::string path; + const uint32_t fatFileOffset; + const uint32_t fileSize; + const time_t lastModTime; + const ino_t inode; + const bool rootlessProtected; + std::string installName; + std::set installNameAliases; + uint32_t installNameOffsetInTEXT; + std::set dependencies; + std::set dependents; + uuid_t uuid; + std::vector segments; + + const uint8_t* getBuffer(); + const bool isDylib(); + const bool isExecutable(); + bool addAlias(const std::string& alias); + + static std::map findDylibInfo(const std::string& path, bool warnOnProblems=false, bool ignoreUncacheableDylibsInExecutables=false); + +private: + bool _dylib; + bool _executable; + + template + std::string machoParser(bool ignoreUncacheableDylibsInExecutables); +}; + + +#endif /* MachOProxy_h */ diff --git a/interlinked-dylibs/Manifest.h b/interlinked-dylibs/Manifest.h new file mode 100644 index 0000000..bface3c --- /dev/null +++ b/interlinked-dylibs/Manifest.h @@ -0,0 +1,208 @@ +// +// Manifest.h +// dyld +// +// Created by Louis Gerbarg on 7/23/15. +// +// + +#ifndef Manifest_h +#define Manifest_h + +#include +#include +#include +#include + +#include +#include + + +struct SharedCache; +struct MachOProxy; +struct Manifest; + +struct Manifest { + struct Project { + std::vector sources; + }; + + struct File { + MachOProxy* proxy; + + File( MachOProxy* P ) : proxy( P ) {} + }; + + struct FileSet { + std::map dylibs; + std::map executables; + }; + + struct Anchor { + std::string installname; + bool required; + + Anchor( const std::string& IN ) : installname( IN ) {} + }; + + struct SegmentInfo { + std::string name; + uint64_t startAddr; + uint64_t endAddr; + }; + + struct SegmentInfoHasher { + std::size_t operator()(const SegmentInfo &x) const { + return std::hash()(x.name) ^ std::hash()(x.startAddr) ^ std::hash()(x.endAddr); + } + }; + + struct CacheInfo { + std::vector regions; + std::string cdHash; + }; + + struct DylibInfo { + bool included; + std::string exclusionInfo; + uuid_t uuid; + std::vector segments; + DylibInfo(void) : included(true) {} + void exclude(const std::string& reason) { + included = false; + exclusionInfo = reason; + } + }; + + struct Results { + std::string failure; + std::map dylibs; + std::vector warnings; + CacheInfo developmentCache; + CacheInfo productionCache; + }; + + struct Architecture { + std::vector anchors; + mutable Results results; + //FIXME: Gross + std::unordered_map> dependents; + + uint64_t hash(void) const { + if (!_hash) { + for (auto& dylib : results.dylibs) { + if (dylib.second.included) { + _hash ^= std::hash()(dylib.first); + //HACK to get some of the UUID into the hash + _hash ^= std::hash()(*(uint64_t *)(&dylib.second.uuid[0])); + } + }; + } + + return _hash; + } + + bool equivalent(const Architecture& O) const { + if (hash() != O.hash()) { + return false; + } + for (auto& dylib : results.dylibs) { + if (dylib.second.included) { + auto Odylib = O.results.dylibs.find(dylib.first); + if (Odylib == O.results.dylibs.end() + || Odylib->second.included == false + || memcmp(&Odylib->second.uuid[0], &dylib.second.uuid[0], sizeof(uuid_t)) != 0) + return false; + } + } + //Iterate over O.results to make sure we included all the same things + for (auto Odylib : O.results.dylibs) { + if (Odylib.second.included) { + auto dylib = results.dylibs.find(Odylib.first); + if (dylib == results.dylibs.end() + || dylib->second.included == false) + return false; + } + } + return true; + } + private: + mutable uint64_t _hash = 0; + }; + + struct Configuration { + std::string platformName; + std::string metabomTag; + std::set metabomExcludeTags; + std::set metabomRestrictTags; + std::set restrictedInstallnames; + std::map architectures; + + uint64_t hash( void ) const { + if (!_hash) { + _hash ^= std::hash()(architectures.size()); + // We want the preliminary info here to make dedupe decisions + for (auto& arch : architectures) { + _hash ^= arch.second.hash(); + }; + } + return _hash; + } + + //Used for dedupe + bool equivalent(const Configuration& O) const { + if (hash() != O.hash()) + return false; + for (const auto& arch : architectures) { + if (O.architectures.count(arch.first) == 0) + return false; + if (!arch.second.equivalent(O.architectures.find(arch.first)->second)) + return false; + } + + return true; + } + private: + mutable uint64_t _hash = 0; + }; + + std::map architectureFiles; + std::map projects; + std::map configurations; + std::string dylibOrderFile; + std::string dirtyDataOrderFile; + std::string metabomFile; + std::string build; + // FIXME every needs to adopt platform string for v5 + std::string platform; + uint32_t manifest_version; + bool normalized; + + Manifest( void ) {} +#if BOM_SUPPORT + Manifest( const std::string& path ); + Manifest( const std::string& path, const std::set& overlays ); +#endif + void write( const std::string& path ); + void canonicalize( void ); + void calculateClosure( bool enforeceRootless ); + void pruneClosure(); + bool sameContentsAsCacheAtPath( const std::string& configuration, const std::string& architecture, + const std::string& path ) const; + MachOProxy* dylibProxy( const std::string& installname, const std::string& arch ); + MachOProxy* removeLargestLeafDylib( const std::string& configuration, const std::string& architecture ); + +private: + void removeDylib( MachOProxy* proxy, const std::string& reason, const std::string& configuration, const std::string& architecture, + std::unordered_set& processedInstallnames ); + File* dylibForInstallName( const std::string& installname, const std::string& arch ); + void calculateClosure( const std::string& configuration, const std::string& architecture); + void pruneClosure(const std::string& configuration, const std::string& architecture); + void canonicalizeDylib( const std::string& installname ); + template + void canonicalizeDylib( const std::string& installname, const uint8_t* p ); + void addImplicitAliases( void ); +}; + + +#endif /* Manifest_h */ diff --git a/interlinked-dylibs/Manifest.mm b/interlinked-dylibs/Manifest.mm new file mode 100644 index 0000000..34e81ec --- /dev/null +++ b/interlinked-dylibs/Manifest.mm @@ -0,0 +1,740 @@ +// +// Manifest.mm +// dyld +// +// Created by Louis Gerbarg on 7/23/15. +// +// + +#if BOM_SUPPORT +extern "C" { +#include +#include +#include +#include +#include +}; +#endif /* BOM_SUPPORT */ + +#include + +#include +#include + +#include "MachOFileAbstraction.hpp" +#include "FileAbstraction.hpp" +#include "Trie.hpp" +#include "Logging.h" + +#include +#include + +#include +#include + +#include "Manifest.h" +#include "dsc_iterator.h" + +#include "mega-dylib-utils.h" + +namespace { + //FIXME this should be in a class + static bool rootless = true; + + static inline NSString* cppToObjStr(const std::string& str) { return [NSString stringWithUTF8String:str.c_str()]; } + +#if BOM_SUPPORT + std::string effectivePath(const std::string source, const std::string target) + { + if (target[0] == '/') + return normalize_absolute_file_path(target); + + std::size_t found = source.find_last_of('/'); + return normalize_absolute_file_path(source.substr(0, found) + "/" + target); + } + + std::string checkSymlink(const std::string path, const std::pair& symlink, const std::set& directories) + { + if (directories.count(symlink.second) == 0) { + if (path == symlink.second) + return symlink.first; + } else { + auto res = std::mismatch(symlink.second.begin(), symlink.second.end(), path.begin()); + if (res.first == symlink.second.end()) { + std::string alias = normalize_absolute_file_path(symlink.first + std::string(res.second, path.end())); + return alias; + } + } + return ""; + } +#endif + } + +#if BOM_SUPPORT + + Manifest::Manifest(const std::string& path) + : Manifest(path, std::set()) + { + } + + Manifest::Manifest(const std::string& path, const std::set& overlays) + { + NSMutableDictionary* manifestDict = [NSMutableDictionary dictionaryWithContentsOfFile:cppToObjStr(path)]; + std::map metabomTagMap; + std::map> metabomExcludeTagMap; + std::map> metabomRestrictedTagMap; + metabomFile = [manifestDict[@"metabomFile"] UTF8String]; + + for (NSString* project in manifestDict[@"projects"]) { + for (NSString* source in manifestDict[@"projects"][project]) { + projects[[project UTF8String]].sources.push_back([source UTF8String]); + } + } + + for (NSString* configuration in manifestDict[@"configurations"]) { + std::string configStr = [configuration UTF8String]; + std::string configTag = [manifestDict[@"configurations"][configuration][@"metabomTag"] UTF8String]; + metabomTagMap[configTag] = configStr; + + if (manifestDict[@"configurations"][configuration][@"metabomExcludeTags"]) { + for (NSString* excludeTag in manifestDict[@"configurations"][configuration][@"metabomExcludeTags"]) { + metabomExcludeTagMap[configStr].insert([excludeTag UTF8String]); + configurations[configStr].metabomExcludeTags.insert([excludeTag UTF8String]); + } + } + + if (manifestDict[@"configurations"][configuration][@"metabomRestrictTags"]) { + for (NSString* restrictTag in manifestDict[@"configurations"][configuration][@"metabomRestrictTags"]) { + metabomRestrictedTagMap[configStr].insert([restrictTag UTF8String]); + configurations[configStr].metabomRestrictTags.insert([restrictTag UTF8String]); + } + } + + configurations[configStr].metabomTag = configTag; + configurations[configStr].platformName = + [manifestDict[@"configurations"][configuration][@"platformName"] UTF8String]; + } + + manifest_version = [manifestDict[@"manifest-version"] unsignedIntValue]; + build = [manifestDict[@"build"] UTF8String]; + if (manifestDict[@"dylibOrderFile"]) { + dylibOrderFile = [manifestDict[@"dylibOrderFile"] UTF8String]; + } + if (manifestDict[@"dirtyDataOrderFile"]) { + dirtyDataOrderFile = [manifestDict[@"dirtyDataOrderFile"] UTF8String]; + } + + auto metabom = MBMetabomOpen(metabomFile.c_str(), false); + auto metabomEnumerator = MBIteratorNewWithPath(metabom, ".", ""); + MBEntry entry; + + std::map symlinks; + std::set directories; + + // FIXME error handling (NULL metabom) + + while ((entry = MBIteratorNext(metabomEnumerator))) { + auto fsObject = MBEntryGetFSObject(entry); + std::string entryPath = BOMFSObjectPathName(fsObject); + if (entryPath[0] == '.') { + entryPath.erase(0, 1); + } + auto entryType = BOMFSObjectType(fsObject); + + switch (entryType) { + case BOMFileType: { + MBTag tag; + auto tagCount = MBEntryGetNumberOfProjectTags(entry); + + if (!BOMFSObjectIsBinaryObject(fsObject)) + break; + + if (tagCount == 0) { + break; + } else if (tagCount == 1) { + MBEntryGetProjectTags(entry, &tag); + } else { + MBTag* tags = (MBTag*)malloc(sizeof(MBTag) * tagCount); + MBEntryGetProjectTags(entry, tags); + + //Sigh, we can have duplicate entries for the same tag, so build a set to work with + std::set tagStrs; + std::map tagStrMap; + for (auto i = 0; i < tagCount; ++i) { + tagStrs.insert(MBMetabomGetProjectForTag(metabom, tags[i])); + tagStrMap.insert(std::make_pair(MBMetabomGetProjectForTag(metabom, tags[i]), tags[i])); + } + + if (tagStrs.size() > 1) { + std::string projects; + for (const auto& tagStr : tagStrs) { + if (!projects.empty()) + projects += ", "; + + projects += "'" + tagStr + "'"; + } + warning("Bom entry '%s' is claimed by multiple projects: %s, taking first entry", entryPath.c_str(), projects.c_str()); + } + tag = tagStrMap[*tagStrs.begin()]; + free(tags); + } + + std::string projectName = MBMetabomGetProjectForTag(metabom, tag); + + // FIXME we need to actually walk down the searchpaths + auto project = projects.find(projectName); + if (project == projects.end()) + break; + if (project->second.sources.size() == 0) + break; + std::string projectPath = project->second.sources[0]; + std::map proxies; + + for (const auto& overlay : overlays) { + proxies = MachOProxy::findDylibInfo(overlay + "/" + entryPath); + if (proxies.size() > 0) + break; + } + + if (proxies.size() == 0) { + proxies = MachOProxy::findDylibInfo(projectPath + "/" + entryPath); + } + + for (auto& proxy : proxies) { + assert(proxy.second != nullptr); + if (proxy.second->isExecutable()) { + architectureFiles[proxy.first].executables.insert(std::make_pair(entryPath, File(proxy.second))); + continue; + } + if (!proxy.second->isDylib()) + continue; + assert(proxy.second->installName != ""); + proxy.second->addAlias(entryPath); + architectureFiles[proxy.first].dylibs.insert( + std::make_pair(proxy.second->installName, File(proxy.second))); + auto tagCount = MBEntryGetNumberOfPackageTags(entry); + MBTag* tags = (MBTag*)malloc(sizeof(MBTag) * tagCount); + MBEntryGetPackageTags(entry, tags); + std::set tagStrs; + + for (auto i = 0; i < tagCount; ++i) { + tagStrs.insert(MBMetabomGetPackageForTag(metabom, tags[i])); + } + + for (const auto& tagStr : tagStrs) { + // Does the configuration exist + auto configuration = metabomTagMap.find(tagStr); + if (configuration == metabomTagMap.end()) + continue; + + auto restrictions = metabomRestrictedTagMap.find(configuration->second); + if (restrictions != metabomRestrictedTagMap.end() && !is_disjoint(restrictions->second, tagStrs)) { + configurations[configuration->second].restrictedInstallnames.insert(proxy.second->installName); + } + // Is the configuration excluded + auto exclusions = metabomExcludeTagMap.find(configuration->second); + if (exclusions != metabomExcludeTagMap.end() && !is_disjoint(exclusions->second, tagStrs)) + continue; + + if ([manifestDict[@"configurations"][cppToObjStr(configuration->second)][@"architectures"] + containsObject:cppToObjStr(proxy.first)]) { + configurations[configuration->second.c_str()].architectures[proxy.first].anchors.push_back( + proxy.second->installName); + } + } + + free(tags); + } + } break; + case BOMSymlinkType: { + if (!has_prefix(entryPath, "/usr/lib/") && !has_prefix(entryPath, "/System/Library/")) + break; + const char* target = BOMFSObjectSymlinkTarget(fsObject); + if (target) { + symlinks[entryPath] = effectivePath(entryPath, target); + } + } break; + case BOMDirectoryType: { + if (!has_prefix(entryPath, "/usr/lib/") && !has_prefix(entryPath, "/System/Library/")) + break; + directories.insert(entryPath); + } break; + default: + break; + } + } + + MBIteratorFree(metabomEnumerator); + MBMetabomFree(metabom); + + dispatch_queue_t symlinkQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, NULL); + dispatch_group_t symlinkGroup = dispatch_group_create(); + + for (auto& fileSet : architectureFiles) { + cacheBuilderDispatchGroupAsync(symlinkGroup, symlinkQueue, [&] { + for (auto& file : fileSet.second.dylibs) { + bool aliasAdded = true; + auto proxy = file.second.proxy; + + while (aliasAdded) { + aliasAdded = false; + + for (auto& symlink : symlinks) { + std::set newAliases; + auto alias = checkSymlink(proxy->installName, symlink, directories); + if (alias != "") { + newAliases.insert(alias); + } + + for (auto& existingAlias : proxy->installNameAliases) { + alias = checkSymlink(existingAlias, symlink, directories); + if (alias != "") { + newAliases.insert(alias); + } + } + + for (auto& alias : newAliases) { + if (proxy->addAlias(alias)) { + aliasAdded = true; + } + } + } + } + } + }); + } + dispatch_group_wait(symlinkGroup, DISPATCH_TIME_FOREVER); + + for (auto& fileSet : architectureFiles) { + for (auto& file : fileSet.second.dylibs) { + auto proxy = file.second.proxy; + + for (const auto& dependency : proxy->dependencies) { + auto dependencyProxy = dylibProxy(dependency, fileSet.first); + if (dependencyProxy == nullptr) + break; + + dependencyProxy->dependents.insert(proxy->installName); + } + } + } +} +#endif + +void Manifest::calculateClosure( bool enforceRootless ) { + rootless = enforceRootless; + + for ( auto& config : configurations ) { + for ( auto& arch : config.second.architectures ) { + calculateClosure( config.first, arch.first ); + } + } +} + +Manifest::File* Manifest::dylibForInstallName( const std::string& installname, const std::string& arch ) { + auto archIter = architectureFiles.find( arch ); + if ( archIter == architectureFiles.end() ) return nullptr; + + auto& files = archIter->second.dylibs; + auto dylibIterator = files.find( installname ); + + if ( dylibIterator != files.end() ) return &dylibIterator->second; + + for ( auto& candidate : files ) { + if ( candidate.second.proxy->installNameAliases.count( installname ) > 0 ) { + dylibIterator = files.find( candidate.first ); + return &dylibIterator->second; + } + } + // Check if we can fallback to an interworkable architecture + std::string fallbackArchStr = fallbackArchStringForArchString( arch ); + if ( !fallbackArchStr.empty() ) { + return dylibForInstallName( installname, fallbackArchStr ); + } + + return nullptr; +} + + +MachOProxy* Manifest::dylibProxy( const std::string& installname, const std::string& arch ) { + auto dylib = dylibForInstallName( installname, arch ); + + if ( dylib != nullptr ) { + assert( dylib->proxy != nullptr ); + return dylib->proxy; + } + + return nullptr; +} + +bool +Manifest::sameContentsAsCacheAtPath(const std::string& configuration, const std::string& architecture, const std::string& path) const { + __block std::set>> cacheDylibs; + std::set>> manifestDylibs; + struct stat statbuf; + if ( ::stat(path.c_str(), &statbuf) == -1 ) { + // don't warn if there is no existing cache file + if ( errno != ENOENT ) + warning("stat() failed for dyld shared cache at %s, errno=%d", path.c_str(), errno); + return false; + } + + int cache_fd = ::open(path.c_str(), O_RDONLY); + if ( cache_fd < 0 ) { + warning("open() failed for shared cache file at %s, errno=%d", path.c_str(), errno); + return false; + } + + const void *mappedCache = ::mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, cache_fd, 0); + if (mappedCache == MAP_FAILED) { + ::close(cache_fd); + warning("mmap() for shared cache at %s failed, errno=%d", path.c_str(), errno); + return false; + } + ::close(cache_fd); + + if (configurations.count(configuration) == 0 + || configurations.find(configuration)->second.architectures.count(architecture) == 0) + return false; + + for (auto& dylib : configurations.find(configuration)->second.architectures.find(architecture)->second.results.dylibs) { + if ( dylib.second.included == true) { + std::pair> dylibPair; + dylibPair.first = dylib.first; + bcopy((const void *)&dylib.second.uuid[0], &dylibPair.second[0], sizeof(uuid_t)); + manifestDylibs.insert(dylibPair); + auto file = architectureFiles.find(architecture)->second.dylibs.find(dylib.first); + if (file != architectureFiles.find(architecture)->second.dylibs.end()) { + for ( auto& alias : file->second.proxy->installNameAliases ) { + std::pair> aliasPair; + aliasPair.first = alias; + bcopy((const void *)&dylib.second.uuid[0], &aliasPair.second[0], sizeof(uuid_t)); + manifestDylibs.insert(aliasPair); + } + } + } + } + + (void)dyld_shared_cache_iterate(mappedCache, (uint32_t)statbuf.st_size, + ^(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo){ + std::pair> dylibPair; + dylibPair.first = dylibInfo->path; + bcopy((const void *)&dylibInfo->uuid[0], &dylibPair.second[0], sizeof(uuid_t)); + cacheDylibs.insert(dylibPair); + }); + + return (manifestDylibs == cacheDylibs); +} + +void Manifest::removeDylib( MachOProxy* proxy, const std::string& reason, const std::string& configuration, + const std::string& architecture, std::unordered_set& processedInstallnames ) { + auto configIter = configurations.find( configuration ); + if ( configIter == configurations.end() ) return; + auto archIter = configIter->second.architectures.find( architecture ); + if ( archIter == configIter->second.architectures.end() ) return; + auto& archManifest = archIter->second; + + if ( archManifest.results.dylibs.count( proxy->installName ) == 0 ) { + bcopy( &proxy->uuid[0], &archManifest.results.dylibs[proxy->installName].uuid[0], sizeof( uuid_t ) ); + processedInstallnames.insert( proxy->installName ); + } + archManifest.results.dylibs[proxy->installName].exclude( reason ); + + processedInstallnames.insert( proxy->installName ); + for ( auto& alias : proxy->installNameAliases ) { + processedInstallnames.insert( alias ); + } + + for ( const auto& dependent : proxy->dependents ) { + auto dependentProxy = dylibProxy( dependent, architecture ); + auto dependentResultIter = archManifest.results.dylibs.find( dependentProxy->installName ); + if ( dependentProxy && + ( dependentResultIter == archManifest.results.dylibs.end() || dependentResultIter->second.included == true ) ) { + removeDylib( dependentProxy, "Missing dependency: " + proxy->installName, configuration, architecture, + processedInstallnames ); + } + } +} + +MachOProxy* Manifest::removeLargestLeafDylib( const std::string& configuration, const std::string& architecture ) { + std::set activeInstallnames; + std::set leafDylibs; + + auto configIter = configurations.find( configuration ); + if ( configIter == configurations.end() ) terminate( "Internal error" ); + ; + auto archIter = configIter->second.architectures.find( architecture ); + if ( archIter == configIter->second.architectures.end() ) terminate( "Internal error" ); + ; + for ( const auto& dylibInfo : archIter->second.results.dylibs ) { + if ( dylibInfo.second.included ) { + activeInstallnames.insert( dylibInfo.first ); + } + } + for ( const auto& installname : activeInstallnames ) { + auto dylib = dylibProxy( installname, architecture ); + bool dependents = false; + for ( const auto& depedent : dylib->dependents ) { + if ( depedent != dylib->installName && activeInstallnames.count( depedent ) ) { + dependents = true; + break; + } + } + if ( !dependents ) { + leafDylibs.insert( dylib ); + } + } + if ( leafDylibs.empty() ) { + terminate( "No leaf dylibs to evict" ); + } + MachOProxy* largestLeafDylib = nullptr; + for ( const auto& dylib : leafDylibs ) { + if ( largestLeafDylib == nullptr || dylib->fileSize > largestLeafDylib->fileSize ) { + largestLeafDylib = dylib; + } + } + std::unordered_set empty; + removeDylib( largestLeafDylib, "VM space overflow", configuration, architecture, empty ); + return largestLeafDylib; +} + +static void recursiveInvalidate(const std::string& invalidName, std::unordered_map>& usesOf, std::unordered_set& unusableInstallNames) +{ + if ( unusableInstallNames.count(invalidName) ) + return; + unusableInstallNames.insert(invalidName); + for (const std::string& name : usesOf[invalidName] ) { + recursiveInvalidate(name, usesOf, unusableInstallNames); + } +} + +void Manifest::pruneClosure() +{ + for (auto& config : configurations) { + for (auto& arch : config.second.architectures) { + pruneClosure(config.first, arch.first); + } + } +} + +void Manifest::pruneClosure(const std::string& configuration, const std::string& architecture) +{ + auto configIter = configurations.find(configuration); + if ( configIter == configurations.end() ) + return; + auto archIter = configIter->second.architectures.find(architecture); + if ( archIter == configIter->second.architectures.end() ) + return; + auto& archManifest = archIter->second; + + // build reverse dependency map and list of excluded dylibs + std::unordered_map> reverseDep; + std::unordered_set unusableStart; + for (const auto& dylib : archManifest.results.dylibs) { + const std::string dylibInstallName = dylib.first; + if ( dylib.second.included ) { + if ( MachOProxy* proxy = dylibProxy(dylibInstallName, architecture) ) { + for (const std::string& dependentPath : proxy->dependencies) { + reverseDep[dependentPath].insert(dylibInstallName); + } + } + } + else { + unusableStart.insert(dylibInstallName); + } + } + + // mark unusable, all dylibs depending on the initially unusable dylibs + std::unordered_set newUnusable; + for (const std::string& unusable : unusableStart) { + recursiveInvalidate(unusable, reverseDep, newUnusable); + } + + // remove unusable dylibs from manifest + std::unordered_set dummy; + for (const std::string& unusable : newUnusable) { + if ( MachOProxy* proxy = dylibProxy(unusable, architecture) ) + removeDylib(proxy, "Missing dependency: " + unusable, configuration, architecture, dummy); + warning("can't use: %s because dependent dylib cannot be used", unusable.c_str()); + } +} + +void Manifest::calculateClosure( const std::string& configuration, const std::string& architecture ) { + auto& archManifest = configurations[configuration].architectures[architecture]; + auto archFileIter = architectureFiles.find( architecture ); + assert( archFileIter != architectureFiles.end() ); + auto files = archFileIter->second.dylibs; + + std::unordered_set newInstallnames; + + for ( auto& anchor : archManifest.anchors ) { + newInstallnames.insert(anchor.installname); + } + + std::unordered_set processedInstallnames; + + while (!newInstallnames.empty()) { + std::unordered_set installnamesToProcess = newInstallnames; + newInstallnames.clear(); + + for (const std::string& installname : installnamesToProcess) { + if (processedInstallnames.count(installname) > 0) { + continue; + } + + auto proxy = dylibProxy( installname, architecture ); + + if ( proxy == nullptr ) { + // No path + archManifest.results.dylibs[installname].exclude( "Could not find file for install name" ); + warning("Could not find file for install name (%s)", installname.c_str()); + continue; + } + + if (configurations[configuration].restrictedInstallnames.count(installname) != 0) { + removeDylib(proxy, "Dylib '" + installname + "' removed due to explict restriction", configuration, architecture, + processedInstallnames); + continue; + } + // Validate we have all are depedencies + for ( const auto& dependency : proxy->dependencies ) { + if ( !dylibProxy( dependency, architecture ) ) { + removeDylib( proxy, "Missing dependency: " + dependency, configuration, architecture, + processedInstallnames ); + break; + } + } + + // assert(info->installName == installname); + if ( archManifest.results.dylibs.count( proxy->installName ) == 0 ) { + bcopy( &proxy->uuid[0], &archManifest.results.dylibs[proxy->installName].uuid[0], sizeof( uuid_t ) ); + processedInstallnames.insert( proxy->installName ); + + auto fileIter = files.find( proxy->installName ); + if ( fileIter != files.end() ) { + for ( auto& aliasName : fileIter->second.proxy->installNameAliases ) { + processedInstallnames.insert( aliasName ); + } + } + } + + for ( const auto& dependency : proxy->dependencies ) { + if ( processedInstallnames.count( dependency ) == 0 ) { + newInstallnames.insert( dependency ); + } + } + } + } +} + +void Manifest::write( const std::string& path ) { + if ( path.empty() ) return; + + NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init]; + NSMutableDictionary* projectDict = [[NSMutableDictionary alloc] init]; + NSMutableDictionary* configurationsDict = [[NSMutableDictionary alloc] init]; + NSMutableDictionary* resultsDict = [[NSMutableDictionary alloc] init]; + + cacheDict[@"manifest-version"] = @( manifest_version ); + cacheDict[@"build"] = cppToObjStr( build ); + cacheDict[@"dylibOrderFile"] = cppToObjStr( dylibOrderFile ); + cacheDict[@"dirtyDataOrderFile"] = cppToObjStr( dirtyDataOrderFile ); + cacheDict[@"metabomFile"] = cppToObjStr( metabomFile ); + + cacheDict[@"projects"] = projectDict; + cacheDict[@"results"] = resultsDict; + cacheDict[@"configurations"] = configurationsDict; + + for ( const auto& project : projects ) { + NSMutableArray* sources = [[NSMutableArray alloc] init]; + + for ( const auto& source : project.second.sources ) { + [sources addObject:cppToObjStr( source )]; + } + + projectDict[cppToObjStr( project.first )] = sources; + } + + for ( auto& configuration : configurations ) { + NSMutableArray* archArray = [[NSMutableArray alloc] init]; + for ( auto& arch : configuration.second.architectures ) { + [archArray addObject:cppToObjStr( arch.first )]; + } + + NSMutableArray* excludeTags = [[NSMutableArray alloc] init]; + for ( const auto& excludeTag : configuration.second.metabomExcludeTags ) { + [excludeTags addObject:cppToObjStr( excludeTag )]; + } + + configurationsDict[cppToObjStr( configuration.first )] = @{ + @"platformName" : cppToObjStr( configuration.second.platformName ), + @"metabomTag" : cppToObjStr( configuration.second.metabomTag ), + @"metabomExcludeTags" : excludeTags, + @"architectures" : archArray + }; + } + + for ( auto& configuration : configurations ) { + NSMutableDictionary* archResultsDict = [[NSMutableDictionary alloc] init]; + for ( auto& arch : configuration.second.architectures ) { + NSMutableDictionary* dylibsDict = [[NSMutableDictionary alloc] init]; + NSMutableArray* warningsArray = [[NSMutableArray alloc] init]; + NSMutableDictionary* devRegionsDict = [[NSMutableDictionary alloc] init]; + NSMutableDictionary* prodRegionsDict = [[NSMutableDictionary alloc] init]; + NSString *prodCDHash = cppToObjStr(arch.second.results.productionCache.cdHash); + NSString *devCDHash = cppToObjStr(arch.second.results.developmentCache.cdHash); + + for ( auto& dylib : arch.second.results.dylibs ) { + NSMutableDictionary* dylibDict = [[NSMutableDictionary alloc] init]; + if ( dylib.second.included ) { + NSMutableDictionary* segments = [[NSMutableDictionary alloc] init]; + dylibDict[@"included"] = @YES; + for ( auto& segment : dylib.second.segments ) { + segments[cppToObjStr( segment.name )] = + @{ @"startAddr" : @( segment.startAddr ), + @"endAddr" : @( segment.endAddr ) }; + } + dylibDict[@"segments"] = segments; + } else { + dylibDict[@"included"] = @NO; + dylibDict[@"exclusionInfo"] = cppToObjStr(dylib.second.exclusionInfo); + } + dylibsDict[cppToObjStr( dylib.first )] = dylibDict; + } + + for ( auto& region : arch.second.results.developmentCache.regions ) { + devRegionsDict[cppToObjStr( region.name )] = + @{ @"startAddr" : @( region.startAddr ), + @"endAddr" : @( region.endAddr ) }; + } + + for ( auto& region : arch.second.results.productionCache.regions ) { + prodRegionsDict[cppToObjStr( region.name )] = + @{ @"startAddr" : @( region.startAddr ), + @"endAddr" : @( region.endAddr ) }; + } + + for ( auto& warning : arch.second.results.warnings ) { + [warningsArray addObject:cppToObjStr( warning )]; + } + + BOOL built = arch.second.results.failure.empty(); + archResultsDict[cppToObjStr( arch.first )] = @{ + @"dylibs" : dylibsDict, + @"built" : @( built ), + @"failure" : cppToObjStr( arch.second.results.failure ), + @"productionCache" : @{@"cdhash" : prodCDHash, @"regions" : prodRegionsDict}, + @"developmentCache" : @{@"cdhash" : devCDHash, @"regions" : devRegionsDict}, + @"warnings" : warningsArray + }; + } + resultsDict[cppToObjStr( configuration.first )] = archResultsDict; + } + + NSError* error = nil; + NSData *outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict + format:NSPropertyListBinaryFormat_v1_0 + options:0 + error:&error]; + (void)[outData writeToFile:cppToObjStr(path) atomically:YES]; +} diff --git a/interlinked-dylibs/MultiCacheBuilder.h b/interlinked-dylibs/MultiCacheBuilder.h new file mode 100644 index 0000000..9bba12d --- /dev/null +++ b/interlinked-dylibs/MultiCacheBuilder.h @@ -0,0 +1,48 @@ +// +// MultiCacheBuilder.h +// dyld +// +// Created by Louis Gerbarg on 6/16/15. +// +// + +#ifndef MultiCacheBuilder_h +#define MultiCacheBuilder_h + +#include + +#include "Manifest.h" + +#include "mega-dylib-utils.h" +#include + +typedef struct _BOMBom* BOMBom; + +struct MultiCacheBuilder { + dispatch_semaphore_t _concurrencyLimitingSemaphore; + dispatch_semaphore_t _writeLimitingSemaphore; + dispatch_queue_t _writeQueue; + dispatch_group_t _writeGroup; + dispatch_queue_t _buildQueue; + Manifest& _manifest; + + uint64_t _filesWritten = 0; + uint64_t _bytesWritten = 0; + const bool _bniMode; + const bool _skipWrites; + const bool _skipBuilds; + const bool _buildRoot; + const bool _enforceRootless; + + MultiCacheBuilder(Manifest& manifest, bool BNI = false, bool SW = false, bool buildRoot = false, bool skipBuilds = false, bool enforceRootless = false); + + void buildCaches(std::string masterDstRoot); + + void logStats(); +private: + void runOnManifestConcurrently(std::function lambda); + void buildCache(const std::string cachePath, const std::set configurations, const std::string architecture, bool development); + void write_cache(std::string cachePath, const std::set& configurations, const std::string& architecture, std::shared_ptr cache, bool developmentCache); +}; + +#endif /* MultiCacheBuilder_h */ diff --git a/interlinked-dylibs/MultiCacheBuilder.mm b/interlinked-dylibs/MultiCacheBuilder.mm new file mode 100644 index 0000000..d1c7bab --- /dev/null +++ b/interlinked-dylibs/MultiCacheBuilder.mm @@ -0,0 +1,441 @@ +// +// SharedCacheBuilder.m +// dyld +// +// Created by Louis Gerbarg on 6/15/15. +// +// + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include // std::setfill, std::setw +#include "mega-dylib-utils.h" +#include "Logging.h" + +#include "MultiCacheBuilder.h" + + +namespace { +#if BOM_SUPPORT +void insertDirInBom( const std::string& path, const std::string& name, BOMBom bom ) { + std::string fullPath = path + "/" + name; + BOMFSObject fso = BOMFSObjectNew( BOMDirectoryType ); + BOMFSObjectSetFlags( fso, B_PATHONLY ); + BOMFSObjectSetPathName( fso, fullPath.c_str(), true ); + BOMFSObjectSetShortName( fso, name.c_str(), true ); + (void)BOMBomInsertFSObject( bom, fso, false ); + BOMFSObjectFree( fso ); +} + +void insertFileInBom( const std::string& path, const std::string& name, BOMBom bom ) { + std::string fullPath = path + "/" + name; + BOMFSObject fso = BOMFSObjectNew( BOMFileType ); + BOMFSObjectSetFlags( fso, B_PATHONLY ); + BOMFSObjectSetPathName( fso, fullPath.c_str(), true ); + BOMFSObjectSetShortName( fso, name.c_str(), true ); + (void)BOMBomInsertFSObject( bom, fso, false ); + BOMFSObjectFree( fso ); +} + +void insertCacheDirInBom( BOMBom bom ) { + BOMFSObject fso = BOMFSObjectNew( BOMDirectoryType ); + BOMFSObjectSetFlags( fso, B_PATHONLY ); + BOMFSObjectSetPathName( fso, ".", true ); + BOMFSObjectSetShortName( fso, ".", true ); + (void)BOMBomInsertFSObject( bom, fso, false ); + BOMFSObjectFree( fso ); + insertDirInBom( ".", "System", bom ); + insertDirInBom( "./System", "Library", bom ); + insertDirInBom( "./System/Library", "Caches", bom ); + insertDirInBom( "./System/Library/Caches", "com.apple.dyld", bom ); +} +#endif /* BOM_SUPPORT */ +} + +MultiCacheBuilder::MultiCacheBuilder(Manifest& manifest, bool BNI, bool SW, bool buildRoot, bool skipBuilds, bool enforceRootles) + : _manifest(manifest), _bniMode(BNI), _skipWrites(SW), _buildRoot(buildRoot), _skipBuilds(skipBuilds), _enforceRootless(enforceRootles), + _writeQueue(dispatch_queue_create("com.apple.dyld.cache.writeout", + dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, + QOS_CLASS_USER_INITIATED, 0))), + _writeGroup(dispatch_group_create()), + _buildQueue(dispatch_queue_create("com.apple.dyld.cache.multi-build", DISPATCH_QUEUE_CONCURRENT)) { + uint64_t thread_count; + uint64_t ram_size; + + size_t len = sizeof(thread_count); + sysctlbyname ("hw.logicalcpu",&thread_count,&len,NULL,0); + len = sizeof(ram_size); + sysctlbyname ("hw.memsize",&ram_size,&len,NULL,0); + + uint64_t buildCount = MIN((ram_size/(1024*1024*1024)/2), thread_count); + uint64_t writerCount = MAX((ram_size/((uint64_t)2*1024*1024*1024)) - buildCount, 1); + + _buildQueue = dispatch_queue_create("com.apple.dyld.cache.build", DISPATCH_QUEUE_CONCURRENT); + _concurrencyLimitingSemaphore = dispatch_semaphore_create(buildCount); + _writeLimitingSemaphore = dispatch_semaphore_create(writerCount); + + if ( _bniMode ) { + log("Running: %llu threads", buildCount); + log("Queuing: %llu writers", writerCount); + } +} + +void MultiCacheBuilder::write_cache(std::string cachePath, const std::set& configurations, const std::string& architecture, std::shared_ptr cache, bool developmentCache) +{ + //FIXME + dispatch_semaphore_wait(_writeLimitingSemaphore, DISPATCH_TIME_FOREVER); + dispatch_group_enter(_writeGroup); + cacheBuilderDispatchAsync(_writeQueue, [=] { + if (!_skipWrites) { + verboseLog("Queuing write out: %s", cachePath.c_str()); + + //Turn off file caching since we won't read it back + //(void)fcntl(fd, F_NOCACHE, 1); + // We should do this after the cache write, but that would involve copying the path string + std::string tempPath = cachePath; + cache->writeCacheMapFile(cachePath + ".map"); + char tempEXT[] = ".XXXXXX"; + mktemp(tempEXT); + tempPath += tempEXT; + + int fd = ::open(tempPath.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd == -1) { + dispatch_group_leave(_writeGroup); + dispatch_semaphore_signal(_writeLimitingSemaphore); + terminate("can't create temp file for %s, errnor=%d (%s)", cachePath.c_str(), errno, strerror(errno)); + } + + if (isProtectedBySIP(tempPath, fd) != _enforceRootless) { + ::close(fd); + ::unlink(tempPath.c_str()); + dispatch_group_leave(_writeGroup); + dispatch_semaphore_signal(_writeLimitingSemaphore); + terminate("SIP protection of output cache file changed (%s)", cachePath.c_str()); + } + + ssize_t writtenSize = pwrite(fd, cache->buffer().get(), cache->fileSize(), 0); + if (writtenSize != cache->fileSize()) { + ::close(fd); + ::unlink(tempPath.c_str()); + dispatch_group_leave(_writeGroup); + dispatch_semaphore_signal(_writeLimitingSemaphore); + terminate("write() failure creating cache file, requested %lld, wrote %ld, errno=%d (%s)", cache->fileSize(), writtenSize, errno, strerror(errno)); + } + + ::close(fd); + + if (rename(tempPath.c_str(), cachePath.c_str()) != 0) { + dispatch_group_leave(_writeGroup); + dispatch_semaphore_signal(_writeLimitingSemaphore); + terminate("move() failure creating cache file, errno=%d (%s)", errno, strerror(errno)); + } + if (_bniMode) + log("Wrote out: %s", cachePath.c_str()); + } else { + log("Skipped: %s", cachePath.c_str()); + } + _filesWritten++; + _bytesWritten += cache->fileSize(); + dispatch_group_leave(_writeGroup); + dispatch_semaphore_signal(_writeLimitingSemaphore); + }); +} + +//FIXME (make development a type) +void MultiCacheBuilder::buildCache(const std::string cachePath, const std::set configurations, const std::string architecture, bool development) +{ + auto& configResults = _manifest.configurations[*configurations.begin()].architectures[architecture].results.dylibs; + + if ( _skipBuilds ) { + log( "Build Skipped" ); + + for ( auto& config : configurations ) { + for ( auto& dylib : configResults ) { + _manifest.configurations[config].architectures[architecture].results.dylibs[dylib.first].exclude( + "All dylibs excluded" ); + } + } + return; + } + + Manifest::Architecture arch; + std::vector> dylibs; + std::vector emptyList; + std::shared_ptr cache = std::make_shared(_manifest, *configurations.begin(), architecture); + + for (auto& config : configurations) { + auto& results = _manifest.configurations[config].architectures[architecture].results.dylibs; + + for (auto& dylib : configResults) { + if (dylib.second.included == false + && results.count(dylib.first) + && results[dylib.first].included == true) { + results[dylib.first].exclude(dylib.second.exclusionInfo); + } + } + } + + if (development) { + cache->buildForDevelopment(cachePath); + } else { + cache->buildForProduction(cachePath); + } + + std::vector regionStartAddresses; + std::vector regionSizes; + std::vector regionFileOffsets; + + cache->forEachRegion([&] (void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + regionStartAddresses.push_back(vmAddr); + regionSizes.push_back(size); + regionFileOffsets.push_back((uint8_t*)content - (uint8_t*)cache->buffer().get()); + const char* prot = "RW"; + if ( permissions == (VM_PROT_EXECUTE|VM_PROT_READ) ) + prot = "EX"; + else if ( permissions == VM_PROT_READ ) + prot = "RO"; + for (auto& config : configurations) { + if (development) { + _manifest.configurations[config].architectures[architecture].results.developmentCache.regions.push_back({prot, vmAddr,vmAddr+size }); + } else { + _manifest.configurations[config].architectures[architecture].results.productionCache.regions.push_back({prot, vmAddr,vmAddr+size }); + } + } + }); + + cache->forEachImage([&](const void* machHeader, const char* installName, time_t mtime, + ino_t inode, const std::vector& segments) { + for (auto& seg : segments) { + uint64_t vmAddr = 0; + for (int i=0; i < regionSizes.size(); ++i) { + if ( (seg.fileOffset >= regionFileOffsets[i]) && (seg.fileOffset < (regionFileOffsets[i]+regionSizes[i])) ) { + vmAddr = regionStartAddresses[i] + seg.fileOffset - regionFileOffsets[i]; + } + } + for (auto& config : configurations) { + _manifest.configurations[config].architectures[architecture].results.dylibs[installName].segments.push_back({seg.name, vmAddr, vmAddr+seg.size}); + if (_manifest.configurations[config].architectures[architecture].results.dylibs[installName].segments.size() == 0) { + warning("Attempting to write info for excluded dylib"); + _manifest.configurations[config].architectures[architecture].results.dylibs[installName].exclude("Internal Error"); + } + } + } + }); + if (development) { + verboseLog("developement cache size = %llu", cache->fileSize()); + } else { + verboseLog("production cache size = %llu", cache->fileSize()); + } + if ( cache->vmSize()+align(cache->vmSize()/200, sharedRegionRegionAlignment(archForString(architecture))) > sharedRegionRegionSize(archForString(architecture))) { + warning("shared cache will not fit in shared regions address space. Overflow amount: %llu", + cache->vmSize() + align(cache->vmSize() / 200, sharedRegionRegionAlignment(archForString(architecture))) - sharedRegionRegionSize(archForString(architecture))); + return; + } + write_cache(cachePath, configurations, architecture, cache, development); + for (auto& config : configurations) { + if (development) { + _manifest.configurations[config].architectures[architecture].results.developmentCache.cdHash = cache->cdHashString(); + } + else { + _manifest.configurations[config].architectures[architecture].results.productionCache.cdHash = cache->cdHashString(); + } + } +} + +void MultiCacheBuilder::runOnManifestConcurrently(std::function lambda) +{ + dispatch_group_t runGroup = dispatch_group_create(); + for (auto& config : _manifest.configurations) { + for (auto& architecture : config.second.architectures) { + dispatch_semaphore_wait(_concurrencyLimitingSemaphore, DISPATCH_TIME_FOREVER); + cacheBuilderDispatchGroupAsync(runGroup, _buildQueue, [&] { + WarningTargets targets; + targets.first = &_manifest; + targets.second.insert(std::make_pair(config.first, architecture.first)); + auto ctx = std::make_shared(config.first + "/" + architecture.first, targets); + setLoggingContext(ctx); + lambda(config.first, architecture.first); + dispatch_semaphore_signal(_concurrencyLimitingSemaphore); + }); + } + } + + dispatch_group_wait(runGroup, DISPATCH_TIME_FOREVER); +} + +void MultiCacheBuilder::buildCaches(std::string masterDstRoot) { + if (_bniMode) { + std::vector> dedupedCacheSets; + for (auto& config : _manifest.configurations) { + bool dupeFound = false; + + for (auto& cacheSet : dedupedCacheSets) { + if (config.second.equivalent(_manifest.configurations[*cacheSet.begin()])) { + cacheSet.insert(config.first); + dupeFound = true; + break; + } + } + + if (!dupeFound) { + std::set temp; + temp.insert(config.first); + dedupedCacheSets.push_back(temp); + } + } + + for (auto& cacheSet : dedupedCacheSets) { + //FIXME we may want to consider moving to hashes of UUID sets + std::string setName; + + for (auto &archName : cacheSet) { + if (!setName.empty()) { + setName += "|"; + } + setName += archName; + } + + std::stringstream fileNameStream; + std::array digest = {0}; + CC_SHA1(setName.c_str(), (unsigned int)setName.length(), &digest[0]); + + fileNameStream << std::hex << std::uppercase << std::setfill( '0' ); + for( int c : digest ) { + fileNameStream << std::setw( 2 ) << c; + } + + std::string fileName(fileNameStream.str()); + + for (auto& config : cacheSet) { + if (!_skipWrites) { + int err = symlink(("DedupedConfigs/" + fileName).c_str(), (masterDstRoot + "/" + config).c_str()); + if (err) { + warning("Could not create symlink '%s' -> 'DedupedConfigs/%s' (%d)", config.c_str(), fileName.c_str(), err); + } + } + } + + for (auto& arch : _manifest.configurations[*cacheSet.begin()].architectures) { + dispatch_semaphore_wait(_concurrencyLimitingSemaphore, DISPATCH_TIME_FOREVER); + cacheBuilderDispatchGroupAsync(_writeGroup, _buildQueue, [=] { + WarningTargets targets; + targets.first = &_manifest; + for (auto& config : cacheSet) { + targets.second.insert(std::make_pair(config, arch.first)); + } + auto ctx = std::make_shared(setName + "/" + arch.first, targets); + setLoggingContext(ctx); + + std::string configPath = masterDstRoot + "/DedupedConfigs/" + fileName + "/System/Library/Caches/com.apple.dyld/"; + + if (!_skipWrites) { + int err = mkpath_np(configPath.c_str(), 0755); + + if (err != 0 && err != EEXIST) { + dispatch_semaphore_signal(_concurrencyLimitingSemaphore); + terminate("mkpath_np fail: %d", err); + } + } + + buildCache(configPath + "dyld_shared_cache_" + arch.first + ".development", cacheSet, arch.first, true); + buildCache(configPath + "dyld_shared_cache_" + arch.first, cacheSet, arch.first, false); + dispatch_semaphore_signal(_concurrencyLimitingSemaphore); + }); + } + } + + dispatch_group_wait(_writeGroup, DISPATCH_TIME_FOREVER); + +#if BOM_SUPPORT + if ( !_skipWrites ) { + for ( auto& configuration : _manifest.configurations ) { + std::vector prodBomPaths; + std::vector devBomPaths; + + for (auto& arch : configuration.second.architectures) { + std::string cachePath = "dyld_shared_cache_" + arch.first; + prodBomPaths.push_back(cachePath); + cachePath += ".development"; + devBomPaths.push_back(cachePath); + dispatch_group_enter(_writeGroup); + cacheBuilderDispatchAsync(_writeQueue, [=] { + char buffer[MAXPATHLEN]; + sprintf(buffer, "%s/Boms/%s.prod.bom", masterDstRoot.c_str(), configuration.first.c_str()); + BOMBom bom = BOMBomNew(buffer); + insertCacheDirInBom(bom); + for (auto& path : prodBomPaths) { + insertFileInBom("./System/Library/Caches/com.apple.dyld", path, bom); + } + BOMBomFree(bom); + + sprintf(buffer, "%s/Boms/%s.dev.bom", masterDstRoot.c_str(), configuration.first.c_str()); + bom = BOMBomNew(buffer); + insertCacheDirInBom(bom); + for (auto& path : devBomPaths) { + insertFileInBom("./System/Library/Caches/com.apple.dyld", path, bom); + } + BOMBomFree(bom); + + sprintf(buffer, "%s/Boms/%s.full.bom", masterDstRoot.c_str(), configuration.first.c_str()); + bom = BOMBomNew(buffer); + insertCacheDirInBom(bom); + for (auto& path : prodBomPaths) { + insertFileInBom("./System/Library/Caches/com.apple.dyld", path, bom); + } + for (auto& path : devBomPaths) { + insertFileInBom("./System/Library/Caches/com.apple.dyld", path, bom); + } + BOMBomFree(bom); + dispatch_group_leave(_writeGroup); + }); + } + } + } +#endif /* BOM_SUPPORT */ + } else { + runOnManifestConcurrently( + [&](const std::string configuration, const std::string architecture) { + cacheBuilderDispatchGroupAsync(_writeGroup, _buildQueue, [=] { + std::set configurations; + configurations.insert( configuration ); + // FIXME hacky, we make implicit assumptions about dev vs non-dev and layout depending on the flags + if ( _buildRoot ) { + int err = mkpath_np( ( masterDstRoot + "/System/Library/Caches/com.apple.dyld/" ).c_str(), 0755 ); + + if ( err != 0 && err != EEXIST ) { + terminate( "mkpath_np fail: %d", err ); + } + buildCache(masterDstRoot + "/System/Library/Caches/com.apple.dyld/dyld_shared_cache_" + architecture, + configurations, architecture, false); + buildCache(masterDstRoot + "/System/Library/Caches/com.apple.dyld/dyld_shared_cache_" + architecture + ".development", + configurations, architecture, true); + } else { + buildCache(masterDstRoot + "/dyld_shared_cache_" + architecture, configurations, architecture, true); + } + }); + }); + dispatch_group_wait(_writeGroup, DISPATCH_TIME_FOREVER); + } + + int err = sync_volume_np(masterDstRoot.c_str(), SYNC_VOLUME_FULLSYNC | SYNC_VOLUME_WAIT); + if (err) { + warning("Volume sync failed errnor=%d (%s)", err, strerror(err)); + } +} + +void MultiCacheBuilder::logStats(void) { + if ( _bniMode ) + log("Processed %llu caches (%.2fGB)", _filesWritten, ((float)_bytesWritten)/(1024*1024*1024)); +} + diff --git a/launch-cache/ObjCLegacyAbstraction.hpp b/interlinked-dylibs/ObjC1Abstraction.hpp similarity index 55% rename from launch-cache/ObjCLegacyAbstraction.hpp rename to interlinked-dylibs/ObjC1Abstraction.hpp index 23018c7..93bf751 100644 --- a/launch-cache/ObjCLegacyAbstraction.hpp +++ b/interlinked-dylibs/ObjC1Abstraction.hpp @@ -25,42 +25,42 @@ #define OBJC_IMAGE_SUPPORTS_GC (1<<1) #define OBJC_IMAGE_REQUIRES_GC (1<<2) -template +template struct objc_image_info { uint32_t version; uint32_t flags; - uint32_t getFlags() INLINE { return A::P::E::get32(flags); } + uint32_t getFlags() INLINE { return P::E::get32(flags); } bool supportsGCFlagSet() INLINE { return getFlags() & OBJC_IMAGE_SUPPORTS_GC; } bool requiresGCFlagSet() INLINE { return getFlags() & OBJC_IMAGE_REQUIRES_GC; } - void setFlag(uint32_t bits) INLINE { uint32_t old = A::P::E::get32(flags); A::P::E::set32(flags, old | bits); } + void setFlag(uint32_t bits) INLINE { uint32_t old = P::E::get32(flags); P::E::set32(flags, old | bits); } void setOptimizedByDyld() INLINE { setFlag(1<<3); } }; -template +template struct objc_method { uint32_t method_name; // SEL uint32_t method_types; // char * uint32_t method_imp; // IMP - uint32_t getName() const INLINE { return A::P::E::get32(method_name); } - void setName(uint32_t newName) INLINE { A::P::E::set32(method_name, newName); } + uint32_t getName() const INLINE { return P::E::get32(method_name); } + void setName(uint32_t newName) INLINE { P::E::set32(method_name, newName); } }; -template +template struct objc_method_list { enum { OBJC_FIXED_UP = 1771 }; uint32_t obsolete; // struct objc_method_list * uint32_t method_count; // int - struct objc_method method_list[0]; + struct objc_method

method_list[0]; - uint32_t getCount() const INLINE { return A::P::E::get32(method_count); } - void setFixedUp(bool fixed) INLINE { A::P::E::set32(obsolete, fixed ? OBJC_FIXED_UP : 0); } + uint32_t getCount() const INLINE { return P::E::get32(method_count); } + void setFixedUp(bool fixed) INLINE { P::E::set32(obsolete, fixed ? OBJC_FIXED_UP : 0); } }; -template +template struct objc_class { uint32_t isa; // struct objc_class * uint32_t super_class; // struct objc_class * @@ -74,12 +74,12 @@ struct objc_class { uint32_t protocols; // objc_protocol_list * uint32_t ivar_layout; // const char * uint32_t ext; // struct objc_class_ext * - - struct objc_class *getIsa(SharedCache *cache) const INLINE { return (struct objc_class *)cache->mappedAddressForVMAddress(A::P::E::get32(isa)); } - struct objc_method_list *getMethodList(SharedCache *cache) const INLINE { return (struct objc_method_list *)cache->mappedAddressForVMAddress(A::P::E::get32(methodList)); } + + struct objc_class

*getIsa(ContentAccessor* cache) const INLINE { return (struct objc_class

*)cache->contentForVMAddr(P::E::get32(isa)); } + struct objc_method_list

*getMethodList(ContentAccessor* cache) const INLINE { return (struct objc_method_list

*)cache->contentForVMAddr(P::E::get32(methodList)); } }; -template +template struct objc_category { uint32_t category_name; // char * uint32_t class_name; // char * @@ -89,11 +89,11 @@ struct objc_category { uint32_t size; // uint32_t uint32_t instance_properties; // struct objc_property_list * - struct objc_method_list *getInstanceMethods(SharedCache *cache) const INLINE { return (struct objc_method_list *)cache->mappedAddressForVMAddress(A::P::E::get32(instance_methods)); } - struct objc_method_list *getClassMethods(SharedCache *cache) const INLINE { return (struct objc_method_list *)cache->mappedAddressForVMAddress(A::P::E::get32(class_methods)); } + struct objc_method_list

*getInstanceMethods(ContentAccessor* cache) const INLINE { return (struct objc_method_list

*)cache->contentForVMAddr(P::E::get32(instance_methods)); } + struct objc_method_list

*getClassMethods(ContentAccessor* cache) const INLINE { return (struct objc_method_list

*)cache->contentForVMAddr(P::E::get32(class_methods)); } }; -template +template struct objc_symtab { uint32_t sel_ref_cnt; // unsigned long uint32_t refs; // SEL * @@ -101,40 +101,40 @@ struct objc_symtab { uint16_t cat_def_cnt; // unsigned short uint32_t defs[0]; // void * - uint16_t getClassCount(void) const INLINE { return A::P::E::get16(cls_def_cnt); } - uint16_t getCategoryCount(void) const INLINE { return A::P::E::get16(cat_def_cnt); } - struct objc_class *getClass(SharedCache *cache, int index) const INLINE { return (struct objc_class *)cache->mappedAddressForVMAddress(A::P::E::get32(defs[index])); } - struct objc_category *getCategory(SharedCache *cache, int index) const INLINE { return (struct objc_category *)cache->mappedAddressForVMAddress(A::P::E::get32(defs[getClassCount() + index])); } + uint16_t getClassCount(void) const INLINE { return P::E::get16(cls_def_cnt); } + uint16_t getCategoryCount(void) const INLINE { return P::E::get16(cat_def_cnt); } + struct objc_class

*getClass(ContentAccessor* cache, int index) const INLINE { return (struct objc_class

*)cache->contentForVMAddr(P::E::get32(defs[index])); } + struct objc_category

*getCategory(ContentAccessor* cache, int index) const INLINE { return (struct objc_category

*)cache->contentForVMAddr(P::E::get32(defs[getClassCount() + index])); } }; -template +template struct objc_module { uint32_t version; // unsigned long uint32_t size; // unsigned long uint32_t name; // char* uint32_t symtab; // Symtab - struct objc_symtab *getSymtab(SharedCache *cache) const INLINE { return (struct objc_symtab *)cache->mappedAddressForVMAddress(A::P::E::get32(symtab)); } + struct objc_symtab

*getSymtab(ContentAccessor* cache) const INLINE { return (struct objc_symtab

*)cache->contentForVMAddr(P::E::get32(symtab)); } }; -template +template struct objc_method_description { uint32_t name; // SEL uint32_t types; // char * - uint32_t getName() const INLINE { return A::P::E::get32(name); } - void setName(uint32_t newName) INLINE { A::P::E::set32(name, newName); } + uint32_t getName() const INLINE { return P::E::get32(name); } + void setName(uint32_t newName) INLINE { P::E::set32(name, newName); } }; -template +template struct objc_method_description_list { uint32_t count; // int - struct objc_method_description list[0]; + struct objc_method_description

list[0]; - uint32_t getCount() const INLINE { return A::P::E::get32(count); } + uint32_t getCount() const INLINE { return P::E::get32(count); } }; -template +template struct objc_protocol { uint32_t isa; // danger! contains strange values! uint32_t protocol_name; // const char * @@ -142,52 +142,49 @@ struct objc_protocol { uint32_t instance_methods; // struct objc_method_description_list * uint32_t class_methods; // struct objc_method_description_list * - struct objc_method_description_list *getInstanceMethodDescriptions(SharedCache *cache) const INLINE { return (struct objc_method_description_list *)cache->mappedAddressForVMAddress(A::P::E::get32(instance_methods)); } - struct objc_method_description_list *getClassMethodDescriptions(SharedCache *cache) const INLINE { return (struct objc_method_description_list *)cache->mappedAddressForVMAddress(A::P::E::get32(class_methods)); } + struct objc_method_description_list

*getInstanceMethodDescriptions(ContentAccessor* cache) const INLINE { return (struct objc_method_description_list

*)cache->contentForVMAddr(P::E::get32(instance_methods)); } + struct objc_method_description_list

*getClassMethodDescriptions(ContentAccessor* cache) const INLINE { return (struct objc_method_description_list

*)cache->contentForVMAddr(P::E::get32(class_methods)); } }; -template +template class LegacySelectorUpdater { + typedef typename P::uint_t pint_t; - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - static void visitMethodList(objc_method_list *mlist, V& visitor) + static void visitMethodList(objc_method_list

*mlist, V& visitor) { for (uint32_t m = 0; m < mlist->getCount(); m++) { - uint64_t oldValue = mlist->method_list[m].getName(); - uint64_t newValue = visitor.visit(oldValue); - mlist->method_list[m].setName(newValue); + pint_t oldValue = mlist->method_list[m].getName(); + pint_t newValue = visitor.visit(oldValue); + mlist->method_list[m].setName((uint32_t)newValue); } mlist->setFixedUp(true); } - static void visitMethodDescriptionList(objc_method_description_list *mlist, V& visitor) + static void visitMethodDescriptionList(objc_method_description_list

*mlist, V& visitor) { - for (uint32_t m = 0; m < mlist->getCount(); m++) { - uint64_t oldValue = mlist->list[m].getName(); - uint64_t newValue = visitor.visit(oldValue); - mlist->list[m].setName(newValue); + for (pint_t m = 0; m < mlist->getCount(); m++) { + pint_t oldValue = mlist->list[m].getName(); + pint_t newValue = visitor.visit(oldValue); + mlist->list[m].setName((uint32_t)newValue); } } public: - static void update(SharedCache* cache, const macho_header

* header, - V& visitor) + static void update(ContentAccessor* cache, const macho_header

* header, V& visitor) { - ArraySection > + ArraySection > modules(cache, header, "__OBJC", "__module_info"); for (uint64_t m = 0; m < modules.count(); m++) { - objc_symtab *symtab = modules.get(m).getSymtab(cache); + objc_symtab

*symtab = modules.get(m).getSymtab(cache); if (!symtab) continue; // Method lists in classes - for (uint64_t c = 0; c < symtab->getClassCount(); c++) { - objc_class *cls = symtab->getClass(cache, c); - objc_class *isa = cls->getIsa(cache); - objc_method_list *mlist; + for (int c = 0; c < symtab->getClassCount(); c++) { + objc_class

*cls = symtab->getClass(cache, c); + objc_class

*isa = cls->getIsa(cache); + objc_method_list

*mlist; if ((mlist = cls->getMethodList(cache))) { visitMethodList(mlist, visitor); } @@ -197,9 +194,9 @@ public: } // Method lists from categories - for (uint64_t c = 0; c < symtab->getCategoryCount(); c++) { - objc_category *cat = symtab->getCategory(cache, c); - objc_method_list *mlist; + for (int c = 0; c < symtab->getCategoryCount(); c++) { + objc_category

*cat = symtab->getCategory(cache, c); + objc_method_list

*mlist; if ((mlist = cat->getInstanceMethods(cache))) { visitMethodList(mlist, visitor); } @@ -210,11 +207,11 @@ public: } // Method description lists from protocols - ArraySection > + ArraySection> protocols(cache, header, "__OBJC", "__protocol"); for (uint64_t p = 0; p < protocols.count(); p++) { - objc_protocol& protocol = protocols.get(p); - objc_method_description_list *mlist; + objc_protocol

& protocol = protocols.get(p); + objc_method_description_list

*mlist; if ((mlist = protocol.getInstanceMethodDescriptions(cache))) { visitMethodDescriptionList(mlist, visitor); } @@ -224,7 +221,7 @@ public: } // Message refs - PointerSection selrefs(cache, header, "__OBJC", "__message_refs"); + PointerSection selrefs(cache, header, "__OBJC", "__message_refs"); for (pint_t s = 0; s < selrefs.count(); s++) { pint_t oldValue = selrefs.getVMAddress(s); pint_t newValue = visitor.visit(oldValue); diff --git a/interlinked-dylibs/ObjC2Abstraction.hpp b/interlinked-dylibs/ObjC2Abstraction.hpp new file mode 100644 index 0000000..35e9aba --- /dev/null +++ b/interlinked-dylibs/ObjC2Abstraction.hpp @@ -0,0 +1,1217 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2008-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include + +// iterate an entsize-based list +// typedef entsize_iterator, type_list_t

> type_iterator; +template +struct entsize_iterator { + uint32_t entsize; + uint32_t index; // keeping track of this saves a divide in operator- + T* current; + + typedef std::random_access_iterator_tag iterator_category; + typedef T value_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef T& reference; + + entsize_iterator() { } + + entsize_iterator(const Tlist& list, uint32_t start = 0) + : entsize(list.getEntsize()), index(start), current(&list.get(start)) + { } + + const entsize_iterator& operator += (ptrdiff_t count) { + current = (T*)((uint8_t *)current + count*entsize); + index += count; + return *this; + } + const entsize_iterator& operator -= (ptrdiff_t count) { + current = (T*)((uint8_t *)current - count*entsize); + index -= count; + return *this; + } + const entsize_iterator operator + (ptrdiff_t count) const { + return entsize_iterator(*this) += count; + } + const entsize_iterator operator - (ptrdiff_t count) const { + return entsize_iterator(*this) -= count; + } + + entsize_iterator& operator ++ () { *this += 1; return *this; } + entsize_iterator& operator -- () { *this -= 1; return *this; } + entsize_iterator operator ++ (int) { + entsize_iterator result(*this); *this += 1; return result; + } + entsize_iterator operator -- (int) { + entsize_iterator result(*this); *this -= 1; return result; + } + + ptrdiff_t operator - (const entsize_iterator& rhs) const { + return (ptrdiff_t)this->index - (ptrdiff_t)rhs.index; + } + + T& operator * () { return *current; } + T& operator * () const { return *current; } + T& operator -> () { return *current; } + const T& operator -> () const { return *current; } + + operator T& () const { return *current; } + + bool operator == (const entsize_iterator& rhs) { + return this->current == rhs.current; + } + bool operator != (const entsize_iterator& rhs) { + return this->current != rhs.current; + } + + bool operator < (const entsize_iterator& rhs) { + return this->current < rhs.current; + } + bool operator > (const entsize_iterator& rhs) { + return this->current > rhs.current; + } + + + static void overwrite(entsize_iterator& dst, const Tlist* srcList) + { + entsize_iterator src; + uint32_t ee = srcList->getEntsize(); + for (src = srcList->begin(); src != srcList->end(); ++src) { + memcpy(&*dst, &*src, ee); + ++dst; + } + } +}; + +template +class objc_header_info_rw_t { + + typedef typename P::uint_t pint_t; + + pint_t data; // loaded:1, allRealised:1, objc_header_info *:ptr + +public: + objc_header_info_rw_t(ContentAccessor* cache, const macho_header

* mh) + : data(0) { + } +}; + +template +class objc_header_info_ro_t { + + typedef typename P::uint_t pint_t; + + pint_t mhdr_offset; // offset to mach_header or mach_header_64 + pint_t info_offset; // offset to objc_image_info * + +public: + objc_header_info_ro_t(ContentAccessor* cache, const macho_header

* mh) + : mhdr_offset(0), info_offset(0) { + P::setP(mhdr_offset, (uint64_t)cache->vmAddrForContent((void*)mh) - (uint64_t)cache->vmAddrForContent(&mhdr_offset)); + assert(header_vmaddr(cache) == (uint64_t)cache->vmAddrForContent((void*)mh)); + const macho_section

* sect = mh->getSection("__DATA", "__objc_imageinfo"); + if (sect) { + P::setP(info_offset, (uint64_t)sect->addr() - (uint64_t)cache->vmAddrForContent(&info_offset)); + // set bit in mach_header.flags to tell dyld that this image has objc content + macho_header

* rwmh = const_cast*>(mh); + rwmh->set_flags(mh->flags() | MH_HAS_OBJC); + } + else + P::setP(info_offset, - (uint64_t)cache->vmAddrForContent(&info_offset)); + } + + pint_t header_vmaddr(ContentAccessor* cache) const { + return (pint_t)(((uint64_t)cache->vmAddrForContent(&mhdr_offset)) + mhdr_offset); + } +}; + + +template +class objc_method_list_t; // forward reference + + +template +class objc_method_t { + typedef typename P::uint_t pint_t; + pint_t name; // SEL + pint_t types; // const char * + pint_t imp; // IMP + friend class objc_method_list_t

; +public: + pint_t getName() const { return (pint_t)P::getP(name); } + void setName(pint_t newName) { P::setP(name, newName); } + + struct SortBySELAddress : + public std::binary_function&, + const objc_method_t

&, bool> + { + bool operator() (const objc_method_t

& lhs, + const objc_method_t

& rhs) + { + return lhs.getName() < rhs.getName(); + } + }; +}; + +template +class objc_method_list_t { + uint32_t entsize; + uint32_t count; + objc_method_t

first; + + void* operator new (size_t, void* buf) { return buf; } + +public: + + typedef entsize_iterator, objc_method_list_t

> method_iterator; + + uint32_t getCount() const { return P::E::get32(count); } + + uint32_t getEntsize() const {return P::E::get32(entsize)&~(uint32_t)3;} + + objc_method_t

& get(uint32_t i) const { return *(objc_method_t

*)((uint8_t *)&first + i * getEntsize()); } + + uint32_t byteSize() const { + return byteSizeForCount(getCount(), getEntsize()); + } + + static uint32_t byteSizeForCount(uint32_t c, uint32_t e = sizeof(objc_method_t

)) { + return sizeof(objc_method_list_t

) - sizeof(objc_method_t

) + c*e; + } + + method_iterator begin() { return method_iterator(*this, 0); } + method_iterator end() { return method_iterator(*this, getCount()); } + const method_iterator begin() const { return method_iterator(*this, 0); } + const method_iterator end() const { return method_iterator(*this, getCount()); } + + void setFixedUp() { P::E::set32(entsize, getEntsize() | 3); } + + void getPointers(std::set& pointersToRemove) { + for(method_iterator it = begin(); it != end(); ++it) { + objc_method_t

& entry = *it; + pointersToRemove.insert(&(entry.name)); + pointersToRemove.insert(&(entry.types)); + pointersToRemove.insert(&(entry.imp)); + } + } + + static void addPointers(uint8_t* methodList, std::vector& pointersToAdd) { + objc_method_list_t

* mlist = (objc_method_list_t

*)methodList; + for(method_iterator it = mlist->begin(); it != mlist->end(); ++it) { + objc_method_t

& entry = *it; + pointersToAdd.push_back(&(entry.name)); + pointersToAdd.push_back(&(entry.types)); + pointersToAdd.push_back(&(entry.imp)); + } + } + + static objc_method_list_t

* newMethodList(size_t newCount, uint32_t newEntsize) { + void *buf = ::calloc(byteSizeForCount(newCount, newEntsize), 1); + return new (buf) objc_method_list_t

(newCount, newEntsize); + } + + void operator delete(void * p) { + ::free(p); + } + + objc_method_list_t(uint32_t newCount, + uint32_t newEntsize = sizeof(objc_method_t

)) + : entsize(newEntsize), count(newCount) + { } + +private: + // use newMethodList instead + void* operator new (size_t); +}; + + +template +class objc_ivar_t { + typedef typename P::uint_t pint_t; + + pint_t offset; // uint32_t* (uint64_t* on x86_64) + pint_t name; // const char* + pint_t type; // const char* + uint32_t alignment; + uint32_t size; + +public: + const char* getName(ContentAccessor* cache) const { return (const char *)cache->contentForVMAddr(P::getP(name)); } + + bool hasOffset() const { return offset != 0; } + uint32_t getOffset(ContentAccessor* cache) const { return P::E::get32(*(uint32_t*)(cache->contentForVMAddr(P::getP(offset)))); } + void setOffset(ContentAccessor* cache, uint32_t newOffset) { P::E::set32(*(uint32_t*)(cache->contentForVMAddr(P::getP(offset))), newOffset); } + + + uint32_t getAlignment() { + uint32_t a = P::E::get32(alignment); + return (a == (uint32_t)-1) ? sizeof(pint_t) : 1< +class objc_ivar_list_t { + typedef typename P::uint_t pint_t; + uint32_t entsize; + uint32_t count; + objc_ivar_t

first; + + void* operator new (size_t, void* buf) { return buf; } + +public: + + typedef entsize_iterator, objc_ivar_list_t

> ivar_iterator; + + uint32_t getCount() const { return P::E::get32(count); } + + uint32_t getEntsize() const { return P::E::get32(entsize); } + + objc_ivar_t

& get(pint_t i) const { return *(objc_ivar_t

*)((uint8_t *)&first + i * P::E::get32(entsize)); } + + uint32_t byteSize() const { + return byteSizeForCount(getCount(), getEntsize()); + } + + static uint32_t byteSizeForCount(uint32_t c, uint32_t e = sizeof(objc_ivar_t

)) { + return sizeof(objc_ivar_list_t

) - sizeof(objc_ivar_t

) + c*e; + } + + ivar_iterator begin() { return ivar_iterator(*this, 0); } + ivar_iterator end() { return ivar_iterator(*this, getCount()); } + const ivar_iterator begin() const { return ivar_iterator(*this, 0); } + const ivar_iterator end() const { return ivar_iterator(*this, getCount()); } + + static objc_ivar_list_t

* newIvarList(size_t newCount, uint32_t newEntsize) { + void *buf = ::calloc(byteSizeForCount(newCount, newEntsize), 1); + return new (buf) objc_ivar_list_t

(newCount, newEntsize); + } + + void operator delete(void * p) { + ::free(p); + } + + objc_ivar_list_t(uint32_t newCount, + uint32_t newEntsize = sizeof(objc_ivar_t

)) + : entsize(newEntsize), count(newCount) + { } +private: + // use newIvarList instead + void* operator new (size_t); +}; + + +template class objc_property_list_t; // forward + +template +class objc_property_t { + typedef typename P::uint_t pint_t; + pint_t name; + pint_t attributes; + friend class objc_property_list_t

; +public: + + const char * getName(ContentAccessor* cache) const { return (const char *)cache->contentForVMAddr(P::getP(name)); } + + const char * getAttributes(ContentAccessor* cache) const { return (const char *)cache->contentForVMAddr(P::getP(attributes)); } +}; + +template +class objc_property_list_t { + uint32_t entsize; + uint32_t count; + objc_property_t

first; + + void* operator new (size_t, void* buf) { return buf; } + +public: + + typedef entsize_iterator, objc_property_list_t

> property_iterator; + + uint32_t getCount() const { return P::E::get32(count); } + + uint32_t getEntsize() const { return P::E::get32(entsize); } + + objc_property_t

& get(uint32_t i) const { return *(objc_property_t

*)((uint8_t *)&first + i * getEntsize()); } + + uint32_t byteSize() const { + return byteSizeForCount(getCount(), getEntsize()); + } + + static uint32_t byteSizeForCount(uint32_t c, uint32_t e = sizeof(objc_property_t

)) { + return sizeof(objc_property_list_t

) - sizeof(objc_property_t

) + c*e; + } + + property_iterator begin() { return property_iterator(*this, 0); } + property_iterator end() { return property_iterator(*this, getCount()); } + const property_iterator begin() const { return property_iterator(*this, 0); } + const property_iterator end() const { return property_iterator(*this, getCount()); } + + void getPointers(std::set& pointersToRemove) { + for(property_iterator it = begin(); it != end(); ++it) { + objc_property_t

& entry = *it; + pointersToRemove.insert(&(entry.name)); + pointersToRemove.insert(&(entry.attributes)); + } + } + + static void addPointers(uint8_t* propertyList, std::vector& pointersToAdd) { + objc_property_list_t

* plist = (objc_property_list_t

*)propertyList; + for(property_iterator it = plist->begin(); it != plist->end(); ++it) { + objc_property_t

& entry = *it; + pointersToAdd.push_back(&(entry.name)); + pointersToAdd.push_back(&(entry.attributes)); + } + } + + static objc_property_list_t

* newPropertyList(size_t newCount, uint32_t newEntsize) { + void *buf = ::calloc(byteSizeForCount(newCount, newEntsize), 1); + return new (buf) objc_property_list_t

(newCount, newEntsize); + } + + void operator delete(void * p) { + ::free(p); + } + + objc_property_list_t(uint32_t newCount, + uint32_t newEntsize = sizeof(objc_property_t

)) + : entsize(newEntsize), count(newCount) + { } +private: + // use newPropertyList instead + void* operator new (size_t); +}; + + +template class objc_protocol_list_t; // forward reference + +template +class objc_protocol_t { + typedef typename P::uint_t pint_t; + + pint_t isa; + pint_t name; + pint_t protocols; + pint_t instanceMethods; + pint_t classMethods; + pint_t optionalInstanceMethods; + pint_t optionalClassMethods; + pint_t instanceProperties; + uint32_t size; + uint32_t flags; + pint_t extendedMethodTypes; + pint_t demangledName; + pint_t classProperties; + +public: + pint_t getIsaVMAddr() const { return (pint_t)P::getP(isa); } + void setIsaVMAddr(pint_t newIsa) { P::setP(isa, newIsa); } + + const char *getName(ContentAccessor* cache) const { return (const char *)cache->contentForVMAddr(P::getP(name)); } + + uint32_t getSize() const { return P::E::get32(size); } + void setSize(uint32_t newSize) { P::E::set32(size, newSize); } + + uint32_t getFlags() const { return P::E::get32(flags); } + + void setFixedUp() { P::E::set32(flags, getFlags() | (1<<30)); } + + objc_protocol_list_t

*getProtocols(ContentAccessor* cache) const { return (objc_protocol_list_t

*)cache->contentForVMAddr(P::getP(protocols)); } + + objc_method_list_t

*getInstanceMethods(ContentAccessor* cache) const { return (objc_method_list_t

*)cache->contentForVMAddr(P::getP(instanceMethods)); } + + objc_method_list_t

*getClassMethods(ContentAccessor* cache) const { return (objc_method_list_t

*)cache->contentForVMAddr(P::getP(classMethods)); } + + objc_method_list_t

*getOptionalInstanceMethods(ContentAccessor* cache) const { return (objc_method_list_t

*)cache->contentForVMAddr(P::getP(optionalInstanceMethods)); } + + objc_method_list_t

*getOptionalClassMethods(ContentAccessor* cache) const { return (objc_method_list_t

*)cache->contentForVMAddr(P::getP(optionalClassMethods)); } + + objc_property_list_t

*getInstanceProperties(ContentAccessor* cache) const { return (objc_property_list_t

*)cache->contentForVMAddr(P::getP(instanceProperties)); } + + pint_t *getExtendedMethodTypes(ContentAccessor* cache) const { + if (getSize() < offsetof(objc_protocol_t

, extendedMethodTypes) + sizeof(extendedMethodTypes)) { + return NULL; + } + return (pint_t *)cache->contentForVMAddr(P::getP(extendedMethodTypes)); + } + + const char *getDemangledName(ContentAccessor* cache) const { + if (sizeof(*this) < offsetof(objc_protocol_t

, demangledName) + sizeof(demangledName)) { + return NULL; + } + return (const char *)cache->contentForVMAddr(P::getP(demangledName)); + } + + void setDemangledName(ContentAccessor* cache, const char *newName) { + if (sizeof(*this) < offsetof(objc_protocol_t

, demangledName) + sizeof(demangledName)) { + terminate("objc protocol has the wrong size"); + } + P::setP(demangledName, cache->vmAddrForContent((void*)newName)); + } + + void addPointers(std::vector& pointersToAdd) + { + pointersToAdd.push_back(&isa); + pointersToAdd.push_back(&name); + if (protocols) pointersToAdd.push_back(&protocols); + if (instanceMethods) pointersToAdd.push_back(&instanceMethods); + if (classMethods) pointersToAdd.push_back(&classMethods); + if (optionalInstanceMethods) pointersToAdd.push_back(&optionalInstanceMethods); + if (optionalClassMethods) pointersToAdd.push_back(&optionalClassMethods); + if (instanceProperties) pointersToAdd.push_back(&instanceProperties); + if (extendedMethodTypes) pointersToAdd.push_back(&extendedMethodTypes); + if (demangledName) pointersToAdd.push_back(&demangledName); + } +}; + + +template +class objc_protocol_list_t { + typedef typename P::uint_t pint_t; + pint_t count; + pint_t list[0]; + + void* operator new (size_t, void* buf) { return buf; } + +public: + + pint_t getCount() const { return (pint_t)P::getP(count); } + + pint_t getVMAddress(pint_t i) { + return (pint_t)P::getP(list[i]); + } + + objc_protocol_t

* get(ContentAccessor* cache, pint_t i) { + return (objc_protocol_t

*)cache->contentForVMAddr(getVMAddress(i)); + } + + void setVMAddress(pint_t i, pint_t protoVMAddr) { + P::setP(list[i], protoVMAddr); + } + + void set(ContentAccessor* cache, pint_t i, objc_protocol_t

* proto) { + setVMAddress(i, cache->vmAddrForContent(proto)); + } + + uint32_t byteSize() const { + return byteSizeForCount(getCount()); + } + static uint32_t byteSizeForCount(pint_t c) { + return sizeof(objc_protocol_list_t

) + c*sizeof(pint_t); + } + + void getPointers(std::set& pointersToRemove) { + for(int i=0 ; i < count; ++i) { + pointersToRemove.insert(&list[i]); + } + } + + static void addPointers(uint8_t* protocolList, std::vector& pointersToAdd) { + objc_protocol_list_t

* plist = (objc_protocol_list_t

*)protocolList; + for(int i=0 ; i < plist->count; ++i) { + pointersToAdd.push_back(&plist->list[i]); + } + } + + static objc_protocol_list_t

* newProtocolList(pint_t newCount) { + void *buf = ::calloc(byteSizeForCount(newCount), 1); + return new (buf) objc_protocol_list_t

(newCount); + } + + void operator delete(void * p) { + ::free(p); + } + + objc_protocol_list_t(uint32_t newCount) : count(newCount) { } +private: + // use newProtocolList instead + void* operator new (size_t); +}; + + +template +class objc_class_data_t { + typedef typename P::uint_t pint_t; + uint32_t flags; + uint32_t instanceStart; + // Note there is 4-bytes of alignment padding between instanceSize and ivarLayout + // on 64-bit archs, but no padding on 32-bit archs. + // This union is a way to model that. + union { + uint32_t instanceSize; + pint_t pad; + } instanceSize; + pint_t ivarLayout; + pint_t name; + pint_t baseMethods; + pint_t baseProtocols; + pint_t ivars; + pint_t weakIvarLayout; + pint_t baseProperties; + +public: + bool isMetaClass() { return P::E::get32(flags) & (1 << 0); } + bool isRootClass() { return P::E::get32(flags) & (1 << 1); } + + uint32_t getInstanceStart() { return P::E::get32(instanceStart); } + void setInstanceStart(uint32_t newStart) { P::E::set32(instanceStart, newStart); } + + uint32_t getInstanceSize() { return P::E::get32(instanceSize.instanceSize); } + void setInstanceSize(uint32_t newSiz) { P::E::set32(instanceSize.instanceSize, newSiz); } + + objc_method_list_t

*getMethodList(ContentAccessor* cache) const { return (objc_method_list_t

*)cache->contentForVMAddr(P::getP(baseMethods)); } + + objc_protocol_list_t

*getProtocolList(ContentAccessor* cache) const { return (objc_protocol_list_t

*)cache->contentForVMAddr(P::getP(baseProtocols)); } + + objc_ivar_list_t

*getIvarList(ContentAccessor* cache) const { return (objc_ivar_list_t

*)cache->contentForVMAddr(P::getP(ivars)); } + + objc_property_list_t

*getPropertyList(ContentAccessor* cache) const { return (objc_property_list_t

*)cache->contentForVMAddr(P::getP(baseProperties)); } + + const char * getName(ContentAccessor* cache) const { return (const char *)cache->contentForVMAddr(P::getP(name)); } + + void setMethodList(ContentAccessor* cache, objc_method_list_t

* mlist) { + P::setP(baseMethods, cache->vmAddrForContent(mlist)); + } + + void setProtocolList(ContentAccessor* cache, objc_protocol_list_t

* protolist) { + P::setP(baseProtocols, cache->vmAddrForContent(protolist)); + } + + void setPropertyList(ContentAccessor* cache, objc_property_list_t

* proplist) { + P::setP(baseProperties, cache->vmAddrForContent(proplist)); + } + + void addMethodListPointer(std::vector& pointersToAdd) { + pointersToAdd.push_back(&this->baseMethods); + } + + void addPropertyListPointer(std::vector& pointersToAdd) { + pointersToAdd.push_back(&this->baseProperties); + } + + void addProtocolListPointer(std::vector& pointersToAdd) { + pointersToAdd.push_back(&this->baseProtocols); + } +}; + +template +class objc_class_t { + typedef typename P::uint_t pint_t; + + pint_t isa; + pint_t superclass; + pint_t method_cache; + pint_t vtable; + pint_t data; + +public: + bool isMetaClass(ContentAccessor* cache) const { return getData(cache)->isMetaClass(); } + bool isRootClass(ContentAccessor* cache) const { return getData(cache)->isRootClass(); } + + objc_class_t

*getIsa(ContentAccessor* cache) const { return (objc_class_t

*)cache->contentForVMAddr(P::getP(isa)); } + + objc_class_t

*getSuperclass(ContentAccessor* cache) const { return (objc_class_t

*)cache->contentForVMAddr(P::getP(superclass)); } + + // Low bit marks Swift classes. + objc_class_data_t

*getData(ContentAccessor* cache) const { return (objc_class_data_t

*)cache->contentForVMAddr(P::getP(data & ~0x1LL)); } + + objc_method_list_t

*getMethodList(ContentAccessor* cache) const { + objc_class_data_t

* d = getData(cache); + return d->getMethodList(cache); + } + + objc_protocol_list_t

*getProtocolList(ContentAccessor* cache) const { return getData(cache)->getProtocolList(cache); } + + objc_property_list_t

*getPropertyList(ContentAccessor* cache) const { return getData(cache)->getPropertyList(cache); } + + const char* getName(ContentAccessor* cache) const { + return getData(cache)->getName(cache); + } + + void setMethodList(ContentAccessor* cache, objc_method_list_t

* mlist) { + getData(cache)->setMethodList(cache, mlist); + } + + void setProtocolList(ContentAccessor* cache, objc_protocol_list_t

* protolist) { + getData(cache)->setProtocolList(cache, protolist); + } + + void setPropertyList(ContentAccessor* cache, objc_property_list_t

* proplist) { + getData(cache)->setPropertyList(cache, proplist); + } + + void addMethodListPointer(ContentAccessor* cache, std::vector& pointersToAdd) { + getData(cache)->addMethodListPointer(pointersToAdd); + } + + void addPropertyListPointer(ContentAccessor* cache, std::vector& pointersToAdd) { + getData(cache)->addPropertyListPointer(pointersToAdd); + } + + void addProtocolListPointer(ContentAccessor* cache, std::vector& pointersToAdd) { + getData(cache)->addProtocolListPointer(pointersToAdd); + } + +}; + + + +template +class objc_category_t { + typedef typename P::uint_t pint_t; + + pint_t name; + pint_t cls; + pint_t instanceMethods; + pint_t classMethods; + pint_t protocols; + pint_t instanceProperties; + +public: + + const char * getName(ContentAccessor* cache) const { return (const char *)cache->contentForVMAddr(P::getP(name)); } + + objc_class_t

*getClass(ContentAccessor* cache) const { return (objc_class_t

*)cache->contentForVMAddr(P::getP(cls)); } + + objc_method_list_t

*getInstanceMethods(ContentAccessor* cache) const { return (objc_method_list_t

*)cache->contentForVMAddr(P::getP(instanceMethods)); } + + objc_method_list_t

*getClassMethods(ContentAccessor* cache) const { return (objc_method_list_t

*)cache->contentForVMAddr(P::getP(classMethods)); } + + objc_protocol_list_t

*getProtocols(ContentAccessor* cache) const { return (objc_protocol_list_t

*)cache->contentForVMAddr(P::getP(protocols)); } + + objc_property_list_t

*getInstanceProperties(ContentAccessor* cache) const { return (objc_property_list_t

*)cache->contentForVMAddr(P::getP(instanceProperties)); } + + void getPointers(std::set& pointersToRemove) { + pointersToRemove.insert(&name); + pointersToRemove.insert(&cls); + pointersToRemove.insert(&instanceMethods); + pointersToRemove.insert(&classMethods); + pointersToRemove.insert(&protocols); + pointersToRemove.insert(&instanceProperties); + } + + +}; + +template +class objc_message_ref_t { + typedef typename P::uint_t pint_t; + + pint_t imp; + pint_t sel; + +public: + pint_t getName() const { return (pint_t)P::getP(sel); } + + void setName(pint_t newName) { P::setP(sel, newName); } +}; + +// Call visitor.visitIvar() on every ivar in a given class. +template +class IvarWalker { + typedef typename P::uint_t pint_t; + V& ivarVisitor; +public: + + IvarWalker(V& visitor) : ivarVisitor(visitor) { } + + void walk(ContentAccessor* cache, const macho_header

* header, objc_class_t

*cls) + { + objc_class_data_t

*data = cls->getData(cache); + objc_ivar_list_t

*ivars = data->getIvarList(cache); + if (ivars) { + for (pint_t i = 0; i < ivars->getCount(); i++) { + objc_ivar_t

& ivar = ivars->get(i); + //fprintf(stderr, "visiting ivar: %s\n", ivar.getName(cache)); + ivarVisitor.visitIvar(cache, header, cls, &ivar); + } + } else { + //fprintf(stderr, "no ivars\n"); + } + } + + void visitClass(ContentAccessor* cache, const macho_header

* header, objc_class_t

*cls) + { + walk(cache, header, cls); + } +}; + +// Call visitor.visitClass() on every class. +template +class ClassWalker { + typedef typename P::uint_t pint_t; + V& _visitor; +public: + + ClassWalker(V& visitor) : _visitor(visitor) { } + + void walk(ContentAccessor* cache, const macho_header

* header) + { + PointerSection*> classList(cache, header, "__DATA", "__objc_classlist"); + + for (pint_t i = 0; i < classList.count(); i++) { + objc_class_t

* cls = classList.get(i); + //fprintf(stderr, "visiting class: %s\n", cls->getName(cache)); + if (cls) _visitor.visitClass(cache, header, cls); + } + } +}; + +// Call visitor.visitProtocol() on every protocol. +template +class ProtocolWalker { + typedef typename P::uint_t pint_t; + V& _protocolVisitor; +public: + + ProtocolWalker(V& visitor) : _protocolVisitor(visitor) { } + + void walk(ContentAccessor* cache, const macho_header

* header) + { + PointerSection *> + protocols(cache, header, "__DATA", "__objc_protolist"); + + for (pint_t i = 0; i < protocols.count(); i++) { + objc_protocol_t

*proto = protocols.get(i); + _protocolVisitor.visitProtocol(cache, header, proto); + } + } +}; + +// Call visitor.visitProtocolReference() on every protocol. +template +class ProtocolReferenceWalker { + typedef typename P::uint_t pint_t; + V& _visitor; + + void visitProtocolList(ContentAccessor* cache, + objc_protocol_list_t

* protolist) + { + if (!protolist) return; + for (pint_t i = 0; i < protolist->getCount(); i++) { + pint_t oldValue = protolist->getVMAddress(i); + pint_t newValue = _visitor.visitProtocolReference(cache, oldValue); + protolist->setVMAddress(i, newValue); + } + } + + friend class ClassWalker>; + + void visitClass(ContentAccessor* cache, const macho_header

*, + objc_class_t

* cls) + { + visitProtocolList(cache, cls->getProtocolList(cache)); + visitProtocolList(cache, cls->getIsa(cache)->getProtocolList(cache)); + } + +public: + + ProtocolReferenceWalker(V& visitor) : _visitor(visitor) { } + void walk(ContentAccessor* cache, const macho_header

* header) + { + // @protocol expressions + PointerSection *> + protorefs(cache, header, "__DATA", "__objc_protorefs"); + for (pint_t i = 0; i < protorefs.count(); i++) { + pint_t oldValue = protorefs.getVMAddress(i); + pint_t newValue = _visitor.visitProtocolReference(cache, oldValue); + protorefs.setVMAddress(i, newValue); + } + + // protocol lists in classes + ClassWalker> classes(*this); + classes.walk(cache, header); + + // protocol lists in protocols + // __objc_protolists itself is NOT updated + PointerSection *> + protocols(cache, header, "__DATA", "__objc_protolist"); + for (pint_t i = 0; i < protocols.count(); i++) { + objc_protocol_t

* proto = protocols.get(i); + visitProtocolList(cache, proto->getProtocols(cache)); + // not recursive: every old protocol object + // must be in some protolist section somewhere + } + } +}; + +// Call visitor.visitMethodList(mlist) on every +// class and category method list in a header. +// Call visitor.visitProtocolMethodList(mlist, typelist) on every +// protocol method list in a header. +template +class MethodListWalker { + + typedef typename P::uint_t pint_t; + + V& mVisitor; + +public: + + MethodListWalker(V& visitor) : mVisitor(visitor) { } + + void walk(ContentAccessor* cache, const macho_header

* header) + { + // Method lists in classes + PointerSection *> + classes(cache, header, "__DATA", "__objc_classlist"); + + for (pint_t i = 0; i < classes.count(); i++) { + objc_class_t

*cls = classes.get(i); + objc_method_list_t

*mlist; + if ((mlist = cls->getMethodList(cache))) { + mVisitor.visitMethodList(mlist); + } + if ((mlist = cls->getIsa(cache)->getMethodList(cache))) { + mVisitor.visitMethodList(mlist); + } + } + + // Method lists from categories + PointerSection *> + cats(cache, header, "__DATA", "__objc_catlist"); + for (pint_t i = 0; i < cats.count(); i++) { + objc_category_t

*cat = cats.get(i); + objc_method_list_t

*mlist; + if ((mlist = cat->getInstanceMethods(cache))) { + mVisitor.visitMethodList(mlist); + } + if ((mlist = cat->getClassMethods(cache))) { + mVisitor.visitMethodList(mlist); + } + } + + // Method description lists from protocols + PointerSection *> + protocols(cache, header, "__DATA", "__objc_protolist"); + for (pint_t i = 0; i < protocols.count(); i++) { + objc_protocol_t

*proto = protocols.get(i); + objc_method_list_t

*mlist; + pint_t *typelist = proto->getExtendedMethodTypes(cache); + + if ((mlist = proto->getInstanceMethods(cache))) { + mVisitor.visitProtocolMethodList(mlist, typelist); + if (typelist) typelist += mlist->getCount(); + } + if ((mlist = proto->getClassMethods(cache))) { + mVisitor.visitProtocolMethodList(mlist, typelist); + if (typelist) typelist += mlist->getCount(); + } + if ((mlist = proto->getOptionalInstanceMethods(cache))) { + mVisitor.visitProtocolMethodList(mlist, typelist); + if (typelist) typelist += mlist->getCount(); + } + if ((mlist = proto->getOptionalClassMethods(cache))) { + mVisitor.visitProtocolMethodList(mlist, typelist); + if (typelist) typelist += mlist->getCount(); + } + } + } +}; + + +// Update selector references. The visitor performs recording and uniquing. +template +class SelectorOptimizer { + + typedef typename P::uint_t pint_t; + + V& mVisitor; + + friend class MethodListWalker >; + void visitMethodList(objc_method_list_t

*mlist) + { + // Gather selectors. Update method names. + for (uint32_t m = 0; m < mlist->getCount(); m++) { + pint_t oldValue = mlist->get(m).getName(); + pint_t newValue = mVisitor.visit(oldValue); + mlist->get(m).setName(newValue); + } + // Do not setFixedUp: the methods are not yet sorted. + } + + void visitProtocolMethodList(objc_method_list_t

*mlist, pint_t *types) + { + visitMethodList(mlist); + } + +public: + + SelectorOptimizer(V& visitor) : mVisitor(visitor) { } + + void optimize(ContentAccessor* cache, const macho_header

* header) + { + // method lists in classes, categories, and protocols + MethodListWalker > mw(*this); + mw.walk(cache, header); + + // @selector references + PointerSection + selrefs(cache, header, "__DATA", "__objc_selrefs"); + for (pint_t i = 0; i < selrefs.count(); i++) { + pint_t oldValue = selrefs.getVMAddress(i); + pint_t newValue = mVisitor.visit(oldValue); + selrefs.setVMAddress(i, newValue); + } + + // message references + ArraySection > + msgrefs(cache, header, "__DATA", "__objc_msgrefs"); + for (pint_t i = 0; i < msgrefs.count(); i++) { + objc_message_ref_t

& msg = msgrefs.get(i); + pint_t oldValue = msg.getName(); + pint_t newValue = mVisitor.visit(oldValue); + msg.setName(newValue); + } + } +}; + + +// Update selector references. The visitor performs recording and uniquing. +template +class IvarOffsetOptimizer { + uint32_t _slide; + uint32_t _maxAlignment; + uint32_t _optimized; + +public: + + IvarOffsetOptimizer() : _optimized(0) { } + + size_t optimized() const { return _optimized; } + + // dual purpose ivar visitor function + // if slide!=0 then slides the ivar by that amount, otherwise computes _maxAlignment + void visitIvar(ContentAccessor* cache, const macho_header

* /*unused, may be NULL*/, objc_class_t

*cls, objc_ivar_t

*ivar) + { + if (_slide == 0) { + uint32_t alignment = ivar->getAlignment(); + if (alignment > _maxAlignment) _maxAlignment = alignment; + } else { + // skip anonymous bitfields + if (ivar->hasOffset()) { + uint32_t oldOffset = (uint32_t)ivar->getOffset(cache); + ivar->setOffset(cache, oldOffset + _slide); + _optimized++; + //fprintf(stderr, "%d -> %d for %s.%s\n", oldOffset, oldOffset + _slide, cls->getName(cache), ivar->getName(cache)); + } else { + //fprintf(stderr, "NULL offset\n"); + } + } + } + + // Class visitor function. Evaluates whether to slide ivars and performs slide if needed. + // The slide algorithm is also implemented in objc. Any changes here should be reflected there also. + void visitClass(ContentAccessor* cache, const macho_header

* /*unused, may be NULL*/, objc_class_t

*cls) + { + objc_class_t

*super = cls->getSuperclass(cache); + if (super) { + // Recursively visit superclasses to ensure we have the correct superclass start + // Note that we don't need the macho_header, so just pass NULL. + visitClass(cache, nullptr, super); + + objc_class_data_t

*data = cls->getData(cache); + objc_class_data_t

*super_data = super->getData(cache); + int32_t diff = super_data->getInstanceSize() - data->getInstanceStart(); + if (diff > 0) { + IvarWalker > ivarVisitor(*this); + _maxAlignment = 1; + _slide = 0; + + // This walk computes _maxAlignment + ivarVisitor.walk(cache, nullptr, cls); + + // Compute a slide value that preserves that alignment + uint32_t alignMask = _maxAlignment - 1; + if (diff & alignMask) diff = (diff + alignMask) & ~alignMask; + + // Slide all of this class's ivars en masse + _slide = diff; + if (_slide != 0) { + //fprintf(stderr, "Sliding ivars in %s by %u (superclass was %d, now %d)\n", cls->getName(cache), _slide, data->getInstanceStart(), super_data->getInstanceSize()); + ivarVisitor.walk(cache, nullptr, cls); + data->setInstanceStart(data->getInstanceStart() + _slide); + data->setInstanceSize(data->getInstanceSize() + _slide); + } + } + } + } + + // Enumerates objc classes in the module and performs any ivar slides + void optimize(ContentAccessor* cache, const macho_header

* header) + { + // The slide code cannot fix up GC layout strings so skip modules that support or require GC + const macho_section

*imageInfoSection = header->getSection("__DATA", "__objc_imageinfo"); + if (imageInfoSection) { + objc_image_info

*info = (objc_image_info

*)cache->contentForVMAddr(imageInfoSection->addr()); + if (!info->supportsGCFlagSet() && !info->requiresGCFlagSet()) { + ClassWalker > classVisitor(*this); + classVisitor.walk(cache, header); + } else { + //fprintf(stderr, "GC support present - skipped module\n"); + } + } + } +}; + + +// Detect classes that have missing weak-import superclasses. +template +class WeakClassDetector { + bool noMissing; + + friend class ClassWalker>; + void visitClass(ContentAccessor* cache, const macho_header

*, + objc_class_t

* cls) + { + auto supercls = cls->getSuperclass(cache); + if (supercls) { + // okay: class with superclass + // Note that the superclass itself might have a missing superclass. + // That is fine for mere detection because we will visit the + // superclass separately. + } else if (cls->isRootClass(cache)) { + // okay: root class is expected to have no superclass + } else { + // bad: cls's superclass is missing. + warning("Superclass of class '%s' is weak-import and missing.", + cls->getName(cache)); + noMissing = false; + } + } + +public: + bool noMissingWeakSuperclasses(ContentAccessor* cache, + std::vector*> dylibs) + { + noMissing = true; + ClassWalker> classes(*this); + for (auto mh : dylibs) { + classes.walk(cache, mh); + } + return noMissing; + } +}; + + +// Sort methods in place by selector. +template +class MethodListSorter { + + typedef typename P::uint_t pint_t; + + uint32_t _optimized; + + friend class MethodListWalker >; + void visitMethodList(objc_method_list_t

*mlist) + { + typename objc_method_t

::SortBySELAddress sorter; + std::stable_sort(mlist->begin(), mlist->end(), sorter); + mlist->setFixedUp(); + _optimized++; + } + + void visitProtocolMethodList(objc_method_list_t

*mlist, pint_t *typelist) + { + typename objc_method_t

::SortBySELAddress sorter; + // can't easily use std::stable_sort here + for (uint32_t i = 0; i < mlist->getCount(); i++) { + for (uint32_t j = i+1; j < mlist->getCount(); j++) { + objc_method_t

& mi = mlist->get(i); + objc_method_t

& mj = mlist->get(j); + if (! sorter(mi, mj)) { + std::swap(mi, mj); + if (typelist) std::swap(typelist[i], typelist[j]); + } + } + } + + mlist->setFixedUp(); + _optimized++; + } + +public: + MethodListSorter() : _optimized(0) { } + + size_t optimized() const { return _optimized; } + + void optimize(ContentAccessor* cache, const macho_header

* header) + { + MethodListWalker > mw(*this); + mw.walk(cache, header); + } +}; + + +template +class HeaderInfoOptimizer { +public: + + typedef typename P::uint_t pint_t; + + HeaderInfoOptimizer() : _hInfos(0), _count(0) { } + + const char* init(uint32_t count, uint8_t*& buf, size_t& bufSize) { + if (count == 0) + return nullptr; + + size_t requiredSize = + 2*sizeof(uint32_t) + count*sizeof(InfoT); + if (bufSize < requiredSize) { + return "libobjc's read/write section is too small (metadata not optimized)"; + } + + uint32_t *buf32 = (uint32_t *)buf; + P::E::set32(buf32[0], count); + P::E::set32(buf32[1], sizeof(InfoT)); + _hInfos = (InfoT*)(buf32+2); + + buf += requiredSize; + bufSize -= requiredSize; + + return nullptr; + } + + void update(ContentAccessor* cache, const macho_header

* mh, std::vector& pointersInData) { + InfoT* hi = new(&_hInfos[_count++]) InfoT(cache, mh); + (void)hi; + } + + InfoT* hinfoForHeader(ContentAccessor* cache, const macho_header

* mh) { + // FIXME: could be binary search + uint64_t mh_vmaddr = cache->vmAddrForContent((void*)mh); + for (size_t i = 0; i < _count; i++) { + InfoT* hi = &_hInfos[i]; + if (hi->header_vmaddr(cache) == mh_vmaddr) return hi; + } + return nullptr; + } +private: + InfoT* _hInfos; + size_t _count; +}; diff --git a/interlinked-dylibs/OptimizerBranches.cpp b/interlinked-dylibs/OptimizerBranches.cpp new file mode 100644 index 0000000..c982e95 --- /dev/null +++ b/interlinked-dylibs/OptimizerBranches.cpp @@ -0,0 +1,1472 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include "OptimizerBranches.h" +#include "Trie.hpp" +#include "MachOFileAbstraction.hpp" +#include "Logging.h" + +#include "dyld_cache_config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + + +static const bool verbose = false; + +// These are functions that are interposed by Instruments.app or ASan +static const char* sNeverStubEliminateSymbols[] = { + "___bzero", + "___cxa_atexit", + "___cxa_throw", + "__longjmp", + "__objc_autoreleasePoolPop", + "_accept", + "_access", + "_asctime", + "_asctime_r", + "_asprintf", + "_atoi", + "_atol", + "_atoll", + "_calloc", + "_chmod", + "_chown", + "_close", + "_confstr", + "_ctime", + "_ctime_r", + "_dispatch_after", + "_dispatch_after_f", + "_dispatch_async", + "_dispatch_async_f", + "_dispatch_barrier_async_f", + "_dispatch_group_async", + "_dispatch_group_async_f", + "_dispatch_source_set_cancel_handler", + "_dispatch_source_set_event_handler", + "_dispatch_sync_f", + "_dlclose", + "_dlopen", + "_dup", + "_dup2", + "_endgrent", + "_endpwent", + "_ether_aton", + "_ether_hostton", + "_ether_line", + "_ether_ntoa", + "_ether_ntohost", + "_fchmod", + "_fchown", + "_fclose", + "_fdopen", + "_fflush", + "_fopen", + "_fork", + "_fprintf", + "_free", + "_freopen", + "_frexp", + "_frexpf", + "_frexpl", + "_fscanf", + "_fstat", + "_fstatfs", + "_fstatfs64", + "_fsync", + "_ftime", + "_getaddrinfo", + "_getattrlist", + "_getcwd", + "_getgrent", + "_getgrgid", + "_getgrgid_r", + "_getgrnam", + "_getgrnam_r", + "_getgroups", + "_gethostbyaddr", + "_gethostbyname", + "_gethostbyname2", + "_gethostent", + "_getifaddrs", + "_getitimer", + "_getnameinfo", + "_getpass", + "_getpeername", + "_getpwent", + "_getpwnam", + "_getpwnam_r", + "_getpwuid", + "_getpwuid_r", + "_getsockname", + "_getsockopt", + "_gmtime", + "_gmtime_r", + "_if_indextoname", + "_if_nametoindex", + "_index", + "_inet_aton", + "_inet_ntop", + "_inet_pton", + "_initgroups", + "_ioctl", + "_lchown", + "_lgamma", + "_lgammaf", + "_lgammal", + "_link", + "_listxattr", + "_localtime", + "_localtime_r", + "_longjmp", + "_lseek", + "_lstat", + "_malloc", + "_malloc_create_zone", + "_malloc_default_purgeable_zone", + "_malloc_default_zone", + "_malloc_good_size", + "_malloc_make_nonpurgeable", + "_malloc_make_purgeable", + "_malloc_set_zone_name", + "_mbsnrtowcs", + "_mbsrtowcs", + "_mbstowcs", + "_memchr", + "_memcmp", + "_memcpy", + "_memmove", + "_memset", + "_mktime", + "_mlock", + "_mlockall", + "_modf", + "_modff", + "_modfl", + "_munlock", + "_munlockall", + "_objc_autoreleasePoolPop", + "_objc_setProperty", + "_objc_setProperty_atomic", + "_objc_setProperty_atomic_copy", + "_objc_setProperty_nonatomic", + "_objc_setProperty_nonatomic_copy", + "_objc_storeStrong", + "_open", + "_opendir", + "_poll", + "_posix_memalign", + "_pread", + "_printf", + "_pthread_attr_getdetachstate", + "_pthread_attr_getguardsize", + "_pthread_attr_getinheritsched", + "_pthread_attr_getschedparam", + "_pthread_attr_getschedpolicy", + "_pthread_attr_getscope", + "_pthread_attr_getstack", + "_pthread_attr_getstacksize", + "_pthread_condattr_getpshared", + "_pthread_create", + "_pthread_getschedparam", + "_pthread_join", + "_pthread_mutex_lock", + "_pthread_mutex_unlock", + "_pthread_mutexattr_getprioceiling", + "_pthread_mutexattr_getprotocol", + "_pthread_mutexattr_getpshared", + "_pthread_mutexattr_gettype", + "_pthread_rwlockattr_getpshared", + "_pwrite", + "_rand_r", + "_read", + "_readdir", + "_readdir_r", + "_readv", + "_readv$UNIX2003", + "_realloc", + "_realpath", + "_recv", + "_recvfrom", + "_recvmsg", + "_remquo", + "_remquof", + "_remquol", + "_scanf", + "_send", + "_sendmsg", + "_sendto", + "_setattrlist", + "_setgrent", + "_setitimer", + "_setlocale", + "_setpwent", + "_shm_open", + "_shm_unlink", + "_sigaction", + "_sigemptyset", + "_sigfillset", + "_siglongjmp", + "_signal", + "_sigpending", + "_sigprocmask", + "_sigwait", + "_snprintf", + "_sprintf", + "_sscanf", + "_stat", + "_statfs", + "_statfs64", + "_strcasecmp", + "_strcat", + "_strchr", + "_strcmp", + "_strcpy", + "_strdup", + "_strerror", + "_strerror_r", + "_strlen", + "_strncasecmp", + "_strncat", + "_strncmp", + "_strncpy", + "_strptime", + "_strtoimax", + "_strtol", + "_strtoll", + "_strtoumax", + "_tempnam", + "_time", + "_times", + "_tmpnam", + "_tsearch", + "_unlink", + "_valloc", + "_vasprintf", + "_vfprintf", + "_vfscanf", + "_vprintf", + "_vscanf", + "_vsnprintf", + "_vsprintf", + "_vsscanf", + "_wait", + "_wait$UNIX2003", + "_wait3", + "_wait4", + "_waitid", + "_waitid$UNIX2003", + "_waitpid", + "_waitpid$UNIX2003", + "_wcslen", + "_wcsnrtombs", + "_wcsrtombs", + "_wcstombs", + "_wordexp", + "_write", + "_writev", + "_writev$UNIX2003", + // always use stubs for C++ symbols that can be overridden + "__ZdaPv", + "__ZdlPv", + "__Znam", + "__Znwm", + + nullptr +}; + + +// These are dylibs that are interposed and stubs calling into them should never be bypassed +static const char* sNeverStubEliminateDylibs[] = { + "/usr/lib/system/libdispatch.dylib", + nullptr +}; + + + +uint64_t branchPoolTextSize(ArchPair arch) +{ + if ( arch.arch == CPU_TYPE_ARM64 ) + return 0x0000C000; // 48KB + else + return 0; +} + +uint64_t branchPoolLinkEditSize(ArchPair arch) +{ + if ( arch.arch == CPU_TYPE_ARM64 ) + return 0x00100000; // 1MB + else + return 0; +} + + +uint64_t branchReach(ArchPair arch) +{ + if ( arch.arch == CPU_TYPE_ARM64 ) + return 0x08000000 - (2*branchPoolTextSize(arch)); // 128MB + else + return 0; +} + +template +class BranchPoolDylib { +public: + BranchPoolDylib(ArchPair arch, void* cacheBuffer, uint64_t cacheSize, uint64_t startAddr, + uint64_t textRegionStartAddr, uint64_t poolLinkEditStartAddr, uint64_t poolLinkEditStartOffset); + + uint64_t addr() { return _startAddr; } + uint64_t getForwardBranch(uint64_t finalTargetAddr, const char* name, std::vector*>& branchIslandPools); + uint64_t getBackBranch(uint64_t finalTargetAddr, const char* name, std::vector*>& branchIslandPools); + void finalizeLoadCommands(); + void printStats(); + +private: + uint64_t indexToAddr(uint32_t index) { return _startAddr + _firstStubOffset + sizeof(uint32_t)*index; } + + static const int64_t b128MegLimit = 0x07FFFFFF; + + typedef typename P::uint_t pint_t; + typedef typename P::E E; + + ArchPair _arch; + void* _cacheBuffer; + uint64_t _startAddr; + std::unordered_map _targetToIslandIndex; + std::unordered_map _islandIndexToName; + macho_symtab_command

* _symbolTableCmd; + macho_dysymtab_command

* _dynamicSymbolTableCmd; + macho_uuid_command

* _uuidCmd; + uint32_t _maxStubs; + uint32_t _nextIndex; + uint32_t _firstStubOffset; + uint32_t* _stubInstructions; + macho_nlist

* _symbolTable; + char* _nextString; + char* _stringPoolStart; + char* _stringPoolEnd; +}; + + +template +BranchPoolDylib

::BranchPoolDylib(ArchPair arch, void* cacheBuffer, uint64_t cacheSize, uint64_t poolStartAddr, + uint64_t textRegionStartAddr, uint64_t poolLinkEditStartAddr, uint64_t poolLinkEditStartOffset) + : _arch(arch), _cacheBuffer(cacheBuffer),_startAddr(poolStartAddr), _nextIndex(0), _firstStubOffset(0x280) +{ + bool is64 = (sizeof(typename P::uint_t) == 8); + + const uint64_t textSegSize = branchPoolTextSize(arch); + const uint64_t linkEditSegSize = branchPoolLinkEditSize(arch); + const unsigned stubCount = (unsigned)((textSegSize - _firstStubOffset)/4); + const uint32_t linkeditOffsetSymbolTable = 0; + const uint32_t linkeditOffsetIndirectSymbolTable = stubCount*sizeof(macho_nlist

); + const uint32_t linkeditOffsetSymbolPoolOffset = linkeditOffsetIndirectSymbolTable + stubCount*sizeof(uint32_t); + _maxStubs = stubCount; + + // write mach_header and load commands for pseudo dylib + macho_header

* mh = (macho_header

*)((uint8_t*)cacheBuffer + poolStartAddr - textRegionStartAddr); + mh->set_magic(is64 ? MH_MAGIC_64 : MH_MAGIC); + mh->set_cputype(arch.arch); + mh->set_cpusubtype(arch.subtype); + mh->set_filetype(MH_DYLIB); + mh->set_ncmds(6); + mh->set_sizeofcmds(is64 ? 0x210 : 100); // FIXME: 32-bit size + mh->set_flags(0x80000000); + // LC_SEGMENT + macho_load_command

* cmd = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + macho_segment_command

* textSegCmd = (macho_segment_command

*)cmd; + textSegCmd->set_cmd(is64 ? LC_SEGMENT_64 : LC_SEGMENT); + textSegCmd->set_cmdsize(sizeof(macho_segment_command

)*2+sizeof(macho_section

)); + textSegCmd->set_segname("__TEXT"); + textSegCmd->set_vmaddr(poolStartAddr); + textSegCmd->set_vmsize(textSegSize); + textSegCmd->set_fileoff(poolStartAddr - textRegionStartAddr); + textSegCmd->set_filesize(branchPoolTextSize(arch)); + textSegCmd->set_maxprot(PROT_READ|PROT_EXEC); + textSegCmd->set_initprot(PROT_READ|PROT_EXEC); + textSegCmd->set_nsects(1); + textSegCmd->set_flags(0); + macho_section

* stubSection = (macho_section

*)((uint8_t*)textSegCmd + sizeof(macho_segment_command

)); + stubSection->set_sectname("__stubs"); + stubSection->set_segname("__TEXT"); + stubSection->set_addr(poolStartAddr + _firstStubOffset); + stubSection->set_size(textSegSize - _firstStubOffset); + stubSection->set_offset((uint32_t)(poolStartAddr + _firstStubOffset - textRegionStartAddr)); + stubSection->set_align(2); + stubSection->set_reloff(0); + stubSection->set_nreloc(0); + stubSection->set_flags(S_SYMBOL_STUBS | S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS); + stubSection->set_reserved1(0); // start index in indirect table + stubSection->set_reserved2(4); // size of stubs + // LC_SEGMENT + cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + macho_segment_command

* linkEditSegCmd = (macho_segment_command

*)cmd; + linkEditSegCmd->set_cmd(is64 ? LC_SEGMENT_64 : LC_SEGMENT); + linkEditSegCmd->set_cmdsize(sizeof(macho_segment_command

)); + linkEditSegCmd->set_segname("__LINKEDIT"); + linkEditSegCmd->set_vmaddr(poolLinkEditStartAddr); + linkEditSegCmd->set_vmsize(linkEditSegSize); + linkEditSegCmd->set_fileoff(poolLinkEditStartOffset); + linkEditSegCmd->set_filesize(linkEditSegSize); + linkEditSegCmd->set_maxprot(PROT_READ); + linkEditSegCmd->set_initprot(PROT_READ); + linkEditSegCmd->set_nsects(0); + linkEditSegCmd->set_flags(0); + // LC_ID_DYLIB + cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + macho_dylib_command

* installNameCmd = (macho_dylib_command

*)cmd; + installNameCmd->set_cmd(LC_ID_DYLIB); + installNameCmd->set_cmdsize(sizeof(macho_dylib_command

) + 48); + installNameCmd->set_timestamp(2); + installNameCmd->set_current_version(0x10000); + installNameCmd->set_compatibility_version(0x10000); + installNameCmd->set_name_offset(); + strcpy((char*)cmd + sizeof(macho_dylib_command

), "dyld_shared_cache_branch_islands"); + // LC_SYMTAB + cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + _symbolTableCmd = (macho_symtab_command

*)cmd; + _symbolTableCmd->set_cmd(LC_SYMTAB); + _symbolTableCmd->set_cmdsize(sizeof(macho_symtab_command

)); + _symbolTableCmd->set_nsyms(stubCount); + _symbolTableCmd->set_symoff((uint32_t)(poolLinkEditStartOffset + linkeditOffsetSymbolTable)); + _symbolTableCmd->set_stroff((uint32_t)(poolLinkEditStartOffset + linkeditOffsetSymbolPoolOffset)); + _symbolTableCmd->set_strsize((uint32_t)(linkEditSegSize - linkeditOffsetSymbolPoolOffset)); + // LC_DYSYMTAB + cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + _dynamicSymbolTableCmd = (macho_dysymtab_command

*)cmd; + _dynamicSymbolTableCmd->set_cmd(LC_DYSYMTAB); + _dynamicSymbolTableCmd->set_cmdsize(sizeof(macho_dysymtab_command

)); + _dynamicSymbolTableCmd->set_ilocalsym(0); + _dynamicSymbolTableCmd->set_nlocalsym(0); + _dynamicSymbolTableCmd->set_iextdefsym(0); + _dynamicSymbolTableCmd->set_nextdefsym(0); + _dynamicSymbolTableCmd->set_iundefsym(0); + _dynamicSymbolTableCmd->set_nundefsym(stubCount); + _dynamicSymbolTableCmd->set_tocoff(0); + _dynamicSymbolTableCmd->set_ntoc(0); + _dynamicSymbolTableCmd->set_modtaboff(0); + _dynamicSymbolTableCmd->set_nmodtab(0); + _dynamicSymbolTableCmd->set_extrefsymoff(0); + _dynamicSymbolTableCmd->set_nextrefsyms(0); + _dynamicSymbolTableCmd->set_indirectsymoff((uint32_t)(poolLinkEditStartOffset + linkeditOffsetIndirectSymbolTable)); + _dynamicSymbolTableCmd->set_nindirectsyms(stubCount); + _dynamicSymbolTableCmd->set_extreloff(0); + _dynamicSymbolTableCmd->set_nextrel(0); + _dynamicSymbolTableCmd->set_locreloff(0); + _dynamicSymbolTableCmd->set_nlocrel(0); + cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + // LC_UUID + _uuidCmd = (macho_uuid_command

*)cmd; + _uuidCmd->set_cmd(LC_UUID); + _uuidCmd->set_cmdsize(sizeof(macho_uuid_command

)); + cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + + // write stubs section content + _stubInstructions = (uint32_t*)((uint8_t*)mh + _firstStubOffset); + for (int i=0; i < stubCount; ++i) { + E::set32(_stubInstructions[i], 0xD4200000); + } + + // write linkedit content + uint8_t* linkeditBufferStart = (uint8_t*)cacheBuffer + poolLinkEditStartOffset; + // write symbol table + _symbolTable = (macho_nlist

*)(linkeditBufferStart); + for (int i=0; i < stubCount; ++i) { + _symbolTable[i].set_n_strx(1); + _symbolTable[i].set_n_type(N_UNDF | N_EXT); + _symbolTable[i].set_n_sect(0); + _symbolTable[i].set_n_desc(0); + _symbolTable[i].set_n_value(0); + } + // write indirect symbol table + uint32_t* indirectSymboTable = (uint32_t*)(linkeditBufferStart + linkeditOffsetIndirectSymbolTable); + for (int i=0; i < stubCount; ++i) { + P::E::set32(indirectSymboTable[i], i); + } + // write string pool + _stringPoolStart = (char*)(linkeditBufferStart + linkeditOffsetSymbolPoolOffset); + _stringPoolEnd = _stringPoolStart + linkEditSegSize - linkeditOffsetSymbolPoolOffset; + _stringPoolStart[0] = '\0'; + strcpy(&_stringPoolStart[1], ""); + _nextString = &_stringPoolStart[10]; +} + + +template +void BranchPoolDylib

::finalizeLoadCommands() +{ + _symbolTableCmd->set_nsyms(_nextIndex); + _symbolTableCmd->set_strsize((uint32_t)(_nextString - _stringPoolStart)); + _dynamicSymbolTableCmd->set_nundefsym(_nextIndex); + + uint8_t digest[CC_MD5_DIGEST_LENGTH]; + CC_MD5(_stubInstructions, _maxStubs*sizeof(uint64_t), digest); + _uuidCmd->set_uuid(digest); + + if ( verbose ) { + verboseLog("branch islands in image at 0x%0llX:", _startAddr); + for (uint32_t i=0; i < _nextIndex; ++i) { + verboseLog(" 0x%llX %s", indexToAddr(i), _islandIndexToName[i]); + } + } +} + +template +uint64_t BranchPoolDylib

::getForwardBranch(uint64_t finalTargetAddr, const char* name, std::vector*>& branchIslandPools) +{ + // check if we can re-used existing branch island + const auto& pos = _targetToIslandIndex.find(finalTargetAddr); + if ( pos != _targetToIslandIndex.end() ) + return indexToAddr(pos->second); + + // skip if instruction pool is full + if ( _nextIndex >= _maxStubs ) + return 0; + + // skip if string pool is full + if ( (_nextString + strlen(name)+1) >= _stringPoolEnd ) + return 0; + + uint64_t branchIslandTargetAddr = finalTargetAddr; + // if final target is too far, we need to use branch island in next pool + if ( (finalTargetAddr - _startAddr) > b128MegLimit ) { + BranchPoolDylib

* nextPool = nullptr; + for (size_t i=0; i < branchIslandPools.size()-1; ++i) { + if ( branchIslandPools[i] == this ) { + nextPool = branchIslandPools[i+1]; + break; + } + } + + if (nextPool == nullptr) { + warning("BranchPoolDylib

::getForwardBranch: nextPool unreachable"); + return 0; + } + + branchIslandTargetAddr = nextPool->getForwardBranch(finalTargetAddr, name, branchIslandPools); + if ( branchIslandTargetAddr == 0 ) + return 0; // next pool is full + } + + // write branch instruction in stubs section + uint32_t index = _nextIndex++; + int64_t branchDelta = branchIslandTargetAddr - indexToAddr(index); + uint32_t branchInstr = 0x14000000 + ((branchDelta/4) & 0x03FFFFFF); + E::set32(_stubInstructions[index], branchInstr); + + // update symbol table + _symbolTable[index].set_n_strx((uint32_t)(_nextString - _stringPoolStart)); + strcpy(_nextString, name); + _nextString += (strlen(name) +1); + + // record island + _targetToIslandIndex[finalTargetAddr] = index; + _islandIndexToName[index] = name; + return indexToAddr(index); +} + +template +uint64_t BranchPoolDylib

::getBackBranch(uint64_t finalTargetAddr, const char* name, std::vector*>& branchIslandPools) +{ + // check if we can re-used existing branch island + const auto& pos = _targetToIslandIndex.find(finalTargetAddr); + if ( pos != _targetToIslandIndex.end() ) + return indexToAddr(pos->second); + + // skip if instruction pool is full + if ( _nextIndex >= _maxStubs ) + return 0; + + // skip if string pool is full + if ( (_nextString + strlen(name)+1) >= _stringPoolEnd ) + return 0; + + uint64_t branchIslandTargetAddr = finalTargetAddr; + // if final target is too far, we need to use branch island in next pool + if ( (indexToAddr(_nextIndex) - finalTargetAddr) > b128MegLimit ) { + BranchPoolDylib

* nextPool = nullptr; + for (long i=branchIslandPools.size()-1; i > 0; --i) { + if ( branchIslandPools[i] == this ) { + nextPool = branchIslandPools[i-1]; + break; + } + } + + if (nextPool == nullptr) { + warning("BranchPoolDylib

::getBackBranch: nextPool unreachable"); + return 0; + } + + branchIslandTargetAddr = nextPool->getBackBranch(finalTargetAddr, name, branchIslandPools); + if ( branchIslandTargetAddr == 0 ) + return 0; // next pool is full + } + + // write branch instruction in stubs section + uint32_t index = _nextIndex++; + int64_t branchDelta = branchIslandTargetAddr - indexToAddr(index); + uint32_t branchInstr = 0x14000000 + ((branchDelta/4) & 0x03FFFFFF); + E::set32(_stubInstructions[index], branchInstr); + + // update symbol table + _symbolTable[index].set_n_strx((uint32_t)(_nextString - _stringPoolStart)); + strcpy(_nextString, name); + _nextString += (strlen(name) +1); + + // record island + _targetToIslandIndex[finalTargetAddr] = index; + _islandIndexToName[index] = name; + return indexToAddr(index); +} + +template +void BranchPoolDylib

::printStats() +{ + verboseLog(" island pool at 0x%0llX has %u stubs and stringPool size=%lu", _startAddr, _nextIndex, _nextString-_stringPoolStart); +} + + + +template +class StubOptimizer { +public: + StubOptimizer(void* cacheBuffer, macho_header

* mh); + void buildStubMap(const std::unordered_set& neverStubEliminate); + void optimizeStubs(std::unordered_map>& targetToBranchIslands); + void bypassStubs(std::unordered_map>& targetToBranchIslands); + void optimizeCallSites(std::vector*>& branchIslandPools); + const char* installName() { return _installName; } + const uint8_t* exportsTrie() { return (uint8_t*)_cacheBuffer + _dyldInfo->export_off(); } + uint32_t exportsTrieSize() { return _dyldInfo->export_size(); } + + uint32_t _stubCount = 0; + uint32_t _stubOptimizedCount = 0; + uint32_t _branchesCount = 0; + uint32_t _branchesModifiedCount = 0; + uint32_t _branchesDirectCount = 0; + uint32_t _branchesIslandCount = 0; + +private: + + typedef std::function CallSiteHandler; + typedef typename P::uint_t pint_t; + typedef typename P::E E; + + void forEachCallSiteToAStub(CallSiteHandler); + void optimizeArm64CallSites(std::vector*>& branchIslandPools); + void optimizeArmCallSites(); + void optimizeArmStubs(); + uint64_t lazyPointerAddrFromArm64Stub(const uint8_t* stubInstructions, uint64_t stubVMAddr); + uint32_t lazyPointerAddrFromArmStub(const uint8_t* stubInstructions, uint32_t stubVMAddr); + int32_t getDisplacementFromThumbBranch(uint32_t instruction, uint32_t instrAddr); + uint32_t setDisplacementInThumbBranch(uint32_t instruction, uint32_t instrAddr, + int32_t displacement, bool targetIsThumb); + + + struct AddressAndName { pint_t targetVMAddr; const char* targetName; }; + typedef std::unordered_map StubVMAddrToTarget; + + static const int64_t b128MegLimit = 0x07FFFFFF; + static const int64_t b16MegLimit = 0x00FFFFFF; + + + macho_header

* _mh; + void* _cacheBuffer; + uint32_t _linkeditSize = 0; + uint32_t _linkeditCacheOffset = 0; + uint64_t _linkeditAddr = 0; + const uint8_t* _linkeditBias = nullptr; + const char* _installName = nullptr; + const macho_symtab_command

* _symTabCmd = nullptr; + const macho_dysymtab_command

* _dynSymTabCmd = nullptr; + const macho_dyld_info_command

* _dyldInfo = nullptr; + macho_linkedit_data_command

* _splitSegInfoCmd = nullptr; + const macho_section

* _textSection = nullptr; + const macho_section

* _stubSection = nullptr; + uint32_t _textSectionIndex = 0; + uint32_t _stubSectionIndex = 0; + pint_t _textSegStartAddr = 0; + uint32_t _textSegCacheOffset = 0; + std::vector*> _segCmds; + std::unordered_map _stubAddrToLPAddr; + std::unordered_map _lpAddrToTargetAddr; + std::unordered_map _targetAddrToName; +}; + + + +template +StubOptimizer

::StubOptimizer(void* cacheBuffer, macho_header

* mh) +: _mh(mh), _cacheBuffer(cacheBuffer) +{ + _linkeditBias = (uint8_t*)cacheBuffer; + const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + const uint32_t cmd_count = mh->ncmds(); + macho_segment_command

* segCmd; + uint32_t sectionIndex = 0; + const macho_load_command

* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd()) { + case LC_ID_DYLIB: + _installName = ((macho_dylib_command

*)cmd)->name(); + break; + case LC_SYMTAB: + _symTabCmd = (macho_symtab_command

*)cmd; + break; + case LC_DYSYMTAB: + _dynSymTabCmd = (macho_dysymtab_command

*)cmd; + break; + case LC_SEGMENT_SPLIT_INFO: + _splitSegInfoCmd = (macho_linkedit_data_command

*)cmd; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + _dyldInfo = (macho_dyld_info_command

*)cmd; + break; + case macho_segment_command

::CMD: + segCmd =( macho_segment_command

*)cmd; + _segCmds.push_back(segCmd); + if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { + _linkeditSize = (uint32_t)segCmd->vmsize(); + _linkeditCacheOffset = (uint32_t)segCmd->fileoff(); + _linkeditAddr = segCmd->vmaddr(); + } + else if ( strcmp(segCmd->segname(), "__TEXT") == 0 ) { + _textSegStartAddr = (pint_t)segCmd->vmaddr(); + _textSegCacheOffset = (uint32_t)((uint8_t*)mh - (uint8_t*)cacheBuffer); + const macho_section

* const sectionsStart = (macho_section

*)((char*)segCmd + sizeof(macho_segment_command

)); + const macho_section

* const sectionsEnd = §ionsStart[segCmd->nsects()]; + for (const macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { + ++sectionIndex; + if ( strcmp(sect->sectname(), "__text") == 0 ) { + _textSection = sect; + _textSectionIndex = sectionIndex; + } + else if ( ((sect->flags() & SECTION_TYPE) == S_SYMBOL_STUBS) && (sect->size() != 0) ) { + _stubSection = sect; + _stubSectionIndex = sectionIndex; + } + } + } + break; + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } +} + + + +template +uint32_t StubOptimizer

::lazyPointerAddrFromArmStub(const uint8_t* stubInstructions, uint32_t stubVMAddr) +{ + uint32_t stubInstr1 = E::get32(*(uint32_t*)stubInstructions); + uint32_t stubInstr2 = E::get32(*(uint32_t*)(stubInstructions+4)); + uint32_t stubInstr3 = E::get32(*(uint32_t*)(stubInstructions+8)); + int32_t stubData = E::get32(*(uint32_t*)(stubInstructions+12)); + if ( stubInstr1 != 0xe59fc004 ) { + warning("first instruction of stub (0x%08X) is not 'ldr ip, pc + 12' for stub at addr 0x%0llX in %s", + stubInstr1, (uint64_t)stubVMAddr, _installName); + return 0; + } + if ( stubInstr2 != 0xe08fc00c ) { + warning("second instruction of stub (0x%08X) is not 'add ip, pc, ip' for stub at addr 0x%0llX in %s", + stubInstr1, (uint64_t)stubVMAddr, _installName); + return 0; + } + if ( stubInstr3 != 0xe59cf000 ) { + warning("third instruction of stub (0x%08X) is not 'ldr pc, [ip]' for stub at addr 0x%0llX in %s", + stubInstr1, (uint64_t)stubVMAddr, _installName); + return 0; + } + return stubVMAddr + 12 + stubData; +} + + +template +uint64_t StubOptimizer

::lazyPointerAddrFromArm64Stub(const uint8_t* stubInstructions, uint64_t stubVMAddr) +{ + uint32_t stubInstr1 = E::get32(*(uint32_t*)stubInstructions); + if ( (stubInstr1 & 0x9F00001F) != 0x90000010 ) { + warning("first instruction of stub (0x%08X) is not ADRP for stub at addr 0x%0llX in %s", + stubInstr1, (uint64_t)stubVMAddr, _installName); + return 0; + } + int32_t adrpValue = ((stubInstr1 & 0x00FFFFE0) >> 3) | ((stubInstr1 & 0x60000000) >> 29); + if ( stubInstr1 & 0x00800000 ) + adrpValue |= 0xFFF00000; + uint32_t stubInstr2 = E::get32(*(uint32_t*)(stubInstructions + 4)); + if ( (stubInstr2 & 0xFFC003FF) != 0xF9400210 ) { + warning("second instruction of stub (0x%08X) is not LDR for stub at addr 0x%0llX in %s", + stubInstr2, (uint64_t)stubVMAddr, _installName); + return 0; + } + uint32_t ldrValue = ((stubInstr2 >> 10) & 0x00000FFF); + return (stubVMAddr & (-4096)) + adrpValue*4096 + ldrValue*8; +} + + + +template +void StubOptimizer

::buildStubMap(const std::unordered_set& neverStubEliminate) +{ + // find all stubs and lazy pointers + const macho_nlist

* symbolTable = (const macho_nlist

*)(((uint8_t*)_cacheBuffer) + _symTabCmd->symoff()); + const char* symbolStrings = (char*)_cacheBuffer + _symTabCmd->stroff(); + const uint32_t* const indirectTable = (uint32_t*)(((uint8_t*)_cacheBuffer) + _dynSymTabCmd->indirectsymoff()); + const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)_mh + sizeof(macho_header

)); + const uint32_t cmd_count = _mh->ncmds(); + const macho_load_command

* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd() == macho_segment_command

::CMD ) { + macho_segment_command

* seg = (macho_segment_command

*)cmd; + macho_section

* const sectionsStart = (macho_section

*)((char*)seg + sizeof(macho_segment_command

)); + macho_section

* const sectionsEnd = §ionsStart[seg->nsects()]; + for(macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { + if ( sect->size() == 0 ) + continue; + unsigned sectionType = (sect->flags() & SECTION_TYPE); + const uint32_t indirectTableOffset = sect->reserved1(); + if ( sectionType == S_SYMBOL_STUBS ) { + const uint32_t stubSize = sect->reserved2(); + _stubCount = (uint32_t)(sect->size() / stubSize); + pint_t stubVMAddr = (pint_t)sect->addr(); + for (uint32_t j=0; j < _stubCount; ++j, stubVMAddr += stubSize) { + uint32_t symbolIndex = E::get32(indirectTable[indirectTableOffset + j]); + switch ( symbolIndex ) { + case INDIRECT_SYMBOL_ABS: + case INDIRECT_SYMBOL_LOCAL: + break; + default: + if ( symbolIndex >= _symTabCmd->nsyms() ) { + warning("symbol index out of range (%d of %d) for stub at addr 0x%0llX in %s", + symbolIndex, _symTabCmd->nsyms(), (uint64_t)stubVMAddr, _installName); + continue; + } + const macho_nlist

* sym = &symbolTable[symbolIndex]; + uint32_t stringOffset = sym->n_strx(); + if ( stringOffset > _symTabCmd->strsize() ) { + warning("symbol string offset out of range (%u of %u) for stub at addr 0x%0llX in %s", + stringOffset, sym->n_strx(), (uint64_t)stubVMAddr, _installName); + continue; + } + const char* symName = &symbolStrings[stringOffset]; + if ( neverStubEliminate.count(symName) ) { + //verboseLog("not bypassing stub to %s in %s because target is interposable\n", symName, _installName); + continue; + } + const uint8_t* stubInstrs = (uint8_t*)_cacheBuffer + sect->offset() + stubVMAddr - sect->addr(); + pint_t targetLPAddr = 0; + switch ( _mh->cputype() ) { + case CPU_TYPE_ARM64: + targetLPAddr = (pint_t)lazyPointerAddrFromArm64Stub(stubInstrs, stubVMAddr); + break; + case CPU_TYPE_ARM: + targetLPAddr = (pint_t)lazyPointerAddrFromArmStub(stubInstrs, (uint32_t)stubVMAddr); + break; + } + if ( targetLPAddr != 0 ) + _stubAddrToLPAddr[stubVMAddr] = targetLPAddr; + break; + } + } + } + else if ( sectionType == S_LAZY_SYMBOL_POINTERS ) { + pint_t lpVMAddr = (pint_t)sect->addr(); + pint_t* lpContent = (pint_t*)(((uint8_t*)_cacheBuffer) + sect->offset()); + uint32_t elementCount = (uint32_t)(sect->size() / sizeof(pint_t)); + uint64_t textSegStartAddr = _segCmds[0]->vmaddr(); + uint64_t textSegEndAddr = _segCmds[0]->vmaddr() + _segCmds[0]->vmsize(); + pint_t lpValue; + for (uint32_t j=0; j < elementCount; ++j) { + uint32_t symbolIndex = E::get32(indirectTable[indirectTableOffset + j]); + switch ( symbolIndex ) { + case INDIRECT_SYMBOL_ABS: + case INDIRECT_SYMBOL_LOCAL: + break; + default: + lpValue = (pint_t)P::getP(lpContent[j]); + if ( symbolIndex >= _symTabCmd->nsyms() ) { + warning("symbol index out of range (%d of %d) for lazy pointer at addr 0x%0llX in %s", + symbolIndex, _symTabCmd->nsyms(), (uint64_t)lpVMAddr, _installName); + continue; + } + const macho_nlist

* sym = &symbolTable[symbolIndex]; + uint32_t stringOffset = sym->n_strx(); + if ( stringOffset > _symTabCmd->strsize() ) { + warning("symbol string offset out of range (%u of %u) for lazy pointer at addr 0x%0llX in %s", + stringOffset, sym->n_strx(), (uint64_t)lpVMAddr, _installName); + continue; + } + const char* symName = &symbolStrings[stringOffset]; + if ( (lpValue > textSegStartAddr) && (lpValue< textSegEndAddr) ) { + //verboseLog("skipping lazy pointer at 0x%0lX to %s in %s because target is within dylib\n", lpVMAddr, symName, _installName); + } + else if ( (sizeof(pint_t) == 8) && ((lpValue % 4) != 0) ) { + warning("lazy pointer at 0x%0llX does not point to 4-byte aligned address(0x%0llX) in %s", + (uint64_t)lpVMAddr, (uint64_t)lpValue, _installName); + } + else { + _lpAddrToTargetAddr[lpVMAddr] = lpValue; + _targetAddrToName[lpValue] = symName; + } + break; + } + lpVMAddr += sizeof(pint_t); + } + } + } + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } +} + + +template +void StubOptimizer

::forEachCallSiteToAStub(CallSiteHandler handler) +{ + const uint8_t* infoStart = &_linkeditBias[_splitSegInfoCmd->dataoff()]; + const uint8_t* infoEnd = &infoStart[_splitSegInfoCmd->datasize()]; + if ( *infoStart++ != DYLD_CACHE_ADJ_V2_FORMAT ) + terminate("malformed split seg info in %s", _installName); + + uint8_t* textSectionContent = (uint8_t*)_cacheBuffer + _textSegCacheOffset + _textSection->addr() -_textSegStartAddr; + + // Whole :== FromToSection+ + // FromToSection :== ToOffset+ + // ToOffset :== FromOffset+ + // FromOffset :== + const uint8_t* p = infoStart; + uint64_t sectionCount = read_uleb128(p, infoEnd); + for (uint64_t i=0; i < sectionCount; ++i) { + uint64_t fromSectionIndex = read_uleb128(p, infoEnd); + uint64_t toSectionIndex = read_uleb128(p, infoEnd); + uint64_t toOffsetCount = read_uleb128(p, infoEnd); + uint64_t toSectionOffset = 0; + for (uint64_t j=0; j < toOffsetCount; ++j) { + uint64_t toSectionDelta = read_uleb128(p, infoEnd); + uint64_t fromOffsetCount = read_uleb128(p, infoEnd); + toSectionOffset += toSectionDelta; + for (uint64_t k=0; k < fromOffsetCount; ++k) { + uint64_t kind = read_uleb128(p, infoEnd); + if ( kind > 12 ) + terminate("bad kind (%llu) value in %s", kind, _installName); + uint64_t fromSectDeltaCount = read_uleb128(p, infoEnd); + uint64_t fromSectionOffset = 0; + for (uint64_t l=0; l < fromSectDeltaCount; ++l) { + uint64_t delta = read_uleb128(p, infoEnd); + fromSectionOffset += delta; + if ( (fromSectionIndex == _textSectionIndex) && (toSectionIndex == _stubSectionIndex) ) { + uint32_t* instrPtr = (uint32_t*)(textSectionContent + fromSectionOffset); + uint64_t instrAddr = _textSection->addr() + fromSectionOffset; + uint64_t stubAddr = _stubSection->addr() + toSectionOffset; + uint32_t instruction = E::get32(*instrPtr); + _branchesCount++; + if ( handler(kind, instrAddr, stubAddr, instruction) ) { + _branchesModifiedCount++; + E::set32(*instrPtr, instruction); + } + } + } + } + } + } +} + + +/// Extract displacement from a thumb b/bl/blx instruction. +template +int32_t StubOptimizer

::getDisplacementFromThumbBranch(uint32_t instruction, uint32_t instrAddr) +{ + bool is_blx = ((instruction & 0xD000F800) == 0xC000F000); + uint32_t s = (instruction >> 10) & 0x1; + uint32_t j1 = (instruction >> 29) & 0x1; + uint32_t j2 = (instruction >> 27) & 0x1; + uint32_t imm10 = instruction & 0x3FF; + uint32_t imm11 = (instruction >> 16) & 0x7FF; + uint32_t i1 = (j1 == s); + uint32_t i2 = (j2 == s); + uint32_t dis = (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1); + int32_t sdis = dis; + int32_t result = s ? (sdis | 0xFE000000) : sdis; + if ( is_blx && (instrAddr & 0x2) ) { + // The thumb blx instruction always has low bit of imm11 as zero. The way + // a 2-byte aligned blx can branch to a 4-byte aligned ARM target is that + // the blx instruction always 4-byte aligns the pc before adding the + // displacement from the blx. We must emulate that when decoding this. + result -= 2; + } + return result; +} + +/// Update a thumb b/bl/blx instruction, switching bl <-> blx as needed. +template +uint32_t StubOptimizer

::setDisplacementInThumbBranch(uint32_t instruction, uint32_t instrAddr, + int32_t displacement, bool targetIsThumb) { + if ( (displacement > 16777214) || (displacement < (-16777216)) ) + terminate("thumb branch out of range at 0x%0X in %s", instrAddr, _installName); + bool is_bl = ((instruction & 0xD000F800) == 0xD000F000); + bool is_blx = ((instruction & 0xD000F800) == 0xC000F000); + bool is_b = ((instruction & 0xD000F800) == 0x9000F000); + uint32_t newInstruction = (instruction & 0xD000F800); + if (is_bl || is_blx) { + if (targetIsThumb) { + newInstruction = 0xD000F000; // Use bl + } + else { + newInstruction = 0xC000F000; // Use blx + // See note in getDisplacementFromThumbBranch() about blx. + if (instrAddr & 0x2) + displacement += 2; + } + } + else if (is_b) { + if ( !targetIsThumb ) + terminate("no pc-rel thumb branch instruction that switches to arm mode at 0x%0X in %s", instrAddr, _installName); + } + else { + terminate("not b/bl/blx at 0x%0X in %s", instrAddr, _installName); + } + uint32_t s = (uint32_t)(displacement >> 24) & 0x1; + uint32_t i1 = (uint32_t)(displacement >> 23) & 0x1; + uint32_t i2 = (uint32_t)(displacement >> 22) & 0x1; + uint32_t imm10 = (uint32_t)(displacement >> 12) & 0x3FF; + uint32_t imm11 = (uint32_t)(displacement >> 1) & 0x7FF; + uint32_t j1 = (i1 == s); + uint32_t j2 = (i2 == s); + uint32_t nextDisp = (j1 << 13) | (j2 << 11) | imm11; + uint32_t firstDisp = (s << 10) | imm10; + newInstruction |= (nextDisp << 16) | firstDisp; + return newInstruction; +} + + +template +void StubOptimizer

::optimizeArmCallSites() +{ + forEachCallSiteToAStub([&](uint8_t kind, uint64_t callSiteAddr, uint64_t stubAddr, uint32_t& instruction) -> bool { + if ( kind == DYLD_CACHE_ADJ_V2_THUMB_BR22 ) { + bool is_bl = ((instruction & 0xD000F800) == 0xD000F000); + bool is_blx = ((instruction & 0xD000F800) == 0xC000F000); + bool is_b = ((instruction & 0xD000F800) == 0x9000F000); + if ( !is_bl && !is_blx && !is_b ){ + warning("non-branch instruction at 0x%0llX in %s", callSiteAddr, _installName); + return false; + } + int32_t brDelta = getDisplacementFromThumbBranch(instruction, (uint32_t)callSiteAddr); + pint_t targetAddr = (pint_t)callSiteAddr + 4 + brDelta; + if ( targetAddr != stubAddr ) { + warning("stub target mismatch at callsite 0x%0llX in %s", callSiteAddr, _installName); + return false; + } + // ignore branch if not to a known stub + const auto& pos = _stubAddrToLPAddr.find(targetAddr); + if ( pos == _stubAddrToLPAddr.end() ) + return false; + // ignore branch if lazy pointer is not known (could be resolver based) + pint_t lpAddr = pos->second; + const auto& pos2 = _lpAddrToTargetAddr.find(lpAddr); + if ( pos2 == _lpAddrToTargetAddr.end() ) + return false; + uint64_t finalTargetAddr = pos2->second; + int64_t deltaToFinalTarget = finalTargetAddr - (callSiteAddr + 4); + // if final target within range, change to branch there directly + if ( (deltaToFinalTarget > -b16MegLimit) && (deltaToFinalTarget < b16MegLimit) ) { + bool targetIsThumb = finalTargetAddr & 1; + instruction = setDisplacementInThumbBranch(instruction, (uint32_t)callSiteAddr, (int32_t)deltaToFinalTarget, targetIsThumb); + _branchesDirectCount++; + return true; + } + } + else if ( kind == DYLD_CACHE_ADJ_V2_ARM_BR24 ) { + // too few of these to be worth trying to optimize + } + + return false; + }); +} + + +template +void StubOptimizer

::optimizeArmStubs() +{ + for (const auto& stubEntry : _stubAddrToLPAddr) { + pint_t stubVMAddr = stubEntry.first; + pint_t lpVMAddr = stubEntry.second; + const auto& pos = _lpAddrToTargetAddr.find(lpVMAddr); + if ( pos == _lpAddrToTargetAddr.end() ) + return; + pint_t targetVMAddr = pos->second; + + int32_t delta = (int32_t)(targetVMAddr - (stubVMAddr + 12)); + const uint32_t* stubInstructions = (uint32_t*)((uint8_t*)_cacheBuffer + _stubSection->offset() + stubVMAddr - _stubSection->addr()); + E::set32(*(uint32_t*)&stubInstructions[0], 0xe59fc000); // ldr ip, L0 + E::set32(*(uint32_t*)&stubInstructions[1], 0xe08ff00c); // add pc, pc, ip + E::set32(*(uint32_t*)&stubInstructions[2], delta); // L0: .long xxxx + E::set32(*(uint32_t*)&stubInstructions[3], 0xe7ffdefe); // trap + _stubOptimizedCount++; + } +} + + + + +template +void StubOptimizer

::optimizeArm64CallSites(std::vector*>& branchIslandPools) +{ + forEachCallSiteToAStub([&](uint8_t kind, uint64_t callSiteAddr, uint64_t stubAddr, uint32_t& instruction) -> bool { + if ( kind != DYLD_CACHE_ADJ_V2_ARM64_BR26 ) + return false; + // skip all but BL or B + if ( (instruction & 0x7C000000) != 0x14000000 ) + return false; + // compute target of branch instruction + int32_t brDelta = (instruction & 0x03FFFFFF) << 2; + if ( brDelta & 0x08000000 ) + brDelta |= 0xF0000000; + uint64_t targetAddr = callSiteAddr + (int64_t)brDelta; + if ( targetAddr != stubAddr ) { + warning("stub target mismatch"); + return false; + } + // ignore branch if not to a known stub + const auto& pos = _stubAddrToLPAddr.find((pint_t)targetAddr); + if ( pos == _stubAddrToLPAddr.end() ) + return false; + // ignore branch if lazy pointer is not known (could be resolver based) + uint64_t lpAddr = pos->second; + const auto& pos2 = _lpAddrToTargetAddr.find((pint_t)lpAddr); + if ( pos2 == _lpAddrToTargetAddr.end() ) + return false; + uint64_t finalTargetAddr = pos2->second; + int64_t deltaToFinalTarget = finalTargetAddr - callSiteAddr; + // if final target within range, change to branch there directly + if ( (deltaToFinalTarget > -b128MegLimit) && (deltaToFinalTarget < b128MegLimit) ) { + instruction= (instruction & 0xFC000000) | ((deltaToFinalTarget >> 2) & 0x03FFFFFF); + _branchesDirectCount++; + return true; + } + // find closest branch island pool between instruction and target and get island + const auto& pos3 = _targetAddrToName.find((pint_t)finalTargetAddr); + if ( pos3 == _targetAddrToName.end() ) + return false; + const char* targetName = pos3->second; + if ( finalTargetAddr > callSiteAddr ) { + // target is after branch so find first pool after branch + for ( BranchPoolDylib

* pool : branchIslandPools ) { + if ( (pool->addr() > callSiteAddr) && (pool->addr() < finalTargetAddr) ) { + uint64_t brIslandAddr = pool->getForwardBranch(finalTargetAddr, targetName, branchIslandPools); + if ( brIslandAddr == 0 ) { + // branch island pool full + warning("pool full. Can't optimizer branch to %s from 0x%llX in %s\n", targetName, callSiteAddr, _installName); + break; + } + int64_t deltaToTarget = brIslandAddr - callSiteAddr; + instruction = (instruction & 0xFC000000) | ((deltaToTarget >> 2) & 0x03FFFFFF); + _branchesIslandCount++; + return true; + } + } + } + else { + // target is before branch so find closest pool before branch + for (size_t j = branchIslandPools.size(); j > 0; --j) { + BranchPoolDylib

* pool = branchIslandPools[j-1]; + if ( (pool->addr() < callSiteAddr) && (pool->addr() > finalTargetAddr) ) { + uint64_t brIslandAddr = pool->getBackBranch(finalTargetAddr, targetName, branchIslandPools); + if ( brIslandAddr == 0 ) { + // branch island pool full + warning("pool full. Can't optimizer branch to %s from 0x%llX in %s\n", targetName, callSiteAddr, _installName); + break; + } + int64_t deltaToTarget = brIslandAddr - callSiteAddr; + instruction = (instruction & 0xFC000000) | ((deltaToTarget >> 2) & 0x03FFFFFF); + _branchesIslandCount++; + return true; + } + } + } + return false; + }); +} + + +template +void StubOptimizer

::optimizeCallSites(std::vector*>& branchIslandPools) +{ + if ( _textSection == NULL ) + return; + if ( _stubSection == NULL ) + return; + + + switch ( _mh->cputype() ) { + case CPU_TYPE_ARM64: + optimizeArm64CallSites(branchIslandPools); + if ( verbose ) { + verboseLog("%5u branches in __text, %5u changed to direct branches, %5u changed to use islands for %s", + _branchesCount, _branchesDirectCount, _branchesIslandCount, _installName); + } + break; + case CPU_TYPE_ARM: + optimizeArmCallSites(); + optimizeArmStubs(); + if ( verbose ) { + verboseLog("%3u of %3u stubs optimized. %5u branches in __text, %5u changed to direct branches for %s", + _stubOptimizedCount, _stubCount, _branchesCount, _branchesDirectCount, _installName); + } + break; + } +} + + +template +void SharedCache::bypassStubs(const std::vector& branchPoolStartAddrs) +{ + verboseLog("Stub elimination optimization:"); + + // construct a StubOptimizer for each image + std::vector*> optimizers; + forEachImage([&](const void* mh, const char*, time_t, ino_t, const std::vector&) { + optimizers.push_back(new StubOptimizer

(_buffer.get(), (macho_header

*)mh)); + }); + + // construct a BranchPoolDylib for each pool + std::vector*> pools; + + if ( _arch.arch == CPU_TYPE_ARM64 ) { + // Find hole at end of linkedit region for branch pool linkedits + uint64_t textRegionStartAddr = 0; + uint64_t linkEditRegionStartAddr = 0; + uint64_t linkEditRegionEndAddr = 0; + uint64_t linkEditRegionStartCacheOffset = 0; + forEachRegion([&] (void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + if ( permissions == (PROT_READ|PROT_EXEC) ) { + textRegionStartAddr = vmAddr; + } + else if ( permissions == PROT_READ ) { + linkEditRegionStartAddr = vmAddr; + linkEditRegionEndAddr = vmAddr + size; + linkEditRegionStartCacheOffset = (char*)content - (char*)_buffer.get(); + } + }); + uint64_t lastLinkEditRegionUsedOffset = 0; + forEachImage([&](const void* mh, const char*, time_t, ino_t, const std::vector& segs) { + for (MachOProxy::Segment seg : segs) { + if ( seg.name != "__LINKEDIT" ) + continue; + if ( seg.fileOffset >= lastLinkEditRegionUsedOffset ) + lastLinkEditRegionUsedOffset = seg.fileOffset + seg.size; + } + }); + uint64_t allPoolsLinkEditStartOffset = lastLinkEditRegionUsedOffset; + uint64_t allPoolsLinkEditStartAddr = linkEditRegionStartAddr + allPoolsLinkEditStartOffset - linkEditRegionStartCacheOffset; + uint64_t allPoolsLinkEditSize = linkEditRegionEndAddr - allPoolsLinkEditStartAddr; + if ( !branchPoolStartAddrs.empty() ) { + uint64_t poolLinkEditStartAddr = allPoolsLinkEditStartAddr; + uint64_t poolLinkEditStartOffset = allPoolsLinkEditStartOffset; + const uint64_t poolSize = (allPoolsLinkEditSize/branchPoolStartAddrs.size()) & (-4096); + for (uint64_t poolAddr : branchPoolStartAddrs) { + pools.push_back(new BranchPoolDylib

(_arch, _buffer.get(), _fileSize, poolAddr, + textRegionStartAddr, poolLinkEditStartAddr, poolLinkEditStartOffset)); + poolLinkEditStartAddr += poolSize; + poolLinkEditStartOffset += poolSize; + } + } + } + + // build set of functions to never stub-eliminate because tools may need to override them + std::unordered_set neverStubEliminate; + for (const char** p=sNeverStubEliminateSymbols; *p != nullptr; ++p) { + neverStubEliminate.insert(*p); + } + for (const char** d=sNeverStubEliminateDylibs; *d != nullptr; ++d) { + for (StubOptimizer

* op : optimizers) { + if ( strcmp(op->installName(), *d) == 0 ) { + // add all exports + const uint8_t* exportsStart = op->exportsTrie(); + const uint8_t* exportsEnd = exportsStart + op->exportsTrieSize(); + std::vector exports; + if ( !ExportInfoTrie::parseTrie(exportsStart, exportsEnd, exports) ) { + terminate("malformed exports trie in %s", *d); + } + for(const ExportInfoTrie::Entry& entry : exports) { + neverStubEliminate.insert(entry.name); + } + } + } + } + + // build maps of stubs-to-lp and lp-to-target + for (StubOptimizer

* op : optimizers) + op->buildStubMap(neverStubEliminate); + + // optimize call sites to by-pass stubs or jump through island + for (StubOptimizer

* op : optimizers) + op->optimizeCallSites(pools); + + // final fix ups in branch pools + for (BranchPoolDylib

* pool : pools) { + pool->finalizeLoadCommands(); + pool->printStats(); + } + + // write total optimization info + uint32_t callSiteCount = 0; + uint32_t callSiteDirectOptCount = 0; + uint32_t callSiteOneHopOptCount = 0; + for (StubOptimizer

* op : optimizers) { + callSiteCount += op->_branchesCount; + callSiteDirectOptCount += op->_branchesDirectCount; + callSiteOneHopOptCount += op->_branchesIslandCount; + } + verboseLog(" cache contains %u call sites of which %u were direct bound and %u were bound through islands", callSiteCount, callSiteDirectOptCount, callSiteOneHopOptCount); + + // clean up + for (StubOptimizer

* op : optimizers) + delete op; + for (BranchPoolDylib

* p : pools) + delete p; + +} + + +void SharedCache::bypassStubs(const std::vector& branchPoolStartAddrs) { + switch( _arch.arch ) { + case CPU_TYPE_ARM: + bypassStubs>(branchPoolStartAddrs); + break; + case CPU_TYPE_ARM64: + bypassStubs>(branchPoolStartAddrs); + break; + default: + // no stub optimization done for other arches + break; + } +} + + +/* +template +void StubOptimizer

::optimizeStubs(std::unordered_map>& targetToBranchIslands) +{ + for (const auto& stubEntry : _stubAddrToLPAddr) { + pint_t stubVMAddr = stubEntry.first; + pint_t lpVMAddr = stubEntry.second; + const auto& pos = _lpAddrToTargetAddr.find(lpVMAddr); + if ( pos == _lpAddrToTargetAddr.end() ) + continue; + pint_t targetVMAddr = pos->second; + int64_t delta = targetVMAddr - stubVMAddr; + if ( (delta > -b128MegLimit) && (delta < b128MegLimit) ) { + // target within reach, change stub to direct branch + uint32_t* stubInstructions = (uint32_t*)((uint8_t*)_cacheBuffer + _textSegCacheOffset + stubVMAddr -_textSegStartAddr); + uint32_t stubInstr1 = E::get32(stubInstructions[0]); + if ( (stubInstr1 & 0x9F00001F) != 0x90000010 ) { + warning("first instruction of stub (0x%08X) is no longer ADRP for stub at addr 0x%0X in %s\n", + stubInstr1, stubVMAddr, _installName); + continue; + } + uint32_t directBranchInstr = 0x14000000 + ((delta/4) & 0x03FFFFFF); + E::set32(stubInstructions[0], directBranchInstr); + uint32_t brkInstr = 0xD4200000; + E::set32(stubInstructions[1], brkInstr); + E::set32(stubInstructions[2], brkInstr); + _stubOptimizedCount++; + targetToBranchIslands[targetVMAddr].push_back(stubVMAddr); + } + } + verboseLog("%3u of %3u stubs optimized for %s\n", _stubOptimizedCount, _stubCount, _installName); +} + + +template +void StubOptimizer

::bypassStubs(std::unordered_map>& targetToBranchIslands) +{ + if ( _textSection == NULL ) + return; + + // scan __text section looking for B(L) instructions that branch to a stub + unsigned instructionCount = (unsigned)(_textSection->size() / 4); + uint32_t* instructions = (uint32_t*)((uint8_t*)_cacheBuffer + _textSegCacheOffset + _textSection->addr() -_textSegStartAddr); + for (unsigned i=0; i < instructionCount; ++i) { + uint32_t instr = E::get32(instructions[i]); + // skip all but BL or B + if ( (instr & 0x7C000000) != 0x14000000 ) + continue; + // compute target of branch instruction + int32_t brDelta = (instr & 0x03FFFFFF) << 2; + if ( brDelta & 0x08000000 ) + brDelta |= 0xF0000000; + uint64_t branchAddr = _textSection->addr() + i*4; + uint64_t targetAddr = branchAddr + (int64_t)brDelta; + // ignore branch if not to a known stub + const auto& pos = _stubAddrToLPAddr.find(targetAddr); + if ( pos == _stubAddrToLPAddr.end() ) + continue; + _branchesCount++; + // ignore branch if lazy pointer is not known (could be resolver based) + const auto& pos2 = _lpAddrToTargetAddr.find(pos->second); + if ( pos2 == _lpAddrToTargetAddr.end() ) + continue; + uint64_t finalTargetAddr = pos2->second; + int64_t deltaToFinalTarget = finalTargetAddr - branchAddr; + // if final target within range, change to branch there directly + if ( (deltaToFinalTarget > -b128MegLimit) && (deltaToFinalTarget < b128MegLimit) ) { + uint32_t newInstr = (instr & 0xFC000000) | ((deltaToFinalTarget >> 2) & 0x03FFFFFF); + E::set32(instructions[i], newInstr); + _branchesDirectCount++; + continue; + } + // see if there is an existing branch island in range that can be used + std::vector& existingBranchIslands = targetToBranchIslands[finalTargetAddr]; + for (uint64_t branchIslandAddr : existingBranchIslands) { + int64_t deltaToBranchIsland = branchIslandAddr - branchAddr; + // if final target within range, change to branch deltaToBranchIsland directly + if ( (deltaToBranchIsland > -b128MegLimit) && (deltaToFinalTarget < b128MegLimit) ) { + uint32_t newInstr = (instr & 0xFC000000) | ((deltaToBranchIsland >> 2) & 0x03FFFFFF); + E::set32(instructions[i], newInstr); + _branchesIslandCount++; + break; + } + } + } + if ( verbose ) { + verboseLog("%5u branches in __text, %5u changed to direct branches, %5u changed to indirect for %s\n", + _branchesCount, _branchesDirectCount, _branchesIslandCount, _installName); + } +} +*/ + diff --git a/interlinked-dylibs/OptimizerBranches.h b/interlinked-dylibs/OptimizerBranches.h new file mode 100644 index 0000000..ae50c53 --- /dev/null +++ b/interlinked-dylibs/OptimizerBranches.h @@ -0,0 +1,36 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef __OPTIMIZER_BRANCHES_H__ +#define __OPTIMIZER_BRANCHES_H__ + +#include "mega-dylib-utils.h" + +//FIXME: merge these into the SharedCache object and delete this header +uint64_t branchPoolTextSize(ArchPair arch); +uint64_t branchPoolLinkEditSize(ArchPair arch); +uint64_t branchReach(ArchPair arch); + +#endif // __OPTIMIZER_BRANCHES_H__ + diff --git a/interlinked-dylibs/OptimizerLinkedit.cpp b/interlinked-dylibs/OptimizerLinkedit.cpp new file mode 100644 index 0000000..e446c43 --- /dev/null +++ b/interlinked-dylibs/OptimizerLinkedit.cpp @@ -0,0 +1,1185 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include "mega-dylib-utils.h" +#include "MachOFileAbstraction.hpp" +#include "Logging.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dyld_cache_config.h" +#include "Trie.hpp" + +#if !NEW_CACHE_FILE_FORMAT + #include "CacheFileAbstraction.hpp" +#endif + +#define ALIGN_AS_TYPE(value, type) \ + ((value + alignof(type) - 1) & (-alignof(type))) + +namespace { + +template +class SortedStringPool +{ +public: + // add a string and symbol table entry index to be updated later + void add(uint32_t symbolIndex, const char* symbolName) { + _map[symbolName].push_back(symbolIndex); + } + + // copy sorted strings to buffer and update all symbol's string offsets + uint32_t copyPoolAndUpdateOffsets(char* dstStringPool, macho_nlist

* symbolTable) { + // make sorted list of strings + std::vector allStrings; + allStrings.reserve(_map.size()); + for (auto& entry : _map) { + allStrings.push_back(entry.first); + } + std::sort(allStrings.begin(), allStrings.end()); + // walk sorted list of strings + dstStringPool[0] = '\0'; // tradition for start of pool to be empty string + uint32_t poolOffset = 1; + for (const std::string& symName : allStrings) { + // append string to pool + strcpy(&dstStringPool[poolOffset], symName.c_str()); + // set each string offset of each symbol using it + for (uint32_t symbolIndex : _map[symName]) { + symbolTable[symbolIndex].set_n_strx(poolOffset); + } + poolOffset += symName.size() + 1; + } + // return size of pool + return poolOffset; + } + +private: + std::unordered_map> _map; +}; + + + + +struct LocalSymbolInfo +{ + uint32_t dylibOffset; + uint32_t nlistStartIndex; + uint32_t nlistCount; +}; + + +template +class LinkeditOptimizer { +public: + LinkeditOptimizer(void* cacheBuffer, macho_header

* mh); + + uint32_t linkeditSize() { return _linkeditSize; } + uint32_t linkeditOffset() { return _linkeditCacheOffset; } + uint64_t linkeditAddr() { return _linkeditAddr; } + const char* installName() { return _installName; } + void copyWeakBindingInfo(uint8_t* newLinkEditContent, uint32_t& offset); + void copyLazyBindingInfo(uint8_t* newLinkEditContent, uint32_t& offset); + void copyBindingInfo(uint8_t* newLinkEditContent, uint32_t& offset); + void copyExportInfo(uint8_t* newLinkEditContent, uint32_t& offset); + void copyExportedSymbols(uint8_t* newLinkEditContent, SortedStringPool

& stringPool, uint32_t& offset, uint32_t& symbolIndex); + void copyImportedSymbols(uint8_t* newLinkEditContent, SortedStringPool

& stringPool, uint32_t& offset, uint32_t& symbolIndex); + void copyLocalSymbols(uint8_t* newLinkEditContent, SortedStringPool

& stringPool, uint32_t& offset, uint32_t& symbolIndex, + bool redact, std::vector& localSymbolInfos, + std::vector>& unmappedLocalSymbols, SortedStringPool

& localSymbolsStringPool); + void copyFunctionStarts(uint8_t* newLinkEditContent, uint32_t& offset); + void copyDataInCode(uint8_t* newLinkEditContent, uint32_t& offset); + void copyIndirectSymbolTable(uint8_t* newLinkEditContent, uint32_t& offset); + void updateLoadCommands(uint32_t linkeditStartOffset, uint64_t mergedLinkeditAddr, uint64_t newLinkeditSize, + uint32_t sharedSymbolTableStartOffset, uint32_t sharedSymbolTableCount, + uint32_t sharedSymbolStringsOffset, uint32_t sharedSymbolStringsSize); + + macho_header

* machHeader() { return _mh; } + const std::vector getDownwardDependents() { return _downDependentPaths; } + const std::vector getAllDependents() { return _allDependentPaths; } + const std::vector getReExportPaths() { return _reExportPaths; } + const std::vector initializerAddresses() { return _initializerAddresses; } + const std::vector*> dofSections() { return _dofSections; } + uint32_t exportsTrieLinkEditOffset() { return _newExportInfoOffset; } + uint32_t exportsTrieLinkEditSize() { return _exportInfoSize; } + uint32_t weakBindingLinkEditOffset() { return _newWeakBindingInfoOffset; } + uint32_t weakBindingLinkEditSize() { return _newWeakBindingSize; } + uint64_t dyldSectionAddress() { return _dyldSectionAddr; } + const std::vector*>& segCmds() { return _segCmds; } + + +private: + + typedef typename P::uint_t pint_t; + typedef typename P::E E; + + macho_header

* _mh; + void* _cacheBuffer; + uint32_t _linkeditSize = 0; + uint32_t _linkeditCacheOffset = 0; + uint64_t _linkeditAddr = 0; + const uint8_t* _linkeditBias = nullptr; + const char* _installName = nullptr; + macho_symtab_command

* _symTabCmd = nullptr; + macho_dysymtab_command

* _dynSymTabCmd = nullptr; + macho_dyld_info_command

* _dyldInfo = nullptr; + macho_linkedit_data_command

* _functionStartsCmd = nullptr; + macho_linkedit_data_command

* _dataInCodeCmd = nullptr; + std::vector*> _segCmds; + std::unordered_map _oldToNewSymbolIndexes; + std::vector _reExportPaths; + std::vector _downDependentPaths; + std::vector _allDependentPaths; + std::vector _initializerAddresses; + std::vector*> _dofSections; + uint32_t _newWeakBindingInfoOffset = 0; + uint32_t _newLazyBindingInfoOffset = 0; + uint32_t _newBindingInfoOffset = 0; + uint32_t _newExportInfoOffset = 0; + uint32_t _exportInfoSize = 0; + uint32_t _newWeakBindingSize = 0; + uint32_t _newExportedSymbolsStartIndex = 0; + uint32_t _newExportedSymbolCount = 0; + uint32_t _newImportedSymbolsStartIndex = 0; + uint32_t _newImportedSymbolCount = 0; + uint32_t _newLocalSymbolsStartIndex = 0; + uint32_t _newLocalSymbolCount = 0; + uint32_t _newFunctionStartsOffset = 0; + uint32_t _newDataInCodeOffset = 0; + uint32_t _newIndirectSymbolTableOffset = 0; + uint64_t _dyldSectionAddr = 0; +}; + + + +template +class AcceleratorTables { +public: + AcceleratorTables(void* cacheBuffer, uint64_t linkeditStartAddr, const std::vector*>& optimizers); + + uint32_t totalSize() const; + void copyTo(uint8_t* buffer); + +private: + typedef typename P::E E; + + struct NodeChain; + + struct DepNode { + std::vector _dependents; + unsigned _depth; + const char* _installName; + + DepNode() : _depth(0), _installName(nullptr) { } + void computeDepth(); + static void verifyUnreachable(DepNode* target, NodeChain& chain, std::unordered_set& visitedNodes, const std::vector& from); + }; + + struct NodeChain { + NodeChain* prev; + DepNode* node; + }; + + std::unordered_map*, DepNode> _depDAG; + std::vector> _extraInfo; + std::vector _trieBytes; + std::vector _reExportArray; + std::vector _dependencyArray; + std::vector _bottomUpArray; + std::vector> _initializers; + std::vector> _dofSections; + std::vector> _rangeTable; + std::unordered_map*, uint32_t> _machHeaderToImageIndex; + std::unordered_map*> _dylibPathToMachHeader; + std::unordered_map*, LinkeditOptimizer

*> _machHeaderToOptimizer; + dyldCacheAcceleratorInfo _acceleratorInfoHeader; +}; + + +template +void AcceleratorTables

::AcceleratorTables::DepNode::verifyUnreachable(AcceleratorTables

::DepNode* target, struct AcceleratorTables

::NodeChain& chain, + std::unordered_set& visitedNodes, const std::vector::DepNode*>& from) { + for (DepNode* node : from) { + bool foundCycle = (node == target); + for (NodeChain* c = &chain; c->prev != nullptr; c = c->prev) { + if ( c->node == node ) + foundCycle = true; + } + if ( foundCycle ) { + NodeChain* chp = &chain; + std::string msg = std::string("found cycle for ") + target->_installName; + while (chp != nullptr) { + msg = msg + "\n " + chp->node->_installName; + chp = chp->prev; + } + warning("%s", msg.c_str()); + return; + } + + if ( visitedNodes.count(node) ) + continue; + NodeChain nextChain; + nextChain.prev = &chain; + nextChain.node = node; + verifyUnreachable(target, nextChain, visitedNodes, node->_dependents); + visitedNodes.insert(node); + } +} + +const uint16_t kBranchIslandDylibIndex = 0x7FFF; + +template +AcceleratorTables

::AcceleratorTables(void* cacheBuffer, uint64_t linkeditStartAddr, const std::vector*>& optimizers) +{ + // build table mapping tables to map between mach_header, index, and optimizer + for ( LinkeditOptimizer

* op : optimizers ) { + _machHeaderToOptimizer[op->machHeader()] = op; + } + uint64_t cacheStartAddress; +#if NEW_CACHE_FILE_FORMAT + #error new format support not implemented +#else + typedef typename P::E E; + const dyldCacheHeader* header = (dyldCacheHeader*)cacheBuffer; + const dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)((uint8_t*)cacheBuffer + header->mappingOffset()); + cacheStartAddress = mappings[0].address(); + const dyldCacheImageInfo* images = (dyldCacheImageInfo*)((uint8_t*)cacheBuffer + header->imagesOffset()); + const unsigned imageCount = header->imagesCount(); + for (unsigned i=0; i < imageCount; ++i) { + uint64_t segCacheFileOffset = images[i].address() - cacheStartAddress; + macho_header

* mhMapped = (macho_header

*)((uint8_t*)cacheBuffer+segCacheFileOffset); + const char* path = (char*)cacheBuffer + images[i].pathFileOffset(); + _dylibPathToMachHeader[path] = mhMapped; + // don't add alias entries (path offset in pool near start of cache) to header->index map + if ( images[i].pathFileOffset() > segCacheFileOffset ) + _machHeaderToImageIndex[mhMapped] = i; + } +#endif + + // build DAG of image dependencies + for (LinkeditOptimizer

* op : optimizers) { + _depDAG[op->machHeader()]._installName = op->installName(); + } + for (LinkeditOptimizer

* op : optimizers) { + DepNode& node = _depDAG[op->machHeader()]; + for (const char* depPath : op->getDownwardDependents()) { + macho_header

* depMH = _dylibPathToMachHeader[depPath]; + assert(depMH != NULL); + DepNode* depNode = &_depDAG[depMH]; + node._dependents.push_back(depNode); + } + } + + // check for cycles in DAG + for (auto& entry : _depDAG) { + DepNode* node = &entry.second; + NodeChain chain; + chain.prev = nullptr; + chain.node = node; + std::unordered_set visitedNodes; + DepNode::verifyUnreachable(node, chain, visitedNodes, node->_dependents); + } + + // compute depth for each DAG node + for (auto& entry : _depDAG) { + entry.second.computeDepth(); + } + + // build sorted (bottom up) list of images + std::vector*> sortedMachHeaders; + sortedMachHeaders.reserve(optimizers.size()); + for (LinkeditOptimizer

* op : optimizers) { + if ( strcmp(op->installName(), "dyld_shared_cache_branch_islands") != 0 ) + sortedMachHeaders.push_back(op->machHeader()); + else + _machHeaderToImageIndex[op->machHeader()] = kBranchIslandDylibIndex; + } + std::sort(sortedMachHeaders.begin(), sortedMachHeaders.end(), + [&](macho_header

* lmh, macho_header

* rmh) -> bool { + if ( _depDAG[lmh]._depth != _depDAG[rmh]._depth ) + return (_depDAG[lmh]._depth < _depDAG[rmh]._depth); + else + return (lmh < rmh); + }); + + // build zeroed array of extra infos + dyldCacheImageInfoExtra emptyExtra; + emptyExtra.set_exportsTrieAddr(0); + emptyExtra.set_weakBindingsAddr(0); + emptyExtra.set_exportsTrieSize(0); + emptyExtra.set_weakBindingsSize(0); + emptyExtra.set_dependentsStartArrayIndex(0); + emptyExtra.set_reExportsStartArrayIndex(0); + _extraInfo.insert(_extraInfo.begin(), sortedMachHeaders.size(), emptyExtra); + + //for ( macho_header

* mh : sortedMachHeaders ) { + // fprintf(stderr, "depth: %3d mh: %p path: %s\n", _depDAG[mh]._depth, mh, _machHeaderToOptimizer[mh]->installName()); + //} + + // build dependency table + _dependencyArray.push_back(0xFFFF); // reserve 0 slot to be "no-dependencies" + for (macho_header

* mh : sortedMachHeaders) { + LinkeditOptimizer

* op = _machHeaderToOptimizer[mh]; + unsigned index = _machHeaderToImageIndex[mh]; + auto depPaths = op->getAllDependents(); + if ( depPaths.empty() ) { + _extraInfo[index].set_dependentsStartArrayIndex(0); + } + else { + _extraInfo[index].set_dependentsStartArrayIndex((uint32_t)_dependencyArray.size()); + auto downPaths = op->getDownwardDependents(); + for (const char* depPath : depPaths) { + macho_header

* depMH = _dylibPathToMachHeader[depPath]; + uint16_t depIndex = _machHeaderToImageIndex[depMH]; + if ( std::find(downPaths.begin(), downPaths.end(), depPath) == downPaths.end()) + depIndex |= 0x8000; + _dependencyArray.push_back(depIndex); + } + _dependencyArray.push_back(0xFFFF); // mark end of list + } + } + + // build re-exports table + _reExportArray.push_back(0xFFFF); // reserve 0 slot to be "no-re-exports" + for (macho_header

* mh : sortedMachHeaders) { + LinkeditOptimizer

* op = _machHeaderToOptimizer[mh]; + unsigned index = _machHeaderToImageIndex[mh]; + auto reExPaths = op->getReExportPaths(); + if ( reExPaths.empty() ) { + _extraInfo[index].set_reExportsStartArrayIndex(0); + } + else { + _extraInfo[index].set_reExportsStartArrayIndex((uint32_t)_reExportArray.size()); + for (const char* reExPath : reExPaths) { + macho_header

* reExMH = _dylibPathToMachHeader[reExPath]; + uint32_t reExIndex = _machHeaderToImageIndex[reExMH]; + _reExportArray.push_back(reExIndex); + } + _reExportArray.push_back(0xFFFF); // mark end of list + } + } + + // build ordered list of initializers + for (macho_header

* mh : sortedMachHeaders) { + LinkeditOptimizer

* op = _machHeaderToOptimizer[mh]; + unsigned index = _machHeaderToImageIndex[mh]; + _bottomUpArray.push_back(index); + for (uint64_t initializer : op->initializerAddresses()) { + //fprintf(stderr, "0x%08llX %s\n", initializer, op->installName()); + dyldCacheAcceleratorInitializer entry; + entry.set_functionOffset((uint32_t)(initializer-cacheStartAddress)); + entry.set_imageIndex(_machHeaderToImageIndex[mh]); + _initializers.push_back(entry); + } + } + + // build ordered list of DOF sections + for (macho_header

* mh : sortedMachHeaders) { + LinkeditOptimizer

* op = _machHeaderToOptimizer[mh]; + assert(op != NULL); + unsigned imageIndex = _machHeaderToImageIndex[mh]; + for (auto& sect : op->dofSections()) { + //fprintf(stderr, "0x%08llX %s\n", initializer, op->installName()); + dyldCacheAcceleratorDOFEntry entry; + entry.set_sectionAddress(sect->addr()); + entry.set_sectionSize((uint32_t)sect->size()); + entry.set_imageIndex(imageIndex); + _dofSections.push_back(entry); + } + } + + // register exports trie and weak binding info in each dylib with image extra info + for (macho_header

* mh : sortedMachHeaders) { + LinkeditOptimizer

* op = _machHeaderToOptimizer[mh]; + unsigned index = _machHeaderToImageIndex[mh]; + _extraInfo[index].set_exportsTrieAddr(op->exportsTrieLinkEditOffset() + linkeditStartAddr); + _extraInfo[index].set_exportsTrieSize(op->exportsTrieLinkEditSize()); + _extraInfo[index].set_weakBindingsAddr(op->weakBindingLinkEditOffset() + linkeditStartAddr); + _extraInfo[index].set_weakBindingsSize(op->weakBindingLinkEditSize()); + } + + // record location of __DATA/__dyld section in libdyld.dylib + macho_header

* libdyldMH = _dylibPathToMachHeader["/usr/lib/system/libdyld.dylib"]; + LinkeditOptimizer

* libdyldOp = _machHeaderToOptimizer[libdyldMH]; + uint64_t dyldSectionAddr = libdyldOp->dyldSectionAddress(); + + // build range table for fast address->image lookups + for (macho_header

* mh : sortedMachHeaders) { + LinkeditOptimizer

* op = _machHeaderToOptimizer[mh]; + unsigned imageIndex = _machHeaderToImageIndex[mh]; + for ( const macho_segment_command

* segCmd : op->segCmds() ) { + if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) + continue; + dyldCacheAcceleratorRangeEntry entry; + entry.set_startAddress(segCmd->vmaddr()); + entry.set_size((uint32_t)segCmd->vmsize()); + entry.set_imageIndex(imageIndex); + _rangeTable.push_back(entry); + } + } + std::sort(_rangeTable.begin(), _rangeTable.end(), + [&](const dyldCacheAcceleratorRangeEntry& lRange, const dyldCacheAcceleratorRangeEntry& rRange) -> bool { + return (lRange.startAddress() < rRange.startAddress()); + }); + + // build trie that maps install names to image index + std::vector dylibEntrys; + for (auto &x : _dylibPathToMachHeader) { + const std::string& path = x.first; + unsigned index = _machHeaderToImageIndex[x.second]; + dylibEntrys.push_back(DylibIndexTrie::Entry(path, DylibIndex(index))); + } + DylibIndexTrie dylibsTrie(dylibEntrys); + dylibsTrie.emit(_trieBytes); + while ( (_trieBytes.size() % 4) != 0 ) + _trieBytes.push_back(0); + + // fill out header + dyldCacheAcceleratorInfo& h = _acceleratorInfoHeader; + h.set_version(1); + h.set_imageExtrasCount((uint32_t)_extraInfo.size()); + h.set_imagesExtrasOffset(ALIGN_AS_TYPE(sizeof(dyld_cache_accelerator_info), dyld_cache_image_info_extra)); + h.set_bottomUpListOffset(h.imagesExtrasOffset() + h.imageExtrasCount()*sizeof(dyld_cache_image_info_extra)); + h.set_dylibTrieOffset(h.bottomUpListOffset() + h.imageExtrasCount()*sizeof(uint16_t)); + h.set_dylibTrieSize((uint32_t)_trieBytes.size()); + h.set_initializersOffset(ALIGN_AS_TYPE(h.dylibTrieOffset() + h.dylibTrieSize(), dyld_cache_accelerator_initializer)); + h.set_initializersCount((uint32_t)_initializers.size()); + h.set_dofSectionsOffset(ALIGN_AS_TYPE(h.initializersOffset() + h.initializersCount()*sizeof(dyld_cache_accelerator_initializer), dyld_cache_accelerator_initializer)); + h.set_dofSectionsCount((uint32_t)_dofSections.size()); + h.set_reExportListOffset(ALIGN_AS_TYPE(h.dofSectionsOffset() + h.dofSectionsCount()*sizeof(dyld_cache_accelerator_dof), dyld_cache_accelerator_dof)); + h.set_reExportCount((uint32_t)_reExportArray.size()); + h.set_depListOffset(ALIGN_AS_TYPE(h.reExportListOffset() + h.reExportCount()*sizeof(uint16_t), uint16_t)); + h.set_depListCount((uint32_t)_dependencyArray.size()); + h.set_rangeTableOffset(ALIGN_AS_TYPE(h.depListOffset() + h.depListCount()*sizeof(uint16_t), dyld_cache_range_entry)); + h.set_rangeTableCount((uint32_t)_rangeTable.size()); + h.set_dyldSectionAddr(dyldSectionAddr); +} + + +template +void AcceleratorTables

::DepNode::computeDepth() +{ + if ( _depth != 0 ) + return; + _depth = 1; + for (DepNode* node : _dependents) { + node->computeDepth(); + if ( node->_depth >= _depth ) + _depth = node->_depth + 1; + } +} + +template +uint32_t AcceleratorTables

::totalSize() const +{ + return (uint32_t)align(_acceleratorInfoHeader.rangeTableOffset() + _acceleratorInfoHeader.rangeTableCount()*sizeof(dyld_cache_range_entry), 14); +} + +template +void AcceleratorTables

::copyTo(uint8_t* buffer) +{ + memcpy(buffer, &_acceleratorInfoHeader, sizeof(dyld_cache_accelerator_info)); + memcpy(&buffer[_acceleratorInfoHeader.imagesExtrasOffset()], &_extraInfo[0], _extraInfo.size()*sizeof(dyld_cache_image_info_extra)); + memcpy(&buffer[_acceleratorInfoHeader.bottomUpListOffset()], &_bottomUpArray[0], _bottomUpArray.size()*sizeof(uint16_t)); + memcpy(&buffer[_acceleratorInfoHeader.initializersOffset()], &_initializers[0], _initializers.size()*sizeof(dyld_cache_accelerator_initializer)); + memcpy(&buffer[_acceleratorInfoHeader.reExportListOffset()], &_reExportArray[0], _reExportArray.size()*sizeof(uint16_t)); + memcpy(&buffer[_acceleratorInfoHeader.dofSectionsOffset()], &_dofSections[0], _dofSections.size()*sizeof(dyld_cache_accelerator_dof)); + memcpy(&buffer[_acceleratorInfoHeader.depListOffset()], &_dependencyArray[0], _dependencyArray.size()*sizeof(uint16_t)); + memcpy(&buffer[_acceleratorInfoHeader.rangeTableOffset()], &_rangeTable[0], _rangeTable.size()*sizeof(dyld_cache_range_entry)); + memcpy(&buffer[_acceleratorInfoHeader.dylibTrieOffset()], &_trieBytes[0], _trieBytes.size()); +} + + + +template +LinkeditOptimizer

::LinkeditOptimizer(void* cacheBuffer, macho_header

* mh) +: _mh(mh), _cacheBuffer(cacheBuffer) +{ + _linkeditBias = (uint8_t*)cacheBuffer; + const unsigned origLoadCommandsSize = mh->sizeofcmds(); + unsigned bytesRemaining = origLoadCommandsSize; + unsigned removedCount = 0; + const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + const uint32_t cmdCount = mh->ncmds(); + const macho_load_command

* cmd = cmds; + const macho_dylib_command

* dylibCmd; + const macho_routines_command

* routinesCmd; + macho_segment_command

* segCmd; + for (uint32_t i = 0; i < cmdCount; ++i) { + bool remove = false; + switch (cmd->cmd()) { + case LC_ID_DYLIB: + _installName = ((macho_dylib_command

*)cmd)->name(); + break; + case LC_SYMTAB: + _symTabCmd = (macho_symtab_command

*)cmd; + break; + case LC_DYSYMTAB: + _dynSymTabCmd = (macho_dysymtab_command

*)cmd; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + _dyldInfo = (macho_dyld_info_command

*)cmd; + _exportInfoSize = _dyldInfo->export_size(); + break; + case LC_FUNCTION_STARTS: + _functionStartsCmd = (macho_linkedit_data_command

*)cmd; + break; + case LC_DATA_IN_CODE: + _dataInCodeCmd = (macho_linkedit_data_command

*)cmd; + break; + case LC_ROUTINES: + case LC_ROUTINES_64: + routinesCmd = (macho_routines_command

*)cmd; + _initializerAddresses.push_back(routinesCmd->init_address()); + break; + case LC_REEXPORT_DYLIB: + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + dylibCmd = (macho_dylib_command

*)cmd; + _allDependentPaths.push_back(dylibCmd->name()); + if ( cmd->cmd() != LC_LOAD_UPWARD_DYLIB ) + _downDependentPaths.push_back(dylibCmd->name()); + if ( cmd->cmd() == LC_REEXPORT_DYLIB ) + _reExportPaths.push_back(dylibCmd->name()); + break; + case macho_segment_command

::CMD: + segCmd = (macho_segment_command

*)cmd; + _segCmds.push_back(segCmd); + if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { + _linkeditSize = (uint32_t)segCmd->vmsize(); + _linkeditCacheOffset = (uint32_t)segCmd->fileoff(); + _linkeditAddr = segCmd->vmaddr(); + } + else if ( segCmd->nsects() > 0 ) { + macho_section

* const sectionsStart = (macho_section

*)((uint8_t*)segCmd + sizeof(macho_segment_command

)); + macho_section

* const sectionsEnd = §ionsStart[segCmd->nsects()]; + for (macho_section

* sect=sectionsStart; sect < sectionsEnd; ++sect) { + const uint8_t type = sect->flags() & SECTION_TYPE; + if ( type == S_MOD_INIT_FUNC_POINTERS ) { + const pint_t* inits = (pint_t*)((char*)cacheBuffer + sect->offset()); + const size_t count = sect->size() / sizeof(pint_t); + for (size_t j=0; j < count; ++j) { + uint64_t func = P::getP(inits[j]); + _initializerAddresses.push_back(func); + } + } + else if ( type == S_DTRACE_DOF ) { + _dofSections.push_back(sect); + } + else if ( (strcmp(sect->sectname(), "__dyld") == 0) && (strncmp(sect->segname(), "__DATA", 6) == 0) ) { + _dyldSectionAddr = sect->addr(); + } + } + } + break; + case LC_SEGMENT_SPLIT_INFO: + remove = true; + break; + } + uint32_t cmdSize = cmd->cmdsize(); + macho_load_command

* nextCmd = (macho_load_command

*)(((uint8_t*)cmd)+cmdSize); + if ( remove ) { + ::memmove((void*)cmd, (void*)nextCmd, bytesRemaining); + ++removedCount; + } + else { + bytesRemaining -= cmdSize; + cmd = nextCmd; + } + } + // zero out stuff removed + ::bzero((void*)cmd, bytesRemaining); + // update header + mh->set_ncmds(cmdCount - removedCount); + mh->set_sizeofcmds(origLoadCommandsSize - bytesRemaining); +} + +/* +static void dumpLoadCommands(const uint8_t* mheader) +{ + const mach_header* const mh = (mach_header*)mheader; + const uint32_t cmd_count = mh->ncmds; + bool is64 = (mh->magic == MH_MAGIC_64); + const load_command* cmds = (load_command*)(mheader + (is64 ? sizeof(mach_header_64) : sizeof(mach_header))); + const load_command* cmd = cmds; + const segment_command* segCmd; + const segment_command_64* seg64Cmd; + const symtab_command* symTab; + const linkedit_data_command* leData; + const uint8_t* linkEditBias = NULL; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT: + segCmd = (const segment_command*)cmd; + printf("LC_SEGMENT\n"); + printf(" segname = %s\n", segCmd->segname); + printf(" vmaddr = 0x%08X\n", segCmd->vmaddr); + printf(" vmsize = 0x%08X\n", segCmd->vmsize); + printf(" fileoff = 0x%08X\n", segCmd->fileoff); + printf(" filesize = 0x%08X\n", segCmd->filesize); + if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { + linkEditBias = mheader - segCmd->fileoff; + } + break; + case LC_SEGMENT_64: + seg64Cmd = (const segment_command_64*)cmd; + printf("LC_SEGMENT_64\n"); + printf(" segname = %s\n", seg64Cmd->segname); + printf(" vmaddr = 0x%09llX\n", seg64Cmd->vmaddr); + printf(" vmsize = 0x%09llX\n", seg64Cmd->vmsize); + printf(" fileoff = 0x%09llX\n", seg64Cmd->fileoff); + printf(" filesize = 0x%09llX\n", seg64Cmd->filesize); + if ( strcmp(seg64Cmd->segname, "__TEXT") == 0 ) { + linkEditBias = mheader - seg64Cmd->fileoff; + } + break; + case LC_SYMTAB: + symTab = (const symtab_command*)cmd; + printf("LC_SYMTAB\n"); + printf(" symoff = 0x%08X\n", symTab->symoff); + printf(" nsyms = 0x%08X\n", symTab->nsyms); + printf(" stroff = 0x%08X\n", symTab->stroff); + printf(" strsize = 0x%08X\n", symTab->strsize); + { + const char* strPool = (char*)&linkEditBias[symTab->stroff]; + const nlist_64* sym0 = (nlist_64*)(&linkEditBias[symTab->symoff]); + printf(" sym[0].n_strx = 0x%08X (%s)\n", sym0->n_un.n_strx, &strPool[sym0->n_un.n_strx]); + printf(" sym[0].n_type = 0x%02X\n", sym0->n_type); + printf(" sym[0].n_sect = 0x%02X\n", sym0->n_sect); + printf(" sym[0].n_desc = 0x%04X\n", sym0->n_desc); + printf(" sym[0].n_value = 0x%llX\n", sym0->n_value); + const nlist_64* sym1 = (nlist_64*)(&linkEditBias[symTab->symoff+16]); + printf(" sym[1].n_strx = 0x%08X (%s)\n", sym1->n_un.n_strx, &strPool[sym1->n_un.n_strx]); + printf(" sym[1].n_type = 0x%02X\n", sym1->n_type); + printf(" sym[1].n_sect = 0x%02X\n", sym1->n_sect); + printf(" sym[1].n_desc = 0x%04X\n", sym1->n_desc); + printf(" sym[1].n_value = 0x%llX\n", sym1->n_value); + } + break; + case LC_FUNCTION_STARTS: + leData = (const linkedit_data_command*)cmd; + printf("LC_FUNCTION_STARTS\n"); + printf(" dataoff = 0x%08X\n", leData->dataoff); + printf(" datasize = 0x%08X\n", leData->datasize); + default: + //printf("0x%08X\n", cmd->cmd); + break; + } + cmd = (const load_command*)(((uint8_t*)cmd)+cmd->cmdsize); + } +} +*/ + +template +void LinkeditOptimizer

::updateLoadCommands(uint32_t mergedLinkeditStartOffset, uint64_t mergedLinkeditAddr, uint64_t newLinkeditSize, + uint32_t sharedSymbolTableStartOffset, uint32_t sharedSymbolTableCount, + uint32_t sharedSymbolStringsOffset, uint32_t sharedSymbolStringsSize) +{ + // update __LINKEDIT segment in all dylibs to overlap the same shared region + for (macho_segment_command

* segCmd : _segCmds) { + if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { + segCmd->set_vmaddr(mergedLinkeditAddr); + segCmd->set_vmsize(newLinkeditSize); + segCmd->set_fileoff(mergedLinkeditStartOffset); + segCmd->set_filesize(newLinkeditSize); + } + else if ( strcmp(segCmd->segname(), "__TEXT") == 0 ) { + // HACK until lldb fixed in: DynamicLoaderMacOSXDYLD fixes for Monarch dyld shared cache + segCmd->set_fileoff(0); + + } + } + + // update symbol table to point to shared symbol table + _symTabCmd->set_symoff(mergedLinkeditStartOffset + sharedSymbolTableStartOffset + _newLocalSymbolsStartIndex*sizeof(macho_nlist

)); + _symTabCmd->set_nsyms(_newLocalSymbolCount+_newExportedSymbolCount+_newImportedSymbolCount); + _symTabCmd->set_stroff(mergedLinkeditStartOffset + sharedSymbolStringsOffset); + _symTabCmd->set_strsize(sharedSymbolStringsSize); + + // update dynamic symbol table to have proper offsets into shared symbol table + _dynSymTabCmd->set_ilocalsym(0); + _dynSymTabCmd->set_nlocalsym(_newLocalSymbolCount); + _dynSymTabCmd->set_iextdefsym(_newExportedSymbolsStartIndex-_newLocalSymbolsStartIndex); + _dynSymTabCmd->set_nextdefsym(_newExportedSymbolCount); + _dynSymTabCmd->set_iundefsym(_newImportedSymbolsStartIndex-_newLocalSymbolsStartIndex); + _dynSymTabCmd->set_nundefsym(_newImportedSymbolCount); + _dynSymTabCmd->set_tocoff(0); + _dynSymTabCmd->set_ntoc(0); + _dynSymTabCmd->set_modtaboff(0); + _dynSymTabCmd->set_nmodtab(0); + _dynSymTabCmd->set_indirectsymoff(mergedLinkeditStartOffset + _newIndirectSymbolTableOffset); + _dynSymTabCmd->set_extreloff(0); + _dynSymTabCmd->set_locreloff(0); + _dynSymTabCmd->set_nlocrel(0); + + // update dyld info + if ( _dyldInfo != nullptr ) { + _dyldInfo->set_rebase_off(0); + _dyldInfo->set_rebase_size(0); + _dyldInfo->set_bind_off(_dyldInfo->bind_size() ? mergedLinkeditStartOffset + _newBindingInfoOffset : 0); + _dyldInfo->set_weak_bind_off(_dyldInfo->weak_bind_size() ? mergedLinkeditStartOffset + _newWeakBindingInfoOffset : 0 ); + _dyldInfo->set_lazy_bind_off(_dyldInfo->lazy_bind_size() ? mergedLinkeditStartOffset + _newLazyBindingInfoOffset : 0 ); + _dyldInfo->set_export_off(mergedLinkeditStartOffset + _newExportInfoOffset); + } + + // update function-starts + if ( _functionStartsCmd != nullptr ) + _functionStartsCmd->set_dataoff(mergedLinkeditStartOffset+_newFunctionStartsOffset); + + // update data-in-code + if ( _dataInCodeCmd != nullptr ) + _dataInCodeCmd->set_dataoff(mergedLinkeditStartOffset+_newDataInCodeOffset); +} + +template +void LinkeditOptimizer

::copyWeakBindingInfo(uint8_t* newLinkEditContent, uint32_t& offset) +{ + if ( _dyldInfo == nullptr ) + return; + unsigned size = _dyldInfo->weak_bind_size(); + if ( size != 0 ) { + ::memcpy(&newLinkEditContent[offset], &_linkeditBias[_dyldInfo->weak_bind_off()], size); + _newWeakBindingInfoOffset = offset; + _newWeakBindingSize = size; + offset += size; + } +} + + +template +void LinkeditOptimizer

::copyLazyBindingInfo(uint8_t* newLinkEditContent, uint32_t& offset) +{ + if ( _dyldInfo == nullptr ) + return; + unsigned size = _dyldInfo->lazy_bind_size(); + if ( size != 0 ) { + ::memcpy(&newLinkEditContent[offset], &_linkeditBias[_dyldInfo->lazy_bind_off()], size); + _newLazyBindingInfoOffset = offset; + offset += size; + } +} + +template +void LinkeditOptimizer

::copyBindingInfo(uint8_t* newLinkEditContent, uint32_t& offset) +{ + if ( _dyldInfo == nullptr ) + return; + unsigned size = _dyldInfo->bind_size(); + if ( size != 0 ) { + ::memcpy(&newLinkEditContent[offset], &_linkeditBias[_dyldInfo->bind_off()], size); + _newBindingInfoOffset = offset; + offset += size; + } +} + +template +void LinkeditOptimizer

::copyExportInfo(uint8_t* newLinkEditContent, uint32_t& offset) +{ + if ( _dyldInfo == nullptr ) + return; + unsigned size = _dyldInfo->export_size(); + if ( size != 0 ) { + ::memcpy(&newLinkEditContent[offset], &_linkeditBias[_dyldInfo->export_off()], size); + _newExportInfoOffset = offset; + offset += size; + } +} + + +template +void LinkeditOptimizer

::copyFunctionStarts(uint8_t* newLinkEditContent, uint32_t& offset) +{ + if ( _functionStartsCmd == nullptr ) + return; + unsigned size = _functionStartsCmd->datasize(); + ::memcpy(&newLinkEditContent[offset], &_linkeditBias[_functionStartsCmd->dataoff()], size); + _newFunctionStartsOffset = offset; + offset += size; +} + +template +void LinkeditOptimizer

::copyDataInCode(uint8_t* newLinkEditContent, uint32_t& offset) +{ + if ( _dataInCodeCmd == nullptr ) + return; + unsigned size = _dataInCodeCmd->datasize(); + ::memcpy(&newLinkEditContent[offset], &_linkeditBias[_dataInCodeCmd->dataoff()], size); + _newDataInCodeOffset = offset; + offset += size; +} + + +template +void LinkeditOptimizer

::copyLocalSymbols(uint8_t* newLinkEditContent, SortedStringPool

& stringPool, uint32_t& offset, uint32_t& symbolIndex, + bool redact, std::vector& localSymbolInfos, + std::vector>& unmappedLocalSymbols, SortedStringPool

& localSymbolsStringPool) +{ + LocalSymbolInfo localInfo; + localInfo.dylibOffset = (uint32_t)(((uint8_t*)_mh) - (uint8_t*)_cacheBuffer); + localInfo.nlistStartIndex = (uint32_t)unmappedLocalSymbols.size(); + localInfo.nlistCount = 0; + _newLocalSymbolsStartIndex = symbolIndex; + const char* strings = (char*)&_linkeditBias[_symTabCmd->stroff()]; + const macho_nlist

* const symbolTable = (macho_nlist

*)(&_linkeditBias[_symTabCmd->symoff()]); + const macho_nlist

* const firstExport = &symbolTable[_dynSymTabCmd->ilocalsym()]; + const macho_nlist

* const lastExport = &symbolTable[_dynSymTabCmd->ilocalsym()+_dynSymTabCmd->nlocalsym()]; + for (const macho_nlist

* entry = firstExport; entry < lastExport; ++entry) { + if ( (entry->n_type() & N_TYPE) != N_SECT) + continue; + if ( (entry->n_type() & N_STAB) != 0) + continue; + const char* name = &strings[entry->n_strx()]; + macho_nlist

* newSymbolEntry = (macho_nlist

*)&newLinkEditContent[offset]; + *newSymbolEntry = *entry; + if ( redact ) { + // if removing local symbols, change __text symbols to "" so backtraces don't have bogus names + if ( entry->n_sect() == 1 ) { + stringPool.add(symbolIndex, ""); + ++symbolIndex; + offset += sizeof(macho_nlist

); + } + // copy local symbol to unmmapped locals area + localSymbolsStringPool.add((uint32_t)unmappedLocalSymbols.size(), name); + unmappedLocalSymbols.push_back(*entry); + unmappedLocalSymbols.back().set_n_strx(0); + } + else { + stringPool.add(symbolIndex, name); + ++symbolIndex; + offset += sizeof(macho_nlist

); + } + } + _newLocalSymbolCount = symbolIndex - _newLocalSymbolsStartIndex; + localInfo.nlistCount = (uint32_t)unmappedLocalSymbols.size() - localInfo.nlistStartIndex; + localSymbolInfos.push_back(localInfo); +} + + +template +void LinkeditOptimizer

::copyExportedSymbols(uint8_t* newLinkEditContent, SortedStringPool

& stringPool, uint32_t& offset, uint32_t& symbolIndex) +{ + _newExportedSymbolsStartIndex = symbolIndex; + const char* strings = (char*)&_linkeditBias[_symTabCmd->stroff()]; + const macho_nlist

* const symbolTable = (macho_nlist

*)(&_linkeditBias[_symTabCmd->symoff()]); + const macho_nlist

* const firstExport = &symbolTable[_dynSymTabCmd->iextdefsym()]; + const macho_nlist

* const lastExport = &symbolTable[_dynSymTabCmd->iextdefsym()+_dynSymTabCmd->nextdefsym()]; + uint32_t oldSymbolIndex = _dynSymTabCmd->iextdefsym(); + for (const macho_nlist

* entry = firstExport; entry < lastExport; ++entry, ++oldSymbolIndex) { + if ( (entry->n_type() & N_TYPE) != N_SECT) + continue; + const char* name = &strings[entry->n_strx()]; + if ( strncmp(name, ".objc_", 6) == 0 ) + continue; + if ( strncmp(name, "$ld$", 4) == 0 ) + continue; + macho_nlist

* newSymbolEntry = (macho_nlist

*)&newLinkEditContent[offset]; + *newSymbolEntry = *entry; + newSymbolEntry->set_n_strx(0); + stringPool.add(symbolIndex, name); + _oldToNewSymbolIndexes[oldSymbolIndex] = symbolIndex - _newLocalSymbolsStartIndex; + ++symbolIndex; + offset += sizeof(macho_nlist

); + } + _newExportedSymbolCount = symbolIndex - _newExportedSymbolsStartIndex; +} + +template +void LinkeditOptimizer

::copyImportedSymbols(uint8_t* newLinkEditContent, SortedStringPool

& stringPool, uint32_t& offset, uint32_t& symbolIndex) +{ + _newImportedSymbolsStartIndex = symbolIndex; + const char* strings = (char*)&_linkeditBias[_symTabCmd->stroff()]; + const macho_nlist

* const symbolTable = (macho_nlist

*)(&_linkeditBias[_symTabCmd->symoff()]); + const macho_nlist

* const firstImport = &symbolTable[_dynSymTabCmd->iundefsym()]; + const macho_nlist

* const lastImport = &symbolTable[_dynSymTabCmd->iundefsym()+_dynSymTabCmd->nundefsym()]; + uint32_t oldSymbolIndex = _dynSymTabCmd->iundefsym(); + for (const macho_nlist

* entry = firstImport; entry < lastImport; ++entry, ++oldSymbolIndex) { + if ( (entry->n_type() & N_TYPE) != N_UNDF) + continue; + const char* name = &strings[entry->n_strx()]; + macho_nlist

* newSymbolEntry = (macho_nlist

*)&newLinkEditContent[offset]; + *newSymbolEntry = *entry; + newSymbolEntry->set_n_strx(0); + stringPool.add(symbolIndex, name); + _oldToNewSymbolIndexes[oldSymbolIndex] = symbolIndex - _newLocalSymbolsStartIndex; + ++symbolIndex; + offset += sizeof(macho_nlist

); + } + _newImportedSymbolCount = symbolIndex - _newImportedSymbolsStartIndex; +} + +template +void LinkeditOptimizer

::copyIndirectSymbolTable(uint8_t* newLinkEditContent, uint32_t& offset) +{ + _newIndirectSymbolTableOffset = offset; + const uint32_t* const indirectTable = (uint32_t*)&_linkeditBias[_dynSymTabCmd->indirectsymoff()]; + uint32_t* newIndirectTable = (uint32_t*)&newLinkEditContent[offset]; + for (int i=0; i < _dynSymTabCmd->nindirectsyms(); ++i) { + uint32_t symbolIndex = E::get32(indirectTable[i]); + if ( (symbolIndex == INDIRECT_SYMBOL_ABS) || (symbolIndex == INDIRECT_SYMBOL_LOCAL) ) + E::set32(newIndirectTable[i], symbolIndex); + else + E::set32(newIndirectTable[i], _oldToNewSymbolIndexes[symbolIndex]); + offset += sizeof(uint32_t); + } +} + +template +uint64_t mergeLinkedits(SharedCache& cache, bool dontMapLocalSymbols, bool addAcceleratorTables, std::vector*>& optimizers) +{ + // allocate space for new linkedit data + uint32_t linkeditStartOffset = 0xFFFFFFFF; + uint32_t linkeditEndOffset = 0; + uint64_t linkeditStartAddr = 0; + for (LinkeditOptimizer

* op : optimizers) { + uint32_t leOffset = op->linkeditOffset(); + if ( leOffset < linkeditStartOffset ) { + linkeditStartOffset = leOffset; + linkeditStartAddr = op->linkeditAddr(); + } + uint32_t leEndOffset = op->linkeditOffset() + op->linkeditSize(); + if ( leEndOffset > linkeditEndOffset ) + linkeditEndOffset = leEndOffset; + } + uint64_t totalUnoptLinkeditsSize = linkeditEndOffset - linkeditStartOffset; + uint8_t* newLinkEdit = (uint8_t*)calloc(totalUnoptLinkeditsSize, 1); + SortedStringPool

stringPool; + uint32_t offset = 0; + + verboseLog("Merged LINKEDIT:"); + + // copy weak binding info + uint32_t startWeakBindInfosOffset = offset; + for (LinkeditOptimizer

* op : optimizers) { + op->copyWeakBindingInfo(newLinkEdit, offset); + } + verboseLog(" weak bindings size: %5uKB", (offset-startWeakBindInfosOffset)/1024); + + // copy export info + uint32_t startExportInfosOffset = offset; + for (LinkeditOptimizer

* op : optimizers) { + op->copyExportInfo(newLinkEdit, offset); + } + verboseLog(" exports info size: %5uKB", (offset-startExportInfosOffset)/1024); + + // in theory, an optimized cache can drop the binding info + if ( true ) { + // copy binding info + uint32_t startBindingsInfosOffset = offset; + for (LinkeditOptimizer

* op : optimizers) { + op->copyBindingInfo(newLinkEdit, offset); + } + verboseLog(" bindings size: %5uKB", (offset-startBindingsInfosOffset)/1024); + + // copy lazy binding info + uint32_t startLazyBindingsInfosOffset = offset; + for (LinkeditOptimizer

* op : optimizers) { + op->copyLazyBindingInfo(newLinkEdit, offset); + } + verboseLog(" lazy bindings size: %5uKB", (offset-startLazyBindingsInfosOffset)/1024); + } + + // copy symbol table entries + std::vector> unmappedLocalSymbols; + if ( dontMapLocalSymbols ) + unmappedLocalSymbols.reserve(0x01000000); + std::vector localSymbolInfos; + localSymbolInfos.reserve(optimizers.size()); + SortedStringPool

localSymbolsStringPool; + uint32_t symbolIndex = 0; + const uint32_t sharedSymbolTableStartOffset = offset; + uint32_t sharedSymbolTableExportsCount = 0; + uint32_t sharedSymbolTableImportsCount = 0; + for (LinkeditOptimizer

* op : optimizers) { + op->copyLocalSymbols(newLinkEdit, stringPool, offset, symbolIndex, dontMapLocalSymbols, + localSymbolInfos, unmappedLocalSymbols, localSymbolsStringPool); + uint32_t x = symbolIndex; + op->copyExportedSymbols(newLinkEdit, stringPool, offset, symbolIndex); + sharedSymbolTableExportsCount += (symbolIndex-x); + uint32_t y = symbolIndex; + op->copyImportedSymbols(newLinkEdit, stringPool, offset, symbolIndex); + sharedSymbolTableImportsCount += (symbolIndex-y); + } + uint32_t sharedSymbolTableCount = symbolIndex; + const uint32_t sharedSymbolTableEndOffset = offset; + + // copy function starts + uint32_t startFunctionStartsOffset = offset; + for (LinkeditOptimizer

* op : optimizers) { + op->copyFunctionStarts(newLinkEdit, offset); + } + verboseLog(" function starts size: %5uKB", (offset-startFunctionStartsOffset)/1024); + + // copy data-in-code info + uint32_t startDataInCodeOffset = offset; + for (LinkeditOptimizer

* op : optimizers) { + op->copyDataInCode(newLinkEdit, offset); + } + verboseLog(" data in code size: %5uB", offset-startDataInCodeOffset); + + // copy indirect symbol tables + for (LinkeditOptimizer

* op : optimizers) { + op->copyIndirectSymbolTable(newLinkEdit, offset); + } + // if indirect table has odd number of entries, end will not be 8-byte aligned + if ( (offset % sizeof(typename P::uint_t)) != 0 ) + offset += 4; + + // copy string pool + uint32_t sharedSymbolStringsOffset = offset; + uint32_t sharedSymbolStringsSize = stringPool.copyPoolAndUpdateOffsets((char*)&newLinkEdit[sharedSymbolStringsOffset], (macho_nlist

*)&newLinkEdit[sharedSymbolTableStartOffset]); + offset += sharedSymbolStringsSize; + uint32_t newLinkeditUnalignedSize = offset; + uint64_t newLinkeditEnd = align(linkeditStartOffset+newLinkeditUnalignedSize, 14); + verboseLog(" symbol table size: %5uKB (%d exports, %d imports)", (sharedSymbolTableEndOffset-sharedSymbolTableStartOffset)/1024, sharedSymbolTableExportsCount, sharedSymbolTableImportsCount); + verboseLog(" symbol string pool size: %5uKB", sharedSymbolStringsSize/1024); + + // overwrite mapped LINKEDIT area in cache with new merged LINKEDIT content + verboseLog("LINKEDITS optimized from %uMB to %uMB", (uint32_t)totalUnoptLinkeditsSize/(1024*1024), (uint32_t)newLinkeditUnalignedSize/(1024*1024)); + ::memcpy((char*)cache.buffer().get()+linkeditStartOffset, newLinkEdit, newLinkeditUnalignedSize); + ::bzero((char*)cache.buffer().get()+linkeditStartOffset+newLinkeditUnalignedSize, totalUnoptLinkeditsSize-newLinkeditUnalignedSize); + ::free(newLinkEdit); + + // If making cache for customers, add extra accelerator tables for dyld + if ( addAcceleratorTables ) { + AcceleratorTables

tables(cache.buffer().get(), linkeditStartAddr, optimizers); + uint32_t tablesSize = tables.totalSize(); + if ( tablesSize < (totalUnoptLinkeditsSize-newLinkeditUnalignedSize) ) { + tables.copyTo((uint8_t*)cache.buffer().get()+newLinkeditEnd); + newLinkeditEnd += tablesSize; + uint64_t accelInfoAddr = align(linkeditStartAddr + newLinkeditUnalignedSize, 14); + cache.setAcceleratorInfoRange(accelInfoAddr, tablesSize); + } + else { + warning("not enough room to add dyld accelerator tables"); + } + } + + // update mapping to reduce linkedit size + cache.setLinkeditsMappingEndFileOffset(newLinkeditEnd); + + // overwrite end of un-opt linkedits to create a new unmapped region for local symbols + uint64_t newFileSize = newLinkeditEnd; + if ( dontMapLocalSymbols ) { + typedef typename P::E E; + uint32_t localSymbolsOffset = (uint32_t)align(newFileSize, 14); + uint32_t spaceAtEnd = linkeditEndOffset - (uint32_t)newLinkeditEnd; + dyldCacheLocalSymbolsInfo* infoHeader = (dyldCacheLocalSymbolsInfo*)((uint8_t*)cache.buffer().get()+localSymbolsOffset); + const uint32_t entriesOffset = sizeof(dyldCacheLocalSymbolsInfo); + const uint32_t entriesCount = (uint32_t)localSymbolInfos.size(); + const uint32_t nlistOffset = (uint32_t)align(entriesOffset + entriesCount * sizeof(dyldCacheLocalSymbolEntry), 4); // 16-byte align start + const uint32_t nlistCount = (uint32_t)unmappedLocalSymbols.size(); + const uint32_t stringsOffset = nlistOffset + nlistCount * sizeof(macho_nlist

); + // copy info for each dylib + dyldCacheLocalSymbolEntry* entries = (dyldCacheLocalSymbolEntry*)(((uint8_t*)infoHeader)+entriesOffset); + for (int i=0; i < entriesCount; ++i) { + entries[i].set_dylibOffset(localSymbolInfos[i].dylibOffset); + entries[i].set_nlistStartIndex(localSymbolInfos[i].nlistStartIndex); + entries[i].set_nlistCount(localSymbolInfos[i].nlistCount); + } + // copy nlists + macho_nlist

* newLocalsSymbolTable = (macho_nlist

*)(((uint8_t*)infoHeader)+nlistOffset); + ::memcpy(newLocalsSymbolTable, &unmappedLocalSymbols[0], nlistCount*sizeof(macho_nlist

)); + // copy string pool + const uint32_t stringsSize = localSymbolsStringPool.copyPoolAndUpdateOffsets(((char*)infoHeader)+stringsOffset, newLocalsSymbolTable); + const uint32_t localsRegionSize = (uint32_t)align(stringsOffset+stringsSize, 14); + if ( localsRegionSize > spaceAtEnd ) + terminate("not enough room to store local symbols"); + // fill in local symbols info + infoHeader->set_nlistOffset(nlistOffset); + infoHeader->set_nlistCount(nlistCount); + infoHeader->set_stringsOffset(stringsOffset); + infoHeader->set_stringsSize(stringsSize); + infoHeader->set_entriesOffset(entriesOffset); + infoHeader->set_entriesCount(entriesCount); + // update cache size + newFileSize += localsRegionSize; + verboseLog("Unmapped local symbol info: %uMB", localsRegionSize/(1024*1024)); + cache.setUnmappedLocalsRange(localSymbolsOffset, localsRegionSize); + } + + // update all load commands to new merged layout + for (LinkeditOptimizer

* op : optimizers) { + op->updateLoadCommands(linkeditStartOffset, linkeditStartAddr, newLinkeditEnd-linkeditStartOffset, + sharedSymbolTableStartOffset, sharedSymbolTableCount, + sharedSymbolStringsOffset, sharedSymbolStringsSize); + } + + return newFileSize; +} + +} // anonymous namespace + +template +void SharedCache::optimizeLinkedit(bool dontMapLocalSymbols, bool addAcceleratorTables, const std::vector& branchPoolOffsets) +{ + // construct a LinkeditOptimizer for each image + std::vector*> optimizers; + forEachImage([&](const void* mh, const char*, time_t, ino_t, const std::vector&) { + optimizers.push_back(new LinkeditOptimizer

(_buffer.get(), (macho_header

*)mh)); + }); + // add optimizer for each branch pool + for (uint64_t poolOffset : branchPoolOffsets) { + macho_header

* mh = (macho_header

*)((char*)_buffer.get() + poolOffset); + optimizers.push_back(new LinkeditOptimizer

(_buffer.get(), mh)); + } + + // merge linkedit info + _fileSize = mergeLinkedits(*this, dontMapLocalSymbols, addAcceleratorTables, optimizers); + + // delete optimizers + for (LinkeditOptimizer

* op : optimizers) + delete op; +} + +void SharedCache::optimizeLinkedit(bool dontMapLocalSymbols, bool addAcceleratorTables, const std::vector& branchPoolOffsets) +{ + switch ( _arch.arch ) { + case CPU_TYPE_ARM: + case CPU_TYPE_I386: + optimizeLinkedit>(dontMapLocalSymbols, addAcceleratorTables, branchPoolOffsets); + break; + case CPU_TYPE_X86_64: + case CPU_TYPE_ARM64: + optimizeLinkedit>(dontMapLocalSymbols, addAcceleratorTables, branchPoolOffsets); + break; + default: + terminate("unsupported arch 0x%08X", _arch.arch); + } +} + + + diff --git a/interlinked-dylibs/OptimizerObjC.cpp b/interlinked-dylibs/OptimizerObjC.cpp new file mode 100644 index 0000000..23c4474 --- /dev/null +++ b/interlinked-dylibs/OptimizerObjC.cpp @@ -0,0 +1,826 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +#include "mega-dylib-utils.h" +#include "Logging.h" +#include "MachOFileAbstraction.hpp" + + +#include +#include +#include +#include +#include +#include + + +// Scan a C++ or Swift length-mangled field. +static bool scanMangledField(const char *&string, const char *end, + const char *&field, int& length) +{ + // Leading zero not allowed. + if (*string == '0') return false; + + length = 0; + field = string; + while (field < end) { + char c = *field; + if (!isdigit(c)) break; + field++; + if (__builtin_smul_overflow(length, 10, &length)) return false; + if (__builtin_sadd_overflow(length, c - '0', &length)) return false; + } + + string = field + length; + return length > 0 && string <= end; +} + + +// copySwiftDemangledName +// Returns the pretty form of the given Swift-mangled class or protocol name. +// Returns nullptr if the string doesn't look like a mangled Swift name. +// The result must be freed with free(). +static char *copySwiftDemangledName(const char *string, bool isProtocol = false) +{ + if (!string) return nullptr; + + // Swift mangling prefix. + if (strncmp(string, isProtocol ? "_TtP" : "_TtC", 4) != 0) return nullptr; + string += 4; + + const char *end = string + strlen(string); + + // Module name. + const char *prefix; + int prefixLength; + if (string[0] == 's') { + // "s" is the Swift module. + prefix = "Swift"; + prefixLength = 5; + string += 1; + } else { + if (! scanMangledField(string, end, prefix, prefixLength)) return nullptr; + } + + // Class or protocol name. + const char *suffix; + int suffixLength; + if (! scanMangledField(string, end, suffix, suffixLength)) return nullptr; + + if (isProtocol) { + // Remainder must be "_". + if (strcmp(string, "_") != 0) return nullptr; + } else { + // Remainder must be empty. + if (string != end) return nullptr; + } + + char *result; + asprintf(&result, "%.*s.%.*s", prefixLength,prefix, suffixLength,suffix); + return result; +} + + +class ContentAccessor { +public: + ContentAccessor(SharedCache& cache) { + cache.forEachRegion([&] (void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + Info info = { (uint8_t*)content, (uint8_t*)content+size, vmAddr, vmAddr+size }; + _regions.push_back(info); + }); + } + + void* contentForVMAddr(uint64_t vmaddr) { + for (Info& info : _regions) { + if ( (info.startAddr <= vmaddr) && (vmaddr < info.endAddr) ) + return (void*)(info.contentStart + vmaddr - info.startAddr); + } + if ( vmaddr == 0 ) + return nullptr; + terminate("contentForVMAddr(0x%0llX) invalid vmaddr in ObjC data", vmaddr); + } + + uint64_t vmAddrForContent(const void* content) { + for (Info& info : _regions) { + if ( (info.contentStart <= content) && (content < info.contentEnd) ) + return info.startAddr + ((uint8_t*)content - (uint8_t*)info.contentStart); + } + terminate("vmAddrForContent(%p) invalid content pointer in ObjC data", content); + } + +private: + struct Info { uint8_t* contentStart; uint8_t* contentEnd; uint64_t startAddr; uint64_t endAddr; }; + std::vector _regions; +}; + + +// Access a section containing a list of pointers +template +class PointerSection +{ + typedef typename P::uint_t pint_t; +public: + PointerSection(ContentAccessor* cache, const macho_header

* mh, + const char* segname, const char* sectname) + : _cache(cache), + _section(mh->getSection(segname, sectname)), + _base(_section ? (pint_t*)cache->contentForVMAddr(_section->addr()) : 0), + _count(_section ? (pint_t)(_section->size() / sizeof(pint_t)) : 0) { + } + + pint_t count() const { return _count; } + + pint_t getVMAddress(pint_t index) const { + if ( index >= _count ) + terminate("index out of range in section %s", _section->sectname()); + return (pint_t)P::getP(_base[index]); + } + + T get(pint_t index) const { + return (T)_cache->contentForVMAddr(getVMAddress(index)); + } + + void setVMAddress(pint_t index, pint_t value) { + if (index >= _count) + terminate("index out of range in section %s", _section->sectname()); + P::setP(_base[index], value); + } + + void removeNulls() { + pint_t shift = 0; + for (pint_t i = 0; i < _count; i++) { + pint_t value = _base[i]; + if (value) { + _base[i-shift] = value; + } else { + shift++; + } + } + _count -= shift; + const_cast*>(_section)->set_size(_count * sizeof(pint_t)); + } + +private: + ContentAccessor* const _cache; + const macho_section

* const _section; + pint_t* const _base; + pint_t const _count; +}; + + +// Access a section containing an array of structures +template +class ArraySection +{ +public: + ArraySection(ContentAccessor* cache, const macho_header

* mh, + const char *segname, const char *sectname) + : _cache(cache), + _section(mh->getSection(segname, sectname)), + _base(_section ? (T *)cache->contentForVMAddr(_section->addr()) : 0), + _count(_section ? _section->size() / sizeof(T) : 0) { + } + + uint64_t count() const { return _count; } + + T& get(uint64_t index) const { + if (index >= _count) + terminate("index out of range in section %s", _section->sectname()); + return _base[index]; + } + +private: + ContentAccessor* const _cache; + const macho_section

* const _section; + T * const _base; + uint64_t const _count; +}; + + +#define SELOPT_WRITE +#include "objc-shared-cache.h" +#include "ObjC1Abstraction.hpp" +#include "ObjC2Abstraction.hpp" + + +namespace { + + + +template +class ObjCSelectorUniquer +{ +public: + typedef typename P::uint_t pint_t; + + ObjCSelectorUniquer(ContentAccessor* cache) : _cache(cache) { } + + pint_t visit(pint_t oldValue) + { + _count++; + const char *s = (const char *)_cache->contentForVMAddr(oldValue); + objc_opt::string_map::iterator element = + _selectorStrings.insert(objc_opt::string_map::value_type(s, oldValue)).first; + return (pint_t)element->second; + } + + objc_opt::string_map& strings() { + return _selectorStrings; + } + + size_t count() const { return _count; } + +private: + objc_opt::string_map _selectorStrings; + ContentAccessor* _cache; + size_t _count = 0; +}; + + +template +class ClassListBuilder +{ +private: + objc_opt::string_map _classNames; + objc_opt::class_map _classes; + size_t _count = 0; + HeaderInfoOptimizer>& _hInfos; + +public: + + ClassListBuilder(HeaderInfoOptimizer>& hinfos) : _hInfos(hinfos) { } + + void visitClass(ContentAccessor* cache, + const macho_header

* header, + objc_class_t

* cls) + { + if (cls->isMetaClass(cache)) return; + + const char *name = cls->getName(cache); + uint64_t name_vmaddr = cache->vmAddrForContent((void*)name); + uint64_t cls_vmaddr = cache->vmAddrForContent(cls); + uint64_t hinfo_vmaddr = cache->vmAddrForContent(_hInfos.hinfoForHeader(cache, header)); + _classNames.insert(objc_opt::string_map::value_type(name, name_vmaddr)); + _classes.insert(objc_opt::class_map::value_type(name, std::pair(cls_vmaddr, hinfo_vmaddr))); + _count++; + } + + objc_opt::string_map& classNames() { + return _classNames; + } + + objc_opt::class_map& classes() { + return _classes; + } + + size_t count() const { return _count; } +}; + +template +class ProtocolOptimizer +{ +private: + typedef typename P::uint_t pint_t; + + objc_opt::string_map _protocolNames; + objc_opt::protocol_map _protocols; + size_t _protocolCount; + size_t _protocolReferenceCount; + + friend class ProtocolReferenceWalker>; + + pint_t visitProtocolReference(ContentAccessor* cache, pint_t oldValue) + { + objc_protocol_t

* proto = (objc_protocol_t

*) + cache->contentForVMAddr(oldValue); + pint_t newValue = (pint_t)_protocols[proto->getName(cache)]; + if (oldValue != newValue) _protocolReferenceCount++; + return newValue; + } + +public: + + ProtocolOptimizer() + : _protocolNames() + , _protocols() + , _protocolCount(0) + , _protocolReferenceCount(0) + { } + + void addProtocols(ContentAccessor* cache, + const macho_header

* header) + { + PointerSection *> + protocols(cache, header, "__DATA", "__objc_protolist"); + + for (pint_t i = 0; i < protocols.count(); i++) { + objc_protocol_t

*proto = protocols.get(i); + + const char *name = proto->getName(cache); + if (_protocolNames.count(name) == 0) { + if (proto->getSize() > sizeof(objc_protocol_t

)) { + terminate("objc protocol is too big"); + } + + uint64_t name_vmaddr = cache->vmAddrForContent((void*)name); + uint64_t proto_vmaddr = cache->vmAddrForContent(proto); + _protocolNames.insert(objc_opt::string_map::value_type(name, name_vmaddr)); + _protocols.insert(objc_opt::protocol_map::value_type(name, proto_vmaddr)); + _protocolCount++; + } + } + } + + const char *writeProtocols(ContentAccessor* cache, + uint8_t *& rwdest, size_t& rwremaining, + uint8_t *& rodest, size_t& roremaining, + std::vector& pointersInData, + pint_t protocolClassVMAddr) + { + if (_protocolCount == 0) return NULL; + + if (protocolClassVMAddr == 0) { + return "libobjc's Protocol class symbol not found (metadata not optimized)"; + } + + size_t rwrequired = _protocolCount * sizeof(objc_protocol_t

); + if (rwremaining < rwrequired) { + return "libobjc's read-write section is too small (metadata not optimized)"; + } + + for (objc_opt::protocol_map::iterator iter = _protocols.begin(); + iter != _protocols.end(); + ++iter) + { + objc_protocol_t

* oldProto = (objc_protocol_t

*) + cache->contentForVMAddr(iter->second); + + // Create a new protocol object. + objc_protocol_t

* proto = (objc_protocol_t

*)rwdest; + rwdest += sizeof(*proto); + rwremaining -= sizeof(*proto); + + // Initialize it. + uint32_t oldSize = oldProto->getSize(); + memcpy(proto, oldProto, oldSize); + if (!proto->getIsaVMAddr()) { + proto->setIsaVMAddr(protocolClassVMAddr); + } + if (oldSize < sizeof(*proto)) { + // Protocol object is old. Populate new fields. + proto->setSize(sizeof(objc_protocol_t

)); + // missing extendedMethodTypes is already nil + } + // Some protocol objects are big enough to have the + // demangledName field but don't initialize it. + // Initialize it here if it is not already set. + if (!proto->getDemangledName(cache)) { + const char *roName = proto->getName(cache); + char *demangledName = copySwiftDemangledName(roName, true); + if (demangledName) { + size_t length = 1 + strlen(demangledName); + if (roremaining < length) { + return "libobjc's read-only section is too small (metadata not optimized)"; + } + + memmove(rodest, demangledName, length); + roName = (const char *)rodest; + rodest += length; + roremaining -= length; + + free(demangledName); + } + proto->setDemangledName(cache, roName); + } + proto->setFixedUp(); + + // Redirect the protocol table at our new object. + iter->second = cache->vmAddrForContent(proto); + + // Add new rebase entries. + proto->addPointers(pointersInData); + } + + return NULL; + } + + void updateReferences(ContentAccessor* cache, const macho_header

* header) + { + ProtocolReferenceWalker> refs(*this); + refs.walk(cache, header); + } + + objc_opt::string_map& protocolNames() { + return _protocolNames; + } + + objc_opt::protocol_map& protocols() { + return _protocols; + } + + size_t protocolCount() const { return _protocolCount; } + size_t protocolReferenceCount() const { return _protocolReferenceCount; } +}; + + +static int percent(size_t num, size_t denom) { + if (denom) + return (int)(num / (double)denom * 100); + else + return 100; +} + + +template +void optimizeObjC(SharedCache& cache, std::vector& pointersForASLR, bool forProduction) +{ + typedef typename P::E E; + typedef typename P::uint_t pint_t; + + verboseLog("Optimizing objc metadata:"); + verboseLog(" cache type is %s", + forProduction ? "production" : "development"); + + ContentAccessor cacheAccessor(cache); + + size_t headerSize = P::round_up(sizeof(objc_opt::objc_opt_t)); + if (headerSize != sizeof(objc_opt::objc_opt_t)) { + warning("libobjc's optimization structure size is wrong (metadata not optimized)"); + } + + // + // Find libobjc's empty sections and build list of images with objc metadata + // + const macho_section

*optROSection = nullptr; + const macho_section

*optRWSection = nullptr; + const macho_section

*optPointerListSection = nullptr; + std::vector*> objcDylibs; + cache.forEachImage([&](const void* machHeader, const char* installName, + time_t, ino_t, const std::vector& segments) { + const macho_header

* mh = (const macho_header

*)machHeader; + if ( strstr(installName, "/libobjc.") != nullptr ) { + optROSection = mh->getSection("__TEXT", "__objc_opt_ro"); + optRWSection = mh->getSection("__DATA", "__objc_opt_rw"); + optPointerListSection = mh->getSection("__DATA", "__objc_opt_ptrs"); + } + if ( mh->getSection("__DATA", "__objc_imageinfo") || mh->getSection("__OBJC", "__image_info") ) { + objcDylibs.push_back(mh); + } + // log("installName %s at mhdr 0x%016lx", installName, (uintptr_t)cacheAccessor.vmAddrForContent((void*)mh)); + }); + if ( optROSection == nullptr ) { + warning("libobjc's read-only section missing (metadata not optimized)"); + return; + } + if ( optRWSection == nullptr ) { + warning("libobjc's read/write section missing (metadata not optimized)"); + return; + } + if ( optPointerListSection == nullptr ) { + warning("libobjc's pointer list section missing (metadata not optimized)"); + return; + } + + uint8_t* optROData = (uint8_t*)cacheAccessor.contentForVMAddr(optROSection->addr()); + size_t optRORemaining = optROSection->size(); + uint8_t* optRWData = (uint8_t*)cacheAccessor.contentForVMAddr(optRWSection->addr()); + size_t optRWRemaining = optRWSection->size(); + if (optRORemaining < headerSize) { + warning("libobjc's read-only section is too small (metadata not optimized)"); + return; + } + objc_opt::objc_opt_t* optROHeader = (objc_opt::objc_opt_t *)optROData; + optROData += headerSize; + optRORemaining -= headerSize; + if (E::get32(optROHeader->version) != objc_opt::VERSION) { + warning("libobjc's read-only section version is unrecognized (metadata not optimized)"); + return; + } + + if (optPointerListSection->size() < sizeof(objc_opt::objc_opt_pointerlist_tt)) { + warning("libobjc's pointer list section is too small (metadata not optimized)"); + return; + } + const objc_opt::objc_opt_pointerlist_tt *optPointerList = (const objc_opt::objc_opt_pointerlist_tt *)cacheAccessor.contentForVMAddr(optPointerListSection->addr()); + + // Write nothing to optROHeader until everything else is written. + // If something fails below, libobjc will not use the section. + + + // + // Make copy of objcList and sort that list. + // + std::vector*> addressSortedDylibs = objcDylibs; + std::sort(addressSortedDylibs.begin(), addressSortedDylibs.end(), [](const macho_header

* lmh, const macho_header

* rmh) -> bool { + return lmh < rmh; + }); + + // + // Build HeaderInfo list in cache + // + // First the RO header info + // log("writing out %d RO dylibs at offset %d", (uint32_t)objcDylibs.size(), (uint32_t)(optROSection->size() - optRORemaining)); + uint64_t hinfoROVMAddr = optROSection->addr() + optROSection->size() - optRORemaining; + HeaderInfoOptimizer> hinfoROOptimizer; + const char* err = hinfoROOptimizer.init((uint32_t)objcDylibs.size(), optROData, optRORemaining); + if (err) { + warning("%s", err); + return; + } + else { + for (const macho_header

* mh : addressSortedDylibs) { + hinfoROOptimizer.update(&cacheAccessor, mh, pointersForASLR); + } + } + + // Then the RW header info + // log("writing out %d RW dylibs at offset %d", (uint32_t)objcDylibs.size(), (uint32_t)(optRWSection->size() - optRWRemaining)); + uint64_t hinfoRWVMAddr = (uint64_t)optRWSection->addr() + (uint64_t)optRWSection->size() - optRWRemaining; + HeaderInfoOptimizer> hinfoRWOptimizer; + err = hinfoRWOptimizer.init((uint32_t)objcDylibs.size(), optRWData, optRWRemaining); + if (err) { + warning("%s", err); + return; + } + else { + for (const macho_header

* mh : addressSortedDylibs) { + hinfoRWOptimizer.update(&cacheAccessor, mh, pointersForASLR); + } + } + + // + // Update selector references and build selector list + // + // This is SAFE: if we run out of room for the selector table, + // the modified binaries are still usable. + // + // Heuristic: choose selectors from libraries with more selector cstring data first. + // This tries to localize selector cstring memory. + // + ObjCSelectorUniquer

uniq(&cacheAccessor); + std::vector*> sizeSortedDylibs = objcDylibs; + std::sort(sizeSortedDylibs.begin(), sizeSortedDylibs.end(), [](const macho_header

* lmh, const macho_header

* rmh) -> bool { + const macho_section

* lSection = lmh->getSection("__TEXT", "__objc_methname"); + const macho_section

* rSection = rmh->getSection("__TEXT", "__objc_methname"); + uint64_t lSelectorSize = (lSection ? lSection->size() : 0); + uint64_t rSelectorSize = (rSection ? rSection->size() : 0); + return lSelectorSize > rSelectorSize; + }); + + SelectorOptimizer > selOptimizer(uniq); + for (const macho_header

* mh : sizeSortedDylibs) { + LegacySelectorUpdater>::update(&cacheAccessor, mh, uniq); + selOptimizer.optimize(&cacheAccessor, mh); + } + + verboseLog(" uniqued % 6ld selectors", + uniq.strings().size()); + verboseLog(" updated % 6ld selector references", + uniq.count()); + + uint64_t seloptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining; + objc_opt::objc_selopt_t *selopt = new(optROData) objc_opt::objc_selopt_t; + err = selopt->write(seloptVMAddr, optRORemaining, uniq.strings()); + if (err) { + warning("%s", err); + return; + } + optROData += selopt->size(); + optRORemaining -= selopt->size(); + uint32_t seloptCapacity = selopt->capacity; + uint32_t seloptOccupied = selopt->occupied; + selopt->byteswap(E::little_endian), selopt = nullptr; + + verboseLog(" selector table occupancy %u/%u (%u%%)", + seloptOccupied, seloptCapacity, + (unsigned)(seloptOccupied/(double)seloptCapacity*100)); + + + // + // Detect classes that have missing weak-import superclasses. + // + // Production only. Development cache does not do this: a replacement + // library could omit a class at runtime that was present during + // cache construction. + // + // This is SAFE: the binaries themselves are unmodified. + bool noMissingWeakSuperclasses = false; // dev cache can't promise otherwise + if (forProduction) { + WeakClassDetector

weakopt; + noMissingWeakSuperclasses = + weakopt.noMissingWeakSuperclasses(&cacheAccessor, sizeSortedDylibs); + + // Shared cache does not currently support unbound weak references. + // Here we assert that there are none. If support is added later then + // this assertion needs to be removed and this path needs to be tested. + if (!noMissingWeakSuperclasses) { + terminate("Some Objective-C class has a superclass that is " + "weak-import and missing from the cache."); + } + } + + + // + // Build class table. + // + // This is SAFE: the binaries themselves are unmodified. + ClassListBuilder

classes(hinfoROOptimizer); + ClassWalker> classWalker(classes); + for (const macho_header

* mh : sizeSortedDylibs) { + classWalker.walk(&cacheAccessor, mh); + } + + verboseLog(" recorded % 6ld classes", + classes.classNames().size()); + + uint64_t clsoptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining; + objc_opt::objc_clsopt_t *clsopt = new(optROData) objc_opt::objc_clsopt_t; + err = clsopt->write(clsoptVMAddr, optRORemaining, + classes.classNames(), classes.classes(), false); + if (err) { + warning("%s", err); + return; + } + optROData += clsopt->size(); + optRORemaining -= clsopt->size(); + size_t duplicateCount = clsopt->duplicateCount(); + uint32_t clsoptCapacity = clsopt->capacity; + uint32_t clsoptOccupied = clsopt->occupied; + clsopt->byteswap(E::little_endian); + clsopt = nullptr; + + verboseLog(" found % 6ld duplicate classes", + duplicateCount); + verboseLog(" class table occupancy %u/%u (%u%%)", + clsoptOccupied, clsoptCapacity, + (unsigned)(clsoptOccupied/(double)clsoptCapacity*100)); + + + // + // Sort method lists. + // + // This is SAFE: modified binaries are still usable as unsorted lists. + // This must be done AFTER uniquing selectors. + MethodListSorter

methodSorter; + for (const macho_header

* mh : sizeSortedDylibs) { + methodSorter.optimize(&cacheAccessor, mh); + } + + verboseLog(" sorted % 6ld method lists", + methodSorter.optimized()); + + + // Unique protocols and build protocol table. + + // This is SAFE: no protocol references are updated yet + // This must be done AFTER updating method lists. + + ProtocolOptimizer

protocolOptimizer; + for (const macho_header

* mh : sizeSortedDylibs) { + protocolOptimizer.addProtocols(&cacheAccessor, mh); + } + + verboseLog(" uniqued % 6ld protocols", + protocolOptimizer.protocolCount()); + + pint_t protocolClassVMAddr = (pint_t)P::getP(optPointerList->protocolClass); + err = protocolOptimizer.writeProtocols(&cacheAccessor, + optRWData, optRWRemaining, + optROData, optRORemaining, + pointersForASLR, protocolClassVMAddr); + if (err) { + warning("%s", err); + return; + } + + uint64_t protocoloptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining; + objc_opt::objc_protocolopt_t *protocolopt = new (optROData) objc_opt::objc_protocolopt_t; + err = protocolopt->write(protocoloptVMAddr, optRORemaining, + protocolOptimizer.protocolNames(), + protocolOptimizer.protocols(), true); + if (err) { + warning("%s", err); + return; + } + optROData += protocolopt->size(); + optRORemaining -= protocolopt->size(); + uint32_t protocoloptCapacity = protocolopt->capacity; + uint32_t protocoloptOccupied = protocolopt->occupied; + protocolopt->byteswap(E::little_endian), protocolopt = NULL; + + verboseLog(" protocol table occupancy %u/%u (%u%%)", + protocoloptOccupied, protocoloptCapacity, + (unsigned)(protocoloptOccupied/(double)protocoloptCapacity*100)); + + + // Redirect protocol references to the uniqued protocols. + + // This is SAFE: the new protocol objects are still usable as-is. + for (const macho_header

* mh : sizeSortedDylibs) { + protocolOptimizer.updateReferences(&cacheAccessor, mh); + } + + verboseLog(" updated % 6ld protocol references", + protocolOptimizer.protocolReferenceCount()); + + + // + // Repair ivar offsets. + // + // This is SAFE: the runtime always validates ivar offsets at runtime. + IvarOffsetOptimizer

ivarOffsetOptimizer; + for (const macho_header

* mh : sizeSortedDylibs) { + ivarOffsetOptimizer.optimize(&cacheAccessor, mh); + } + + verboseLog(" updated % 6ld ivar offsets", + ivarOffsetOptimizer.optimized()); + + + // Collect flags. + uint32_t headerFlags = 0; + if (forProduction) { + headerFlags |= objc_opt::IsProduction; + } + if (noMissingWeakSuperclasses) { + headerFlags |= objc_opt::NoMissingWeakSuperclasses; + } + + + // Success. Mark dylibs as optimized. + for (const macho_header

* mh : sizeSortedDylibs) { + const macho_section

* imageInfoSection = mh->getSection("__DATA", "__objc_imageinfo"); + if (!imageInfoSection) { + imageInfoSection = mh->getSection("__OBJC", "__image_info"); + } + if (imageInfoSection) { + objc_image_info

* info = (objc_image_info

*)cacheAccessor.contentForVMAddr(imageInfoSection->addr()); + info->setOptimizedByDyld(); + } + } + + + // Success. Update RO header last. + E::set32(optROHeader->flags, headerFlags); + E::set32(optROHeader->selopt_offset, (uint32_t)(seloptVMAddr - optROSection->addr())); + E::set32(optROHeader->clsopt_offset, (uint32_t)(clsoptVMAddr - optROSection->addr())); + E::set32(optROHeader->protocolopt_offset, (uint32_t)(protocoloptVMAddr - optROSection->addr())); + E::set32(optROHeader->headeropt_ro_offset, (uint32_t)(hinfoROVMAddr - optROSection->addr())); + E::set32(optROHeader->headeropt_rw_offset, (uint32_t)(hinfoRWVMAddr - optROSection->addr())); + + // Log statistics. + size_t roSize = optROSection->size() - optRORemaining; + size_t rwSize = optRWSection->size() - optRWRemaining; + verboseLog(" %zu/%llu bytes " + "(%d%%) used in libobjc read-only optimization section", + roSize, optROSection->size(), + percent(roSize, optROSection->size())); + verboseLog(" %zu/%llu bytes " + "(%d%%) used in libobjc read/write optimization section", + rwSize, optRWSection->size(), + percent(rwSize, optRWSection->size())); + verboseLog(" wrote objc metadata optimization version %d", + objc_opt::VERSION); +} + + +} // anon namespace + + +void SharedCache::optimizeObjC(bool forProduction) +{ + switch ( _arch.arch ) { + case CPU_TYPE_ARM: + case CPU_TYPE_I386: + ::optimizeObjC>(*this, _pointersForASLR, forProduction); + break; + case CPU_TYPE_X86_64: + case CPU_TYPE_ARM64: + ::optimizeObjC>(*this, _pointersForASLR, forProduction); + break; + default: + terminate("unsupported arch 0x%08X", _arch.arch); + } +} diff --git a/interlinked-dylibs/SharedCache.cpp b/interlinked-dylibs/SharedCache.cpp new file mode 100644 index 0000000..acabc4c --- /dev/null +++ b/interlinked-dylibs/SharedCache.cpp @@ -0,0 +1,1623 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include "mega-dylib-utils.h" +#include "MachOFileAbstraction.hpp" +#include "FileAbstraction.hpp" +#include "Logging.h" + +#include "dyld_cache_config.h" + +#import "Trie.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OptimizerBranches.h" + +#include "CacheFileAbstraction.hpp" +#include "CodeSigningTypes.h" + +namespace { + +uint64_t sharedRegionStartExecutableAddress(ArchPair arch) +{ + switch (arch.arch) { + case CPU_TYPE_ARM: + return ARM_SHARED_REGION_START; + case CPU_TYPE_I386: + return SHARED_REGION_BASE_I386; + case CPU_TYPE_X86_64: + return SHARED_REGION_BASE_X86_64; + case CPU_TYPE_ARM64: + return ARM64_SHARED_REGION_START; + default: + terminate("unsupported arch 0x%08X", arch.arch); + } +} + +uint64_t sharedRegionStartWriteableAddress(ArchPair arch, uint64_t textEndAddress) +{ + switch (arch.arch) { + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: + // more efficient if code and data never in same 2MB chunk + return textEndAddress + 0x04000000; + case CPU_TYPE_ARM: + return textEndAddress; + case CPU_TYPE_ARM64: + return textEndAddress + 32*1024*1024; // Add 32MB padding before arm64 dyld shared cache R/W region + default: + terminate("unsupported arch 0x%08X", arch.arch); + } +} + +uint64_t sharedRegionStartReadOnlyAddress(ArchPair arch, uint64_t dataEndAddress, uint64_t textEndAddress) +{ + switch (arch.arch) { + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: + // more efficient if code and data never in same 2MB chunk + return dataEndAddress + 0x04000000; + case CPU_TYPE_ARM: + return dataEndAddress; + case CPU_TYPE_ARM64: + return dataEndAddress + 32*1024*1024; // Add 32MB padding before arm64 dyld shared cache R/W region + default: + terminate("unsupported arch 0x%08X", arch.arch); + } +} + +} // anon namespace + +uint8_t sharedRegionRegionAlignment(ArchPair arch) { + switch (arch.arch) { + return ARM_SHARED_REGION_SIZE; + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: + return 12; // 4KB + case CPU_TYPE_ARM: + case CPU_TYPE_ARM64: + return 14; // 16KB + default: + terminate("unsupported arch 0x%08X", arch.arch); + } +} + +uint64_t sharedRegionRegionSize(ArchPair arch) { + switch ( arch.arch ) { + case CPU_TYPE_I386: + return SHARED_REGION_SIZE_I386; + case CPU_TYPE_X86_64: + return SHARED_REGION_SIZE_X86_64; + case CPU_TYPE_ARM: + return ARM_SHARED_REGION_SIZE; + case CPU_TYPE_ARM64: + return ARM64_SHARED_REGION_SIZE; + default: + terminate("unsupported arch 0x%08X", arch.arch); + } +} + +static const std::tuple gArchitectures[] = { + {"i386", nullptr, ArchPair( CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL )}, + {"x86_64", nullptr, ArchPair( CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL )}, + {"x86_64h", "x86_64", ArchPair( CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H )}, + {"armv4t", nullptr, ArchPair( CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V4T )}, + {"armv5", nullptr, ArchPair( CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V5TEJ )}, + {"armv6", nullptr, ArchPair( CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6 )}, + {"armv7", nullptr, ArchPair( CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7 )}, + {"armv7f", nullptr, ArchPair( CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7F )}, + {"armv7k", nullptr, ArchPair( CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K )}, + {"armv7s", "armv7", ArchPair( CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S )}, + {"arm64", nullptr, ArchPair( CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL )}, +}; + +ArchPair archForString(const std::string& archStr) { + for (auto& a : gArchitectures) { + if ( std::get<0>( a ) == archStr ) return std::get<2>( a ); + } + terminate("unknown architecture %s", archStr.c_str()); +} + +std::string stringForArch(ArchPair arch, bool allowUnknown) { + for (auto& a : gArchitectures) { + // FIXME LIB64 is set on some binaries and not other + if ( std::get<2>( a ).arch == arch.arch && std::get<2>( a ).subtype == ( arch.subtype & ~CPU_SUBTYPE_MASK ) ) + return std::get<0>( a ); + } + + auto unknownString = + "unrecognized cpu type " + std::to_string(arch.arch) + + " subtype " + std::to_string(arch.subtype); + if (allowUnknown) return unknownString; + else terminate("%s", unknownString.c_str()); +} + +std::string fallbackArchStringForArchString( const std::string& archStr ) { + for ( auto& a : gArchitectures ) { + if ( std::get<0>( a ) == archStr && std::get<1>( a ) != nullptr ) { + return std::get<1>( a ); + } + } + + return ""; +} + +SharedCache::SharedCache(Manifest& manifest, + const std::string& configuration, const std::string& architecture) : + _manifest(manifest), _arch(archForString(architecture)), + _archManifest(manifest.configurations.find(configuration)->second.architectures.find(architecture)->second), _buffer(nullptr), + _fileSize(0), _vmSize(0), _aliasCount(0), _slideInfoFileOffset(0), _slideInfoBufferSize(0) { + auto maxCacheVMSize = sharedRegionRegionSize(_arch); + + for ( auto& includedDylib : _archManifest.results.dylibs ) { + if (includedDylib.second.included) { + //assert(manifest.dylibs.count(includedDylib.first) > 0); + //assert(manifest.dylibs.find(includedDylib.first)->second.proxies.count(architecture) > 0); + MachOProxy* proxy = _manifest.dylibProxy( includedDylib.first, architecture ); + assert(proxy != nullptr); + _dylibs.push_back(proxy); + } + } + + // error out instead of crash if cache has no dylibs + if ( _dylibs.size() < 30 ) // FIXME: plist should specify required vs optional dylibs + terminate("missing required minimum set of dylibs"); + + for (auto &dylib : _dylibs) { + _segmentMap[dylib].reserve(dylib->segments.size()); + for (const auto& seg : dylib->segments) + _segmentMap[dylib].push_back(&seg); + _aliasCount += dylib->installNameAliases.size(); + } + + sortDylibs(_manifest.dylibOrderFile); + if ( !_manifest.dirtyDataOrderFile.empty() ) + loadDirtyDataOrderFile(_manifest.dirtyDataOrderFile); + + assignSegmentAddresses(); + if ( _vmSize > maxCacheVMSize ) + verboseLog("%s cache overflow. %lluMB (max %lluMB)", archName().c_str(), _vmSize/1024/1024, maxCacheVMSize/1024/1024); + while (_vmSize > maxCacheVMSize) { + auto evictedDylib = manifest.removeLargestLeafDylib( configuration, architecture ); + _dylibs.erase( std::remove( _dylibs.begin(), _dylibs.end(), evictedDylib ), _dylibs.end() ); + _aliasCount -= evictedDylib->installNameAliases.size(); + assignSegmentAddresses(); + } +} + +// There is an order file specifying the order in which dylibs are laid out in +// general, as well as an order file specifying the order in which __DATA_DIRTY +// segments are laid out in particular. +// +// The syntax is one dylib (install name) per line. Blank lines are ignored. +// Comments start with the # character. + +static std::unordered_map loadOrderFile(const std::string& orderFile) { + std::unordered_map order; + + std::ifstream myfile(orderFile); + if ( myfile.is_open() ) { + uint32_t count = 0; + std::string line; + while ( std::getline(myfile, line) ) { + size_t pos = line.find('#'); + if ( pos != std::string::npos ) + line.resize(pos); + while ( !line.empty() && isspace(line.back()) ) { + line.pop_back(); + } + if ( !line.empty() ) + order[line] = count++; + } + myfile.close(); + } else { + warning("could not load orderfile '%s'", orderFile.c_str()); + } + + return order; +} + +void SharedCache::loadDirtyDataOrderFile(const std::string& dirtyDataOrderFile) { + _dataDirtySegsOrder = loadOrderFile(dirtyDataOrderFile); +} + +void SharedCache::sortDylibs(const std::string& dylibOrderFile) { + std::unordered_map dylibOrder; + if ( !dylibOrderFile.empty() ) + dylibOrder = loadOrderFile(dylibOrderFile); + + std::sort(_dylibs.begin(), _dylibs.end(), [&](const MachOProxy* a, + const MachOProxy* b) { + const std::string& pathA = a->installName; + const std::string& pathB = b->installName; + + const auto& orderA = dylibOrder.find(pathA); + const auto& orderB = dylibOrder.find(pathB); + bool foundA = (orderA != dylibOrder.end()); + bool foundB = (orderB != dylibOrder.end()); + + // Order all dylibs specified in the order file first, in the order specified in + // the file, followed by any other dylibs in lexicographic order. + if ( foundA && foundB ) + return orderA->second < orderB->second; + else if ( foundA ) + return true; + else if ( foundB ) + return false; + else + return pathA < pathB; + }); +} + +void SharedCache::buildUnoptimizedCache(void) { + _buffer = std::shared_ptr(calloc(_fileSize, 1), free); + writeCacheHeader(); + writeCacheSegments(); + rebaseAll(); + bindAll(); +} + +template +void SharedCache::buildForDevelopment(const std::string& cachePath) { + typedef typename P::E E; + std::vector emptyBranchPoolOffsets; + buildUnoptimizedCache(); + optimizeObjC(false/*not production*/); + if (_manifest.platform == "osx") { + optimizeLinkedit(false, false, emptyBranchPoolOffsets); + } else { + optimizeLinkedit(true, false, emptyBranchPoolOffsets); + } + writeSlideInfoV2(); + + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + header->set_cacheType(kDyldSharedCacheTypeDevelopment); + recomputeCacheUUID(); + + // Calculate the VMSize of the resulting cache + uint64_t endAddr = 0; + + forEachRegion([&] (void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + if (vmAddr+size > endAddr) + endAddr = vmAddr+size; + }); + _vmSize = endAddr - sharedRegionStartExecutableAddress(_arch); + + if (_manifest.platform == "osx") { + appendCodeSignature("release"); + } else { + appendCodeSignature("development"); + } +} + +template +void SharedCache::buildForProduction(const std::string& cachePath) { + typedef typename P::E E; + buildUnoptimizedCache(); + optimizeObjC(true/*production*/); + uint64_t cacheStartAddress = sharedRegionStartExecutableAddress(_arch); + + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + header->set_cacheType(kDyldSharedCacheTypeProduction); + + // build vector of branch pool addresss + std::vector branchPoolStartAddrs; + std::vector branchPoolOffsets; + const uint64_t* p = (uint64_t*)((uint8_t*)_buffer.get() + header->branchPoolsOffset()); + for (int i=0; i < header->branchPoolsCount(); ++i) { + uint64_t poolAddr = LittleEndian::get64(p[i]); + branchPoolStartAddrs.push_back(poolAddr); + branchPoolOffsets.push_back(poolAddr - cacheStartAddress); + } + + bypassStubs(branchPoolStartAddrs); + optimizeLinkedit(true, true, branchPoolOffsets); + writeSlideInfoV2(); + + recomputeCacheUUID(); + + // Calculate the VMSize of the resulting cache + uint64_t endAddr = 0; + + forEachRegion([&] (void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + if (vmAddr+size > endAddr) + endAddr = vmAddr+size; + }); + _vmSize = endAddr - cacheStartAddress; + + appendCodeSignature("release"); +} + +bool SharedCache::writeCacheMapFile(const std::string& mapPath) { + FILE* fmap = ::fopen(mapPath.c_str(), "w"); + if ( fmap == NULL ) + return false; + + std::vector regionStartAddresses; + std::vector regionSizes; + std::vector regionFileOffsets; + + forEachRegion([&] (void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + regionStartAddresses.push_back(vmAddr); + regionSizes.push_back(size); + regionFileOffsets.push_back((uint8_t*)content - (uint8_t*)_buffer.get()); + const char* prot = "RW"; + if ( permissions == (VM_PROT_EXECUTE|VM_PROT_READ) ) + prot = "EX"; + else if ( permissions == VM_PROT_READ ) + prot = "RO"; + if ( size > 1024*1024 ) + fprintf(fmap, "mapping %s %4lluMB 0x%0llX -> 0x%0llX\n", prot, size/(1024*1024), vmAddr, vmAddr+size); + else + fprintf(fmap, "mapping %s %4lluKB 0x%0llX -> 0x%0llX\n", prot, size/1024, vmAddr, vmAddr+size); + }); + + // TODO: add linkedit breakdown + fprintf(fmap, "\n\n"); + + std::unordered_set seenHeaders; + forEachImage([&](const void* machHeader, const char* installName, time_t mtime, + ino_t inode, const std::vector& segments) { + if ( !seenHeaders.count(machHeader) ) { + seenHeaders.insert(machHeader); + + fprintf(fmap, "%s\n", installName); + for (const MachOProxy::Segment& seg : segments) { + uint64_t vmAddr = 0; + for (int i=0; i < regionSizes.size(); ++i) { + if ( (seg.fileOffset >= regionFileOffsets[i]) && (seg.fileOffset < (regionFileOffsets[i]+regionSizes[i])) ) { + vmAddr = regionStartAddresses[i] + seg.fileOffset - regionFileOffsets[i]; + } + } + fprintf(fmap, "\t%16s 0x%08llX -> 0x%08llX\n", seg.name.c_str(), vmAddr, vmAddr+seg.size); + } + } + }); + + + ::fclose(fmap); + return true; +} + +template +std::vector getSegments(const void* cacheBuffer, const void* machHeader) +{ + std::vector result; + macho_header

* mh = (macho_header

*)machHeader; + const uint32_t cmd_count = mh->ncmds(); + const macho_load_command

* cmd = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd() != macho_segment_command

::CMD ) + continue; + macho_segment_command

* segCmd = (macho_segment_command

*)cmd; + MachOProxy::Segment seg; + seg.name = segCmd->segname(); + seg.size = segCmd->vmsize(); + seg.diskSize = (uint32_t)segCmd->filesize(); + seg.fileOffset = (uint32_t)segCmd->fileoff(); + seg.protection = segCmd->initprot(); + // HACK until lldb fixed in + if ( (seg.fileOffset == 0) && (strcmp(segCmd->segname(), "__TEXT") == 0) ) + seg.fileOffset = (uint32_t)((char*)machHeader - (char*)cacheBuffer); + if ( segCmd->nsects() > 0 ) { + seg.p2align = 0; + const macho_section

* const sectionsStart = (macho_section

*)((uint8_t*)segCmd + sizeof(macho_segment_command

)); + const macho_section

* const sectionsEnd = §ionsStart[segCmd->nsects()]; + for (const macho_section

* sect=sectionsStart; sect < sectionsEnd; ++sect) { + if ( sect->align() > seg.p2align ) + seg.p2align = sect->align(); + } + } + else { + seg.p2align = 12; + } + result.push_back(seg); + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } + return result; +} + +template +void SharedCache::forEachImage(DylibHandler handler) +{ +#if NEW_CACHE_FILE_FORMAT + terminate("forEachImage() not implemented"); +#else + typedef typename P::E E; + const dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + const dyldCacheImageInfo* dylibs = (dyldCacheImageInfo*)((char*)_buffer.get() + header->imagesOffset()); + const dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)((char*)_buffer.get() + header->mappingOffset()); + if ( mappings[0].file_offset() != 0 ) + terminate("malformed cache file"); + uint64_t firstImageOffset = 0; + uint64_t firstRegionAddress = mappings[0].address(); + const void* cacheEnd = (char*)_buffer.get() + _fileSize; + if ( (const void*)&dylibs[header->imagesCount()] > cacheEnd ) + return; + for (uint32_t i=0; i < header->imagesCount(); ++i) { + const char* dylibPath = (char*)_buffer.get() + dylibs[i].pathFileOffset(); + if ( dylibPath > cacheEnd ) + return; + uint64_t offset = dylibs[i].address() - firstRegionAddress; + if ( firstImageOffset == 0 ) + firstImageOffset = offset; + // skip over aliases + if ( dylibs[i].pathFileOffset() < firstImageOffset) + continue; + const void* mh = (char*)_buffer.get() + offset; + time_t inode = dylibs[i].inode(); + ino_t modTime = dylibs[i].modTime(); + handler(mh, dylibPath, modTime, inode, getSegments

(_buffer.get(), mh)); + } +#endif +} + + +template +void SharedCache::recomputeCacheUUID(void) +{ + uint8_t* uuidLoc = nullptr; +#if NEW_CACHE_FILE_FORMAT + const macho_header

* mh = (macho_header

*)cacheBuffer; + const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + const uint32_t cmd_count = mh->ncmds(); + const macho_load_command

* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd() == LC_UUID ) { + const macho_uuid_command

* uuidCmd = (macho_uuid_command

*)cmd; + uuidLoc = const_cast(uuidCmd->uuid()); + break; + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } +#else + dyldCacheHeader

* header = (dyldCacheHeader

*)_buffer.get(); + uuidLoc = const_cast(header->uuid()); +#endif + + // Clear existing UUID, then MD5 whole cache buffer. + bzero(uuidLoc, 16); + CC_MD5(_buffer.get(), (unsigned)_fileSize, uuidLoc); + // uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats + uuidLoc[6] = ( uuidLoc[6] & 0x0F ) | ( 3 << 4 ); + uuidLoc[8] = ( uuidLoc[8] & 0x3F ) | 0x80; +} + +template +void SharedCache::setLinkeditsMappingEndFileOffset(uint64_t newFileSize) +{ +#if NEW_CACHE_FILE_FORMAT + terminate("setLinkeditsMappingEndFileOffset() not implemented"); +#else + typedef typename P::E E; + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)((char*)_buffer.get() + header->mappingOffset()); + uint64_t newReadOnlySize = newFileSize - mappings[2].file_offset(); + mappings[2].set_size(newReadOnlySize); + header->set_codeSignatureOffset(newFileSize); + _readOnlyRegion.size = (newReadOnlySize); +#endif +} + +template +void SharedCache::setUnmappedLocalsRange(uint64_t localSymbolsOffset, uint32_t unmappedSize) +{ +#if NEW_CACHE_FILE_FORMAT + terminate("setUnmappedLocalsRange() not implemented"); +#else + typedef typename P::E E; + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + header->set_localSymbolsOffset(localSymbolsOffset); + header->set_localSymbolsSize(unmappedSize); + // move start of code signature to new end of file + header->set_codeSignatureOffset(localSymbolsOffset+unmappedSize); +#endif +} + +template +void SharedCache::setAcceleratorInfoRange(uint64_t accelInfoAddr, uint32_t accelInfoSize) +{ +#if NEW_CACHE_FILE_FORMAT + terminate("setUnmappedLocalsRange() not implemented"); +#else + typedef typename P::E E; + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + header->set_accelerateInfoAddr(accelInfoAddr); + header->set_accelerateInfoSize(accelInfoSize); +#endif +} + +template +void SharedCache::forEachRegion(RegionHandler handler) +{ +#if NEW_CACHE_FILE_FORMAT + const macho_header

* mh = (macho_header

*)cacheBuffer; + const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); + const uint32_t cmd_count = mh->ncmds(); + const macho_load_command

* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd() == macho_segment_command

::CMD ) { + const macho_segment_command

* segCmd = (macho_segment_command

*)cmd; + handler((char*)cacheBuffer + segCmd->fileoff(), segCmd->vmaddr(), segCmd->vmsize(), segCmd->initprot()); + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } +#else + typedef typename P::E E; + const dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + const dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)((char*)_buffer.get() + header->mappingOffset()); + const dyldCacheFileMapping* mappingsEnd = &mappings[header->mappingCount()]; + for (const dyldCacheFileMapping* m=mappings; m < mappingsEnd; ++m) { + handler((char*)_buffer.get() + m->file_offset(), m->address(), m->size(), m->init_prot()); + } +#endif +} + +std::shared_ptr SharedCache::buffer(void) const { + return _buffer; +} + +std::string SharedCache::archName() { + return stringForArch(_arch); +} + +void SharedCache::assignSegmentAddresses() +{ + _branchPoolStarts.clear(); + uint64_t addr = sharedRegionStartExecutableAddress(_arch); + + // assign TEXT segment addresses + _textRegion.address = addr; + _textRegion.fileOffset = 0; + _textRegion.prot = VM_PROT_READ | VM_PROT_EXECUTE; +#if NEW_CACHE_FILE_FORMAT + addr += 0x4000; // header +#else + addr += 0x28000; // header +#endif + uint64_t brPoolTextSize = branchPoolTextSize(_arch); + uint64_t brPoolLinkEditSize = branchPoolLinkEditSize(_arch); + uint64_t brRearch = branchReach(_arch); + uint64_t lastPoolAddress = addr; + for (auto& dylib : _dylibs) { + for (auto& seg : _segmentMap[dylib]) { + if ( seg.base->protection != (VM_PROT_READ | VM_PROT_EXECUTE) ) + continue; + // Insert branch island pools every 128MB for arm64 + if ( (brPoolTextSize != 0) && ((addr + seg.base->size - lastPoolAddress) > brRearch) ) { + _branchPoolStarts.push_back(addr); + //verboseLog("adding branch pool at 0x%lX\n", addr); + lastPoolAddress = addr; + addr += brPoolTextSize; + } + // Keep __TEXT segments 4K or more aligned + uint64_t startAlignPad = align(addr, std::max(seg.base->p2align, (uint8_t)12)) - addr; + addr += startAlignPad; + seg.address = addr; + seg.cacheFileOffset = addr - _textRegion.address + _textRegion.fileOffset; + seg.cacheSegSize = align(seg.base->sizeOfSections, 12); + addr += align(seg.base->sizeOfSections, 12); + } + } + // align TEXT region end + uint64_t endTextAddress = align(addr, sharedRegionRegionAlignment(_arch)); + _textRegion.size = endTextAddress - _textRegion.address; + + std::unordered_map dataDirtySegPaths; + + // co-locate similar __DATA* segments + std::vector dataSegs; + std::vector dataConstSegs; + std::vector dataDirtySegs; + for (auto& dylib : _dylibs) { + for (auto& seg : _segmentMap[dylib]) { + if ( seg.base->protection == (VM_PROT_READ | VM_PROT_WRITE) ) { + if ( seg.base->name == "__DATA_CONST" ) { + dataConstSegs.push_back(&seg); + } + else if ( seg.base->name == "__DATA_DIRTY" ) { + dataDirtySegs.push_back(&seg); + dataDirtySegPaths[&seg] = dylib->installName; + } + else { + dataSegs.push_back(&seg); + } + } + } + } + + // assign __DATA* addresses + addr = sharedRegionStartWriteableAddress(_arch, endTextAddress); + _dataRegion.address = addr; + _dataRegion.fileOffset = _textRegion.fileOffset + _textRegion.size; + _dataRegion.prot = VM_PROT_READ | VM_PROT_WRITE; + + // layout all __DATA_CONST segments + for (SegmentInfo* seg : dataConstSegs) { + // Keep __DATA_CONST segments 4K or more aligned + uint64_t startAlignPad = align(addr, std::max(seg->base->p2align, (uint8_t)12)) - addr; + addr += startAlignPad; + seg->address = addr; + seg->cacheFileOffset = addr - _dataRegion.address + _dataRegion.fileOffset; + seg->cacheSegSize = seg->base->sizeOfSections; + addr += seg->base->sizeOfSections; + } + + // layout all __DATA segments + for (SegmentInfo* seg : dataSegs) { + // Keep __DATA segments 4K or more aligned + uint64_t startAlignPad = align(addr, std::max(seg->base->p2align, (uint8_t)12)) - addr; + addr += startAlignPad; + seg->address = addr; + seg->cacheFileOffset = addr - _dataRegion.address + _dataRegion.fileOffset; + seg->cacheSegSize = seg->base->sizeOfSections; + addr += seg->base->sizeOfSections; + } + + // layout all __DATA_DIRTY segments + addr = align(addr, 12); + std::sort(dataDirtySegs.begin(), dataDirtySegs.end(), [&](const SegmentInfo *a, const SegmentInfo *b) { + const std::string& pathA = dataDirtySegPaths[a]; + const std::string& pathB = dataDirtySegPaths[b]; + + const auto& orderA = _dataDirtySegsOrder.find(pathA); + const auto& orderB = _dataDirtySegsOrder.find(pathB); + bool foundA = (orderA != _dataDirtySegsOrder.end()); + bool foundB = (orderB != _dataDirtySegsOrder.end()); + + // Order all __DATA_DIRTY segments specified in the order file first, in + // the order specified in the file, followed by any other __DATA_DIRTY + // segments in lexicographic order. + if ( foundA && foundB ) + return orderA->second < orderB->second; + else if ( foundA ) + return true; + else if ( foundB ) + return false; + else + return pathA < pathB; + }); + for (SegmentInfo* seg : dataDirtySegs) { + // Pack __DATA_DIRTY segments + uint64_t startAlignPad = align(addr, seg->base->p2align) - addr; + addr += startAlignPad; + seg->address = addr; + seg->cacheFileOffset = addr - _dataRegion.address + _dataRegion.fileOffset; + seg->cacheSegSize = seg->base->sizeOfSections; + addr += seg->base->sizeOfSections; + } + + // align DATA region end + uint64_t endDataAddress = align(addr, sharedRegionRegionAlignment(_arch)); + _dataRegion.size = endDataAddress - _dataRegion.address; + + // start read-only region + addr = sharedRegionStartReadOnlyAddress(_arch, endDataAddress, endTextAddress); + _readOnlyRegion.address = addr; + _readOnlyRegion.fileOffset = _dataRegion.fileOffset + _dataRegion.size; + _readOnlyRegion.prot = VM_PROT_READ; + + // reserve space for kernel ASLR slide info at start of r/o region + _slideInfoBufferSize = align((_dataRegion.size/4096) * 130, 12); // bitmap entry + toc entry + _slideInfoFileOffset = _readOnlyRegion.fileOffset; + addr += _slideInfoBufferSize; + + // layout all read-only (but not LINKEDIT) segments + for (auto& dylib : _dylibs) { + for (auto& seg : _segmentMap[dylib]) { + if ( seg.base->protection != VM_PROT_READ ) + continue; + if ( seg.base->name == "__LINKEDIT" ) + continue; + // Keep segments 4K or more aligned + addr = align(addr, std::min(seg.base->p2align, (uint8_t)12)); + seg.address = addr; + seg.cacheFileOffset = addr - _readOnlyRegion.address + _readOnlyRegion.fileOffset;; + seg.cacheSegSize = seg.base->size; + addr += seg.base->size; + //verboseLog("read-only offset=0x%08X, for path=%s\n", seg.cacheFileOffset, ex->proxy->installName.c_str()); + } + } + // layout all LINKEDIT segments (after other read-only segments) + for (auto& dylib : _dylibs) { + for (auto& seg : _segmentMap[dylib]) { + if ( seg.base->protection != VM_PROT_READ ) + continue; + if ( seg.base->name != "__LINKEDIT" ) + continue; + // Keep LINKEDIT segments 4K aligned + addr = align(addr, 12); + seg.address = addr; + seg.cacheFileOffset = addr - _readOnlyRegion.address + _readOnlyRegion.fileOffset;; + seg.cacheSegSize = seg.base->diskSize; + addr += seg.base->size; + //verboseLog("linkedit offset=0x%08X, for path=%s\n", seg.cacheFileOffset, ex->proxy->installName.c_str()); + } + } + // add room for branch pool linkedits + _branchPoolsLinkEditStartAddr = addr; + addr += (_branchPoolStarts.size() * brPoolLinkEditSize); + + // align r/o region end + uint64_t endReadOnlyAddress = align(addr, sharedRegionRegionAlignment(_arch)); + _readOnlyRegion.size = endReadOnlyAddress - _readOnlyRegion.address; + _fileSize = _readOnlyRegion.fileOffset + _readOnlyRegion.size; + // assume LINKEDIT optimzation reduces LINKEDITs to %40 of original size + _vmSize = _readOnlyRegion.address+(_readOnlyRegion.size * 2/5) - _textRegion.address; +} + +uint64_t SharedCache::pathHash(const char* path) +{ + uint64_t sum = 0; + for (const char* s=path; *s != '\0'; ++s) + sum += sum*4 + *s; + return sum; +} + + + +void SharedCache::findDylibAndSegment(const void* contentPtr, std::string& dylibName, std::string& segName) +{ + uint64_t fileOffset = (uint8_t*)contentPtr - (uint8_t*)_buffer.get(); + for (const auto& entry : _segmentMap ) { + const MachOProxy* dylib = entry.first; + for (const SegmentInfo& segInfo : entry.second) { + //fprintf(stderr, " cacheFileOffset=0x%08llX, end=0x%08llX\n", segInfo.cacheFileOffset, segInfo.cacheFileOffset+segInfo.base->size); + if ( (segInfo.cacheFileOffset <= fileOffset) && (fileOffset < segInfo.cacheFileOffset+segInfo.base->size) ) { + dylibName = dylib->installName; + segName = segInfo.base->name; + return; + } + } + } + dylibName = "???"; + segName = "???"; +} + + +template +bool SharedCache::makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t offset, const dyldCacheSlideInfo2* info) +{ + typedef typename P::uint_t pint_t; + + const pint_t deltaMask = (pint_t)(info->delta_mask()); + const pint_t valueMask = ~deltaMask; + const pint_t valueAdd = (pint_t)(info->value_add()); + const unsigned deltaShift = __builtin_ctzll(deltaMask) - 2; + const uint32_t maxDelta = (uint32_t)(deltaMask >> deltaShift); + + pint_t* lastLoc = (pint_t*)&pageContent[lastLocationOffset+0]; + pint_t lastValue = (pint_t)P::getP(*lastLoc); + if ( (lastValue - valueAdd) & deltaMask ) { + std::string dylibName; + std::string segName; + findDylibAndSegment((void*)pageContent, dylibName, segName); + terminate("rebase pointer does not point within cache. lastOffset=0x%04X, seg=%s, dylib=%s\n", + lastLocationOffset, segName.c_str(), dylibName.c_str()); + } + if ( offset <= (lastLocationOffset+maxDelta) ) { + // previous location in range, make link from it + // encode this location into last value + pint_t delta = offset - lastLocationOffset; + pint_t newLastValue = ((lastValue - valueAdd) & valueMask) | (delta << deltaShift); + //warning(" add chain: delta = %d, lastOffset=0x%03X, offset=0x%03X, org value=0x%08lX, new value=0x%08lX", + // offset - lastLocationOffset, lastLocationOffset, offset, (long)lastValue, (long)newLastValue); + P::setP(*lastLoc, newLastValue); + return true; + } + //warning(" too big delta = %d, lastOffset=0x%03X, offset=0x%03X", offset - lastLocationOffset, lastLocationOffset, offset); + + // distance between rebase locations is too far + // see if we can make a chain from non-rebase locations + uint16_t nonRebaseLocationOffsets[1024]; + unsigned nrIndex = 0; + for (uint16_t i = lastLocationOffset; i < offset-maxDelta; ) { + nonRebaseLocationOffsets[nrIndex] = 0; + for (int j=maxDelta; j > 0; j -= 4) { + pint_t value = (pint_t)P::getP(*(pint_t*)&pageContent[i+j]); + if ( value == 0 ) { + // Steal values of 0 to be used in the rebase chain + nonRebaseLocationOffsets[nrIndex] = i+j; + break; + } + } + if ( nonRebaseLocationOffsets[nrIndex] == 0 ) { + lastValue = (pint_t)P::getP(*lastLoc); + pint_t newValue = ((lastValue - valueAdd) & valueMask); + //warning(" no way to make non-rebase delta chain, terminate off=0x%03X, old value=0x%08lX, new value=0x%08lX", lastLocationOffset, (long)value, (long)newValue); + P::setP(*lastLoc, newValue); + return false; + } + i = nonRebaseLocationOffsets[nrIndex]; + ++nrIndex; + } + + // we can make chain. go back and add each non-rebase location to chain + uint16_t prevOffset = lastLocationOffset; + pint_t* prevLoc = (pint_t*)&pageContent[prevOffset]; + for (int n=0; n < nrIndex; ++n) { + uint16_t nOffset = nonRebaseLocationOffsets[n]; + assert(nOffset != 0); + pint_t* nLoc = (pint_t*)&pageContent[nOffset]; + uint32_t delta2 = nOffset - prevOffset; + pint_t value = (pint_t)P::getP(*prevLoc); + pint_t newValue; + if ( value == 0 ) + newValue = (delta2 << deltaShift); + else + newValue = ((value - valueAdd) & valueMask) | (delta2 << deltaShift); + //warning(" non-rebase delta = %d, to off=0x%03X, old value=0x%08lX, new value=0x%08lX", delta2, nOffset, (long)value, (long)newValue); + P::setP(*prevLoc, newValue); + prevOffset = nOffset; + prevLoc = nLoc; + } + uint32_t delta3 = offset - prevOffset; + pint_t value = (pint_t)P::getP(*prevLoc); + pint_t newValue; + if ( value == 0 ) + newValue = (delta3 << deltaShift); + else + newValue = ((value - valueAdd) & valueMask) | (delta3 << deltaShift); + //warning(" non-rebase delta = %d, to off=0x%03X, old value=0x%08lX, new value=0x%08lX", delta3, offset, (long)value, (long)newValue); + P::setP(*prevLoc, newValue); + + return true; +} + + +template +void SharedCache::addPageStarts(uint8_t* pageContent, const bool bitmap[], const dyldCacheSlideInfo2* info, + std::vector& pageStarts, std::vector& pageExtras) +{ + typedef typename P::uint_t pint_t; + + const pint_t deltaMask = (pint_t)(info->delta_mask()); + const pint_t valueMask = ~deltaMask; + const uint32_t pageSize = info->page_size(); + const pint_t valueAdd = (pint_t)(info->value_add()); + + uint16_t startValue = DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE; + uint16_t lastLocationOffset = 0xFFFF; + for(int i=0; i < pageSize/4; ++i) { + unsigned offset = i*4; + if ( bitmap[i] ) { + if ( startValue == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE ) { + // found first rebase location in page + startValue = i; + } + else if ( !makeRebaseChain

(pageContent, lastLocationOffset, offset, info) ) { + // can't record all rebasings in one chain + if ( (startValue & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) == 0 ) { + // switch page_start to "extras" which is a list of chain starts + unsigned indexInExtras = (unsigned)pageExtras.size(); + if ( indexInExtras > 0x3FFF ) + terminate("rebase overflow in page extras"); + pageExtras.push_back(startValue); + startValue = indexInExtras | DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA; + } + pageExtras.push_back(i); + } + lastLocationOffset = offset; + } + } + if ( lastLocationOffset != 0xFFFF ) { + // mark end of chain + pint_t* lastLoc = (pint_t*)&pageContent[lastLocationOffset]; + pint_t lastValue = (pint_t)P::getP(*lastLoc); + pint_t newValue = ((lastValue - valueAdd) & valueMask); + P::setP(*lastLoc, newValue); + } + if ( startValue & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA ) { + // add end bit to extras + pageExtras.back() |= DYLD_CACHE_SLIDE_PAGE_ATTR_END; + } + pageStarts.push_back(startValue); +} + +template +void SharedCache::writeSlideInfoV2(uint64_t deltaMask, uint64_t valueAdd) +{ + // i386 cache does not support sliding because stubs use absolute addressing (text relocs) + if (_arch.arch == CPU_TYPE_I386 ) { + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + header->set_slideInfoSize(0); + return; + } + + typedef typename P::E E; + const uint32_t pageSize = 4096; + + // build one 1024/4096 bool bitmap per page (4KB/16KB) of DATA + uint8_t* const dataStart = (uint8_t*)_buffer.get() + _dataRegion.fileOffset; + uint8_t* const dataEnd = dataStart + _dataRegion.size; + unsigned pageCount = (unsigned)(_dataRegion.size+pageSize-1)/pageSize; + const long bitmapSize = pageCount*(pageSize/4)*sizeof(bool); + bool* bitmap = (bool*)calloc(bitmapSize, 1); + for (void* p : _pointersForASLR) { + if ( (p < dataStart) || ( p > dataEnd) ) + terminate("DATA pointer for sliding, out of range\n"); + long byteOffset = (long)((uint8_t*)p - dataStart); + if ( (byteOffset % 4) != 0 ) + terminate("pointer not 4-byte aligned in DATA offset 0x%08lX\n", byteOffset); + long boolIndex = byteOffset / 4; + // work around by ignoring pointers to be slid that are NULL on disk + if ( *(typename P::uint_t*)p == 0 ) { + std::string dylibName; + std::string segName; + findDylibAndSegment(p, dylibName, segName); + warning("NULL pointer asked to be slid in %s of %s", segName.c_str(), dylibName.c_str()); + continue; + } + bitmap[boolIndex] = true; + } + + // fill in fixed info + dyldCacheSlideInfo2* info = (dyldCacheSlideInfo2*)((uint8_t*)_buffer.get() + _slideInfoFileOffset); + info->set_version(2); + info->set_page_size(pageSize); + info->set_delta_mask(deltaMask); + info->set_value_add(valueAdd); + + // set page starts and extras for each page + std::vector pageStarts; + std::vector pageExtras; + pageStarts.reserve(pageCount); + uint8_t* pageContent = dataStart;; + const bool* bitmapForPage = bitmap; + for (unsigned i=0; i < pageCount; ++i) { + //warning("page[%d]", i); + addPageStarts

(pageContent, bitmapForPage, info, pageStarts, pageExtras); + pageContent += pageSize; + bitmapForPage += (sizeof(bool)*(pageSize/4)); + } + free((void*)bitmap); + + // fill in computed info + info->set_page_starts_offset(sizeof(dyldCacheSlideInfo2)); + info->set_page_starts_count((unsigned)pageStarts.size()); + info->set_page_extras_offset((unsigned)(sizeof(dyldCacheSlideInfo2)+pageStarts.size()*sizeof(uint16_t))); + info->set_page_extras_count((unsigned)pageExtras.size()); + for (unsigned i=0; i < pageStarts.size(); ++i) + info->set_page_starts(i, pageStarts[i]); + for (unsigned i=0; i < pageExtras.size(); ++i) + info->set_page_extras(i, pageExtras[i]); + //warning("pageCount=%u, page_starts_count=%lu, page_extras_count=%lu", pageCount, pageStarts.size(), pageExtras.size()); + _slideInfoBufferSize = align(info->page_extras_offset() + pageExtras.size()*sizeof(uint16_t), 12); + +#if NEW_CACHE_FILE_FORMAT + +#else + unsigned long slideInfoPageSize = align(_slideInfoBufferSize, sharedRegionRegionAlignment(_arch)); + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + header->set_slideInfoSize(slideInfoPageSize); +#endif +} + +void SharedCache::writeSlideInfoV2(void) +{ + switch (_arch.arch) { + case CPU_TYPE_ARM: + // linked list based slide info needs high 3 bits of pointer, won't work with > 512MB of pointable content + if ( (_textRegion.size + _dataRegion.size) > 512*1024*1024 ) { + warning("cache TEXT+DATA > 512MB, using larger slide info format"); + writeSlideInfo(); + } + else { + writeSlideInfoV2>(0xE0000000, ARM_SHARED_REGION_START); + } + break; + case CPU_TYPE_I386: + writeSlideInfoV2>(0xE0000000, 0x90000000); + break; + case CPU_TYPE_X86_64: + writeSlideInfoV2>(0xFFFF000000000000, 0); + break; + case CPU_TYPE_ARM64: + writeSlideInfoV2>(0x00FFFF0000000000, 0); + break; + default: + warning("unsupported arch 0x%08X", _arch.arch); + return; + } +} + + + +template +void SharedCache::writeSlideInfo(void) +{ + // i386 cache does not support sliding because stubs use absolute addressing (text relocs) + if (_arch.arch == CPU_TYPE_I386 ) { + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + header->set_slideInfoSize(0); + return; + } + + // build one 128-byte bitmap per page (4096) of DATA + uint8_t* const dataStart = (uint8_t*)_buffer.get() + _dataRegion.fileOffset; + uint8_t* const dataEnd = dataStart + _dataRegion.size; + const long bitmapSize = (dataEnd - dataStart)/(4*8); + uint8_t* bitmap = (uint8_t*)calloc(bitmapSize, 1); + for (void* p : _pointersForASLR) { + if ( (p < dataStart) || ( p > dataEnd) ) + terminate("DATA pointer for sliding, out of range\n"); + long offset = (long)((uint8_t*)p - dataStart); + if ( (offset % 4) != 0 ) + terminate("pointer not 4-byte aligned in DATA offset 0x%08lX\n", offset); + long byteIndex = offset / (4*8); + long bitInByte = (offset % 32) >> 2; + bitmap[byteIndex] |= (1 << bitInByte); + } + + // allocate worst case size block of all slide info + const unsigned entry_size = 4096/(8*4); // 8 bits per byte, possible pointer every 4 bytes. + const unsigned toc_count = (unsigned)bitmapSize/entry_size; + dyldCacheSlideInfo* slideInfo = (dyldCacheSlideInfo*)((uint8_t*)_buffer.get() + _slideInfoFileOffset); + slideInfo->set_version(1); + slideInfo->set_toc_offset(sizeof(dyldCacheSlideInfo)); + slideInfo->set_toc_count(toc_count); + slideInfo->set_entries_offset((slideInfo->toc_offset()+2*toc_count+127)&(-128)); + slideInfo->set_entries_count(0); + slideInfo->set_entries_size(entry_size); + // append each unique entry + const dyldCacheSlideInfoEntry* bitmapAsEntries = (dyldCacheSlideInfoEntry*)bitmap; + dyldCacheSlideInfoEntry* const entriesInSlidInfo = (dyldCacheSlideInfoEntry*)((char*)slideInfo+slideInfo->entries_offset()); + int entry_count = 0; + for (int i=0; i < toc_count; ++i) { + const dyldCacheSlideInfoEntry* thisEntry = &bitmapAsEntries[i]; + // see if it is same as one already added + bool found = false; + for (int j=0; j < entry_count; ++j) { + if ( memcmp(thisEntry, &entriesInSlidInfo[j], entry_size) == 0 ) { + slideInfo->set_toc(i, j); + found = true; + break; + } + } + if ( !found ) { + // append to end + memcpy(&entriesInSlidInfo[entry_count], thisEntry, entry_size); + slideInfo->set_toc(i, entry_count++); + } + } + slideInfo->set_entries_count(entry_count); + ::free((void*)bitmap); + +#if NEW_CACHE_FILE_FORMAT + +#else + unsigned long slideInfoPageSize = align(slideInfo->entries_offset() + entry_count*entry_size, sharedRegionRegionAlignment(_arch)); + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get(); + header->set_slideInfoSize(slideInfoPageSize); +#endif +} + +template +void SharedCache::writeCacheHeader(void) +{ +#if NEW_CACHE_FILE_FORMAT + macho_header

* mh = (macho_header

*)cacheBuffer; + mh->set_magic((sizeof(typename P::uint_t) == 8) ? MH_MAGIC_64 : MH_MAGIC); + mh->set_cputype(arch.arch); + mh->set_cpusubtype(arch.subtype); + mh->set_filetype(MH_DYLIB); + mh->set_ncmds(0); + mh->set_sizeofcmds(0); + mh->set_flags(0); + + uint8_t* cmd = (uint8_t*)cacheBuffer + sizeof(macho_header

); + + // write LC_SEGMENT for each region + macho_segment_command

* rxSegCmd = (macho_segment_command

*)cmd; + rxSegCmd->set_cmd(macho_segment_command

::CMD); + rxSegCmd->set_cmdsize(sizeof(macho_segment_command

)); + rxSegCmd->set_segname("R.X"); + rxSegCmd->set_vmaddr(_textRegion.address); + rxSegCmd->set_vmsize(_textRegion.size); + rxSegCmd->set_fileoff(_textRegion.fileOffset); + rxSegCmd->set_filesize(_textRegion.size); + rxSegCmd->set_maxprot(VM_PROT_READ | VM_PROT_EXECUTE); + rxSegCmd->set_initprot(VM_PROT_READ | VM_PROT_EXECUTE); + rxSegCmd->set_nsects(0); + rxSegCmd->set_flags(0); + mh->set_ncmds(mh->ncmds()+1); + mh->set_sizeofcmds(mh->sizeofcmds()+rxSegCmd->cmdsize()); + cmd += rxSegCmd->cmdsize(); + + macho_segment_command

* rwSegCmd = (macho_segment_command

*)cmd; + rwSegCmd->set_cmd(macho_segment_command

::CMD); + rwSegCmd->set_cmdsize(sizeof(macho_segment_command

)); + rwSegCmd->set_segname("RW."); + rwSegCmd->set_vmaddr(_dataRegion.address); + rwSegCmd->set_vmsize(_dataRegion.size); + rwSegCmd->set_fileoff(_dataRegion.fileOffset); + rwSegCmd->set_filesize(_dataRegion.size); + rwSegCmd->set_maxprot(VM_PROT_READ | VM_PROT_WRITE); + rwSegCmd->set_initprot(VM_PROT_READ | VM_PROT_WRITE); + rwSegCmd->set_nsects(0); + rwSegCmd->set_flags(0); + mh->set_ncmds(mh->ncmds()+1); + mh->set_sizeofcmds(mh->sizeofcmds()+rwSegCmd->cmdsize()); + cmd += rwSegCmd->cmdsize(); + + macho_segment_command

* roSegCmd = (macho_segment_command

*)cmd; + roSegCmd->set_cmd(macho_segment_command

::CMD); + roSegCmd->set_cmdsize(sizeof(macho_segment_command

)); + roSegCmd->set_segname("R.."); + roSegCmd->set_vmaddr(_readOnlyRegion.address); + roSegCmd->set_vmsize(_readOnlyRegion.size); + roSegCmd->set_fileoff(_readOnlyRegion.fileOffset); + roSegCmd->set_filesize(_readOnlyRegion.size); + roSegCmd->set_maxprot(VM_PROT_READ); + roSegCmd->set_initprot(VM_PROT_READ); + roSegCmd->set_nsects(0); + roSegCmd->set_flags(0); + mh->set_ncmds(mh->ncmds()+1); + mh->set_sizeofcmds(mh->sizeofcmds()+roSegCmd->cmdsize()); + cmd += roSegCmd->cmdsize(); + + // Add LC_ID_DYLIB + macho_dylib_command

* dylibIdCmd = (macho_dylib_command

*)cmd; + const char* installName = "/System/Library/Frameworks/OS.framework/OS"; // FIXME + uint32_t sz = (uint32_t)align(sizeof(macho_dylib_command

) + strlen(installName) + 1, 3); + dylibIdCmd->set_cmd(LC_ID_DYLIB); + dylibIdCmd->set_cmdsize(sz); + dylibIdCmd->set_name_offset(); + dylibIdCmd->set_timestamp(1); + dylibIdCmd->set_current_version(0x10000); + dylibIdCmd->set_compatibility_version(0x10000); + strcpy((char*)&cmd[sizeof(macho_dylib_command

)], installName); + mh->set_ncmds(mh->ncmds()+1); + mh->set_sizeofcmds(mh->sizeofcmds()+sz); + cmd += dylibIdCmd->cmdsize(); + + // Add LC_UUID + macho_uuid_command

* uuidCmd = (macho_uuid_command

*)cmd; + uint8_t zeros[16]; + bzero(zeros, 16); + uuidCmd->set_cmd(LC_UUID); + uuidCmd->set_cmdsize(sizeof(macho_uuid_command

)); + uuidCmd->set_uuid(zeros); + cmd += uuidCmd->cmdsize(); + + // Build dylib trie + std::vector dylibTrieEntires; + int pathLengths = 0; + for (Extra* ex : _sortedDylibs) { + mach_o::trie::Entry entry; + entry.name = ex->proxy->installName.c_str(); + entry.address = ex->segments[0].address; + entry.flags = 0; + entry.other = 0; + entry.importName = NULL; + dylibTrieEntires.push_back(entry); + pathLengths += (strlen(entry.name) + 1); + for (const std::string& alias : ex->proxy->installNameAliases) { + mach_o::trie::Entry aliasEntry; + aliasEntry.name = alias.c_str(); + aliasEntry.address = ex->segments[0].address; + aliasEntry.flags = 0; + aliasEntry.other = 0; + aliasEntry.importName = NULL; + dylibTrieEntires.push_back(aliasEntry); + pathLengths += (strlen(aliasEntry.name) + 1); + } + } + std::vector dylibTrieBytes; + dylibTrieBytes.reserve(4096); + mach_o::trie::makeTrie(dylibTrieEntires, dylibTrieBytes); + fprintf(stderr, "dylib trie size = %lu bytes, for %lu entries, pathLength=%d\n", dylibTrieBytes.size(), dylibTrieEntires.size(), pathLengths); + + + + // Build SPI trie (optimized cache only) + + + + // add LC_CODE_SIGNATURE + macho_linkedit_data_command

* codeSigCmd = (macho_linkedit_data_command

*)cmd; + codeSigCmd->set_cmd(LC_CODE_SIGNATURE); + codeSigCmd->set_cmdsize(sizeof(macho_linkedit_data_command

)); + codeSigCmd->set_dataoff((uint32_t)(_readOnlyRegion.fileOffset + _readOnlyRegion.size)); + codeSigCmd->set_datasize(0); // FIXME + mh->set_ncmds(mh->ncmds()+1); + mh->set_sizeofcmds(mh->sizeofcmds()+codeSigCmd->cmdsize()); + cmd += codeSigCmd->cmdsize(); + +#else + typedef typename P::E E; + // fill in header + uint8_t* buffer = (uint8_t*)_buffer.get(); + dyldCacheHeader* header = (dyldCacheHeader*)_buffer.get();; + // "dyld_v1" + spaces + archName(), with enough spaces to pad to 15 bytes + std::string magic = "dyld_v1"; + magic.append(15 - magic.length() - archName().length(), ' '); + magic.append(archName()); + assert(magic.length() == 15); + header->set_magic(magic.c_str()); + header->set_mappingOffset(sizeof(dyldCacheHeader)); + header->set_mappingCount(3); + header->set_imagesOffset((uint32_t)(header->mappingOffset() + 3*sizeof(dyldCacheFileMapping) + sizeof(uint64_t)*_branchPoolStarts.size())); + header->set_imagesCount((uint32_t)_dylibs.size() + _aliasCount); + header->set_dyldBaseAddress(0); + header->set_codeSignatureOffset(_fileSize); + header->set_codeSignatureSize(0); + header->set_slideInfoOffset(_slideInfoFileOffset); + header->set_slideInfoSize(_slideInfoBufferSize); + header->set_localSymbolsOffset(0); + header->set_localSymbolsSize(0); + header->set_cacheType(kDyldSharedCacheTypeDevelopment); + header->set_accelerateInfoAddr(0); + header->set_accelerateInfoSize(0); + static const uint8_t zero_uuid[16] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; + header->set_uuid(zero_uuid); // overwritten later by recomputeCacheUUID() + header->set_branchPoolsOffset(header->mappingOffset() + 3*sizeof(dyldCacheFileMapping)); + header->set_branchPoolsCount((uint32_t)_branchPoolStarts.size()); + header->set_imagesTextOffset(0); + header->set_imagesTextCount(_dylibs.size()); + + // fill in mappings + dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)&buffer[header->mappingOffset()]; + mappings[0].set_address(_textRegion.address); + mappings[0].set_size(_textRegion.size); + mappings[0].set_file_offset(_textRegion.fileOffset); + mappings[0].set_max_prot(_textRegion.prot); + mappings[0].set_init_prot(_textRegion.prot); + + mappings[1].set_address(_dataRegion.address); + mappings[1].set_size(_dataRegion.size); + mappings[1].set_file_offset(_dataRegion.fileOffset); + mappings[1].set_max_prot(_dataRegion.prot); + mappings[1].set_init_prot(_dataRegion.prot); + + mappings[2].set_address(_readOnlyRegion.address); + mappings[2].set_size(_readOnlyRegion.size); + mappings[2].set_file_offset(_readOnlyRegion.fileOffset); + mappings[2].set_max_prot(_readOnlyRegion.prot); + mappings[2].set_init_prot(_readOnlyRegion.prot); + + // fill in branch pool addresses + uint64_t* p = (uint64_t*)&buffer[header->branchPoolsOffset()]; + for (uint64_t pool : _branchPoolStarts) { + E::set64(*p, pool); + ++p; + } + + // fill in image table + dyldCacheImageInfo* images = (dyldCacheImageInfo*)&buffer[header->imagesOffset()]; + for (auto& dylib : _dylibs) { + auto textSeg = _segmentMap[dylib][0]; + images->set_address(textSeg.address); + if (_manifest.platform == "osx") { + images->set_modTime(dylib->lastModTime); + images->set_inode(dylib->inode); + } else { + images->set_modTime(0); + images->set_inode(pathHash(dylib->installName.c_str())); + } + images->set_pathFileOffset((uint32_t)textSeg.cacheFileOffset + dylib->installNameOffsetInTEXT); + ++images; + } + // append aliases image records and strings + uint32_t offset = header->imagesOffset() + header->imagesCount()*sizeof(dyld_cache_image_info); + for (auto &dylib : _dylibs) { + if (!dylib->installNameAliases.empty()) { + for (const std::string& alias : dylib->installNameAliases) { + images->set_address(_segmentMap[dylib][0].address); + if (_manifest.platform == "osx") { + images->set_modTime(dylib->lastModTime); + images->set_inode(dylib->inode); + } else { + images->set_modTime(0); + images->set_inode(pathHash(alias.c_str())); + } + images->set_pathFileOffset(offset); + ::strcpy((char*)&buffer[offset], alias.c_str()); + offset += alias.size() + 1; + ++images; + } + } + } + + // calculate start of text image array and trailing string pool + offset = (offset + 15) & (-16); + header->set_imagesTextOffset(offset); + dyldCacheImageTextInfo* textImages = (dyldCacheImageTextInfo*)&buffer[header->imagesTextOffset()]; + uint32_t stringOffset = offset + (uint32_t)(sizeof(dyldCacheImageTextInfo) * _dylibs.size()); + + // write text image array and image names pool at same time + for (auto& dylib : _dylibs) { + textImages->set_uuid(dylib->uuid); + textImages->set_loadAddress(_segmentMap[dylib][0].address); + textImages->set_textSegmentSize((uint32_t)dylib->segments[0].size); + textImages->set_pathOffset(stringOffset); + ::strcpy((char*)&buffer[stringOffset], dylib->installName.c_str()); + stringOffset += dylib->installName.size()+1; + ++textImages; + } + + assert(stringOffset < 0x28000); +#endif +} + +void SharedCache::rebase(MachOProxy* dylib) +{ + std::vector segNewStartAddresses; + std::vector segCacheFileOffsets; + std::vector segCacheFileSizes; + for (auto& seg : _segmentMap[dylib]) { + segNewStartAddresses.push_back(seg.address); + segCacheFileOffsets.push_back(seg.cacheFileOffset); + segCacheFileSizes.push_back(seg.cacheSegSize); + } + adjustImageForNewSegmentLocations(segNewStartAddresses, segCacheFileOffsets, segCacheFileSizes, _pointersForASLR); +} + + +void SharedCache::rebaseAll(void) +{ + for (auto& dylib : _dylibs) + rebase(dylib); +} + +void SharedCache::bindAll(void) +{ + std::unordered_map dylibPathToMachHeader; + for (auto& dylib : _dylibs) { + void* mh = (uint8_t*)_buffer.get() + _segmentMap[dylib][0].cacheFileOffset; + dylibPathToMachHeader[dylib->installName] = mh; + for (const std::string& path : dylib->installNameAliases) { + if (path != dylib->installName) { + dylibPathToMachHeader[path] = mh; + } + } + } + + bindAllImagesInCache(dylibPathToMachHeader, _pointersForASLR); +} + +void SharedCache::writeCacheSegments(void) +{ + uint8_t* cacheBytes = (uint8_t*)_buffer.get(); + for (auto& dylib : _dylibs) { + struct stat stat_buf; + const uint8_t* srcDylib; + bool rootless; + + std::tie(srcDylib, stat_buf, rootless) = fileCache.cacheLoad(dylib->path); + for (auto& seg : _segmentMap[dylib]) { + uint32_t segFileOffset = dylib->fatFileOffset + seg.base->fileOffset; + uint64_t copySize = std::min(seg.cacheSegSize, (uint64_t)seg.base->diskSize); + verboseLog("copy segment %12s (0x%08llX bytes) to %p (logical addr 0x%llX) for %s", seg.base->name.c_str(), copySize, &cacheBytes[seg.cacheFileOffset], seg.address, dylib->installName.c_str()); + ::memcpy(&cacheBytes[seg.cacheFileOffset], &srcDylib[segFileOffset], copySize); + } + } +} + + + +void SharedCache::appendCodeSignature(const std::string& suffix) +{ + // select which codesigning hash + uint8_t dscHashType = CS_HASHTYPE_SHA1; + uint8_t dscHashSize = CS_HASH_SIZE_SHA1; + uint32_t dscDigestFormat = kCCDigestSHA1; + if ( _manifest.platform == "osx" ) { + dscHashType = CS_HASHTYPE_SHA256; + dscHashSize = CS_HASH_SIZE_SHA256; + dscDigestFormat = kCCDigestSHA256; + } + + std::string cacheIdentifier = "com.apple.dyld.cache." + archName() + "." + suffix; + // get pointers into shared cache buffer + size_t inBbufferSize = _fileSize; + const uint8_t* inBuffer = (uint8_t*)_buffer.get(); + uint8_t* csBuffer = (uint8_t*)_buffer.get()+inBbufferSize; + + // layout code signature contents + size_t idSize = cacheIdentifier.size()+1; // +1 for terminating 0 + uint32_t slotCount = (uint32_t)((inBbufferSize + CS_PAGE_SIZE - 1) / CS_PAGE_SIZE); + uint32_t xSlotCount = CSSLOT_REQUIREMENTS; + size_t scatOffset = sizeof(CS_CodeDirectory); + size_t scatSize = 4*sizeof(CS_Scatter); // only 3 used?? + size_t idOffset = scatOffset+scatSize; + size_t hashOffset = idOffset+idSize + dscHashSize*xSlotCount; + size_t cdSize = hashOffset + (slotCount * dscHashSize); + size_t reqsSize = 12; + size_t cmsSize = sizeof(CS_Blob); + size_t cdOffset = sizeof(CS_SuperBlob) + 3*sizeof(CS_BlobIndex); + size_t reqsOffset = cdOffset + cdSize; + size_t cmsOffset = reqsOffset + reqsSize; + size_t sbSize = cmsOffset + cmsSize; + size_t sigSize = align(sbSize, 14); // keep whole cache 16KB aligned + + // create overall code signature which is a superblob + CS_SuperBlob* sb = reinterpret_cast(csBuffer); + sb->magic = htonl(CSMAGIC_EMBEDDED_SIGNATURE); + sb->length = htonl(sbSize); + sb->count = htonl(3); + sb->index[0].type = htonl(CSSLOT_CODEDIRECTORY); + sb->index[0].offset = htonl(cdOffset); + sb->index[1].type = htonl(CSSLOT_REQUIREMENTS); + sb->index[1].offset = htonl(reqsOffset); + sb->index[2].type = htonl(CSSLOT_CMS_SIGNATURE); + sb->index[2].offset = htonl(cmsOffset); + + // initialize fixed fields of Code Directory + CS_CodeDirectory* cd = (CS_CodeDirectory*)(((char*)sb)+cdOffset); + cd->magic = htonl(CSMAGIC_CODEDIRECTORY); + cd->length = htonl(cdSize); + cd->version = htonl(0x20100); + cd->flags = htonl(kSecCodeSignatureAdhoc); + cd->hashOffset = htonl(hashOffset); + cd->identOffset = htonl(idOffset); + cd->nSpecialSlots = htonl(xSlotCount); + cd->nCodeSlots = htonl(slotCount); + cd->codeLimit = htonl(inBbufferSize); + cd->hashSize = dscHashSize; + cd->hashType = dscHashType; + cd->platform = 0; // not platform binary + cd->pageSize = __builtin_ctz(CS_PAGE_SIZE); // log2(CS_PAGE_SIZE); + cd->spare2 = 0; // unused (must be zero) + cd->scatterOffset = htonl(scatOffset); + + // initialize dynamic fields of Code Directory + strcpy((char*)cd + idOffset, cacheIdentifier.c_str()); + + // add scatter info + CS_Scatter* scatter = reinterpret_cast((char*)cd+scatOffset); + scatter[0].count = htonl(_textRegion.size/CS_PAGE_SIZE); + scatter[0].base = htonl(_textRegion.fileOffset/CS_PAGE_SIZE); + scatter[0].targetOffset = htonll(_textRegion.address); + scatter[0].spare = 0; + scatter[1].count = htonl(_dataRegion.size/CS_PAGE_SIZE); + scatter[1].base = htonl(_dataRegion.fileOffset/CS_PAGE_SIZE); + scatter[1].targetOffset = htonll(_dataRegion.address); + scatter[1].spare = 0; + scatter[2].count = htonl(_readOnlyRegion.size/CS_PAGE_SIZE); + scatter[2].base = htonl(_readOnlyRegion.fileOffset/CS_PAGE_SIZE); + scatter[2].targetOffset = htonll(_readOnlyRegion.address); + scatter[2].spare = 0; + + // fill in empty requirements + CS_RequirementsBlob* reqs = (CS_RequirementsBlob*)(((char*)sb)+reqsOffset); + reqs->magic = htonl(CSMAGIC_REQUIREMENTS); + reqs->length = htonl(sizeof(CS_RequirementsBlob)); + reqs->data = 0; + + // fill in empty CMS blob for ad-hoc signing + CS_Blob* cms = (CS_Blob*)(((char*)sb)+cmsOffset); + cms->magic = htonl(CSMAGIC_BLOBWRAPPER); + cms->length = htonl(sizeof(CS_Blob)); + + // add special slot hashes + uint8_t* hashSlot = (uint8_t*)cd + hashOffset; + uint8_t* reqsHashSlot = &hashSlot[-CSSLOT_REQUIREMENTS*dscHashSize]; + CCDigest(dscDigestFormat, (uint8_t*)reqs, sizeof(CS_RequirementsBlob), reqsHashSlot); + + // alter header of cache to record size and location of code signature + // do this *before* hashing each page + dyldCacheHeader* header = (dyldCacheHeader*)inBuffer; + header->set_codeSignatureOffset(inBbufferSize); + header->set_codeSignatureSize(sigSize); + + // compute hashes + const uint8_t* code = inBuffer; + for (uint32_t i=0; i < slotCount; ++i) { + CCDigest(dscDigestFormat, code, CS_PAGE_SIZE, hashSlot); + hashSlot += dscHashSize; + code += CS_PAGE_SIZE; + } + + // hash of entire code directory (cdHash) uses same has hash as each page + uint8_t fullCdHash[dscHashSize]; + CCDigest(dscDigestFormat, (const uint8_t*)cd, cdSize, fullCdHash); + // Note: cdHash is defined as first 20 bytes of hash + memcpy(_cdHash, fullCdHash, 20); + + // increase file size to include newly append code signature + _fileSize += sigSize; +} + +std::string SharedCache::cdHashString() +{ + char buff[48]; + for (int i = 0; i < sizeof(_cdHash); ++i) + sprintf(&buff[2*i], "%2.2x", _cdHash[i]); + return buff; +} + + +#pragma mark - +#pragma mark Template dispatchers + +#define TEMPLATE_DISPATCHER_BODY(method,...) \ + switch( _arch.arch ) { \ + case CPU_TYPE_ARM: \ + case CPU_TYPE_I386: \ + method>(__VA_ARGS__); \ + break; \ + case CPU_TYPE_X86_64: \ + case CPU_TYPE_ARM64: \ + method>(__VA_ARGS__); \ + break; \ + default: \ + terminate("unsupported arch 0x%08X", _arch.arch); \ + } + +void SharedCache::writeCacheHeader() { + TEMPLATE_DISPATCHER_BODY(writeCacheHeader) +}; + +void SharedCache::buildForDevelopment(const std::string& cachePath) { + TEMPLATE_DISPATCHER_BODY(buildForDevelopment, cachePath) +}; + +void SharedCache::buildForProduction(const std::string& cachePath) { + TEMPLATE_DISPATCHER_BODY(buildForProduction, cachePath) +}; + +void SharedCache::setLinkeditsMappingEndFileOffset(uint64_t newFileSize) { + TEMPLATE_DISPATCHER_BODY(setLinkeditsMappingEndFileOffset, newFileSize) +} + +void SharedCache::setUnmappedLocalsRange(uint64_t localSymbolsOffset, uint32_t unmappedSize) { + TEMPLATE_DISPATCHER_BODY(setUnmappedLocalsRange, localSymbolsOffset, unmappedSize) +} + +void SharedCache::setAcceleratorInfoRange(uint64_t accelInfoAddr, uint32_t accelInfoSize) { + TEMPLATE_DISPATCHER_BODY(setAcceleratorInfoRange, accelInfoAddr, accelInfoSize) +} + +void SharedCache::recomputeCacheUUID(void) { + TEMPLATE_DISPATCHER_BODY(recomputeCacheUUID) +} + +void SharedCache::forEachImage(DylibHandler handler) { + TEMPLATE_DISPATCHER_BODY(forEachImage, handler) +} + +void SharedCache::forEachRegion(RegionHandler handler) { + TEMPLATE_DISPATCHER_BODY(forEachRegion, handler) +} diff --git a/interlinked-dylibs/Trie.hpp b/interlinked-dylibs/Trie.hpp new file mode 100644 index 0000000..d77a21a --- /dev/null +++ b/interlinked-dylibs/Trie.hpp @@ -0,0 +1,498 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2008-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + //This is the exposed iterface for the Trie template + //TODO: add erase methods + //TODO: re-enable iterators + + template + struct Trie { + struct Entry + { + std::string name; + V info; + + Entry(void) {} + Entry(const std::string& N, V I) : name(N), info(I) {} + }; + + struct const_iterator : std::iterator; + + const_iterator begin() const; + const_iterator end() const; + + Trie(void); + Trie(const uint8_t* start, const uint8_t* end); + Trie(const std::vector& entries); + + void emit(std::vector& output); + */ + + +#ifndef __TRIE__ +#define __TRIE__ +#define TRIE_DEBUG (0) + +#include +#include +#include +#include +#include +#include + +#include "MachOFileAbstraction.hpp" + +#if __cplusplus <= 201103L +namespace std { + template + std::unique_ptr make_unique(Args&&... args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } +} +#endif + +namespace TrieUtils { + +static void append_uleb128(uint64_t value, std::vector& out) { + uint8_t byte; + do { + byte = value & 0x7F; + value &= ~0x7F; + if ( value != 0 ) + byte |= 0x80; + out.push_back(byte); + value = value >> 7; + } while( byte >= 0x80 ); +} + +static void append_string(std::string str, std::vector& out) { + for(char& c : str) + out.push_back(c); + out.push_back('\0'); +} + +static unsigned int uleb128_size(uint64_t value) { + uint32_t result = 0; + do { + value = value >> 7; + ++result; + } while ( value != 0 ); + return result; +} + +static +inline bool parse_uleb128(const uint8_t*& p, const uint8_t* end, uint64_t& result) { + result = 0; + int bit = 0; + do { + if (p == end) + return false; // malformed uleb128 extends beyond input + uint64_t slice = *p & 0x7f; + + if (bit >= 64 || slice << bit >> bit != slice) + return false; // malformed + else { + result |= (slice << bit); + bit += 7; + } + } + while (*p++ & 0x80); + return true; +} +}; + +template +struct Trie { + uint32_t count; + uint32_t nodeCount; + + struct Entry + { + std::string name; + V info; + + Entry(void) {} + Entry(const std::string& N, V I) : name(N), info(I) {} + }; + + Trie(const std::vector& entries) : count(0), nodeCount(1) { + // make nodes for all exported symbols + for (auto& entry : entries) { + addEntry(entry); + } + } + + void emit(std::vector& output) { + // create vector of nodes + std::vector orderedNodes; + orderedNodes.reserve(nodeCount); + orderTrie(&root, orderedNodes); + + // assign each node in the vector an offset in the trie stream, iterating until all uleb128 sizes have stabilized + bool more; + do { + uint32_t offset = 0; + more = false; + for (auto& node : orderedNodes) { + if (node->updateOffset(offset)) { + more = true; + } + } + } while ( more ); + + // create trie stream + for (auto& node : orderedNodes) { + node->appendToStream(output); + } + } + + static + inline bool parseTrie(const uint8_t* start, const uint8_t* end, std::vector& output) + { + // empty trie has no entries + if ( start == end ) + return false; + char cummulativeString[32768]; + std::vector entries; + if ( !processExportNode(start, start, end, cummulativeString, 0, entries) ) + return false; + // to preserve tie layout order, sort by node offset + std::sort(entries.begin(), entries.end()); + // copy to output + output.reserve(entries.size()); + for (auto& entryWithOffset : entries) { + output.push_back(entryWithOffset.entry); + } + return true; + } + +private: + struct Node + { + //This needs to be a map to unsure deterministic ordering of tries. + std::map > fChildren; + bool fIsTerminal; + uint32_t fTrieOffset; + V fInfo; + + Node(void) : fIsTerminal(false), fTrieOffset(0) {} + Node(V v) :fInfo(v), fIsTerminal(true), fTrieOffset(0) {} + Node(Node&&) = default; + + // byte for terminal node size in bytes, or 0x00 if not terminal node + // teminal node (uleb128 flags, uleb128 addr [uleb128 other]) + // byte for child node count + // each child: zero terminated substring, uleb128 node offset + bool updateOffset(uint32_t& offset) { + uint32_t nodeSize = 1; // length of export info when no export info + if ( fIsTerminal ) { + nodeSize = fInfo.encodedSize(); + // do have export info, overall node size so far is uleb128 of export info + export info + nodeSize += TrieUtils::uleb128_size(nodeSize); + } + // add children + ++nodeSize; // byte for count of chidren + + for (auto &edge : fChildren) { + nodeSize += edge.first.length() + 1 + TrieUtils::uleb128_size(edge.second->fTrieOffset); + } + + bool result = (fTrieOffset != offset); + fTrieOffset = offset; + //fprintf(stderr, "updateOffset %p %05d %s\n", this, fTrieOffset, fCummulativeString); + offset += nodeSize; + // return true if fTrieOffset was changed + return result; + } + + void appendToStream(std::vector& out) { + if ( fIsTerminal ) { + fInfo.appendToStream(out); + } + else { + // no export info uleb128 of zero is one byte of zero + out.push_back(0); + } + // write number of children + out.push_back(fChildren.size()); + // write each child + for (auto &edge : fChildren) { + TrieUtils::append_string(edge.first, out); + TrieUtils::append_uleb128(edge.second->fTrieOffset, out); + } + } + }; + + Node root; + + struct EntryWithOffset + { + uintptr_t nodeOffset; + Entry entry; + + bool operator<(const EntryWithOffset& other) const { return ( nodeOffset < other.nodeOffset ); } + }; + + void addEntry(const std::string& fullStr, std::string::const_iterator start, V v) { + Node *currentNode = &root; + bool done = false; + + while (!done && !currentNode->fChildren.empty() ) { + done = true; + + for (auto &entry : currentNode->fChildren) { + auto res = std::mismatch(entry.first.begin(), entry.first.end(), start); + + if (res.first == entry.first.end()) { + //Matched a full edge, go down it + done = false; + currentNode = entry.second.get(); + start = res.second; + break; + } else if (res.first != entry.first.begin()) { + // found a common substring, splice in new node + // was A -> C, now A -> B -> C + + //Build the new strings + std::string abEdgeStr(entry.first.begin(), res.first); + std::string bcEdgeStr(res.first, entry.first.end()); + + //Copy out the exist node and delete it from the currentNode + std::unique_ptr nodeC; + std::swap(nodeC, entry.second); + currentNode->fChildren.erase(entry.first); + + //Build the new node and insert it + std::unique_ptr nodeB = std::make_unique(); + Node *newNode = nodeB.get(); + + nodeB->fChildren.insert(std::make_pair(bcEdgeStr, std::move(nodeC))); + currentNode->fChildren.insert(std::make_pair(abEdgeStr, std::move(nodeB))); + + currentNode = newNode; + start = res.second; + ++nodeCount; + break; + } + } + } + + // no commonality with any existing child, make a new edge that is this whole string + std::string edgeStr(start, fullStr.end()); + v.willInsertAs(fullStr); + + if (edgeStr.empty()) { + currentNode->fIsTerminal = true; + currentNode->fInfo = v; + } else { + currentNode->fChildren.emplace(edgeStr, std::make_unique(v)); + ++nodeCount; + } + ++count; + } + + void addEntry(Entry entry) { + addEntry(entry.name, entry.name.begin(), entry.info); + } + +#if TRIE_DEBUG + void printTrie(Node& node, std::string cummulativeString) { + if (node.fTerminal) { + printf("%s: \n", cummulativeString.c_str()); + } + for (auto &edge : node.fChildren) { + printTrie(*edge.second, cummulativeString+edge.first); + } + } + +public: + void printTrie(void) { + printTrie(root, ""); + } +private: +#endif + + static inline bool processExportNode(const uint8_t* const start, const uint8_t* p, const uint8_t* const end, + char* cummulativeString, int curStrOffset, + std::vector& output) + { + if ( p >= end ) + return false; + uint64_t terminalSize; + if ( !TrieUtils::parse_uleb128(p, end, terminalSize) ) + return false; + const uint8_t* children = p + terminalSize; + if ( children >= end ) + return false; + if ( terminalSize != 0 ) { + EntryWithOffset e; + e.nodeOffset = p-start; + e.entry.name = cummulativeString; + e.entry.info.loadData(p,end); + output.push_back(e); + } + const uint8_t childrenCount = *children++; + const uint8_t* s = children; + for (uint8_t i=0; i < childrenCount; ++i) { + int edgeStrLen = 0; + while (*s != '\0') { + cummulativeString[curStrOffset+edgeStrLen] = *s++; + ++edgeStrLen; + } + cummulativeString[curStrOffset+edgeStrLen] = *s++; + uint64_t childNodeOffet; + if ( !TrieUtils::parse_uleb128(s, end, childNodeOffet) ) + return false; + if ( !processExportNode(start, start+childNodeOffet, end, cummulativeString, curStrOffset+edgeStrLen, output) ) + return false; + } + return true; + } + + void orderTrie(Node* node, std::vector& orderedNodes) { + orderedNodes.push_back(node); + for (auto &edge : node->fChildren) { + orderTrie(edge.second.get(), orderedNodes); + } + } +}; // struct Trie + +struct ExportInfo { + uint64_t address; + uint64_t flags; + uint64_t other; + std::string importName; + + ExportInfo() : other(0), address(0) {} + + uint32_t encodedSize(void) { + uint32_t size = 0; + if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { + size = TrieUtils::uleb128_size(flags) + TrieUtils::uleb128_size(other); // ordinal + if ( !importName.empty() ) + size += importName.length(); + ++size; // trailing zero in imported name + } + else { + size = TrieUtils::uleb128_size(flags) + TrieUtils::uleb128_size(address); + if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) + size += TrieUtils::uleb128_size(other); + } + return size; + } + + void appendToStream(std::vector& out) { + if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { + if ( !importName.empty() ) { + // nodes with re-export info: size, flags, ordinal, string + uint32_t nodeSize = (uint32_t)(TrieUtils::uleb128_size(flags) + TrieUtils::uleb128_size(other) + importName.length() + 1); + out.push_back(nodeSize); + TrieUtils::append_uleb128(flags, out); + TrieUtils::append_uleb128(other, out); + TrieUtils::append_string(importName, out); + } + else { + // nodes with re-export info: size, flags, ordinal, empty-string + uint32_t nodeSize = TrieUtils::uleb128_size(flags) + TrieUtils::uleb128_size(other) + 1; + out.push_back(nodeSize); + TrieUtils::append_uleb128(flags, out); + TrieUtils::append_uleb128(other, out); + out.push_back(0); + } + } + else if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) { + // nodes with export info: size, flags, address, other + uint32_t nodeSize = TrieUtils::uleb128_size(flags) + TrieUtils::uleb128_size(address) + TrieUtils::uleb128_size(other); + out.push_back(nodeSize); + TrieUtils::append_uleb128(flags, out); + TrieUtils::append_uleb128(address, out); + TrieUtils::append_uleb128(other, out); + } + else { + // nodes with export info: size, flags, address + uint32_t nodeSize = TrieUtils::uleb128_size(flags) + TrieUtils::uleb128_size(address); + out.push_back(nodeSize); + TrieUtils::append_uleb128(flags, out); + TrieUtils::append_uleb128(address, out); + } + } + + void loadData(const uint8_t* p, const uint8_t* const end) { + TrieUtils::parse_uleb128(p, end, flags); + if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { + TrieUtils::parse_uleb128(p, end, other); // dylib ordinal + importName = (char*)p; + } + else { + TrieUtils::parse_uleb128(p, end, address); + if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) + TrieUtils::parse_uleb128(p, end, other); + } + } + + void willInsertAs(const std::string& name) { + // Symbols re-exported under the same name do not need an explict import name, delete it to save space + if ((name == importName) ) { + importName = ""; + } + } +}; + +typedef Trie ExportInfoTrie; + + +// Used by accelerator tables in dyld shared cache +struct DylibIndex { + uint32_t index; + + DylibIndex() : index(0) {} + DylibIndex(uint32_t i) : index(i) {} + + uint32_t encodedSize(void) { + return TrieUtils::uleb128_size(index); + } + + void appendToStream(std::vector& out) { + uint32_t nodeSize = TrieUtils::uleb128_size(index); + out.push_back(nodeSize); + TrieUtils::append_uleb128(index, out); + } + + void loadData(const uint8_t* p, const uint8_t* const end) { + uint64_t temp; + TrieUtils::parse_uleb128(p, end, temp); + index = (uint32_t)temp; + } + + void willInsertAs(const std::string& name) { + } +}; +typedef Trie DylibIndexTrie; + + +#endif // __TRIE__ + + diff --git a/interlinked-dylibs/dyld_shared_cache_builder.mm b/interlinked-dylibs/dyld_shared_cache_builder.mm new file mode 100644 index 0000000..075a5ed --- /dev/null +++ b/interlinked-dylibs/dyld_shared_cache_builder.mm @@ -0,0 +1,387 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2016 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include "Manifest.h" +#include "MultiCacheBuilder.h" + +#include "mega-dylib-utils.h" +#include "Logging.h" + +#if !__has_feature(objc_arc) +#error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks +#endif + +extern char** environ; + +static dispatch_queue_t build_queue; + +inline bool has_suffix(std::string const & value, std::string const & ending) +{ + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +static const char *tempRootDirTemplate = "/tmp/dyld_shared_cache_builder.XXXXXX"; +static char *tempRootDir = nullptr; + +void processRoot( const std::string &root, std::set &roots ) { + struct stat sb; + int res = 0; + pid_t pid; + int status; + const char* args[8]; + + res = stat(root.c_str(), &sb); + + if (res == 0 && S_ISDIR(sb.st_mode)) { + roots.insert( root ); + return; + } else if ( has_suffix( root, ".cpio" ) || has_suffix( root, ".cpio.gz" ) || has_suffix( root, ".cpgz" ) || + has_suffix( root, ".cpio.bz2" ) || has_suffix( root, ".cpbz2" ) || has_suffix( root, ".pax" ) || + has_suffix( root, ".pax.gz" ) || has_suffix( root, ".pgz" ) || has_suffix( root, ".pax.bz2" ) || + has_suffix( root, ".pbz2" ) ) { + args[0] = (char *)"/usr/bin/ditto"; + args[1] = (char *)"-x"; + args[2] = (char *)root.c_str(); + args[3] = tempRootDir; + args[4] = nullptr; + } else if (has_suffix(root, ".tar")) { + args[0] = (char *)"/usr/bin/tar"; + args[1] = (char *)"xf"; + args[2] = (char *)root.c_str(); + args[3] = (char *)"-C"; + args[4] = tempRootDir; + args[5] = nullptr; + } else if (has_suffix(root, ".tar.gz") || has_suffix(root, ".tgz")) { + args[0] = (char *)"/usr/bin/tar"; + args[1] = (char *)"xzf"; + args[2] = (char *)root.c_str(); + args[3] = (char *)"-C"; + args[4] = tempRootDir; + args[5] = nullptr; + } else if (has_suffix(root, ".tar.bz2") + || has_suffix(root, ".tbz2") + || has_suffix(root, ".tbz")) { + args[0] = (char *)"/usr/bin/tar"; + args[1] = (char *)"xjf"; + args[2] = (char *)root.c_str(); + args[3] = (char *)"-C"; + args[4] = tempRootDir; + args[5] = nullptr; + } else if (has_suffix(root, ".xar")) { + args[0] = (char *)"/usr/bin/xar"; + args[1] = (char *)"-xf"; + args[2] = (char *)root.c_str(); + args[3] = (char *)"-C"; + args[4] = tempRootDir; + args[5] = nullptr; + } else if (has_suffix(root, ".zip")) { + args[0] = (char *)"/usr/bin/ditto"; + args[1] = (char *)"-xk"; + args[2] = (char *)root.c_str(); + args[3] = tempRootDir; + args[4] = nullptr; + } else { + terminate("unknown archive type: %s", root.c_str()); + } + + res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ); + if (res != 0) terminate("Failed to spawn %s: %s (%d)", args[0], strerror(res), res); + + do { + res = waitpid(pid, &status, 0); + } while (res == -1 && errno == EINTR); + if (res != -1) { + if (WIFEXITED(status)) { + res = WEXITSTATUS(status); + } else { + res = -1; + } + } + + if (res != 0) terminate("Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res); + + for (auto &existingRoot : roots) { + if (existingRoot == tempRootDir) + return; + } + + roots.insert( tempRootDir ); +} + +bool writeRootList( const std::string &dstRoot, const std::set &roots ) { + if ( roots.size() == 0 ) return false; + + std::string rootFile = dstRoot + "/roots.txt"; + FILE* froots = ::fopen(rootFile.c_str(), "w"); + if ( froots == NULL ) + return false; + + for (auto &root : roots) { + fprintf(froots, "%s\n", root.c_str()); + } + + ::fclose(froots); + return true; +} + +std::set cachePaths; + +BOMCopierCopyOperation filteredCopy(BOMCopier copier, const char *path, BOMFSObjType type, off_t size) { + std::string absolutePath = &path[1]; + if (cachePaths.count(absolutePath)) { + return BOMCopierSkipFile; + } + return BOMCopierContinue; +} + +int main (int argc, const char * argv[]) { + @autoreleasepool { + std::set roots; + std::vector inputRoots; + std::string dylibCacheDir; + std::string release; + bool emitDevCaches = true; + bool emitElidedDylibs = true; + bool listConfigs = false; + bool copyRoots = false; + std::string dstRoot; + std::string configuration; + std::string resultPath; + + time_t mytime = time(0); + log("Started: %s", asctime(localtime(&mytime))); + + tempRootDir = strdup(tempRootDirTemplate); + mkdtemp(tempRootDir); + + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + if (arg[0] == '-') { + if (strcmp(arg, "-debug") == 0) { + setVerbose(true); + } else if (strcmp(arg, "-list_configs") == 0) { + listConfigs = true; + } else if (strcmp(arg, "-root") == 0) { + std::string root = argv[++i]; + inputRoots.push_back(root); + processRoot(root, roots); + } else if (strcmp(arg, "-copy_roots") == 0) { + copyRoots = true; + } else if (strcmp(arg, "-dylib_cache") == 0) { + dylibCacheDir = argv[++i]; + } else if (strcmp(arg, "-no_development_cache") == 0) { + emitDevCaches = false; + } else if (strcmp(arg, "-no_overflow_dylibs") == 0) { + emitElidedDylibs = false; + } else if (strcmp(arg, "-development_cache") == 0) { + emitDevCaches = true; + } else if (strcmp(arg, "-overflow_dylibs") == 0) { + emitElidedDylibs = true; + } else if (strcmp(arg, "-dst_root") == 0) { + dstRoot = argv[++i]; + } else if (strcmp(arg, "-release") == 0) { + release = argv[++i]; + } else if (strcmp(arg, "-results") == 0) { + resultPath = argv[++i]; + } else { + //usage(); + terminate("unknown option: %s\n", arg); + } + } else { + if (!configuration.empty()) { + terminate("You may only specify one configruation"); + } + configuration = argv[i]; + } + } + + struct rlimit rl = {OPEN_MAX, OPEN_MAX}; + (void)setrlimit(RLIMIT_NOFILE, &rl); + + if (dylibCacheDir.empty() && release.empty()) { + terminate("you must specify either -dylib_cache or -release"); + } else if (!dylibCacheDir.empty() && !release.empty()) { + terminate("you may not use -dylib_cache and -release at the same time"); + } + + if ((configuration.empty() || dstRoot.empty()) && !listConfigs) { + terminate("Must specify a configuration and a -dst_root OR -list_configs\n"); + } + + + if (dylibCacheDir.empty()) { + dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + release + ".dlc"; + } + + //Move into the dir so we can use relative path manifests + chdir(dylibCacheDir.c_str()); + + auto manifest = Manifest(dylibCacheDir + "/Manifest.plist", roots); + + if (manifest.build.empty()) { + terminate("No manifest found at '%s/Manifest.plist'\n", dylibCacheDir.c_str()); + } + log("Building Caches for %s", manifest.build.c_str()); + + if (listConfigs) { + for (auto& config : manifest.configurations) { + printf("%s\n", config.first.c_str()); + } + exit(0); + } + + std::map filteredConfigs; + + for (auto& config : manifest.configurations) { + if (config.first == configuration) { + filteredConfigs[config.first] = config.second; + + for (auto& arch : filteredConfigs[config.first].architectures) { + arch.second.results = Manifest::Results(); + } + } + } + + if ( filteredConfigs.empty() ) { + terminate( "No config %s. Please run with -list_configs to see configurations available for this %s.\n", + configuration.c_str(), manifest.build.c_str() ); + } + + manifest.configurations = filteredConfigs; + manifest.calculateClosure(false); + + // FIXME: Plumb through no_development + + std::shared_ptr builder = std::make_shared( manifest, false, false, true ); + builder->buildCaches(dstRoot); + writeRootList(dstRoot, roots); + + if (copyRoots) { + for (auto& config : manifest.configurations) { + for (auto& arch : config.second.architectures) { + for (auto& dylib : arch.second.results.dylibs) { + if (dylib.second.included) { + MachOProxy *proxy = manifest.dylibProxy( dylib.first, arch.first ); + cachePaths.insert( proxy->installName ); + for ( auto &alias : proxy->installNameAliases ) { + cachePaths.insert(alias); + } + } + } + } + } + + BOMCopier copier = BOMCopierNewWithSys(BomSys_default()); + BOMCopierSetCopyFileStartedHandler(copier, filteredCopy); + for (auto& root : roots) { + BOMCopierCopy(copier, root.c_str(), dstRoot.c_str()); + } + BOMCopierFree(copier); + } + + // Create an empty FIPS data in the root + (void)mkpath_np((dstRoot + "/private/var/db/FIPS/").c_str(), 0755); + int fd = open((dstRoot + "/private/var/db/FIPS/fips_data").c_str(), O_CREAT | O_TRUNC, 0644); + close(fd); + + // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after + // everything is written. + + builder->logStats(); + if (!resultPath.empty()) { + manifest.write(resultPath); + } + + pid_t pid; + int res = 0; + int status; + const char* args[8]; + + args[0] = (char*)"/bin/rm"; + args[1] = (char*)"-rf"; + args[2] = (char*)tempRootDir; + args[3] = nullptr; + + res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ); + if (res != 0) + terminate("Failed to spawn %s: %s (%d)", args[0], strerror(res), res); + + do { + res = waitpid(pid, &status, 0); + } while (res == -1 && errno == EINTR); + if (res != -1) { + if (WIFEXITED(status)) { + res = WEXITSTATUS(status); + } else { + res = -1; + } + } + + dumpLogAndExit(); + } + + dispatch_main(); + + return 0; +} diff --git a/interlinked-dylibs/mega-dylib-utils.h b/interlinked-dylibs/mega-dylib-utils.h new file mode 100644 index 0000000..0482ffa --- /dev/null +++ b/interlinked-dylibs/mega-dylib-utils.h @@ -0,0 +1,255 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef __MEGA_DYLIB_UTILS_H__ +#define __MEGA_DYLIB_UTILS_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CacheFileAbstraction.hpp" + +#include "MachOFileAbstraction.hpp" + +#include "Manifest.h" +#include "MachOProxy.h" + +struct SharedCache; + +struct FileCache { + FileCache(void); + std::tuple cacheLoad(const std::string path); + void preflightCache(const std::string& path); + void preflightCache(const std::unordered_set &paths); +private: + void fill(const std::string& path); + + std::unordered_map> entries; + dispatch_queue_t cache_queue; +}; + +extern FileCache fileCache; + +typedef std::pair>> WarningTargets; + +inline uint64_t align(uint64_t addr, uint8_t p2) +{ + uint64_t mask = (1 << p2); + return (addr + mask - 1) & (-mask); +} + + +uint64_t sharedRegionRegionSize(ArchPair arch); +uint8_t sharedRegionRegionAlignment(ArchPair arch); +ArchPair archForString(const std::string& archStr); +std::string stringForArch(ArchPair arch, bool allowUnknown = false); +std::string fallbackArchStringForArchString( const std::string& archStr ); + +struct SharedCache { + struct SegmentInfo { + SegmentInfo(const MachOProxy::Segment* seg) + : base(seg), address(0), cacheFileOffset(0), cacheSegSize(0) { } + + const MachOProxy::Segment* base; + uint64_t address; + uint64_t cacheFileOffset; + uint64_t cacheSegSize; + }; + + struct Region { + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint32_t prot; + }; + + SharedCache(Manifest& manifest, + const std::string& configuration, const std::string& architecture); + + // We do not need an explicit destructor despite our move/copy constructor because the resource the are dealing with is a + // std::unique which has a registered destructor + + //FIXME reminants of merging the two classes, unifyu these + uint64_t vmSize() const { return _vmSize; } + uint64_t fileSize() const { return _fileSize; } + const uint8_t* cdHash() { return _cdHash; } + std::string cdHashString(); + + //FIXME: This should be private, but we can't do that until we move write out into the class after the next refactor + std::shared_ptr buffer() const; + + void buildForDevelopment(const std::string& cachePath); + void buildForProduction(const std::string& cachePath); + bool writeCacheMapFile(const std::string& mapPath); + + typedef std::function& segments)> DylibHandler; + // Calls lambda once per image in the cache + void forEachImage(DylibHandler handler); + + typedef std::function RegionHandler; + // Calls lambda once per region in the cache + void forEachRegion(RegionHandler handler); + + void setLinkeditsMappingEndFileOffset(uint64_t newFileSize); + void setAcceleratorInfoRange(uint64_t accelInfoAddr, uint32_t accelInfoSize); + void setUnmappedLocalsRange(uint64_t localSymbolsOffset, uint32_t unmappedSize); + void recomputeCacheUUID(void); + +private: + void loadDirtyDataOrderFile(const std::string& dirtyDataOrderFile); + void sortDylibs(const std::string& dylibOrderFile); + void assignSegmentAddresses(); + void bypassStubs(const std::vector& branchPoolStartAddrs); + void optimizeLinkedit(bool dontMapLocalSymbols, bool addAcceleratorTables, const std::vector& branchPoolOffsets); + + // Once all a dylib's segments are copied into a cache, this function will adjust the contents of + // the TEXT, DATA, and LINKEDIT segments in the cache to be correct for their new addresses. + void bindAllImagesInCache(const std::unordered_map& dylibPathToMachHeader, std::vector& pointersForASLR); + + // After adjustImageForNewSegmentLocations() is called to rebase all segments, this function can be called to + // bind all symbols to their new addresses + void adjustImageForNewSegmentLocations(const std::vector& segNewStartAddresses, + const std::vector& segCacheFileOffsets, + const std::vector& segCacheSizes, std::vector& pointersForASLR); + + uint64_t pathHash(const char* path); + void writeCacheHeader(void); + void writeCacheSegments(void); + void rebaseAll(void); + void rebase(MachOProxy* dylib); + void bindAll(void); + void optimizeObjC(bool forProduction); + void writeSlideInfoV2(void); + + void buildUnoptimizedCache(); + void appendCodeSignature(const std::string& suffix); + template void buildForDevelopment(const std::string& cachePath); + template void buildForProduction(const std::string& cachePath); + template void forEachImage(DylibHandler handler); + template void forEachRegion(RegionHandler handler); + template void setLinkeditsMappingEndFileOffset(uint64_t newFileSize); + template void setAcceleratorInfoRange(uint64_t accelInfoAddr, uint32_t accelInfoSize); + template void setUnmappedLocalsRange(uint64_t localSymbolsOffset, uint32_t unmappedSize); + template void recomputeCacheUUID(void); + template void bypassStubs(const std::vector& branchPoolStartAddrs); + template void optimizeLinkedit(bool dontMapLocalSymbols, bool addAcceleratorTables, const std::vector& branchPoolOffsets); + template void writeCacheHeader(void); + template void writeSlideInfo(void); + template void writeSlideInfoV2(uint64_t deltaMask, uint64_t valueAdd); + + template void findImplicitAliases(std::shared_ptr dylib); + template void addPageStarts(uint8_t* pageContent, const bool bitmap[], const dyldCacheSlideInfo2* info, + std::vector& pageStarts, std::vector& pageExtras); + template bool makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t newOffset, const dyldCacheSlideInfo2* info); + void findDylibAndSegment(const void* contentPtr, std::string& dylibName, std::string& segName); + + ArchPair _arch; + std::vector _dylibs; + std::shared_ptr _buffer; + std::unordered_map> _segmentMap; + + std::string archName(); + + Manifest& _manifest; + const Manifest::Architecture& _archManifest; + uint32_t _aliasCount; + Region _textRegion; + Region _dataRegion; + Region _readOnlyRegion; + uint64_t _slideInfoFileOffset; + uint64_t _slideInfoBufferSize; + uint64_t _fileSize; + uint64_t _vmSize; + std::unordered_map _dataDirtySegsOrder; + std::vector _pointersForASLR; + std::vector _branchPoolStarts; + uint64_t _branchPoolsLinkEditStartAddr; + uint8_t _cdHash[20]; +}; + +std::string normalize_absolute_file_path(const std::string &path); +std::string baspath(const std::string& path); +std::string dirpath(const std::string& path); + +std::string toolDir(); +bool isProtectedBySIP(const std::string& path, int fd=-1); + + +template +inline bool is_disjoint(const Set1& set1, const Set2& set2) +{ + if (set1.empty() || set2.empty()) + return true; + + typename Set1::const_iterator it1 = set1.begin(), it1End = set1.end(); + typename Set2::const_iterator it2 = set2.begin(), it2End = set2.end(); + + if (*it1 > *set2.rbegin() || *it2 > *set1.rbegin()) + return true; + + while (it1 != it1End && it2 != it2End) { + if (*it1 == *it2) + return false; + if (*it1 < *it2) { + it1++; + } else { + it2++; + } + } + + return true; +} + +inline bool has_prefix(const std::string& str, const std::string& prefix) +{ + return std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end(); +} + + +#define NEW_CACHE_FILE_FORMAT 0 + +#endif // __MEGA_DYLIB_UTILS_H__ + + + + + diff --git a/interlinked-dylibs/multi_dyld_shared_cache_builder.mm b/interlinked-dylibs/multi_dyld_shared_cache_builder.mm new file mode 100644 index 0000000..7aec8dc --- /dev/null +++ b/interlinked-dylibs/multi_dyld_shared_cache_builder.mm @@ -0,0 +1,289 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2016 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "MultiCacheBuilder.h" +#include "Manifest.h" + +#include "mega-dylib-utils.h" +#include "Logging.h" + +#if !__has_feature(objc_arc) +#error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks +#endif + +static dispatch_queue_t build_queue = dispatch_queue_create("com.apple.dyld.build", DISPATCH_QUEUE_CONCURRENT); +static dispatch_group_t build_group = dispatch_group_create(); +static dispatch_semaphore_t writeLimitingSemaphore = dispatch_semaphore_create(1); + +bool copyFile(const std::string& from, const std::string& to) +{ + const uint8_t* p = (uint8_t*)(-1); + struct stat stat_buf; + bool rp; + + std::tie(p, stat_buf, rp) = fileCache.cacheLoad(from); + if (p == (uint8_t*)(-1)) + return false; + + dispatch_group_enter(build_group); + mkpath_np(dirpath(to).c_str(), 0755); + + dispatch_semaphore_wait(writeLimitingSemaphore, DISPATCH_TIME_FOREVER); + int fd = open(to.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd > 0) { + ssize_t writtenSize = pwrite(fd, p, stat_buf.st_size, 0); + if (writtenSize != stat_buf.st_size) + terminate("write() failure creating cache file, errno=%d (%s)", errno, strerror(errno)); + + ::close(fd); + verboseLog("Wrote out: %s", to.c_str()); + } else { + terminate("can't create file '%s', errnor=%d (%s)", to.c_str(), errno, strerror(errno)); + } + dispatch_semaphore_signal(writeLimitingSemaphore); + dispatch_group_leave(build_group); + + return true; +} + +#define kDylibCachePrefix "/AppleInternal/Developer/DylibCaches/" + +void createArtifact(Manifest& manifest, const std::string& dylibCachePath, bool includeExecutables) +{ + mkpath_np(dylibCachePath.c_str(), 0755); + (void)copyFile(manifest.dylibOrderFile, dylibCachePath + "Metadata/dylibOrderFile.txt"); + (void)copyFile(manifest.dirtyDataOrderFile, dylibCachePath + "Metadata/dirtyDataOrderFile.txt"); + (void)copyFile(manifest.metabomFile, dylibCachePath + "Metadata/metabom.bom"); + + std::set copied; + + for (auto archFiles : manifest.architectureFiles) { + for (auto& file : archFiles.second.dylibs) { + std::string installname = file.first; + if (copied.count(installname) > 0) { + continue; + } + (void)copyFile(file.second.proxy->path, normalize_absolute_file_path(dylibCachePath + "/Root/" + file.first)); + copied.insert(installname); + } + if (includeExecutables) { + for (auto& file : archFiles.second.executables) { + std::string installname = file.first; + if (copied.count(installname) > 0) { + continue; + } + (void)copyFile(file.second.proxy->path, normalize_absolute_file_path(dylibCachePath + "/Root/" + file.first)); + copied.insert(installname); + } + } + } + + log("Artifact dylibs copied"); +} + +void addArtifactPaths(Manifest &manifest) { + manifest.dylibOrderFile = "./Metadata/dylibOrderFile.txt"; + manifest.dirtyDataOrderFile = "./Metadata/dirtyDataOrderFile.txt"; + manifest.metabomFile = "./Metadata/metabom.bom"; + + for ( auto& projects : manifest.projects ) { + if ( projects.second.sources[0] != "./Root/" ) { + projects.second.sources.insert( projects.second.sources.begin(), "./Root/" ); + } + } +} + +int main (int argc, const char * argv[]) { + @autoreleasepool { + auto defaultCtx = std::make_shared(""); + setLoggingContext(defaultCtx); + + Manifest manifest; + std::string masterDstRoot; + std::string dylibCacheDir; + std::string resultPath; + bool skipWrites = false; + bool skipBuilds = false; + bool preflight = false; + std::string manifestPath; + + time_t mytime = time(0); + log("Started: %s", asctime(localtime(&mytime))); + + // parse command line options + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + if (arg[0] == '-') { + if (strcmp(arg, "-debug") == 0) { + setVerbose(true); + } else if (strcmp(arg, "-skip_writes") == 0) { + skipWrites = true; + } else if (strcmp(arg, "-skip_builds") == 0) { + skipBuilds = true; + } else if (strcmp(arg, "-delete_writes") == 0) { + skipWrites = true; + } else if (strcmp(arg, "-dylib_cache") == 0) { + dylibCacheDir = argv[++i]; + } else if (strcmp(arg, "-preflight") == 0) { + preflight = true; + skipWrites = true; + } else if (strcmp(arg, "-master_dst_root") == 0) { + masterDstRoot = argv[++i]; + if (masterDstRoot.empty()) { + terminate("-master_dst_root missing path argument"); + } + } else if (strcmp(arg, "-results") == 0) { + resultPath = argv[++i]; + } else if (strcmp(arg, "-plist") == 0) { + manifestPath = argv[++i]; + (void)chdir(dirname(strdup(manifestPath.c_str()))); + manifest = Manifest(manifestPath); + } else { + // usage(); + terminate("unknown option: %s", arg); + } + } else { + manifestPath = argv[i]; + + (void)chdir(dirname(strdup(argv[i]))); + } + } + + if ( getenv( "LGG_SKIP_CACHE_FUN" ) != nullptr ) { + skipBuilds = true; + } + + if (manifest.build.empty()) { + terminate("No version found in manifest"); + } + log("Building Caches for %s", manifest.build.c_str()); + + if ( masterDstRoot.empty() ) { + terminate("-master_dst_root required path argument"); + } + + if (manifest.manifest_version < 4) { + terminate("must specify valid manifest file"); + } + + struct rlimit rl = {OPEN_MAX, OPEN_MAX}; + (void)setrlimit(RLIMIT_NOFILE, &rl); + + if (!skipWrites) { + (void)mkpath_np((masterDstRoot + "/Boms/").c_str(), 0755); + } + + if (manifest.dylibOrderFile.empty()) { + manifest.dylibOrderFile = toolDir() + "/dylib-order.txt"; + } + + if (manifest.dirtyDataOrderFile.empty()) { + manifest.dirtyDataOrderFile = toolDir() + "/dirty-data-segments-order.txt"; + } + + auto dylibCacheCtx = std::make_shared("DylibCache"); + setLoggingContext(dylibCacheCtx); + + if (!skipWrites) { + cacheBuilderDispatchGroupAsync(build_group, build_queue, [&] { + createArtifact(manifest, masterDstRoot + "/Artifact.dlc/", true); + }); + + if (!dylibCacheDir.empty()) { + cacheBuilderDispatchGroupAsync(build_group, build_queue, [&] { + createArtifact(manifest, dylibCacheDir + "/AppleInternal/Developer/DylibCaches/" + manifest.build + ".dlc/", false); + }); + } + } + setLoggingContext(defaultCtx); + + manifest.calculateClosure(false); + std::shared_ptr builder = std::make_shared(manifest, true, skipWrites, false, skipBuilds); + dispatch_group_async(build_group, build_queue, [&] { builder->buildCaches(masterDstRoot); }); + dispatch_group_wait(build_group, DISPATCH_TIME_FOREVER); + + if (!preflight) { + setLoggingContext(dylibCacheCtx); + addArtifactPaths(manifest); + setLoggingContext(defaultCtx); + + if (!resultPath.empty()) { + manifest.write(resultPath); + } + + setLoggingContext(dylibCacheCtx); + copyFile(manifestPath, masterDstRoot + "/Artifact.dlc/BNIManifest.plist"); + manifest.write(masterDstRoot + "/Artifact.dlc/Manifest.plist"); + + if (!dylibCacheDir.empty()) { + manifest.write(dylibCacheDir + kDylibCachePrefix + manifest.build + ".dlc/Manifest.plist"); + } + setLoggingContext(defaultCtx); + } + + builder->logStats(); + + dumpLogAndExit(); + } + + dispatch_main(); + + return 0; +} + + diff --git a/interlinked-dylibs/update_dyld_shared_cache.mm b/interlinked-dylibs/update_dyld_shared_cache.mm new file mode 100644 index 0000000..46f9e3a --- /dev/null +++ b/interlinked-dylibs/update_dyld_shared_cache.mm @@ -0,0 +1,448 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +#include +#include +#include +#include +#include + +#include "MachOProxy.h" +#include "manifest.h" +#include "mega-dylib-utils.h" +#include "Logging.h" + +#import "MultiCacheBuilder.h" + +#if !__has_feature(objc_arc) +#error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks +#endif + +const std::string anchorsDirPath = "/private/var/db/dyld/shared_region_roots"; + +bool parsePathsFile( const std::string& filePath, std::set& paths ) { + verboseLog( "parsing .paths file '%s'", filePath.c_str() ); + std::ifstream myfile( filePath ); + if ( myfile.is_open() ) { + std::string line; + while ( std::getline(myfile, line) ) { + size_t pos = line.find('#'); + if ( pos != std::string::npos ) + line.resize(pos); + while ( line.size() != 0 && isspace(line.back()) ) { + line.pop_back(); + } + if ( !line.empty() ) paths.insert( line ); + } + myfile.close(); + + return true; + } + return false; +} + +static bool parseDirectoryOfPathsFiles(const std::string& dirPath, std::set& paths) +{ + DIR* dir = ::opendir(dirPath.c_str()); + if ( dir == NULL ) + return false; + + for (dirent* entry = ::readdir(dir); entry != NULL; entry = ::readdir(dir)) { + if ( entry->d_type == DT_REG || entry->d_type == DT_UNKNOWN ) { + // only look at regular files ending in .paths + if ( strcmp(&entry->d_name[entry->d_namlen-6], ".paths") == 0 ) { + struct stat statBuf; + std::string filePathStr = dirPath + "/" + entry->d_name; + const char* filePath = filePathStr.c_str(); + if ( lstat(filePath, &statBuf) == -1 ) { + warning("can't access file '%s'", filePath); + } + else if ( S_ISREG(statBuf.st_mode) ) { + parsePathsFile(filePath, paths); + } + else { + warning("not a regular file '%s'", filePath); + } + } + else { + warning("ignoring file with wrong extension '%s'", entry->d_name); + } + } + } + ::closedir(dir); + return true; +} + +static bool buildInitialPaths(const std::string& volumeRootPath, const std::string& overlayPath, std::set& paths) +{ + // in -root mode, look for roots in /rootpath/private/var/db/dyld/shared_region_roots + if ( volumeRootPath != "/" ) { + if ( parseDirectoryOfPathsFiles(volumeRootPath + "/" + anchorsDirPath, paths) ) + return true; + // fallback to .paths files on boot volume + return parseDirectoryOfPathsFiles(anchorsDirPath, paths); + } + + // in -overlay mode, look for .paths first in each /overlay/private/var/db/dyld/shared_region_roots + if ( !overlayPath.empty() ) { + parseDirectoryOfPathsFiles(overlayPath + "/" + anchorsDirPath, paths); + } + + // look for .paths files in /private/var/db/dyld/shared_region_roots + return parseDirectoryOfPathsFiles(anchorsDirPath, paths); +} + +bool fileExists(const std::string& path, bool& isSymLink) +{ + struct stat statBuf; + if ( lstat(path.c_str(), &statBuf) == -1 ) + return false; + isSymLink = S_ISLNK(statBuf.st_mode); + return S_ISREG(statBuf.st_mode) || isSymLink; +} + +bool tryPath(const std::string& prefix, const std::string& path, std::string& foundPath, std::vector& aliases) +{ + foundPath = prefix + path; + bool isSymLink; + if ( !fileExists(foundPath, isSymLink) ) + return false; + if ( isSymLink ) { + // handle case where install name is a symlink to real file (e.g. libstdc++.6.dylib -> libstdc++.6.0.9.dylib) + char pathInSymLink[MAXPATHLEN]; + long len = ::readlink(foundPath.c_str(), pathInSymLink, sizeof(pathInSymLink)); + if ( len != -1 ) { + pathInSymLink[len] = '\0'; + if ( pathInSymLink[0] != '/' ) { + std::string aliasPath = path; + size_t pos = aliasPath.rfind('/'); + if ( pos != std::string::npos ) { + std::string newPath = aliasPath.substr(0,pos+1) + pathInSymLink; + aliases.push_back(newPath); + } + } + } + } + char realPath[MAXPATHLEN]; + if ( ::realpath(foundPath.c_str(), realPath) ) { + if ( foundPath != realPath ) { + std::string altPath = realPath; + if ( !prefix.empty() ) { + if ( altPath.substr(0, prefix.size()) == prefix ) { + altPath = altPath.substr(prefix.size()); + } + } + if ( altPath != path ) + aliases.push_back(path); + else + aliases.push_back(altPath); + } + } + return true; +} + +bool improvePath(const char* volumeRootPath, const std::vector& overlayPaths, + const std::string& path, std::string& foundPath, std::vector& aliases) +{ + for (const char* overlay : overlayPaths) { + if ( tryPath(overlay, path, foundPath, aliases) ) + return true; + } + if ( volumeRootPath[0] != '\0' ) { + if ( tryPath(volumeRootPath, path, foundPath, aliases) ) + return true; + } + return tryPath("", path, foundPath, aliases); +} + +std::string fileExists( const std::string& path ) { + const uint8_t* p = (uint8_t*)( -1 ); + struct stat stat_buf; + bool rootless; + + std::tie( p, stat_buf, rootless ) = fileCache.cacheLoad( path ); + if ( p != (uint8_t*)( -1 ) ) { + return normalize_absolute_file_path( path ); + } + + return ""; +} + +void populateManifest(Manifest& manifest, std::set archs, const std::string& overlayPath, + const std::string& rootPath, const std::set& paths) { + for ( const auto& arch : archs ) { + auto fallback = fallbackArchStringForArchString(arch); + std::set allArchs = archs; + std::set processedPaths; + std::set unprocessedPaths = paths; + std::set pathsToProcess; + std::set_difference( unprocessedPaths.begin(), unprocessedPaths.end(), processedPaths.begin(), processedPaths.end(), + std::inserter( pathsToProcess, pathsToProcess.begin() ) ); + while ( !pathsToProcess.empty() ) { + for (const std::string path : pathsToProcess) { + processedPaths.insert(path); + std::string fullPath; + if ( rootPath != "/" ) { + // with -root, only look in the root path volume + fullPath = fileExists(rootPath + path); + } + else { + // with -overlay, look first in overlay dir + if ( !overlayPath.empty() ) + fullPath = fileExists(overlayPath + path); + // if not in overlay, look in boot volume + if ( fullPath.empty() ) + fullPath = fileExists(path); + } + if ( fullPath.empty() ) + continue; + auto proxies = MachOProxy::findDylibInfo(fullPath, true, true); + auto proxy = proxies.find(arch); + if (proxy == proxies.end()) + proxy = proxies.find(fallback); + if (proxy == proxies.end()) + continue; + + for ( const auto& dependency : proxy->second->dependencies ) { + unprocessedPaths.insert( dependency ); + } + + if ( proxy->second->installName.empty() ) { + continue; + } + + proxy->second->addAlias( path ); + manifest.architectureFiles[arch].dylibs.insert(std::make_pair(proxy->second->installName, + Manifest::File(proxy->second))); + manifest.configurations["localhost"].architectures[arch].anchors.push_back( proxy->second->installName ); + } + + pathsToProcess.clear(); + std::set_difference( unprocessedPaths.begin(), unprocessedPaths.end(), processedPaths.begin(), processedPaths.end(), + std::inserter( pathsToProcess, pathsToProcess.begin() ) ); + } + } +} + +static bool runningOnHaswell() +{ + // check system is capable of running x86_64h code + struct host_basic_info info; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + mach_port_t hostPort = mach_host_self(); + kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count); + mach_port_deallocate(mach_task_self(), hostPort); + + return ( (result == KERN_SUCCESS) && (info.cpu_subtype == CPU_SUBTYPE_X86_64_H) ); +} + + +#define TERMINATE_IF_LAST_ARG( s ) \ + do { \ + if ( i == argc - 1 ) terminate( s ); \ + } while ( 0 ) + +int main(int argc, const char* argv[]) +{ + std::string rootPath; + std::string overlayPath; + std::string platform = "osx"; + std::string dylibListFile; + bool universal = false; + bool force = false; + std::string cacheDir; + std::set archStrs; + std::vector anchors; + + // parse command line options + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + if (arg[0] == '-') { + if (strcmp(arg, "-debug") == 0) { + setVerbose(true); + } else if (strcmp(arg, "-verbose") == 0) { + setVerbose(true); + } else if (strcmp(arg, "-dont_map_local_symbols") == 0) { + //We are going to ignore this + } else if (strcmp(arg, "-iPhone") == 0) { + platform = "iphoneos"; + } else if (strcmp(arg, "-dylib_list") == 0) { + TERMINATE_IF_LAST_ARG("-dylib_list missing argument"); + dylibListFile = argv[++i]; + } else if ((strcmp(arg, "-root") == 0) || (strcmp(arg, "--root") == 0)) { + TERMINATE_IF_LAST_ARG("-root missing path argument\n"); + rootPath = argv[++i]; + } else if (strcmp(arg, "-overlay") == 0) { + TERMINATE_IF_LAST_ARG("-overlay missing path argument\n"); + overlayPath = argv[++i]; + } else if (strcmp(arg, "-cache_dir") == 0) { + TERMINATE_IF_LAST_ARG("-cache_dir missing path argument\n"); + cacheDir = argv[++i]; + } else if (strcmp(arg, "-arch") == 0) { + TERMINATE_IF_LAST_ARG("-arch missing argument\n"); + archStrs.insert(argv[++i]); + } else if (strcmp(arg, "-force") == 0) { + force = true; + } else if (strcmp(arg, "-sort_by_name") == 0) { + //No-op, we always do this now + } else if (strcmp(arg, "-universal_boot") == 0) { + universal = true; + } else { + //usage(); + terminate("unknown option: %s\n", arg); + } + } else { + //usage(); + terminate("unknown option: %s\n", arg); + } + } + + setReturnNonZeroOnTerminate(); + setWarnAnErrorPrefixes("update_dyld_shared_cache: warning: ", "update_dyld_shared_cache failed: "); + + if ( !rootPath.empty() & !overlayPath.empty() ) + terminate("-root and -overlay cannot be used together\n"); + + //FIXME realpath on root and overlays + + if (rootPath.empty()) { + rootPath = "/"; + } + + if ( cacheDir.empty() ) { + // write cache file into -root or -overlay directory, if used + if ( rootPath != "/" ) + cacheDir = rootPath + MACOSX_DYLD_SHARED_CACHE_DIR; + else if ( !overlayPath.empty() ) + cacheDir = overlayPath + MACOSX_DYLD_SHARED_CACHE_DIR; + else + cacheDir = MACOSX_DYLD_SHARED_CACHE_DIR; + } + + if (universal) { + if ( platform == "iphoneos" ) { + terminate("-iPhoneOS and -universal are incompatible\n"); + } + archStrs.clear(); + } + + if (archStrs.size() == 0) { + if ( platform == "iphoneos" ) { + terminate("Must specify -arch(s) when using -iPhone\n"); + } + else { + if ( universal ) { + // -universal_boot should make all possible dyld caches + archStrs.insert("i386"); + archStrs.insert("x86_64"); + archStrs.insert("x86_64h"); + } + else { + // just make caches for this machine + archStrs.insert("i386"); + archStrs.insert(runningOnHaswell() ? "x86_64h" : "x86_64"); + } + } + } + + int err = mkpath_np(cacheDir.c_str(), S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + if (err != 0 && err != EEXIST) { + terminate("mkpath_np fail: %d", err); + } + + Manifest manifest; + + std::set paths; + + if ( !dylibListFile.empty() ) { + if ( !parsePathsFile( dylibListFile, paths ) ) { + terminate( "could not build intiial paths\n" ); + } + } else if ( !buildInitialPaths( rootPath, overlayPath, paths ) ) { + terminate( "could not build intiial paths\n" ); + } + + manifest.platform = platform; + populateManifest( manifest, archStrs, overlayPath, rootPath, paths ); + + // If the path we are writing to is trusted then our sources need to be trusted + // Can't update the update_dyld_shared_cache on a non-boot volume + bool requireDylibsBeRootlessProtected = isProtectedBySIP(cacheDir); + manifest.calculateClosure( requireDylibsBeRootlessProtected ); + manifest.pruneClosure(); + + for (const std::string& archStr : archStrs) { + std::string cachePath = cacheDir + "/dyld_shared_cache_" + archStr; + if ( manifest.sameContentsAsCacheAtPath("localhost", archStr, cachePath) && !force ) { + manifest.configurations["localhost"].architectures.erase(archStr); + verboseLog("%s is already up to date", cachePath.c_str()); + } + } + + // If caches already up to date, do nothing + if ( manifest.configurations["localhost"].architectures.empty() ) + dumpLogAndExit(false); + + // build caches + std::shared_ptr builder = std::make_shared(manifest, false, false, false, false, requireDylibsBeRootlessProtected); + builder->buildCaches(cacheDir); + + // Save off spintrace data + std::string nuggetRoot = (overlayPath.empty() ? rootPath : overlayPath); + (void)dscsym_save_nuggets_for_current_caches(nuggetRoot.c_str()); + + // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after + // everything is written. + builder->logStats(); + dumpLogAndExit(false); + + dispatch_main(); +} + diff --git a/interlinked-dylibs/update_dyld_shared_cache_compat.cpp b/interlinked-dylibs/update_dyld_shared_cache_compat.cpp new file mode 100644 index 0000000..75f3988 --- /dev/null +++ b/interlinked-dylibs/update_dyld_shared_cache_compat.cpp @@ -0,0 +1,309 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (c) 2014 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mega-dylib-utils.h" + +/* + This is a compatibility driver to allow us to support migrating to the new cache codebase and format without forcing B&I to alter gencaches. + This tool only supports flags used by B&I + This intention is for this tool to be removed in the near future and replaced with a tool that is not commandline compatible, + but allows shared caches to be built directly out of BuildRecords. + */ + +/* + Example commandline: + [60042] INFO - Executing Command: /SWE/Teams/DT-SWB/iOS/Binaries/Binaries5/update_dyld_shared_cache/update_dyld_shared_cache-357.0.91~2/Root/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/local/bin/update_dyld_shared_cache + -debug + -root /BuildRoot//Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.Internal.sdk + -dylib_list /tmp/dylibs.K93aInternalOS.60042 + -cache_dir /tmp/K93aInternalOS.60042 + -arch armv7 + -iPhone + -dont_map_local_symbols + -overlay /SWE/Teams/DT-SWB/iOS/Binaries/Binaries5/XPCService_caches/XPCService_caches-119~94/Root/K93aInternalOS/ + -overlay /SWE/Teams/DT-SWB/iOS/Binaries/Binaries5/libxpc_caches/libxpc_caches-649~18/Root/K93aInternalOS/ + */ + +// record warnings and add to .map file +static std::vector> sWarnings; + + +static void write_cache(const char* cachePath, SharedCache& cache) { + // create temp file for cache + int fd = ::open(cachePath, O_CREAT | O_RDWR | O_TRUNC, 0644); + if ( fd == -1 ) + terminate("can't create temp file %s, errnor=%d", cachePath, errno); + + // try to allocate whole cache file contiguously + fstore_t fcntlSpec = { F_ALLOCATECONTIG|F_ALLOCATEALL, F_PEOFPOSMODE, 0, static_cast(cache.fileSize()), 0 }; + ::fcntl(fd, F_PREALLOCATE, &fcntlSpec); + + // write out cache file + if ( ::pwrite(fd, cache.buffer().get(), cache.fileSize(), 0) != cache.fileSize() ) + terminate("write() failure creating cache file, errno=%d", errno); + + // flush to disk and close + int result = ::fcntl(fd, F_FULLFSYNC, NULL); + if ( result == -1 ) + warning("fcntl(F_FULLFSYNC) failed with errno=%d for %s\n", errno, cachePath); + ::close(fd); + + // write .map file + char mapPath[MAXPATHLEN]; + strlcpy(mapPath, cachePath, MAXPATHLEN); + strlcat(mapPath, ".map", MAXPATHLEN); + cache.writeCacheMapFile(mapPath); + + // append any warnings encountered + if ( !sWarnings.empty() ) { + FILE* fmap = ::fopen(mapPath, "a"); + if ( fmap != NULL ) { + fprintf(fmap, "\n\n"); + for ( std::unique_ptr& msg : sWarnings ) { + fprintf(fmap, "# %s", &*msg); + } + ::fclose(fmap); + } + } +} + +int main(int argc, const char* argv[]) +{ + std::set onlyArchs; + const char *rootPath = NULL; + std::vector searchPaths; + std::vector> dylibs; + const char* dylibListFile = NULL; +// bool force = false; +// bool keepSignatures = false; + bool explicitCacheDir = false; + bool dontMapLocalSymbols = false; + bool verbose = false; + bool iPhoneOS = false; + const char* cacheDir = NULL; + const char* archStr = NULL; + ArchPair archPair(CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL); + + // parse command line options + for(int i=1; i < argc; ++i) { + const char* arg = argv[i]; + if ( arg[0] == '-' ) { + if ( strcmp(arg, "-debug") == 0 ) { + verbose = true; + } + else if ( strcmp(arg, "-dont_map_local_symbols") == 0 ) { + dontMapLocalSymbols = true; + } + else if ( strcmp(arg, "-iPhone") == 0 ) { + iPhoneOS = true; + } + else if ( strcmp(arg, "-dylib_list") == 0 ) { + dylibListFile = argv[++i]; + if ( dylibListFile == NULL ) + terminate("-dylib_list missing path argument\n"); + } + else if ( (strcmp(arg, "-root") == 0) || (strcmp(arg, "--root") == 0) ) { + rootPath = argv[++i]; + } + else if ( strcmp(arg, "-overlay") == 0 ) { + const char* path = argv[++i]; + if ( path == NULL ) + terminate("-overlay missing path argument\n"); + searchPaths.push_back(path); + } + else if ( strcmp(arg, "-cache_dir") == 0 ) { + cacheDir = argv[++i]; + if ( cacheDir == NULL ) + terminate("-cache_dir missing path argument\n"); + explicitCacheDir = true; + } + else if ( strcmp(arg, "-arch") == 0 ) { + archStr = argv[++i]; + archPair = archForString(archStr); // terminates if unknown + } + else if ( strcmp(arg, "-force") == 0 ) { + // ignore. always forced for iOS + } + else { + //usage(); + terminate("unknown option: %s\n", arg); + } + } + else { + //usage(); + terminate("unknown option: %s\n", arg); + } + } + + if (!rootPath) { + terminate("-root is a required option\n"); + } + if (!iPhoneOS) { + terminate("-iPhone is a required option\n"); + } + if (!cacheDir) { + terminate("-cache_dir is a required option\n"); + } + if (!dylibListFile) { + terminate("-dylib_list is a required option\n"); + } + if (!archStr) { + terminate("-arch is a required option\n"); + } + + char prodCachePath[MAXPATHLEN]; + char devCachePath[MAXPATHLEN]; + strcpy(prodCachePath, cacheDir); + if ( prodCachePath[strlen(prodCachePath)-1] != '/' ) + strcat(prodCachePath, "/"); + strcat(prodCachePath, "dyld_shared_cache_"); + strcat(prodCachePath, archStr); + strcpy(devCachePath, prodCachePath); + strcat(devCachePath, ".development"); + + verboseLog("developement cache path = %s\n", devCachePath); + verboseLog("cache path = %s\n", prodCachePath); + + // create cache dirs if needed + char neededDirs[1024]; + strcpy(neededDirs, prodCachePath); + char* lastSlash = strrchr(neededDirs, '/'); + if ( lastSlash != NULL ) + lastSlash[1] = '\0'; + struct stat stat_buf; + if ( stat(neededDirs, &stat_buf) != 0 ) { + const char* afterSlash = &neededDirs[1]; + char* slash; + while ( (slash = strchr(afterSlash, '/')) != NULL ) { + *slash = '\0'; + ::mkdir(neededDirs, S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + *slash = '/'; + afterSlash = slash+1; + } + } + + std::string dylibOrderFile = toolDir() + "/dylib-order.txt"; + std::string dirtyDataOrderFile = toolDir() + "/dirty-data-segments-order.txt"; + + std::unordered_map> dependents; + SharedCache devCache(rootPath, searchPaths, dylibListFile, archPair, dylibOrderFile, dirtyDataOrderFile); + + if ( devCache.fileSize() == 0 ) + terminate("Could not find all necessary dylibs\n"); + + devCache.buildUnoptimizedCache(); + + SharedCache prodCache = devCache; + + prodCache.optimizeForProduction(); + devCache.optimizeForDevelopment(); + + verboseLog("developement cache size = %llu", devCache.fileSize()); + verboseLog("developement cache vm size = %llu", devCache.vmSize()); + + // optimize cache + write_cache(devCachePath, devCache); + if ( devCache.vmSize()+align(devCache.vmSize()/200, sharedRegionRegionAlignment(archPair)) > sharedRegionRegionSize(archPair)) { + terminate("update_dyld_shared_cache[%u] for arch=%s, shared cache will not fit in shared regions address space. Overflow amount: %llu\n", + getpid(), archStr, devCache.vmSize()+align(devCache.vmSize()/200, sharedRegionRegionAlignment(archPair)) - sharedRegionRegionSize(archPair)); + } + + write_cache(prodCachePath, prodCache); + + return 0; +} + + +void uniqueWarning(const char* format, ...) +{ + va_list list; + va_start(list, format); + vfprintf(stderr, format, list); + va_end(list); + fprintf(stderr, "\n"); +} + +void log(const char * __restrict format, ...) +{ + va_list list; + va_start(list, format); + vfprintf(stderr, format, list); + va_end(list); + fprintf(stderr, "\n"); +} +void verboseLog(const char* format, ...) +{ + va_list list; + va_start(list, format); + vfprintf(stderr, format, list); + va_end(list); + fprintf(stderr, "\n"); +} + +void warning(const char* format, ...) +{ + fprintf(stderr, "update_dyld_shared_cache warning: "); + va_list list; + va_start(list, format); + char* msg; + vasprintf(&msg, format, list); + va_end(list); + fprintf(stderr, "%s\n", msg); + sWarnings.push_back(std::unique_ptr(msg)); +} + +void terminate(const char* format, ...) +{ + fprintf(stderr, "update_dyld_shared_cache error: "); + va_list list; + va_start(list, format); + vfprintf(stderr, format, list); + va_end(list); + exit(1); +} diff --git a/launch-cache/CacheFileAbstraction.hpp b/launch-cache/CacheFileAbstraction.hpp index c6aca25..b2dddbc 100644 --- a/launch-cache/CacheFileAbstraction.hpp +++ b/launch-cache/CacheFileAbstraction.hpp @@ -71,8 +71,26 @@ public: const uint8_t* uuid() const INLINE { return fields.uuid; } void set_uuid(const uint8_t value[16]) INLINE { memcpy(fields.uuid, value, 16); } - uint64_t cacheType() const INLINE { return E::get64(fields.cacheType); } - void set_cacheType(uint64_t value) INLINE { E::set64(fields.cacheType, value); } + uint64_t cacheType() const INLINE { return E::get64(fields.cacheType); } + void set_cacheType(uint64_t value) INLINE { E::set64(fields.cacheType, value); } + + uint32_t branchPoolsOffset() const INLINE { return E::get32(fields.branchPoolsOffset); } + void set_branchPoolsOffset(uint32_t value) INLINE { E::set32(fields.branchPoolsOffset, value); } + + uint32_t branchPoolsCount() const INLINE { return E::get32(fields.branchPoolsCount); } + void set_branchPoolsCount(uint32_t value) INLINE { E::set32(fields.branchPoolsCount, value); } + + uint64_t accelerateInfoAddr() const INLINE { return E::get64(fields.accelerateInfoAddr); } + void set_accelerateInfoAddr(uint64_t value) INLINE { E::set64(fields.accelerateInfoAddr, value); } + + uint64_t accelerateInfoSize() const INLINE { return E::get64(fields.accelerateInfoSize); } + void set_accelerateInfoSize(uint64_t value) INLINE { E::set64(fields.accelerateInfoSize, value); } + + uint64_t imagesTextOffset() const INLINE { return E::get64(fields.imagesTextOffset); } + void set_imagesTextOffset(uint64_t value) INLINE { E::set64(fields.imagesTextOffset, value); } + + uint64_t imagesTextCount() const INLINE { return E::get64(fields.imagesTextCount); } + void set_imagesTextCount(uint64_t value) INLINE { E::set64(fields.imagesTextCount, value); } private: dyld_cache_header fields; @@ -121,6 +139,159 @@ private: dyld_cache_image_info fields; }; +template +class dyldCacheImageTextInfo { +public: + const uint8_t* uuid() const INLINE { return fields.uuid; } + void set_uuid(const uint8_t value[16]) INLINE { memcpy(fields.uuid, value, 16); } + + uint64_t loadAddress() const INLINE { return E::get64(fields.loadAddress); } + void set_loadAddress(uint64_t value) INLINE { E::set64(fields.loadAddress, value); } + + uint32_t textSegmentSize() const INLINE { return E::get32(fields.textSegmentSize); } + void set_textSegmentSize(uint32_t value) INLINE { E::set32(fields.textSegmentSize, value); } + + uint32_t pathOffset() const INLINE { return E::get32(fields.pathOffset); } + void set_pathOffset(uint32_t value) INLINE { E::set32(fields.pathOffset, value); } + +private: + dyld_cache_image_text_info fields; +}; + + + +template +class dyldCacheImageInfoExtra { +public: + uint64_t exportsTrieAddr() const INLINE { return E::get64(fields.exportsTrieAddr); } + void set_exportsTrieAddr(uint64_t value) INLINE { E::set64(fields.exportsTrieAddr, value); } + + uint64_t weakBindingsAddr() const INLINE { return E::get64(fields.weakBindingsAddr); } + void set_weakBindingsAddr(uint64_t value) INLINE { E::set64(fields.weakBindingsAddr, value); } + + uint32_t exportsTrieSize() const INLINE { return E::get32(fields.exportsTrieSize); } + void set_exportsTrieSize(uint32_t value) INLINE { E::set32(fields.exportsTrieSize, value); } + + uint32_t weakBindingsSize() const INLINE { return E::get32(fields.weakBindingsSize); } + void set_weakBindingsSize(uint32_t value) INLINE { E::set32(fields.weakBindingsSize, value); } + + uint32_t dependentsStartArrayIndex() const INLINE { return E::get32(fields.dependentsStartArrayIndex); } + void set_dependentsStartArrayIndex(uint32_t value) INLINE { E::set32(fields.dependentsStartArrayIndex, value); } + + uint32_t reExportsStartArrayIndex() const INLINE { return E::get32(fields.reExportsStartArrayIndex); } + void set_reExportsStartArrayIndex(uint32_t value) INLINE { E::set32(fields.reExportsStartArrayIndex, value); } + +private: + dyld_cache_image_info_extra fields; +}; + + +template +class dyldCacheAcceleratorInfo { +public: + uint32_t version() const INLINE { return E::get32(fields.version); } + void set_version(uint32_t value) INLINE { E::set32(fields.version, value); } + + uint32_t imageExtrasCount() const INLINE { return E::get32(fields.imageExtrasCount); } + void set_imageExtrasCount(uint32_t value) INLINE { E::set32(fields.imageExtrasCount, value); } + + uint32_t imagesExtrasOffset() const INLINE { return E::get32(fields.imagesExtrasOffset); } + void set_imagesExtrasOffset(uint32_t value) INLINE { E::set32(fields.imagesExtrasOffset, value); } + + uint32_t bottomUpListOffset() const INLINE { return E::get32(fields.bottomUpListOffset); } + void set_bottomUpListOffset(uint32_t value) INLINE { E::set32(fields.bottomUpListOffset, value); } + + uint32_t dylibTrieOffset() const INLINE { return E::get32(fields.dylibTrieOffset); } + void set_dylibTrieOffset(uint32_t value) INLINE { E::set32(fields.dylibTrieOffset, value); } + + uint32_t dylibTrieSize() const INLINE { return E::get32(fields.dylibTrieSize); } + void set_dylibTrieSize(uint32_t value) INLINE { E::set32(fields.dylibTrieSize, value); } + + uint32_t initializersOffset() const INLINE { return E::get32(fields.initializersOffset); } + void set_initializersOffset(uint32_t value) INLINE { E::set32(fields.initializersOffset, value); } + + uint32_t initializersCount() const INLINE { return E::get32(fields.initializersCount); } + void set_initializersCount(uint32_t value) INLINE { E::set32(fields.initializersCount, value); } + + uint32_t dofSectionsOffset() const INLINE { return E::get32(fields.dofSectionsOffset); } + void set_dofSectionsOffset(uint32_t value) INLINE { E::set32(fields.dofSectionsOffset, value); } + + uint32_t dofSectionsCount() const INLINE { return E::get32(fields.dofSectionsCount); } + void set_dofSectionsCount(uint32_t value) INLINE { E::set32(fields.dofSectionsCount, value); } + + uint32_t reExportListOffset() const INLINE { return E::get32(fields.reExportListOffset); } + void set_reExportListOffset(uint32_t value) INLINE { E::set32(fields.reExportListOffset, value); } + + uint32_t reExportCount() const INLINE { return E::get32(fields.reExportCount); } + void set_reExportCount(uint32_t value) INLINE { E::set32(fields.reExportCount, value); } + + uint32_t depListOffset() const INLINE { return E::get32(fields.depListOffset); } + void set_depListOffset(uint32_t value) INLINE { E::set32(fields.depListOffset, value); } + + uint32_t depListCount() const INLINE { return E::get32(fields.depListCount); } + void set_depListCount(uint32_t value) INLINE { E::set32(fields.depListCount, value); } + + uint32_t rangeTableOffset() const INLINE { return E::get32(fields.rangeTableOffset); } + void set_rangeTableOffset(uint32_t value) INLINE { E::set32(fields.rangeTableOffset, value); } + + uint32_t rangeTableCount() const INLINE { return E::get32(fields.rangeTableCount); } + void set_rangeTableCount(uint32_t value) INLINE { E::set32(fields.rangeTableCount, value); } + + uint64_t dyldSectionAddr() const INLINE { return E::get64(fields.dyldSectionAddr); } + void set_dyldSectionAddr(uint64_t value) INLINE { E::set64(fields.dyldSectionAddr, value); } + + +private: + dyld_cache_accelerator_info fields; +}; + + +template +class dyldCacheAcceleratorInitializer { +public: + uint32_t functionOffset() const INLINE { return E::get32(fields.functionOffset); } + void set_functionOffset(uint32_t value) INLINE { E::set32(fields.functionOffset, value); } + + uint32_t imageIndex() const INLINE { return E::get32(fields.imageIndex); } + void set_imageIndex(uint32_t value) INLINE { E::set32(fields.imageIndex, value); } + +private: + dyld_cache_accelerator_initializer fields; +}; + + +template +class dyldCacheAcceleratorRangeEntry { +public: + uint64_t startAddress() const INLINE { return E::get64(fields.startAddress); } + void set_startAddress(uint64_t value) INLINE { E::set64(fields.startAddress, value); } + + uint32_t size() const INLINE { return E::get32(fields.size); } + void set_size(uint32_t value) INLINE { E::set32(fields.size, value); } + + uint32_t imageIndex() const INLINE { return E::get32(fields.imageIndex); } + void set_imageIndex(uint32_t value) INLINE { E::set32(fields.imageIndex, value); } + +private: + dyld_cache_range_entry fields; +}; + +template +class dyldCacheAcceleratorDOFEntry { +public: + uint64_t sectionAddress() const INLINE { return E::get64(fields.sectionAddress); } + void set_sectionAddress(uint64_t value) INLINE { E::set64(fields.sectionAddress, value); } + + uint32_t sectionSize() const INLINE { return E::get32(fields.sectionSize); } + void set_sectionSize(uint32_t value) INLINE { E::set32(fields.sectionSize, value); } + + uint32_t imageIndex() const INLINE { return E::get32(fields.imageIndex); } + void set_imageIndex(uint32_t value) INLINE { E::set32(fields.imageIndex, value); } + +private: + dyld_cache_accelerator_dof fields; +}; + template class dyldCacheSlideInfo { public: @@ -155,6 +326,45 @@ struct dyldCacheSlideInfoEntry { }; +template +class dyldCacheSlideInfo2 { +public: + uint32_t version() const INLINE { return E::get32(fields.version); } + void set_version(uint32_t value) INLINE { E::set32(fields.version, value); } + + uint32_t page_starts_offset() const INLINE { return E::get32(fields.page_starts_offset); } + void set_page_starts_offset(uint32_t value) INLINE { E::set32(fields.page_starts_offset, value); } + + uint32_t page_starts_count() const INLINE { return E::get32(fields.page_starts_count); } + void set_page_starts_count(uint32_t value) INLINE { E::set32(fields.page_starts_count, value); } + + uint32_t page_extras_offset() const INLINE { return E::get32(fields.page_extras_offset); } + void set_page_extras_offset(uint32_t value) INLINE { E::set32(fields.page_extras_offset, value); } + + uint32_t page_extras_count() const INLINE { return E::get32(fields.page_extras_count); } + void set_page_extras_count(uint32_t value) INLINE { E::set32(fields.page_extras_count, value); } + + uint32_t page_size() const INLINE { return E::get32(fields.page_size); } + void set_page_size(uint32_t value) INLINE { E::set32(fields.page_size, value); } + + uint64_t delta_mask() const INLINE { return E::get64(fields.delta_mask); } + void set_delta_mask(uint64_t value) INLINE { E::set64(fields.delta_mask, value); } + + uint64_t value_add() const INLINE { return E::get64(fields.value_add); } + void set_value_add(uint64_t value) INLINE { E::set64(fields.value_add, value); } + + uint16_t page_starts(unsigned index) const INLINE { return E::get16(((uint16_t*)(((uint8_t*)this)+E::get16(fields.page_starts_offset)))[index]); } + void set_page_starts(unsigned index, uint16_t value) INLINE { return E::set16(((uint16_t*)(((uint8_t*)this)+E::get16(fields.page_starts_offset)))[index], value); } + + uint16_t page_extras(unsigned index) const INLINE { return E::get16(((uint16_t*)(((uint8_t*)this)+E::get16(fields.page_extras_offset)))[index]); } + void set_page_extras(unsigned index, uint16_t value) INLINE { return E::set16(((uint16_t*)(((uint8_t*)this)+E::get16(fields.page_extras_offset)))[index], value); } + + +private: + dyld_cache_slide_info2 fields; +}; + + template class dyldCacheLocalSymbolsInfo { diff --git a/launch-cache/MachOFileAbstraction.hpp b/launch-cache/MachOFileAbstraction.hpp index 4cda3d3..f92a95b 100644 --- a/launch-cache/MachOFileAbstraction.hpp +++ b/launch-cache/MachOFileAbstraction.hpp @@ -114,6 +114,7 @@ struct uuid_command { #define DYLD_CACHE_ADJ_V2_THUMB_BR22 0x0B #define DYLD_CACHE_ADJ_V2_IMAGE_OFF_32 0x0C +#define MH_HAS_OBJC 0x40000000 #include "FileAbstraction.hpp" #include "Architectures.hpp" @@ -131,6 +132,10 @@ struct ArchPair return (this->arch < other.arch); return (this->subtype < other.subtype); } + + bool operator==(const ArchPair& other) const { + return this->arch == other.arch && this->subtype == other.subtype; + } }; @@ -933,7 +938,7 @@ inline uint64_t read_uleb128(const uint8_t*& p, const uint8_t* end) { } -static int64_t read_sleb128(const uint8_t*& p, const uint8_t* end) +inline int64_t read_sleb128(const uint8_t*& p, const uint8_t* end) { int64_t result = 0; int bit = 0; diff --git a/launch-cache/MachOLayout.hpp b/launch-cache/MachOLayout.hpp index c799e30..49f593e 100644 --- a/launch-cache/MachOLayout.hpp +++ b/launch-cache/MachOLayout.hpp @@ -38,7 +38,6 @@ #include #include #include -#include #include #include @@ -141,7 +140,6 @@ public: virtual bool isSplitSeg() const = 0; virtual bool hasSplitSegInfo() const = 0; virtual bool hasSplitSegInfoV2() const = 0; - virtual int notTrusted() const = 0; virtual bool inSharableLocation() const = 0; virtual bool hasDynamicLookupLinkage() const = 0; virtual bool hasMainExecutableLookupLinkage() const = 0; @@ -190,7 +188,6 @@ public: virtual bool isSplitSeg() const; virtual bool hasSplitSegInfo() const { return fSplitSegInfo != NULL; } virtual bool hasSplitSegInfoV2() const{ return fHasSplitSegInfoV2; } - virtual int notTrusted() const { return fRootlessErrno; } virtual bool inSharableLocation() const { return fShareableLocation; } virtual bool hasDynamicLookupLinkage() const { return fDynamicLookupLinkage; } virtual bool hasMainExecutableLookupLinkage() const { return fMainExecutableLookupLinkage; } @@ -252,7 +249,6 @@ private: uint64_t fVMReadOnlySize; const macho_linkedit_data_command

* fSplitSegInfo; bool fHasSplitSegInfoV2; - int fRootlessErrno; bool fShareableLocation; bool fDynamicLookupLinkage; bool fMainExecutableLookupLinkage; @@ -556,7 +552,7 @@ uint64_t MachOLayout::sectionsAlignment(const macho_segment_command MachOLayout::MachOLayout(const void* machHeader, uint64_t offset, const char* path, ino_t inode, time_t modTime, uid_t uid) - : fPath(path), fOffset(offset), fArchPair(0,0), fMTime(modTime), fInode(inode), fSplitSegInfo(NULL), fHasSplitSegInfoV2(false), fRootlessErrno(0), + : fPath(path), fOffset(offset), fArchPair(0,0), fMTime(modTime), fInode(inode), fSplitSegInfo(NULL), fHasSplitSegInfoV2(false), fShareableLocation(false), fDynamicLookupLinkage(false), fMainExecutableLookupLinkage(false), fIsDylib(false), fHasDyldInfo(false), fHasTooManyWritableSegments(false), fDyldInfoExports(NULL) { @@ -584,8 +580,6 @@ MachOLayout::MachOLayout(const void* machHeader, uint64_t offset, const char* fFileType = mh->filetype(); fArchPair.arch = mh->cputype(); fArchPair.subtype = mh->cpusubtype(); - if ( rootless_check_trusted(path) != 0 && rootless_protected_volume(path) == 1) - fRootlessErrno = errno; const macho_dyld_info_command

* dyldInfo = NULL; const macho_symtab_command

* symbolTableCmd = NULL; diff --git a/launch-cache/MachORebaser.hpp b/launch-cache/MachORebaser.hpp index 21a4634..464220a 100644 --- a/launch-cache/MachORebaser.hpp +++ b/launch-cache/MachORebaser.hpp @@ -943,6 +943,8 @@ void Rebaser::adjustReference(uint32_t kind, uint8_t* mappedAddr, uint64_t pointersInData.push_back( mappedAddr); break; case DYLD_CACHE_ADJ_V2_IMAGE_OFF_32: + if ( adjust == 0 ) + break; mappedAddr32 = (uint32_t*)mappedAddr; value32 = (uint32_t)(toNewAddress - imageStartAddress); E::set32(*mappedAddr32, value32); diff --git a/launch-cache/MachOTrie.hpp b/launch-cache/MachOTrie.hpp index 7720b11..d2f137e 100644 --- a/launch-cache/MachOTrie.hpp +++ b/launch-cache/MachOTrie.hpp @@ -62,14 +62,14 @@ struct Node const char* partialStr = &fullStr[strlen(fCummulativeString)]; for (std::vector::iterator it = fChildren.begin(); it != fChildren.end(); ++it) { Edge& e = *it; - int subStringLen = strlen(e.fSubString); + long subStringLen = strlen(e.fSubString); if ( strncmp(e.fSubString, partialStr, subStringLen) == 0 ) { // already have matching edge, go down that path e.fChild->addSymbol(fullStr, address, flags, other, importName); return; } else { - for (int i=subStringLen-1; i > 0; --i) { + for (long i=subStringLen-1; i > 0; --i) { if ( strncmp(e.fSubString, partialStr, i) == 0 ) { // found a common substring, splice in new node // was A -> C, now A -> B -> C @@ -116,7 +116,7 @@ struct Node const char* partialStr = &name[strlen(fCummulativeString)]; for (std::vector::iterator it = fChildren.begin(); it != fChildren.end(); ++it) { Edge& e = *it; - int subStringLen = strlen(e.fSubString); + long subStringLen = strlen(e.fSubString); if ( strncmp(e.fSubString, partialStr, subStringLen) == 0 ) { // already have matching edge, go down that path e.fChild->addOrderedNodes(name, orderedNodes); @@ -165,7 +165,7 @@ struct Node if ( fFlags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { if ( fImportedName != NULL ) { // nodes with re-export info: size, flags, ordinal, string - uint32_t nodeSize = uleb128_size(fFlags) + uleb128_size(fOther) + strlen(fImportedName) + 1; + uint32_t nodeSize = (uint32_t)(uleb128_size(fFlags) + uleb128_size(fOther) + strlen(fImportedName) + 1); out.push_back(nodeSize); append_uleb128(fFlags, out); append_uleb128(fOther, out); @@ -365,7 +365,7 @@ static inline void processExportNode(const uint8_t* const start, const uint8_t* ++edgeStrLen; } cummulativeString[curStrOffset+edgeStrLen] = *s++; - uint32_t childNodeOffet = read_uleb128(s, end); + uint32_t childNodeOffet = (uint32_t)read_uleb128(s, end); processExportNode(start, start+childNodeOffet, end, cummulativeString, curStrOffset+edgeStrLen, output); } } @@ -376,7 +376,7 @@ inline void parseTrie(const uint8_t* start, const uint8_t* end, std::vector entries; processExportNode(start, start, end, cummulativeString, 0, entries); // to preserve tie layout order, sort by node offset diff --git a/launch-cache/ObjCModernAbstraction.hpp b/launch-cache/ObjCModernAbstraction.hpp deleted file mode 100644 index dfa332a..0000000 --- a/launch-cache/ObjCModernAbstraction.hpp +++ /dev/null @@ -1,1234 +0,0 @@ -/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- - * - * Copyright (c) 2008-2010 Apple Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#include "MachOLayout.hpp" -#include -#include - -// iterate an entsize-based list -// typedef entsize_iterator< A, type_t, type_list_t > type_iterator; -template -struct entsize_iterator { - uint32_t entsize; - uint32_t index; // keeping track of this saves a divide in operator- - T* current; - - typedef std::random_access_iterator_tag iterator_category; - typedef T value_type; - typedef ptrdiff_t difference_type; - typedef T* pointer; - typedef T& reference; - - entsize_iterator() { } - - entsize_iterator(const Tlist& list, uint32_t start = 0) - : entsize(list.getEntsize()), index(start), current(&list.get(start)) - { } - - const entsize_iterator& operator += (ptrdiff_t count) { - current = (T*)((uint8_t *)current + count*entsize); - index += count; - return *this; - } - const entsize_iterator& operator -= (ptrdiff_t count) { - current = (T*)((uint8_t *)current - count*entsize); - index -= count; - return *this; - } - const entsize_iterator operator + (ptrdiff_t count) const { - return entsize_iterator(*this) += count; - } - const entsize_iterator operator - (ptrdiff_t count) const { - return entsize_iterator(*this) -= count; - } - - entsize_iterator& operator ++ () { *this += 1; return *this; } - entsize_iterator& operator -- () { *this -= 1; return *this; } - entsize_iterator operator ++ (int) { - entsize_iterator result(*this); *this += 1; return result; - } - entsize_iterator operator -- (int) { - entsize_iterator result(*this); *this -= 1; return result; - } - - ptrdiff_t operator - (const entsize_iterator& rhs) const { - return (ptrdiff_t)this->index - (ptrdiff_t)rhs.index; - } - - T& operator * () { return *current; } - T& operator * () const { return *current; } - T& operator -> () { return *current; } - const T& operator -> () const { return *current; } - - operator T& () const { return *current; } - - bool operator == (const entsize_iterator& rhs) { - return this->current == rhs.current; - } - bool operator != (const entsize_iterator& rhs) { - return this->current != rhs.current; - } - - bool operator < (const entsize_iterator& rhs) { - return this->current < rhs.current; - } - bool operator > (const entsize_iterator& rhs) { - return this->current > rhs.current; - } -}; - -template -class objc_header_info_t { - - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - pint_t next; // objc_header_info * - pint_t mhdr; // mach_header or mach_header_64 - pint_t info; // objc_image_info * - pint_t fname; // const char * - bool loaded; - bool inSharedCache; - bool allClassesRealized; - -public: - objc_header_info_t(SharedCache* cache, const macho_header

* mh) - : next(0), - mhdr(0), - info(0), - fname(0), - loaded(0), - allClassesRealized(0) - { - A::P::setP(mhdr, cache->VMAddressForMappedAddress(mh)); - const macho_section

* sect = mh->getSection("__DATA", "__objc_imageinfo"); - if (sect) A::P::setP(info, sect->addr()); - - // can't set fname because dyld sometimes edits it - } - - void addPointers(std::vector& pointersToAdd) { - pointersToAdd.push_back(&mhdr); - if (info) pointersToAdd.push_back(&info); - } - - uint64_t header_vmaddr() const { return mhdr; } -}; - -template class objc_method_list_t; // forward reference - -template -class objc_method_t { - typename A::P::uint_t name; // SEL - typename A::P::uint_t types; // const char * - typename A::P::uint_t imp; // IMP - friend class objc_method_list_t; -public: - typename A::P::uint_t getName() const { return A::P::getP(name); } - void setName(typename A::P::uint_t newName) { A::P::setP(name, newName); } - - struct SortBySELAddress : - public std::binary_function&, - const objc_method_t&, bool> - { - bool operator() (const objc_method_t& lhs, - const objc_method_t& rhs) - { - return lhs.getName() < rhs.getName(); - } - }; -}; - -template -class objc_method_list_t { - uint32_t entsize; - uint32_t count; - objc_method_t first; - - void* operator new (size_t, void* buf) { return buf; } - -public: - - typedef entsize_iterator< A, objc_method_t, objc_method_list_t > method_iterator; - - uint32_t getCount() const { return A::P::E::get32(count); } - - uint32_t getEntsize() const {return A::P::E::get32(entsize)&~(uint32_t)3;} - - objc_method_t& get(uint32_t i) const { return *(objc_method_t *)((uint8_t *)&first + i * getEntsize()); } - - uint32_t byteSize() const { - return byteSizeForCount(getCount(), getEntsize()); - } - - static uint32_t byteSizeForCount(uint32_t c, uint32_t e = sizeof(objc_method_t)) { - return sizeof(objc_method_list_t) - sizeof(objc_method_t) + c*e; - } - - method_iterator begin() { return method_iterator(*this, 0); } - method_iterator end() { return method_iterator(*this, getCount()); } - const method_iterator begin() const { return method_iterator(*this, 0); } - const method_iterator end() const { return method_iterator(*this, getCount()); } - - void setFixedUp() { A::P::E::set32(entsize, getEntsize() | 3); } - - void getPointers(std::set& pointersToRemove) { - for(method_iterator it = begin(); it != end(); ++it) { - objc_method_t& entry = *it; - pointersToRemove.insert(&(entry.name)); - pointersToRemove.insert(&(entry.types)); - pointersToRemove.insert(&(entry.imp)); - } - } - - static void addPointers(uint8_t* methodList, std::vector& pointersToAdd) { - objc_method_list_t* mlist = (objc_method_list_t*)methodList; - for(method_iterator it = mlist->begin(); it != mlist->end(); ++it) { - objc_method_t& entry = *it; - pointersToAdd.push_back(&(entry.name)); - pointersToAdd.push_back(&(entry.types)); - pointersToAdd.push_back(&(entry.imp)); - } - } - - static objc_method_list_t* newMethodList(size_t newCount, uint32_t newEntsize) { - void *buf = ::calloc(byteSizeForCount(newCount, newEntsize), 1); - return new (buf) objc_method_list_t(newCount, newEntsize); - } - - void operator delete(void * p) { - ::free(p); - } - - objc_method_list_t(uint32_t newCount, - uint32_t newEntsize = sizeof(objc_method_t)) - : entsize(newEntsize), count(newCount) - { } - -private: - // use newMethodList instead - void* operator new (size_t); -}; - - -// Ivar offset variables are 64-bit on x86_64 and 32-bit everywhere else. - -template -class objc_ivar_offset_t { - typedef typename A::P::uint_t pint_t; - typename A::P::uint_t ptr; // uint32_t * - - uint32_t& offset(SharedCache *cache) const { return *(uint32_t *)cache->mappedAddressForVMAddress(A::P::getP(ptr)); } - -public: - bool hasOffset() const { return A::P::getP(ptr) != 0; } - pint_t getOffset(SharedCache *cache) const { return A::P::E::get32(offset(cache)); } - void setOffset(SharedCache *cache, pint_t newOffset) { A::P::E::set32(offset(cache), newOffset); } -}; - -template <> -class objc_ivar_offset_t { - typedef x86_64 A; - typedef typename A::P::uint_t pint_t; - typename A::P::uint_t ptr; // uint64_t * - - uint64_t& offset(SharedCache *cache) const { return *(uint64_t *)cache->mappedAddressForVMAddress(A::P::getP(ptr)); } - -public: - bool hasOffset() const { return A::P::getP(ptr) != 0; } - pint_t getOffset(SharedCache *cache) const { return A::P::E::get64(offset(cache)); } - void setOffset(SharedCache *cache, pint_t newOffset) { A::P::E::set64(offset(cache), newOffset); } -}; - -template -class objc_ivar_t { - typedef typename A::P::uint_t pint_t; - objc_ivar_offset_t offset; // uint32_t * (uint64_t * on x86_64) - typename A::P::uint_t name; // const char * - typename A::P::uint_t type; // const char * - uint32_t alignment; - uint32_t size; - -public: - const char * getName(SharedCache *cache) const { return (const char *)cache->mappedAddressForVMAddress(A::P::getP(name)); } - - bool hasOffset() const { return offset.hasOffset(); } - pint_t getOffset(SharedCache *cache) const { return offset.getOffset(cache); } - void setOffset(SharedCache *cache, pint_t newOffset) { offset.setOffset(cache, newOffset); } - - uint32_t getAlignment() - { - uint32_t a = A::P::E::get32(alignment); - return a == (uint32_t)-1 ? sizeof(typename A::P::uint_t) : 1< -class objc_ivar_list_t { - typedef typename A::P::uint_t pint_t; - uint32_t entsize; - uint32_t count; - objc_ivar_t first; - - void* operator new (size_t, void* buf) { return buf; } - -public: - - typedef entsize_iterator< A, objc_ivar_t, objc_ivar_list_t > ivar_iterator; - - uint32_t getCount() const { return A::P::E::get32(count); } - - uint32_t getEntsize() const { return A::P::E::get32(entsize); } - - objc_ivar_t& get(pint_t i) const { return *(objc_ivar_t *)((uint8_t *)&first + i * A::P::E::get32(entsize)); } - - uint32_t byteSize() const { - return byteSizeForCount(getCount(), getEntsize()); - } - - static uint32_t byteSizeForCount(uint32_t c, uint32_t e = sizeof(objc_ivar_t)) { - return sizeof(objc_ivar_list_t) - sizeof(objc_ivar_t) + c*e; - } - - ivar_iterator begin() { return ivar_iterator(*this, 0); } - ivar_iterator end() { return ivar_iterator(*this, getCount()); } - const ivar_iterator begin() const { return ivar_iterator(*this, 0); } - const ivar_iterator end() const { return ivar_iterator(*this, getCount()); } - - static objc_ivar_list_t* newIvarList(size_t newCount, uint32_t newEntsize) { - void *buf = ::calloc(byteSizeForCount(newCount, newEntsize), 1); - return new (buf) objc_ivar_list_t(newCount, newEntsize); - } - - void operator delete(void * p) { - ::free(p); - } - - objc_ivar_list_t(uint32_t newCount, - uint32_t newEntsize = sizeof(objc_ivar_t)) - : entsize(newEntsize), count(newCount) - { } -private: - // use newIvarList instead - void* operator new (size_t); -}; - - -template class objc_property_list_t; // forward - -template -class objc_property_t { - typename A::P::uint_t name; - typename A::P::uint_t attributes; - friend class objc_property_list_t; -public: - - const char * getName(SharedCache* cache) const { return (const char *)cache->mappedAddressForVMAddress(A::P::getP(name)); } - - const char * getAttributes(SharedCache* cache) const { return (const char *)cache->mappedAddressForVMAddress(A::P::getP(attributes)); } -}; - -template -class objc_property_list_t { - uint32_t entsize; - uint32_t count; - objc_property_t first; - - void* operator new (size_t, void* buf) { return buf; } - -public: - - typedef entsize_iterator< A, objc_property_t, objc_property_list_t > property_iterator; - - uint32_t getCount() const { return A::P::E::get32(count); } - - uint32_t getEntsize() const { return A::P::E::get32(entsize); } - - objc_property_t& get(uint32_t i) const { return *(objc_property_t *)((uint8_t *)&first + i * getEntsize()); } - - uint32_t byteSize() const { - return byteSizeForCount(getCount(), getEntsize()); - } - - static uint32_t byteSizeForCount(uint32_t c, uint32_t e = sizeof(objc_property_t)) { - return sizeof(objc_property_list_t) - sizeof(objc_property_t) + c*e; - } - - property_iterator begin() { return property_iterator(*this, 0); } - property_iterator end() { return property_iterator(*this, getCount()); } - const property_iterator begin() const { return property_iterator(*this, 0); } - const property_iterator end() const { return property_iterator(*this, getCount()); } - - void getPointers(std::set& pointersToRemove) { - for(property_iterator it = begin(); it != end(); ++it) { - objc_property_t& entry = *it; - pointersToRemove.insert(&(entry.name)); - pointersToRemove.insert(&(entry.attributes)); - } - } - - static void addPointers(uint8_t* propertyList, std::vector& pointersToAdd) { - objc_property_list_t* plist = (objc_property_list_t*)propertyList; - for(property_iterator it = plist->begin(); it != plist->end(); ++it) { - objc_property_t& entry = *it; - pointersToAdd.push_back(&(entry.name)); - pointersToAdd.push_back(&(entry.attributes)); - } - } - - static objc_property_list_t* newPropertyList(size_t newCount, uint32_t newEntsize) { - void *buf = ::calloc(byteSizeForCount(newCount, newEntsize), 1); - return new (buf) objc_property_list_t(newCount, newEntsize); - } - - void operator delete(void * p) { - ::free(p); - } - - objc_property_list_t(uint32_t newCount, - uint32_t newEntsize = sizeof(objc_property_t)) - : entsize(newEntsize), count(newCount) - { } -private: - // use newPropertyList instead - void* operator new (size_t); -}; - - -template class objc_protocol_list_t; // forward reference - -template -class objc_protocol_t { - typedef typename A::P::uint_t pint_t; - - pint_t isa; - pint_t name; - pint_t protocols; - pint_t instanceMethods; - pint_t classMethods; - pint_t optionalInstanceMethods; - pint_t optionalClassMethods; - pint_t instanceProperties; - uint32_t size; - uint32_t flags; - pint_t extendedMethodTypes; - pint_t demangledName; - -public: - pint_t getIsaVMAddr() const { return A::P::getP(isa); } - pint_t setIsaVMAddr(pint_t newIsa) { A::P::setP(isa, newIsa); } - - const char *getName(SharedCache* cache) const { return (const char *)cache->mappedAddressForVMAddress(A::P::getP(name)); } - - uint32_t getSize() const { return A::P::E::get32(size); } - void setSize(uint32_t newSize) { A::P::E::set32(size, newSize); } - - uint32_t getFlags() const { return A::P::E::get32(flags); } - - void setFixedUp() { A::P::E::set32(flags, getFlags() | (1<<30)); } - - objc_protocol_list_t *getProtocols(SharedCache* cache) const { return (objc_protocol_list_t *)cache->mappedAddressForVMAddress(A::P::getP(protocols)); } - - objc_method_list_t *getInstanceMethods(SharedCache* cache) const { return (objc_method_list_t *)cache->mappedAddressForVMAddress(A::P::getP(instanceMethods)); } - - objc_method_list_t *getClassMethods(SharedCache* cache) const { return (objc_method_list_t *)cache->mappedAddressForVMAddress(A::P::getP(classMethods)); } - - objc_method_list_t *getOptionalInstanceMethods(SharedCache* cache) const { return (objc_method_list_t *)cache->mappedAddressForVMAddress(A::P::getP(optionalInstanceMethods)); } - - objc_method_list_t *getOptionalClassMethods(SharedCache* cache) const { return (objc_method_list_t *)cache->mappedAddressForVMAddress(A::P::getP(optionalClassMethods)); } - - objc_property_list_t *getInstanceProperties(SharedCache* cache) const { return (objc_property_list_t *)cache->mappedAddressForVMAddress(A::P::getP(instanceProperties)); } - - pint_t *getExtendedMethodTypes(SharedCache* cache) const { - if (getSize() < offsetof(objc_protocol_t, extendedMethodTypes) + sizeof(extendedMethodTypes)) { - return NULL; - } - return (pint_t *)cache->mappedAddressForVMAddress(A::P::getP(extendedMethodTypes)); - } - - const char *getDemangledName(SharedCache* cache) const { - if (sizeof(*this) < offsetof(objc_protocol_t, demangledName) + sizeof(demangledName)) { - return NULL; - } - return (const char *)cache->mappedAddressForVMAddress(A::P::getP(demangledName)); - } - - void setDemangledName(SharedCache* cache, const char *newName) { - if (sizeof(*this) < offsetof(objc_protocol_t, demangledName) + sizeof(demangledName)) { - throw "objc protocol has the wrong size"; - } - A::P::setP(demangledName, cache->VMAddressForMappedAddress(newName)); - } - - void addPointers(std::vector& pointersToAdd) - { - pointersToAdd.push_back(&isa); - pointersToAdd.push_back(&name); - if (protocols) pointersToAdd.push_back(&protocols); - if (instanceMethods) pointersToAdd.push_back(&instanceMethods); - if (classMethods) pointersToAdd.push_back(&classMethods); - if (optionalInstanceMethods) pointersToAdd.push_back(&optionalInstanceMethods); - if (optionalClassMethods) pointersToAdd.push_back(&optionalClassMethods); - if (instanceProperties) pointersToAdd.push_back(&instanceProperties); - if (extendedMethodTypes) pointersToAdd.push_back(&extendedMethodTypes); - if (demangledName) pointersToAdd.push_back(&demangledName); - } -}; - -template -class objc_protocol_list_t { - typedef typename A::P::uint_t pint_t; - pint_t count; - pint_t list[0]; - - void* operator new (size_t, void* buf) { return buf; } - -public: - - pint_t getCount() const { return A::P::getP(count); } - - pint_t getVMAddress(pint_t i) { - return A::P::getP(list[i]); - } - - objc_protocol_t* get(SharedCache* cache, pint_t i) { - return (objc_protocol_t*)cache->mappedAddressForVMAddress(getVMAddress(i)); - } - - void setVMAddress(pint_t i, pint_t protoVMAddr) { - A::P::setP(list[i], protoVMAddr); - } - - void set(SharedCache* cache, pint_t i, objc_protocol_t* proto) { - setVMAddress(i, cache->VMAddressForMappedAddress(proto)); - } - - uint32_t byteSize() const { - return byteSizeForCount(getCount()); - } - static uint32_t byteSizeForCount(pint_t c) { - return sizeof(objc_protocol_list_t) + c*sizeof(pint_t); - } - - void getPointers(std::set& pointersToRemove) { - for(int i=0 ; i < count; ++i) { - pointersToRemove.insert(&list[i]); - } - } - - static void addPointers(uint8_t* protocolList, std::vector& pointersToAdd) { - objc_protocol_list_t* plist = (objc_protocol_list_t*)protocolList; - for(int i=0 ; i < plist->count; ++i) { - pointersToAdd.push_back(&plist->list[i]); - } - } - - static objc_protocol_list_t* newProtocolList(pint_t newCount) { - void *buf = ::calloc(byteSizeForCount(newCount), 1); - return new (buf) objc_protocol_list_t(newCount); - } - - void operator delete(void * p) { - ::free(p); - } - - objc_protocol_list_t(uint32_t newCount) : count(newCount) { } -private: - // use newProtocolList instead - void* operator new (size_t); -}; - - -template -class objc_class_data_t { - uint32_t flags; - uint32_t instanceStart; - // Note there is 4-bytes of alignment padding between instanceSize and ivarLayout - // on 64-bit archs, but no padding on 32-bit archs. - // This union is a way to model that. - union { - uint32_t instanceSize; - typename A::P::uint_t pad; - } instanceSize; - typename A::P::uint_t ivarLayout; - typename A::P::uint_t name; - typename A::P::uint_t baseMethods; - typename A::P::uint_t baseProtocols; - typename A::P::uint_t ivars; - typename A::P::uint_t weakIvarLayout; - typename A::P::uint_t baseProperties; - -public: - bool isMetaClass() { return A::P::E::get32(flags) & 1; } - - uint32_t getInstanceStart() { return A::P::E::get32(instanceStart); } - void setInstanceStart(uint32_t newStart) { A::P::E::set32(instanceStart, newStart); } - - uint32_t getInstanceSize() { return A::P::E::get32(instanceSize.instanceSize); } - void setInstanceSize(uint32_t newSiz) { A::P::E::set32(instanceSize.instanceSize, newSiz); } - - objc_method_list_t *getMethodList(SharedCache* cache) const { return (objc_method_list_t *)cache->mappedAddressForVMAddress(A::P::getP(baseMethods)); } - - objc_protocol_list_t *getProtocolList(SharedCache* cache) const { return (objc_protocol_list_t *)cache->mappedAddressForVMAddress(A::P::getP(baseProtocols)); } - - objc_ivar_list_t *getIvarList(SharedCache* cache) const { return (objc_ivar_list_t *)cache->mappedAddressForVMAddress(A::P::getP(ivars)); } - - objc_property_list_t *getPropertyList(SharedCache* cache) const { return (objc_property_list_t *)cache->mappedAddressForVMAddress(A::P::getP(baseProperties)); } - - const char * getName(SharedCache* cache) const { return (const char *)cache->mappedAddressForVMAddress(A::P::getP(name)); } - - void setMethodList(SharedCache* cache, objc_method_list_t* mlist) { - A::P::setP(baseMethods, cache->VMAddressForMappedAddress(mlist)); - } - - void setProtocolList(SharedCache* cache, objc_protocol_list_t* protolist) { - A::P::setP(baseProtocols, cache->VMAddressForMappedAddress(protolist)); - } - - void setPropertyList(SharedCache* cache, objc_property_list_t* proplist) { - A::P::setP(baseProperties, cache->VMAddressForMappedAddress(proplist)); - } - - void addMethodListPointer(std::vector& pointersToAdd) { - pointersToAdd.push_back(&this->baseMethods); - } - - void addPropertyListPointer(std::vector& pointersToAdd) { - pointersToAdd.push_back(&this->baseProperties); - } - - void addProtocolListPointer(std::vector& pointersToAdd) { - pointersToAdd.push_back(&this->baseProtocols); - } -}; - -template -class objc_class_t { - typename A::P::uint_t isa; - typename A::P::uint_t superclass; - typename A::P::uint_t method_cache; - typename A::P::uint_t vtable; - typename A::P::uint_t data; - -public: - bool isMetaClass(SharedCache* cache) const { return getData(cache)->isMetaClass(); } - - objc_class_t *getIsa(SharedCache *cache) const { return (objc_class_t *)cache->mappedAddressForVMAddress(A::P::getP(isa)); } - - objc_class_t *getSuperclass(SharedCache *cache) const { return (objc_class_t *)cache->mappedAddressForVMAddress(A::P::getP(superclass)); } - - objc_class_data_t *getData(SharedCache* cache) const { return (objc_class_data_t *)cache->mappedAddressForVMAddress(A::P::getP(data)); } - - objc_method_list_t *getMethodList(SharedCache* cache) const { return getData(cache)->getMethodList(cache); } - - objc_protocol_list_t *getProtocolList(SharedCache* cache) const { return getData(cache)->getProtocolList(cache); } - - objc_property_list_t *getPropertyList(SharedCache* cache) const { return getData(cache)->getPropertyList(cache); } - - const char * getName(SharedCache* cache) const { - return getData(cache)->getName(cache); - } - - void setMethodList(SharedCache* cache, objc_method_list_t* mlist) { - getData(cache)->setMethodList(cache, mlist); - } - - void setProtocolList(SharedCache* cache, objc_protocol_list_t* protolist) { - getData(cache)->setProtocolList(cache, protolist); - } - - void setPropertyList(SharedCache* cache, objc_property_list_t* proplist) { - getData(cache)->setPropertyList(cache, proplist); - } - - void addMethodListPointer(SharedCache* cache, std::vector& pointersToAdd) { - getData(cache)->addMethodListPointer(pointersToAdd); - } - - void addPropertyListPointer(SharedCache* cache, std::vector& pointersToAdd) { - getData(cache)->addPropertyListPointer(pointersToAdd); - } - - void addProtocolListPointer(SharedCache* cache, std::vector& pointersToAdd) { - getData(cache)->addProtocolListPointer(pointersToAdd); - } - -}; - - - -template -class objc_category_t { - typename A::P::uint_t name; - typename A::P::uint_t cls; - typename A::P::uint_t instanceMethods; - typename A::P::uint_t classMethods; - typename A::P::uint_t protocols; - typename A::P::uint_t instanceProperties; - -public: - - const char * getName(SharedCache *cache) const { return (const char *)cache->mappedAddressForVMAddress(A::P::getP(name)); } - - objc_class_t *getClass(SharedCache *cache) const { return (objc_class_t *)cache->mappedAddressForVMAddress(A::P::getP(cls)); } - - objc_method_list_t *getInstanceMethods(SharedCache* cache) const { return (objc_method_list_t *)cache->mappedAddressForVMAddress(A::P::getP(instanceMethods)); } - - objc_method_list_t *getClassMethods(SharedCache* cache) const { return (objc_method_list_t *)cache->mappedAddressForVMAddress(A::P::getP(classMethods)); } - - objc_protocol_list_t *getProtocols(SharedCache* cache) const { return (objc_protocol_list_t *)cache->mappedAddressForVMAddress(A::P::getP(protocols)); } - - objc_property_list_t *getInstanceProperties(SharedCache* cache) const { return (objc_property_list_t *)cache->mappedAddressForVMAddress(A::P::getP(instanceProperties)); } - - void getPointers(std::set& pointersToRemove) { - pointersToRemove.insert(&name); - pointersToRemove.insert(&cls); - pointersToRemove.insert(&instanceMethods); - pointersToRemove.insert(&classMethods); - pointersToRemove.insert(&protocols); - pointersToRemove.insert(&instanceProperties); - } - - -}; - -template -class objc_message_ref_t { - typename A::P::uint_t imp; - typename A::P::uint_t sel; - -public: - typename A::P::uint_t getName() const { return A::P::getP(sel); } - - void setName(typename A::P::uint_t newName) { A::P::setP(sel, newName); } -}; - -// Call visitor.visitIvar() on every ivar in a given class. -template -class IvarWalker { - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - V& ivarVisitor; -public: - - IvarWalker(V& visitor) : ivarVisitor(visitor) { } - - void walk(SharedCache* cache, const macho_header

* header, objc_class_t *cls) - { - objc_class_data_t *data = cls->getData(cache); - objc_ivar_list_t *ivars = data->getIvarList(cache); - if (ivars) { - for (pint_t i = 0; i < ivars->getCount(); i++) { - objc_ivar_t& ivar = ivars->get(i); - //fprintf(stderr, "visiting ivar: %s\n", ivar.getName(cache)); - ivarVisitor.visitIvar(cache, header, cls, &ivar); - } - } else { - //fprintf(stderr, "no ivars\n"); - } - } - - void visitClass(SharedCache* cache, const macho_header

* header, objc_class_t *cls) - { - walk(cache, header, cls); - } -}; - -// Call visitor.visitClass() on every class. -template -class ClassWalker { - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - V& classVisitor; -public: - - ClassWalker(V& visitor) : classVisitor(visitor) { } - - void walk(SharedCache* cache, const macho_header

* header) - { - PointerSection *> - classes(cache, header, "__DATA", "__objc_classlist"); - - for (pint_t i = 0; i < classes.count(); i++) { - objc_class_t *cls = classes.get(i); - //fprintf(stderr, "visiting class: %s\n", cls->getName(cache)); - if (cls) classVisitor.visitClass(cache, header, cls); - } - } -}; - - -// Call visitor.visitProtocol() on every protocol. -template -class ProtocolWalker { - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - V& protocolVisitor; -public: - - ProtocolWalker(V& visitor) : protocolVisitor(visitor) { } - - void walk(SharedCache* cache, const macho_header

* header) - { - PointerSection *> - protocols(cache, header, "__DATA", "__objc_protolist"); - - for (pint_t i = 0; i < protocols.count(); i++) { - objc_protocol_t *proto = protocols.get(i); - protocolVisitor.visitProtocol(cache, header, proto); - } - } -}; - - -// Call visitor.visitProtocolReference() on every protocol. -template -class ProtocolReferenceWalker { - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - V& mVisitor; - - void visitProtocolList(SharedCache* cache, - objc_protocol_list_t* protolist) - { - if (!protolist) return; - for (pint_t i = 0; i < protolist->getCount(); i++) { - pint_t oldValue = protolist->getVMAddress(i); - pint_t newValue = mVisitor.visitProtocolReference(cache, oldValue); - protolist->setVMAddress(i, newValue); - } - } - - friend class ClassWalker>; - void visitClass(SharedCache* cache, const macho_header

*, - objc_class_t* cls) - { - visitProtocolList(cache, cls->getProtocolList(cache)); - visitProtocolList(cache, cls->getIsa(cache)->getProtocolList(cache)); - } - -public: - - ProtocolReferenceWalker(V& visitor) : mVisitor(visitor) { } - void walk(SharedCache* cache, const macho_header

* header) - { - // @protocol expressions - PointerSection *> - protorefs(cache, header, "__DATA", "__objc_protorefs"); - for (pint_t i = 0; i < protorefs.count(); i++) { - pint_t oldValue = protorefs.getVMAddress(i); - pint_t newValue = mVisitor.visitProtocolReference(cache, oldValue); - protorefs.setVMAddress(i, newValue); - } - - // protocol lists in classes - ClassWalker> classes(*this); - classes.walk(cache, header); - - // protocol lists in protocols - // __objc_protolists itself is NOT updated - PointerSection *> - protocols(cache, header, "__DATA", "__objc_protolist"); - for (pint_t i = 0; i < protocols.count(); i++) { - objc_protocol_t* proto = protocols.get(i); - visitProtocolList(cache, proto->getProtocols(cache)); - // not recursive: every old protocol object - // must be in some protolist section somewhere - } - } -}; - - -// Call visitor.visitMethodList(mlist) on every -// class and category method list in a header. -// Call visitor.visitProtocolMethodList(mlist, typelist) on every -// protocol method list in a header. -template -class MethodListWalker { - - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - V& mVisitor; - -public: - - MethodListWalker(V& visitor) : mVisitor(visitor) { } - - void walk(SharedCache* cache, const macho_header

* header) - { - // Method lists in classes - PointerSection *> - classes(cache, header, "__DATA", "__objc_classlist"); - - for (pint_t i = 0; i < classes.count(); i++) { - objc_class_t *cls = classes.get(i); - objc_method_list_t *mlist; - if ((mlist = cls->getMethodList(cache))) { - mVisitor.visitMethodList(mlist); - } - if ((mlist = cls->getIsa(cache)->getMethodList(cache))) { - mVisitor.visitMethodList(mlist); - } - } - - // Method lists from categories - PointerSection *> - cats(cache, header, "__DATA", "__objc_catlist"); - for (pint_t i = 0; i < cats.count(); i++) { - objc_category_t *cat = cats.get(i); - objc_method_list_t *mlist; - if ((mlist = cat->getInstanceMethods(cache))) { - mVisitor.visitMethodList(mlist); - } - if ((mlist = cat->getClassMethods(cache))) { - mVisitor.visitMethodList(mlist); - } - } - - // Method description lists from protocols - PointerSection *> - protocols(cache, header, "__DATA", "__objc_protolist"); - for (pint_t i = 0; i < protocols.count(); i++) { - objc_protocol_t *proto = protocols.get(i); - objc_method_list_t *mlist; - pint_t *typelist = proto->getExtendedMethodTypes(cache); - - if ((mlist = proto->getInstanceMethods(cache))) { - mVisitor.visitProtocolMethodList(mlist, typelist); - if (typelist) typelist += mlist->getCount(); - } - if ((mlist = proto->getClassMethods(cache))) { - mVisitor.visitProtocolMethodList(mlist, typelist); - if (typelist) typelist += mlist->getCount(); - } - if ((mlist = proto->getOptionalInstanceMethods(cache))) { - mVisitor.visitProtocolMethodList(mlist, typelist); - if (typelist) typelist += mlist->getCount(); - } - if ((mlist = proto->getOptionalClassMethods(cache))) { - mVisitor.visitProtocolMethodList(mlist, typelist); - if (typelist) typelist += mlist->getCount(); - } - } - } -}; - - -// Update selector references. The visitor performs recording and uniquing. -template -class SelectorOptimizer { - - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - V& mVisitor; - - friend class MethodListWalker< A, SelectorOptimizer >; - void visitMethodList(objc_method_list_t *mlist) - { - // Gather selectors. Update method names. - for (pint_t m = 0; m < mlist->getCount(); m++) { - pint_t oldValue = mlist->get(m).getName(); - pint_t newValue = mVisitor.visit(oldValue); - mlist->get(m).setName(newValue); - } - // Do not setFixedUp: the methods are not yet sorted. - } - - void visitProtocolMethodList(objc_method_list_t *mlist, pint_t *types) - { - visitMethodList(mlist); - } - -public: - - SelectorOptimizer(V& visitor) : mVisitor(visitor) { } - - void optimize(SharedCache* cache, const macho_header

* header) - { - // method lists in classes, categories, and protocols - MethodListWalker< A, SelectorOptimizer > mw(*this); - mw.walk(cache, header); - - // @selector references - PointerSection - selrefs(cache, header, "__DATA", "__objc_selrefs"); - for (pint_t i = 0; i < selrefs.count(); i++) { - pint_t oldValue = selrefs.getVMAddress(i); - pint_t newValue = mVisitor.visit(oldValue); - selrefs.setVMAddress(i, newValue); - } - - // message references - ArraySection > - msgrefs(cache, header, "__DATA", "__objc_msgrefs"); - for (pint_t i = 0; i < msgrefs.count(); i++) { - objc_message_ref_t& msg = msgrefs.get(i); - pint_t oldValue = msg.getName(); - pint_t newValue = mVisitor.visit(oldValue); - msg.setName(newValue); - } - } -}; - - -template -static bool headerSupportsGC(SharedCache* cache, - const macho_header* header) -{ - const macho_section *imageInfoSection = - header->getSection("__DATA", "__objc_imageinfo"); - if (imageInfoSection) { - objc_image_info *info = (objc_image_info *) - cache->mappedAddressForVMAddress(imageInfoSection->addr()); - return (info->supportsGCFlagSet() || info->requiresGCFlagSet()); - } - - return false; -} - - -// Gather the set of GC-supporting classes -template -class GCClassSet { - typedef typename A::P P; - - std::set*> fGCClasses; - -public: - bool contains(objc_class_t* cls) const { - return fGCClasses.count(cls) != 0; - } - - void visitClass(SharedCache* cache, const macho_header

* header, objc_class_t *cls) - { - fGCClasses.insert(cls); - } -}; - - -// Update selector references. The visitor performs recording and uniquing. -template -class IvarOffsetOptimizer { - typedef typename A::P P; - - uint32_t slide; - uint32_t maxAlignment; - - uint32_t fOptimized; - - GCClassSet fGCClasses; - -public: - - IvarOffsetOptimizer() : fOptimized(0) { } - - size_t optimized() const { return fOptimized; } - - // dual purpose ivar visitor function - // if slide!=0 then slides the ivar by that amount, otherwise computes maxAlignment - void visitIvar(SharedCache* cache, const macho_header

* /*unused, may be NULL*/, objc_class_t *cls, objc_ivar_t *ivar) - { - if (slide == 0) { - uint32_t alignment = ivar->getAlignment(); - if (alignment > maxAlignment) maxAlignment = alignment; - } else { - // skip anonymous bitfields - if (ivar->hasOffset()) { - uint32_t oldOffset = (uint32_t)ivar->getOffset(cache); - ivar->setOffset(cache, oldOffset + slide); - fOptimized++; - //fprintf(stderr, "%d -> %d for %s.%s\n", oldOffset, oldOffset + slide, cls->getName(cache), ivar->getName(cache)); - } else { - //fprintf(stderr, "NULL offset\n"); - } - } - } - - // Class visitor function. Evaluates whether to slide ivars and performs slide if needed. - // The slide algorithm is also implemented in objc. Any changes here should be reflected there also. - void visitClass(SharedCache* cache, const macho_header

* /*unused, may be NULL*/, objc_class_t *cls) - { - if (fGCClasses.contains(cls)) { - // This class supports GC. We don't know how to update - // GC ivar layout bitmaps, so don't touch anything. - return; - } - - objc_class_t *super = cls->getSuperclass(cache); - if (super) { - // Recursively visit superclasses to ensure we have the correct superclass start - // Note that we don't need the macho_header, so just pass NULL. - visitClass(cache, NULL, super); - - objc_class_data_t *data = cls->getData(cache); - objc_class_data_t *super_data = super->getData(cache); - int32_t diff = super_data->getInstanceSize() - data->getInstanceStart(); - if (diff > 0) { - IvarWalker > ivarVisitor(*this); - maxAlignment = 0; - slide = 0; - - // This walk computes maxAlignment - ivarVisitor.walk(cache, NULL, cls); - - // Compute a slide value that preserves that alignment - uint32_t alignMask = maxAlignment - 1; - if (diff & alignMask) diff = (diff + alignMask) & ~alignMask; - - // Slide all of this class's ivars en masse - slide = diff; - if (slide != 0) { - //fprintf(stderr, "Sliding ivars in %s by %u (superclass was %d, now %d)\n", cls->getName(cache), slide, data->getInstanceStart(), super_data->getInstanceSize()); - ivarVisitor.walk(cache, NULL, cls); - data->setInstanceStart(data->getInstanceStart() + slide); - data->setInstanceSize(data->getInstanceSize() + slide); - } - } - } - } - - // Gather the list of GC-supporting classes. - // Ivars in these classes cannot be updated because - // we don't know how to update ivar layout bitmaps. - void findGCClasses(SharedCache* cache, const macho_header

* header) - { - if (headerSupportsGC(cache, header)) { - ClassWalker > classVisitor(fGCClasses); - classVisitor.walk(cache, header); - } - } - - // Enumerates objc classes in the module and performs any ivar slides - void optimize(SharedCache* cache, const macho_header

* header) - { - if (! headerSupportsGC(cache, header)) { - ClassWalker > classVisitor(*this); - classVisitor.walk(cache, header); - } - } -}; - - -// Sort methods in place by selector. -template -class MethodListSorter { - - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - uint32_t fOptimized; - - friend class MethodListWalker >; - void visitMethodList(objc_method_list_t *mlist) - { - typename objc_method_t::SortBySELAddress sorter; - std::stable_sort(mlist->begin(), mlist->end(), sorter); - mlist->setFixedUp(); - fOptimized++; - } - - void visitProtocolMethodList(objc_method_list_t *mlist, pint_t *typelist) - { - typename objc_method_t::SortBySELAddress sorter; - // can't easily use std::stable_sort here - for (uint32_t i = 0; i < mlist->getCount(); i++) { - for (uint32_t j = i+1; j < mlist->getCount(); j++) { - objc_method_t& mi = mlist->get(i); - objc_method_t& mj = mlist->get(j); - if (! sorter(mi, mj)) { - std::swap(mi, mj); - if (typelist) std::swap(typelist[i], typelist[j]); - } - } - } - - mlist->setFixedUp(); - fOptimized++; - } - -public: - MethodListSorter() : fOptimized(0) { } - - size_t optimized() const { return fOptimized; } - - void optimize(SharedCache* cache, macho_header

* header) - { - MethodListWalker > mw(*this); - mw.walk(cache, header); - } -}; - - -template -class HeaderInfoOptimizer { - - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - objc_header_info_t* fHinfos; - size_t fCount; - -public: - HeaderInfoOptimizer() : fHinfos(0), fCount(0) { } - - const char *init(size_t count, uint8_t*& buf, size_t& bufSize) - { - if (count == 0) return NULL; - - size_t requiredSize = - 2*sizeof(uint32_t) + count*sizeof(objc_header_info_t); - if (bufSize < requiredSize) { - return "libobjc's read/write section is too small (metadata not optimized)"; - } - - uint32_t *buf32 = (uint32_t *)buf; - A::P::E::set32(buf32[0], count); - A::P::E::set32(buf32[1], sizeof(objc_header_info_t)); - fHinfos = (objc_header_info_t*)(buf32+2); - - buf += requiredSize; - bufSize -= requiredSize; - - return NULL; - } - - void update(SharedCache* cache, const macho_header

* mh, std::vector& pointersInData) - { - objc_header_info_t* hi = new(&fHinfos[fCount++]) objc_header_info_t(cache, mh); - hi->addPointers(pointersInData); - } - - objc_header_info_t* hinfoForHeader(SharedCache* cache, const macho_header

* mh) - { - // fixme could be binary search - pint_t mh_vmaddr = cache->VMAddressForMappedAddress(mh); - for (size_t i = 0; i < fCount; i++) { - objc_header_info_t* hi = &fHinfos[i]; - if (hi->header_vmaddr() == mh_vmaddr) return hi; - } - return NULL; - } -}; diff --git a/launch-cache/dsc_extractor.cpp b/launch-cache/dsc_extractor.cpp index e21a0f6..30ba094 100644 --- a/launch-cache/dsc_extractor.cpp +++ b/launch-cache/dsc_extractor.cpp @@ -98,7 +98,7 @@ private: if ( (entry.flags & EXPORT_SYMBOL_FLAGS_REEXPORT) == 0 ) return true; // If the symbol comes from a dylib that is re-exported, this is not an individual symbol re-export - if ( _reexportDeps.count(entry.other) != 0 ) + if ( _reexportDeps.count((int)entry.other) != 0 ) return true; return false; } @@ -118,8 +118,11 @@ int optimize_linkedit(macho_header* mh, uint64_t textOffsetInCach // update load commands uint64_t cumulativeFileSize = 0; + const unsigned origLoadCommandsSize = mh->sizeofcmds(); + unsigned bytesRemaining = origLoadCommandsSize; + unsigned removedCount = 0; const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)mh + sizeof(macho_header

)); - const uint32_t cmd_count = mh->ncmds(); + const uint32_t cmdCount = mh->ncmds(); const macho_load_command

* cmd = cmds; macho_segment_command

* linkEditSegCmd = NULL; macho_symtab_command

* symtab = NULL; @@ -130,7 +133,8 @@ int optimize_linkedit(macho_header* mh, uint64_t textOffsetInCach uint32_t exportsTrieSize = 0; std::set reexportDeps; int depIndex = 0; - for (uint32_t i = 0; i < cmd_count; ++i) { + for (uint32_t i = 0; i < cmdCount; ++i) { + bool remove = false; switch ( cmd->cmd() ) { case macho_segment_command

::CMD: { @@ -188,10 +192,28 @@ int optimize_linkedit(macho_header* mh, uint64_t textOffsetInCach reexportDeps.insert(depIndex); } break; + case LC_SEGMENT_SPLIT_INFO: + // dylibs iOS 9 dyld caches have bogus LC_SEGMENT_SPLIT_INFO + remove = true; + break; + } + uint32_t cmdSize = cmd->cmdsize(); + macho_load_command

* nextCmd = (macho_load_command

*)(((uint8_t*)cmd)+cmdSize); + if ( remove ) { + ::memmove((void*)cmd, (void*)nextCmd, bytesRemaining); + ++removedCount; + } + else { + bytesRemaining -= cmdSize; + cmd = nextCmd; } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); } - + // zero out stuff removed + ::bzero((void*)cmd, bytesRemaining); + // update header + mh->set_ncmds(cmdCount - removedCount); + mh->set_sizeofcmds(origLoadCommandsSize - bytesRemaining); + // rebuild symbol table if ( linkEditSegCmd == NULL ) { fprintf(stderr, "__LINKEDIT not found\n"); diff --git a/launch-cache/dyld_cache_format.h b/launch-cache/dyld_cache_format.h index 6862581..fd9d1c2 100644 --- a/launch-cache/dyld_cache_format.h +++ b/launch-cache/dyld_cache_format.h @@ -1,6 +1,6 @@ /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- * - * Copyright (c) 2006-2009 Apple Inc. All rights reserved. + * Copyright (c) 2006-2015 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -27,6 +27,7 @@ #include #include #include +#include struct dyld_cache_header @@ -44,7 +45,13 @@ struct dyld_cache_header uint64_t localSymbolsOffset; // file offset of where local symbols are stored uint64_t localSymbolsSize; // size of local symbols information uint8_t uuid[16]; // unique value for each shared cache file - uint64_t cacheType; // 1 for development, 0 for optimized + uint64_t cacheType; // 0 for development, 1 for production + uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses + uint32_t branchPoolsCount; // number of uint64_t entries + uint64_t accelerateInfoAddr; // (unslid) address of optimization info + uint64_t accelerateInfoSize; // size of optimization info + uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info + uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries }; struct dyld_cache_mapping_info { @@ -64,6 +71,69 @@ struct dyld_cache_image_info uint32_t pad; }; +struct dyld_cache_image_info_extra +{ + uint64_t exportsTrieAddr; // address of trie in unslid cache + uint64_t weakBindingsAddr; + uint32_t exportsTrieSize; + uint32_t weakBindingsSize; + uint32_t dependentsStartArrayIndex; + uint32_t reExportsStartArrayIndex; +}; + + +struct dyld_cache_accelerator_info +{ + uint32_t version; // currently 1 + uint32_t imageExtrasCount; // does not include aliases + uint32_t imagesExtrasOffset; // offset into this chunk of first dyld_cache_image_info_extra + uint32_t bottomUpListOffset; // offset into this chunk to start of 16-bit array of sorted image indexes + uint32_t dylibTrieOffset; // offset into this chunk to start of trie containing all dylib paths + uint32_t dylibTrieSize; // size of trie containing all dylib paths + uint32_t initializersOffset; // offset into this chunk to start of initializers list + uint32_t initializersCount; // size of initializers list + uint32_t dofSectionsOffset; // offset into this chunk to start of DOF sections list + uint32_t dofSectionsCount; // size of initializers list + uint32_t reExportListOffset; // offset into this chunk to start of 16-bit array of re-exports + uint32_t reExportCount; // size of re-exports + uint32_t depListOffset; // offset into this chunk to start of 16-bit array of dependencies (0x8000 bit set if upward) + uint32_t depListCount; // size of dependencies + uint32_t rangeTableOffset; // offset into this chunk to start of ss + uint32_t rangeTableCount; // size of dependencies + uint64_t dyldSectionAddr; // address of libdyld's __dyld section in unslid cache +}; + +struct dyld_cache_accelerator_initializer +{ + uint32_t functionOffset; // address offset from start of cache mapping + uint32_t imageIndex; +}; + +struct dyld_cache_range_entry +{ + uint64_t startAddress; // unslid address of start of region + uint32_t size; + uint32_t imageIndex; +}; + +struct dyld_cache_accelerator_dof +{ + uint64_t sectionAddress; // unslid address of start of region + uint32_t sectionSize; + uint32_t imageIndex; +}; + +struct dyld_cache_image_text_info +{ + uuid_t uuid; + uint64_t loadAddress; // unslid address of start of __TEXT + uint32_t textSegmentSize; + uint32_t pathOffset; // offset from start of cache file +}; + + +// The rebasing info is to allow the kernel to lazily rebase DATA pages of the +// dyld shared cache. Rebasing is adding the slide to interior pointers. struct dyld_cache_slide_info { uint32_t version; // currently 1 @@ -77,6 +147,89 @@ struct dyld_cache_slide_info }; +// The version 2 of the slide info uses a different compression scheme. Since +// only interior pointers (pointers that point within the cache) are rebased +// (slid), we know the possible range of the pointers and thus know there are +// unused bits in each pointer. We use those bits to form a linked list of +// locations needing rebasing in each page. +// +// Definitions: +// +// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size +// pageStarts[] = info + info->page_starts_offset +// pageExtras[] = info + info->page_extras_offset +// valueMask = ~(info->delta_mask) +// deltaShift = __builtin_ctzll(info->delta_mask) - 2 +// +// There are three cases: +// +// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE +// The page contains no values that need rebasing. +// +// 2) (pageStarts[pageIndex] & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) == 0 +// All rebase locations are in one linked list. The offset of the first +// rebase location in the page is pageStarts[pageIndex] * 4. +// +// 3) pageStarts[pageIndex] & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA +// Multiple linked lists are needed for all rebase locations in a page. +// The pagesExtras array contains 2 or more entries each of which is the +// start of a new linked list in the page. The first is at: +// extrasStartIndex = (pageStarts[pageIndex] & 0x3FFF) +// The next is at extrasStartIndex+1. The last is denoted by +// having the high bit (DYLD_CACHE_SLIDE_PAGE_ATTR_END) of the pageExtras[] +// set. +// +// For 64-bit architectures, there is always enough free bits to encode all +// possible deltas. The info->delta_mask field shows where the delta is located +// in the pointer. That value must be masked off (valueMask) before the slide +// is added to the pointer. +// +// For 32-bit architectures, there are only three bits free (the three most +// significant bits). To extract the delta, you must first subtract value_add +// from the pointer value, then AND with delta_mask, then shift by deltaShift. +// That still leaves a maximum delta to the next rebase location of 28 bytes. +// To reduce the number or chains needed, an optimization was added. Turns +// out zero is common in the DATA region. A zero can be turned into a +// non-rebasing entry in the linked list. The can be done because nothing +// in the shared cache should point out of its dylib to the start of the shared +// cache. +// +// The code for processing a linked list (chain) is: +// +// uint32_t delta = 1; +// while ( delta != 0 ) { +// uint8_t* loc = pageStart + pageOffset; +// uintptr_t rawValue = *((uintptr_t*)loc); +// delta = ((rawValue & deltaMask) >> deltaShift); +// uintptr_t newValue = (rawValue & valueMask); +// if ( newValue != 0 ) { +// newValue += valueAdd; +// newValue += slideAmount; +// } +// *((uintptr_t*)loc) = newValue; +// pageOffset += delta; +// } +// +// +struct dyld_cache_slide_info2 +{ + uint32_t version; // currently 2 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_offset; + uint32_t page_starts_count; + uint32_t page_extras_offset; + uint32_t page_extras_count; + uint64_t delta_mask; // which (contiguous) set of bits contains the delta to the next rebase location + uint64_t value_add; + //uint16_t page_starts[page_starts_count]; + //uint16_t page_extras[page_extras_count]; +}; +#define DYLD_CACHE_SLIDE_PAGE_ATTRS 0xC000 // high bits of uint16_t are flags +#define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array) +#define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing +#define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page + + struct dyld_cache_local_symbols_info { uint32_t nlistOffset; // offset into this chunk of nlist entries @@ -96,9 +249,14 @@ struct dyld_cache_local_symbols_entry -#define MACOSX_DYLD_SHARED_CACHE_DIR "/var/db/dyld/" +#define MACOSX_DYLD_SHARED_CACHE_DIR "/private/var/db/dyld/" #define IPHONE_DYLD_SHARED_CACHE_DIR "/System/Library/Caches/com.apple.dyld/" #define DYLD_SHARED_CACHE_BASE_NAME "dyld_shared_cache_" +#define DYLD_SHARED_CACHE_DEVELOPMENT_EXT ".development" + +static const uint64_t kDyldSharedCacheTypeDevelopment = 0; +static const uint64_t kDyldSharedCacheTypeProduction = 1; + diff --git a/launch-cache/dyld_shared_cache_util.cpp b/launch-cache/dyld_shared_cache_util.cpp index be6aa65..63d6b73 100644 --- a/launch-cache/dyld_shared_cache_util.cpp +++ b/launch-cache/dyld_shared_cache_util.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -33,16 +34,19 @@ #include #include #include +#include #include #include #include #include "dsc_iterator.h" +#include "dsc_extractor.h" #include "dyld_cache_format.h" #include "Architectures.hpp" #include "MachOFileAbstraction.hpp" #include "CacheFileAbstraction.hpp" +#include "Trie.hpp" enum Mode { modeNone, @@ -50,15 +54,20 @@ enum Mode { modeMap, modeDependencies, modeSlideInfo, + modeAcceleratorInfo, + modeTextInfo, modeLinkEdit, + modeLocalSymbols, modeInfo, - modeSize + modeSize, + modeExtract }; struct Options { Mode mode; const char* dependentsOfPath; const void* mappedCache; + const char* extractionDir; bool printUUIDs; bool printVMAddrs; bool printDylibVersions; @@ -86,7 +95,7 @@ struct Results { void usage() { - fprintf(stderr, "Usage: dyld_shared_cache_util -list [ -uuid ] [-vmaddr] | -dependents [ -versions ] | -linkedit | -map [ shared-cache-file ] | -slide_info | -info\n"); + fprintf(stderr, "Usage: dyld_shared_cache_util -list [ -uuid ] [-vmaddr] | -dependents [ -versions ] | -linkedit | -map | -slide_info | -info | -extract [ shared-cache-file ] \n"); } #if __x86_64__ @@ -331,7 +340,7 @@ void print_map(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_ static void checkMode(Mode mode) { if ( mode != modeNone ) { - fprintf(stderr, "Error: select one of: -list, -dependents, -info, -slide_info, -linkedit, -map, or -size\n"); + fprintf(stderr, "Error: select one of: -list, -dependents, -info, -slide_info, -linkedit, -map, -extract, or -size\n"); usage(); exit(1); } @@ -348,7 +357,8 @@ int main (int argc, const char* argv[]) { options.printDylibVersions = false; options.printInodes = false; options.dependentsOfPath = NULL; - + options.extractionDir = NULL; + for (uint32_t i = 1; i < argc; i++) { const char* opt = argv[i]; if (opt[0] == '-') { @@ -377,7 +387,19 @@ int main (int argc, const char* argv[]) { else if (strcmp(opt, "-slide_info") == 0) { checkMode(options.mode); options.mode = modeSlideInfo; - } + } + else if (strcmp(opt, "-accelerator_info") == 0) { + checkMode(options.mode); + options.mode = modeAcceleratorInfo; + } + else if (strcmp(opt, "-text_info") == 0) { + checkMode(options.mode); + options.mode = modeTextInfo; + } + else if (strcmp(opt, "-local_symbols") == 0) { + checkMode(options.mode); + options.mode = modeLocalSymbols; + } else if (strcmp(opt, "-map") == 0) { checkMode(options.mode); options.mode = modeMap; @@ -385,7 +407,17 @@ int main (int argc, const char* argv[]) { else if (strcmp(opt, "-size") == 0) { checkMode(options.mode); options.mode = modeSize; - } + } + else if (strcmp(opt, "-extract") == 0) { + checkMode(options.mode); + options.mode = modeExtract; + options.extractionDir = argv[++i]; + if ( i >= argc ) { + fprintf(stderr, "Error: option -extract requires a directory argument\n"); + usage(); + exit(1); + } + } else if (strcmp(opt, "-uuid") == 0) { options.printUUIDs = true; } @@ -461,14 +493,46 @@ int main (int argc, const char* argv[]) { uint64_t dataSize = dataMapping->size(); const dyldCacheSlideInfo* slideInfoHeader = (dyldCacheSlideInfo*)((char*)options.mappedCache+header->slideInfoOffset()); printf("slide info version=%d\n", slideInfoHeader->version()); - printf("toc_count=%d, data page count=%lld\n", slideInfoHeader->toc_count(), dataSize/4096); - const dyldCacheSlideInfoEntry* entries = (dyldCacheSlideInfoEntry*)((char*)slideInfoHeader + slideInfoHeader->entries_offset()); - for(int i=0; i < slideInfoHeader->toc_count(); ++i) { - printf("0x%08llX: [% 5d,% 5d] ", dataStartAddress + i*4096, i, slideInfoHeader->toc(i)); - const dyldCacheSlideInfoEntry* entry = &entries[slideInfoHeader->toc(i)]; - for(int j=0; j < slideInfoHeader->entries_size(); ++j) - printf("%02X", entry->bits[j]); - printf("\n"); + if ( slideInfoHeader->version() == 1 ) { + printf("toc_count=%d, data page count=%lld\n", slideInfoHeader->toc_count(), dataSize/4096); + const dyldCacheSlideInfoEntry* entries = (dyldCacheSlideInfoEntry*)((char*)slideInfoHeader + slideInfoHeader->entries_offset()); + for(int i=0; i < slideInfoHeader->toc_count(); ++i) { + printf("0x%08llX: [% 5d,% 5d] ", dataStartAddress + i*4096, i, slideInfoHeader->toc(i)); + const dyldCacheSlideInfoEntry* entry = &entries[slideInfoHeader->toc(i)]; + for(int j=0; j < slideInfoHeader->entries_size(); ++j) + printf("%02X", entry->bits[j]); + printf("\n"); + } + } + else if ( slideInfoHeader->version() == 2 ) { + const dyldCacheSlideInfo2* slideInfo = (dyldCacheSlideInfo2*)(slideInfoHeader); + printf("page_size=%d\n", slideInfo->page_size()); + printf("delta_mask=0x%016llX\n", slideInfo->delta_mask()); + printf("value_add=0x%016llX\n", slideInfo->value_add()); + printf("page_starts_count=%d, page_extras_count=%d\n", slideInfo->page_starts_count(), slideInfo->page_extras_count()); + const uint16_t* starts = (uint16_t* )((char*)slideInfo + slideInfo->page_starts_offset()); + const uint16_t* extras = (uint16_t* )((char*)slideInfo + slideInfo->page_extras_offset()); + for (int i=0; i < slideInfo->page_starts_count(); ++i) { + const uint16_t start = starts[i]; + if ( start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE ) { + printf("page[% 5d]: no rebasing\n", i); + } + else if ( start & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA ) { + printf("page[% 5d]: ", i); + int j=(start & 0x3FFF); + bool done = false; + do { + uint16_t aStart = extras[j]; + printf("start=0x%04X ", aStart & 0x3FFF); + done = (extras[j] & DYLD_CACHE_SLIDE_PAGE_ATTR_END); + ++j; + } while ( !done ); + printf("\n"); + } + else { + printf("page[% 5d]: start=0x%04X\n", i, starts[i]); + } + } } } else if ( options.mode == modeInfo ) { @@ -486,22 +550,211 @@ int main (int argc, const char* argv[]) { printf("n/a\n"); } printf("image count: %u\n", header->imagesCount()); + if ( (header->mappingOffset() >= 0x78) && (header->branchPoolsOffset() != 0) ) { + printf("branch pool count: %u\n", header->branchPoolsCount()); + } printf("mappings:\n"); const dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)((char*)options.mappedCache + header->mappingOffset()); for (uint32_t i=0; i < header->mappingCount(); ++i) { if ( mappings[i].init_prot() & VM_PROT_EXECUTE ) - printf(" __TEXT %3lluMB, 0x%08llX -> 0x%08llX\n", mappings[i].size()/(1024*1024), mappings[i].address(), mappings[i].address() + mappings[i].size()); + printf(" __TEXT %3lluMB, file offset: 0x%08llX -> 0x%08llX, address: 0x%08llX -> 0x%08llX\n", + mappings[i].size()/(1024*1024), mappings[i].file_offset(), mappings[i].file_offset() + mappings[i].size(), + mappings[i].address(), mappings[i].address() + mappings[i].size()); else if ( mappings[i]. init_prot() & VM_PROT_WRITE ) - printf(" __DATA %3lluMB, 0x%08llX -> 0x%08llX\n", mappings[i].size()/(1024*1024), mappings[i].address(), mappings[i].address() + mappings[i].size()); + printf(" __DATA %3lluMB, file offset: 0x%08llX -> 0x%08llX, address: 0x%08llX -> 0x%08llX\n", + mappings[i].size()/(1024*1024), mappings[i].file_offset(), mappings[i].file_offset() + mappings[i].size(), + mappings[i].address(), mappings[i].address() + mappings[i].size()); else if ( mappings[i].init_prot() & VM_PROT_READ ) - printf(" __LINKEDIT %3lluMB, 0x%08llX -> 0x%08llX\n", mappings[i].size()/(1024*1024), mappings[i].address(), mappings[i].address() + mappings[i].size()); + printf(" __LINKEDIT %3lluMB, file offset: 0x%08llX -> 0x%08llX, address: 0x%08llX -> 0x%08llX\n", + mappings[i].size()/(1024*1024), mappings[i].file_offset(), mappings[i].file_offset() + mappings[i].size(), + mappings[i].address(), mappings[i].address() + mappings[i].size()); } if ( header->codeSignatureOffset() != 0 ) { - uint64_t size = statbuf.st_size - header->codeSignatureOffset(); + uint64_t size = statbuf.st_size - header->codeSignatureOffset(); uint64_t csAddr = mappings[header->mappingCount()-1].address() + mappings[header->mappingCount()-1].size(); - printf(" code sign %3lluMB, 0x%08llX -> 0x%08llX\n", size/(1024*1024), csAddr, csAddr + size); + if ( size != 0 ) + printf(" code sign %3lluMB, file offset: 0x%08llX -> 0x%08llX, address: 0x%08llX -> 0x%08llX\n", + size/(1024*1024), header->codeSignatureOffset(), header->codeSignatureOffset() + size, csAddr, csAddr + size); + } + printf("slide info: %4lluKB, file offset: 0x%08llX -> 0x%08llX\n", + header->slideInfoSize()/1024, header->slideInfoOffset(), header->slideInfoOffset() + header->slideInfoSize()); + if ( header->localSymbolsOffset() != 0 ) + printf("local symbols: %3lluMB, file offset: 0x%08llX -> 0x%08llX\n", + header->localSymbolsSize()/(1024*1024), header->localSymbolsOffset(), header->localSymbolsOffset() + header->localSymbolsSize()); + if ( (header->mappingOffset() >= 0x78) && (header->accelerateInfoSize() != 0) ) + printf("accelerate tab: %3lluKB, address: 0x%08llX -> 0x%08llX\n", + header->accelerateInfoSize()/1024, header->accelerateInfoAddr(), header->accelerateInfoAddr() + header->accelerateInfoSize()); + } + else if ( options.mode == modeAcceleratorInfo ) { + const dyldCacheHeader* header = (dyldCacheHeader*)options.mappedCache; + if ( (header->mappingOffset() < sizeof(dyldCacheHeader)) || (header->accelerateInfoSize() == 0) ) { + printf("no accelerator info\n"); + } + else { + const dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)((char*)options.mappedCache + header->mappingOffset()); + uint64_t aiAddr = header->accelerateInfoAddr(); + dyldCacheAcceleratorInfo* accelInfo = NULL; + for (uint32_t i=0; i < header->mappingCount(); ++i) { + if ( (mappings[i].address() <= aiAddr) && (aiAddr < mappings[i].address()+mappings[i].size()) ) { + uint64_t offset = aiAddr - mappings[i].address() + mappings[i].file_offset(); + accelInfo = (dyldCacheAcceleratorInfo*)((uint8_t*)options.mappedCache + offset); + } + } + if ( accelInfo == NULL ) { + printf("accelerator info not in any mapped range\n"); + } + else { + const dyldCacheImageInfo* images = (dyldCacheImageInfo*)((char*)options.mappedCache + header->imagesOffset()); + const dyldCacheImageInfoExtra* imagesExtra = (dyldCacheImageInfoExtra*)((char*)accelInfo + accelInfo->imagesExtrasOffset()); + const uint16_t* dependencyArray = (uint16_t*)((char*)accelInfo + accelInfo->depListOffset()); + const uint16_t* reExportArray = (uint16_t*)((char*)accelInfo + accelInfo->reExportListOffset()); + printf("extra image info (count=%u):\n", accelInfo->imageExtrasCount()); + for (uint32_t i=0; i < accelInfo->imageExtrasCount(); ++i) { + printf(" image[%3u] %s:\n", i, (char*)options.mappedCache +images[i].pathFileOffset()); + printf(" exports trie: addr=0x%llX, size=0x%08X\n", imagesExtra[i].exportsTrieAddr(), imagesExtra[i].exportsTrieSize()); + if ( imagesExtra[i].weakBindingsSize() ) + printf(" weak bind info: addr=0x%llX, size=0x%08X\n", imagesExtra[i].weakBindingsAddr(), imagesExtra[i].weakBindingsSize()); + printf(" dependents: "); + for (uint32_t d=imagesExtra[i].dependentsStartArrayIndex(); dependencyArray[d] != 0xFFFF; ++d) { + uint16_t depIndex = dependencyArray[d]; + if ( depIndex & 0x8000 ) + printf(" up(%d) ", depIndex & 0x7FFF); + else + printf(" %d ", depIndex); + } + printf("\n"); + printf(" re-exports: "); + for (uint32_t r=imagesExtra[i].reExportsStartArrayIndex(); reExportArray[r] != 0xFFFF; ++r) + printf(" %d ", reExportArray[r]); + printf("\n"); + } + printf("libdyld.dylib:\n"); + printf(" __dyld section address: 0x%llX\n", accelInfo->dyldSectionAddr()); + printf("initializers (count=%u):\n", accelInfo->initializersCount()); + const dyldCacheAcceleratorInitializer* initializers = (dyldCacheAcceleratorInitializer*)((char*)accelInfo + accelInfo->initializersOffset()); + for (uint32_t i=0; i < accelInfo->initializersCount(); ++i) { + printf(" image[%3u] 0x%llX\n", initializers[i].imageIndex(), mappings[0].address() + initializers[i].functionOffset()); + } + printf("DOF sections (count=%u):\n", accelInfo->dofSectionsCount()); + const dyldCacheAcceleratorDOFEntry* dofs = (dyldCacheAcceleratorDOFEntry*)((char*)accelInfo + accelInfo->dofSectionsOffset()); + for (uint32_t i=0; i < accelInfo->dofSectionsCount(); ++i) { + printf(" image[%3u] 0x%llX -> 0x%llX\n", dofs[i].imageIndex(), dofs[i].sectionAddress(), dofs[i].sectionAddress()+dofs[i].sectionSize()); + } + printf("bottom up order (count=%u):\n", accelInfo->imageExtrasCount()); + const uint16_t* bottomUpArray = (uint16_t*)((char*)accelInfo + accelInfo->bottomUpListOffset()); + for (uint32_t i=0; i < accelInfo->imageExtrasCount(); ++i) { + unsigned imageIndex = bottomUpArray[i]; + if ( imageIndex < accelInfo->imageExtrasCount() ) + printf(" image[%3u] %s\n", imageIndex, (char*)options.mappedCache + images[imageIndex].pathFileOffset()); + else + printf(" image[%3u] BAD INDEX\n", imageIndex); + } + printf("range table (count=%u):\n", accelInfo->rangeTableCount()); + const dyldCacheAcceleratorRangeEntry* rangeTable = (dyldCacheAcceleratorRangeEntry*)((char*)accelInfo + accelInfo->rangeTableOffset()); + for (uint32_t i=0; i < accelInfo->rangeTableCount(); ++i) { + const dyldCacheAcceleratorRangeEntry& entry = rangeTable[i]; + printf(" 0x%llX -> 0x%llX %s\n", entry.startAddress(), entry.startAddress() + entry.size(), (char*)options.mappedCache + images[entry.imageIndex()].pathFileOffset()); + } + printf("dylib trie (size=%u):\n", accelInfo->dylibTrieSize()); + const uint8_t* dylibTrieStart = (uint8_t*)accelInfo + accelInfo->dylibTrieOffset(); + const uint8_t* dylibTrieEnd = dylibTrieStart + accelInfo->dylibTrieSize(); + std::vector dylibEntries; + if ( !Trie::parseTrie(dylibTrieStart, dylibTrieEnd, dylibEntries) ) + printf(" malformed dylibs trie\n"); + for (const DylibIndexTrie::Entry& x : dylibEntries) { + printf(" image[%3u] %s\n", x.info.index, x.name.c_str()); + } + } + } + } + else if ( options.mode == modeTextInfo ) { + const dyldCacheHeader* header = (dyldCacheHeader*)options.mappedCache; + if ( (header->mappingOffset() < sizeof(dyldCacheHeader)) || (header->imagesTextCount() == 0) ) { + printf("no text info\n"); + } + else { + const dyldCacheImageTextInfo* imagesText = (dyldCacheImageTextInfo*)((char*)options.mappedCache + header->imagesTextOffset()); + const dyldCacheImageTextInfo* imagesTextEnd = &imagesText[header->imagesTextCount()]; + printf("dylib text infos (count=%llu):\n", header->imagesTextCount()); + for (const dyldCacheImageTextInfo* p=imagesText; p < imagesTextEnd; ++p) { + printf(" 0x%09llX -> 0x%09llX <", p->loadAddress(), p->loadAddress() + p->textSegmentSize()); + for (int i=0; i<16; ++i) { + switch (i) { + case 4: + case 6: + case 8: + case 10: + printf("-"); + break; + } + printf("%02X", p->uuid()[i]); + } + printf("> %s\n", (char*)options.mappedCache + p->pathOffset()); + } } } + else if ( options.mode == modeLocalSymbols ) { + const dyldCacheHeader* header = (dyldCacheHeader*)options.mappedCache; + if ( header->localSymbolsOffset() == 0 ) { + fprintf(stderr, "Error: dyld shared cache does not contain local symbols info\n"); + exit(1); + } + const bool is64 = (strstr((char*)options.mappedCache, "64") != NULL); + const dyldCacheImageInfo* imageInfos = (dyldCacheImageInfo*)((char*)options.mappedCache + header->imagesOffset()); + const dyldCacheLocalSymbolsInfo* localsInfo = (dyldCacheLocalSymbolsInfo*)((char*)options.mappedCache + header->localSymbolsOffset()); + const uint32_t nlistFileOffset = (uint32_t)(header->localSymbolsOffset() + localsInfo->nlistOffset()); + const uint32_t nlistCount = localsInfo->nlistCount(); + const uint32_t nlistByteSize = is64 ? nlistCount*16 : nlistCount*12; + const uint32_t stringsFileOffset = (uint32_t)(header->localSymbolsOffset() + localsInfo->stringsOffset()); + const uint32_t stringsSize = localsInfo->stringsSize(); + const uint32_t entriesCount = localsInfo->entriesCount(); + const dyldCacheLocalSymbolEntry* entries = (dyldCacheLocalSymbolEntry*)((char*)localsInfo + localsInfo->entriesOffset()); + printf("local symbols nlist array: %3uMB, file offset: 0x%08X -> 0x%08X\n", nlistByteSize/(1024*1024), nlistFileOffset, nlistFileOffset+nlistByteSize); + printf("local symbols string pool: %3uMB, file offset: 0x%08X -> 0x%08X\n", stringsSize/(1024*1024), stringsFileOffset, stringsFileOffset+stringsSize); + printf("local symbols by dylib (count=%d):\n", entriesCount); + const char* stringPool = (char*)options.mappedCache + stringsFileOffset; + for (int i=0; i < entriesCount; ++i) { + const char* imageName = (char*)options.mappedCache + imageInfos[i].pathFileOffset(); + printf(" nlistStartIndex=%5d, nlistCount=%5d, image=%s\n", entries[i].nlistStartIndex(), entries[i].nlistCount(), imageName); + #if 0 + if ( is64 ) { + const nlist_64* symTab = (nlist_64*)((char*)options.mappedCache + nlistFileOffset); + for (int e=0; e < entries[i].nlistCount(); ++e) { + const nlist_64* entry = &symTab[entries[i].nlistStartIndex()+e]; + printf(" nlist[%d].str=%d, %s\n", e, entry->n_un.n_strx, &stringPool[entry->n_un.n_strx]); + printf(" nlist[%d].value=0x%0llX\n", e, entry->n_value); + } + } + #endif + } + } + else if ( options.mode == modeExtract ) { + char pathBuffer[PATH_MAX]; + uint32_t bufferSize = PATH_MAX; + if ( _NSGetExecutablePath(pathBuffer, &bufferSize) != 0 ) { + fprintf(stderr, "Error: could not get path of program\n"); + return 1; + } + char* last = strrchr(pathBuffer, '/'); + strcpy(last+1, "../../lib/dsc_extractor.bundle"); + void* handle = dlopen(pathBuffer, RTLD_LAZY); + if ( handle == NULL ) { + fprintf(stderr, "Error: dsc_extractor.bundle could not be loaded at %s\n", pathBuffer); + return 1; + } + + typedef int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path, + void (^progress)(unsigned current, unsigned total)); + + extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress"); + if ( proc == NULL ) { + fprintf(stderr, "Error: dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\n"); + return 1; + } + + int result = (*proc)(sharedCachePath, options.extractionDir, ^(unsigned c, unsigned total) { } ); + return result; + } else { segment_callback_t callback; if ( strcmp((char*)options.mappedCache, "dyld_v1 i386") == 0 ) { @@ -524,6 +777,10 @@ int main (int argc, const char* argv[]) { case modeNone: case modeInfo: case modeSlideInfo: + case modeAcceleratorInfo: + case modeTextInfo: + case modeLocalSymbols: + case modeExtract: break; } } @@ -548,6 +805,10 @@ int main (int argc, const char* argv[]) { case modeNone: case modeInfo: case modeSlideInfo: + case modeAcceleratorInfo: + case modeTextInfo: + case modeLocalSymbols: + case modeExtract: break; } } @@ -572,6 +833,10 @@ int main (int argc, const char* argv[]) { case modeNone: case modeInfo: case modeSlideInfo: + case modeAcceleratorInfo: + case modeTextInfo: + case modeLocalSymbols: + case modeExtract: break; } } @@ -595,6 +860,10 @@ int main (int argc, const char* argv[]) { case modeNone: case modeInfo: case modeSlideInfo: + case modeAcceleratorInfo: + case modeTextInfo: + case modeLocalSymbols: + case modeExtract: break; } } @@ -626,7 +895,7 @@ int main (int argc, const char* argv[]) { printf(" 0x%08llX %s\n", it->textSize, it->path); } } - + if ( (options.mode == modeDependencies) && options.dependentsOfPath && !results.dependentTargetFound) { fprintf(stderr, "Error: could not find '%s' in the shared cache at\n %s\n", options.dependentsOfPath, sharedCachePath); exit(1); diff --git a/launch-cache/update_dyld_shared_cache.cpp b/launch-cache/update_dyld_shared_cache.cpp deleted file mode 100644 index 4ab3694..0000000 --- a/launch-cache/update_dyld_shared_cache.cpp +++ /dev/null @@ -1,4033 +0,0 @@ -/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- - * - * Copyright (c) 2006-2011 Apple Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dyld_cache_format.h" - -#include -#include -#include -#include - -#include "Architectures.hpp" -#include "MachOLayout.hpp" -#include "MachORebaser.hpp" -#include "MachOBinder.hpp" -#include "CacheFileAbstraction.hpp" -#include "dyld_cache_config.h" - -#define SELOPT_WRITE -#include "objc-shared-cache.h" - -#define FIRST_DYLIB_TEXT_OFFSET 0x10000 - -#ifndef LC_FUNCTION_STARTS - #define LC_FUNCTION_STARTS 0x26 -#endif - -static bool verbose = false; -static bool progress = false; -static bool iPhoneOS = false; -static bool rootless = true; -static std::vector warnings; - - -static void warn(const char *arch, const char *format, ...) -{ - char *msg; - - va_list args; - va_start(args, format); - ::vasprintf(&msg, format, args); - va_end(args); - - warnings.push_back(msg); - - if ( verbose ) { - ::fprintf(::stderr, "update_dyld_shared_cache: warning: %s%s%s%s\n", - arch ? "for arch " : "", - arch ? arch : "", - arch ? ", " : "", - msg); - } -} - - -class CStringHash { -public: - size_t operator()(const char* __s) const { - size_t __h = 0; - for ( ; *__s; ++__s) - __h = 5 * __h + *__s; - return __h; - }; -}; -class CStringEquals -{ -public: - bool operator()(const char* left, const char* right) const { return (strcmp(left, right) == 0); } -}; - - - -class ArchGraph -{ -public: - typedef std::unordered_map StringToString; - - static void addArchPair(ArchPair ap); - static void addRoot(const char* vpath, const std::set& archs); - static uint64_t maxCacheSizeForArchPair(ArchPair ap); - static void findSharedDylibs(ArchPair ap); - static ArchGraph* graphForArchPair(ArchPair ap) { return fgPerArchGraph[ap]; } - static void setFileSystemRoot(const char* root) { fgFileSystemRoot = root; } - static void setFileSystemOverlay(const std::vector& overlays); - static const char* archName(ArchPair ap); - - ArchPair getArchPair() { return fArchPair; } - std::set& getSharedDylibs() { return fSharedDylibs; } - StringToString& getDylibAliases() { return fAliasesMap; } - const char* archName() { return archName(fArchPair); } - -private: - - class DependencyNode - { - public: - DependencyNode(ArchGraph*, const char* path, const MachOLayoutAbstraction* layout); - void loadDependencies(const MachOLayoutAbstraction*); - void markNeededByRoot(DependencyNode*); - const char* getPath() const { return fPath; } - const MachOLayoutAbstraction* getLayout() const { return fLayout; } - size_t useCount() const { return fRootsDependentOnThis.size(); } - bool allDependentsFound() const { return !fDependentMissing; } - bool dependsOnDylibList() const { return fRootsDependentOnThis.count(const_cast(this)); } - - private: - ArchGraph* fGraph; - const char* fPath; - const MachOLayoutAbstraction* fLayout; - bool fDependenciesLoaded; - bool fDependentMissing; - std::set fDependsOn; - std::set fRootsDependentOnThis; - }; - - typedef std::unordered_map PathToNode; - - - ArchGraph(ArchPair ap) : fArchPair(ap) {} - void addRoot(const char* path, const MachOLayoutAbstraction*); - DependencyNode* getNode(const char* path); - DependencyNode* getNodeForVirtualPath(const char* vpath); - static bool canBeShared(const MachOLayoutAbstraction* layout, ArchPair ap, const std::set& possibleLibs, std::map& shareableMap); - static bool sharable(const MachOLayoutAbstraction* layout, ArchPair ap, char** msg); - - static std::map fgPerArchGraph; - static const char* fgFileSystemRoot; - static std::vector fgFileSystemOverlays; - - ArchPair fArchPair; - std::set fRoots; - PathToNode fNodes; - std::set fSharedDylibs; // use set to avoid duplicates when installname!=realpath - StringToString fAliasesMap; -}; -std::map ArchGraph::fgPerArchGraph; -const char* ArchGraph::fgFileSystemRoot = ""; -std::vector ArchGraph::fgFileSystemOverlays; - -void ArchGraph::addArchPair(ArchPair ap) -{ - //fprintf(stderr, "adding ArchPair 0x%08X,0x%08X\n", ap.arch, ap.subtype); - fgPerArchGraph[ap] = new ArchGraph(ap); -} - -void ArchGraph::setFileSystemOverlay(const std::vector& overlays) -{ - for (std::vector::const_iterator it=overlays.begin(); it != overlays.end(); ++it) - fgFileSystemOverlays.push_back(*it); -} - -void ArchGraph::addRoot(const char* vpath, const std::set& onlyArchs) -{ - //fprintf(stderr, "addRoot(%s)\n", vpath); - char completePath[MAXPATHLEN]; - const char* path = NULL; - // check -overlay path first - for (std::vector::const_iterator it=fgFileSystemOverlays.begin(); it != fgFileSystemOverlays.end(); ++it) { - strcpy(completePath, *it); - strcat(completePath, vpath); // assumes vpath starts with '/' - struct stat stat_buf; - if ( stat(completePath, &stat_buf) == 0 ) { - path = completePath; - break; - } - } - // if not found in overlay, check for -root - if ( (path == NULL) && (fgFileSystemRoot[0] != '\0') ) { - strcpy(completePath, fgFileSystemRoot); - strcat(completePath, vpath); // assumes vpath starts with '/' - struct stat stat_buf; - if ( stat(completePath, &stat_buf) == 0 ) - path = completePath; - } - if ( path == NULL ) - path = vpath; - - try { - //fprintf(stderr, " UniversalMachOLayout::find(%s)\n", path); - const UniversalMachOLayout& uni = UniversalMachOLayout::find(path, &onlyArchs); - for(std::set::iterator ait = onlyArchs.begin(); ait != onlyArchs.end(); ++ait) { - try { - const MachOLayoutAbstraction* layout = uni.getSlice(*ait); - if ( layout != NULL ) - fgPerArchGraph[*ait]->addRoot(path, layout); - } - catch (const char* msg) { - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: warning for %s can't use root '%s': %s\n", fgPerArchGraph[*ait]->archName(), path, msg); - } - - } - } - catch (const char* msg) { - fprintf(stderr, "update_dyld_shared_cache: warning can't use root '%s': %s\n", path, msg); - } -} - - - -void ArchGraph::addRoot(const char* path, const MachOLayoutAbstraction* layout) -{ - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: adding root: %s\n", path); - DependencyNode* node = this->getNode(path); - fRoots.insert(node); - const MachOLayoutAbstraction* mainExecutableLayout = NULL; - if ( layout->getFileType() == MH_EXECUTE ) - mainExecutableLayout = layout; - node->loadDependencies(mainExecutableLayout); - node->markNeededByRoot(node); - if ( layout->getFileType() == MH_DYLIB ) - node->markNeededByRoot(NULL); -} - -// a virtual path does not have the fgFileSystemRoot prefix -ArchGraph::DependencyNode* ArchGraph::getNodeForVirtualPath(const char* vpath) -{ - //fprintf(stderr, "getNodeForVirtualPath(%s)\n", vpath); - char completePath[MAXPATHLEN]; - for (std::vector::const_iterator it=fgFileSystemOverlays.begin(); it != fgFileSystemOverlays.end(); ++it) { - const char* overlayPath = *it; - // using -overlay means if /overlay/path/dylib exists use it, otherwise use /path/dylib - strcpy(completePath, overlayPath); - strcat(completePath, vpath); // assumes vpath starts with '/' - struct stat stat_buf; - if ( stat(completePath, &stat_buf) == 0 ) { - return this->getNode(completePath); - } - // support when install name is a symlink - const char* pathToSymlink = vpath; - if ( fgFileSystemRoot[0] != '\0' ) { - strcpy(completePath, fgFileSystemRoot); - strcat(completePath, vpath); - pathToSymlink = completePath; - } - if ( (lstat(pathToSymlink, &stat_buf) == 0) && S_ISLNK(stat_buf.st_mode) ) { - // requested path did not exist in /overlay, but leaf of path is a symlink in / - char pathInSymLink[MAXPATHLEN]; - size_t res = readlink(pathToSymlink, pathInSymLink, sizeof(pathInSymLink)); - if ( res != -1 ) { - pathInSymLink[res] = '\0'; - if ( pathInSymLink[0] != '/' ) { - char symFullPath[MAXPATHLEN]; - strcpy(symFullPath, vpath); - char* lastSlash = strrchr(symFullPath, '/'); - if ( lastSlash != NULL ) { - strcpy(lastSlash+1, pathInSymLink); - // (re)try looking for what symlink points to, but in /overlay - return this->getNodeForVirtualPath(symFullPath); - } - } - } - } - } - - if ( fgFileSystemRoot[0] != '\0' ) { - // using -root means always use /rootpath/usr/lib - strcpy(completePath, fgFileSystemRoot); - strcat(completePath, vpath); // assumes vpath starts with '/' - return this->getNode(completePath); - } - // not found in -overlay or -root not used - return this->getNode(vpath); -} - -ArchGraph::DependencyNode* ArchGraph::getNode(const char* path) -{ - //fprintf(stderr, "getNode(%s)\n", path); - // look up supplied path to see if node already exists - PathToNode::iterator pos = fNodes.find(path); - if ( pos != fNodes.end() ) - return pos->second; - - // get real path - char realPath[MAXPATHLEN]; - if ( realpath(path, realPath) == NULL ) - throwf("realpath() failed on %s\n", path); - - // look up real path to see if node already exists - pos = fNodes.find(realPath); - if ( pos != fNodes.end() ) { - // update fAliasesMap with symlinks found - const char* aliasPath = path; - if ( (fgFileSystemRoot != NULL) && (strncmp(path, fgFileSystemRoot, strlen(fgFileSystemRoot)) == 0) ) { - aliasPath = &path[strlen(fgFileSystemRoot)]; - } - if ( fAliasesMap.find(aliasPath) == fAliasesMap.end() ) { - if ( strcmp(aliasPath, pos->second->getLayout()->getID().name) != 0 ) { - fAliasesMap[strdup(aliasPath)] = pos->second->getLayout()->getID().name; - //fprintf(stderr, "getNode() %s: added alias %s -> %s\n", archName(fArchPair), aliasPath, fAliasesMap[aliasPath]); - } - } - return pos->second; - } - - // still does not exist, so create a new node - const UniversalMachOLayout& uni = UniversalMachOLayout::find(realPath); - DependencyNode* node = new DependencyNode(this, realPath, uni.getSlice(fArchPair)); - if ( node->getLayout() == NULL ) { - throwf("%s is missing arch %s", realPath, archName(fArchPair)); - } - // add realpath to node map - fNodes[node->getPath()] = node; - // if install name is not real path, add install name to node map - if ( (node->getLayout()->getFileType() == MH_DYLIB) && (strcmp(realPath, node->getLayout()->getID().name) != 0) ) { - //fprintf(stderr, "adding %s node alias %s for %s\n", archName(fArchPair), node->getLayout()->getID().name, realPath); - pos = fNodes.find(node->getLayout()->getID().name); - if ( pos != fNodes.end() ) { - // get uuids of two dylibs to see if this is accidental copy of a dylib or two differnent dylibs with same -install_name - uuid_t uuid1; - uuid_t uuid2; - node->getLayout()->uuid(uuid1); - pos->second->getLayout()->uuid(uuid2); - if ( memcmp(&uuid1, &uuid2, 16) == 0 ) { - // warn if two dylib in cache have same install_name - char* msg; - asprintf(&msg, "update_dyld_shared_cache: warning, found two copies of the same dylib with same install path: %s\n\t%s\n\t%s\n", - node->getLayout()->getID().name, pos->second->getPath(), node->getPath()); - fprintf(stderr, "%s", msg); - warnings.push_back(msg); - } - else { - // update_dyld_shared_cache should fail if two images have same install name - fprintf(stderr, "update_dyld_shared_cache: found two different dylibs with same install path: %s\n\t%s\n\t%s\n", - node->getLayout()->getID().name, pos->second->getPath(), node->getPath()); - exit(1); - } - } - else - fNodes[node->getLayout()->getID().name] = node; - // update fAliasesMap with symlinks found - const char* aliasPath = realPath; - if ( (fgFileSystemRoot != NULL) && (fgFileSystemRoot[0] != '\0') && (strncmp(realPath, fgFileSystemRoot, strlen(fgFileSystemRoot)) == 0) ) { - aliasPath = &realPath[strlen(fgFileSystemRoot)]; - } - // Too many aliases in -overlay mode - for (std::vector::const_iterator it=fgFileSystemOverlays.begin(); it != fgFileSystemOverlays.end(); ++it) { - const char* overlayPath = *it; - if ( strncmp(realPath, overlayPath, strlen(overlayPath)) == 0 ) { - aliasPath = &realPath[strlen(overlayPath)]; - break; - } - } - if ( fAliasesMap.find(aliasPath) == fAliasesMap.end() ) { - if ( strcmp(aliasPath, node->getLayout()->getID().name) != 0 ) { - fAliasesMap[strdup(aliasPath)] = node->getLayout()->getID().name; - //fprintf(stderr, "getNode() %s: added alias %s -> %s\n", archName(fArchPair), aliasPath, fAliasesMap[aliasPath]); - } - } - } - return node; -} - - -void ArchGraph::DependencyNode::loadDependencies(const MachOLayoutAbstraction* mainExecutableLayout) -{ - if ( !fDependenciesLoaded ) { - fDependenciesLoaded = true; - // add dependencies - const std::vector& dependsOn = fLayout->getLibraries(); - for(std::vector::const_iterator it = dependsOn.begin(); it != dependsOn.end(); ++it) { - try { - const char* dependentPath = it->name; - if ( strncmp(dependentPath, "@executable_path/", 17) == 0 ) { - if ( mainExecutableLayout == NULL ) - throw "@executable_path without main executable"; - // expand @executable_path path prefix - const char* executablePath = mainExecutableLayout->getFilePath(); - char newPath[strlen(executablePath) + strlen(dependentPath)+2]; - if ( (fgFileSystemRoot != NULL) && (strncmp(executablePath, fgFileSystemRoot, strlen(fgFileSystemRoot)) == 0) ) { - // executablePath already has rootPath prefix, need to remove that to get to base virtual path - strcpy(newPath, &executablePath[strlen(fgFileSystemRoot)]); - } - else { - strcpy(newPath, executablePath); - } - char* addPoint = strrchr(newPath,'/'); - if ( addPoint != NULL ) - strcpy(&addPoint[1], &dependentPath[17]); - else - strcpy(newPath, &dependentPath[17]); - dependentPath = strdup(newPath); - } - else if ( strncmp(dependentPath, "@loader_path/", 13) == 0 ) { - // expand @loader_path path prefix - char newPath[strlen(fPath) + strlen(dependentPath)+2]; - if ( (fgFileSystemRoot != NULL) && (strncmp(fPath, fgFileSystemRoot, strlen(fgFileSystemRoot)) == 0) ) { - // fPath already has rootPath prefix, need to remove that to get to base virtual path - strcpy(newPath, &fPath[strlen(fgFileSystemRoot)]); - } - else { - strcpy(newPath, fPath); - } - char* addPoint = strrchr(newPath,'/'); - if ( addPoint != NULL ) - strcpy(&addPoint[1], &dependentPath[13]); - else - strcpy(newPath, &dependentPath[13]); - dependentPath = strdup(newPath); - } - else if ( strncmp(dependentPath, "@rpath/", 7) == 0 ) { - throw "@rpath not supported in dyld shared cache"; - } - // silently ignore dependents from main executables that can't be in shared cache - bool addDependent = true; - if ( fLayout->getFileType() == MH_EXECUTE ) { - if ( (strncmp(dependentPath, "/usr/lib/", 9) != 0) && (strncmp(dependentPath, "/System/Library/", 16) != 0) ) { - addDependent = false; - } - } - if ( addDependent ) - fDependsOn.insert(fGraph->getNodeForVirtualPath(dependentPath)); - } - catch (const char* msg) { - if ( it->weakImport || ! fLayout->hasSplitSegInfo() ) { - // ok to ignore missing weak imported dylibs from things that are - // not going to be in the dyld shared cache - } - else { - fprintf(stderr, "warning, could not bind %s because %s\n", fPath, msg); - fDependentMissing = true; - } - } - } - // recurse - for(std::set::iterator it = fDependsOn.begin(); it != fDependsOn.end(); ++it) { - (*it)->loadDependencies(mainExecutableLayout); - } - } -} - -void ArchGraph::DependencyNode::markNeededByRoot(ArchGraph::DependencyNode* rootNode) -{ - if ( fRootsDependentOnThis.count(rootNode) == 0 ) { - fRootsDependentOnThis.insert(rootNode); - for(std::set::iterator it = fDependsOn.begin(); it != fDependsOn.end(); ++it) { - (*it)->markNeededByRoot(rootNode); - } - } -} - - - -ArchGraph::DependencyNode::DependencyNode(ArchGraph* graph, const char* path, const MachOLayoutAbstraction* layout) - : fGraph(graph), fPath(strdup(path)), fLayout(layout), fDependenciesLoaded(false), fDependentMissing(false) -{ - //fprintf(stderr, "new DependencyNode(0x%08X, %s)\n", graph->fArch, path); -} - -uint64_t ArchGraph::maxCacheSizeForArchPair(ArchPair ap) { - switch ( ap.arch ) { - case CPU_TYPE_I386: - return 0x20000000; - case CPU_TYPE_X86_64: - return 0x40000000; - case CPU_TYPE_ARM: - return ARM_SHARED_REGION_SIZE; - case CPU_TYPE_ARM64: - return ARM64_SHARED_REGION_SIZE; - default: return UINT64_MAX; - } -} - -void ArchGraph::findSharedDylibs(ArchPair ap) -{ - const PathToNode& nodes = fgPerArchGraph[ap]->fNodes; - std::set possibleLibs; - std::map layoutToNode; - //fprintf(stderr, "shared for arch %s\n", archName(ap)); - for(PathToNode::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { - DependencyNode* node = it->second; - // put all dylibs in shared cache - not just ones used by more than one app - if ( node->allDependentsFound() /*&& (node->useCount() > 1)*/ ) { - const MachOLayoutAbstraction* layout = node->getLayout(); - if ( layout->isDylib() ) { - char* msg; - if ( sharable(layout, ap, &msg) ) { - possibleLibs.insert(layout); - layoutToNode[layout] = node; - } - else { - if ( !iPhoneOS && (layout->getID().name[0] == '@') ) { - // update_dyld_shared_cache should suppress warnings for embedded frameworks - } - else { - warnings.push_back(msg); - fprintf(stderr, "update_dyld_shared_cache: for arch %s, %s\n", archName(ap), msg); - } - } - } - } - } - - // prune so that all shareable libs depend only on other shareable libs - std::set& sharedLibs = fgPerArchGraph[ap]->fSharedDylibs; - std::map shareableMap; - uint64_t totalLibSize = 0; - for (std::set::iterator lit = possibleLibs.begin(); lit != possibleLibs.end(); ++lit) { - if ( canBeShared(*lit, ap, possibleLibs, shareableMap) ) { - totalLibSize += (*lit)->getVMSize(); - sharedLibs.insert(*lit); - } - } - -#if 0 // disable auto-eviction because it happens before linkedit optimization which means it is overly conservative. - - // Check to see if the unoptimized cache size is too large, if so trim out some libraries - uint64_t maxCacheSize = maxCacheSizeForArchPair(ap); - if (totalLibSize > maxCacheSize) { - fprintf(stderr, "update_dyld_shared_cache: unoptimized %s shared cache overflow, total VM space: %lldMB (max=%lldMB)\n", archName(ap), totalLibSize/(1024*1024), maxCacheSize/(1024*1024)); - std::vector removableLibs; - - for (const MachOLayoutAbstraction* layout : sharedLibs) { - // Every library uses itself, and every MH_DYLIB has an extra useCount, so we know useCount of 2 implies nothing else in the shared cache uses it - if (layoutToNode[layout]->useCount() == 2) { - if ( layoutToNode[layout]->dependsOnDylibList() ) { - removableLibs.push_back(layout); - //fprintf(stderr, " possible to evict: %s\n", layout->getID().name); - } - } - } - - std::sort(removableLibs.begin(), removableLibs.end(), [](const MachOLayoutAbstraction* a, const MachOLayoutAbstraction* b){ - return a->getVMSize() < b->getVMSize(); - }); - - while ( (totalLibSize > maxCacheSize) && !removableLibs.empty() ) { - const MachOLayoutAbstraction* largestRemovableLib = removableLibs.back(); - removableLibs.pop_back(); - if ( largestRemovableLib->getVMSize() > 1024*1024 ) - fprintf(stderr, "update_dyld_shared_cache: evicting % 3lldMB leaf dylib %s\n", largestRemovableLib->getVMSize()/(1024*1024), largestRemovableLib->getID().name); - else - fprintf(stderr, "update_dyld_shared_cache: evicting % 3lldKB leaf dylib %s\n", largestRemovableLib->getVMSize()/1024, largestRemovableLib->getID().name); - sharedLibs.erase(largestRemovableLib); - totalLibSize -= largestRemovableLib->getVMSize(); - } - fprintf(stderr, "update_dyld_shared_cache: unoptimized %s shared cache reduced to total VM space: %lldMB\n", archName(ap), totalLibSize/1024/1024); - } -#endif -} - -const char* ArchGraph::archName(ArchPair ap) -{ - switch ( ap.arch ) { - case CPU_TYPE_I386: - return "i386"; - case CPU_TYPE_X86_64: - switch ( ap.subtype ) { - case CPU_SUBTYPE_X86_64_H: - return "x86_64h"; - default: - return "x86_64"; - } - case CPU_TYPE_ARM: - switch ( ap.subtype ) { - case CPU_SUBTYPE_ARM_V4T: - return "armv4t"; - case CPU_SUBTYPE_ARM_V6: - return "armv6"; - case CPU_SUBTYPE_ARM_V5TEJ: - return "armv5"; - case CPU_SUBTYPE_ARM_XSCALE: - return "arm-xscale"; - case CPU_SUBTYPE_ARM_V7: - return "armv7"; - case CPU_SUBTYPE_ARM_V7F: - return "armv7f"; - case CPU_SUBTYPE_ARM_V7K: - return "armv7k"; - case CPU_SUBTYPE_ARM_V7S: - return "armv7s"; - default: - return "arm"; - } - case CPU_TYPE_ARM64: - return "arm64"; - default: - return "unknown"; - } -} - -bool ArchGraph::sharable(const MachOLayoutAbstraction* layout, ArchPair ap, char** msg) -{ - int trustErr = layout->notTrusted(); - if ( ! layout->isTwoLevelNamespace() ) - asprintf(msg, "can't put %s in shared cache because it was built -flat_namespace", layout->getID().name); - else if ( ! layout->inSharableLocation() ) - asprintf(msg, "can't put %s in shared cache because its -install_name is not in /usr/lib or /System/Library", layout->getID().name); - else if ( ! layout->hasSplitSegInfo() ) - asprintf(msg, "can't put %s in shared cache because it was not built for %s or later", layout->getID().name, (iPhoneOS ? "iPhoneOS 3.1" : "MacOSX 10.5")); - else if ( rootless == true && trustErr != 0 ) - asprintf(msg, "can't put %s in shared cache because it is not trusted: %s", layout->getFilePath(), strerror(trustErr)); - else if ( layout->hasDynamicLookupLinkage() ) - asprintf(msg, "can't put %s in shared cache because it was built with '-undefined dynamic_lookup'", layout->getID().name); - else if ( layout->hasMainExecutableLookupLinkage() ) - asprintf(msg, "can't put %s in shared cache because it was built with '-bundle_loader'", layout->getID().name); - else - return true; - return false; -} - -bool ArchGraph::canBeShared(const MachOLayoutAbstraction* layout, ArchPair ap, const std::set& possibleLibs, std::map& shareableMap) -{ - // check map which is a cache of results - std::map::iterator mapPos = shareableMap.find(layout); - if ( mapPos != shareableMap.end() ) { - return mapPos->second; - } - // see if possible - if ( possibleLibs.count(layout) == 0 ) { - shareableMap[layout] = false; - char* msg; - if ( sharable(layout, ap, &msg) ) - asprintf(&msg, "can't put %s in shared cache, unknown reason", layout->getID().name); - warnings.push_back(msg); - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: for arch %s, %s\n", archName(ap), msg); - return false; - } - // look recursively - shareableMap[layout] = true; // mark this shareable early in case of circular references - const PathToNode& nodes = fgPerArchGraph[ap]->fNodes; - const std::vector& dependents = layout->getLibraries(); - for (std::vector::const_iterator dit = dependents.begin(); dit != dependents.end(); ++dit) { - PathToNode::const_iterator pos = nodes.find(dit->name); - if ( pos == nodes.end() ) { - // path from load command does not match any loaded dylibs, maybe there is a temp symlink - char realPath[MAXPATHLEN]; - if ( realpath(dit->name, realPath) != NULL ) { - if ( nodes.find(realPath) != nodes.end() ) - continue; - } - // handle weak imported dylibs not found - if ( dit->weakImport ) - continue; - shareableMap[layout] = false; - char* msg; - asprintf(&msg, "can't put %s in shared cache because it depends on %s which can't be found", layout->getID().name, dit->name); - warnings.push_back(msg); - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: for arch %s, %s\n", archName(ap), msg); - return false; - } - else { - if ( ! canBeShared(pos->second->getLayout(), ap, possibleLibs, shareableMap) ) { - shareableMap[layout] = false; - char* msg; - asprintf(&msg, "can't put %s in shared cache because it depends on %s which can't be in shared cache", layout->getID().name, dit->name); - warnings.push_back(msg); - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: for arch %s, %s\n", archName(ap), msg); - return false; - } - } - } - return true; -} - - - -class StringPool -{ -public: - StringPool(); - const char* getBuffer(); - uint32_t size(); - uint32_t add(const char* str); - uint32_t addUnique(const char* str); - const char* stringAtIndex(uint32_t) const; - -private: - typedef std::unordered_map StringToOffset; - - char* fBuffer; - uint32_t fBufferAllocated; - uint32_t fBufferUsed; - StringToOffset fUniqueStrings; -}; - - -StringPool::StringPool() - : fBufferUsed(0), fBufferAllocated(128*1024*1024) -{ - fBuffer = (char*)malloc(fBufferAllocated); -} - -uint32_t StringPool::add(const char* str) -{ - uint32_t len = strlen(str); - if ( (fBufferUsed + len + 1) > fBufferAllocated ) { - // grow buffer - throw "string buffer exhausted"; - } - strcpy(&fBuffer[fBufferUsed], str); - uint32_t result = fBufferUsed; - fUniqueStrings[&fBuffer[fBufferUsed]] = result; - fBufferUsed += len+1; - return result; -} - -uint32_t StringPool::addUnique(const char* str) -{ - StringToOffset::iterator pos = fUniqueStrings.find(str); - if ( pos != fUniqueStrings.end() ) - return pos->second; - else { - //fprintf(stderr, "StringPool::addUnique() new string: %s\n", str); - return this->add(str); - } -} - -uint32_t StringPool::size() -{ - return fBufferUsed; -} - -const char* StringPool::getBuffer() -{ - return fBuffer; -} - -const char* StringPool::stringAtIndex(uint32_t index) const -{ - return &fBuffer[index]; -} - - - -struct LocalSymbolInfo -{ - uint32_t dylibOffset; - uint32_t nlistStartIndex; - uint32_t nlistCount; -}; - - -template -class SharedCache -{ -public: - SharedCache(ArchGraph* graph, const char* rootPath, const std::vector& overlayPaths, const char* cacheDir, bool explicitCacheDir, - bool alphaSort, bool verify, bool optimize, uint64_t dyldBaseAddress); - bool update(bool force, bool optimize, bool deleteExistingFirst, int archIndex, - int archCount, bool keepSignatures, bool dontMapLocalSymbols); - void writeCacheFile(const char *cacheFilePath, uint8_t *cacheFileBuffer, uint32_t cacheFileSize, bool deleteOldCache); - static const char* cacheFileSuffix(bool optimized, const char* archName); - - // vm address = address AS WRITTEN into the cache - // mapped address = address AS MAPPED into the update process only - // file offset = offset relative to start of cache file - void * mappedAddressForVMAddress(uint64_t vmaddr); - uint64_t VMAddressForMappedAddress(const void *mapaddr); - uint64_t cacheFileOffsetForVMAddress(uint64_t addr) const; - uint64_t VMAddressForCacheFileOffset(uint64_t addr) const; - - static const char* archName(); - -private: - typedef typename A::P P; - typedef typename A::P::E E; - typedef typename A::P::uint_t pint_t; - - bool notUpToDate(const char* path, unsigned int aliasCount); - bool notUpToDate(const void* cache, unsigned int aliasCount); - uint8_t* optimizeLINKEDIT(bool keepSignatures, bool dontMapLocalSymbols); - void optimizeObjC(std::vector& pointersInData); - - static void getSharedCacheBasAddresses(cpu_type_t arch, uint64_t* baseReadOnly, uint64_t* baseWritable); - static cpu_type_t arch(); - static uint64_t sharedRegionStartAddress(); - static uint64_t sharedRegionSize(); - static uint64_t sharedRegionStartWritableAddress(uint64_t); - static uint64_t sharedRegionStartReadOnlyAddress(uint64_t, uint64_t); - static uint64_t getWritableSegmentNewAddress(uint64_t proposedNewAddress, uint64_t originalAddress, uint64_t executableSlide); - static bool addCacheSlideInfo(); - static uint64_t pathHash(const char*); - - static uint64_t pageAlign(uint64_t addr); - static uint64_t regionAlign(uint64_t addr); - static uint64_t pageAlign4KB(uint64_t addr); - void assignNewBaseAddresses(bool verify); - - struct LayoutInfo { - const MachOLayoutAbstraction* layout; - std::vector aliases; - dyld_cache_image_info info; - }; - - struct ByNameSorter { - bool operator()(const LayoutInfo& left, const LayoutInfo& right) - { return (strcmp(left.layout->getID().name, right.layout->getID().name) < 0); } - }; - - struct ByAddressSorter { - bool operator()(const LayoutInfo& left, const LayoutInfo& right) { - return (left.layout->getSegments()[0].newAddress() < right.layout->getSegments()[0].newAddress()); - } - }; - - struct ByCStringSectionSizeSorter { - bool operator()(const LayoutInfo& left, const LayoutInfo& right) { - const std::vector& segs_l = - left.layout->getSegments(); - const std::vector& segs_r = - right.layout->getSegments(); - if (segs_l.size() == 0 || segs_r.size() == 0) { - // one image has no segments - return segs_l.size() > segs_r.size(); - } - const macho_header

*mh_l = (macho_header

*)segs_l[0].mappedAddress(); - const macho_header

*mh_r = (macho_header

*)segs_r[0].mappedAddress(); - const macho_section

*cstring_l = mh_l->getSection("__TEXT", "__cstring"); - const macho_section

*cstring_r = mh_r->getSection("__TEXT", "__cstring"); - if (!cstring_l || !cstring_r) { - // one image has no cstrings - return cstring_l && !cstring_r; - } - - return cstring_l->size() > cstring_r->size(); - } - }; - - struct Sorter { - Sorter(std::map& map): fMap(map) {} - bool operator()(const LayoutInfo& left, const LayoutInfo& right) { - return (fMap[left.layout] < fMap[right.layout]); - } - private: - std::map& fMap; - }; - - - ArchGraph* fArchGraph; - const bool fVerify; - bool fExistingIsNotUpToDate; - bool fCacheFileInFinalLocation; - const char* fCacheFilePath; - uint8_t* fExistingCacheForVerification; - std::vector fDylibs; - std::vector fDylibAliases; - std::vector fMappings; - std::vector > fUnmappedLocalSymbols; - StringPool fUnmappedLocalsStringPool; - std::vector fLocalSymbolInfos; - uint32_t fHeaderSize; - uint8_t* fInMemoryCache; - uint64_t fDyldBaseAddress; - uint64_t fLinkEditsTotalUnoptimizedSize; - uint64_t fLinkEditsStartAddress; - MachOLayoutAbstraction::Segment* fFirstLinkEditSegment; - uint32_t fOffsetOfBindInfoInCombinedLinkedit; - uint32_t fOffsetOfWeakBindInfoInCombinedLinkedit; - uint32_t fOffsetOfLazyBindInfoInCombinedLinkedit; - uint32_t fOffsetOfExportInfoInCombinedLinkedit; - uint32_t fOffsetOfOldSymbolTableInfoInCombinedLinkedit; - uint32_t fSizeOfOldSymbolTableInfoInCombinedLinkedit; - uint32_t fOffsetOfOldExternalRelocationsInCombinedLinkedit; - uint32_t fSizeOfOldExternalRelocationsInCombinedLinkedit; - uint32_t fOffsetOfOldIndirectSymbolsInCombinedLinkedit; - uint32_t fSizeOfOldIndirectSymbolsInCombinedLinkedit; - uint32_t fOffsetOfOldStringPoolInCombinedLinkedit; - uint32_t fSizeOfOldStringPoolInCombinedLinkedit; - uint32_t fOffsetOfFunctionStartsInCombinedLinkedit; - uint32_t fSizeOfFunctionStartsInCombinedLinkedit; - uint32_t fOffsetOfDataInCodeInCombinedLinkedit; - uint32_t fSizeOfDataInCodeInCombinedLinkedit; - uint32_t fLinkEditsTotalOptimizedSize; - uint32_t fUnmappedLocalSymbolsSize; -}; - - -// Access a section containing a list of pointers -template -class PointerSection -{ - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - SharedCache* const fCache; - const macho_section

* const fSection; - pint_t * const fBase; - pint_t fCount; - -public: - PointerSection(SharedCache* cache, const macho_header

* header, - const char *segname, const char *sectname) - : fCache(cache) - , fSection(header->getSection(segname, sectname)) - , fBase(fSection ? (pint_t *)cache->mappedAddressForVMAddress(fSection->addr()) : 0) - , fCount(fSection ? fSection->size() / sizeof(pint_t) : 0) - { - } - - pint_t count() const { return fCount; } - - pint_t getVMAddress(pint_t index) const { - if (index >= fCount) throwf("index out of range"); - return P::getP(fBase[index]); - } - - T get(pint_t index) const { - return (T)fCache->mappedAddressForVMAddress(getVMAddress(index)); - } - - void setVMAddress(pint_t index, pint_t value) { - if (index >= fCount) throwf("index out of range"); - P::setP(fBase[index], value); - } - - void removeNulls() { - pint_t shift = 0; - for (pint_t i = 0; i < fCount; i++) { - pint_t value = fBase[i]; - if (value) { - fBase[i-shift] = value; - } else { - shift++; - } - } - fCount -= shift; - const_cast*>(fSection)->set_size(fCount * sizeof(pint_t)); - } -}; - -// Access a section containing an array of structures -template -class ArraySection -{ - typedef typename A::P P; - - SharedCache* const fCache; - const macho_section

* const fSection; - T * const fBase; - uint64_t const fCount; - -public: - ArraySection(SharedCache* cache, const macho_header

* header, - const char *segname, const char *sectname) - : fCache(cache) - , fSection(header->getSection(segname, sectname)) - , fBase(fSection ? (T *)cache->mappedAddressForVMAddress(fSection->addr()) : 0) - , fCount(fSection ? fSection->size() / sizeof(T) : 0) - { - } - - uint64_t count() const { return fCount; } - - T& get(uint64_t index) const { - if (index >= fCount) throwf("index out of range"); - return fBase[index]; - } -}; - - -// GrP fixme -#include "ObjCLegacyAbstraction.hpp" -#include "ObjCModernAbstraction.hpp" - - - -template <> cpu_type_t SharedCache::arch() { return CPU_TYPE_I386; } -template <> cpu_type_t SharedCache::arch() { return CPU_TYPE_X86_64; } -template <> cpu_type_t SharedCache::arch() { return CPU_TYPE_ARM; } -template <> cpu_type_t SharedCache::arch() { return CPU_TYPE_ARM64; } - -template <> uint64_t SharedCache::sharedRegionStartAddress() { return 0x90000000; } -template <> uint64_t SharedCache::sharedRegionStartAddress() { return 0x7FFF80000000LL; } -template <> uint64_t SharedCache::sharedRegionStartAddress() { return ARM_SHARED_REGION_START; } -template <> uint64_t SharedCache::sharedRegionStartAddress() { return ARM64_SHARED_REGION_START; } - -template <> uint64_t SharedCache::sharedRegionSize() { return 0x20000000; } -template <> uint64_t SharedCache::sharedRegionSize() { return 0x40000000; } -template <> uint64_t SharedCache::sharedRegionSize() { return ARM_SHARED_REGION_SIZE; } -template <> uint64_t SharedCache::sharedRegionSize() { return ARM64_SHARED_REGION_SIZE; } - -template <> uint64_t SharedCache::sharedRegionStartWritableAddress(uint64_t exEnd) { return exEnd + 0x04000000; } -template <> uint64_t SharedCache::sharedRegionStartWritableAddress(uint64_t exEnd) { return 0x7FFF70000000LL; } -template <> uint64_t SharedCache::sharedRegionStartWritableAddress(uint64_t exEnd) { return (exEnd + 16383) & (-16384); } -template <> uint64_t SharedCache::sharedRegionStartWritableAddress(uint64_t exEnd) { return exEnd; } - -template <> uint64_t SharedCache::sharedRegionStartReadOnlyAddress(uint64_t wrEnd, uint64_t exEnd) { return wrEnd + 0x04000000; } -template <> uint64_t SharedCache::sharedRegionStartReadOnlyAddress(uint64_t wrEnd, uint64_t exEnd){ return exEnd; } -template <> uint64_t SharedCache::sharedRegionStartReadOnlyAddress(uint64_t wrEnd, uint64_t exEnd) { return (wrEnd + 16383) & (-16384); } -template <> uint64_t SharedCache::sharedRegionStartReadOnlyAddress(uint64_t wrEnd, uint64_t exEnd) { return (wrEnd + 16383) & (-16384); } - -template <> const char* SharedCache::archName() { return "i386"; } -template <> const char* SharedCache::archName() { return "x86_64"; } -template <> const char* SharedCache::archName() { return "arm"; } -template <> const char* SharedCache::archName() { return "arm64"; } - -template <> const char* SharedCache::cacheFileSuffix(bool, const char* archName) { return archName; } -template <> const char* SharedCache::cacheFileSuffix(bool, const char* archName){ return archName; } -template <> const char* SharedCache::cacheFileSuffix(bool, const char* archName) { return archName; } -template <> const char* SharedCache::cacheFileSuffix(bool, const char* archName) { return archName; } - -template <> uint64_t SharedCache::pageAlign(uint64_t addr) { return ( (addr + 4095) & (-4096) ); } -template <> uint64_t SharedCache::pageAlign(uint64_t addr) { return ( (addr + 4095) & (-4096) ); } -template <> uint64_t SharedCache::pageAlign(uint64_t addr) { return ( (addr + 4095) & (-4096) ); } -template <> uint64_t SharedCache::pageAlign(uint64_t addr) { return ( (addr + 16383) & (-16384) ); } - -template <> uint64_t SharedCache::regionAlign(uint64_t addr) { return ( (addr + 4095) & (-4096) ); } -template <> uint64_t SharedCache::regionAlign(uint64_t addr) { return ( (addr + 4095) & (-4096) ); } -template <> uint64_t SharedCache::regionAlign(uint64_t addr) { return ( (addr + 16383) & (-16384) ); } -template <> uint64_t SharedCache::regionAlign(uint64_t addr) { return ( (addr + 16383) & (-16384) ); } - - -template -uint64_t SharedCache::pageAlign4KB(uint64_t addr) { return ( (addr + 4095) & (-4096) ); } - -template -SharedCache::SharedCache(ArchGraph* graph, const char* rootPath, const std::vector& overlayPaths, const char* cacheDir, bool explicitCacheDir, bool alphaSort, bool verify, bool optimize, uint64_t dyldBaseAddress) - : fArchGraph(graph), fVerify(verify), fExistingIsNotUpToDate(true), - fCacheFileInFinalLocation(rootPath[0] == '\0'), fCacheFilePath(NULL), - fExistingCacheForVerification(NULL), fDyldBaseAddress(dyldBaseAddress), - fOffsetOfBindInfoInCombinedLinkedit(0), fOffsetOfWeakBindInfoInCombinedLinkedit(0), - fOffsetOfLazyBindInfoInCombinedLinkedit(0), fOffsetOfExportInfoInCombinedLinkedit(0), - fOffsetOfOldSymbolTableInfoInCombinedLinkedit(0), fSizeOfOldSymbolTableInfoInCombinedLinkedit(0), - fOffsetOfOldExternalRelocationsInCombinedLinkedit(0), fSizeOfOldExternalRelocationsInCombinedLinkedit(0), - fOffsetOfOldIndirectSymbolsInCombinedLinkedit(0), fSizeOfOldIndirectSymbolsInCombinedLinkedit(0), - fOffsetOfOldStringPoolInCombinedLinkedit(0), fSizeOfOldStringPoolInCombinedLinkedit(0), - fOffsetOfFunctionStartsInCombinedLinkedit(0), fSizeOfFunctionStartsInCombinedLinkedit(0), - fOffsetOfDataInCodeInCombinedLinkedit(0), fSizeOfDataInCodeInCombinedLinkedit(0), - fUnmappedLocalSymbolsSize(0) -{ - if ( fArchGraph->getArchPair().arch != arch() ) - throwf("SharedCache object is wrong architecture: 0x%08X vs 0x%08X", fArchGraph->getArchPair().arch, arch()); - - // build vector of all shared dylibs - unsigned int aliasCount = 0; - std::set& dylibs = fArchGraph->getSharedDylibs(); - ArchGraph::StringToString& aliases = fArchGraph->getDylibAliases(); - for(std::set::iterator it = dylibs.begin(); it != dylibs.end(); ++it) { - const MachOLayoutAbstraction* lib = *it; - LayoutInfo temp; - temp.layout = lib; - temp.info.address = 0; - temp.info.inode = lib->getInode(); - temp.info.modTime = lib->getLastModTime(); - if ( iPhoneOS ) { - temp.info.inode = pathHash(lib->getID().name); - temp.info.modTime = 0; - } - temp.info.pathFileOffset = lib->getNameFileOffset(); // for now this is the offset within the dylib - for(ArchGraph::StringToString::iterator ait = aliases.begin(); ait != aliases.end(); ++ait) { - if ( strcmp(ait->second, lib->getID().name) == 0 ) { - temp.aliases.push_back(ait->first); - ++aliasCount; - } - } - fDylibs.push_back(temp); - } - - // create path to cache file - char cachePathCanonical[MAXPATHLEN]; - strcpy(cachePathCanonical, cacheDir); - if ( cachePathCanonical[strlen(cachePathCanonical)-1] != '/' ) - strcat(cachePathCanonical, "/"); - strcat(cachePathCanonical, DYLD_SHARED_CACHE_BASE_NAME); - strcat(cachePathCanonical, cacheFileSuffix(optimize, fArchGraph->archName())); - char cachePath[MAXPATHLEN]; - if ( explicitCacheDir ) { - fCacheFilePath = strdup(cachePathCanonical); - } - else if ( overlayPaths.size() == 1 ) { - // if no -cache_dir and exactly on -overlay, write cache file into that overlay dir - strcpy(cachePath, overlayPaths[0]); - strcat(cachePath, "/"); - strcat(cachePath, cachePathCanonical); - fCacheFilePath = strdup(cachePath); - } - else if ( rootPath[0] != '\0' ) { - strcpy(cachePath, rootPath); - strcat(cachePath, "/"); - strcat(cachePath, cachePathCanonical); - fCacheFilePath = strdup(cachePath); - } - else { - fCacheFilePath = strdup(cachePathCanonical); - } - - // If the path we are writing to is trusted then our sources need to be trusted - // Can't update the update_dyld_shared_cache on a non-boot volume - rootless = rootless_check_trusted(fCacheFilePath); - - if ( overlayPaths.size() == 1 ) { - // in overlay mode if there already is a cache file in the overlay, - // check if it is up to date. - struct stat stat_buf; - if ( stat(fCacheFilePath, &stat_buf) == 0 ) { - fExistingIsNotUpToDate = this->notUpToDate(fCacheFilePath, aliasCount); - } - else if ( rootPath[0] != '\0' ) { - // using -root and -overlay, but no cache file in overlay, check one in -root - char cachePathRoot[MAXPATHLEN]; - strcpy(cachePathRoot, rootPath); - strcat(cachePathRoot, "/"); - strcat(cachePathRoot, cachePathCanonical); - fExistingIsNotUpToDate = this->notUpToDate(cachePathRoot, aliasCount); - } - else { - // uisng -overlay, but no cache file in overlay, check one in boot volume - fExistingIsNotUpToDate = this->notUpToDate(cachePathCanonical, aliasCount); - } - } - else { - fExistingIsNotUpToDate = this->notUpToDate(fCacheFilePath, aliasCount); - } - - // sort shared dylibs - if ( verify ) { - // already sorted by notUpToDate() - } - else if ( alphaSort ) { - std::sort(fDylibs.begin(), fDylibs.end(), ByNameSorter()); - } - else { - // random sort for Address Space Randomization - std::map map; - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) - map[it->layout] = arc4random(); - std::sort(fDylibs.begin(), fDylibs.end(), Sorter(map)); - } - - // assign segments in each dylib a new address - this->assignNewBaseAddresses(verify); - - // calculate where string pool offset will start - // calculate cache file header size - fHeaderSize = sizeof(dyld_cache_header) - + fMappings.size()*sizeof(shared_file_mapping_np) - + (fDylibs.size()+aliasCount)*sizeof(dyld_cache_image_info); - const uint64_t baseHeaderSize = fHeaderSize; - //fprintf(stderr, "aliasCount=%d, fHeaderSize=0x%08X\n", aliasCount, fHeaderSize); - // build list of aliases and compute where each ones path string will go - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - for(std::vector::const_iterator ait = it->aliases.begin(); ait != it->aliases.end(); ++ait) { - LayoutInfo temp = *it; - // alias looks just like real dylib, but has a different name string - const char* aliasPath = *ait; - temp.aliases.clear(); - temp.aliases.push_back(aliasPath); - temp.info.pathFileOffset = fHeaderSize; - if ( iPhoneOS ) { - temp.info.inode = pathHash(aliasPath); - temp.info.modTime = 0; - } - fDylibAliases.push_back(temp); - fHeaderSize += strlen(aliasPath)+1; - } - } - std::sort(fDylibAliases.begin(), fDylibAliases.end(), ByNameSorter()); - //fprintf(stderr, "fHeaderSize=0x%08X, fDylibAliases.size()=%lu\n", fHeaderSize, fDylibAliases.size()); - fHeaderSize = pageAlign(fHeaderSize); - - // check that cache we are about to create for verification purposes has same layout as existing cache - if ( verify ) { - // if no existing cache, say so - if ( fExistingCacheForVerification == NULL ) { - throwf("update_dyld_shared_cache[%u] for arch=%s, could not verify because cache file does not exist in /var/db/dyld/\n", - getpid(), fArchGraph->archName()); - } - const dyldCacheHeader* header = (dyldCacheHeader*)fExistingCacheForVerification; - const dyldCacheImageInfo* cacheEntry = (dyldCacheImageInfo*)(fExistingCacheForVerification + header->imagesOffset()); - for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it, ++cacheEntry) { - if ( cacheEntry->address() != it->layout->getSegments()[0].newAddress() ) { - throwf("update_dyld_shared_cache[%u] warning: for arch=%s, could not verify cache because start address of %s is 0x%llX in cache, but should be 0x%llX\n", - getpid(), fArchGraph->archName(), it->layout->getID().name, cacheEntry->address(), it->layout->getSegments()[0].newAddress()); - } - } - } - - - if ( fHeaderSize > FIRST_DYLIB_TEXT_OFFSET ) - throwf("header size overflow: allowed=0x%08X, base=0x%08llX, aliases=0x%08llX", FIRST_DYLIB_TEXT_OFFSET, baseHeaderSize, fHeaderSize-baseHeaderSize); -} - - -template -uint64_t SharedCache::getWritableSegmentNewAddress(uint64_t proposedNewAddress, uint64_t originalAddress, uint64_t executableSlide) -{ - return proposedNewAddress; -} - -template -uint64_t SharedCache::pathHash(const char* path) -{ - uint64_t sum = 0; - for (const char* s=path; *s != '\0'; ++s) - sum += sum*4 + *s; - return sum; -} - - -template -void SharedCache::assignNewBaseAddresses(bool verify) -{ - // first layout TEXT for dylibs - const uint64_t startExecuteAddress = sharedRegionStartAddress(); - uint64_t currentExecuteAddress = startExecuteAddress + FIRST_DYLIB_TEXT_OFFSET; - for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - std::vector& segs = ((MachOLayoutAbstraction*)(it->layout))->getSegments(); - for (int i=0; i < segs.size(); ++i) { - MachOLayoutAbstraction::Segment& seg = segs[i]; - seg.reset(); - if ( seg.executable() && !seg.writable() ) { - // Some dylib require extra alignment - currentExecuteAddress = (currentExecuteAddress + seg.alignment() - 1) & (-seg.alignment()); - // __TEXT segment - if ( it->info.address == 0 ) - it->info.address = currentExecuteAddress; - seg.setNewAddress(currentExecuteAddress); - currentExecuteAddress += pageAlign(seg.size()); - } - } - } - // align __TEXT region - currentExecuteAddress = regionAlign(currentExecuteAddress); - -#define DENSE_PACK 0 - // layout __DATA* segments - std::vector dataSegs; - std::vector dataConstSegs; - std::vector dataDirtySegs; - const uint64_t startWritableAddress = sharedRegionStartWritableAddress(currentExecuteAddress); - uint64_t currentWritableAddress = startWritableAddress; - for (const LayoutInfo& info : fDylibs ) { - for (MachOLayoutAbstraction::Segment& seg : ((MachOLayoutAbstraction*)(info.layout))->getSegments()) { - if ( seg.writable() ) { - if ( seg.executable() ) - throw "found writable and executable segment"; - seg.reset(); - if ( strcmp(seg.name(), "__DATA_CONST") == 0 ) - dataConstSegs.push_back(&seg); - else if ( strcmp(seg.name(), "__DATA_DIRTY") == 0 ) - dataDirtySegs.push_back(&seg); - else - dataSegs.push_back(&seg); - } - } - } - // coalesce all __DATA_CONST segments - for (MachOLayoutAbstraction::Segment* seg : dataConstSegs) { - #if DENSE_PACK - // start segment at needed alignment - currentWritableAddress = (currentWritableAddress + seg->sectionsAlignment() - 1) & (-seg->sectionsAlignment()); - seg->setNewAddress(currentWritableAddress); - // pack together - uint64_t justSectionsSize = seg->sectionsSize(); - currentWritableAddress = seg->newAddress() + justSectionsSize; - seg->setSize(justSectionsSize); - if ( seg->fileSize() > justSectionsSize ) - seg->setFileSize(justSectionsSize); - #else - seg->setNewAddress(currentWritableAddress); - // pack to 4KB pages - currentWritableAddress = pageAlign4KB(seg->newAddress() + seg->size()); - #endif - } - #if DENSE_PACK - currentWritableAddress = pageAlign4KB(currentWritableAddress); - #endif - // coalesce all __DATA segments - for (MachOLayoutAbstraction::Segment* seg : dataSegs) { - #if DENSE_PACK - // start segment at needed alignment - currentWritableAddress = (currentWritableAddress + seg->sectionsAlignment() - 1) & (-seg->sectionsAlignment()); - seg->setNewAddress(currentWritableAddress); - // pack together - uint64_t justSectionsSize = seg->sectionsSize(); - currentWritableAddress = seg->newAddress() + justSectionsSize; - seg->setSize(justSectionsSize); - if ( seg->fileSize() > justSectionsSize ) - seg->setFileSize(justSectionsSize); - #else - seg->setNewAddress(currentWritableAddress); - // pack to 4KB pages - currentWritableAddress = pageAlign4KB(seg->newAddress() + seg->size()); - #endif - } - #if DENSE_PACK - currentWritableAddress = pageAlign4KB(currentWritableAddress); - #endif - // coalesce all __DATA_DIRTY segments - for (MachOLayoutAbstraction::Segment* seg : dataDirtySegs) { - // start segment at needed alignment - currentWritableAddress = (currentWritableAddress + seg->sectionsAlignment() - 1) & (-seg->sectionsAlignment()); - seg->setNewAddress(currentWritableAddress); - // pack together - uint64_t justSectionsSize = seg->sectionsSize(); - currentWritableAddress = seg->newAddress() + justSectionsSize; - seg->setSize(justSectionsSize); - if ( seg->fileSize() > justSectionsSize ) - seg->setFileSize(justSectionsSize); - } - // align __DATA region - currentWritableAddress = regionAlign(currentWritableAddress); - - // layout all read-only (but not LINKEDIT) segments - const uint64_t startReadOnlyAddress = sharedRegionStartReadOnlyAddress(currentWritableAddress, currentExecuteAddress); - uint64_t currentReadOnlyAddress = startReadOnlyAddress; - for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - std::vector& segs = ((MachOLayoutAbstraction*)(it->layout))->getSegments(); - for(int i=0; i < segs.size(); ++i) { - MachOLayoutAbstraction::Segment& seg = segs[i]; - if ( seg.readable() && !seg.writable() && !seg.executable() && (strcmp(seg.name(), "__LINKEDIT") != 0) ) { - // __UNICODE segment - seg.setNewAddress(currentReadOnlyAddress); - currentReadOnlyAddress += pageAlign(seg.size()); - } - } - } - - // layout all LINKEDIT segments at end of all read-only segments - currentReadOnlyAddress = regionAlign(currentReadOnlyAddress); // - fLinkEditsStartAddress = currentReadOnlyAddress; - fFirstLinkEditSegment = NULL; - for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - std::vector& segs = ((MachOLayoutAbstraction*)(it->layout))->getSegments(); - for(int i=0; i < segs.size(); ++i) { - MachOLayoutAbstraction::Segment& seg = segs[i]; - if ( seg.readable() && !seg.writable() && !seg.executable() && (strcmp(seg.name(), "__LINKEDIT") == 0) ) { - if ( fFirstLinkEditSegment == NULL ) - fFirstLinkEditSegment = &seg; - seg.setNewAddress(currentReadOnlyAddress); - currentReadOnlyAddress += pageAlign(seg.size()); - } - } - } - fLinkEditsTotalUnoptimizedSize = pageAlign(currentReadOnlyAddress - fLinkEditsStartAddress); - - // populate large mappings - uint64_t cacheFileOffset = 0; - if ( currentExecuteAddress > startExecuteAddress ) { - shared_file_mapping_np executeMapping; - executeMapping.sfm_address = startExecuteAddress; - executeMapping.sfm_size = currentExecuteAddress - startExecuteAddress; - executeMapping.sfm_file_offset = cacheFileOffset; - executeMapping.sfm_max_prot = VM_PROT_READ | VM_PROT_EXECUTE; - executeMapping.sfm_init_prot = VM_PROT_READ | VM_PROT_EXECUTE; - fMappings.push_back(executeMapping); - cacheFileOffset += executeMapping.sfm_size; - - shared_file_mapping_np writableMapping; - writableMapping.sfm_address = startWritableAddress; - writableMapping.sfm_size = currentWritableAddress - startWritableAddress; - writableMapping.sfm_file_offset = cacheFileOffset; - writableMapping.sfm_max_prot = VM_PROT_READ | VM_PROT_WRITE; - writableMapping.sfm_init_prot = VM_PROT_READ | VM_PROT_WRITE; - fMappings.push_back(writableMapping); - cacheFileOffset += writableMapping.sfm_size; - - // make read-only (contains LINKEDIT segments) last, so it can be cut back when optimized - shared_file_mapping_np readOnlyMapping; - readOnlyMapping.sfm_address = startReadOnlyAddress; - readOnlyMapping.sfm_size = currentReadOnlyAddress - startReadOnlyAddress; - readOnlyMapping.sfm_file_offset = cacheFileOffset; - readOnlyMapping.sfm_max_prot = VM_PROT_READ; - readOnlyMapping.sfm_init_prot = VM_PROT_READ; - fMappings.push_back(readOnlyMapping); - cacheFileOffset += readOnlyMapping.sfm_size; - } - else { - // empty cache - shared_file_mapping_np cacheHeaderMapping; - cacheHeaderMapping.sfm_address = startExecuteAddress; - cacheHeaderMapping.sfm_size = FIRST_DYLIB_TEXT_OFFSET; - cacheHeaderMapping.sfm_file_offset = cacheFileOffset; - cacheHeaderMapping.sfm_max_prot = VM_PROT_READ; - cacheHeaderMapping.sfm_init_prot = VM_PROT_READ; - fMappings.push_back(cacheHeaderMapping); - cacheFileOffset += cacheHeaderMapping.sfm_size; - } -} - - -template -uint64_t SharedCache::cacheFileOffsetForVMAddress(uint64_t vmaddr) const -{ - for(std::vector::const_iterator it = fMappings.begin(); it != fMappings.end(); ++it) { - if ( (it->sfm_address <= vmaddr) && (vmaddr < it->sfm_address+it->sfm_size) ) - return it->sfm_file_offset + vmaddr - it->sfm_address; - } - throwf("address 0x%0llX is not in cache", vmaddr); -} - -template -uint64_t SharedCache::VMAddressForCacheFileOffset(uint64_t offset) const -{ - for(std::vector::const_iterator it = fMappings.begin(); it != fMappings.end(); ++it) { - if ( (it->sfm_file_offset <= offset) && (offset < it->sfm_file_offset+it->sfm_size) ) - return it->sfm_address + offset - it->sfm_file_offset; - } - throwf("offset 0x%0llX is not in cache", offset); -} - -template -void *SharedCache::mappedAddressForVMAddress(uint64_t vmaddr) -{ - if (!vmaddr) return NULL; - else return fInMemoryCache + cacheFileOffsetForVMAddress(vmaddr); -} - -template -uint64_t SharedCache::VMAddressForMappedAddress(const void *mapaddr) -{ - if (!mapaddr) return 0; - uint64_t offset = (uint8_t *)mapaddr - (uint8_t *)fInMemoryCache; - return VMAddressForCacheFileOffset(offset); -} - - -template -bool SharedCache::notUpToDate(const void* cache, unsigned int aliasCount) -{ - dyldCacheHeader* header = (dyldCacheHeader*)cache; - // not valid if header signature is wrong - const char* archPairName = fArchGraph->archName(); - char temp[16]; - strcpy(temp, "dyld_v1 "); - strcpy(&temp[15-strlen(archPairName)], archPairName); - if ( strcmp(header->magic(), temp) != 0 ) { - if ( fVerify ) { - fprintf(stderr, "update_dyld_shared_cache[%u] cannot verify %s because current cache file has invalid header\n", getpid(), archPairName); - return false; - } - else { - fprintf(stderr, "update_dyld_shared_cache[%u] updating cache because current cache file has invalid header\n", getpid()); - return true; - } - } - // not valid if count of images does not match current images needed - if ( header->imagesCount() != (fDylibs.size()+aliasCount) ) { - if ( fVerify ) { - fprintf(stderr, "update_dyld_shared_cache[%u] cannot verify %s because current cache file contains a different set of dylibs\n", getpid(), archPairName); - return false; - } - else { - fprintf(stderr, "update_dyld_shared_cache[%u] updating %s cache because current cache file contains a different set of dylibs\n", getpid(), archPairName); - return true; - } - } - // get end of TEXT region - const dyldCacheFileMapping* textMapping = (dyldCacheFileMapping*)((uint8_t*)cache+sizeof(dyldCacheHeader)); - const uint32_t textSize = textMapping->size(); - - // verify every dylib in constructed graph is in existing cache with same inode and modTime - std::map sortingMap; - const dyldCacheImageInfo* imagesStart = (dyldCacheImageInfo*)((uint8_t*)cache + header->imagesOffset()); - const dyldCacheImageInfo* imagesEnd = &imagesStart[header->imagesCount()]; - for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - bool found = false; - //fprintf(stderr, "inode=0x%llX, mTime=0x%llX, path=%s\n", it->info.inode, it->info.modTime, it->layout->getID().name); - for(const dyldCacheImageInfo* cacheEntry = imagesStart; cacheEntry < imagesEnd; ++cacheEntry) { - if ( fVerify ) { - if ( cacheEntry->pathFileOffset() > textSize ) { - throwf("update_dyld_shared_cache[%u]: for arch=%s, image entries corrupt, bad path offset in %s\n", - getpid(), archPairName, it->layout->getID().name); - } - // in -verify mode, just match by path and warn if file looks different - if ( strcmp((char*)cache+cacheEntry->pathFileOffset(), it->layout->getID().name) == 0 ) { - found = true; - sortingMap[it->layout] = cacheEntry-imagesStart; - if ( (cacheEntry->inode() != it->info.inode) || (cacheEntry->modTime() != it->info.modTime) ) { - fprintf(stderr, "update_dyld_shared_cache[%u] warning: for arch=%s, %s has changed since cache was built\n", - getpid(), archPairName, it->layout->getID().name); - } - break; - } - } - else { - if ( cacheEntry->pathFileOffset() > textSize ) { - // cache corrupt, needs to be regenerated - return true; - } - // in normal update mode, everything has to match for cache to be up-to-date - if ( (cacheEntry->inode() == it->info.inode) - && (cacheEntry->modTime() == it->info.modTime) - && (strcmp((char*)cache+cacheEntry->pathFileOffset(), it->layout->getID().name) == 0) ) { - found = true; - break; - } - } - } - if ( !found ) { - if ( fVerify ) { - throwf("update_dyld_shared_cache[%u] can't verify %s cache because %s is not in existing cache\n", getpid(), archPairName, it->layout->getID().name); - } - else { - fprintf(stderr, "update_dyld_shared_cache[%u] updating %s cache because dylib at %s has changed\n", getpid(), archPairName, it->layout->getID().name); - return true; - } - } - } - // all dylibs in existing cache file match those determined need to be in shared cache - if ( fVerify ) { - // sort fDylibs to match existing cache file so we can compare content - std::sort(fDylibs.begin(), fDylibs.end(), Sorter(sortingMap)); - //fprintf(stderr, "dylibs sorted like existing cache:\n"); - //for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - // fprintf(stderr," %s\n", it->layout->getID().name); - //} - // do regenerate a new cache so we can compare content with existing - return true; - } - else { - // existing cache file is up-to-date, don't need to regenerate - return false; - } -} - - -template -bool SharedCache::notUpToDate(const char* path, unsigned int aliasCount) -{ - // mmap existing cache file - int fd = ::open(path, O_RDONLY); - if ( fd == -1 ) - return true; - struct stat stat_buf; - ::fstat(fd, &stat_buf); - uint32_t cacheFileSize = stat_buf.st_size; - uint32_t cacheAllocatedSize = pageAlign(cacheFileSize); - uint8_t* mappingAddr = NULL; - if ( vm_allocate(mach_task_self(), (vm_address_t*)(&mappingAddr), cacheAllocatedSize, VM_FLAGS_ANYWHERE) != KERN_SUCCESS ) - throwf("can't vm_allocate cache of size %u", cacheFileSize); - // update_dyld_shared_cache -verify finds differences - (void)fcntl(fd, F_NOCACHE, 1); - ssize_t readResult = pread(fd, mappingAddr, cacheFileSize, 0); - if ( readResult != cacheFileSize ) - throwf("can't read all of existing cache file (%lu of %u): %s", readResult, cacheFileSize, path); - ::close(fd); - - // validate it - bool result = this->notUpToDate(mappingAddr, aliasCount); - if ( fVerify ) { - // don't unmap yet, leave so it can be verified later - fExistingCacheForVerification = mappingAddr; - } - else { - // unmap - vm_deallocate(mach_task_self(), (vm_address_t)mappingAddr, cacheAllocatedSize); - if ( verbose && !result ) - fprintf(stderr, "update_dyld_shared_cache: %s is up-to-date\n", path); - } - return result; -} - - - -template -class LinkEditOptimizer -{ -public: - LinkEditOptimizer(const MachOLayoutAbstraction&, const SharedCache&, uint8_t*, StringPool&); - virtual ~LinkEditOptimizer() {} - - void copyBindInfo(uint32_t&); - void copyWeakBindInfo(uint32_t&); - void copyLazyBindInfo(uint32_t&); - void copyExportInfo(uint32_t&); - void copyLocalSymbols(uint32_t symbolTableOffset, uint32_t&, bool dontMapLocalSymbols, - uint8_t* cacheStart, StringPool& unmappedLocalsStringPool, - std::vector >& unmappedSymbols, - std::vector& info); - void copyExportedSymbols(uint32_t symbolTableOffset, uint32_t&); - void copyImportedSymbols(uint32_t symbolTableOffset, uint32_t&); - void copyExternalRelocations(uint32_t& offset); - void copyIndirectSymbolTable(uint32_t& offset); - void copyFunctionStarts(uint32_t& offset); - void copyDataInCode(uint32_t& offset); - void updateLoadCommands(uint64_t newVMAddress, uint64_t size, uint32_t stringPoolOffset, - uint32_t linkEditsFileOffset, bool keepSignatures); - - -protected: - typedef typename A::P P; - typedef typename A::P::E E; - typedef typename A::P::uint_t pint_t; - -private: - - const SharedCache& fSharedCache; - const macho_header

* fHeader; - uint8_t* fNewLinkEditStart; - uint8_t* fLinkEditBase; - const MachOLayoutAbstraction& fLayout; - macho_dyld_info_command

* fDyldInfo; - macho_dysymtab_command

* fDynamicSymbolTable; - macho_linkedit_data_command

* fFunctionStarts; - macho_linkedit_data_command

* fDataInCode; - macho_symtab_command

* fSymbolTableLoadCommand; - const macho_nlist

* fSymbolTable; - const char* fStrings; - StringPool& fNewStringPool; - std::map fOldToNewSymbolIndexes; - uint32_t fBindInfoOffsetIntoNewLinkEdit; - uint32_t fBindInfoSizeInNewLinkEdit; - uint32_t fWeakBindInfoOffsetIntoNewLinkEdit; - uint32_t fWeakBindInfoSizeInNewLinkEdit; - uint32_t fLazyBindInfoOffsetIntoNewLinkEdit; - uint32_t fLazyBindInfoSizeInNewLinkEdit; - uint32_t fExportInfoOffsetIntoNewLinkEdit; - uint32_t fExportInfoSizeInNewLinkEdit; - uint32_t fSymbolTableStartOffsetInNewLinkEdit; - uint32_t fLocalSymbolsStartIndexInNewLinkEdit; - uint32_t fLocalSymbolsCountInNewLinkEdit; - uint32_t fExportedSymbolsStartIndexInNewLinkEdit; - uint32_t fExportedSymbolsCountInNewLinkEdit; - uint32_t fImportSymbolsStartIndexInNewLinkEdit; - uint32_t fImportedSymbolsCountInNewLinkEdit; - uint32_t fExternalRelocationsOffsetIntoNewLinkEdit; - uint32_t fIndirectSymbolTableOffsetInfoNewLinkEdit; - uint32_t fFunctionStartsOffsetInNewLinkEdit; - uint32_t fDataInCodeOffsetInNewLinkEdit; - uint32_t fUnmappedLocalSymbolsStartIndexInNewLinkEdit; - uint32_t fUnmappedLocalSymbolsCountInNewLinkEdit; -}; - - - -template -LinkEditOptimizer::LinkEditOptimizer(const MachOLayoutAbstraction& layout, const SharedCache& sharedCache, uint8_t* newLinkEdit, StringPool& stringPool) - : fSharedCache(sharedCache), fLayout(layout), fLinkEditBase(NULL), fNewLinkEditStart(newLinkEdit), fDyldInfo(NULL), - fDynamicSymbolTable(NULL), fFunctionStarts(NULL), fDataInCode(NULL), - fSymbolTableLoadCommand(NULL), fSymbolTable(NULL), fStrings(NULL), fNewStringPool(stringPool), - fBindInfoOffsetIntoNewLinkEdit(0), fBindInfoSizeInNewLinkEdit(0), - fWeakBindInfoOffsetIntoNewLinkEdit(0), fWeakBindInfoSizeInNewLinkEdit(0), - fLazyBindInfoOffsetIntoNewLinkEdit(0), fLazyBindInfoSizeInNewLinkEdit(0), - fExportInfoOffsetIntoNewLinkEdit(0), fExportInfoSizeInNewLinkEdit(0), - fSymbolTableStartOffsetInNewLinkEdit(0), - fLocalSymbolsStartIndexInNewLinkEdit(0), fLocalSymbolsCountInNewLinkEdit(0), - fExportedSymbolsStartIndexInNewLinkEdit(0), fExportedSymbolsCountInNewLinkEdit(0), - fImportSymbolsStartIndexInNewLinkEdit(0), fImportedSymbolsCountInNewLinkEdit(0), - fExternalRelocationsOffsetIntoNewLinkEdit(0), fIndirectSymbolTableOffsetInfoNewLinkEdit(0), - fFunctionStartsOffsetInNewLinkEdit(0), fDataInCodeOffsetInNewLinkEdit(0), - fUnmappedLocalSymbolsStartIndexInNewLinkEdit(0), fUnmappedLocalSymbolsCountInNewLinkEdit(0) - -{ - fHeader = (const macho_header

*)fLayout.getSegments()[0].mappedAddress(); - - const std::vector& segments = fLayout.getSegments(); - for(std::vector::const_iterator it = segments.begin(); it != segments.end(); ++it) { - const MachOLayoutAbstraction::Segment& seg = *it; - if ( strcmp(seg.name(), "__LINKEDIT") == 0 ) - fLinkEditBase = (uint8_t*)seg.mappedAddress() - seg.fileOffset(); - } - if ( fLinkEditBase == NULL ) - throw "no __LINKEDIT segment"; - - const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)fHeader + sizeof(macho_header

)); - const uint32_t cmd_count = fHeader->ncmds(); - const macho_load_command

* cmd = cmds; - for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd()) { - case LC_SYMTAB: - { - fSymbolTableLoadCommand = (macho_symtab_command

*)cmd; - fSymbolTable = (macho_nlist

*)(&fLinkEditBase[fSymbolTableLoadCommand->symoff()]); - fStrings = (char*)&fLinkEditBase[fSymbolTableLoadCommand->stroff()]; - } - break; - case LC_DYSYMTAB: - fDynamicSymbolTable = (macho_dysymtab_command

*)cmd; - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - fDyldInfo = (macho_dyld_info_command

*)cmd; - break; - case LC_FUNCTION_STARTS: - fFunctionStarts = (macho_linkedit_data_command

*)cmd; - case LC_DATA_IN_CODE: - fDataInCode = (macho_linkedit_data_command

*)cmd; - break; - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } - if ( fSymbolTable == NULL ) - throw "no LC_SYMTAB"; - if ( fDynamicSymbolTable == NULL ) - throw "no LC_DYSYMTAB"; - -} - - -template -class SymbolSorter -{ -public: - typedef typename A::P P; - SymbolSorter(const StringPool& pool) : fStringPool(pool) {} - bool operator()(const macho_nlist

& left, const macho_nlist

& right) { - return (strcmp(fStringPool.stringAtIndex(left.n_strx()) , fStringPool.stringAtIndex(right.n_strx())) < 0); - } - -private: - const StringPool& fStringPool; -}; - - -template -void LinkEditOptimizer::copyBindInfo(uint32_t& offset) -{ - if ( (fDyldInfo != NULL) && (fDyldInfo->bind_off() != 0) ) { - fBindInfoOffsetIntoNewLinkEdit = offset; - fBindInfoSizeInNewLinkEdit = fDyldInfo->bind_size(); - memcpy(fNewLinkEditStart+offset, &fLinkEditBase[fDyldInfo->bind_off()], fDyldInfo->bind_size()); - offset += fDyldInfo->bind_size(); - } -} - -template -void LinkEditOptimizer::copyWeakBindInfo(uint32_t& offset) -{ - if ( (fDyldInfo != NULL) && (fDyldInfo->weak_bind_off() != 0) ) { - fWeakBindInfoOffsetIntoNewLinkEdit = offset; - fWeakBindInfoSizeInNewLinkEdit = fDyldInfo->weak_bind_size(); - memcpy(fNewLinkEditStart+offset, &fLinkEditBase[fDyldInfo->weak_bind_off()], fDyldInfo->weak_bind_size()); - offset += fDyldInfo->weak_bind_size(); - } -} - -template -void LinkEditOptimizer::copyLazyBindInfo(uint32_t& offset) -{ - if ( (fDyldInfo != NULL) && (fDyldInfo->lazy_bind_off() != 0) ) { - fLazyBindInfoOffsetIntoNewLinkEdit = offset; - fLazyBindInfoSizeInNewLinkEdit = fDyldInfo->lazy_bind_size(); - memcpy(fNewLinkEditStart+offset, &fLinkEditBase[fDyldInfo->lazy_bind_off()], fDyldInfo->lazy_bind_size()); - offset += fDyldInfo->lazy_bind_size(); - } -} - -template -void LinkEditOptimizer::copyExportInfo(uint32_t& offset) -{ - if ( (fDyldInfo != NULL) && (fLayout.getDyldInfoExports() != NULL) ) { - fExportInfoOffsetIntoNewLinkEdit = offset; - fExportInfoSizeInNewLinkEdit = fDyldInfo->export_size(); - memcpy(fNewLinkEditStart+offset, fLayout.getDyldInfoExports(), fDyldInfo->export_size()); - offset += fDyldInfo->export_size(); - } -} - - -template -void LinkEditOptimizer::copyLocalSymbols(uint32_t symbolTableOffset, uint32_t& symbolIndex, bool dontMapLocalSymbols, uint8_t* cacheStart, - StringPool& unmappedLocalsStringPool, std::vector >& unmappedSymbols, - std::vector& dylibInfos) -{ - fLocalSymbolsStartIndexInNewLinkEdit = symbolIndex; - LocalSymbolInfo localInfo; - localInfo.dylibOffset = ((uint8_t*)fHeader) - cacheStart; - localInfo.nlistStartIndex = unmappedSymbols.size(); - localInfo.nlistCount = 0; - fSymbolTableStartOffsetInNewLinkEdit = symbolTableOffset + symbolIndex*sizeof(macho_nlist

); - macho_nlist

* const newSymbolTableStart = (macho_nlist

*)(fNewLinkEditStart+symbolTableOffset); - const macho_nlist

* const firstLocal = &fSymbolTable[fDynamicSymbolTable->ilocalsym()]; - const macho_nlist

* const lastLocal = &fSymbolTable[fDynamicSymbolTable->ilocalsym()+fDynamicSymbolTable->nlocalsym()]; - uint32_t oldIndex = fDynamicSymbolTable->ilocalsym(); - for (const macho_nlist

* entry = firstLocal; entry < lastLocal; ++entry, ++oldIndex) { - // don't copy stab symbols - if ( (entry->n_sect() != NO_SECT) && ((entry->n_type() & N_STAB) == 0) ) { - const char* name = &fStrings[entry->n_strx()]; - macho_nlist

* newSymbolEntry = &newSymbolTableStart[symbolIndex]; - *newSymbolEntry = *entry; - if ( dontMapLocalSymbols ) { - // if local in __text, add symbol name to shared cache so backtraces don't have bogus names - if ( entry->n_sect() == 1 ) { - newSymbolEntry->set_n_strx(fNewStringPool.addUnique("")); - ++symbolIndex; - } - // copy local symbol to unmmapped locals area - unmappedSymbols.push_back(*entry); - unmappedSymbols.back().set_n_strx(unmappedLocalsStringPool.addUnique(name)); - } - else { - newSymbolEntry->set_n_strx(fNewStringPool.addUnique(name)); - ++symbolIndex; - } - } - } - fLocalSymbolsCountInNewLinkEdit = symbolIndex - fLocalSymbolsStartIndexInNewLinkEdit; - localInfo.nlistCount = unmappedSymbols.size() - localInfo.nlistStartIndex; - dylibInfos.push_back(localInfo); - //fprintf(stderr, "%u locals starting at %u for %s\n", fLocalSymbolsCountInNewLinkEdit, fLocalSymbolsStartIndexInNewLinkEdit, fLayout.getFilePath()); -} - - -template -void LinkEditOptimizer::copyExportedSymbols(uint32_t symbolTableOffset, uint32_t& symbolIndex) -{ - fExportedSymbolsStartIndexInNewLinkEdit = symbolIndex; - macho_nlist

* const newSymbolTableStart = (macho_nlist

*)(fNewLinkEditStart+symbolTableOffset); - const macho_nlist

* const firstExport = &fSymbolTable[fDynamicSymbolTable->iextdefsym()]; - const macho_nlist

* const lastExport = &fSymbolTable[fDynamicSymbolTable->iextdefsym()+fDynamicSymbolTable->nextdefsym()]; - uint32_t oldIndex = fDynamicSymbolTable->iextdefsym(); - for (const macho_nlist

* entry = firstExport; entry < lastExport; ++entry, ++oldIndex) { - if ( ((entry->n_type() & N_TYPE) == N_SECT) && (strncmp(&fStrings[entry->n_strx()], ".objc_", 6) != 0) - && (strncmp(&fStrings[entry->n_strx()], "$ld$", 4) != 0) ) { - macho_nlist

* newSymbolEntry = &newSymbolTableStart[symbolIndex]; - *newSymbolEntry = *entry; - newSymbolEntry->set_n_strx(fNewStringPool.addUnique(&fStrings[entry->n_strx()])); - fOldToNewSymbolIndexes[oldIndex] = symbolIndex-fLocalSymbolsStartIndexInNewLinkEdit; - ++symbolIndex; - } - } - fExportedSymbolsCountInNewLinkEdit = symbolIndex - fExportedSymbolsStartIndexInNewLinkEdit; - //fprintf(stderr, "%u exports starting at %u for %s\n", fExportedSymbolsCountInNewLinkEdit, fExportedSymbolsStartIndexInNewLinkEdit, fLayout.getFilePath()); - // sort by name, so that dyld does not need a toc - macho_nlist

* newSymbolsStart = &newSymbolTableStart[fExportedSymbolsStartIndexInNewLinkEdit]; - macho_nlist

* newSymbolsEnd = &newSymbolTableStart[fExportedSymbolsStartIndexInNewLinkEdit+fExportedSymbolsCountInNewLinkEdit]; - std::sort(newSymbolsStart, newSymbolsEnd, SymbolSorter(fNewStringPool)); - //for (macho_nlist

* entry = newSymbolsStart; entry < newSymbolsEnd; ++entry) - // fprintf(stderr, "\t%u\t %s\n", (entry-newSymbolsStart)+fExportedSymbolsStartIndexInNewLinkEdit, fNewStringPool.stringAtIndex(entry->n_strx())); -} - - -template -void LinkEditOptimizer::copyImportedSymbols(uint32_t symbolTableOffset, uint32_t& symbolIndex) -{ - fImportSymbolsStartIndexInNewLinkEdit = symbolIndex; - macho_nlist

* const newSymbolTableStart = (macho_nlist

*)(fNewLinkEditStart+symbolTableOffset); - const macho_nlist

* const firstImport = &fSymbolTable[fDynamicSymbolTable->iundefsym()]; - const macho_nlist

* const lastImport = &fSymbolTable[fDynamicSymbolTable->iundefsym()+fDynamicSymbolTable->nundefsym()]; - uint32_t oldIndex = fDynamicSymbolTable->iundefsym(); - for (const macho_nlist

* entry = firstImport; entry < lastImport; ++entry, ++oldIndex) { - if ( ((entry->n_type() & N_TYPE) == N_UNDF) && (strncmp(&fStrings[entry->n_strx()], ".objc_", 6) != 0) ) { - macho_nlist

* newSymbolEntry = &newSymbolTableStart[symbolIndex]; - *newSymbolEntry = *entry; - newSymbolEntry->set_n_strx(fNewStringPool.addUnique(&fStrings[entry->n_strx()])); - fOldToNewSymbolIndexes[oldIndex] = symbolIndex-fLocalSymbolsStartIndexInNewLinkEdit; - ++symbolIndex; - } - } - fImportedSymbolsCountInNewLinkEdit = symbolIndex - fImportSymbolsStartIndexInNewLinkEdit; - //fprintf(stderr, "%u imports starting at %u for %s\n", fImportedSymbolsCountInNewLinkEdit, fImportSymbolsStartIndexInNewLinkEdit, fLayout.getFilePath()); - //macho_nlist

* newSymbolsStart = &((macho_nlist

*)fNewLinkEditStart)[fImportSymbolsStartIndexInNewLinkEdit]; - //macho_nlist

* newSymbolsEnd = &((macho_nlist

*)fNewLinkEditStart)[fImportSymbolsStartIndexInNewLinkEdit+fImportedSymbolsCountInNewLinkEdit]; - //for (macho_nlist

* entry = newSymbolsStart; entry < newSymbolsEnd; ++entry) - // fprintf(stderr, "\t%u\t%s\n", (entry-newSymbolsStart)+fImportSymbolsStartIndexInNewLinkEdit, fNewStringPool.stringAtIndex(entry->n_strx())); -} - - -template -void LinkEditOptimizer::copyExternalRelocations(uint32_t& offset) -{ - fExternalRelocationsOffsetIntoNewLinkEdit = offset; - const macho_relocation_info

* const relocsStart = (macho_relocation_info

*)(&fLinkEditBase[fDynamicSymbolTable->extreloff()]); - const macho_relocation_info

* const relocsEnd = &relocsStart[fDynamicSymbolTable->nextrel()]; - for (const macho_relocation_info

* reloc=relocsStart; reloc < relocsEnd; ++reloc) { - macho_relocation_info

* newReloc = (macho_relocation_info

*)(&fNewLinkEditStart[offset]); - *newReloc = *reloc; - uint32_t newSymbolIndex = fOldToNewSymbolIndexes[reloc->r_symbolnum()]; - //fprintf(stderr, "copyExternalRelocations() old=%d, new=%u name=%s in %s\n", reloc->r_symbolnum(), newSymbolIndex, - // &fStrings[fSymbolTable[reloc->r_symbolnum()].n_strx()], fLayout.getFilePath()); - newReloc->set_r_symbolnum(newSymbolIndex); - offset += sizeof(macho_relocation_info

); - } -} - -template -void LinkEditOptimizer::copyFunctionStarts(uint32_t& offset) -{ - if ( fFunctionStarts != NULL ) { - fFunctionStartsOffsetInNewLinkEdit = offset; - memcpy(&fNewLinkEditStart[offset], &fLinkEditBase[fFunctionStarts->dataoff()], fFunctionStarts->datasize()); - offset += fFunctionStarts->datasize(); - } -} - -template -void LinkEditOptimizer::copyDataInCode(uint32_t& offset) -{ - if ( fDataInCode != NULL ) { - fDataInCodeOffsetInNewLinkEdit = offset; - memcpy(&fNewLinkEditStart[offset], &fLinkEditBase[fDataInCode->dataoff()], fDataInCode->datasize()); - offset += fDataInCode->datasize(); - } -} - - -template -void LinkEditOptimizer::copyIndirectSymbolTable(uint32_t& offset) -{ - fIndirectSymbolTableOffsetInfoNewLinkEdit = offset; - const uint32_t* const indirectTable = (uint32_t*)&this->fLinkEditBase[fDynamicSymbolTable->indirectsymoff()]; - uint32_t* newIndirectTable = (uint32_t*)&fNewLinkEditStart[offset]; - for (int i=0; i < fDynamicSymbolTable->nindirectsyms(); ++i) { - uint32_t oldSymbolIndex = E::get32(indirectTable[i]); - uint32_t newSymbolIndex = oldSymbolIndex; - if ( (oldSymbolIndex != INDIRECT_SYMBOL_ABS) && (oldSymbolIndex != INDIRECT_SYMBOL_LOCAL) ) { - newSymbolIndex = fOldToNewSymbolIndexes[oldSymbolIndex]; - //fprintf(stderr, "copyIndirectSymbolTable() old=%d, new=%u name=%s in %s\n", oldSymbolIndex, newSymbolIndex, - // &fStrings[fSymbolTable[oldSymbolIndex].n_strx()], fLayout.getFilePath()); - } - E::set32(newIndirectTable[i], newSymbolIndex); - } - offset += (fDynamicSymbolTable->nindirectsyms() * 4); -} - -template -void LinkEditOptimizer::updateLoadCommands(uint64_t newVMAddress, uint64_t leSize, uint32_t stringPoolOffset, - uint32_t linkEditsFileOffset, bool keepSignatures) -{ - // set LINKEDIT segment commmand to new merged LINKEDIT - const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)fHeader + sizeof(macho_header

)); - const uint32_t cmd_count = fHeader->ncmds(); - const macho_load_command

* cmd = cmds; - for (uint32_t i = 0; i < cmd_count; ++i) { - if ( cmd->cmd() == macho_segment_command

::CMD ) { - macho_segment_command

* seg = (macho_segment_command

*)cmd; - if ( strcmp(seg->segname(), "__LINKEDIT") == 0 ) { - seg->set_vmaddr(newVMAddress); - seg->set_vmsize(leSize); - seg->set_filesize(leSize); - seg->set_fileoff(linkEditsFileOffset); - } - else { - pint_t oldFileOff = seg->fileoff(); - // don't alter __TEXT until is fixed - if ( strcmp(seg->segname(), "__TEXT") != 0 ) { - // update all other segments fileoff to be offset from start of cache file - seg->set_fileoff(fSharedCache.cacheFileOffsetForVMAddress(seg->vmaddr())); - } - pint_t fileOffsetDelta = seg->fileoff() - oldFileOff; - const MachOLayoutAbstraction::Segment* layoutSeg = fLayout.getSegment(seg->segname()); - if ( layoutSeg != NULL ) { - //if ( seg->filesize() != layoutSeg->fileSize() ) { - // fprintf(stderr, "LC filesize=0x%08llX, trimmed seg file size=0x%08llX, seg=%s, path=%s\n", - // seg->filesize(), layoutSeg->fileSize(), seg->segname(), fLayout.getFilePath()); - //} - //if ( seg->vmsize() != layoutSeg->size() ) { - // fprintf(stderr, "LC vmsize=0x%08llX, trimmed seg size=0x%08llX, seg=%s, path=%s\n", - // seg->vmsize(), layoutSeg->size(), seg->segname(), fLayout.getFilePath()); - //} - seg->set_vmsize(layoutSeg->size()); - seg->set_filesize(layoutSeg->fileSize()); - } - // update all sections in this segment - macho_section

* const sectionsStart = (macho_section

*)((char*)seg + sizeof(macho_segment_command

)); - macho_section

* const sectionsEnd = §ionsStart[seg->nsects()]; - for(macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { - if ( sect->offset() != 0 ) - sect->set_offset(sect->offset()+fileOffsetDelta); - //if ( (sect->flags() & SECTION_TYPE) == S_MOD_INIT_FUNC_POINTERS ) - // fprintf(stderr, "found initializer(s) in %s\n", fLayout.getFilePath()); - } - } - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } - - // update dyld_info with new offsets - if ( fDyldInfo != NULL ) { - fDyldInfo->set_rebase_off(0); - fDyldInfo->set_rebase_size(0); - fDyldInfo->set_bind_off(linkEditsFileOffset+fBindInfoOffsetIntoNewLinkEdit); - fDyldInfo->set_bind_size(fBindInfoSizeInNewLinkEdit); - fDyldInfo->set_weak_bind_off(linkEditsFileOffset+fWeakBindInfoOffsetIntoNewLinkEdit); - fDyldInfo->set_weak_bind_size(fWeakBindInfoSizeInNewLinkEdit); - fDyldInfo->set_lazy_bind_off(linkEditsFileOffset+fLazyBindInfoOffsetIntoNewLinkEdit); - fDyldInfo->set_lazy_bind_size(fLazyBindInfoSizeInNewLinkEdit); - fDyldInfo->set_export_off(linkEditsFileOffset+fExportInfoOffsetIntoNewLinkEdit); - fDyldInfo->set_export_size(fExportInfoSizeInNewLinkEdit); - -// fprintf(stderr, "dylib %s\n", fLayout.getFilePath()); -// fprintf(stderr, " bind_off=0x%08X\n", fDyldInfo->bind_off()); -// fprintf(stderr, " export_off=0x%08X\n", fDyldInfo->export_off()); -// fprintf(stderr, " export_size=%d\n", fDyldInfo->export_size()); - - } - - // update symbol table and dynamic symbol table with new offsets - fSymbolTableLoadCommand->set_symoff(linkEditsFileOffset+fSymbolTableStartOffsetInNewLinkEdit); - fSymbolTableLoadCommand->set_nsyms(fLocalSymbolsCountInNewLinkEdit+fExportedSymbolsCountInNewLinkEdit+fImportedSymbolsCountInNewLinkEdit); - fSymbolTableLoadCommand->set_stroff(linkEditsFileOffset+stringPoolOffset); - fSymbolTableLoadCommand->set_strsize(fNewStringPool.size()); - fDynamicSymbolTable->set_ilocalsym(0); - fDynamicSymbolTable->set_nlocalsym(fLocalSymbolsCountInNewLinkEdit); - fDynamicSymbolTable->set_iextdefsym(fExportedSymbolsStartIndexInNewLinkEdit-fLocalSymbolsStartIndexInNewLinkEdit); - fDynamicSymbolTable->set_nextdefsym(fExportedSymbolsCountInNewLinkEdit); - fDynamicSymbolTable->set_iundefsym(fImportSymbolsStartIndexInNewLinkEdit-fLocalSymbolsStartIndexInNewLinkEdit); - fDynamicSymbolTable->set_nundefsym(fImportedSymbolsCountInNewLinkEdit); - fDynamicSymbolTable->set_tocoff(0); - fDynamicSymbolTable->set_ntoc(0); - fDynamicSymbolTable->set_modtaboff(0); - fDynamicSymbolTable->set_nmodtab(0); - fDynamicSymbolTable->set_indirectsymoff(linkEditsFileOffset+fIndirectSymbolTableOffsetInfoNewLinkEdit); - fDynamicSymbolTable->set_extreloff(linkEditsFileOffset+fExternalRelocationsOffsetIntoNewLinkEdit); - fDynamicSymbolTable->set_locreloff(0); - fDynamicSymbolTable->set_nlocrel(0); - - // update function starts - if ( fFunctionStarts != NULL ) { - fFunctionStarts->set_dataoff(linkEditsFileOffset+fFunctionStartsOffsetInNewLinkEdit); - } - // update data-in-code info - if ( fDataInCode != NULL ) { - fDataInCode->set_dataoff(linkEditsFileOffset+fDataInCodeOffsetInNewLinkEdit); - } - - // now remove load commands no longer needed - const macho_load_command

* srcCmd = cmds; - macho_load_command

* dstCmd = (macho_load_command

*)cmds; - int32_t newCount = 0; - for (uint32_t i = 0; i < cmd_count; ++i) { - uint32_t cmdSize = srcCmd->cmdsize(); - switch ( srcCmd->cmd() ) { - case LC_SEGMENT_SPLIT_INFO: - case LC_DYLIB_CODE_SIGN_DRS: - case LC_RPATH: - // don't copy - break; - case LC_CODE_SIGNATURE: - if ( !keepSignatures ) - break; - // otherwise fall into copy case - default: - memmove(dstCmd, srcCmd, cmdSize); - dstCmd = (macho_load_command

*)(((uint8_t*)dstCmd)+cmdSize); - ++newCount; - break; - } - srcCmd = (const macho_load_command

*)(((uint8_t*)srcCmd)+cmdSize); - } - // zero out stuff removed - bzero(dstCmd, (uint8_t*)srcCmd - (uint8_t*)dstCmd); - - // update mach_header - macho_header

* writableHeader = (macho_header

*)fHeader; - writableHeader->set_ncmds(newCount); - writableHeader->set_sizeofcmds((uint8_t*)dstCmd - ((uint8_t*)fHeader + sizeof(macho_header

))); - - // this invalidates some ivars - fDynamicSymbolTable = NULL; - fSymbolTableLoadCommand = NULL; - fDyldInfo = NULL; - fSymbolTable = NULL; - fStrings = NULL; -} - - - -template -uint8_t* SharedCache::optimizeLINKEDIT(bool keepSignatures, bool dontMapLocalSymbols) -{ - // allocate space for optimized LINKEDIT area - uint8_t* newLinkEdit = new uint8_t[fLinkEditsTotalUnoptimizedSize]; - bzero(newLinkEdit, fLinkEditsTotalUnoptimizedSize); - - // make a string pool - StringPool stringPool; - - // create optimizer object for each LINKEDIT segment - std::vector*> optimizers; - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - optimizers.push_back(new LinkEditOptimizer(*it->layout, *this, newLinkEdit, stringPool)); - } - - // rebase info is not copied because images in shared cache are never rebased - - // copy weak bind info - uint32_t offset = 0; - fOffsetOfWeakBindInfoInCombinedLinkedit = offset; - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyWeakBindInfo(offset); - } - - // copy export info - fOffsetOfExportInfoInCombinedLinkedit = offset; - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyExportInfo(offset); - } - - // copy bind info - fOffsetOfBindInfoInCombinedLinkedit = offset; - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyBindInfo(offset); - } - - // copy lazy bind info - fOffsetOfLazyBindInfoInCombinedLinkedit = offset; - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyLazyBindInfo(offset); - } - - // copy symbol table entries - fOffsetOfOldSymbolTableInfoInCombinedLinkedit = offset; - uint32_t symbolTableOffset = offset; - uint32_t symbolTableIndex = 0; - if ( dontMapLocalSymbols ) - fUnmappedLocalSymbols.reserve(16384); - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyLocalSymbols(symbolTableOffset, symbolTableIndex, dontMapLocalSymbols, fInMemoryCache, - fUnmappedLocalsStringPool, fUnmappedLocalSymbols, fLocalSymbolInfos); - (*it)->copyExportedSymbols(symbolTableOffset, symbolTableIndex); - (*it)->copyImportedSymbols(symbolTableOffset, symbolTableIndex); - } - fSizeOfOldSymbolTableInfoInCombinedLinkedit = symbolTableIndex * sizeof(macho_nlist); - offset = symbolTableOffset + fSizeOfOldSymbolTableInfoInCombinedLinkedit & (-8); - - // copy external relocations, 8-byte aligned after end of symbol table - fOffsetOfOldExternalRelocationsInCombinedLinkedit = offset; - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyExternalRelocations(offset); - } - fSizeOfOldExternalRelocationsInCombinedLinkedit = offset - fOffsetOfOldExternalRelocationsInCombinedLinkedit; - - // copy function starts - fOffsetOfFunctionStartsInCombinedLinkedit = offset; - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyFunctionStarts(offset); - } - fSizeOfFunctionStartsInCombinedLinkedit = offset - fOffsetOfFunctionStartsInCombinedLinkedit; - - // copy data-in-code info - fOffsetOfDataInCodeInCombinedLinkedit = offset; - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyDataInCode(offset); - } - fSizeOfDataInCodeInCombinedLinkedit = offset - fOffsetOfDataInCodeInCombinedLinkedit; - - // copy indirect symbol tables - fOffsetOfOldIndirectSymbolsInCombinedLinkedit = offset; - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->copyIndirectSymbolTable(offset); - } - fSizeOfOldIndirectSymbolsInCombinedLinkedit = offset - fOffsetOfOldIndirectSymbolsInCombinedLinkedit; - - // copy string pool - fOffsetOfOldStringPoolInCombinedLinkedit = offset; - memcpy(&newLinkEdit[offset], stringPool.getBuffer(), stringPool.size()); - fSizeOfOldStringPoolInCombinedLinkedit = stringPool.size(); - - // total new size round up to page size - fLinkEditsTotalOptimizedSize = pageAlign(fOffsetOfOldStringPoolInCombinedLinkedit + fSizeOfOldStringPoolInCombinedLinkedit); - - // choose new linkedit file offset - uint32_t linkEditsFileOffset = cacheFileOffsetForVMAddress(fLinkEditsStartAddress); -// uint32_t linkEditsFileOffset = fLinkEditsStartAddress - sharedRegionStartAddress(); - - // update load commands so that all dylibs shared different areas of the same LINKEDIT segment - for(typename std::vector*>::iterator it = optimizers.begin(); it != optimizers.end(); ++it) { - (*it)->updateLoadCommands(fLinkEditsStartAddress, fLinkEditsTotalOptimizedSize, fOffsetOfOldStringPoolInCombinedLinkedit, linkEditsFileOffset, keepSignatures); - } - - //fprintf(stderr, "fLinkEditsTotalUnoptimizedSize=%llu, fLinkEditsTotalOptimizedSize=%u\n", fLinkEditsTotalUnoptimizedSize, fLinkEditsTotalOptimizedSize); - //printf(stderr, "mega link edit mapped starting at: %p\n", fFirstLinkEditSegment->mappedAddress()); - - // overwrite mapped LINKEDIT area with new optimized LINKEDIT segment - memcpy(fFirstLinkEditSegment->mappedAddress(), newLinkEdit, fLinkEditsTotalUnoptimizedSize); - - // update all LINKEDIT Segment objects to point to same merged LINKEDIT area - for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - std::vector& segs = ((MachOLayoutAbstraction*)(it->layout))->getSegments(); - for(int i=0; i < segs.size(); ++i) { - MachOLayoutAbstraction::Segment& seg = segs[i]; - if ( !seg.writable() && !seg.executable() && (strcmp(seg.name(), "__LINKEDIT") == 0) ) { - seg.setNewAddress(fLinkEditsStartAddress); - seg.setMappedAddress(fFirstLinkEditSegment->mappedAddress()); - seg.setSize(fLinkEditsTotalOptimizedSize); - seg.setFileSize(fLinkEditsTotalOptimizedSize); - seg.setFileOffset(linkEditsFileOffset); - } - } - } - - // return new end of cache - return (uint8_t*)fFirstLinkEditSegment->mappedAddress() + regionAlign(fLinkEditsTotalOptimizedSize); -} - - -template -class ObjCSelectorUniquer -{ -private: - objc_opt::string_map fSelectorStrings; - SharedCache *fCache; - size_t fCount; - -public: - - ObjCSelectorUniquer(SharedCache *newCache) - : fSelectorStrings() - , fCache(newCache) - , fCount(0) - { } - - typename A::P::uint_t visit(typename A::P::uint_t oldValue) - { - fCount++; - const char *s = (const char *) - fCache->mappedAddressForVMAddress(oldValue); - objc_opt::string_map::iterator element = - fSelectorStrings.insert(objc_opt::string_map::value_type(s, oldValue)).first; - return (typename A::P::uint_t)element->second; - } - - objc_opt::string_map& strings() { - return fSelectorStrings; - } - - size_t count() const { return fCount; } -}; - - -template -class ClassListBuilder -{ -private: - typedef typename A::P P; - - objc_opt::string_map fClassNames; - objc_opt::class_map fClasses; - size_t fCount; - HeaderInfoOptimizer& fHinfos; - -public: - - ClassListBuilder(HeaderInfoOptimizer& hinfos) - : fClassNames() - , fClasses() - , fCount(0) - , fHinfos(hinfos) - { } - - void visitClass(SharedCache* cache, - const macho_header

* header, - objc_class_t* cls) - { - if (cls->isMetaClass(cache)) return; - - const char *name = cls->getName(cache); - uint64_t name_vmaddr = cache->VMAddressForMappedAddress(name); - uint64_t cls_vmaddr = cache->VMAddressForMappedAddress(cls); - uint64_t hinfo_vmaddr = cache->VMAddressForMappedAddress(fHinfos.hinfoForHeader(cache, header)); - fClassNames.insert(objc_opt::string_map::value_type(name, name_vmaddr)); - fClasses.insert(objc_opt::class_map::value_type(name, std::pair(cls_vmaddr, hinfo_vmaddr))); - fCount++; - } - - objc_opt::string_map& classNames() { - return fClassNames; - } - - objc_opt::class_map& classes() { - return fClasses; - } - - size_t count() const { return fCount; } -}; - - -template -class ProtocolOptimizer -{ -private: - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - objc_opt::string_map fProtocolNames; - objc_opt::protocol_map fProtocols; - size_t fProtocolCount; - size_t fProtocolReferenceCount; - - friend class ProtocolReferenceWalker>; - pint_t visitProtocolReference(SharedCache* cache, pint_t oldValue) - { - objc_protocol_t* proto = (objc_protocol_t*) - cache->mappedAddressForVMAddress(oldValue); - pint_t newValue = fProtocols[proto->getName(cache)]; - if (oldValue != newValue) fProtocolReferenceCount++; - return newValue; - } - -public: - - ProtocolOptimizer() - : fProtocolNames() - , fProtocols() - , fProtocolCount(0) - , fProtocolReferenceCount(0) - { } - - void addProtocols(SharedCache* cache, - const macho_header

* header) - { - PointerSection *> - protocols(cache, header, "__DATA", "__objc_protolist"); - - for (pint_t i = 0; i < protocols.count(); i++) { - objc_protocol_t *proto = protocols.get(i); - - const char *name = proto->getName(cache); - if (fProtocolNames.count(name) == 0) { - // Need a Swift demangler API in OS before we can handle this - if (0 == strncmp(name, "_TtP", 4)) { - throw "objc protocol has Swift name"; - } - if (proto->getSize() > sizeof(objc_protocol_t)) { - throw "objc protocol is too big"; - } - - uint64_t name_vmaddr = cache->VMAddressForMappedAddress(name); - uint64_t proto_vmaddr = cache->VMAddressForMappedAddress(proto); - fProtocolNames.insert(objc_opt::string_map::value_type(name, name_vmaddr)); - fProtocols.insert(objc_opt::protocol_map::value_type(name, proto_vmaddr)); - fProtocolCount++; - } - } - } - - const char *writeProtocols(SharedCache* cache, - uint8_t *& dest, size_t& remaining, - std::vector& pointersInData, - pint_t protocolClassVMAddr) - { - if (fProtocolCount == 0) return NULL; - - if (protocolClassVMAddr == 0) { - return "libobjc's Protocol class symbol not found (metadata not optimized)"; - } - - size_t required = fProtocolCount * sizeof(objc_protocol_t); - if (remaining < required) { - return "libobjc's read-write section is too small (metadata not optimized)"; - } - - for (objc_opt::protocol_map::iterator iter = fProtocols.begin(); - iter != fProtocols.end(); - ++iter) - { - objc_protocol_t* oldProto = (objc_protocol_t*) - cache->mappedAddressForVMAddress(iter->second); - - // Create a new protocol object. - objc_protocol_t* proto = (objc_protocol_t*)dest; - dest += sizeof(*proto); - remaining -= sizeof(*proto); - - // Initialize it. - uint32_t oldSize = oldProto->getSize(); - memcpy(proto, oldProto, oldSize); - if (!proto->getIsaVMAddr()) { - proto->setIsaVMAddr(protocolClassVMAddr); - } - if (oldSize < sizeof(*proto)) { - // Protocol object is old. Populate new fields. - proto->setSize(sizeof(objc_protocol_t)); - // missing extendedMethodTypes is already nil - } - // Some protocol objects are big enough to have the - // demangledName field but don't initialize it. - if (! proto->getDemangledName(cache)) { - proto->setDemangledName(cache, proto->getName(cache)); - } - proto->setFixedUp(); - - // Redirect the protocol table at our new object. - iter->second = cache->VMAddressForMappedAddress(proto); - - // Add new rebase entries. - proto->addPointers(pointersInData); - } - - return NULL; - } - - void updateReferences(SharedCache* cache, const macho_header

* header) - { - ProtocolReferenceWalker> refs(*this); - refs.walk(cache, header); - } - - objc_opt::string_map& protocolNames() { - return fProtocolNames; - } - - objc_opt::protocol_map& protocols() { - return fProtocols; - } - - size_t protocolCount() const { return fProtocolCount; } - size_t protocolReferenceCount() const { return fProtocolReferenceCount; } -}; - - -static int percent(size_t num, size_t denom) { - if (denom) return (int)(num / (double)denom * 100); - else return 100; -} - -template -void SharedCache::optimizeObjC(std::vector& pointersInData) -{ - const char *err; - - if ( verbose ) { - fprintf(stderr, "update_dyld_shared_cache: for %s, optimizing objc metadata\n", archName()); - } - - size_t headerSize = P::round_up(sizeof(objc_opt::objc_opt_t)); - if (headerSize != sizeof(objc_opt::objc_opt_t)) { - warn(archName(), "libobjc's optimization structure size is wrong (metadata not optimized)"); - } - - // Find libobjc's empty sections to fill in. - // Find libobjc's list of pointers for us to use. - const macho_section

*optROSection = NULL; - const macho_section

*optRWSection = NULL; - const macho_section

*optPointerListSection = NULL; - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - if ( strstr(it->layout->getFilePath(), "/libobjc.") != NULL ) { - const macho_header

* mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - optROSection = mh->getSection("__TEXT", "__objc_opt_ro"); - optRWSection = mh->getSection("__DATA", "__objc_opt_rw"); - optPointerListSection = mh->getSection("__DATA", "__objc_opt_ptrs"); - break; - } - } - - if ( optROSection == NULL ) { - warn(archName(), "libobjc's read-only section missing (metadata not optimized)"); - return; - } - - if ( optRWSection == NULL ) { - warn(archName(), "libobjc's read/write section missing (metadata not optimized)"); - return; - } - - if ( optPointerListSection == NULL ) { - warn(archName(), "libobjc's pointer list section missing (metadata not optimized)"); - return; - } - - uint8_t* optROData = (uint8_t*)mappedAddressForVMAddress(optROSection->addr()); - size_t optRORemaining = optROSection->size(); - - uint8_t* optRWData = (uint8_t*)mappedAddressForVMAddress(optRWSection->addr()); - size_t optRWRemaining = optRWSection->size(); - - if (optRORemaining < headerSize) { - warn(archName(), "libobjc's read-only section is too small (metadata not optimized)"); - return; - } - objc_opt::objc_opt_t* optROHeader = (objc_opt::objc_opt_t *)optROData; - optROData += headerSize; - optRORemaining -= headerSize; - - if (E::get32(optROHeader->version) != objc_opt::VERSION) { - warn(archName(), "libobjc's read-only section version is unrecognized (metadata not optimized)"); - return; - } - - if (optPointerListSection->size() < sizeof(objc_opt::objc_opt_pointerlist_tt)) { - warn(archName(), "libobjc's pointer list section is too small (metadata not optimized)"); - return; - } - const objc_opt::objc_opt_pointerlist_tt *optPointerList = (const objc_opt::objc_opt_pointerlist_tt *)mappedAddressForVMAddress(optPointerListSection->addr()); - - // Write nothing to optROHeader until everything else is written. - // If something fails below, libobjc will not use the section. - - // Find objc-containing dylibs - std::vector objcDylibs; - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - macho_header

*mh = (macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - if (mh->getSection("__DATA", "__objc_imageinfo") || mh->getSegment("__OBJC")) { - objcDylibs.push_back(*it); - } - } - - // Build image list - - // This is SAFE: the binaries themselves are unmodified. - - std::vector addressSortedDylibs = objcDylibs; - std::sort(addressSortedDylibs.begin(), addressSortedDylibs.end(), ByAddressSorter()); - - uint64_t hinfoVMAddr = optRWSection->addr() + optRWSection->size() - optRWRemaining; - HeaderInfoOptimizer hinfoOptimizer; - err = hinfoOptimizer.init(objcDylibs.size(), optRWData, optRWRemaining); - if (err) { - warn(archName(), err); - return; - } - for(typename std::vector::const_iterator it = addressSortedDylibs.begin(); it != addressSortedDylibs.end(); ++it) { - const macho_header

*mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - hinfoOptimizer.update(this, mh, pointersInData); - } - - - // Update selector references and build selector list - - // This is SAFE: if we run out of room for the selector table, - // the modified binaries are still usable. - - // Heuristic: choose selectors from libraries with more cstring data first. - // This tries to localize selector cstring memory. - ObjCSelectorUniquer uniq(this); - std::vector sizeSortedDylibs = objcDylibs; - std::sort(sizeSortedDylibs.begin(), sizeSortedDylibs.end(), ByCStringSectionSizeSorter()); - - SelectorOptimizer > selOptimizer(uniq); - for(typename std::vector::const_iterator it = sizeSortedDylibs.begin(); it != sizeSortedDylibs.end(); ++it) { - const macho_header

*mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - LegacySelectorUpdater >::update(this, mh, uniq); - selOptimizer.optimize(this, mh); - } - - uint64_t seloptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining; - objc_opt::objc_selopt_t *selopt = new(optROData) objc_opt::objc_selopt_t; - err = selopt->write(seloptVMAddr, optRORemaining, uniq.strings()); - if (err) { - warn(archName(), err); - return; - } - optROData += selopt->size(); - optRORemaining -= selopt->size(); - selopt->byteswap(E::little_endian), selopt = NULL; - - - // Build class table. - - // This is SAFE: the binaries themselves are unmodified. - - ClassListBuilder classes(hinfoOptimizer); - ClassWalker< A, ClassListBuilder > classWalker(classes); - for(typename std::vector::const_iterator it = sizeSortedDylibs.begin(); it != sizeSortedDylibs.end(); ++it) { - const macho_header

*mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - classWalker.walk(this, mh); - } - - uint64_t clsoptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining; - objc_opt::objc_clsopt_t *clsopt = new(optROData) objc_opt::objc_clsopt_t; - err = clsopt->write(clsoptVMAddr, optRORemaining, - classes.classNames(), classes.classes(), verbose); - if (err) { - warn(archName(), err); - return; - } - optROData += clsopt->size(); - optRORemaining -= clsopt->size(); - size_t duplicateCount = clsopt->duplicateCount(); - clsopt->byteswap(E::little_endian), clsopt = NULL; - - - // Sort method lists. - - // This is SAFE: modified binaries are still usable as unsorted lists. - // This must be done AFTER uniquing selectors. - - MethodListSorter methodSorter; - for(typename std::vector::const_iterator it = sizeSortedDylibs.begin(); it != sizeSortedDylibs.end(); ++it) { - macho_header

*mh = (macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - methodSorter.optimize(this, mh); - } - - - // Unique protocols and build protocol table. - - // This is SAFE: no protocol references are updated yet - // This must be done AFTER updating method lists. - - ProtocolOptimizer protocolOptimizer; - for(typename std::vector::const_iterator it = sizeSortedDylibs.begin(); it != sizeSortedDylibs.end(); ++it) { - const macho_header

*mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - protocolOptimizer.addProtocols(this, mh); - } - - pint_t protocolClassVMAddr = P::getP(optPointerList->protocolClass); - err = protocolOptimizer.writeProtocols(this, optRWData, optRWRemaining, - pointersInData, protocolClassVMAddr); - if (err) { - warn(archName(), err); - return; - } - - uint64_t protocoloptVMAddr = optROSection->addr() + optROSection->size() - optRORemaining; - objc_opt::objc_protocolopt_t *protocolopt = new(optROData) objc_opt::objc_protocolopt_t; - err = protocolopt->write(protocoloptVMAddr, optRORemaining, - protocolOptimizer.protocolNames(), - protocolOptimizer.protocols(), verbose); - if (err) { - warn(archName(), err); - return; - } - optROData += protocolopt->size(); - optRORemaining -= protocolopt->size(); - protocolopt->byteswap(E::little_endian), protocolopt = NULL; - - - // Redirect protocol references to the uniqued protocols. - - // This is SAFE: the new protocol objects are still usable as-is. - for(typename std::vector::const_iterator it = sizeSortedDylibs.begin(); it != sizeSortedDylibs.end(); ++it) { - const macho_header

*mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - protocolOptimizer.updateReferences(this, mh); - } - - - // Repair ivar offsets. - - // This is SAFE: the runtime always validates ivar offsets at runtime. - - IvarOffsetOptimizer ivarOffsetOptimizer; - for(typename std::vector::const_iterator it = sizeSortedDylibs.begin(); it != sizeSortedDylibs.end(); ++it) { - const macho_header

*mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - ivarOffsetOptimizer.findGCClasses(this, mh); - } - for(typename std::vector::const_iterator it = sizeSortedDylibs.begin(); it != sizeSortedDylibs.end(); ++it) { - const macho_header

*mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - ivarOffsetOptimizer.optimize(this, mh); - } - - - // Success. Mark dylibs as optimized. - for(typename std::vector::const_iterator it = sizeSortedDylibs.begin(); it != sizeSortedDylibs.end(); ++it) { - const macho_header

*mh = (const macho_header

*)(*it->layout).getSegments()[0].mappedAddress(); - const macho_section

*imageInfoSection; - imageInfoSection = mh->getSection("__DATA", "__objc_imageinfo"); - if (!imageInfoSection) { - imageInfoSection = mh->getSection("__OBJC", "__image_info"); - } - if (imageInfoSection) { - objc_image_info *info = (objc_image_info *) - mappedAddressForVMAddress(imageInfoSection->addr()); - info->setOptimizedByDyld(); - } - } - - - // Success. Update RO header last. - E::set32(optROHeader->selopt_offset, seloptVMAddr - optROSection->addr()); - E::set32(optROHeader->clsopt_offset, clsoptVMAddr - optROSection->addr()); - E::set32(optROHeader->protocolopt_offset, protocoloptVMAddr - optROSection->addr()); - E::set32(optROHeader->headeropt_offset, hinfoVMAddr - optROSection->addr()); - - if ( verbose ) { - size_t roSize = optROSection->size() - optRORemaining; - size_t rwSize = optRWSection->size() - optRWRemaining; - fprintf(stderr, "update_dyld_shared_cache: for %s, %zu/%llu bytes " - "(%d%%) used in libobjc read-only optimization section\n", - archName(), roSize, optROSection->size(), - percent(roSize, optROSection->size())); - fprintf(stderr, "update_dyld_shared_cache: for %s, %zu/%llu bytes " - "(%d%%) used in libobjc read/write optimization section\n", - archName(), rwSize, optRWSection->size(), - percent(rwSize, optRWSection->size())); - fprintf(stderr, "update_dyld_shared_cache: for %s, " - "uniqued %zu selectors\n", - archName(), uniq.strings().size()); - fprintf(stderr, "update_dyld_shared_cache: for %s, " - "updated %zu selector references\n", - archName(), uniq.count()); - fprintf(stderr, "update_dyld_shared_cache: for %s, " - "uniqued %zu protocols\n", - archName(), protocolOptimizer.protocolCount()); - fprintf(stderr, "update_dyld_shared_cache: for %s, " - "updated %zu protocol references\n", - archName(), protocolOptimizer.protocolReferenceCount()); - fprintf(stderr, "update_dyld_shared_cache: for %s, " - "updated %zu ivar offsets\n", - archName(), ivarOffsetOptimizer.optimized()); - fprintf(stderr, "update_dyld_shared_cache: for %s, " - "sorted %zu method lists\n", - archName(), methodSorter.optimized()); - fprintf(stderr, "update_dyld_shared_cache: for %s, " - "recorded %zu classes (%zu duplicates)\n", - archName(), classes.classNames().size(), duplicateCount); - fprintf(stderr, "update_dyld_shared_cache: for %s, " - "wrote objc metadata optimization version %d\n", - archName(), objc_opt::VERSION); - } - - return; -} - - -static const char* sCleanupFile = NULL; -static void cleanup(int sig) -{ - ::signal(sig, SIG_DFL); - if ( sCleanupFile != NULL ) - ::unlink(sCleanupFile); - //if ( verbose ) - // fprintf(stderr, "update_dyld_shared_cache: deleting temp file in response to a signal\n"); - if ( sig == SIGINT ) - ::exit(1); -} - - -// update_dyld_shared_cache should use sync_volume_np() instead of sync() -static void sync_volume(const char* volumePath) -{ -#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 - int error = sync_volume_np(volumePath, SYNC_VOLUME_FULLSYNC|SYNC_VOLUME_FULLSYNC); -#else - int full_sync = 3; // SYNC_VOLUME_FULLSYNC | SYNC_VOLUME_FULLSYNC - int error = 0; - if ( fsctl(volumePath, 0x80004101 /*FSCTL_SYNC_VOLUME*/, &full_sync, 0) == -1) - error = errno; -#endif - if ( error ) - ::sync(); -} - - -// update shared cache should sign the shared cache -static bool adhoc_codesign_share_cache(const char* path) -{ - CFURLRef target = ::CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)path, strlen(path), FALSE); - if ( target == NULL ) - return false; - - SecStaticCodeRef code; - OSStatus status = ::SecStaticCodeCreateWithPath(target, kSecCSDefaultFlags, &code); - CFRelease(target); - if ( status ) { - ::fprintf(stderr, "codesign: failed to create url to signed object\n"); - return false; - } - - const void * keys[1] = { (void *)kSecCodeSignerIdentity } ; - const void * values[1] = { (void *)kCFNull }; - CFDictionaryRef params = ::CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - if ( params == NULL ) { - CFRelease(code); - return false; - } - - SecCodeSignerRef signer; - status = ::SecCodeSignerCreate(params, kSecCSDefaultFlags, &signer); - CFRelease(params); - if ( status ) { - CFRelease(code); - ::fprintf(stderr, "codesign: failed to create signer object\n"); - return false; - } - - status = ::SecCodeSignerAddSignatureWithErrors(signer, code, kSecCSDefaultFlags, NULL); - CFRelease(code); - CFRelease(signer); - if ( status ) { - ::fprintf(stderr, "codesign: failed to sign object: %s\n", path); - return false; - } - - if ( verbose ) - ::fprintf(stderr, "codesigning complete of %s\n", path); - - return true; -} - -template -void SharedCache::writeCacheFile(const char *cacheFilePath, uint8_t *cacheFileBuffer, uint32_t cacheFileSize, bool deleteOldCache) { - char tempCachePath[strlen(cacheFilePath)+16]; - sprintf(tempCachePath, "%s.tmp%u", cacheFilePath, getpid()); - - try { - // install signal handlers to delete temp file if program is killed - sCleanupFile = tempCachePath; - ::signal(SIGINT, cleanup); - ::signal(SIGBUS, cleanup); - ::signal(SIGSEGV, cleanup); - - // compute UUID of whole cache - uint8_t digest[16]; - CC_MD5(cacheFileBuffer, cacheFileSize, digest); - // uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats - digest[6] = ( digest[6] & 0x0F ) | ( 3 << 4 ); - digest[8] = ( digest[8] & 0x3F ) | 0x80; - ((dyldCacheHeader*)cacheFileBuffer)->set_uuid(digest); - - // create var/db/dyld dirs if needed - char dyldDirs[1024]; - strcpy(dyldDirs, cacheFilePath); - char* lastSlash = strrchr(dyldDirs, '/'); - if ( lastSlash != NULL ) - lastSlash[1] = '\0'; - struct stat stat_buf; - if ( stat(dyldDirs, &stat_buf) != 0 ) { - const char* afterSlash = &dyldDirs[1]; - char* slash; - while ( (slash = strchr(afterSlash, '/')) != NULL ) { - *slash = '\0'; - ::mkdir(dyldDirs, S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); - *slash = '/'; - afterSlash = slash+1; - } - } - - // create temp file for cache - int fd = ::open(tempCachePath, O_CREAT | O_RDWR | O_TRUNC, 0644); - if ( fd == -1 ) - throwf("can't create temp file %s, errno=%d", tempCachePath, errno); - - // try to allocate whole cache file contiguously - fstore_t fcntlSpec = { F_ALLOCATECONTIG|F_ALLOCATEALL, F_PEOFPOSMODE, 0, cacheFileSize, 0 }; - ::fcntl(fd, F_PREALLOCATE, &fcntlSpec); - - // write out cache file - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: writing cache to disk: %s\n", tempCachePath); - if ( ::pwrite(fd, cacheFileBuffer, cacheFileSize, 0) != cacheFileSize ) - throwf("write() failure creating cache file, errno=%d", errno); - - // flush to disk and close - int result = ::fcntl(fd, F_FULLFSYNC, NULL); - if ( result == -1 ) - fprintf(stderr, "update_dyld_shared_cache: warning, fcntl(F_FULLFSYNC) failed with errno=%d for %s\n", errno, tempCachePath); - result = ::close(fd); - if ( result != 0 ) - fprintf(stderr, "update_dyld_shared_cache: warning, close() failed with errno=%d for %s\n", errno, tempCachePath); - - if ( !iPhoneOS ) - adhoc_codesign_share_cache(tempCachePath); - - if ( deleteOldCache ) { - const char* pathLastSlash = strrchr(cacheFilePath, '/'); - if ( pathLastSlash != NULL ) { - result = ::unlink(cacheFilePath); - if ( result != 0 ) { - if ( errno != ENOENT ) - fprintf(stderr, "update_dyld_shared_cache: warning, unable to remove existing cache %s because errno=%d\n", cacheFilePath, errno); - } - } - } - - // move new cache file to correct location for use after reboot - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: atomically moving cache file into place: %s\n", cacheFilePath); - result = ::rename(tempCachePath, cacheFilePath); - if ( result != 0 ) - throwf("can't swap newly create dyld shared cache file: rename(%s,%s) returned errno=%d", tempCachePath, cacheFilePath, errno); - - // flush everything to disk to assure rename() gets recorded - sync_volume(cacheFilePath); - - // restore default signal handlers - ::signal(SIGINT, SIG_DFL); - ::signal(SIGBUS, SIG_DFL); - ::signal(SIGSEGV, SIG_DFL); - } - catch (...){ - // remove temp cache file - ::unlink(tempCachePath); - throw; - } -} - - -template <> bool SharedCache::addCacheSlideInfo(){ return true; } -template <> bool SharedCache::addCacheSlideInfo() { return true; } -template <> bool SharedCache::addCacheSlideInfo() { return false; } -template <> bool SharedCache::addCacheSlideInfo() { return true; } - - -template -bool SharedCache::update(bool force, bool optimize, bool deleteExistingFirst, int archIndex, - int archCount, bool keepSignatures, bool dontMapLocalSymbols) -{ - bool didUpdate = false; - bool canEmitDevelopmentCache = true; - char devCacheFilePath[strlen(fCacheFilePath)+strlen(".development")]; - char fileListFilePath[strlen(fCacheFilePath)+strlen(".list")]; - sprintf(devCacheFilePath, "%s.development", fCacheFilePath); - sprintf(fileListFilePath, "%s.list", fCacheFilePath); - std::vector paths; - - // already up to date? - if ( force || fExistingIsNotUpToDate ) { - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: regenerating %s\n", fCacheFilePath); - if ( fDylibs.size() == 0 ) { - fprintf(stderr, "update_dyld_shared_cache: warning, empty cache not generated for arch %s\n", archName()); - return false; - } - // delete existing cache while building the new one - // this is a flag to dyld to stop pinging update_dyld_shared_cache - if ( deleteExistingFirst ) - ::unlink(fCacheFilePath); - uint8_t* inMemoryCache = NULL; - uint32_t allocatedCacheSize = 0; - try { - // allocate a memory block to hold cache - uint32_t cacheFileSize = 0; - for(std::vector::iterator it = fMappings.begin(); it != fMappings.end(); ++it) { - uint32_t end = it->sfm_file_offset + it->sfm_size; - if ( end > cacheFileSize ) - cacheFileSize = end; - } - if ( vm_allocate(mach_task_self(), (vm_address_t*)(&inMemoryCache), cacheFileSize, VM_FLAGS_ANYWHERE) != KERN_SUCCESS ) - throwf("can't vm_allocate cache of size %u", cacheFileSize); - allocatedCacheSize = cacheFileSize; - fInMemoryCache = inMemoryCache; - - // fill in header - dyldCacheHeader* header = (dyldCacheHeader*)inMemoryCache; - const char* archPairName = fArchGraph->archName(); - char temp[16]; - strcpy(temp, "dyld_v1 "); - strcpy(&temp[15-strlen(archPairName)], archPairName); - header->set_magic(temp); - //header->set_architecture(arch()); - header->set_mappingOffset(sizeof(dyldCacheHeader)); - header->set_mappingCount(fMappings.size()); - header->set_imagesOffset(header->mappingOffset() + fMappings.size()*sizeof(dyldCacheFileMapping)); - header->set_imagesCount(fDylibs.size()+fDylibAliases.size()); - header->set_dyldBaseAddress(fDyldBaseAddress); - header->set_codeSignatureOffset(cacheFileSize); - header->set_codeSignatureSize(0); - header->set_slideInfoOffset(0); - header->set_slideInfoSize(0); - header->set_localSymbolsOffset(0); - header->set_localSymbolsSize(0); - - // fill in mappings - dyldCacheFileMapping* mapping = (dyldCacheFileMapping*)&inMemoryCache[sizeof(dyldCacheHeader)]; - for(std::vector::iterator it = fMappings.begin(); it != fMappings.end(); ++it) { - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: cache mappings: address=0x%0llX, size=0x%0llX, fileOffset=0x%0llX, prot=0x%X\n", - it->sfm_address, it->sfm_size, it->sfm_file_offset, it->sfm_init_prot); - mapping->set_address(it->sfm_address); - mapping->set_size(it->sfm_size); - mapping->set_file_offset(it->sfm_file_offset); - mapping->set_max_prot(it->sfm_max_prot); - mapping->set_init_prot(it->sfm_init_prot); - ++mapping; - } - - // fill in image table - dyldCacheImageInfo* image = (dyldCacheImageInfo*)mapping; - for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - image->set_address(it->info.address); - image->set_modTime(it->info.modTime); - image->set_inode(it->info.inode); - image->set_pathFileOffset(cacheFileOffsetForVMAddress(it->info.address+it->info.pathFileOffset)); - ++image; - } - - // add aliases to end of image table - for(typename std::vector::iterator it = fDylibAliases.begin(); it != fDylibAliases.end(); ++it) { - image->set_address(it->info.address); - image->set_modTime(it->info.modTime); - image->set_inode(it->info.inode); - image->set_pathFileOffset(it->info.pathFileOffset); - strcpy((char*)inMemoryCache+it->info.pathFileOffset, it->aliases[0]); - //fprintf(stderr, "adding alias to offset 0x%08X %s\n", it->info.pathFileOffset, it->aliases[0]); - ++image; - } - - // copy each segment to cache buffer - const int dylibCount = fDylibs.size(); - int dylibIndex = 0; - int progressIndex = 0; - bool foundLibSystem = false; - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it, ++dylibIndex) { - const char* path = it->layout->getFilePath(); - int src = ::open(path, O_RDONLY, 0); - if ( src == -1 ) - throwf("can't open file %s, errno=%d", it->layout->getID().name, errno); - // mark source as "don't cache" - (void)fcntl(src, F_NOCACHE, 1); - // verify file has not changed since dependency analysis - struct stat stat_buf; - if ( fstat(src, &stat_buf) == -1) - throwf("can't stat open file %s, errno=%d", path, errno); - if ( (it->layout->getInode() != stat_buf.st_ino) ) - throwf("file inode changed from %llu to %llu during cache creation: %s", it->layout->getInode(), stat_buf.st_ino, path); - else if ( it->layout->getLastModTime() != stat_buf.st_mtime ) - throwf("file mtime changed from 0x%lX to 0x%lX during cache creation: %s", it->layout->getLastModTime(), stat_buf.st_mtime, path); - if ( strcmp(it->layout->getID().name, "/usr/lib/libSystem.B.dylib") == 0 ) - foundLibSystem = true; - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: copying %s to cache\n", it->layout->getFilePath()); - try { - const std::vector& segs = it->layout->getSegments(); - for (int i=0; i < segs.size(); ++i) { - const MachOLayoutAbstraction::Segment& seg = segs[i]; - if ( verbose ) { - fprintf(stderr, "\t\tsegment %s, size=0x%0llX, cache address=0x%0llX, buffer address=%p\n", - seg.name(), seg.size(), seg.newAddress(), &inMemoryCache[cacheFileOffsetForVMAddress(seg.newAddress())]); - } - if ( seg.size() > 0 ) { - const uint64_t segmentSrcStartOffset = it->layout->getOffsetInUniversalFile()+seg.fileOffset(); - const uint64_t segmentSize = seg.fileSize(); - const uint64_t segmentDstStartOffset = cacheFileOffsetForVMAddress(seg.newAddress()); - ssize_t readResult = ::pread(src, &inMemoryCache[segmentDstStartOffset], segmentSize, segmentSrcStartOffset); - if ( readResult != segmentSize ) { - if ( readResult == -1 ) - throwf("read failure copying dylib errno=%d for %s", errno, it->layout->getID().name); - else - throwf("read failure copying dylib. Read of %lld bytes at file offset %lld returned %ld for %s", - segmentSize, segmentSrcStartOffset, readResult, it->layout->getID().name); - } - } - } - } - catch (const char* msg) { - throwf("%s while copying %s to shared cache", msg, it->layout->getID().name); - } - ::close(src); - paths.push_back(it->layout->getID().name); - if ( progress ) { - // assuming read takes 40% of time - int nextProgressIndex = archIndex*100+(40*dylibIndex)/dylibCount; - if ( nextProgressIndex != progressIndex ) - fprintf(stdout, "%3u/%u\n", nextProgressIndex, archCount*100); - progressIndex = nextProgressIndex; - } - } - if ( !foundLibSystem ) - throw "cache would be missing required dylib /usr/lib/libSystem.B.dylib"; - - // set mapped address for each segment - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - std::vector& segs = ((MachOLayoutAbstraction*)(it->layout))->getSegments(); - for (int i=0; i < segs.size(); ++i) { - MachOLayoutAbstraction::Segment& seg = segs[i]; - if ( seg.size() > 0 ) - seg.setMappedAddress(inMemoryCache + cacheFileOffsetForVMAddress(seg.newAddress())); - //fprintf(stderr, "%s at %p to %p for %s\n", seg.name(), seg.mappedAddress(), (char*)seg.mappedAddress()+ seg.size(), it->layout->getID().name); - } - } - - // also construct list of all pointers in cache to other things in cache - std::vector pointersInData; - pointersInData.reserve(1024); - - // rebase each dylib in shared cache - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - try { - Rebaser r(*it->layout); - if (!r.rebase(pointersInData)) { - canEmitDevelopmentCache = false; - fprintf(stderr, "update_dyld_shared_cache: Omitting development cache for %s, cannot rebase dylib into place for %s\n", archName(), it->layout->getID().name); - } - //if ( verbose ) - // fprintf(stderr, "update_dyld_shared_cache: for %s, rebasing dylib into cache for %s\n", archName(), it->layout->getID().name); - } - catch (const char* msg) { - throwf("%s in %s", msg, it->layout->getID().name); - } - } - - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: for %s, updating binding information for %lu files:\n", archName(), fDylibs.size()); - // instantiate a Binder for each image and add to map - typename Binder::Map map; - std::vector*> binders; - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - //fprintf(stderr, "binding %s\n", it->layout->getID().name); - Binder* binder = new Binder(*it->layout); - binders.push_back(binder); - // only add dylibs to map - if ( it->layout->getID().name != NULL ) - map[it->layout->getID().name] = binder; - } - // tell each Binder about the others - for(typename std::vector*>::iterator it = binders.begin(); it != binders.end(); ++it) { - (*it)->setDependentBinders(map); - } - // perform binding - for(typename std::vector*>::iterator it = binders.begin(); it != binders.end(); ++it) { - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: for %s, updating binding information in cache for %s\n", archName(), (*it)->getDylibID()); - try { - (*it)->bind(pointersInData); - } - catch (const char* msg) { - throwf("%s in %s", msg, (*it)->getDylibID()); - } - } - - for(typename std::vector::iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - const macho_header

* fHeader = (const macho_header

*)it->layout->getSegments()[0].mappedAddress(); - const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)fHeader + sizeof(macho_header

)); - const uint32_t cmd_count = fHeader->ncmds(); - const macho_load_command

* cmd = cmds; - macho_dyld_info_command

* fDyldInfo; - uint64_t originalLinkEditVMAddr = 0; - for (uint32_t i = 0; i < cmd_count; ++i) { - if ( cmd->cmd() == macho_segment_command

::CMD ) { - macho_segment_command

* seg = (macho_segment_command

*)cmd; - if ( strcmp(seg->segname(), "__LINKEDIT") != 0 ) { - pint_t oldFileOff = seg->fileoff(); - originalLinkEditVMAddr += seg->vmsize(); - // don't alter __TEXT until is fixed - if ( strcmp(seg->segname(), "__TEXT") != 0 ) { - // update all other segments fileoff to be offset from start of cache file - seg->set_fileoff(cacheFileOffsetForVMAddress(seg->vmaddr())); - } - pint_t fileOffsetDelta = seg->fileoff() - oldFileOff; - const MachOLayoutAbstraction::Segment* layoutSeg = it->layout->getSegment(seg->segname()); - if ( layoutSeg != NULL ) { - seg->set_vmsize(layoutSeg->size()); - seg->set_filesize(layoutSeg->fileSize()); - } - // update all sections in this segment - macho_section

* const sectionsStart = (macho_section

*)((char*)seg + sizeof(macho_segment_command

)); - macho_section

* const sectionsEnd = §ionsStart[seg->nsects()]; - for(macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { - if ( sect->offset() != 0 ) - sect->set_offset(sect->offset()+fileOffsetDelta); - } - } - } else if (cmd->cmd() == LC_DYLD_INFO || cmd->cmd() == LC_DYLD_INFO_ONLY) { - fDyldInfo = (macho_dyld_info_command

*)cmd; - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } - } - - // optimize binding - for(typename std::vector*>::iterator it = binders.begin(); it != binders.end(); ++it) { - try { - (*it)->optimize(); - } - catch (const char* msg) { - throwf("%s in %s", msg, (*it)->getDylibID()); - } - } - - // delete binders - for(typename std::vector*>::iterator it = binders.begin(); it != binders.end(); ++it) { - delete *it; - } - - // merge/optimize all LINKEDIT segments - if ( optimize ) { - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: original cache file size %uMB\n", cacheFileSize/(1024*1024)); - cacheFileSize = (this->optimizeLINKEDIT(keepSignatures, dontMapLocalSymbols) - inMemoryCache); - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: optimized cache file size %uMB\n", cacheFileSize/(1024*1024)); - // update header to reduce mapping size - dyldCacheHeader* cacheHeader = (dyldCacheHeader*)inMemoryCache; - dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)&inMemoryCache[sizeof(dyldCacheHeader)]; - dyldCacheFileMapping* lastMapping = &mappings[cacheHeader->mappingCount()-1]; - lastMapping->set_size(cacheFileSize-lastMapping->file_offset()); - // update fMappings so .map file will print correctly - fMappings.back().sfm_size = cacheFileSize-fMappings.back().sfm_file_offset; - // update header - //fprintf(stderr, "update_dyld_shared_cache: changing end of cache address from 0x%08llX to 0x%08llX\n", - // header->codeSignatureOffset(), fMappings.back().sfm_address + fMappings.back().sfm_size); - header->set_codeSignatureOffset(fMappings.back().sfm_file_offset + fMappings.back().sfm_size); - } - - // dump dev cache with optimized linkedit, but not ObjC optimizations - if (iPhoneOS && canEmitDevelopmentCache) { - int fileListFD = ::open(fileListFilePath, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if ( fileListFD != -1 ) { - for (const char* path : paths) { - write(fileListFD, path, strlen(path)+1); - write(fileListFD, "\n", 1); - } - close(fileListFD); - } - - ((dyldCacheHeader*)inMemoryCache)->set_cacheType(1); - writeCacheFile(devCacheFilePath, inMemoryCache, cacheFileSize, fCacheFileInFinalLocation); - } - - // unique objc selectors and update other objc metadata - if ( optimize ) { - optimizeObjC(pointersInData); - if ( progress ) { - // assuming objc optimizations takes 15% of time - fprintf(stdout, "%3u/%u\n", (archIndex+1)*55, archCount*100); - } - } - - if ( addCacheSlideInfo() ) { - // build bitmap of which pointers need sliding - uint8_t* const dataStart = &inMemoryCache[fMappings[1].sfm_file_offset]; // R/W mapping is always second - uint8_t* const dataEnd = &inMemoryCache[fMappings[1].sfm_file_offset+fMappings[1].sfm_size]; - const int bitmapSize = (dataEnd - dataStart)/(4*8); - uint8_t* bitmap = (uint8_t*)calloc(bitmapSize, 1); - void* lastPointer = inMemoryCache; - for(std::vector::iterator pit=pointersInData.begin(); pit != pointersInData.end(); ++pit) { - if ( *pit != lastPointer ) { - void* p = *pit; - if ( (p < dataStart) || ( p > dataEnd) ) - throwf("DATA pointer for sliding, out of range 0x%08lX\n", (long)((uint8_t*)p-inMemoryCache)); - long offset = (long)((uint8_t*)p - dataStart); - if ( (offset % 4) != 0 ) - throwf("pointer not 4-byte aligned in DATA offset 0x%08lX\n", offset); - long byteIndex = offset / (4*8); - long bitInByte = (offset % 32) >> 2; - bitmap[byteIndex] |= (1 << bitInByte); - lastPointer = p; - } - } - - // allocate worst case size block of all slide info - const int entry_size = 4096/(8*4); // 8 bits per byte, possible pointer every 4 bytes. - const int toc_count = bitmapSize/entry_size; - int slideInfoSize = sizeof(dyldCacheSlideInfo) + 2*toc_count + entry_size*(toc_count+1); - dyldCacheSlideInfo* slideInfo = (dyldCacheSlideInfo*)calloc(slideInfoSize, 1); - slideInfo->set_version(1); - slideInfo->set_toc_offset(sizeof(dyldCacheSlideInfo)); - slideInfo->set_toc_count(toc_count); - slideInfo->set_entries_offset((slideInfo->toc_offset()+2*toc_count+127)&(-128)); - slideInfo->set_entries_count(0); - slideInfo->set_entries_size(entry_size); - // append each unique entry - const dyldCacheSlideInfoEntry* bitmapAsEntries = (dyldCacheSlideInfoEntry*)bitmap; - dyldCacheSlideInfoEntry* const entriesInSlidInfo = (dyldCacheSlideInfoEntry*)((char*)slideInfo+slideInfo->entries_offset()); - int entry_count = 0; - for (int i=0; i < toc_count; ++i) { - const dyldCacheSlideInfoEntry* thisEntry = &bitmapAsEntries[i]; - // see if it is same as one already added - bool found = false; - for (int j=0; j < entry_count; ++j) { - if ( memcmp(thisEntry, &entriesInSlidInfo[j], entry_size) == 0 ) { - //fprintf(stderr, "toc[%d] optimized to %d\n", i, j); - slideInfo->set_toc(i, j); - found = true; - break; - } - } - if ( ! found ) { - // append to end - memcpy(&entriesInSlidInfo[entry_count], thisEntry, entry_size); - slideInfo->set_toc(i, entry_count++); - } - } - slideInfo->set_entries_count(entry_count); - - int slideInfoPageSize = regionAlign(slideInfo->entries_offset() + entry_count*entry_size); - cacheFileSize += slideInfoPageSize; - - // update mappings to increase RO size - dyldCacheHeader* cacheHeader = (dyldCacheHeader*)inMemoryCache; - dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)&inMemoryCache[sizeof(dyldCacheHeader)]; - dyldCacheFileMapping* lastMapping = &mappings[cacheHeader->mappingCount()-1]; - lastMapping->set_size(lastMapping->size()+slideInfoPageSize); - - // update header to show location of slidePointers - cacheHeader->set_slideInfoOffset(cacheHeader->codeSignatureOffset()); - cacheHeader->set_slideInfoSize(slideInfoPageSize); - cacheHeader->set_codeSignatureOffset(cacheHeader->codeSignatureOffset()+slideInfoPageSize); - - // update fMappings so .map file will print correctly - fMappings.back().sfm_size = cacheFileSize-fMappings.back().sfm_file_offset; - - // copy compressed into into buffer - memcpy(&inMemoryCache[cacheHeader->slideInfoOffset()], slideInfo, slideInfoPageSize); - } - - // append local symbol info in an unmapped region - if ( dontMapLocalSymbols ) { - uint32_t spaceAtEnd = allocatedCacheSize - cacheFileSize; - uint32_t localSymbolsOffset = pageAlign(cacheFileSize); - dyldCacheLocalSymbolsInfo* infoHeader = (dyldCacheLocalSymbolsInfo*)(&inMemoryCache[localSymbolsOffset]); - const uint32_t entriesOffset = sizeof(dyldCacheLocalSymbolsInfo); - const uint32_t entriesCount = fLocalSymbolInfos.size(); - const uint32_t nlistOffset = entriesOffset + entriesCount * sizeof(dyldCacheLocalSymbolEntry); - const uint32_t nlistCount = fUnmappedLocalSymbols.size(); - const uint32_t stringsOffset = nlistOffset + nlistCount * sizeof(macho_nlist

); - const uint32_t stringsSize = fUnmappedLocalsStringPool.size(); - if ( stringsOffset+stringsSize > spaceAtEnd ) - throwf("update_dyld_shared_cache[%u] for arch=%s, out of space for local symbols. Have 0x%X, Need 0x%X\n", - getpid(), fArchGraph->archName(), spaceAtEnd, stringsOffset+stringsSize); - // fill in local symbols info - infoHeader->set_nlistOffset(nlistOffset); - infoHeader->set_nlistCount(nlistCount); - infoHeader->set_stringsOffset(stringsOffset); - infoHeader->set_stringsSize(stringsSize); - infoHeader->set_entriesOffset(entriesOffset); - infoHeader->set_entriesCount(entriesCount); - // copy info for each dylib - dyldCacheLocalSymbolEntry* entries = (dyldCacheLocalSymbolEntry*)(&inMemoryCache[localSymbolsOffset+entriesOffset]); - for (int i=0; i < entriesCount; ++i) { - entries[i].set_dylibOffset(fLocalSymbolInfos[i].dylibOffset); - entries[i].set_nlistStartIndex(fLocalSymbolInfos[i].nlistStartIndex); - entries[i].set_nlistCount(fLocalSymbolInfos[i].nlistCount); - } - // copy nlists - memcpy(&inMemoryCache[localSymbolsOffset+nlistOffset], &fUnmappedLocalSymbols[0], nlistCount*sizeof(macho_nlist

)); - // copy string pool - memcpy(&inMemoryCache[localSymbolsOffset+stringsOffset], fUnmappedLocalsStringPool.getBuffer(), stringsSize); - - // update state - fUnmappedLocalSymbolsSize = pageAlign(stringsOffset + stringsSize); - cacheFileSize = regionAlign(localSymbolsOffset + fUnmappedLocalSymbolsSize); - - // update header to show location of slidePointers - dyldCacheHeader* cacheHeader = (dyldCacheHeader*)inMemoryCache; - cacheHeader->set_localSymbolsOffset(localSymbolsOffset); - cacheHeader->set_localSymbolsSize(stringsOffset+stringsSize); - cacheHeader->set_codeSignatureOffset(cacheFileSize); - } - - // make sure after all optimizations, that whole cache file fits into shared region address range - { - dyldCacheHeader* cacheHeader = (dyldCacheHeader*)inMemoryCache; - dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)&inMemoryCache[cacheHeader->mappingOffset()]; - // incorporate code signature size into overflow check - uint32_t estCodeSigSize = regionAlign(cacheFileSize/200); // guess 0.5% for code signature - for (int i=0; i < cacheHeader->mappingCount(); ++i) { - uint64_t endAddr = mappings[i].address() + mappings[i].size() + estCodeSigSize; - if ( endAddr > (sharedRegionStartAddress() + sharedRegionSize()) ) { - throwf("update_dyld_shared_cache[%u] for arch=%s, shared cache will not fit in shared regions address space. Overflow amount: %lluKB\n", - getpid(), fArchGraph->archName(), (endAddr-(sharedRegionStartAddress() + sharedRegionSize()))/1024); - } - } - } - - if ( fVerify ) { - // if no existing cache, say so - if ( fExistingCacheForVerification == NULL ) { - throwf("update_dyld_shared_cache[%u] for arch=%s, could not verify because cache file does not exist in /var/db/dyld/\n", - getpid(), archName()); - } - // new cache is built, compare header entries - const dyldCacheHeader* newHeader = (dyldCacheHeader*)inMemoryCache; - const dyldCacheHeader* oldHeader = (dyldCacheHeader*)fExistingCacheForVerification; - if ( newHeader->mappingCount() != oldHeader->mappingCount() ) { - throwf("update_dyld_shared_cache[%u] for arch=%s, could not verify cache because caches have a different number of mappings\n", - getpid(), archName()); - } - const dyldCacheFileMapping* newMappings = (dyldCacheFileMapping*)&inMemoryCache[newHeader->mappingOffset()]; - const dyldCacheFileMapping* oldMappings = (dyldCacheFileMapping*)&fExistingCacheForVerification[oldHeader->mappingOffset()]; - for (int i=0; i < newHeader->mappingCount(); ++i) { - if ( newMappings[i].address() != oldMappings[i].address() ) { - throwf("update_dyld_shared_cache[%u] for arch=%s, could not verify cache because mapping %d starts at a different address 0x%0llX vs 0x%0llX\n", - getpid(), archName(), i, newMappings[i].address(), oldMappings[i].address() ); - } - if ( newMappings[i].size() != oldMappings[i].size() ) { - throwf("update_dyld_shared_cache[%u] for arch=%s, could not verify cache because mapping %d has a different size\n", - getpid(), archName(), i); - } - } - - //fprintf(stderr, "%s existing cache = %p\n", archName(), fExistingCacheForVerification); - //fprintf(stderr, "%s new cache = %p\n", archName(), inMemoryCache); - // compare content to existing cache page by page - for (int offset=0; offset < cacheFileSize; offset += 4096) { - if ( memcmp(&inMemoryCache[offset], &fExistingCacheForVerification[offset], 4096) != 0 ) { - fprintf(stderr, "verifier found differences on page offset 0x%08X for %s:\n", offset, archName()); - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it, ++dylibIndex) { - const std::vector& segs = it->layout->getSegments(); - for(std::vector::const_iterator sit = segs.begin(); sit != segs.end(); ++sit) { - const MachOLayoutAbstraction::Segment& seg = *sit; - if ( (seg.mappedAddress() <= &inMemoryCache[offset]) && (&inMemoryCache[offset] < ((uint8_t*)seg.mappedAddress() + seg.fileSize())) ) { - // all LINKEDITs point to the same region, so just print one - if ( strcmp(seg.name(), "__LINKEDIT") == 0 ) - fprintf(stderr, " in merged LINKEDIT segment\n"); - else - fprintf(stderr, " in segment %s of dylib %s\n", seg.name(), it->layout->getID().name); - break; - } - } - } - for (int po=0; po < 4096; po += 16) { - if ( memcmp(&inMemoryCache[offset+po], &fExistingCacheForVerification[offset+po], 16) != 0 ) { - fprintf(stderr, " existing: 0x%08X: ", offset+po); - for ( int j=0; j < 16; ++j) - fprintf(stderr, " 0x%02X", fExistingCacheForVerification[offset+po+j]); - fprintf(stderr, "\n"); - fprintf(stderr, " should be: 0x%08X: ", offset+po); - for ( int j=0; j < 16; ++j) - fprintf(stderr, " 0x%02X", inMemoryCache[offset+po+j]); - fprintf(stderr, "\n"); - } - } - } - } - } - else { - ((dyldCacheHeader*)inMemoryCache)->set_cacheType(0); - writeCacheFile(fCacheFilePath, inMemoryCache, cacheFileSize, fCacheFileInFinalLocation); - didUpdate = true; - // generate human readable "map" file that shows the layout of the cache file - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: writing .map file to disk\n"); - char mapFilePath[strlen(fCacheFilePath)+16]; - sprintf(mapFilePath, "%s.map", fCacheFilePath); - char tempMapFilePath[strlen(fCacheFilePath)+32]; - sprintf(tempMapFilePath, "%s.map%u", fCacheFilePath, getpid()); - FILE* fmap = ::fopen(tempMapFilePath, "w"); - if ( fmap == NULL ) { - fprintf(stderr, "can't create map file %s, errno=%d", tempMapFilePath, errno); - } - else { - for(std::vector::iterator it = fMappings.begin(); it != fMappings.end(); ++it) { - const char* prot = "RW"; - if ( it->sfm_init_prot == (VM_PROT_EXECUTE|VM_PROT_READ) ) - prot = "EX"; - else if ( it->sfm_init_prot == VM_PROT_READ ) - prot = "RO"; - else if ( it->sfm_init_prot == (VM_PROT_EXECUTE|VM_PROT_WRITE|VM_PROT_READ) ) - prot = "WX"; - if ( it->sfm_size > 1024*1024 ) - fprintf(fmap, "mapping %s %4lluMB 0x%0llX -> 0x%0llX\n", prot, it->sfm_size/(1024*1024), - it->sfm_address, it->sfm_address+it->sfm_size); - else - fprintf(fmap, "mapping %s %4lluKB 0x%0llX -> 0x%0llX\n", prot, it->sfm_size/1024, - it->sfm_address, it->sfm_address+it->sfm_size); - } - - fprintf(fmap, " linkedit %4uKB 0x%0llX -> 0x%0llX weak binding info\n", - (fOffsetOfExportInfoInCombinedLinkedit-fOffsetOfWeakBindInfoInCombinedLinkedit)/1024, - fLinkEditsStartAddress+fOffsetOfWeakBindInfoInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfExportInfoInCombinedLinkedit); - fprintf(fmap, " linkedit %4uKB 0x%0llX -> 0x%0llX export info\n", - (fOffsetOfBindInfoInCombinedLinkedit-fOffsetOfExportInfoInCombinedLinkedit)/1024, - fLinkEditsStartAddress+fOffsetOfExportInfoInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfBindInfoInCombinedLinkedit); - fprintf(fmap, " linkedit %4uKB 0x%0llX -> 0x%0llX binding info\n", - (fOffsetOfLazyBindInfoInCombinedLinkedit-fOffsetOfBindInfoInCombinedLinkedit)/1024, - fLinkEditsStartAddress+fOffsetOfBindInfoInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfLazyBindInfoInCombinedLinkedit); - fprintf(fmap, " linkedit %4uKB 0x%0llX -> 0x%0llX lazy binding info\n", - (fOffsetOfOldSymbolTableInfoInCombinedLinkedit-fOffsetOfLazyBindInfoInCombinedLinkedit)/1024, - fLinkEditsStartAddress+fOffsetOfLazyBindInfoInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfOldSymbolTableInfoInCombinedLinkedit); - fprintf(fmap, " linkedit %4uMB 0x%0llX -> 0x%0llX non-dyld symbol table size\n", - (fSizeOfOldSymbolTableInfoInCombinedLinkedit)/(1024*1024), - fLinkEditsStartAddress+fOffsetOfOldSymbolTableInfoInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfOldSymbolTableInfoInCombinedLinkedit+fSizeOfOldSymbolTableInfoInCombinedLinkedit); - if ( fSizeOfFunctionStartsInCombinedLinkedit != 0 ) - fprintf(fmap, " linkedit %4uKB 0x%0llX -> 0x%0llX non-dyld functions starts size\n", - fSizeOfFunctionStartsInCombinedLinkedit/1024, - fLinkEditsStartAddress+fOffsetOfFunctionStartsInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfFunctionStartsInCombinedLinkedit+fSizeOfFunctionStartsInCombinedLinkedit); - if ( fSizeOfDataInCodeInCombinedLinkedit != 0 ) - fprintf(fmap, " linkedit %4uKB 0x%0llX -> 0x%0llX non-dyld data-in-code info size\n", - fSizeOfDataInCodeInCombinedLinkedit/1024, - fLinkEditsStartAddress+fOffsetOfDataInCodeInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfDataInCodeInCombinedLinkedit+fSizeOfDataInCodeInCombinedLinkedit); - if ( fSizeOfOldExternalRelocationsInCombinedLinkedit != 0 ) - fprintf(fmap, " linkedit %4uKB 0x%0llX -> 0x%0llX non-dyld external relocs size\n", - fSizeOfOldExternalRelocationsInCombinedLinkedit/1024, - fLinkEditsStartAddress+fOffsetOfOldExternalRelocationsInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfOldExternalRelocationsInCombinedLinkedit+fSizeOfOldExternalRelocationsInCombinedLinkedit); - fprintf(fmap, " linkedit %4uKB 0x%0llX -> 0x%0llX non-dyld indirect symbol table size\n", - fSizeOfOldIndirectSymbolsInCombinedLinkedit/1024, - fLinkEditsStartAddress+fOffsetOfOldIndirectSymbolsInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfOldIndirectSymbolsInCombinedLinkedit+fSizeOfOldIndirectSymbolsInCombinedLinkedit); - fprintf(fmap, " linkedit %4uMB 0x%0llX -> 0x%0llX non-dyld string pool\n", - (fSizeOfOldStringPoolInCombinedLinkedit)/(1024*1024), - fLinkEditsStartAddress+fOffsetOfOldStringPoolInCombinedLinkedit, - fLinkEditsStartAddress+fOffsetOfOldStringPoolInCombinedLinkedit+fSizeOfOldStringPoolInCombinedLinkedit); - - dyldCacheHeader* cacheHeader = (dyldCacheHeader*)inMemoryCache; - if ( cacheHeader->slideInfoSize() != 0 ) { - fprintf(fmap, " linkedit %4lluKB kernel slide info\n", (cacheHeader->slideInfoSize())/1024); - } - - fprintf(fmap, "unmapped -- %4uMB local symbol info\n", fUnmappedLocalSymbolsSize/(1024*1024)); - - uint64_t endMappingAddr = fMappings[2].sfm_address + fMappings[2].sfm_size; - fprintf(fmap, "total map %4lluMB\n", (endMappingAddr - sharedRegionStartAddress())/(1024*1024)); - if ( sharedRegionStartWritableAddress(0) == 0x7FFF70000000LL ) { - // x86_64 has different slide constraints - uint64_t freeSpace = 256*1024*1024 - fMappings[1].sfm_size; - fprintf(fmap, "r/w space %4lluMB -> %d bits of entropy for ASLR\n\n", freeSpace/(1024*1024), (int)log2(freeSpace/4096)); - } - else { - uint64_t freeSpace = sharedRegionStartAddress() + sharedRegionSize() - endMappingAddr; - fprintf(fmap, "free space %4lluMB -> %d bits of entropy for ASLR\n\n", freeSpace/(1024*1024), (int)log2(freeSpace/4096)); - } - - for(typename std::vector::const_iterator it = fDylibs.begin(); it != fDylibs.end(); ++it) { - fprintf(fmap, "%s\n", it->layout->getID().name); - for (std::vector::const_iterator ait = it->aliases.begin(); ait != it->aliases.end(); ++ait) - fprintf(fmap, "%s\n", *ait); - const std::vector& segs = it->layout->getSegments(); - for (int i=0; i < segs.size(); ++i) { - const MachOLayoutAbstraction::Segment& seg = segs[i]; - fprintf(fmap, "\t%16s 0x%0llX -> 0x%0llX\n", seg.name(), seg.newAddress(), seg.newAddress()+seg.size()); - } - } - if ( warnings.size() > 0 ) { - fprintf(fmap, "# Warnings:\n"); - for (std::vector::iterator it=warnings.begin(); it != warnings.end(); ++it) { - fprintf(fmap, "# %s\n", *it); - } - } - fclose(fmap); - ::rename(tempMapFilePath, mapFilePath); - } - } - - // free in memory cache - vm_deallocate(mach_task_self(), (vm_address_t)inMemoryCache, allocatedCacheSize); - inMemoryCache = NULL; - if ( progress ) { - // finished - fprintf(stdout, "%3u/%u\n", (archIndex+1)*100, archCount*100); - } - } - catch (...){ - // remove in memory cache - if ( inMemoryCache != NULL ) - vm_deallocate(mach_task_self(), (vm_address_t)inMemoryCache, allocatedCacheSize); - throw; - } - } - return didUpdate; -} - - - -// -// The shared cache is driven by /var/db/dyld/shared_region_roots which contains -// the paths used to search for dylibs that should go in the shared cache -// -// Leading and trailing white space is ignored -// Blank lines are ignored -// Lines starting with # are ignored -// -static void parsePathsFile(const char* filePath, std::vector& paths) -{ - // read in whole file - int fd = open(filePath, O_RDONLY, 0); - if ( fd == -1 ) { - fprintf(stderr, "update_dyld_shared_cache: can't open file: %s\n", filePath); - exit(1); - } - struct stat stat_buf; - fstat(fd, &stat_buf); - char* p = (char*)malloc(stat_buf.st_size); - if ( p == NULL ) { - fprintf(stderr, "update_dyld_shared_cache: malloc failure\n"); - exit(1); - } - if ( read(fd, p, stat_buf.st_size) != stat_buf.st_size ) { - fprintf(stderr, "update_dyld_shared_cache: can't read file: %s\n", filePath); - exit(1); - } - ::close(fd); - - // parse into paths and add to vector - char * const end = &p[stat_buf.st_size]; - enum { lineStart, inSymbol, inComment } state = lineStart; - char* symbolStart = NULL; - for (char* s = p; s < end; ++s ) { - switch ( state ) { - case lineStart: - if ( *s =='#' ) { - state = inComment; - } - else if ( !isspace(*s) ) { - state = inSymbol; - symbolStart = s; - } - break; - case inSymbol: - if ( *s == '\n' ) { - *s = '\0'; - // removing any trailing spaces - char* last = s-1; - while ( isspace(*last) ) { - *last = '\0'; - --last; - } - paths.push_back(symbolStart); - symbolStart = NULL; - state = lineStart; - } - break; - case inComment: - if ( *s == '\n' ) - state = lineStart; - break; - } - } - // Note: we do not free() the malloc buffer, because the strings in it are used by exec() -} - - - -static void setSharedDylibs(const char* rootPath, const std::vector& overlayPaths, const std::set& onlyArchs, std::vector rootsPaths) -{ - // set file system root - ArchGraph::setFileSystemRoot(rootPath); - ArchGraph::setFileSystemOverlay(overlayPaths); - - // initialize all architectures requested - for(std::set::iterator a = onlyArchs.begin(); a != onlyArchs.end(); ++a) - ArchGraph::addArchPair(*a); - - // add roots to graph - for(std::vector::const_iterator it = rootsPaths.begin(); it != rootsPaths.end(); ++it) - ArchGraph::addRoot(*it, onlyArchs); - - // determine shared dylibs - for(std::set::iterator a = onlyArchs.begin(); a != onlyArchs.end(); ++a) - ArchGraph::findSharedDylibs(*a); -} - - -static void scanForSharedDylibs(const char* rootPath, const std::vector& overlayPaths, const char* dirOfPathFiles, const std::set& onlyArchs) -{ - char rootDirOfPathFiles[strlen(rootPath)+strlen(dirOfPathFiles)+2]; - // in -root mode, look for roots in /rootpath/var/db/dyld - if ( rootPath[0] != '\0' ) { - strcpy(rootDirOfPathFiles, rootPath); - strcat(rootDirOfPathFiles, dirOfPathFiles); - dirOfPathFiles = rootDirOfPathFiles; - } - - // extract all root paths from files in "/var/db/dyld/shared_region_roots/" - if ( verbose ) - fprintf(stderr, "update_dyld_shared_cache: finding roots in: %s\n", dirOfPathFiles); - std::vector rootsPaths; - DIR* dir = ::opendir(dirOfPathFiles); - if ( dir == NULL ) - throwf("%s does not exist, errno=%d\n", dirOfPathFiles, errno); - for (dirent* entry = ::readdir(dir); entry != NULL; entry = ::readdir(dir)) { - if ( entry->d_type == DT_REG || entry->d_type == DT_UNKNOWN ) { - // only look at regular files ending in .paths - if ( strcmp(&entry->d_name[entry->d_namlen-6], ".paths") == 0 ) { - struct stat tmpStatPathsFile; - char fullPath[strlen(dirOfPathFiles)+entry->d_namlen+2]; - strcpy(fullPath, dirOfPathFiles); - strcat(fullPath, "/"); - strcat(fullPath, entry->d_name); - if ( lstat(fullPath, &tmpStatPathsFile) == -1 ) { - fprintf(stderr, "update_dyld_shared_cache: can't access %s\n", fullPath); - } - else if ( S_ISREG(tmpStatPathsFile.st_mode) ) { - parsePathsFile(fullPath, rootsPaths); - } - else { - fprintf(stderr, "update_dyld_shared_cache: wrong file type for %s\n", fullPath); - } - } - else { - fprintf(stderr, "update_dyld_shared_cache: warning, ignore file with wrong extension: %s\n", entry->d_name); - } - } - } - ::closedir(dir); - - if ( rootsPaths.size() == 0 ) - fprintf(stderr, "update_dyld_shared_cache: warning, no entries found in shared_region_roots\n"); - setSharedDylibs(rootPath, overlayPaths, onlyArchs, rootsPaths); -} - -static void setSharedDylibs(const char* rootPath, const std::vector& overlayPaths, const char* pathsFile, const std::set& onlyArchs) -{ - std::vector rootsPaths; - parsePathsFile(pathsFile, rootsPaths); - setSharedDylibs(rootPath, overlayPaths, onlyArchs, rootsPaths); -} - - -// If the 10.5.0 version of update_dyld_shared_cache was killed or crashed, it -// could leave large half written cache files laying around. The function deletes -// those files. To prevent the deletion of tmp files being created by another -// copy of update_dyld_shared_cache, it only deletes the temp cache file if its -// creation time was before the last restart of this machine. -static void deleteOrphanTempCacheFiles() -{ - DIR* dir = ::opendir(MACOSX_DYLD_SHARED_CACHE_DIR); - if ( dir != NULL ) { - std::vector filesToDelete; - for (dirent* entry = ::readdir(dir); entry != NULL; entry = ::readdir(dir)) { - if ( entry->d_type == DT_REG ) { - // only look at files with .tmp in name - if ( strstr(entry->d_name, ".tmp") != NULL ) { - char fullPath[strlen(MACOSX_DYLD_SHARED_CACHE_DIR)+entry->d_namlen+2]; - strcpy(fullPath, MACOSX_DYLD_SHARED_CACHE_DIR); - strcat(fullPath, "/"); - strcat(fullPath, entry->d_name); - struct stat tmpFileStatInfo; - if ( stat(fullPath, &tmpFileStatInfo) != -1 ) { - int mib[2] = {CTL_KERN, KERN_BOOTTIME}; - struct timeval boottime; - size_t size = sizeof(boottime); - if ( (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1) && (boottime.tv_sec != 0) ) { - // make sure this file is older than the boot time of this machine - if ( tmpFileStatInfo.st_mtime < boottime.tv_sec ) { - filesToDelete.push_back(strdup(fullPath)); - } - } - } - } - } - } - ::closedir(dir); - for(std::vector::iterator it = filesToDelete.begin(); it != filesToDelete.end(); ++it) { - fprintf(stderr, "update_dyld_shared_cache: deleting old temp cache file: %s\n", *it); - ::unlink(*it); - } - } -} - - - -static bool updateSharedeCacheFile(const char* rootPath, const std::vector& overlayPaths, const char* cacheDir, bool explicitCacheDir, const std::set& onlyArchs, - bool force, bool alphaSort, bool optimize, bool deleteExistingFirst, bool verify, bool keepSignatures, bool dontMapLocalSymbols) -{ - bool didUpdate = false; - // get dyld load address info - UniversalMachOLayout* dyldLayout = NULL; - char dyldPath[1024]; - strlcpy(dyldPath, rootPath, 1024); - strlcat(dyldPath, "/usr/lib/dyld", 1024); - struct stat stat_buf; - if ( stat(dyldPath, &stat_buf) == 0 ) { - dyldLayout = new UniversalMachOLayout(dyldPath, &onlyArchs); - } - else { - dyldLayout = new UniversalMachOLayout("/usr/lib/dyld", &onlyArchs); - } - const int archCount = onlyArchs.size(); - int index = 0; - for(std::set::iterator a = onlyArchs.begin(); a != onlyArchs.end(); ++a, ++index) { - const MachOLayoutAbstraction* dyldLayoutForArch = dyldLayout->getSlice(*a); - uint64_t dyldBaseAddress = 0; - if ( dyldLayoutForArch != NULL ) - dyldBaseAddress = dyldLayoutForArch->getBaseAddress(); - else - fprintf(stderr, "update_dyld_shared_cache: warning, dyld not available for specified architectures\n"); - switch ( a->arch ) { - case CPU_TYPE_I386: - { - SharedCache cache(ArchGraph::graphForArchPair(*a), rootPath, overlayPaths, cacheDir, explicitCacheDir, alphaSort, verify, optimize, dyldBaseAddress); - didUpdate |= cache.update(force, optimize, deleteExistingFirst, index, archCount, keepSignatures, dontMapLocalSymbols); - } - break; - case CPU_TYPE_X86_64: - { - SharedCache cache(ArchGraph::graphForArchPair(*a), rootPath, overlayPaths, cacheDir, explicitCacheDir, alphaSort, verify, optimize, dyldBaseAddress); - didUpdate |= cache.update(force, optimize, deleteExistingFirst, index, archCount, keepSignatures, dontMapLocalSymbols); - } - break; - case CPU_TYPE_ARM: - { - SharedCache cache(ArchGraph::graphForArchPair(*a), rootPath, overlayPaths, cacheDir, explicitCacheDir, alphaSort, verify, optimize, dyldBaseAddress); - didUpdate |= cache.update(force, optimize, deleteExistingFirst, index, archCount, keepSignatures, dontMapLocalSymbols); - } - break; - case CPU_TYPE_ARM64: - { - SharedCache cache(ArchGraph::graphForArchPair(*a), rootPath, overlayPaths, cacheDir, explicitCacheDir, alphaSort, verify, optimize, dyldBaseAddress); - didUpdate |= cache.update(force, optimize, deleteExistingFirst, index, archCount, keepSignatures, dontMapLocalSymbols); - } - break; - } - } - - if ( !iPhoneOS ) - deleteOrphanTempCacheFiles(); - - return didUpdate; -} - - -static void usage() -{ - fprintf(stderr, "update_dyld_shared_cache [-force] [-root dir] [-overlay dir] [-arch arch] [-debug]\n"); -} - - -int main(int argc, const char* argv[]) -{ - std::set onlyArchs; - const char* rootPath = ""; - std::vector overlayPaths; - const char* dylibListFile = NULL; - bool force = false; - bool alphaSort = false; - bool optimize = true; - bool verify = false; - bool keepSignatures = false; - bool explicitCacheDir = false; - bool dontMapLocalSymbols = false; - bool relaunchForHaswell = false; - const char* cacheDir = NULL; - - try { - // parse command line options - for(int i=1; i < argc; ++i) { - const char* arg = argv[i]; - if ( arg[0] == '-' ) { - if ( strcmp(arg, "-debug") == 0 ) { - verbose = true; - } - else if ( strcmp(arg, "-force") == 0 ) { - force = true; - } - else if ( strcmp(arg, "-verify") == 0 ) { - verify = true; - } - else if ( strcmp(arg, "-sort_by_name") == 0 ) { - alphaSort = true; - } - else if ( strcmp(arg, "-progress") == 0 ) { - progress = true; - } - else if ( strcmp(arg, "-opt") == 0 ) { - optimize = true; - } - else if ( strcmp(arg, "-no_opt") == 0 ) { - optimize = false; - } - else if ( strcmp(arg, "-dont_map_local_symbols") == 0 ) { - dontMapLocalSymbols = true; - } - else if ( strcmp(arg, "-iPhone") == 0 ) { - iPhoneOS = true; - alphaSort = true; - } - else if ( strcmp(arg, "-dylib_list") == 0 ) { - dylibListFile = argv[++i]; - if ( dylibListFile == NULL ) - throw "-dylib_list missing path argument"; - } - else if ( (strcmp(arg, "-root") == 0) || (strcmp(arg, "--root") == 0) ) { - rootPath = argv[++i]; - if ( rootPath == NULL ) - throw "-root missing path argument"; - } - else if ( strcmp(arg, "-overlay") == 0 ) { - const char* path = argv[++i]; - if ( path == NULL ) - throw "-overlay missing path argument"; - overlayPaths.push_back(path); - } - else if ( strcmp(arg, "-cache_dir") == 0 ) { - cacheDir = argv[++i]; - if ( cacheDir == NULL ) - throw "-cache_dir missing path argument"; - explicitCacheDir = true; - } - else if ( strcmp(arg, "-arch") == 0 ) { - const char* arch = argv[++i]; - if ( strcmp(arch, "i386") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL)); - else if ( strcmp(arch, "x86_64") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL)); - else if ( strcmp(arch, "x86_64h") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H)); - else if ( strcmp(arch, "armv4t") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V4T)); - else if ( strcmp(arch, "armv5") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V5TEJ)); - else if ( strcmp(arch, "armv6") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6)); - else if ( strcmp(arch, "armv7") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7)); - else if ( strcmp(arch, "armv7f") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7F)); - else if ( strcmp(arch, "armv7k") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K)); - else if ( strcmp(arch, "armv7s") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S)); - else if ( strcmp(arch, "arm64") == 0 ) - onlyArchs.insert(ArchPair(CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL)); - else - throwf("unknown architecture %s", arch); - } - else if ( strcmp(arg, "-universal_boot") == 0 ) { - onlyArchs.insert(ArchPair(CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL)); - onlyArchs.insert(ArchPair(CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL)); - relaunchForHaswell = true; - } - else { - usage(); - throwf("unknown option: %s\n", arg); - } - } - else { - usage(); - throwf("unknown option: %s\n", arg); - } - } - - // strip tailing slashes on -root - // make it a real path so as to not make all dylibs look like symlink aliases - if ( rootPath[0] != '\0' ) { - char realRootPath[MAXPATHLEN]; - if ( realpath(rootPath, realRootPath) == NULL ) - throwf("realpath() failed on %s\n", rootPath); - rootPath = strdup(realRootPath); - } - - // strip tailing slashes on -overlay - for (std::vector::iterator it=overlayPaths.begin(); it != overlayPaths.end(); ++it) { - char realOverlayPath[MAXPATHLEN]; - if ( realpath(*it, realOverlayPath) == NULL ) - throwf("realpath() failed on %s\n", *it); - *it = strdup(realOverlayPath); - } - - // set default location to write cache dir - if ( cacheDir == NULL ) - cacheDir = (iPhoneOS ? IPHONE_DYLD_SHARED_CACHE_DIR : MACOSX_DYLD_SHARED_CACHE_DIR); - - // if no restrictions specified, use architectures that work on this machine - if ( onlyArchs.size() == 0 ) { - if ( iPhoneOS ) { - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6)); - onlyArchs.insert(ArchPair(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7)); - } - else { - int available; - size_t len = sizeof(int); - #if __i386__ || __x86_64__ - onlyArchs.insert(ArchPair(CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL)); - // check system is capable of running 64-bit programs - if ( (sysctlbyname("hw.optional.x86_64", &available, &len, NULL, 0) == 0) && available ) { - // check system is capable of running x86_64h code - struct host_basic_info info; - mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; - mach_port_t hostPort = mach_host_self(); - kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count); - mach_port_deallocate(mach_task_self(), hostPort); - if ( result != KERN_SUCCESS ) - throw "host_info() failed"; - if ( info.cpu_subtype == CPU_SUBTYPE_X86_64_H ) - onlyArchs.insert(ArchPair(CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H)); - else - onlyArchs.insert(ArchPair(CPU_TYPE_X86_64, CPU_SUBTYPE_X86_ALL)); - } - #else - #error unsupported architecture - #endif - } - } - - if ( !verify && (geteuid() != 0) ) - throw "you must be root to run this tool"; - - // build list of shared dylibs - if ( dylibListFile != NULL ) - setSharedDylibs(rootPath, overlayPaths, dylibListFile, onlyArchs); - else - scanForSharedDylibs(rootPath, overlayPaths, "/var/db/dyld/shared_region_roots/", onlyArchs); - bool didUpdate = updateSharedeCacheFile(rootPath, overlayPaths, cacheDir, explicitCacheDir, onlyArchs, force, alphaSort, optimize, - false, verify, keepSignatures, dontMapLocalSymbols); - - if ( didUpdate && !iPhoneOS ) { - void* handle = dlopen("/usr/lib/libspindump.dylib", RTLD_LAZY); - if ( handle != NULL ) { - typedef bool (*dscsym_proc_t)(const char *root); - dscsym_proc_t proc = (dscsym_proc_t)dlsym(handle, "dscsym_save_nuggets_for_current_caches"); - const char* nuggetRootPath = "/"; - if ( !overlayPaths.empty() ) - nuggetRootPath = overlayPaths[0]; - else if ( rootPath[0] != '\0' ) - nuggetRootPath = rootPath; - (*proc)(nuggetRootPath); - } - dlclose(handle); - } - - if ( relaunchForHaswell ) { - char cmd[2048]; - strlcpy(cmd, argv[0], 2048); - strlcat(cmd, " -arch x86_64h", 2048); - if ( force ) - strlcat(cmd, " -force", 2048); - if ( verify ) - strlcat(cmd, " -verify", 2048); - if ( alphaSort ) - strlcat(cmd, " -sort_by_name", 2048); - if ( (rootPath != NULL) && (rootPath[0] != '\0') ) { - strlcat(cmd, " -root ", 2048); - strlcat(cmd, rootPath, 2048); - } - return system(cmd); - } - - } - catch (const char* msg) { - fprintf(stderr, "update_dyld_shared_cache failed: %s\n", msg); - return 1; - } - - return 0; -} - - - diff --git a/src/ImageLoader.cpp b/src/ImageLoader.cpp index 657677a..89d9cdc 100644 --- a/src/ImageLoader.cpp +++ b/src/ImageLoader.cpp @@ -53,12 +53,16 @@ uint32_t ImageLoader::fgTotalSegmentsMapped = 0; uint64_t ImageLoader::fgTotalBytesMapped = 0; uint64_t ImageLoader::fgTotalBytesPreFetched = 0; uint64_t ImageLoader::fgTotalLoadLibrariesTime; +uint64_t ImageLoader::fgTotalObjCSetupTime = 0; +uint64_t ImageLoader::fgTotalDebuggerPausedTime = 0; +uint64_t ImageLoader::fgTotalRebindCacheTime = 0; uint64_t ImageLoader::fgTotalRebaseTime; uint64_t ImageLoader::fgTotalBindTime; uint64_t ImageLoader::fgTotalWeakBindTime; uint64_t ImageLoader::fgTotalDOF; uint64_t ImageLoader::fgTotalInitTime; uint16_t ImageLoader::fgLoadOrdinal = 0; +uint32_t ImageLoader::fgSymbolTrieSearchs = 0; std::vectorImageLoader::fgInterposingTuples; uintptr_t ImageLoader::fgNextPIEDylibAddress = 0; @@ -106,7 +110,7 @@ void ImageLoader::setFileInfo(dev_t device, ino_t inode, time_t modDate) void ImageLoader::setMapped(const LinkContext& context) { fState = dyld_image_state_mapped; - context.notifySingle(dyld_image_state_mapped, this); // note: can throw exception + context.notifySingle(dyld_image_state_mapped, this, NULL); // note: can throw exception } int ImageLoader::compare(const ImageLoader* right) const @@ -190,15 +194,20 @@ bool ImageLoader::statMatch(const struct stat& stat_buf) const return ( (this->fDevice == stat_buf.st_dev) && (this->fInode == stat_buf.st_ino) ); } -const char* ImageLoader::getShortName() const +const char* ImageLoader::shortName(const char* fullName) { // try to return leaf name - if ( fPath != NULL ) { - const char* s = strrchr(fPath, '/'); + if ( fullName != NULL ) { + const char* s = strrchr(fullName, '/'); if ( s != NULL ) return &s[1]; } - return fPath; + return fullName; +} + +const char* ImageLoader::getShortName() const +{ + return shortName(fPath); } void ImageLoader::setLeaveMapped() @@ -288,6 +297,18 @@ static bool notInImgageList(const ImageLoader* image, const ImageLoader** dsiSta return true; } +bool ImageLoader::findExportedSymbolAddress(const LinkContext& context, const char* symbolName, + const ImageLoader* requestorImage, int requestorOrdinalOfDef, + bool runResolver, const ImageLoader** foundIn, uintptr_t* address) const +{ + const Symbol* sym = this->findExportedSymbol(symbolName, true, foundIn); + if ( sym != NULL ) { + *address = (*foundIn)->getExportedSymbolAddress(sym, context, requestorImage, runResolver); + return true; + } + return false; +} + // private method that handles circular dependencies by only search any image once const ImageLoader::Symbol* ImageLoader::findExportedSymbolInDependentImagesExcept(const char* name, @@ -296,7 +317,7 @@ const ImageLoader::Symbol* ImageLoader::findExportedSymbolInDependentImagesExcep const ImageLoader::Symbol* sym; // search self if ( notInImgageList(this, dsiStart, dsiCur) ) { - sym = this->findExportedSymbol(name, false, foundIn); + sym = this->findExportedSymbol(name, false, this->getPath(), foundIn); if ( sym != NULL ) return sym; *dsiCur++ = this; @@ -306,7 +327,7 @@ const ImageLoader::Symbol* ImageLoader::findExportedSymbolInDependentImagesExcep for(unsigned int i=0; i < libraryCount(); ++i) { ImageLoader* dependentImage = libImage(i); if ( (dependentImage != NULL) && notInImgageList(dependentImage, dsiStart, dsiCur) ) { - const ImageLoader::Symbol* sym = dependentImage->findExportedSymbol(name, false, foundIn); + sym = dependentImage->findExportedSymbol(name, false, libPath(i), foundIn); if ( sym != NULL ) return sym; } @@ -317,7 +338,7 @@ const ImageLoader::Symbol* ImageLoader::findExportedSymbolInDependentImagesExcep ImageLoader* dependentImage = libImage(i); if ( (dependentImage != NULL) && notInImgageList(dependentImage, dsiStart, dsiCur) ) { *dsiCur++ = dependentImage; - const ImageLoader::Symbol* sym = dependentImage->findExportedSymbolInDependentImagesExcept(name, dsiStart, dsiCur, dsiEnd, foundIn); + sym = dependentImage->findExportedSymbolInDependentImagesExcept(name, dsiStart, dsiCur, dsiEnd, foundIn); if ( sym != NULL ) return sym; } @@ -329,7 +350,7 @@ const ImageLoader::Symbol* ImageLoader::findExportedSymbolInDependentImagesExcep const ImageLoader::Symbol* ImageLoader::findExportedSymbolInDependentImages(const char* name, const LinkContext& context, const ImageLoader** foundIn) const { - unsigned int imageCount = context.imageCount(); + unsigned int imageCount = context.imageCount()+2; const ImageLoader* dontSearchImages[imageCount]; dontSearchImages[0] = this; // don't search this image const ImageLoader** cur = &dontSearchImages[1]; @@ -338,7 +359,7 @@ const ImageLoader::Symbol* ImageLoader::findExportedSymbolInDependentImages(cons const ImageLoader::Symbol* ImageLoader::findExportedSymbolInImageOrDependentImages(const char* name, const LinkContext& context, const ImageLoader** foundIn) const { - unsigned int imageCount = context.imageCount(); + unsigned int imageCount = context.imageCount()+2; const ImageLoader* dontSearchImages[imageCount]; const ImageLoader** cur = &dontSearchImages[0]; return this->findExportedSymbolInDependentImagesExcept(name, &dontSearchImages[0], cur, &dontSearchImages[imageCount], foundIn); @@ -388,17 +409,17 @@ void ImageLoader::addDynamicInterposingTuples(const struct dyld_interpose_tuple } -void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths) +void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath) { - //dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", this->getPath(), fDlopenReferenceCount, fNeverUnload); + //dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload); // clear error strings - (*context.setErrorStrings)(dyld_error_kind_none, NULL, NULL, NULL); + (*context.setErrorStrings)(0, NULL, NULL, NULL); uint64_t t0 = mach_absolute_time(); - this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths); - context.notifyBatch(dyld_image_state_dependents_mapped); - + this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath); + context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly); + // we only do the loading step for preflights if ( preflightOnly ) return; @@ -409,7 +430,7 @@ void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool pr uint64_t t2 = mach_absolute_time(); this->recursiveRebase(context); - context.notifyBatch(dyld_image_state_rebased); + context.notifyBatch(dyld_image_state_rebased, false); uint64_t t3 = mach_absolute_time(); this->recursiveBind(context, forceLazysBound, neverUnload); @@ -419,7 +440,7 @@ void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool pr this->weakBind(context); uint64_t t5 = mach_absolute_time(); - context.notifyBatch(dyld_image_state_bound); + context.notifyBatch(dyld_image_state_bound, false); uint64_t t6 = mach_absolute_time(); std::vector dofs; @@ -433,7 +454,7 @@ void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool pr } // clear error strings - (*context.setErrorStrings)(dyld_error_kind_none, NULL, NULL, NULL); + (*context.setErrorStrings)(0, NULL, NULL, NULL); fgTotalLoadLibrariesTime += t1 - t0; fgTotalRebaseTime += t3 - t2; @@ -468,14 +489,14 @@ bool ImageLoader::decrementDlopenReferenceCount() void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread, InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images) { - uint32_t maxImageCount = context.imageCount(); + uint32_t maxImageCount = context.imageCount()+2; ImageLoader::UninitedUpwards upsBuffer[maxImageCount]; ImageLoader::UninitedUpwards& ups = upsBuffer[0]; ups.count = 0; // Calling recursive init on all images in images list, building a new list of // uninitialized upward dependencies. for (uintptr_t i=0; i < images.count; ++i) { - images.images[i]->recursiveInitialization(context, thisThread, timingInfo, ups); + images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups); } // If any upward dependencies remain, init them. if ( ups.count > 0 ) @@ -491,7 +512,7 @@ void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingL up.count = 1; up.images[0] = this; processInitializers(context, thisThread, timingInfo, up); - context.notifyBatch(dyld_image_state_initialized); + context.notifyBatch(dyld_image_state_initialized, false); mach_port_deallocate(mach_task_self(), thisThread); uint64_t t2 = mach_absolute_time(); fgTotalInitTime += (t2 - t1); @@ -574,7 +595,7 @@ unsigned int ImageLoader::recursiveUpdateDepth(unsigned int maxDepth) } -void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths) +void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath) { if ( fState < dyld_image_state_dependents_mapped ) { // break cycles @@ -594,7 +615,7 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli for(unsigned int i=0; i < fLibraryCount; ++i){ ImageLoader* dependentLib; bool depLibReExported = false; - bool depLibReRequired = false; + bool depLibRequired = false; bool depLibCheckSumsMatch = false; DependentLibraryInfo& requiredLibInfo = libraryInfos[i]; #if DYLD_SHARED_CACHE_SUPPORT @@ -606,10 +627,11 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli } #endif try { - dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(), &thisRPaths); + unsigned cacheIndex; + dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(), &thisRPaths, cacheIndex); if ( dependentLib == this ) { // found circular reference, perhaps DYLD_LIBARY_PATH is causing this rdar://problem/3684168 - dependentLib = context.loadLibrary(requiredLibInfo.name, false, NULL, NULL); + dependentLib = context.loadLibrary(requiredLibInfo.name, false, NULL, NULL, cacheIndex); if ( dependentLib != this ) dyld::warn("DYLD_ setting caused circular dependency in %s\n", this->getPath()); } @@ -620,8 +642,8 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli else { dependentLib->fIsReferencedDownward = true; } - LibraryInfo actualInfo = dependentLib->doGetLibraryInfo(); - depLibReRequired = requiredLibInfo.required; + LibraryInfo actualInfo = dependentLib->doGetLibraryInfo(requiredLibInfo.info); + depLibRequired = requiredLibInfo.required; depLibCheckSumsMatch = ( actualInfo.checksum == requiredLibInfo.info.checksum ); depLibReExported = requiredLibInfo.reExported; if ( ! depLibReExported ) { @@ -637,8 +659,8 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli dependentLib->getShortName(), actualInfo.minVersion >> 16, (actualInfo.minVersion >> 8) & 0xff, actualInfo.minVersion & 0xff); } // prebinding for this image disabled if any dependent library changed - if ( !depLibCheckSumsMatch ) - canUsePrelinkingInfo = false; + //if ( !depLibCheckSumsMatch ) + // canUsePrelinkingInfo = false; // prebinding for this image disabled unless both this and dependent are in the shared cache if ( !dependentLib->inSharedCache() || !this->inSharedCache() ) canUsePrelinkingInfo = false; @@ -657,12 +679,18 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli if ( requiredLibInfo.required ) { fState = dyld_image_state_mapped; // record values for possible use by CrashReporter or Finder - if ( strstr(msg, "Incompatible") != NULL ) - (*context.setErrorStrings)(dyld_error_kind_dylib_version, this->getPath(), requiredLibInfo.name, NULL); + if ( strstr(msg, "Incompatible library version") != NULL ) + (*context.setErrorStrings)(DYLD_EXIT_REASON_DYLIB_WRONG_VERSION, this->getPath(), requiredLibInfo.name, NULL); else if ( strstr(msg, "architecture") != NULL ) - (*context.setErrorStrings)(dyld_error_kind_dylib_wrong_arch, this->getPath(), requiredLibInfo.name, NULL); + (*context.setErrorStrings)(DYLD_EXIT_REASON_DYLIB_WRONG_ARCH, this->getPath(), requiredLibInfo.name, NULL); + else if ( strstr(msg, "file system sandbox") != NULL ) + (*context.setErrorStrings)(DYLD_EXIT_REASON_FILE_SYSTEM_SANDBOX, this->getPath(), requiredLibInfo.name, NULL); + else if ( strstr(msg, "code signature") != NULL ) + (*context.setErrorStrings)(DYLD_EXIT_REASON_CODE_SIGNATURE, this->getPath(), requiredLibInfo.name, NULL); + else if ( strstr(msg, "malformed") != NULL ) + (*context.setErrorStrings)(DYLD_EXIT_REASON_MALFORMED_MACHO, this->getPath(), requiredLibInfo.name, NULL); else - (*context.setErrorStrings)(dyld_error_kind_dylib_missing, this->getPath(), requiredLibInfo.name, NULL); + (*context.setErrorStrings)(DYLD_EXIT_REASON_DYLIB_MISSING, this->getPath(), requiredLibInfo.name, NULL); const char* newMsg = dyld::mkstringf("Library not loaded: %s\n Referenced from: %s\n Reason: %s", requiredLibInfo.name, this->getRealPath(), msg); free((void*)msg); // our free() will do nothing if msg is a string literal throw newMsg; @@ -680,7 +708,7 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli for(unsigned int i=0; i < libraryCount(); ++i) { ImageLoader* dependentImage = libImage(i); if ( dependentImage != NULL ) { - dependentImage->recursiveLoadLibraries(context, preflightOnly, thisRPaths); + dependentImage->recursiveLoadLibraries(context, preflightOnly, thisRPaths, libraryInfos[i].name); } } @@ -722,7 +750,7 @@ void ImageLoader::recursiveRebase(const LinkContext& context) doRebase(context); // notify - context.notifySingle(dyld_image_state_rebased, this); + context.notifySingle(dyld_image_state_rebased, this, NULL); } catch (const char* msg) { // this image is not rebased @@ -786,7 +814,7 @@ void ImageLoader::recursiveBind(const LinkContext& context, bool forceLazysBound if ( neverUnload ) this->setNeverUnload(); - context.notifySingle(dyld_image_state_bound, this); + context.notifySingle(dyld_image_state_bound, this, NULL); } catch (const char* msg) { // restore state @@ -804,20 +832,17 @@ void ImageLoader::weakBind(const LinkContext& context) uint64_t t1 = mach_absolute_time(); // get set of ImageLoaders that participate in coalecsing ImageLoader* imagesNeedingCoalescing[fgImagesRequiringCoalescing]; - int count = context.getCoalescedImages(imagesNeedingCoalescing); + unsigned imageIndexes[fgImagesRequiringCoalescing]; + int count = context.getCoalescedImages(imagesNeedingCoalescing, imageIndexes); // count how many have not already had weakbinding done int countNotYetWeakBound = 0; - int countOfImagesWithWeakDefinitions = 0; int countOfImagesWithWeakDefinitionsNotInSharedCache = 0; for(int i=0; i < count; ++i) { - if ( ! imagesNeedingCoalescing[i]->fWeakSymbolsBound ) + if ( ! imagesNeedingCoalescing[i]->weakSymbolsBound(imageIndexes[i]) ) ++countNotYetWeakBound; - if ( imagesNeedingCoalescing[i]->hasCoalescedExports() ) { - ++countOfImagesWithWeakDefinitions; - if ( ! imagesNeedingCoalescing[i]->inSharedCache() ) - ++countOfImagesWithWeakDefinitionsNotInSharedCache; - } + if ( ! imagesNeedingCoalescing[i]->inSharedCache() ) + ++countOfImagesWithWeakDefinitionsNotInSharedCache; } // don't need to do any coalescing if only one image has overrides, or all have already been done @@ -826,10 +851,10 @@ void ImageLoader::weakBind(const LinkContext& context) ImageLoader::CoalIterator iterators[count]; ImageLoader::CoalIterator* sortedIts[count]; for(int i=0; i < count; ++i) { - imagesNeedingCoalescing[i]->initializeCoalIterator(iterators[i], i); + imagesNeedingCoalescing[i]->initializeCoalIterator(iterators[i], i, imageIndexes[i]); sortedIts[i] = &iterators[i]; if ( context.verboseWeakBind ) - dyld::log("dyld: weak bind load order %d/%d for %s\n", i, count, imagesNeedingCoalescing[i]->getPath()); + dyld::log("dyld: weak bind load order %d/%d for %s\n", i, count, imagesNeedingCoalescing[i]->getIndexedPath(imageIndexes[i])); } // walk all symbols keeping iterators in sync by @@ -862,21 +887,25 @@ void ImageLoader::weakBind(const LinkContext& context) // pick first symbol in load order (and non-weak overrides weak) uintptr_t targetAddr = 0; ImageLoader* targetImage = NULL; + unsigned targetImageIndex = 0; for(int i=0; i < count; ++i) { if ( strcmp(iterators[i].symbolName, nameToCoalesce) == 0 ) { if ( context.verboseWeakBind ) - dyld::log("dyld: weak bind, found %s weak=%d in %s \n", nameToCoalesce, iterators[i].weakSymbol, iterators[i].image->getPath()); + dyld::log("dyld: weak bind, found %s weak=%d in %s \n", nameToCoalesce, iterators[i].weakSymbol, iterators[i].image->getIndexedPath((unsigned)iterators[i].imageIndex)); if ( iterators[i].weakSymbol ) { if ( targetAddr == 0 ) { targetAddr = iterators[i].image->getAddressCoalIterator(iterators[i], context); - if ( targetAddr != 0 ) + if ( targetAddr != 0 ) { targetImage = iterators[i].image; + targetImageIndex = (unsigned)iterators[i].imageIndex; + } } } else { targetAddr = iterators[i].image->getAddressCoalIterator(iterators[i], context); if ( targetAddr != 0 ) { targetImage = iterators[i].image; + targetImageIndex = (unsigned)iterators[i].imageIndex; // strong implementation found, stop searching break; } @@ -885,14 +914,19 @@ void ImageLoader::weakBind(const LinkContext& context) } // tell each to bind to this symbol (unless already bound) if ( targetAddr != 0 ) { - if ( context.verboseWeakBind ) - dyld::log("dyld: weak binding all uses of %s to copy from %s\n", nameToCoalesce, targetImage->getShortName()); + if ( context.verboseWeakBind ) { + dyld::log("dyld: weak binding all uses of %s to copy from %s\n", + nameToCoalesce, targetImage->getIndexedShortName(targetImageIndex)); + } for(int i=0; i < count; ++i) { if ( strcmp(iterators[i].symbolName, nameToCoalesce) == 0 ) { - if ( context.verboseWeakBind ) - dyld::log("dyld: weak bind, setting all uses of %s in %s to 0x%lX from %s\n", nameToCoalesce, iterators[i].image->getShortName(), targetAddr, targetImage->getShortName()); - if ( ! iterators[i].image->fWeakSymbolsBound ) - iterators[i].image->updateUsesCoalIterator(iterators[i], targetAddr, targetImage, context); + if ( context.verboseWeakBind ) { + dyld::log("dyld: weak bind, setting all uses of %s in %s to 0x%lX from %s\n", + nameToCoalesce, iterators[i].image->getIndexedShortName((unsigned)iterators[i].imageIndex), + targetAddr, targetImage->getIndexedShortName(targetImageIndex)); + } + if ( ! iterators[i].image->weakSymbolsBound(imageIndexes[i]) ) + iterators[i].image->updateUsesCoalIterator(iterators[i], targetAddr, targetImage, targetImageIndex, context); iterators[i].symbolMatches = false; } } @@ -903,7 +937,7 @@ void ImageLoader::weakBind(const LinkContext& context) // mark all as having all weak symbols bound for(int i=0; i < count; ++i) { - imagesNeedingCoalescing[i]->fWeakSymbolsBound = true; + imagesNeedingCoalescing[i]->setWeakSymbolsBound(imageIndexes[i]); } } uint64_t t2 = mach_absolute_time(); @@ -964,8 +998,20 @@ void ImageLoader::recursiveSpinUnLock() fInitializerRecursiveLock = NULL; } +void ImageLoader::InitializerTimingList::addTime(const char* name, uint64_t time) +{ + for (int i=0; i < count; ++i) { + if ( strcmp(images[i].shortName, name) == 0 ) { + images[i].initTime += time; + return; + } + } + images[count].initTime = time; + images[count].shortName = name; + ++count; +} -void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, +void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) { recursive_lock lock_info(this_thread); @@ -986,7 +1032,7 @@ void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_ uninitUps.count++; } else if ( dependentImage->fDepth >= fDepth ) { - dependentImage->recursiveInitialization(context, this_thread, timingInfo, uninitUps); + dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps); } } } @@ -999,7 +1045,7 @@ void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_ uint64_t t1 = mach_absolute_time(); fState = dyld_image_state_dependents_initialized; oldState = fState; - context.notifySingle(dyld_image_state_dependents_initialized, this); + context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo); // initialize this image bool hasInitializers = this->doInitialization(context); @@ -1007,13 +1053,11 @@ void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_ // let anyone know we finished initializing this image fState = dyld_image_state_initialized; oldState = fState; - context.notifySingle(dyld_image_state_initialized, this); + context.notifySingle(dyld_image_state_initialized, this, NULL); if ( hasInitializers ) { uint64_t t2 = mach_absolute_time(); - timingInfo.images[timingInfo.count].image = this; - timingInfo.images[timingInfo.count].initTime = (t2-t1); - timingInfo.count++; + timingInfo.addTime(this->getShortName(), t2-t1); } } catch (const char* msg) { @@ -1042,7 +1086,12 @@ static void printTime(const char* msg, uint64_t partTime, uint64_t totalTime) uint32_t milliSeconds = (uint32_t)(milliSecondsTimesHundred/100); uint32_t percentTimesTen = (uint32_t)((partTime*1000)/totalTime); uint32_t percent = percentTimesTen/10; - dyld::log("%s: %u.%02u milliseconds (%u.%u%%)\n", msg, milliSeconds, milliSecondsTimesHundred-milliSeconds*100, percent, percentTimesTen-percent*10); + if ( milliSeconds >= 100 ) + dyld::log("%s: %u.%02u milliseconds (%u.%u%%)\n", msg, milliSeconds, milliSecondsTimesHundred-milliSeconds*100, percent, percentTimesTen-percent*10); + else if ( milliSeconds >= 10 ) + dyld::log("%s: %u.%02u milliseconds (%u.%u%%)\n", msg, milliSeconds, milliSecondsTimesHundred-milliSeconds*100, percent, percentTimesTen-percent*10); + else + dyld::log("%s: %u.%02u milliseconds (%u.%u%%)\n", msg, milliSeconds, milliSecondsTimesHundred-milliSeconds*100, percent, percentTimesTen-percent*10); } else { uint32_t secondsTimeTen = (uint32_t)((partTime*10)/sUnitsPerSecond); @@ -1077,25 +1126,43 @@ static char* commatize(uint64_t in, char* out) void ImageLoader::printStatistics(unsigned int imageCount, const InitializerTimingList& timingInfo) { - uint64_t totalTime = fgTotalLoadLibrariesTime + fgTotalRebaseTime + fgTotalBindTime + fgTotalWeakBindTime + fgTotalDOF + fgTotalInitTime; + uint64_t totalTime = fgTotalLoadLibrariesTime + fgTotalRebaseTime + fgTotalBindTime + fgTotalWeakBindTime + fgTotalDOF + fgTotalInitTime; + + uint64_t totalDyldTime = totalTime - fgTotalDebuggerPausedTime - fgTotalRebindCacheTime; + printTime("Total pre-main time", totalDyldTime, totalDyldTime); + printTime(" dylib loading time", fgTotalLoadLibrariesTime-fgTotalDebuggerPausedTime, totalDyldTime); + printTime(" rebase/binding time", fgTotalRebaseTime+fgTotalBindTime+fgTotalWeakBindTime-fgTotalRebindCacheTime, totalDyldTime); + printTime(" ObjC setup time", fgTotalObjCSetupTime, totalDyldTime); + printTime(" initializer time", fgTotalInitTime-fgTotalObjCSetupTime, totalDyldTime); + dyld::log(" slowest intializers :\n"); + for (uintptr_t i=0; i < timingInfo.count; ++i) { + uint64_t t = timingInfo.images[i].initTime; + if ( t*50 < totalDyldTime ) + continue; + dyld::log("%30s ", timingInfo.images[i].shortName); + if ( strncmp(timingInfo.images[i].shortName, "libSystem.", 10) == 0 ) + t -= fgTotalObjCSetupTime; + printTime("", t, totalDyldTime); + } + dyld::log("\n"); +} + +void ImageLoader::printStatisticsDetails(unsigned int imageCount, const InitializerTimingList& timingInfo) +{ + uint64_t totalTime = fgTotalLoadLibrariesTime + fgTotalRebaseTime + fgTotalBindTime + fgTotalWeakBindTime + fgTotalDOF + fgTotalInitTime; char commaNum1[40]; char commaNum2[40]; - printTime("total time", totalTime, totalTime); -#if __IPHONE_OS_VERSION_MIN_REQUIRED - if ( fgImagesUsedFromSharedCache != 0 ) - dyld::log("total images loaded: %d (%u from dyld shared cache)\n", imageCount, fgImagesUsedFromSharedCache); - else - dyld::log("total images loaded: %d\n", imageCount); -#else - dyld::log("total images loaded: %d (%u from dyld shared cache)\n", imageCount, fgImagesUsedFromSharedCache); -#endif - dyld::log("total segments mapped: %u, into %llu pages with %llu pages pre-fetched\n", fgTotalSegmentsMapped, fgTotalBytesMapped/4096, fgTotalBytesPreFetched/4096); - printTime("total images loading time", fgTotalLoadLibrariesTime, totalTime); - printTime("total dtrace DOF registration time", fgTotalDOF, totalTime); - dyld::log("total rebase fixups: %s\n", commatize(fgTotalRebaseFixups, commaNum1)); - printTime("total rebase fixups time", fgTotalRebaseTime, totalTime); - dyld::log("total binding fixups: %s\n", commatize(fgTotalBindFixups, commaNum1)); + printTime(" total time", totalTime, totalTime); + dyld::log(" total images loaded: %d (%u from dyld shared cache)\n", imageCount, fgImagesUsedFromSharedCache); + dyld::log(" total segments mapped: %u, into %llu pages with %llu pages pre-fetched\n", fgTotalSegmentsMapped, fgTotalBytesMapped/4096, fgTotalBytesPreFetched/4096); + printTime(" total images loading time", fgTotalLoadLibrariesTime, totalTime); + printTime(" total load time in ObjC", fgTotalObjCSetupTime, totalTime); + printTime(" total debugger pause time", fgTotalDebuggerPausedTime, totalTime); + printTime(" total dtrace DOF registration time", fgTotalDOF, totalTime); + dyld::log(" total rebase fixups: %s\n", commatize(fgTotalRebaseFixups, commaNum1)); + printTime(" total rebase fixups time", fgTotalRebaseTime, totalTime); + dyld::log(" total binding fixups: %s\n", commatize(fgTotalBindFixups, commaNum1)); if ( fgTotalBindSymbolsResolved != 0 ) { uint32_t avgTimesTen = (fgTotalBindImageSearches * 10) / fgTotalBindSymbolsResolved; uint32_t avgInt = fgTotalBindImageSearches / fgTotalBindSymbolsResolved; @@ -1103,13 +1170,19 @@ void ImageLoader::printStatistics(unsigned int imageCount, const InitializerTimi dyld::log("total binding symbol lookups: %s, average images searched per symbol: %u.%u\n", commatize(fgTotalBindSymbolsResolved, commaNum1), avgInt, avgTenths); } - printTime("total binding fixups time", fgTotalBindTime, totalTime); - printTime("total weak binding fixups time", fgTotalWeakBindTime, totalTime); - dyld::log("total bindings lazily fixed up: %s of %s\n", commatize(fgTotalLazyBindFixups, commaNum1), commatize(fgTotalPossibleLazyBindFixups, commaNum2)); - printTime("total initializer time", fgTotalInitTime, totalTime); + printTime(" total binding fixups time", fgTotalBindTime, totalTime); + printTime(" total weak binding fixups time", fgTotalWeakBindTime, totalTime); + printTime(" total redo shared cached bindings time", fgTotalRebindCacheTime, totalTime); + dyld::log(" total bindings lazily fixed up: %s of %s\n", commatize(fgTotalLazyBindFixups, commaNum1), commatize(fgTotalPossibleLazyBindFixups, commaNum2)); + printTime(" total time in initializers and ObjC +load", fgTotalInitTime-fgTotalObjCSetupTime, totalTime); for (uintptr_t i=0; i < timingInfo.count; ++i) { - dyld::log("%21s ", timingInfo.images[i].image->getShortName()); - printTime("", timingInfo.images[i].initTime, totalTime); + uint64_t t = timingInfo.images[i].initTime; + if ( t*1000 < totalTime ) + continue; + dyld::log("%42s ", timingInfo.images[i].shortName); + if ( strncmp(timingInfo.images[i].shortName, "libSystem.", 10) == 0 ) + t -= fgTotalObjCSetupTime; + printTime("", t, totalTime); } } @@ -1145,6 +1218,128 @@ void ImageLoader::addSuffix(const char* path, const char* suffix, char* result) } +// +// This function is the hotspot of symbol lookup. It was pulled out of findExportedSymbol() +// to enable it to be re-written in assembler if needed. +// +const uint8_t* ImageLoader::trieWalk(const uint8_t* start, const uint8_t* end, const char* s) +{ + //dyld::log("trieWalk(%p, %p, %s)\n", start, end, s); + ++fgSymbolTrieSearchs; + const uint8_t* p = start; + while ( p != NULL ) { + uintptr_t terminalSize = *p++; + if ( terminalSize > 127 ) { + // except for re-export-with-rename, all terminal sizes fit in one byte + --p; + terminalSize = read_uleb128(p, end); + } + if ( (*s == '\0') && (terminalSize != 0) ) { + //dyld::log("trieWalk(%p) returning %p\n", start, p); + return p; + } + const uint8_t* children = p + terminalSize; + if ( children > end ) { + dyld::log("trieWalk() malformed trie node, terminalSize=0x%lx extends past end of trie\n", terminalSize); + return NULL; + } + //dyld::log("trieWalk(%p) sym=%s, terminalSize=%lu, children=%p\n", start, s, terminalSize, children); + uint8_t childrenRemaining = *children++; + p = children; + uintptr_t nodeOffset = 0; + for (; childrenRemaining > 0; --childrenRemaining) { + const char* ss = s; + //dyld::log("trieWalk(%p) child str=%s\n", start, (char*)p); + bool wrongEdge = false; + // scan whole edge to get to next edge + // if edge is longer than target symbol name, don't read past end of symbol name + char c = *p; + while ( c != '\0' ) { + if ( !wrongEdge ) { + if ( c != *ss ) + wrongEdge = true; + ++ss; + } + ++p; + c = *p; + } + if ( wrongEdge ) { + // advance to next child + ++p; // skip over zero terminator + // skip over uleb128 until last byte is found + while ( (*p & 0x80) != 0 ) + ++p; + ++p; // skip over last byte of uleb128 + if ( p > end ) { + dyld::log("trieWalk() malformed trie node, child node extends past end of trie\n"); + return NULL; + } + } + else { + // the symbol so far matches this edge (child) + // so advance to the child's node + ++p; + nodeOffset = read_uleb128(p, end); + if ( (nodeOffset == 0) || ( &start[nodeOffset] > end) ) { + dyld::log("trieWalk() malformed trie child, nodeOffset=0x%lx out of range\n", nodeOffset); + return NULL; + } + s = ss; + //dyld::log("trieWalk() found matching edge advancing to node 0x%lx\n", nodeOffset); + break; + } + } + if ( nodeOffset != 0 ) + p = &start[nodeOffset]; + else + p = NULL; + } + //dyld::log("trieWalk(%p) return NULL\n", start); + return NULL; +} + + + +uintptr_t ImageLoader::read_uleb128(const uint8_t*& p, const uint8_t* end) +{ + uint64_t result = 0; + int bit = 0; + do { + if (p == end) + dyld::throwf("malformed uleb128"); + + uint64_t slice = *p & 0x7f; + + if (bit > 63) + dyld::throwf("uleb128 too big for uint64, bit=%d, result=0x%0llX", bit, result); + else { + result |= (slice << bit); + bit += 7; + } + } while (*p++ & 0x80); + return result; +} + + +intptr_t ImageLoader::read_sleb128(const uint8_t*& p, const uint8_t* end) +{ + int64_t result = 0; + int bit = 0; + uint8_t byte; + do { + if (p == end) + throw "malformed sleb128"; + byte = *p++; + result |= (((int64_t)(byte & 0x7f)) << bit); + bit += 7; + } while (byte & 0x80); + // sign extend negative numbers + if ( (byte & 0x40) != 0 ) + result |= (-1LL) << bit; + return result; +} + + VECTOR_NEVER_DESTRUCTED_IMPL(ImageLoader::InterposeTuple); VECTOR_NEVER_DESTRUCTED_IMPL(ImagePair); diff --git a/src/ImageLoader.h b/src/ImageLoader.h index 877044c..334b9c7 100644 --- a/src/ImageLoader.h +++ b/src/ImageLoader.h @@ -108,7 +108,9 @@ #define SUPPORT_VERSIONED_PATHS 1 #define SUPPORT_CLASSIC_MACHO __arm__ #define SUPPORT_ZERO_COST_EXCEPTIONS (!__USING_SJLJ_EXCEPTIONS__) - #define INITIAL_IMAGE_COUNT 256 + #define INITIAL_IMAGE_COUNT 150 + #define SUPPORT_ACCELERATE_TABLES (__arm__ || __arm64__) + #define SUPPORT_ROOT_PATH TARGET_IPHONE_SIMULATOR #else #define SPLIT_SEG_SHARED_REGION_SUPPORT 0 #define SPLIT_SEG_DYLIB_SUPPORT __i386__ @@ -121,9 +123,13 @@ #define SUPPORT_CLASSIC_MACHO 1 #define SUPPORT_ZERO_COST_EXCEPTIONS 1 #define INITIAL_IMAGE_COUNT 200 + #define SUPPORT_ACCELERATE_TABLES 0 + #define SUPPORT_ROOT_PATH 1 #endif +#define MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE (32*1024) +#define MH_HAS_OBJC 0x40000000 // optimize away dyld's initializers #define VECTOR_NEVER_DESTRUCTED(type) \ @@ -242,17 +248,28 @@ public: ImageLoader* to; }; + struct InitializerTimingList + { + uintptr_t count; + struct { + const char* shortName; + uint64_t initTime; + } images[1]; + + void addTime(const char* name, uint64_t time); + }; + struct LinkContext { - ImageLoader* (*loadLibrary)(const char* libraryName, bool search, const char* origin, const RPathChain* rpaths); + ImageLoader* (*loadLibrary)(const char* libraryName, bool search, const char* origin, const RPathChain* rpaths, unsigned& cacheIndex); void (*terminationRecorder)(ImageLoader* image); bool (*flatExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image); bool (*coalescedExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image); - unsigned int (*getCoalescedImages)(ImageLoader* images[]); + unsigned int (*getCoalescedImages)(ImageLoader* images[], unsigned imageIndex[]); void (*undefinedHandler)(const char* name); MappedRegion* (*getAllMappedRegions)(MappedRegion*); void * (*bindingHandler)(const char *, const char *, void *); - void (*notifySingle)(dyld_image_states, const ImageLoader* image); - void (*notifyBatch)(dyld_image_states state); + void (*notifySingle)(dyld_image_states, const ImageLoader* image, InitializerTimingList*); + void (*notifyBatch)(dyld_image_states state, bool preflightOnly); void (*removeImage)(ImageLoader* image); void (*registerDOFs)(const std::vector& dofs); void (*clearAllDepths)(); @@ -264,6 +281,11 @@ public: const char* errorTargetDylibPath, const char* errorSymbol); ImageLoader* (*findImageContainingAddress)(const void* addr); void (*addDynamicReference)(ImageLoader* from, ImageLoader* to); +#if SUPPORT_ACCELERATE_TABLES + void (*notifySingleFromCache)(dyld_image_states, const mach_header* mh, const char* path); + dyld_image_state_change_handler (*getPreInitNotifyHandler)(unsigned index); + dyld_image_state_change_handler (*getBoundBatchHandler)(unsigned index); +#endif #if SUPPORT_OLD_CRT_INITIALIZATION void (*setRunInitialzersOldWay)(); @@ -277,23 +299,29 @@ public: ProgramVars programVars; ImageLoader* mainExecutable; const char* imageSuffix; +#if SUPPORT_ROOT_PATH const char** rootPaths; +#endif const dyld_interpose_tuple* dynamicInterposeArray; size_t dynamicInterposeCount; PrebindMode prebindUsage; SharedRegionMode sharedRegionMode; bool dyldLoadedAtSameAddressNeededBySharedCache; - bool codeSigningEnforced; + bool strictMachORequired; + bool requireCodeSignature; bool mainExecutableCodeSigned; bool preFetchDisabled; bool prebinding; bool bindFlat; bool linkingMainExecutable; bool startedInitializingMainExecutable; +#if __MAC_OS_X_VERSION_MIN_REQUIRED bool processIsRestricted; - bool processRequiresLibraryValidation; + bool processUsingLibraryValidation; +#endif bool verboseOpts; bool verboseEnv; + bool verboseLoading; bool verboseMapping; bool verboseRebase; bool verboseBind; @@ -322,21 +350,13 @@ public: uintptr_t address; uintptr_t type; uintptr_t addend; + uintptr_t imageIndex; }; - virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder) = 0; + virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder, unsigned imageIndex) = 0; virtual bool incrementCoalIterator(CoalIterator&) = 0; virtual uintptr_t getAddressCoalIterator(CoalIterator&, const LinkContext& context) = 0; - virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, const LinkContext& context) = 0; - - struct InitializerTimingList - { - uintptr_t count; - struct { - ImageLoader* image; - uint64_t initTime; - } images[1]; - }; + virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, unsigned targetIndex, const LinkContext& context) = 0; struct UninitedUpwards { @@ -350,7 +370,7 @@ public: // link() takes a newly instantiated ImageLoader and does all // fixups needed to make it usable by the process - void link(const LinkContext& context, bool forceLazysBound, bool preflight, bool neverUnload, const RPathChain& loaderRPaths); + void link(const LinkContext& context, bool forceLazysBound, bool preflight, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath); // runInitializers() is normally called in link() but the main executable must // run crt code before initializers @@ -365,6 +385,9 @@ public: // get short name of this image const char* getShortName() const; + // returns leaf name + static const char* shortName(const char* fullName); + // get path used to load this image, not necessarily the "real" path const char* getPath() const { return fPath; } @@ -376,7 +399,7 @@ public: // get path this image is intended to be placed on disk or NULL if no preferred install location virtual const char* getInstallPath() const = 0; - // image was loaded with NSADDIMAGE_OPTION_MATCH_FILENAME_BY_INSTALLNAME and all clients are looking for install path + // image was loaded with NSADDIMAGE_OPTION_MATCH_FILENAME_BY_INSTALLNAME and all clients are looking for install path bool matchInstallPath() const; void setMatchInstallPath(bool); @@ -430,13 +453,23 @@ public: // image has exports that participate in runtime coalescing virtual bool hasCoalescedExports() const = 0; + + // search symbol table of definitions in this image for requested name + virtual bool findExportedSymbolAddress(const LinkContext& context, const char* symbolName, + const ImageLoader* requestorImage, int requestorOrdinalOfDef, + bool runResolver, const ImageLoader** foundIn, uintptr_t* address) const; + + // search symbol table of definitions in this image for requested name + virtual const Symbol* findExportedSymbol(const char* name, bool searchReExports, const char* thisPath, const ImageLoader** foundIn) const = 0; // search symbol table of definitions in this image for requested name - virtual const Symbol* findExportedSymbol(const char* name, bool searchReExports, const ImageLoader** foundIn) const = 0; + virtual const Symbol* findExportedSymbol(const char* name, bool searchReExports, const ImageLoader** foundIn) const { + return findExportedSymbol(name, searchReExports, this->getPath(), foundIn); + } // gets address of implementation (code) of the specified exported symbol virtual uintptr_t getExportedSymbolAddress(const Symbol* sym, const LinkContext& context, - const ImageLoader* requestor=NULL, bool runResolver=false) const = 0; + const ImageLoader* requestor=NULL, bool runResolver=false, const char* symbolName=NULL) const = 0; // gets attributes of the specified exported symbol virtual DefinitionFlags getExportedSymbolInfo(const Symbol* sym) const = 0; @@ -473,6 +506,10 @@ public: // find the closest symbol before addr virtual const char* findClosestSymbol(const void* addr, const void** closestAddr) const = 0; + // for use with accelerator tables + virtual const char* getIndexedPath(unsigned) const { return getPath(); } + virtual const char* getIndexedShortName(unsigned) const { return getShortName(); } + // checks if this image is a bundle and can be loaded but not linked virtual bool isBundle() const = 0; @@ -531,6 +568,11 @@ public: // record interposing for any late binding void addDynamicInterposingTuples(const struct dyld_interpose_tuple array[], size_t count); + virtual const char* libPath(unsigned int) const = 0; + + // Image has objc sections, so information objc about when it comes and goes + virtual bool notifyObjC() const { return false; } + // // A segment is a chunk of an executable file that is mapped into memory. // @@ -565,7 +607,9 @@ public: void applyInterposing(const LinkContext& context); dyld_image_states getState() { return (dyld_image_states)fState; } - + + ino_t getInode() const { return fInode; } + // used to sort images bottom-up int compare(const ImageLoader* right) const; @@ -589,15 +633,21 @@ public: // triggered by DYLD_PRINT_STATISTICS to write info on work done and how fast static void printStatistics(unsigned int imageCount, const InitializerTimingList& timingInfo); - + static void printStatisticsDetails(unsigned int imageCount, const InitializerTimingList& timingInfo); + // used with DYLD_IMAGE_SUFFIX static void addSuffix(const char* path, const char* suffix, char* result); static uint32_t hash(const char*); + static const uint8_t* trieWalk(const uint8_t* start, const uint8_t* end, const char* stringToFind); + // used instead of directly deleting image static void deleteImage(ImageLoader*); - + + static bool haveInterposingTuples() { return !fgInterposingTuples.empty(); } + static void clearInterposingTuples() { fgInterposingTuples.clear(); } + bool dependsOn(ImageLoader* image); void setPath(const char* path); @@ -624,6 +674,9 @@ public: uintptr_t replacee; }; + static uintptr_t read_uleb128(const uint8_t*& p, const uint8_t* end); + static intptr_t read_sleb128(const uint8_t*& p, const uint8_t* end); + protected: // abstract base class so all constructors protected ImageLoader(const char* path, unsigned int libCount); @@ -668,22 +721,20 @@ protected: // To link() an image, its dependent libraries are loaded, it is rebased, bound, and initialized. // These methods do the above, exactly once, and it the right order - void recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths); - void recursiveUnLoadMappedLibraries(const LinkContext& context); - unsigned int recursiveUpdateDepth(unsigned int maxDepth); - void recursiveValidate(const LinkContext& context); - void recursiveRebase(const LinkContext& context); - void recursiveBind(const LinkContext& context, bool forceLazysBound, bool neverUnload); - void recursiveApplyInterposing(const LinkContext& context); - void recursiveGetDOFSections(const LinkContext& context, std::vector& dofs); - void recursiveInitialization(const LinkContext& context, mach_port_t this_thread, + virtual void recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath); + virtual unsigned recursiveUpdateDepth(unsigned int maxDepth); + virtual void recursiveRebase(const LinkContext& context); + virtual void recursiveBind(const LinkContext& context, bool forceLazysBound, bool neverUnload); + virtual void recursiveApplyInterposing(const LinkContext& context); + virtual void recursiveGetDOFSections(const LinkContext& context, std::vector& dofs); + virtual void recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&); // fill in information about dependent libraries (array length is fLibraryCount) virtual void doGetDependentLibraries(DependentLibraryInfo libs[]) = 0; // called on images that are libraries, returns info about itself - virtual LibraryInfo doGetLibraryInfo() = 0; + virtual LibraryInfo doGetLibraryInfo(const LibraryInfo& requestorInfo) = 0; // do any fix ups in this image that depend only on the load address of the image virtual void doRebase(const LinkContext& context) = 0; @@ -711,10 +762,10 @@ protected: // built with PIC code and can load at any address virtual bool segmentsCanSlide() const = 0; - + // set how much all segments slide virtual void setSlide(intptr_t slide) = 0; - + // returns if all dependent libraries checksum's were as expected and none slide bool allDependentLibrariesAsWhenPreBound() const; @@ -723,12 +774,17 @@ protected: // in mach-o a parent library knows name of sub libraries it re-exports.. virtual bool hasSubLibrary(const LinkContext& context, const ImageLoader* child) const = 0; - + + virtual bool weakSymbolsBound(unsigned index) { return fWeakSymbolsBound; } + virtual void setWeakSymbolsBound(unsigned index) { fWeakSymbolsBound = true; } + // set fState to dyld_image_state_memory_mapped void setMapped(const LinkContext& context); void setFileInfo(dev_t device, ino_t inode, time_t modDate); - + + void setDepth(uint16_t depth) { fDepth = depth; } + static uintptr_t interposedAddress(const LinkContext& context, uintptr_t address, const ImageLoader* notInImage, const ImageLoader* onlyInImage=NULL); static uintptr_t fgNextPIEDylibAddress; @@ -743,9 +799,15 @@ protected: static uint32_t fgTotalLazyBindFixups; static uint32_t fgTotalPossibleLazyBindFixups; static uint32_t fgTotalSegmentsMapped; + static uint32_t fgSymbolTrieSearchs; static uint64_t fgTotalBytesMapped; static uint64_t fgTotalBytesPreFetched; static uint64_t fgTotalLoadLibrariesTime; +public: + static uint64_t fgTotalObjCSetupTime; + static uint64_t fgTotalDebuggerPausedTime; + static uint64_t fgTotalRebindCacheTime; +protected: static uint64_t fgTotalRebaseTime; static uint64_t fgTotalBindTime; static uint64_t fgTotalWeakBindTime; @@ -761,7 +823,6 @@ protected: uint32_t fPathHash; uint32_t fDlopenReferenceCount; // count of how many dlopens have been done on this image -private: struct recursive_lock { recursive_lock(mach_port_t t) : thread(t), count(0) {} mach_port_t thread; @@ -770,6 +831,7 @@ private: void recursiveSpinLock(recursive_lock&); void recursiveSpinUnLock(); +private: const ImageLoader::Symbol* findExportedSymbolInDependentImagesExcept(const char* name, const ImageLoader** dsiStart, const ImageLoader**& dsiCur, const ImageLoader** dsiEnd, const ImageLoader** foundIn) const; diff --git a/src/ImageLoaderMachO.cpp b/src/ImageLoaderMachO.cpp index d552ef3..dd9b5af 100644 --- a/src/ImageLoaderMachO.cpp +++ b/src/ImageLoaderMachO.cpp @@ -52,6 +52,7 @@ #include "ImageLoaderMachOClassic.h" #endif #include "mach-o/dyld_images.h" +#include "dyld.h" // use stack guard random value to add padding between dylibs extern "C" long __stack_chk_guard; @@ -69,6 +70,12 @@ extern "C" long __stack_chk_guard; #endif +#if TARGET_IPHONE_SIMULATOR + #define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.dylib" +#else + #define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.B.dylib" +#endif + // relocation_info.r_length field has value 3 for 64-bit executables and value 2 for 32-bit executables #if __LP64__ #define LC_SEGMENT_COMMAND LC_SEGMENT_64 @@ -87,7 +94,6 @@ extern "C" long __stack_chk_guard; #endif uint32_t ImageLoaderMachO::fgSymbolTableBinarySearchs = 0; -uint32_t ImageLoaderMachO::fgSymbolTrieSearchs = 0; ImageLoaderMachO::ImageLoaderMachO(const macho_header* mh, const char* path, unsigned int segCount, @@ -103,7 +109,7 @@ fSegmentsCount(segCount), fIsSplitSeg(false), fInSharedCache(false), fReadOnlyImportSegment(false), #endif fHasSubLibraries(false), fHasSubUmbrella(false), fInUmbrella(false), fHasDOFSections(false), fHasDashInit(false), - fHasInitializers(false), fHasTerminators(false), fRegisteredAsRequiresCoalescing(false) + fHasInitializers(false), fHasTerminators(false), fNotifyObjC(false), fRetainForObjC(false), fRegisteredAsRequiresCoalescing(false) { fIsSplitSeg = ((mh->flags & MH_SPLIT_SEGS) != 0); @@ -140,13 +146,24 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat *encryptCmd = NULL; const uint32_t cmd_count = mh->ncmds; - const struct load_command* const startCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header)); - const struct load_command* const endCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header) + mh->sizeofcmds); + const uint32_t sizeofcmds = mh->sizeofcmds; + if ( sizeofcmds > (MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE-sizeof(macho_header)) ) + dyld::throwf("malformed mach-o: load commands size (%u) > %u", sizeofcmds, MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE); + if ( cmd_count > (sizeofcmds/sizeof(load_command)) ) + dyld::throwf("malformed mach-o: ncmds (%u) too large to fit in sizeofcmds (%u)", cmd_count, sizeofcmds); + const struct load_command* const startCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header)); + const struct load_command* const endCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header) + sizeofcmds); const struct load_command* cmd = startCmds; bool foundLoadCommandSegment = false; + const macho_segment_command* linkeditSegCmd = NULL; + const macho_segment_command* startOfFileSegCmd = NULL; + const dyld_info_command* dyldInfoCmd = NULL; + const symtab_command* symTabCmd = NULL; + const dysymtab_command* dynSymbTabCmd = NULL; for (uint32_t i = 0; i < cmd_count; ++i) { uint32_t cmdLength = cmd->cmdsize; - struct macho_segment_command* segCmd; + const macho_segment_command* segCmd; + const dylib_command* dylibCmd; if ( cmdLength < 8 ) { dyld::throwf("malformed mach-o image: load command #%d length (%u) too small in %s", i, cmdLength, path); @@ -159,6 +176,9 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat switch (cmd->cmd) { case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: + if ( cmd->cmdsize != sizeof(dyld_info_command) ) + throw "malformed mach-o image: LC_DYLD_INFO size wrong"; + dyldInfoCmd = (struct dyld_info_command*)cmd; *compressed = true; break; case LC_SEGMENT_COMMAND: @@ -167,27 +187,74 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat // rdar://problem/19617624 allow unmapped segments on OSX (but not iOS) if ( (segCmd->filesize > segCmd->vmsize) && (segCmd->vmsize != 0) ) #else - if ( segCmd->filesize > segCmd->vmsize ) + // dyld should support non-allocatable __LLVM segment + if ( (segCmd->filesize > segCmd->vmsize) && ((segCmd->vmsize != 0) || ((segCmd->flags & SG_NORELOC) == 0)) ) #endif dyld::throwf("malformed mach-o image: segment load command %s filesize is larger than vmsize", segCmd->segname); + if ( cmd->cmdsize < sizeof(macho_segment_command) ) + throw "malformed mach-o image: LC_SEGMENT size too small"; + if ( cmd->cmdsize != (sizeof(macho_segment_command) + segCmd->nsects * sizeof(macho_section)) ) + throw "malformed mach-o image: LC_SEGMENT size wrong for number of sections"; // ignore zero-sized segments if ( segCmd->vmsize != 0 ) *segCount += 1; - if ( context.codeSigningEnforced ) { + if ( strcmp(segCmd->segname, "__LINKEDIT") == 0 ) { + #if TARGET_IPHONE_SIMULATOR + // Note: should check on all platforms that __LINKEDIT is read-only, but + if ( segCmd->initprot != VM_PROT_READ ) + throw "malformed mach-o image: __LINKEDIT segment does not have read-only permissions"; + #endif + if ( segCmd->fileoff == 0 ) + throw "malformed mach-o image: __LINKEDIT has fileoff==0 which overlaps mach_header"; + if ( linkeditSegCmd != NULL ) + throw "malformed mach-o image: multiple __LINKEDIT segments"; + linkeditSegCmd = segCmd; + } + else { + if ( segCmd->initprot & 0xFFFFFFF8 ) + dyld::throwf("malformed mach-o image: %s segment has invalid permission bits (0x%X) in initprot", segCmd->segname, segCmd->initprot); + if ( segCmd->maxprot & 0xFFFFFFF8 ) + dyld::throwf("malformed mach-o image: %s segment has invalid permission bits (0x%X) in maxprot", segCmd->segname, segCmd->maxprot); + if ( (segCmd->initprot != 0) && ((segCmd->initprot & VM_PROT_READ) == 0) ) + dyld::throwf("malformed mach-o image: %s segment is not mapped readable", segCmd->segname); + } + if ( (segCmd->fileoff == 0) && (segCmd->filesize != 0) ) { + if ( (segCmd->initprot & VM_PROT_READ) == 0 ) + dyld::throwf("malformed mach-o image: %s segment maps start of file but is not readable", segCmd->segname); + if ( (segCmd->initprot & VM_PROT_WRITE) == VM_PROT_WRITE ) { + if ( context.strictMachORequired ) + dyld::throwf("malformed mach-o image: %s segment maps start of file but is writable", segCmd->segname); + } + if ( segCmd->filesize < (sizeof(macho_header) + mh->sizeofcmds) ) + dyld::throwf("malformed mach-o image: %s segment does not map all of load commands", segCmd->segname); + if ( startOfFileSegCmd != NULL ) + dyld::throwf("malformed mach-o image: multiple segments map start of file: %s %s", startOfFileSegCmd->segname, segCmd->segname); + startOfFileSegCmd = segCmd; + } + if ( context.strictMachORequired ) { uintptr_t vmStart = segCmd->vmaddr; uintptr_t vmSize = segCmd->vmsize; uintptr_t vmEnd = vmStart + vmSize; uintptr_t fileStart = segCmd->fileoff; uintptr_t fileSize = segCmd->filesize; - if ( (intptr_t)(vmEnd) < 0) - dyld::throwf("malformed mach-o image: segment load command %s vmsize too large", segCmd->segname); + if ( (intptr_t)(vmSize) < 0 ) + dyld::throwf("malformed mach-o image: segment load command %s vmsize too large in %s", segCmd->segname, path); if ( vmStart > vmEnd ) dyld::throwf("malformed mach-o image: segment load command %s wraps around address space", segCmd->segname); if ( vmSize != fileSize ) { - if ( (segCmd->initprot == 0) && (fileSize != 0) ) - dyld::throwf("malformed mach-o image: unaccessable segment %s has filesize != 0", segCmd->segname); - else if ( vmSize < fileSize ) - dyld::throwf("malformed mach-o image: segment %s has vmsize < filesize", segCmd->segname); + if ( segCmd->initprot == 0 ) { + // allow: fileSize == 0 && initprot == 0 e.g. __PAGEZERO + // allow: vmSize == 0 && initprot == 0 e.g. __LLVM + if ( (fileSize != 0) && (vmSize != 0) ) + dyld::throwf("malformed mach-o image: unaccessable segment %s has non-zero filesize and vmsize", segCmd->segname); + } + else { + // allow: vmSize > fileSize && initprot != X e.g. __DATA + if ( vmSize < fileSize ) + dyld::throwf("malformed mach-o image: segment %s has vmsize < filesize", segCmd->segname); + if ( segCmd->initprot & VM_PROT_EXECUTE ) + dyld::throwf("malformed mach-o image: segment %s has vmsize != filesize and is executable", segCmd->segname); + } } if ( inCache ) { if ( (fileSize != 0) && (segCmd->initprot == (VM_PROT_READ | VM_PROT_EXECUTE)) ) { @@ -223,23 +290,68 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: *libCount += 1; + // fall thru + case LC_ID_DYLIB: + dylibCmd = (dylib_command*)cmd; + if ( dylibCmd->dylib.name.offset > cmdLength ) + dyld::throwf("malformed mach-o image: dylib load command #%d has offset (%u) outside its size (%u)", i, dylibCmd->dylib.name.offset, cmdLength); + if ( (dylibCmd->dylib.name.offset + strlen((char*)dylibCmd + dylibCmd->dylib.name.offset) + 1) > cmdLength ) + dyld::throwf("malformed mach-o image: dylib load command #%d string extends beyond end of load command", i); break; case LC_CODE_SIGNATURE: - *codeSigCmd = (struct linkedit_data_command*)cmd; // only support one LC_CODE_SIGNATURE per image + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + throw "malformed mach-o image: LC_CODE_SIGNATURE size wrong"; + // only support one LC_CODE_SIGNATURE per image + if ( *codeSigCmd != NULL ) + throw "malformed mach-o image: multiple LC_CODE_SIGNATURE load commands"; + *codeSigCmd = (struct linkedit_data_command*)cmd; break; case LC_ENCRYPTION_INFO: + if ( cmd->cmdsize != sizeof(encryption_info_command) ) + throw "malformed mach-o image: LC_ENCRYPTION_INFO size wrong"; + // only support one LC_ENCRYPTION_INFO per image + if ( *encryptCmd != NULL ) + throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO load commands"; + *encryptCmd = (encryption_info_command*)cmd; + break; case LC_ENCRYPTION_INFO_64: - *encryptCmd = (struct encryption_info_command*)cmd; // only support one LC_ENCRYPTION_INFO[_64] per image + if ( cmd->cmdsize != sizeof(encryption_info_command_64) ) + throw "malformed mach-o image: LC_ENCRYPTION_INFO_64 size wrong"; + // only support one LC_ENCRYPTION_INFO_64 per image + if ( *encryptCmd != NULL ) + throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO_64 load commands"; + *encryptCmd = (encryption_info_command*)cmd; break; + case LC_SYMTAB: + if ( cmd->cmdsize != sizeof(symtab_command) ) + throw "malformed mach-o image: LC_SYMTAB size wrong"; + symTabCmd = (symtab_command*)cmd; + break; + case LC_DYSYMTAB: + if ( cmd->cmdsize != sizeof(dysymtab_command) ) + throw "malformed mach-o image: LC_DYSYMTAB size wrong"; + dynSymbTabCmd = (dysymtab_command*)cmd; + break; +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // error when loading iOS Simulator mach-o binary into macOS process + case LC_VERSION_MIN_WATCHOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_IPHONEOS: + throw "mach-o, but built for simulator (not macOS)"; + break; +#endif } cmd = nextCmd; } - if ( context.codeSigningEnforced && !foundLoadCommandSegment ) + if ( context.strictMachORequired && !foundLoadCommandSegment ) throw "load commands not in a segment"; - + if ( linkeditSegCmd == NULL ) + throw "malformed mach-o image: missing __LINKEDIT segment"; + if ( startOfFileSegCmd == NULL ) + throw "malformed mach-o image: missing __TEXT segment that maps start of file"; // verify every segment does not overlap another segment - if ( context.codeSigningEnforced ) { + if ( context.strictMachORequired ) { uintptr_t lastFileStart = 0; uintptr_t linkeditFileStart = 0; const struct load_command* cmd1 = startCmds; @@ -285,6 +397,114 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat dyld::throwf("malformed mach-o image: __LINKEDIT must be last segment"); } + // validate linkedit content + if ( (dyldInfoCmd == NULL) && (symTabCmd == NULL) ) + throw "malformed mach-o image: missing LC_SYMTAB or LC_DYLD_INFO"; + if ( dynSymbTabCmd == NULL ) + throw "malformed mach-o image: missing LC_DYSYMTAB"; + + uint32_t linkeditFileOffsetStart = (uint32_t)linkeditSegCmd->fileoff; + uint32_t linkeditFileOffsetEnd = (uint32_t)linkeditSegCmd->fileoff + (uint32_t)linkeditSegCmd->filesize; + + if ( !inCache && (dyldInfoCmd != NULL) && context.strictMachORequired ) { + // validate all LC_DYLD_INFO chunks fit in LINKEDIT and don't overlap + uint32_t offset = linkeditFileOffsetStart; + if ( dyldInfoCmd->rebase_size != 0 ) { + if ( dyldInfoCmd->rebase_size & 0x80000000 ) + throw "malformed mach-o image: dyld rebase info size overflow"; + if ( dyldInfoCmd->rebase_off < offset ) + throw "malformed mach-o image: dyld rebase info underruns __LINKEDIT"; + offset = dyldInfoCmd->rebase_off + dyldInfoCmd->rebase_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld rebase info overruns __LINKEDIT"; + } + if ( dyldInfoCmd->bind_size != 0 ) { + if ( dyldInfoCmd->bind_size & 0x80000000 ) + throw "malformed mach-o image: dyld bind info size overflow"; + if ( dyldInfoCmd->bind_off < offset ) + throw "malformed mach-o image: dyld bind info overlaps rebase info"; + offset = dyldInfoCmd->bind_off + dyldInfoCmd->bind_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld bind info overruns __LINKEDIT"; + } + if ( dyldInfoCmd->weak_bind_size != 0 ) { + if ( dyldInfoCmd->weak_bind_size & 0x80000000 ) + throw "malformed mach-o image: dyld weak bind info size overflow"; + if ( dyldInfoCmd->weak_bind_off < offset ) + throw "malformed mach-o image: dyld weak bind info overlaps bind info"; + offset = dyldInfoCmd->weak_bind_off + dyldInfoCmd->weak_bind_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld weak bind info overruns __LINKEDIT"; + } + if ( dyldInfoCmd->lazy_bind_size != 0 ) { + if ( dyldInfoCmd->lazy_bind_size & 0x80000000 ) + throw "malformed mach-o image: dyld lazy bind info size overflow"; + if ( dyldInfoCmd->lazy_bind_off < offset ) + throw "malformed mach-o image: dyld lazy bind info overlaps weak bind info"; + offset = dyldInfoCmd->lazy_bind_off + dyldInfoCmd->lazy_bind_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld lazy bind info overruns __LINKEDIT"; + } + if ( dyldInfoCmd->export_size != 0 ) { + if ( dyldInfoCmd->export_size & 0x80000000 ) + throw "malformed mach-o image: dyld export info size overflow"; + if ( dyldInfoCmd->export_off < offset ) + throw "malformed mach-o image: dyld export info overlaps lazy bind info"; + offset = dyldInfoCmd->export_off + dyldInfoCmd->export_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld export info overruns __LINKEDIT"; + } + } + + if ( symTabCmd != NULL ) { + // validate symbol table fits in LINKEDIT + if ( symTabCmd->symoff < linkeditFileOffsetStart ) + throw "malformed mach-o image: symbol table underruns __LINKEDIT"; + if ( symTabCmd->nsyms > 0x10000000 ) + throw "malformed mach-o image: symbol table too large"; + uint32_t symbolsSize = symTabCmd->nsyms * sizeof(macho_nlist); + if ( symbolsSize > linkeditSegCmd->filesize ) + throw "malformed mach-o image: symbol table overruns __LINKEDIT"; + if ( symTabCmd->symoff + symbolsSize < symTabCmd->symoff ) + throw "malformed mach-o image: symbol table size wraps"; + if ( symTabCmd->symoff + symbolsSize > symTabCmd->stroff ) + throw "malformed mach-o image: symbol table overlaps symbol strings"; + if ( symTabCmd->stroff + symTabCmd->strsize < symTabCmd->stroff ) + throw "malformed mach-o image: symbol string size wraps"; + if ( symTabCmd->stroff + symTabCmd->strsize > linkeditFileOffsetEnd ) { + // let old apps overflow as long as it stays within mapped page + if ( context.strictMachORequired || (symTabCmd->stroff + symTabCmd->strsize > ((linkeditFileOffsetEnd + 4095) & (-4096))) ) + throw "malformed mach-o image: symbol strings overrun __LINKEDIT"; + } + // validate indirect symbol table + if ( dynSymbTabCmd->nindirectsyms != 0 ) { + if ( dynSymbTabCmd->indirectsymoff < linkeditFileOffsetStart ) + throw "malformed mach-o image: indirect symbol table underruns __LINKEDIT"; + if ( dynSymbTabCmd->nindirectsyms > 0x10000000 ) + throw "malformed mach-o image: indirect symbol table too large"; + uint32_t indirectTableSize = dynSymbTabCmd->nindirectsyms * sizeof(uint32_t); + if ( indirectTableSize > linkeditSegCmd->filesize ) + throw "malformed mach-o image: indirect symbol table overruns __LINKEDIT"; + if ( dynSymbTabCmd->indirectsymoff + indirectTableSize < dynSymbTabCmd->indirectsymoff ) + throw "malformed mach-o image: indirect symbol table size wraps"; + if ( context.strictMachORequired && (dynSymbTabCmd->indirectsymoff + indirectTableSize > symTabCmd->stroff) ) + throw "malformed mach-o image: indirect symbol table overruns string pool"; + } + if ( (dynSymbTabCmd->nlocalsym > symTabCmd->nsyms) || (dynSymbTabCmd->ilocalsym > symTabCmd->nsyms) ) + throw "malformed mach-o image: indirect symbol table local symbol count exceeds total symbols"; + if ( dynSymbTabCmd->ilocalsym + dynSymbTabCmd->nlocalsym < dynSymbTabCmd->ilocalsym ) + throw "malformed mach-o image: indirect symbol table local symbol count wraps"; + if ( (dynSymbTabCmd->nextdefsym > symTabCmd->nsyms) || (dynSymbTabCmd->iextdefsym > symTabCmd->nsyms) ) + throw "malformed mach-o image: indirect symbol table extern symbol count exceeds total symbols"; + if ( dynSymbTabCmd->iextdefsym + dynSymbTabCmd->nextdefsym < dynSymbTabCmd->iextdefsym ) + throw "malformed mach-o image: indirect symbol table extern symbol count wraps"; + if ( (dynSymbTabCmd->nundefsym > symTabCmd->nsyms) || (dynSymbTabCmd->iundefsym > symTabCmd->nsyms) ) + throw "malformed mach-o image: indirect symbol table undefined symbol count exceeds total symbols"; + if ( dynSymbTabCmd->iundefsym + dynSymbTabCmd->nundefsym < dynSymbTabCmd->iundefsym ) + throw "malformed mach-o image: indirect symbol table undefined symbol count wraps"; + } + + // fSegmentsArrayCount is only 8-bits if ( *segCount > 255 ) dyld::throwf("malformed mach-o image: more than 255 segments in %s", path); @@ -323,32 +543,21 @@ ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, // create image by mapping in a mach-o file -ImageLoader* ImageLoaderMachO::instantiateFromFile(const char* path, int fd, const uint8_t firstPage[4096], uint64_t offsetInFat, +ImageLoader* ImageLoaderMachO::instantiateFromFile(const char* path, int fd, const uint8_t firstPages[], size_t firstPagesSize, uint64_t offsetInFat, uint64_t lenInFat, const struct stat& info, const LinkContext& context) { - // get load commands - const unsigned int dataSize = sizeof(macho_header) + ((macho_header*)firstPage)->sizeofcmds; - uint8_t buffer[dataSize]; - const uint8_t* fileData = firstPage; - if ( dataSize > 4096 ) { - // only read more if cmds take up more space than first page - fileData = buffer; - memcpy(buffer, firstPage, 4096); - pread(fd, &buffer[4096], dataSize-4096, offsetInFat+4096); - } - bool compressed; unsigned int segCount; unsigned int libCount; const linkedit_data_command* codeSigCmd; const encryption_info_command* encryptCmd; - sniffLoadCommands((const macho_header*)fileData, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); + sniffLoadCommands((const macho_header*)firstPages, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); // instantiate concrete class based on content of load commands if ( compressed ) - return ImageLoaderMachOCompressed::instantiateFromFile(path, fd, fileData, dataSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, encryptCmd, context); + return ImageLoaderMachOCompressed::instantiateFromFile(path, fd, firstPages, firstPagesSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, encryptCmd, context); else #if SUPPORT_CLASSIC_MACHO - return ImageLoaderMachOClassic::instantiateFromFile(path, fd, fileData, dataSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context); + return ImageLoaderMachOClassic::instantiateFromFile(path, fd, firstPages, firstPagesSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context); #else throw "missing LC_DYLD_INFO load command"; #endif @@ -418,7 +627,7 @@ void ImageLoaderMachO::parseLoadCmds(const LinkContext& context) for(unsigned int i=0; i < fSegmentsCount; ++i) { // set up pointer to __LINKEDIT segment if ( strcmp(segName(i),"__LINKEDIT") == 0 ) { - if ( context.codeSigningEnforced && (segFileOffset(i) > fCoveredCodeLength)) + if ( context.requireCodeSignature && (segFileOffset(i) > fCoveredCodeLength)) dyld::throwf("cannot load '%s' (segment outside of code signature)", this->getShortName()); fLinkEditBase = (uint8_t*)(segActualLoadAddress(i) - segFileOffset(i)); } @@ -495,6 +704,13 @@ void ImageLoaderMachO::parseLoadCmds(const LinkContext& context) { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; const bool isTextSeg = (strcmp(seg->segname, "__TEXT") == 0); + #if __i386__ && __MAC_OS_X_VERSION_MIN_REQUIRED + const bool isObjCSeg = (strcmp(seg->segname, "__OBJC") == 0); + if ( isObjCSeg ) + fNotifyObjC = true; + #else + const bool isDataSeg = (strncmp(seg->segname, "__DATA", 6) == 0); + #endif const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { @@ -509,6 +725,32 @@ void ImageLoaderMachO::parseLoadCmds(const LinkContext& context) fEHFrameSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData); else if ( isTextSeg && (strcmp(sect->sectname, "__unwind_info") == 0) ) fUnwindInfoSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData); + + #if __i386__ && __MAC_OS_X_VERSION_MIN_REQUIRED + else if ( isObjCSeg ) { + if ( strcmp(sect->sectname, "__image_info") == 0 ) { + const uint32_t* imageInfo = (uint32_t*)(sect->addr + fSlide); + uint32_t flags = imageInfo[1]; + if ( (flags & 4) && (((macho_header*)fMachOData)->filetype != MH_EXECUTE) ) + dyld::throwf("cannot load '%s' because Objective-C garbage collection is not supported", getPath()); + } + else if ( ((macho_header*)fMachOData)->filetype == MH_DYLIB ) { + fRetainForObjC = true; + } + } + #else + else if ( isDataSeg && (strncmp(sect->sectname, "__objc_imageinfo", 16) == 0) ) { + #if __MAC_OS_X_VERSION_MIN_REQUIRED + const uint32_t* imageInfo = (uint32_t*)(sect->addr + fSlide); + uint32_t flags = imageInfo[1]; + if ( (flags & 4) && (((macho_header*)fMachOData)->filetype != MH_EXECUTE) ) + dyld::throwf("cannot load '%s' because Objective-C garbage collection is not supported", getPath()); + #endif + fNotifyObjC = true; + } + else if ( isDataSeg && (strncmp(sect->sectname, "__objc_", 7) == 0) && (((macho_header*)fMachOData)->filetype == MH_DYLIB) ) + fRetainForObjC = true; + #endif } } break; @@ -718,7 +960,7 @@ void ImageLoaderMachO::UnmapSegments() unsigned int textSegmentIndex = 0; for(unsigned int i=0; i < fSegmentsCount; ++i) { //dyld::log("unmap %s at 0x%08lX\n", seg->getName(), seg->getActualLoadAddress(this)); - if ( strcmp(segName(i), "__TEXT") == 0 ) { + if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) { textSegmentIndex = i; } else { @@ -847,33 +1089,13 @@ void ImageLoaderMachO::setSlide(intptr_t slide) void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* codeSigCmd, int fd, uint64_t offsetInFatFile, const LinkContext& context) { // if dylib being loaded has no code signature load command - if ( codeSigCmd == NULL ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - bool codeSigningEnforced = context.codeSigningEnforced; - if ( context.mainExecutableCodeSigned && !codeSigningEnforced ) { - static bool codeSignEnforcementDynamicallyEnabled = false; - if ( !codeSignEnforcementDynamicallyEnabled ) { - uint32_t flags; - if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { - if ( flags & CS_ENFORCEMENT ) { - codeSignEnforcementDynamicallyEnabled = true; - } - } - } - codeSigningEnforced = codeSignEnforcementDynamicallyEnabled; - } - // if we require dylibs to be code signed - if ( codeSigningEnforced ) { - // if there is a non-load command based code signature, use it - off_t offset = (off_t)offsetInFatFile; - if ( fcntl(fd, F_FINDSIGS, &offset, sizeof(offset)) != -1 ) - return; - // otherwise gracefully return from dlopen() + if ( codeSigCmd == NULL) { + if (context.requireCodeSignature ) { + // if we require dylibs to be codesigned there needs to be a signature. dyld::throwf("required code signature missing for '%s'\n", this->getPath()); + } else { + disableCoverageCheck(); } -#endif - //Since we don't have a range for the signature we have to assume full coverage - fCoveredCodeLength = UINT64_MAX; } else { #if __MAC_OS_X_VERSION_MIN_REQUIRED @@ -908,6 +1130,21 @@ void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* cod dyld::log("dyld: Registered code signature for %s\n", this->getPath()); } fCoveredCodeLength = siginfo.fs_file_start; + +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( context.processUsingLibraryValidation ) { + fchecklv checkInfo; + char messageBuffer[512]; + messageBuffer[0] = '\0'; + checkInfo.lv_file_start = offsetInFatFile; + checkInfo.lv_error_message_size = sizeof(messageBuffer); + checkInfo.lv_error_message = messageBuffer; + int res = fcntl(fd, F_CHECK_LV, &checkInfo); + if ( res == -1 ) { + dyld::throwf("code signature in (%s) not valid for use in process using Library Validation: %s", this->getPath(), messageBuffer); + } + } +#endif } } @@ -921,18 +1158,28 @@ void ImageLoaderMachO::validateFirstPages(const struct linkedit_data_command* co } #endif if (codeSigCmd != NULL) { - if ( context.verboseMapping ) - dyld::log("dyld: validate pages: %llu\n", (unsigned long long)offsetInFat); - void *fdata = xmmap(NULL, lenFileData, PROT_READ | PROT_EXEC, MAP_SHARED, fd, offsetInFat); if ( fdata == MAP_FAILED ) { - if ( context.processRequiresLibraryValidation ) +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( context.processUsingLibraryValidation ) { dyld::throwf("cannot load image with wrong team ID in process using Library Validation"); + } else - dyld::throwf("mmap() errno=%d validating first page of '%s'", errno, getInstallPath()); +#endif + { + int errnoCopy = errno; + if ( errnoCopy == EPERM ) { + if ( dyld::sandboxBlockedMmap(getPath()) ) + dyld::throwf("file system sandbox blocked mmap() of '%s'", getPath()); + else + dyld::throwf("code signing blocked mmap() of '%s'", getPath()); + } + else + dyld::throwf("mmap() errno=%d validating first page of '%s'", errnoCopy, getPath()); + } } if ( memcmp(fdata, fileData, lenFileData) != 0 ) - dyld::throwf("mmap() page compare failed for '%s'", getInstallPath()); + dyld::throwf("mmap() page compare failed for '%s'", getPath()); munmap(fdata, lenFileData); } } @@ -963,14 +1210,20 @@ void ImageLoaderMachO::registerInterposing() const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if ( ((sect->flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") == 0) && (strcmp(seg->segname, "__DATA") == 0)) ) { + // Ensure section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("interpose section has malformed address range for %s\n", this->getPath()); const InterposeData* interposeArray = (InterposeData*)(sect->addr + fSlide); const size_t count = sect->size / sizeof(InterposeData); - for (size_t i=0; i < count; ++i) { + for (size_t j=0; j < count; ++j) { ImageLoader::InterposeTuple tuple; - tuple.replacement = interposeArray[i].replacement; + tuple.replacement = interposeArray[j].replacement; tuple.neverImage = this; tuple.onlyImage = NULL; - tuple.replacee = interposeArray[i].replacee; + tuple.replacee = interposeArray[j].replacee; + // ignore interposing on a weak function that does not exist + if ( tuple.replacee == 0 ) + continue; // verify that replacement is in this image if ( this->containsAddress((void*)tuple.replacement) ) { // chain to any existing interpositions @@ -991,10 +1244,10 @@ void ImageLoaderMachO::registerInterposing() } } -uint32_t ImageLoaderMachO::sdkVersion() const +uint32_t ImageLoaderMachO::sdkVersion(const mach_header* mh) { - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const uint32_t cmd_count = mh->ncmds; + const struct load_command* const cmds = (struct load_command*)(((char*)mh) + sizeof(macho_header)); const struct load_command* cmd = cmds; const struct version_min_command* versCmd; for (uint32_t i = 0; i < cmd_count; ++i) { @@ -1011,6 +1264,11 @@ uint32_t ImageLoaderMachO::sdkVersion() const return 0; } +uint32_t ImageLoaderMachO::sdkVersion() const +{ + return ImageLoaderMachO::sdkVersion(machHeader()); +} + uint32_t ImageLoaderMachO::minOSVersion(const mach_header* mh) { const uint32_t cmd_count = mh->ncmds; @@ -1136,7 +1394,7 @@ void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[]) { if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) { DependentLibraryInfo* lib = &libs[0]; - lib->name = "/usr/lib/libSystem.B.dylib"; + lib->name = LIBSYSTEM_DYLIB_PATH; lib->info.checksum = 0; lib->info.minVersion = 0; lib->info.maxVersion = 0; @@ -1174,7 +1432,7 @@ void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[]) } } -ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo() +ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo(const LibraryInfo&) { LibraryInfo info; if ( fDylibIDOffset != 0 ) { @@ -1202,10 +1460,12 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorpath.offset; if ( (strncmp(path, "@loader_path", 12) == 0) && ((path[12] == '/') || (path[12] == '\0')) ) { - if ( context.processIsRestricted && !context.processRequiresLibraryValidation && (context.mainExecutable == this) ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( context.processIsRestricted && (context.mainExecutable == this) ) { dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @loader_path\n", path, this->getPath()); break; } +#endif char resolvedPath[PATH_MAX]; if ( realpath(this->getPath(), resolvedPath) != NULL ) { char newRealPath[strlen(resolvedPath) + strlen(path)]; @@ -1218,10 +1478,12 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorgetPath()); break; } +#endif char resolvedPath[PATH_MAX]; if ( realpath(context.mainExecutable->getPath(), resolvedPath) != NULL ) { char newRealPath[strlen(resolvedPath) + strlen(path)]; @@ -1233,10 +1495,13 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorgetPath()); break; } +#endif +#if SUPPORT_ROOT_PATH else if ( (path[0] == '/') && (context.rootPaths != NULL) ) { // DYLD_ROOT_PATH should apply to LC_RPATH rpaths // DYLD_ROOT_PATH can be a list of paths, but at this point we can only support one, so use first combination that exists @@ -1258,6 +1523,7 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vector Delay calling setNeverUnload() until we know this is not for dlopen_preflight() + if ( fRetainForObjC ) + this->setNeverUnload(); + // if prebound and loaded at prebound address, then no need to rebase if ( this->usablePrebinding(context) ) { // skip rebasing because prebinding is valid @@ -1337,7 +1607,7 @@ void ImageLoaderMachO::doRebase(const LinkContext& context) #endif // do actual rebasing - this->rebase(context); + this->rebase(context, fSlide); #if TEXT_RELOC_SUPPORT // if there were __TEXT fixups, restore write protection @@ -1368,10 +1638,10 @@ void ImageLoaderMachO::makeTextSegmentWritable(const LinkContext& context, bool } #endif -const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name, bool searchReExports, const ImageLoader** foundIn) const +const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name, bool searchReExports, const char* thisPath, const ImageLoader** foundIn) const { // look in this image first - const ImageLoader::Symbol* result = this->findExportedSymbol(name, foundIn); + const ImageLoader::Symbol* result = this->findShallowExportedSymbol(name, foundIn); if ( result != NULL ) return result; @@ -1380,7 +1650,8 @@ const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name if ( libReExported(i) ) { ImageLoader* image = libImage(i); if ( image != NULL ) { - const Symbol* result = image->findExportedSymbol(name, searchReExports, foundIn); + const char* reExPath = libPath(i); + result = image->findExportedSymbol(name, searchReExports, reExPath, foundIn); if ( result != NULL ) return result; } @@ -1395,7 +1666,7 @@ const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name uintptr_t ImageLoaderMachO::getExportedSymbolAddress(const Symbol* sym, const LinkContext& context, - const ImageLoader* requestor, bool runResolver) const + const ImageLoader* requestor, bool runResolver, const char* symbolName) const { return this->getSymbolAddress(sym, requestor, context, runResolver); } @@ -1507,6 +1778,49 @@ void ImageLoaderMachO::getUnwindInfo(dyld_unwind_sections* info) } } +intptr_t ImageLoaderMachO::computeSlide(const mach_header* mh) +{ + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_SEGMENT_COMMAND ) { + const macho_segment_command* seg = (macho_segment_command*)cmd; + if ( (seg->fileoff == 0) && (seg->filesize != 0) ) + return (char*)mh - (char*)(seg->vmaddr); + } + cmd = (const load_command*)(((char*)cmd)+cmd->cmdsize); + } + return 0; +} + +bool ImageLoaderMachO::findSection(const mach_header* mh, const char* segmentName, const char* sectionName, void** sectAddress, size_t* sectSize) +{ + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + { + const macho_segment_command* seg = (macho_segment_command*)cmd; + const macho_section* const sectionsStart = (macho_section*)((char*)seg + sizeof(macho_segment_command)); + const macho_section* const sectionsEnd = §ionsStart[seg->nsects]; + for (const macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { + if ( (strcmp(sect->segname, segmentName) == 0) && (strcmp(sect->sectname, sectionName) == 0) ) { + *sectAddress = (void*)(sect->addr + computeSlide(mh)); + *sectSize = sect->size; + return true; + } + } + } + break; + } + cmd = (const load_command*)(((char*)cmd)+cmd->cmdsize); + } + return false; +} + bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) { @@ -1542,13 +1856,43 @@ bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segme return false; } +const char* ImageLoaderMachO::libPath(unsigned int index) const +{ + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + unsigned count = 0; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch ( cmd->cmd ) { + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + if ( index == count ) { + const struct dylib_command* dylibCmd = (struct dylib_command*)cmd; + return (char*)cmd + dylibCmd->dylib.name.offset; + } + ++count; + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + + // if image linked with nothing and we implicitly added libSystem.dylib, return that + if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) { + return LIBSYSTEM_DYLIB_PATH; + } + + return NULL; +} + void __attribute__((noreturn)) ImageLoaderMachO::throwSymbolNotFound(const LinkContext& context, const char* symbol, const char* referencedFrom, const char* fromVersMismatch, const char* expectedIn) { // record values for possible use by CrashReporter or Finder - (*context.setErrorStrings)(dyld_error_kind_symbol_missing, referencedFrom, expectedIn, symbol); + (*context.setErrorStrings)(DYLD_EXIT_REASON_SYMBOL_MISSING, referencedFrom, expectedIn, symbol); dyld::throwf("Symbol not found: %s\n Referenced from: %s%s\n Expected in: %s\n", symbol, referencedFrom, fromVersMismatch, expectedIn); } @@ -1579,20 +1923,20 @@ const void* ImageLoaderMachO::getEnd() const uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t location, uintptr_t value, - const ImageLoader* targetImage, uint8_t type, const char* symbolName, - intptr_t addend, const char* msg) + uint8_t type, const char* symbolName, + intptr_t addend, const char* inPath, const char* toPath, const char* msg) { // log if ( context.verboseBind ) { if ( addend != 0 ) dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX + %ld\n", - msg, this->getShortName(), (uintptr_t)location, - ((targetImage != NULL) ? targetImage->getShortName() : ""), + msg, shortName(inPath), (uintptr_t)location, + ((toPath != NULL) ? shortName(toPath) : ""), symbolName, (uintptr_t)location, value, addend); else dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX\n", - msg, this->getShortName(), (uintptr_t)location, - ((targetImage != NULL) ? targetImage->getShortName() : "import-missing>"), + msg, shortName(inPath), (uintptr_t)location, + ((toPath != NULL) ? shortName(toPath) : ""), symbolName, (uintptr_t)location, value); } #if LOG_BINDINGS @@ -1741,24 +2085,24 @@ void ImageLoaderMachO::lookupProgramVars(const LinkContext& context) const vars.mh = (macho_header*)fMachOData; // lookup _NXArgc - sym = this->findExportedSymbol("_NXArgc", false, NULL); + sym = this->findShallowExportedSymbol("_NXArgc", NULL); if ( sym != NULL ) - vars.NXArgcPtr = (int*)this->getExportedSymbolAddress(sym, context, this, false); + vars.NXArgcPtr = (int*)this->getExportedSymbolAddress(sym, context, this, false, NULL); // lookup _NXArgv - sym = this->findExportedSymbol("_NXArgv", false, NULL); + sym = this->findShallowExportedSymbol("_NXArgv", NULL); if ( sym != NULL ) - vars.NXArgvPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false); + vars.NXArgvPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false, NULL); // lookup _environ - sym = this->findExportedSymbol("_environ", false, NULL); + sym = this->findShallowExportedSymbol("_environ", NULL); if ( sym != NULL ) - vars.environPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false); + vars.environPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false, NULL); // lookup __progname - sym = this->findExportedSymbol("___progname", false, NULL); + sym = this->findShallowExportedSymbol("___progname", NULL); if ( sym != NULL ) - vars.__prognamePtr = (const char**)this->getExportedSymbolAddress(sym, context, this, false); + vars.__prognamePtr = (const char**)this->getExportedSymbolAddress(sym, context, this, false, NULL); context.setNewProgramVars(vars); } @@ -1802,6 +2146,10 @@ void ImageLoaderMachO::doImageInit(const LinkContext& context) if ( ! this->containsAddress((void*)func) ) { dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath()); } + if ( ! dyld::gProcessInfo->libSystemInitialized ) { + // libSystem initializer must run first + dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath()); + } if ( context.verboseInit ) dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath()); func(context.argc, context.argv, context.envp, context.apple, &context.programVars); @@ -1828,15 +2176,30 @@ void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) if ( type == S_MOD_INIT_FUNC_POINTERS ) { Initializer* inits = (Initializer*)(sect->addr + fSlide); const size_t count = sect->size / sizeof(uintptr_t); - for (size_t i=0; i < count; ++i) { - Initializer func = inits[i]; + // Ensure __mod_init_func section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("__mod_init_funcs section has malformed address range for %s\n", this->getPath()); + for (size_t j=0; j < count; ++j) { + Initializer func = inits[j]; // verify initializers are in image if ( ! this->containsAddress((void*)func) ) { dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath()); } + if ( ! dyld::gProcessInfo->libSystemInitialized ) { + // libSystem initializer must run first + const char* installPath = getInstallPath(); + if ( (installPath == NULL) || (strcmp(installPath, LIBSYSTEM_DYLIB_PATH) != 0) ) + dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath()); + } if ( context.verboseInit ) dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath()); + bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL); func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL); + if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) { + // now safe to use malloc() and other calls in libSystem.dylib + dyld::gProcessInfo->libSystemInitialized = true; + } } } } @@ -1848,9 +2211,6 @@ void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) - - - void ImageLoaderMachO::doGetDOFSections(const LinkContext& context, std::vector& dofs) { if ( fHasDOFSections ) { @@ -1867,6 +2227,9 @@ void ImageLoaderMachO::doGetDOFSections(const LinkContext& context, std::vector< const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if ( (sect->flags & SECTION_TYPE) == S_DTRACE_DOF ) { + // Ensure section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("DOF section has malformed address range for %s\n", this->getPath()); ImageLoader::DOFInfo info; info.dof = (void*)(sect->addr + fSlide); info.imageHeader = this->machHeader(); @@ -1922,10 +2285,13 @@ void ImageLoaderMachO::doTermination(const LinkContext& context) for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { const uint8_t type = sect->flags & SECTION_TYPE; if ( type == S_MOD_TERM_FUNC_POINTERS ) { + // Ensure section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("DOF section has malformed address range for %s\n", this->getPath()); Terminator* terms = (Terminator*)(sect->addr + fSlide); const size_t count = sect->size / sizeof(uintptr_t); - for (size_t i=count; i > 0; --i) { - Terminator func = terms[i-1]; + for (size_t j=count; j > 0; --j) { + Terminator func = terms[j-1]; // verify terminators are in image if ( ! this->containsAddress((void*)func) ) { dyld::throwf("termination function %p not in mapped image for %s\n", func, this->getPath()); @@ -1943,9 +2309,9 @@ void ImageLoaderMachO::doTermination(const LinkContext& context) } -void ImageLoaderMachO::printStatistics(unsigned int imageCount, const InitializerTimingList& timingInfo) +void ImageLoaderMachO::printStatisticsDetails(unsigned int imageCount, const InitializerTimingList& timingInfo) { - ImageLoader::printStatistics(imageCount, timingInfo); + ImageLoader::printStatisticsDetails(imageCount, timingInfo); dyld::log("total symbol trie searches: %d\n", fgSymbolTrieSearchs); dyld::log("total symbol table binary searches: %d\n", fgSymbolTableBinarySearchs); dyld::log("total images defining weak symbols: %u\n", fgImagesHasWeakDefinitions); @@ -1988,7 +2354,7 @@ intptr_t ImageLoaderMachO::assignSegmentAddresses(const LinkContext& context) } else if ( ! this->segmentsCanSlide() ) { for(unsigned int i=0, e=segmentCount(); i < e; ++i) { - if ( strcmp(segName(i), "__PAGEZERO") == 0 ) + if ( (strcmp(segName(i), "__PAGEZERO") == 0) && (segFileSize(i) == 0) && (segPreferredLoadAddress(i) == 0) ) continue; if ( !reserveAddressRange(segPreferredLoadAddress(i), segSize(i)) ) dyld::throwf("can't map unslidable segment %s to 0x%lX with size 0x%lX", segName(i), segPreferredLoadAddress(i), segSize(i)); @@ -2059,8 +2425,12 @@ void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInF protection |= PROT_EXEC; if ( segReadable(i) ) protection |= PROT_READ; - if ( segWriteable(i) ) + if ( segWriteable(i) ) { protection |= PROT_WRITE; + // rdar://problem/22525618 force __LINKEDIT to always be mapped read-only + if ( strcmp(segName(i), "__LINKEDIT") == 0 ) + protection = PROT_READ; + } } #if __i386__ // initially map __IMPORT segments R/W so dyld can update them @@ -2075,8 +2445,16 @@ void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInF } void* loadAddress = xmmap((void*)requestedLoadAddress, size, protection, MAP_FIXED | MAP_PRIVATE, fd, fileOffset); if ( loadAddress == ((void*)(-1)) ) { - dyld::throwf("mmap() error %d at address=0x%08lX, size=0x%08lX segment=%s in Segment::map() mapping %s", - errno, requestedLoadAddress, (uintptr_t)size, segName(i), getPath()); + int mmapErr = errno; + if ( mmapErr == EPERM ) { + if ( dyld::sandboxBlockedMmap(getPath()) ) + dyld::throwf("file system sandbox blocked mmap() of '%s'", this->getPath()); + else + dyld::throwf("code signing blocked mmap() of '%s'", this->getPath()); + } + else + dyld::throwf("mmap() errno=%d at address=0x%08lX, size=0x%08lX segment=%s in Segment::map() mapping %s", + mmapErr, requestedLoadAddress, (uintptr_t)size, segName(i), getPath()); } } // update stats @@ -2162,3 +2540,190 @@ void ImageLoaderMachO::segMakeWritable(unsigned int segIndex, const ImageLoader: } +const char* ImageLoaderMachO::findClosestSymbol(const mach_header* mh, const void* addr, const void** closestAddr) +{ + // called by dladdr() + // only works with compressed LINKEDIT if classic symbol table is also present + const dysymtab_command* dynSymbolTable = NULL; + const symtab_command* symtab = NULL; + const macho_segment_command* seg; + const uint8_t* unslidLinkEditBase = NULL; + bool linkEditBaseFound = false; + intptr_t slide = 0; + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + seg = (macho_segment_command*)cmd; + if ( strcmp(seg->segname, "__LINKEDIT") == 0 ) { + unslidLinkEditBase = (uint8_t*)(seg->vmaddr - seg->fileoff); + linkEditBaseFound = true; + } + else if ( (seg->fileoff == 0) && (seg->filesize != 0) ) + slide = (uintptr_t)mh - seg->vmaddr; + break; + case LC_SYMTAB: + symtab = (symtab_command*)cmd; + break; + case LC_DYSYMTAB: + dynSymbolTable = (dysymtab_command*)cmd; + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + // no symbol table => no lookup by address + if ( (symtab == NULL) || (dynSymbolTable == NULL) || !linkEditBaseFound ) + return NULL; + + const uint8_t* linkEditBase = unslidLinkEditBase + slide; + const char* symbolTableStrings = (const char*)&linkEditBase[symtab->stroff]; + const macho_nlist* symbolTable = (macho_nlist*)(&linkEditBase[symtab->symoff]); + + uintptr_t targetAddress = (uintptr_t)addr - slide; + const struct macho_nlist* bestSymbol = NULL; + // first walk all global symbols + const struct macho_nlist* const globalsStart = &symbolTable[dynSymbolTable->iextdefsym]; + const struct macho_nlist* const globalsEnd= &globalsStart[dynSymbolTable->nextdefsym]; + for (const struct macho_nlist* s = globalsStart; s < globalsEnd; ++s) { + if ( (s->n_type & N_TYPE) == N_SECT ) { + if ( bestSymbol == NULL ) { + if ( s->n_value <= targetAddress ) + bestSymbol = s; + } + else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) { + bestSymbol = s; + } + } + } + // next walk all local symbols + const struct macho_nlist* const localsStart = &symbolTable[dynSymbolTable->ilocalsym]; + const struct macho_nlist* const localsEnd= &localsStart[dynSymbolTable->nlocalsym]; + for (const struct macho_nlist* s = localsStart; s < localsEnd; ++s) { + if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) { + if ( bestSymbol == NULL ) { + if ( s->n_value <= targetAddress ) + bestSymbol = s; + } + else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) { + bestSymbol = s; + } + } + } + if ( bestSymbol != NULL ) { +#if __arm__ + if (bestSymbol->n_desc & N_ARM_THUMB_DEF) + *closestAddr = (void*)((bestSymbol->n_value | 1) + slide); + else + *closestAddr = (void*)(bestSymbol->n_value + slide); +#else + *closestAddr = (void*)(bestSymbol->n_value + slide); +#endif + return &symbolTableStrings[bestSymbol->n_un.n_strx]; + } + return NULL; +} + +bool ImageLoaderMachO::getLazyBindingInfo(uint32_t& lazyBindingInfoOffset, const uint8_t* lazyInfoStart, const uint8_t* lazyInfoEnd, + uint8_t* segIndex, uintptr_t* segOffset, int* ordinal, const char** symbolName, bool* doneAfterBind) +{ + if ( lazyBindingInfoOffset > (lazyInfoEnd-lazyInfoStart) ) + return false; + uint8_t type = BIND_TYPE_POINTER; + uint8_t symboFlags = 0; + bool done = false; + const uint8_t* p = &lazyInfoStart[lazyBindingInfoOffset]; + while ( !done && (p < lazyInfoEnd) ) { + uint8_t immediate = *p & BIND_IMMEDIATE_MASK; + uint8_t opcode = *p & BIND_OPCODE_MASK; + ++p; + switch (opcode) { + case BIND_OPCODE_DONE: + *doneAfterBind = false; + return true; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: + *ordinal = immediate; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + *ordinal = (int)read_uleb128(p, lazyInfoEnd); + break; + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + // the special ordinals are negative numbers + if ( immediate == 0 ) + *ordinal = 0; + else { + int8_t signExtended = BIND_OPCODE_MASK | immediate; + *ordinal = signExtended; + } + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + *symbolName = (char*)p; + symboFlags = immediate; + while (*p != '\0') + ++p; + ++p; + break; + case BIND_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + *segIndex = immediate; + *segOffset = read_uleb128(p, lazyInfoEnd); + break; + case BIND_OPCODE_DO_BIND: + *doneAfterBind = ((*p & BIND_OPCODE_MASK) == BIND_OPCODE_DONE); + lazyBindingInfoOffset += p - &lazyInfoStart[lazyBindingInfoOffset]; + return true; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + case BIND_OPCODE_ADD_ADDR_ULEB: + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + default: + return false; + } + } + return false; +} + +const dyld_info_command* ImageLoaderMachO::findDyldInfoLoadCommand(const mach_header* mh) +{ + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + return (dyld_info_command*)cmd; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + return NULL; +} + + +uintptr_t ImageLoaderMachO::segPreferredAddress(const mach_header* mh, unsigned segIndex) +{ + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + unsigned curSegIndex = 0; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_SEGMENT_COMMAND ) { + if ( segIndex == curSegIndex ) { + const macho_segment_command* segCmd = (macho_segment_command*)cmd; + return segCmd->vmaddr; + } + ++curSegIndex; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + return 0; +} + + + diff --git a/src/ImageLoaderMachO.h b/src/ImageLoaderMachO.h index 72c90f1..5c48589 100644 --- a/src/ImageLoaderMachO.h +++ b/src/ImageLoaderMachO.h @@ -41,7 +41,7 @@ class ImageLoaderMachO : public ImageLoader { public: static ImageLoader* instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context); - static ImageLoader* instantiateFromFile(const char* path, int fd, const uint8_t firstPages[4096], uint64_t offsetInFat, + static ImageLoader* instantiateFromFile(const char* path, int fd, const uint8_t firstPages[], size_t firstPagesSize, uint64_t offsetInFat, uint64_t lenInFat, const struct stat& info, const LinkContext& context); static ImageLoader* instantiateFromCache(const macho_header* mh, const char* path, long slide, const struct stat& info, const LinkContext& context); static ImageLoader* instantiateFromMemory(const char* moduleName, const macho_header* mh, uint64_t len, const LinkContext& context); @@ -57,9 +57,9 @@ public: virtual uintptr_t getSlide() const; virtual const void* getEnd() const; virtual bool hasCoalescedExports() const; - virtual const Symbol* findExportedSymbol(const char* name, bool searchReExports, const ImageLoader** foundIn) const; + virtual const Symbol* findExportedSymbol(const char* name, bool searchReExports, const char* thisPath, const ImageLoader** foundIn) const; virtual uintptr_t getExportedSymbolAddress(const Symbol* sym, const LinkContext& context, - const ImageLoader* requestor, bool runResolver) const; + const ImageLoader* requestor, bool runResolver, const char*) const; virtual DefinitionFlags getExportedSymbolInfo(const Symbol* sym) const; virtual const char* getExportedSymbolName(const Symbol* sym) const; virtual uint32_t getExportedSymbolCount() const; @@ -75,10 +75,10 @@ public: virtual bool forceFlat() const; virtual bool participatesInCoalescing() const; virtual const char* findClosestSymbol(const void* addr, const void** closestAddr) const = 0; - virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder) = 0; + virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder, unsigned) = 0; virtual bool incrementCoalIterator(CoalIterator&) = 0; virtual uintptr_t getAddressCoalIterator(CoalIterator&, const LinkContext& contex) = 0; - virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, const LinkContext& context) = 0; + virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, unsigned targetIndex, const LinkContext& context) = 0; virtual uintptr_t doBindLazySymbol(uintptr_t* lazyPointer, const LinkContext& context) = 0; virtual uintptr_t doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context, void (*lock)(), void (*unlock)()) = 0; virtual void doTermination(const LinkContext& context); @@ -104,11 +104,26 @@ public: virtual void registerInterposing(); virtual uint32_t sdkVersion() const; virtual uint32_t minOSVersion() const; - - - static void printStatistics(unsigned int imageCount, const InitializerTimingList&); - static uint32_t minOSVersion(const mach_header*); - + virtual const char* libPath(unsigned int) const; + virtual bool notifyObjC() const { return fNotifyObjC; } + + static void printStatisticsDetails(unsigned int imageCount, const InitializerTimingList&); + static uint32_t minOSVersion(const mach_header*); + static uint32_t sdkVersion(const mach_header* mh); + static intptr_t computeSlide(const mach_header* mh); + static bool findSection(const mach_header* mh, const char* segmentName, const char* sectionName, void** sectAddress, uintptr_t* sectSize); + static const dyld_info_command* findDyldInfoLoadCommand(const mach_header* mh); + static const char* findClosestSymbol(const mach_header* mh, const void* addr, const void** closestAddr); + static bool getLazyBindingInfo(uint32_t& lazyBindingInfoOffset, const uint8_t* lazyInfoStart, const uint8_t* lazyInfoEnd, + uint8_t* segIndex, uintptr_t* segOffset, int* ordinal, const char** symbolName, bool* doneAfterBind); + static uintptr_t segPreferredAddress(const mach_header* mh, unsigned segIndex); + static uintptr_t bindLocation(const LinkContext& context, uintptr_t location, uintptr_t value, + uint8_t type, const char* symbolName, + intptr_t addend, const char* inPath, const char* toPath, const char* msg); + virtual void rebase(const LinkContext& context, uintptr_t slide) = 0; + + + protected: ImageLoaderMachO(const ImageLoaderMachO&); ImageLoaderMachO(const macho_header* mh, const char* path, unsigned int segCount, @@ -122,8 +137,7 @@ protected: virtual bool isSubframeworkOf(const LinkContext& context, const ImageLoader* image) const = 0; virtual bool hasSubLibrary(const LinkContext& context, const ImageLoader* child) const = 0; virtual uint32_t* segmentCommandOffsets() const = 0; - virtual void rebase(const LinkContext& context) = 0; - virtual const ImageLoader::Symbol* findExportedSymbol(const char* name, const ImageLoader** foundIn) const = 0; + virtual const ImageLoader::Symbol* findShallowExportedSymbol(const char* name, const ImageLoader** foundIn) const = 0; virtual bool containsSymbol(const void* addr) const = 0; virtual uintptr_t exportedSymbolAddress(const LinkContext& context, const Symbol* symbol, const ImageLoader* requestor, bool runResolver) const = 0; virtual bool exportedSymbolIsWeakDefintion(const Symbol* symbol) const = 0; @@ -136,10 +150,10 @@ protected: #if PREBOUND_IMAGE_SUPPORT virtual void resetPreboundLazyPointers(const LinkContext& context) = 0; #endif - + virtual void doGetDependentLibraries(DependentLibraryInfo libs[]); - virtual LibraryInfo doGetLibraryInfo(); + virtual LibraryInfo doGetLibraryInfo(const LibraryInfo& requestorInfo); virtual void getRPaths(const LinkContext& context, std::vector&) const; virtual bool getUUID(uuid_t) const; virtual void doRebase(const LinkContext& context); @@ -154,7 +168,6 @@ protected: virtual bool usesTwoLevelNameSpace() const; virtual bool isPrebindable() const; - protected: void destroy(); @@ -188,10 +201,7 @@ protected: void doModInitFunctions(const LinkContext& context); void setupLazyPointerHandler(const LinkContext& context); void lookupProgramVars(const LinkContext& context) const; - uintptr_t bindLocation(const LinkContext& context, uintptr_t location, uintptr_t value, - const ImageLoader* targetImage, uint8_t type, const char* symbolName, - intptr_t addend, const char* msg); - + void makeTextSegmentWritable(const LinkContext& context, bool writeable); void preFetchDATA(int fd, uint64_t offsetInFat, const LinkContext& context); @@ -227,11 +237,12 @@ protected: fHasDashInit : 1, fHasInitializers : 1, fHasTerminators : 1, + fNotifyObjC : 1, + fRetainForObjC : 1, fRegisteredAsRequiresCoalescing : 1; // Loading MH_DYLIB_STUB causing coalescable miscount - + static uint32_t fgSymbolTableBinarySearchs; - static uint32_t fgSymbolTrieSearchs; }; diff --git a/src/ImageLoaderMachOClassic.cpp b/src/ImageLoaderMachOClassic.cpp index 4a0f5b3..fa71b09 100644 --- a/src/ImageLoaderMachOClassic.cpp +++ b/src/ImageLoaderMachOClassic.cpp @@ -442,8 +442,8 @@ ImageLoaderMachOClassic::mapSplitSegDylibOutsideSharedRegion(int fd, if ( 0 != r ) { // no room here, deallocate what has succeeded so far for(unsigned int j=0; j < i; ++j) { - vm_address_t addr = nextAltLoadAddress + regions[j].sfm_address - regions[0].sfm_address; - vm_size_t size = regions[j].sfm_size ; + addr = nextAltLoadAddress + regions[j].sfm_address - regions[0].sfm_address; + size = regions[j].sfm_size ; (void)vm_deallocate(mach_task_self(), addr, size); } nextAltLoadAddress += 0x00100000; // skip ahead 1MB and try again @@ -757,10 +757,9 @@ void ImageLoaderMachOClassic::resetPreboundLazyPointers(const LinkContext& conte -void ImageLoaderMachOClassic::rebase(const LinkContext& context) +void ImageLoaderMachOClassic::rebase(const LinkContext& context, uintptr_t slide) { CRSetCrashLogMessage2(this->getPath()); - register const uintptr_t slide = this->fSlide; const uintptr_t relocBase = this->getRelocBase(); // prefetch any LINKEDIT pages needed @@ -933,7 +932,7 @@ const struct macho_nlist* ImageLoaderMachOClassic::binarySearch(const char* key, } -const ImageLoader::Symbol* ImageLoaderMachOClassic::findExportedSymbol(const char* name, const ImageLoader** foundIn) const +const ImageLoader::Symbol* ImageLoaderMachOClassic::findShallowExportedSymbol(const char* name, const ImageLoader** foundIn) const { const struct macho_nlist* sym = NULL; if ( fDynamicInfo->tocoff == 0 ) @@ -1043,7 +1042,7 @@ uintptr_t ImageLoaderMachOClassic::getSymbolAddress(const macho_nlist* sym, cons } uintptr_t ImageLoaderMachOClassic::resolveUndefined(const LinkContext& context, const struct macho_nlist* undefinedSymbol, - bool twoLevel, bool dontCoalesce, const ImageLoader** foundIn) + bool twoLevel, bool dontCoalesce, bool runResolver, const ImageLoader** foundIn) { ++fgTotalBindSymbolsResolved; const char* symbolName = &fStrings[undefinedSymbol->n_un.n_strx]; @@ -1069,7 +1068,7 @@ uintptr_t ImageLoaderMachOClassic::resolveUndefined(const LinkContext& context, // if a bundle is loaded privately the above will not find its exports if ( this->isBundle() && this->hasHiddenExports() ) { // look in self for needed symbol - sym = this->findExportedSymbol(symbolName, foundIn); + sym = this->findShallowExportedSymbol(symbolName, foundIn); if ( sym != NULL ) return (*foundIn)->getExportedSymbolAddress(sym, context, this); } @@ -1141,12 +1140,12 @@ uintptr_t ImageLoaderMachOClassic::resolveUndefined(const LinkContext& context, //dyld::log("resolveUndefined(%s) in %s\n", symbolName, this->getPath()); throw "symbol not found"; } - - const Symbol* sym = target->findExportedSymbol(symbolName, true, foundIn); - if ( sym!= NULL ) { - return (*foundIn)->getExportedSymbolAddress(sym, context, this); - } - else if ( (undefinedSymbol->n_type & N_PEXT) != 0 ) { + + uintptr_t address; + if ( target->findExportedSymbolAddress(context, symbolName, this, ord, runResolver, foundIn, &address) ) + return address; + + if ( (undefinedSymbol->n_type & N_PEXT) != 0 ) { // don't know why the static linker did not eliminate the internal reference to a private extern definition *foundIn = this; return this->getSymbolAddress(undefinedSymbol, context, false); @@ -1269,7 +1268,7 @@ void ImageLoaderMachOClassic::doBindExternalRelocations(const LinkContext& conte // range of global symbols. To handle that case we do the coalesing now. dontCoalesce = false; } - symbolAddr = this->resolveUndefined(context, undefinedSymbol, twoLevel, dontCoalesce, &image); + symbolAddr = this->resolveUndefined(context, undefinedSymbol, twoLevel, dontCoalesce, false, &image); lastUndefinedSymbol = undefinedSymbol; symbolAddrCached = false; } @@ -1435,7 +1434,7 @@ uintptr_t ImageLoaderMachOClassic::doBindLazySymbol(uintptr_t* lazyPointer, cons if ( symbolIndex != INDIRECT_SYMBOL_ABS && symbolIndex != INDIRECT_SYMBOL_LOCAL ) { const char* symbolName = &fStrings[fSymbolTable[symbolIndex].n_un.n_strx]; const ImageLoader* image = NULL; - uintptr_t symbolAddr = this->resolveUndefined(context, &fSymbolTable[symbolIndex], twoLevel, false, &image); + uintptr_t symbolAddr = this->resolveUndefined(context, &fSymbolTable[symbolIndex], twoLevel, false, true, &image); symbolAddr = this->bindIndirectSymbol(lazyPointer, sect, symbolName, symbolAddr, image, context); ++fgTotalLazyBindFixups; return symbolAddr; @@ -1451,7 +1450,7 @@ uintptr_t ImageLoaderMachOClassic::doBindLazySymbol(uintptr_t* lazyPointer, cons -void ImageLoaderMachOClassic::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder) +void ImageLoaderMachOClassic::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder, unsigned) { it.image = this; it.symbolName = " "; @@ -1542,7 +1541,7 @@ uintptr_t ImageLoaderMachOClassic::getAddressCoalIterator(CoalIterator& it, cons } -void ImageLoaderMachOClassic::updateUsesCoalIterator(CoalIterator& it, uintptr_t value, ImageLoader* targetImage, const LinkContext& context) +void ImageLoaderMachOClassic::updateUsesCoalIterator(CoalIterator& it, uintptr_t value, ImageLoader* targetImage, unsigned targetIndex, const LinkContext& context) { // flat_namespace images with classic LINKEDIT do not need late coalescing. // They still need to be iterated becuase they may implement @@ -1644,7 +1643,7 @@ void ImageLoaderMachOClassic::updateUsesCoalIterator(CoalIterator& it, uintptr_t if ( reloc->r_pcrel ) type = BIND_TYPE_TEXT_PCREL32; #endif - this->bindLocation(context, (uintptr_t)location, value, targetImage, type, symbolName, addend, "weak "); + this->bindLocation(context, (uintptr_t)location, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); boundSomething = true; } } @@ -1798,7 +1797,7 @@ void ImageLoaderMachOClassic::bindIndirectSymbolPointers(const LinkContext& cont // range of global symbols. To handle that case we do the coalesing now. dontCoalesce = false; } - uintptr_t symbolAddr = resolveUndefined(context, sym, twoLevel, dontCoalesce, &image); + uintptr_t symbolAddr = resolveUndefined(context, sym, twoLevel, dontCoalesce, false, &image); // update pointer symbolAddr = this->bindIndirectSymbol((uintptr_t*)ptrToBind, sect, &fStrings[sym->n_un.n_strx], symbolAddr, image, context); // update stats @@ -1853,7 +1852,7 @@ void ImageLoaderMachOClassic::initializeLazyStubs(const LinkContext& context) const char* symbolName = &fStrings[fSymbolTable[symbolIndex].n_un.n_strx]; const ImageLoader* image = NULL; try { - uintptr_t symbolAddr = this->resolveUndefined(context, &fSymbolTable[symbolIndex], this->usesTwoLevelNameSpace(), false, &image); + uintptr_t symbolAddr = this->resolveUndefined(context, &fSymbolTable[symbolIndex], this->usesTwoLevelNameSpace(), false, false, &image); symbolAddr = this->bindIndirectSymbol((uintptr_t*)entry, sect, symbolName, symbolAddr, image, context); ++fgTotalBindFixups; uint32_t rel32 = symbolAddr - (((uint32_t)entry)+5); @@ -2034,14 +2033,14 @@ void ImageLoaderMachOClassic::dynamicInterpose(const LinkContext& context) const size_t pointerCount = sect->size / sizeof(uintptr_t); uintptr_t* const symbolPointers = (uintptr_t*)(sect->addr + fSlide); for (size_t pointerIndex=0; pointerIndex < pointerCount; ++pointerIndex) { - for(size_t i=0; i < context.dynamicInterposeCount; ++i) { + for(size_t j=0; j < context.dynamicInterposeCount; ++j) { // replace all references to 'replacee' with 'replacement' - if ( symbolPointers[pointerIndex] == (uintptr_t)context.dynamicInterposeArray[i].replacee ) { + if ( symbolPointers[pointerIndex] == (uintptr_t)context.dynamicInterposeArray[j].replacee ) { if ( context.verboseInterposing ) { dyld::log("dyld: dynamic interposing: at %p replace %p with %p in %s\n", - &symbolPointers[pointerIndex], context.dynamicInterposeArray[i].replacee, context.dynamicInterposeArray[i].replacement, this->getPath()); + &symbolPointers[pointerIndex], context.dynamicInterposeArray[j].replacee, context.dynamicInterposeArray[j].replacement, this->getPath()); } - symbolPointers[pointerIndex] = (uintptr_t)context.dynamicInterposeArray[i].replacement; + symbolPointers[pointerIndex] = (uintptr_t)context.dynamicInterposeArray[j].replacement; } } } diff --git a/src/ImageLoaderMachOClassic.h b/src/ImageLoaderMachOClassic.h index 071bea3..1f63ae0 100644 --- a/src/ImageLoaderMachOClassic.h +++ b/src/ImageLoaderMachOClassic.h @@ -59,10 +59,10 @@ public: virtual uintptr_t doBindLazySymbol(uintptr_t* lazyPointer, const LinkContext& context); virtual uintptr_t doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context, void (*lock)(), void (*unlock)()); virtual const char* findClosestSymbol(const void* addr, const void** closestAddr) const; - virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder); + virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder, unsigned); virtual bool incrementCoalIterator(CoalIterator&); virtual uintptr_t getAddressCoalIterator(CoalIterator&, const LinkContext& contex); - virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, const LinkContext& context); + virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, unsigned targetIndex, const LinkContext& context); protected: virtual void doInterpose(const LinkContext& context); @@ -72,8 +72,8 @@ protected: virtual bool isSubframeworkOf(const LinkContext& context, const ImageLoader* image) const; virtual bool hasSubLibrary(const LinkContext& context, const ImageLoader* child) const; virtual uint32_t* segmentCommandOffsets() const; - virtual void rebase(const LinkContext& context); - virtual const ImageLoader::Symbol* findExportedSymbol(const char* name, const ImageLoader** foundIn) const; + virtual void rebase(const LinkContext& context, uintptr_t slide); + virtual const ImageLoader::Symbol* findShallowExportedSymbol(const char* name, const ImageLoader** foundIn) const; virtual bool containsSymbol(const void* addr) const; virtual uintptr_t exportedSymbolAddress(const LinkContext& context, const Symbol* symbol, const ImageLoader* requestor, bool runResolver) const; virtual bool exportedSymbolIsWeakDefintion(const Symbol* symbol) const; @@ -102,7 +102,7 @@ private: static bool symbolIsWeakReference(const struct macho_nlist* symbol); static bool symbolIsWeakDefinition(const struct macho_nlist* symbol); uintptr_t resolveUndefined(const LinkContext& context, const struct macho_nlist* symbol, bool twoLevel, - bool dontCoalesce, const ImageLoader **foundIn); + bool dontCoalesce, bool runResolver, const ImageLoader **foundIn); uintptr_t getSymbolAddress(const macho_nlist*, const LinkContext& context, bool runResolver) const; bool isAddrInSection(uintptr_t addr, uint8_t sectionIndex); void doBindExternalRelocations(const LinkContext& context); diff --git a/src/ImageLoaderMachOCompressed.cpp b/src/ImageLoaderMachOCompressed.cpp index 329b81d..fb3c04e 100644 --- a/src/ImageLoaderMachOCompressed.cpp +++ b/src/ImageLoaderMachOCompressed.cpp @@ -62,46 +62,6 @@ struct macho_routines_command : public routines_command {}; #endif - -static uintptr_t read_uleb128(const uint8_t*& p, const uint8_t* end) -{ - uint64_t result = 0; - int bit = 0; - do { - if (p == end) - dyld::throwf("malformed uleb128"); - - uint64_t slice = *p & 0x7f; - - if (bit > 63) - dyld::throwf("uleb128 too big for uint64, bit=%d, result=0x%0llX", bit, result); - else { - result |= (slice << bit); - bit += 7; - } - } while (*p++ & 0x80); - return result; -} - - -static intptr_t read_sleb128(const uint8_t*& p, const uint8_t* end) -{ - int64_t result = 0; - int bit = 0; - uint8_t byte; - do { - if (p == end) - throw "malformed sleb128"; - byte = *p++; - result |= (((int64_t)(byte & 0x7f)) << bit); - bit += 7; - } while (byte & 0x80); - // sign extend negative numbers - if ( (byte & 0x40) != 0 ) - result |= (-1LL) << bit; - return result; -} - // create image for main executable ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, @@ -430,15 +390,14 @@ void ImageLoaderMachOCompressed::rebaseAt(const LinkContext& context, uintptr_t void ImageLoaderMachOCompressed::throwBadRebaseAddress(uintptr_t address, uintptr_t segmentEndAddress, int segmentIndex, const uint8_t* startOpcodes, const uint8_t* endOpcodes, const uint8_t* pos) { - dyld::throwf("malformed rebase opcodes (%ld/%ld): address 0x%08lX is beyond end of segment %s (0x%08lX -> 0x%08lX)", + dyld::throwf("malformed rebase opcodes (%ld/%ld): address 0x%08lX is outside of segment %s (0x%08lX -> 0x%08lX)", (intptr_t)(pos-startOpcodes), (intptr_t)(endOpcodes-startOpcodes), address, segName(segmentIndex), segActualLoadAddress(segmentIndex), segmentEndAddress); } -void ImageLoaderMachOCompressed::rebase(const LinkContext& context) +void ImageLoaderMachOCompressed::rebase(const LinkContext& context, uintptr_t slide) { CRSetCrashLogMessage2(this->getPath()); - const uintptr_t slide = this->fSlide; const uint8_t* const start = fLinkEditBase + fDyldInfo->rebase_off; const uint8_t* const end = &start[fDyldInfo->rebase_size]; const uint8_t* p = start; @@ -447,6 +406,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) uint8_t type = 0; int segmentIndex = 0; uintptr_t address = segActualLoadAddress(0); + uintptr_t segmentStartAddress = segActualLoadAddress(0); uintptr_t segmentEndAddress = segActualEndAddress(0); uintptr_t count; uintptr_t skip; @@ -467,8 +427,16 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) if ( segmentIndex >= fSegmentsCount ) dyld::throwf("REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", segmentIndex, fSegmentsCount-1); - address = segActualLoadAddress(segmentIndex) + read_uleb128(p, end); + #if TEXT_RELOC_SUPPORT + if ( !segWriteable(segmentIndex) && !segHasRebaseFixUps(segmentIndex) && !segHasBindFixUps(segmentIndex) ) + #else + if ( !segWriteable(segmentIndex) ) + #endif + dyld::throwf("REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is not a writable segment (%s)", + segmentIndex, segName(segmentIndex)); + segmentStartAddress = segActualLoadAddress(segmentIndex); segmentEndAddress = segActualEndAddress(segmentIndex); + address = segmentStartAddress + read_uleb128(p, end); break; case REBASE_OPCODE_ADD_ADDR_ULEB: address += read_uleb128(p, end); @@ -478,7 +446,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) break; case REBASE_OPCODE_DO_REBASE_IMM_TIMES: for (int i=0; i < immediate; ++i) { - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadRebaseAddress(address, segmentEndAddress, segmentIndex, start, end, p); rebaseAt(context, address, slide, type); address += sizeof(uintptr_t); @@ -488,7 +456,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) case REBASE_OPCODE_DO_REBASE_ULEB_TIMES: count = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadRebaseAddress(address, segmentEndAddress, segmentIndex, start, end, p); rebaseAt(context, address, slide, type); address += sizeof(uintptr_t); @@ -496,7 +464,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) fgTotalRebaseFixups += count; break; case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadRebaseAddress(address, segmentEndAddress, segmentIndex, start, end, p); rebaseAt(context, address, slide, type); address += read_uleb128(p, end) + sizeof(uintptr_t); @@ -506,7 +474,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) count = read_uleb128(p, end); skip = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadRebaseAddress(address, segmentEndAddress, segmentIndex, start, end, p); rebaseAt(context, address, slide, type); address += skip + sizeof(uintptr_t); @@ -526,74 +494,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context) CRSetCrashLogMessage2(NULL); } -// -// This function is the hotspot of symbol lookup. It was pulled out of findExportedSymbol() -// to enable it to be re-written in assembler if needed. -// -const uint8_t* ImageLoaderMachOCompressed::trieWalk(const uint8_t* start, const uint8_t* end, const char* s) -{ - const uint8_t* p = start; - while ( p != NULL ) { - uintptr_t terminalSize = *p++; - if ( terminalSize > 127 ) { - // except for re-export-with-rename, all terminal sizes fit in one byte - --p; - terminalSize = read_uleb128(p, end); - } - if ( (*s == '\0') && (terminalSize != 0) ) { - //dyld::log("trieWalk(%p) returning %p\n", start, p); - return p; - } - const uint8_t* children = p + terminalSize; - //dyld::log("trieWalk(%p) sym=%s, terminalSize=%d, children=%p\n", start, s, terminalSize, children); - uint8_t childrenRemaining = *children++; - p = children; - uintptr_t nodeOffset = 0; - for (; childrenRemaining > 0; --childrenRemaining) { - const char* ss = s; - //dyld::log("trieWalk(%p) child str=%s\n", start, (char*)p); - bool wrongEdge = false; - // scan whole edge to get to next edge - // if edge is longer than target symbol name, don't read past end of symbol name - char c = *p; - while ( c != '\0' ) { - if ( !wrongEdge ) { - if ( c != *ss ) - wrongEdge = true; - ++ss; - } - ++p; - c = *p; - } - if ( wrongEdge ) { - // advance to next child - ++p; // skip over zero terminator - // skip over uleb128 until last byte is found - while ( (*p & 0x80) != 0 ) - ++p; - ++p; // skil over last byte of uleb128 - } - else { - // the symbol so far matches this edge (child) - // so advance to the child's node - ++p; - nodeOffset = read_uleb128(p, end); - s = ss; - //dyld::log("trieWalk() found matching edge advancing to node 0x%x\n", nodeOffset); - break; - } - } - if ( nodeOffset != 0 ) - p = &start[nodeOffset]; - else - p = NULL; - } - //dyld::log("trieWalk(%p) return NULL\n", start); - return NULL; -} - - -const ImageLoader::Symbol* ImageLoaderMachOCompressed::findExportedSymbol(const char* symbol, const ImageLoader** foundIn) const +const ImageLoader::Symbol* ImageLoaderMachOCompressed::findShallowExportedSymbol(const char* symbol, const ImageLoader** foundIn) const { //dyld::log("Compressed::findExportedSymbol(%s) in %s\n", symbol, this->getShortName()); if ( fDyldInfo->export_size == 0 ) @@ -618,7 +519,8 @@ const ImageLoader::Symbol* ImageLoaderMachOCompressed::findExportedSymbol(const if ( (ordinal > 0) && (ordinal <= libraryCount()) ) { const ImageLoader* reexportedFrom = libImage((unsigned int)ordinal-1); //dyld::log("Compressed::findExportedSymbol(), %s -> %s/%s\n", symbol, reexportedFrom->getShortName(), importedName); - return reexportedFrom->findExportedSymbol(importedName, true, foundIn); + const char* reExportLibPath = libPath((unsigned int)ordinal-1); + return reexportedFrom->findExportedSymbol(importedName, true, reExportLibPath, foundIn); } else { //dyld::throwf("bad mach-o binary, library ordinal (%u) invalid (max %u) for re-exported symbol %s in %s", @@ -741,7 +643,7 @@ uintptr_t ImageLoaderMachOCompressed::resolveFlat(const LinkContext& context, co // if a bundle is loaded privately the above will not find its exports if ( this->isBundle() && this->hasHiddenExports() ) { // look in self for needed symbol - sym = this->ImageLoaderMachO::findExportedSymbol(symbolName, false, foundIn); + sym = this->findShallowExportedSymbol(symbolName, foundIn); if ( sym != NULL ) return (*foundIn)->getExportedSymbolAddress(sym, context, this, runResolver); } @@ -753,15 +655,15 @@ uintptr_t ImageLoaderMachOCompressed::resolveFlat(const LinkContext& context, co } -uintptr_t ImageLoaderMachOCompressed::resolveTwolevel(const LinkContext& context, const ImageLoader* targetImage, bool weak_import, - const char* symbolName, bool runResolver, const ImageLoader** foundIn) +uintptr_t ImageLoaderMachOCompressed::resolveTwolevel(const LinkContext& context, const char* symbolName, const ImageLoader* definedInImage, + const ImageLoader* requestorImage, unsigned requestorOrdinalOfDef, bool weak_import, bool runResolver, + const ImageLoader** foundIn) { // two level lookup - const Symbol* sym = targetImage->findExportedSymbol(symbolName, true, foundIn); - if ( sym != NULL ) { - return (*foundIn)->getExportedSymbolAddress(sym, context, this, runResolver); - } - + uintptr_t address; + if ( definedInImage->findExportedSymbolAddress(context, symbolName, requestorImage, requestorOrdinalOfDef, runResolver, foundIn, &address) ) + return address; + if ( weak_import ) { // definition can't be found anywhere, ok because it is weak, just return 0 return 0; @@ -784,7 +686,7 @@ uintptr_t ImageLoaderMachOCompressed::resolveTwolevel(const LinkContext& context strcpy(versMismatch, msg); ::free((void*)msg); } - throwSymbolNotFound(context, symbolName, this->getPath(), versMismatch, targetImage->getPath()); + throwSymbolNotFound(context, symbolName, this->getPath(), versMismatch, definedInImage->getPath()); } @@ -838,10 +740,10 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const } } else { - symbolAddress = resolveTwolevel(context, *targetImage, weak_import, symbolName, runResolver, targetImage); + symbolAddress = resolveTwolevel(context, symbolName, *targetImage, this, (unsigned)libraryOrdinal, weak_import, runResolver, targetImage); } } - + // save off lookup results if client wants if ( last != NULL ) { last->ordinal = libraryOrdinal; @@ -855,23 +757,24 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const } uintptr_t ImageLoaderMachOCompressed::bindAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char* symbolName, - uint8_t symboFlags, intptr_t addend, long libraryOrdinal, const char* msg, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, const char* msg, LastLookup* last, bool runResolver) { const ImageLoader* targetImage; uintptr_t symbolAddress; // resolve symbol - symbolAddress = this->resolve(context, symbolName, symboFlags, libraryOrdinal, &targetImage, last, runResolver); + symbolAddress = this->resolve(context, symbolName, symbolFlags, libraryOrdinal, &targetImage, last, runResolver); // do actual update - return this->bindLocation(context, addr, symbolAddress, targetImage, type, symbolName, addend, msg); + return this->bindLocation(context, addr, symbolAddress, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, msg); } + void ImageLoaderMachOCompressed::throwBadBindingAddress(uintptr_t address, uintptr_t segmentEndAddress, int segmentIndex, const uint8_t* startOpcodes, const uint8_t* endOpcodes, const uint8_t* pos) { - dyld::throwf("malformed binding opcodes (%ld/%ld): address 0x%08lX is beyond end of segment %s (0x%08lX -> 0x%08lX)", + dyld::throwf("malformed binding opcodes (%ld/%ld): address 0x%08lX is outside segment %s (0x%08lX -> 0x%08lX)", (intptr_t)(pos-startOpcodes), (intptr_t)(endOpcodes-startOpcodes), address, segName(segmentIndex), segActualLoadAddress(segmentIndex), segmentEndAddress); } @@ -887,7 +790,8 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa // don't need to bind } else { - + uint64_t t0 = mach_absolute_time(); + #if TEXT_RELOC_SUPPORT // if there are __TEXT fixups, temporarily make __TEXT writable if ( fTextSegmentBinds ) @@ -917,6 +821,9 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa // tell kernel we are done with chunks of LINKEDIT if ( !context.preFetchDisabled ) this->markFreeLINKEDIT(context); + + uint64_t t1 = mach_absolute_time(); + ImageLoader::fgTotalRebindCacheTime += (t1-t0); } // set up dyld entry points in image @@ -935,15 +842,18 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl { try { uint8_t type = 0; - int segmentIndex = 0; + int segmentIndex = -1; uintptr_t address = segActualLoadAddress(0); + uintptr_t segmentStartAddress = segActualLoadAddress(0); uintptr_t segmentEndAddress = segActualEndAddress(0); const char* symbolName = NULL; uint8_t symboFlags = 0; + bool libraryOrdinalSet = false; long libraryOrdinal = 0; intptr_t addend = 0; uintptr_t count; uintptr_t skip; + uintptr_t segOffset; LastLookup last = { 0, 0, NULL, 0, NULL }; const uint8_t* const start = fLinkEditBase + fDyldInfo->bind_off; const uint8_t* const end = &start[fDyldInfo->bind_size]; @@ -959,9 +869,11 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl break; case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: libraryOrdinal = immediate; + libraryOrdinalSet = true; break; case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: libraryOrdinal = read_uleb128(p, end); + libraryOrdinalSet = true; break; case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: // the special ordinals are negative numbers @@ -971,6 +883,7 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl int8_t signExtended = BIND_OPCODE_MASK | immediate; libraryOrdinal = signExtended; } + libraryOrdinalSet = true; break; case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: symbolName = (char*)p; @@ -987,38 +900,72 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl break; case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: segmentIndex = immediate; - if ( segmentIndex >= fSegmentsCount ) - dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", + if ( (segmentIndex >= fSegmentsCount) || (segmentIndex < 0) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is out of range (0..%d)", segmentIndex, fSegmentsCount-1); - address = segActualLoadAddress(segmentIndex) + read_uleb128(p, end); + #if TEXT_RELOC_SUPPORT + if ( !segWriteable(segmentIndex) && !segHasRebaseFixUps(segmentIndex) && !segHasBindFixUps(segmentIndex) ) + #else + if ( !segWriteable(segmentIndex) ) + #endif + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is not writable", segmentIndex); + segOffset = read_uleb128(p, end); + if ( segOffset > segSize(segmentIndex) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(segmentIndex)); + segmentStartAddress = segActualLoadAddress(segmentIndex); + address = segmentStartAddress + segOffset; segmentEndAddress = segActualEndAddress(segmentIndex); break; case BIND_OPCODE_ADD_ADDR_ULEB: address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); address += sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); address += read_uleb128(p, end) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); address += immediate*sizeof(intptr_t) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); count = read_uleb128(p, end); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); skip = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - if ( address >= segmentEndAddress ) + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); address += skip + sizeof(intptr_t); @@ -1040,9 +987,11 @@ void ImageLoaderMachOCompressed::eachLazyBind(const LinkContext& context, bind_h { try { uint8_t type = BIND_TYPE_POINTER; - int segmentIndex = 0; + int segmentIndex = -1; uintptr_t address = segActualLoadAddress(0); + uintptr_t segmentStartAddress = segActualLoadAddress(0); uintptr_t segmentEndAddress = segActualEndAddress(0); + uintptr_t segOffset; const char* symbolName = NULL; uint8_t symboFlags = 0; long libraryOrdinal = 0; @@ -1089,18 +1038,28 @@ void ImageLoaderMachOCompressed::eachLazyBind(const LinkContext& context, bind_h break; case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: segmentIndex = immediate; - if ( segmentIndex >= fSegmentsCount ) - dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", + if ( (segmentIndex >= fSegmentsCount) || (segmentIndex < 0) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is out of range (0..%d)", segmentIndex, fSegmentsCount-1); - address = segActualLoadAddress(segmentIndex) + read_uleb128(p, end); + if ( !segWriteable(segmentIndex) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is not writable", segmentIndex); + segOffset = read_uleb128(p, end); + if ( segOffset > segSize(segmentIndex) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(segmentIndex)); + segmentStartAddress = segActualLoadAddress(segmentIndex); segmentEndAddress = segActualEndAddress(segmentIndex); + address = segmentStartAddress + segOffset; break; case BIND_OPCODE_ADD_ADDR_ULEB: address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - if ( address >= segmentEndAddress ) + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "forced lazy ", NULL, false); address += sizeof(intptr_t); break; @@ -1194,6 +1153,7 @@ uintptr_t ImageLoaderMachOCompressed::doBindLazySymbol(uintptr_t* lazyPointer, c } + uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context, void (*lock)(), void (*unlock)()) { @@ -1209,73 +1169,26 @@ uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingI const uint8_t* const start = fLinkEditBase + fDyldInfo->lazy_bind_off; const uint8_t* const end = &start[fDyldInfo->lazy_bind_size]; - if ( lazyBindingInfoOffset > fDyldInfo->lazy_bind_size ) { - dyld::throwf("fast lazy bind offset out of range (%u, max=%u) in image %s", - lazyBindingInfoOffset, fDyldInfo->lazy_bind_size, this->getPath()); - } + uint8_t segIndex; + uintptr_t segOffset; + int libraryOrdinal; + const char* symbolName; + bool doneAfterBind; + uintptr_t result; + do { + if ( ! getLazyBindingInfo(lazyBindingInfoOffset, start, end, &segIndex, &segOffset, &libraryOrdinal, &symbolName, &doneAfterBind) ) + dyld::throwf("bad lazy bind info"); + + if ( segIndex >= fSegmentsCount ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", + segIndex, fSegmentsCount-1); + if ( segOffset > segSize(segIndex) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(segIndex)); + uintptr_t address = segActualLoadAddress(segIndex) + segOffset; + result = this->bindAt(context, address, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal, "lazy ", NULL, true); + // Some old apps had multiple lazy symbols bound at once + } while (!doneAfterBind && !context.strictMachORequired); - uint8_t type = BIND_TYPE_POINTER; - uintptr_t address = 0; - const char* symbolName = NULL; - uint8_t symboFlags = 0; - long libraryOrdinal = 0; - bool done = false; - uintptr_t result = 0; - const uint8_t* p = &start[lazyBindingInfoOffset]; - while ( !done && (p < end) ) { - uint8_t immediate = *p & BIND_IMMEDIATE_MASK; - uint8_t opcode = *p & BIND_OPCODE_MASK; - ++p; - switch (opcode) { - case BIND_OPCODE_DONE: - done = true; - break; - case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: - libraryOrdinal = immediate; - break; - case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: - libraryOrdinal = read_uleb128(p, end); - break; - case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: - // the special ordinals are negative numbers - if ( immediate == 0 ) - libraryOrdinal = 0; - else { - int8_t signExtended = BIND_OPCODE_MASK | immediate; - libraryOrdinal = signExtended; - } - break; - case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: - symbolName = (char*)p; - symboFlags = immediate; - while (*p != '\0') - ++p; - ++p; - break; - case BIND_OPCODE_SET_TYPE_IMM: - type = immediate; - break; - case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: - if ( immediate >= fSegmentsCount ) - dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", - immediate, fSegmentsCount-1); - address = segActualLoadAddress(immediate) + read_uleb128(p, end); - break; - case BIND_OPCODE_DO_BIND: - - - result = this->bindAt(context, address, type, symbolName, 0, 0, libraryOrdinal, "lazy ", NULL, true); - break; - case BIND_OPCODE_SET_ADDEND_SLEB: - case BIND_OPCODE_ADD_ADDR_ULEB: - case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: - default: - dyld::throwf("bad lazy bind opcode %d", *p); - } - } - if ( !this->usesTwoLevelNameSpace() ) { // release dyld global lock if ( unlock != NULL ) @@ -1284,7 +1197,7 @@ uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingI return result; } -void ImageLoaderMachOCompressed::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder) +void ImageLoaderMachOCompressed::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder, unsigned) { it.image = this; it.symbolName = " "; @@ -1316,6 +1229,7 @@ bool ImageLoaderMachOCompressed::incrementCoalIterator(CoalIterator& it) const uint8_t* end = fLinkEditBase + fDyldInfo->weak_bind_off + this->fDyldInfo->weak_bind_size; uintptr_t count; uintptr_t skip; + uintptr_t segOffset; while ( p < end ) { uint8_t immediate = *p & BIND_IMMEDIATE_MASK; uint8_t opcode = *p & BIND_OPCODE_MASK; @@ -1345,7 +1259,21 @@ bool ImageLoaderMachOCompressed::incrementCoalIterator(CoalIterator& it) if ( immediate >= fSegmentsCount ) dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", immediate, fSegmentsCount-1); - it.address = segActualLoadAddress(immediate) + read_uleb128(p, end); + #if __arm__ + // iOS app compatibility + if ( !segWriteable(immediate) && it.image->isPositionIndependentExecutable() ) + #elif TEXT_RELOC_SUPPORT + // i386 OS X app compatibility + if ( !segWriteable(immediate) && !segHasRebaseFixUps(immediate) && !segHasBindFixUps(immediate) + && (!it.image->isExecutable() || it.image->isPositionIndependentExecutable()) ) + #else + if ( !segWriteable(immediate) ) + #endif + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB targets segment %s which is not writable", segName(immediate)); + segOffset = read_uleb128(p, end); + if ( segOffset > segSize(immediate) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(immediate)); + it.address = segActualLoadAddress(immediate) + segOffset; break; case BIND_OPCODE_ADD_ADDR_ULEB: it.address += read_uleb128(p, end); @@ -1381,7 +1309,7 @@ uintptr_t ImageLoaderMachOCompressed::getAddressCoalIterator(CoalIterator& it, c { //dyld::log("looking for %s in %s\n", it.symbolName, this->getPath()); const ImageLoader* foundIn = NULL; - const ImageLoader::Symbol* sym = this->findExportedSymbol(it.symbolName, &foundIn); + const ImageLoader::Symbol* sym = this->findShallowExportedSymbol(it.symbolName, &foundIn); if ( sym != NULL ) { //dyld::log("sym=%p, foundIn=%p\n", sym, foundIn); return foundIn->getExportedSymbolAddress(sym, context, this); @@ -1390,7 +1318,7 @@ uintptr_t ImageLoaderMachOCompressed::getAddressCoalIterator(CoalIterator& it, c } -void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintptr_t value, ImageLoader* targetImage, const LinkContext& context) +void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintptr_t value, ImageLoader* targetImage, unsigned targetIndex, const LinkContext& context) { // weak binding done too early with inserted libraries if ( this->getState() < dyld_image_state_bound ) @@ -1406,6 +1334,7 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt intptr_t addend = it.addend; uintptr_t count; uintptr_t skip; + uintptr_t segOffset; bool done = false; bool boundSomething = false; while ( !done && (p < end) ) { @@ -1429,23 +1358,37 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt if ( immediate >= fSegmentsCount ) dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (0..%d)", immediate, fSegmentsCount-1); - address = segActualLoadAddress(immediate) + read_uleb128(p, end); + #if __arm__ + // iOS app compatibility + if ( !segWriteable(immediate) && it.image->isPositionIndependentExecutable() ) + #elif TEXT_RELOC_SUPPORT + // i386 OS X app compatibility + if ( !segWriteable(immediate) && !segHasRebaseFixUps(immediate) && !segHasBindFixUps(immediate) + && (!it.image->isExecutable() || it.image->isPositionIndependentExecutable()) ) + #else + if ( !segWriteable(immediate) ) + #endif + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB targets segment %s which is not writable", segName(immediate)); + segOffset = read_uleb128(p, end); + if ( segOffset > segSize(immediate) ) + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(immediate)); + address = segActualLoadAddress(immediate) + segOffset; break; case BIND_OPCODE_ADD_ADDR_ULEB: address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - bindLocation(context, address, value, targetImage, type, symbolName, addend, "weak "); + bindLocation(context, address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); boundSomething = true; address += sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - bindLocation(context, address, value, targetImage, type, symbolName, addend, "weak "); + bindLocation(context, address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); boundSomething = true; address += read_uleb128(p, end) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - bindLocation(context, address, value, targetImage, type, symbolName, addend, "weak "); + bindLocation(context, address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); boundSomething = true; address += immediate*sizeof(intptr_t) + sizeof(intptr_t); break; @@ -1453,7 +1396,7 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt count = read_uleb128(p, end); skip = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - bindLocation(context, address, value, targetImage, type, symbolName, addend, "weak "); + bindLocation(context, address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); boundSomething = true; address += skip + sizeof(intptr_t); } @@ -1474,8 +1417,9 @@ uintptr_t ImageLoaderMachOCompressed::interposeAt(const LinkContext& context, ui uintptr_t* fixupLocation = (uintptr_t*)addr; uintptr_t curValue = *fixupLocation; uintptr_t newValue = interposedAddress(context, curValue, this); - if ( newValue != curValue) + if ( newValue != curValue) { *fixupLocation = newValue; + } } return 0; } @@ -1523,78 +1467,9 @@ void ImageLoaderMachOCompressed::dynamicInterpose(const LinkContext& context) eachLazyBind(context, &ImageLoaderMachOCompressed::dynamicInterposeAt); } - const char* ImageLoaderMachOCompressed::findClosestSymbol(const void* addr, const void** closestAddr) const { - // called by dladdr() - // only works with compressed LINKEDIT if classic symbol table is also present - const macho_nlist* symbolTable = NULL; - const char* symbolTableStrings = NULL; - const dysymtab_command* dynSymbolTable = NULL; - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; - const struct load_command* cmd = cmds; - for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_SYMTAB: - { - const struct symtab_command* symtab = (struct symtab_command*)cmd; - symbolTableStrings = (const char*)&fLinkEditBase[symtab->stroff]; - symbolTable = (macho_nlist*)(&fLinkEditBase[symtab->symoff]); - } - break; - case LC_DYSYMTAB: - dynSymbolTable = (struct dysymtab_command*)cmd; - break; - } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); - } - // no symbol table => no lookup by address - if ( (symbolTable == NULL) || (dynSymbolTable == NULL) ) - return NULL; - - uintptr_t targetAddress = (uintptr_t)addr - fSlide; - const struct macho_nlist* bestSymbol = NULL; - // first walk all global symbols - const struct macho_nlist* const globalsStart = &symbolTable[dynSymbolTable->iextdefsym]; - const struct macho_nlist* const globalsEnd= &globalsStart[dynSymbolTable->nextdefsym]; - for (const struct macho_nlist* s = globalsStart; s < globalsEnd; ++s) { - if ( (s->n_type & N_TYPE) == N_SECT ) { - if ( bestSymbol == NULL ) { - if ( s->n_value <= targetAddress ) - bestSymbol = s; - } - else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) { - bestSymbol = s; - } - } - } - // next walk all local symbols - const struct macho_nlist* const localsStart = &symbolTable[dynSymbolTable->ilocalsym]; - const struct macho_nlist* const localsEnd= &localsStart[dynSymbolTable->nlocalsym]; - for (const struct macho_nlist* s = localsStart; s < localsEnd; ++s) { - if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) { - if ( bestSymbol == NULL ) { - if ( s->n_value <= targetAddress ) - bestSymbol = s; - } - else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) { - bestSymbol = s; - } - } - } - if ( bestSymbol != NULL ) { -#if __arm__ - if (bestSymbol->n_desc & N_ARM_THUMB_DEF) - *closestAddr = (void*)((bestSymbol->n_value | 1) + fSlide); - else - *closestAddr = (void*)(bestSymbol->n_value + fSlide); -#else - *closestAddr = (void*)(bestSymbol->n_value + fSlide); -#endif - return &symbolTableStrings[bestSymbol->n_un.n_strx]; - } - return NULL; + return ImageLoaderMachO::findClosestSymbol((mach_header*)fMachOData, addr, closestAddr); } @@ -1647,9 +1522,8 @@ void ImageLoaderMachOCompressed::updateAlternateLazyPointer(uint8_t* stub, void* void ImageLoaderMachOCompressed::updateOptimizedLazyPointers(const LinkContext& context) { #if __arm__ || __x86_64__ - // find stubs and lazy pointer sections + // find stubs and indirect symbol table const struct macho_section* stubsSection = NULL; - const struct macho_section* lazyPointerSection = NULL; const dysymtab_command* dynSymbolTable = NULL; const macho_header* mh = (macho_header*)fMachOData; const uint32_t cmd_count = mh->ncmds; @@ -1664,8 +1538,6 @@ void ImageLoaderMachOCompressed::updateOptimizedLazyPointers(const LinkContext& const uint8_t type = sect->flags & SECTION_TYPE; if ( type == S_SYMBOL_STUBS ) stubsSection = sect; - else if ( type == S_LAZY_SYMBOL_POINTERS ) - lazyPointerSection = sect; } } else if ( cmd->cmd == LC_DYSYMTAB ) { @@ -1673,36 +1545,51 @@ void ImageLoaderMachOCompressed::updateOptimizedLazyPointers(const LinkContext& } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - - // sanity check if ( dynSymbolTable == NULL ) return; - if ( (stubsSection == NULL) || (lazyPointerSection == NULL) ) - return; - const uint32_t stubsCount = stubsSection->size / stubsSection->reserved2; - const uint32_t lazyPointersCount = lazyPointerSection->size / sizeof(void*); - if ( stubsCount != lazyPointersCount ) + const uint32_t* const indirectTable = (uint32_t*)&fLinkEditBase[dynSymbolTable->indirectsymoff]; + if ( stubsSection == NULL ) return; + const uint32_t stubsSize = stubsSection->reserved2; + const uint32_t stubsCount = (uint32_t)(stubsSection->size / stubsSize); const uint32_t stubsIndirectTableOffset = stubsSection->reserved1; - const uint32_t lazyPointersIndirectTableOffset = lazyPointerSection->reserved1; if ( (stubsIndirectTableOffset+stubsCount) > dynSymbolTable->nindirectsyms ) return; - if ( (lazyPointersIndirectTableOffset+lazyPointersCount) > dynSymbolTable->nindirectsyms ) - return; - - // walk stubs and lazy pointers - const uint32_t* const indirectTable = (uint32_t*)&fLinkEditBase[dynSymbolTable->indirectsymoff]; - void** const lazyPointersStartAddr = (void**)(lazyPointerSection->addr + this->fSlide); - uint8_t* const stubsStartAddr = (uint8_t*)(stubsSection->addr + this->fSlide); - uint8_t* stub = stubsStartAddr; - void** lpa = lazyPointersStartAddr; - for(uint32_t i=0; i < stubsCount; ++i, stub += stubsSection->reserved2, ++lpa) { - // sanity check symbol index of stub and lazy pointer match - if ( indirectTable[stubsIndirectTableOffset+i] != indirectTable[lazyPointersIndirectTableOffset+i] ) - continue; - this->updateAlternateLazyPointer(stub, lpa, context); + uint8_t* const stubsAddr = (uint8_t*)(stubsSection->addr + this->fSlide); + + // for each lazy pointer section + cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if (cmd->cmd == LC_SEGMENT_COMMAND) { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); + const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; + for (const struct macho_section* lazyPointerSection=sectionsStart; lazyPointerSection < sectionsEnd; ++lazyPointerSection) { + const uint8_t type = lazyPointerSection->flags & SECTION_TYPE; + if ( type != S_LAZY_SYMBOL_POINTERS ) + continue; + const uint32_t lazyPointersCount = (uint32_t)(lazyPointerSection->size / sizeof(void*)); + const uint32_t lazyPointersIndirectTableOffset = lazyPointerSection->reserved1; + if ( (lazyPointersIndirectTableOffset+lazyPointersCount) > dynSymbolTable->nindirectsyms ) + continue; + void** const lazyPointersAddr = (void**)(lazyPointerSection->addr + this->fSlide); + // for each lazy pointer + for(uint32_t lpIndex=0; lpIndex < lazyPointersCount; ++lpIndex) { + const uint32_t lpSymbolIndex = indirectTable[lazyPointersIndirectTableOffset+lpIndex]; + // find matching stub and validate it uses this lazy pointer + for(uint32_t stubIndex=0; stubIndex < stubsCount; ++stubIndex) { + if ( indirectTable[stubsIndirectTableOffset+stubIndex] == lpSymbolIndex ) { + this->updateAlternateLazyPointer(stubsAddr+stubIndex*stubsSize, &lazyPointersAddr[lpIndex], context); + break; + } + } + } + + } + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - + #endif } diff --git a/src/ImageLoaderMachOCompressed.h b/src/ImageLoaderMachOCompressed.h index e655adc..013bb90 100644 --- a/src/ImageLoaderMachOCompressed.h +++ b/src/ImageLoaderMachOCompressed.h @@ -62,12 +62,11 @@ public: virtual uintptr_t doBindLazySymbol(uintptr_t* lazyPointer, const LinkContext& context); virtual uintptr_t doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context, void (*lock)(), void (*unlock)()); virtual const char* findClosestSymbol(const void* addr, const void** closestAddr) const; - virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder); + virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder, unsigned); virtual bool incrementCoalIterator(CoalIterator&); virtual uintptr_t getAddressCoalIterator(CoalIterator&, const LinkContext& contex); - virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, const LinkContext& context); + virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, unsigned targetIndex, const LinkContext& context); - protected: virtual void doInterpose(const LinkContext& context); virtual void dynamicInterpose(const LinkContext& context); @@ -76,8 +75,8 @@ protected: virtual bool isSubframeworkOf(const LinkContext& context, const ImageLoader* image) const { return false; } virtual bool hasSubLibrary(const LinkContext& context, const ImageLoader* child) const { return false; } virtual uint32_t* segmentCommandOffsets() const; - virtual void rebase(const LinkContext& context); - virtual const ImageLoader::Symbol* findExportedSymbol(const char* name, const ImageLoader** foundIn) const; + virtual void rebase(const LinkContext& context, uintptr_t slide); + virtual const ImageLoader::Symbol* findShallowExportedSymbol(const char* name, const ImageLoader** foundIn) const; virtual bool containsSymbol(const void* addr) const; virtual uintptr_t exportedSymbolAddress(const LinkContext& context, const Symbol* symbol, const ImageLoader* requestor, bool runResolver) const; virtual bool exportedSymbolIsWeakDefintion(const Symbol* symbol) const; @@ -127,13 +126,13 @@ private: uintptr_t resolveFlat(const LinkContext& context, const char* symbolName, bool weak_import, bool runResolver, const ImageLoader** foundIn); uintptr_t resolveCoalesced(const LinkContext& context, const char* symbolName, const ImageLoader** foundIn); - uintptr_t resolveTwolevel(const LinkContext& context, const ImageLoader* targetImage, bool weak_import, - const char* symbolName, bool runResolver, const ImageLoader** foundIn); + uintptr_t resolveTwolevel(const LinkContext& context, const char* symbolName, const ImageLoader* definedInImage, + const ImageLoader* requestorImage, unsigned requestorOrdinalOfDef, bool weak_import, bool runResolver, + const ImageLoader** foundInn); uintptr_t interposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char*, uint8_t, intptr_t, long, const char*, LastLookup*, bool runResolver); uintptr_t dynamicInterposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char*, uint8_t, intptr_t, long, const char*, LastLookup*, bool runResolver); - static const uint8_t* trieWalk(const uint8_t* start, const uint8_t* end, const char* s); void updateOptimizedLazyPointers(const LinkContext& context); void updateAlternateLazyPointer(uint8_t* stub, void** originalLazyPointerAddr, const LinkContext& context); void registerEncryption(const struct encryption_info_command* encryptCmd, const LinkContext& context); diff --git a/src/ImageLoaderMegaDylib.cpp b/src/ImageLoaderMegaDylib.cpp new file mode 100644 index 0000000..f3612d6 --- /dev/null +++ b/src/ImageLoaderMegaDylib.cpp @@ -0,0 +1,1144 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +#if __arm__ || __arm64__ + #include +#else + #include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ImageLoaderMegaDylib.h" +#include "ImageLoaderMachO.h" +#include "mach-o/dyld_images.h" +#include "dyldLibSystemInterface.h" +#include "dyld.h" + +// from dyld_gdb.cpp +extern void addImagesToAllImages(uint32_t infoCount, const dyld_image_info info[]); + + +#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE + #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 +#endif + +// relocation_info.r_length field has value 3 for 64-bit executables and value 2 for 32-bit executables +#if __LP64__ + #define RELOC_SIZE 3 + #define LC_SEGMENT_COMMAND LC_SEGMENT_64 + #define LC_ROUTINES_COMMAND LC_ROUTINES_64 + struct macho_segment_command : public segment_command_64 {}; + struct macho_section : public section_64 {}; + struct macho_routines_command : public routines_command_64 {}; +#else + #define RELOC_SIZE 2 + #define LC_SEGMENT_COMMAND LC_SEGMENT + #define LC_ROUTINES_COMMAND LC_ROUTINES + struct macho_segment_command : public segment_command {}; + struct macho_section : public section {}; + struct macho_routines_command : public routines_command {}; +#endif + + + +#if SUPPORT_ACCELERATE_TABLES + + +ImageLoaderMegaDylib* ImageLoaderMegaDylib::makeImageLoaderMegaDylib(const dyld_cache_header* header, long slide, const LinkContext& context) +{ + return new ImageLoaderMegaDylib(header, slide, context); +} + +struct DATAdyld { + void* dyldLazyBinder; // filled in at launch by dyld to point into dyld to &stub_binding_helper + void* dyldFuncLookup; // filled in at launch by dyld to point into dyld to &_dyld_func_lookup + ProgramVars vars; +}; + + + + +ImageLoaderMegaDylib::ImageLoaderMegaDylib(const dyld_cache_header* header, long slide, const LinkContext& context) + : ImageLoader("dyld shared cache", 0), _header(header), _linkEditBias(NULL), _slide(slide), + _lockArray(NULL), _lockArrayInUseCount(0) +{ + pthread_mutex_init(&_lockArrayGuard, NULL); + const dyld_cache_mapping_info* mappings = (const dyld_cache_mapping_info*)((uint8_t*)_header + _header->mappingOffset); + const dyld_cache_mapping_info* lastMapping = &mappings[_header->mappingCount-1]; + const dyld_cache_accelerator_info* accHeader = (dyld_cache_accelerator_info*)(_header->accelerateInfoAddr + slide); + for (const dyld_cache_mapping_info* m=mappings; m <= lastMapping; ++m) { + if ( m->initProt == VM_PROT_READ ) { + _linkEditBias = (uint8_t*)(m->address - m->fileOffset) + _slide; + } + } + + _endOfCacheInMemory = (void*)(lastMapping->address + lastMapping->size + slide); + _images = (const dyld_cache_image_info*)((uint8_t*)_header + _header->imagesOffset); + _imageExtras = (dyld_cache_image_info_extra*)((char*)accHeader + accHeader->imagesExtrasOffset); + _initializers = (dyld_cache_accelerator_initializer*)((char*)accHeader + accHeader->initializersOffset); + _reExportsArray = (uint16_t*)((char*)accHeader + accHeader->reExportListOffset); + _dependenciesArray = (uint16_t*)((char*)accHeader + accHeader->depListOffset); + _bottomUpArray = (uint16_t*)((char*)accHeader + accHeader->bottomUpListOffset); + _rangeTable = (dyld_cache_range_entry*)((char*)accHeader + accHeader->rangeTableOffset); + _rangeTableCount = accHeader->rangeTableCount; + _imageCount = accHeader->imageExtrasCount; + _stateFlags = (uint8_t*)calloc(_imageCount, 1); + _initializerCount = accHeader->initializersCount; + _dylibsTrieStart = (uint8_t*)accHeader + accHeader->dylibTrieOffset; + _dylibsTrieEnd = _dylibsTrieStart + accHeader->dylibTrieSize; + _imageTextInfo = (dyld_cache_image_text_info*)((uint8_t*)_header + _header->imagesTextOffset); + DATAdyld* dyldSection = (DATAdyld*)(accHeader->dyldSectionAddr + slide); + dyldSection->dyldLazyBinder = NULL; // not used by libdyld.dylib + dyldSection->dyldFuncLookup = (void*)&_dyld_func_lookup; + dyldSection->vars.mh = context.mainExecutable->machHeader(); + context.setNewProgramVars(dyldSection->vars); +} + + +void ImageLoaderMegaDylib::unreachable() const +{ + abort(); +} + +ImageLoaderMegaDylib::~ImageLoaderMegaDylib() +{ +} + +const char* ImageLoaderMegaDylib::getInstallPath() const { + unreachable(); +} + +const macho_header* ImageLoaderMegaDylib::getIndexedMachHeader(unsigned index) const +{ + if ( index > _header->imagesCount ) + dyld::throwf("cache image index out of range (%u), max=%u", index, _header->imagesCount - 1); + return (const macho_header*)(_images[index].address + _slide); +} + +const char* ImageLoaderMegaDylib::getIndexedPath(unsigned index) const +{ + if ( index > _header->imagesCount ) + dyld::throwf("cache image index out of range (%u), max=%u", index, _header->imagesCount - 1); + return (char*)_header + _images[index].pathFileOffset; +} + +const char* ImageLoaderMegaDylib::getIndexedShortName(unsigned index) const +{ + const char* path = getIndexedPath(index); + const char* lastSlash = strrchr(path, '/'); + if ( lastSlash == NULL ) + return path; + else + return lastSlash+1; +} + +void ImageLoaderMegaDylib::getDylibUUID(unsigned int index, uuid_t uuid) const +{ + if ( index > _header->imagesCount ) + dyld::throwf("cache image index out of range (%u), max=%u", index, _header->imagesCount - 1); + memcpy(uuid, _imageTextInfo[index].uuid, 16); +} + +void ImageLoaderMegaDylib::printSegments(const macho_header* mh) const +{ + const uint32_t cmd_count = mh->ncmds; + const struct load_command* const cmds = (struct load_command*)((uint8_t*)mh + sizeof(macho_header)); + const struct load_command* cmd = cmds; + const macho_segment_command* seg; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + seg = (macho_segment_command*)cmd; + dyld::log("%18s at 0x%08lX->0x%08lX\n", seg->segname, (long)(seg->vmaddr + _slide), (long)(seg->vmaddr + seg->vmsize + _slide)); + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } +} + +bool ImageLoaderMegaDylib::hasDylib(const char* path, unsigned* index) const +{ + const uint8_t* imageNode = ImageLoader::trieWalk(_dylibsTrieStart, _dylibsTrieEnd, path); + if ( imageNode == NULL ) { + #if __MAC_OS_X_VERSION_MIN_REQUIRED + // not all symlinks are recorded as aliases in accelerator tables + if ( (strncmp(path, "/usr/lib/", 9) == 0) || (strncmp(path, "/System/Library/", 16) == 0) ) { + char resolvedPath[PATH_MAX]; + if ( realpath(path, resolvedPath) != NULL ) { + imageNode = ImageLoader::trieWalk(_dylibsTrieStart, _dylibsTrieEnd, resolvedPath); + } + } + if ( imageNode == NULL ) + return false; + #else + return false; + #endif + } + *index = (unsigned)read_uleb128(imageNode, _dylibsTrieEnd); + return true; +} + +bool ImageLoaderMegaDylib::addressInCache(const void* address, const mach_header** mh, const char** path, unsigned* index) +{ + // quick out of bounds check +#if __x86_64__ + if ( (uintptr_t)address < 0x7FFF70000000LL ) + return false; +#else + if ( address < (void*)_header ) + return false; +#endif + if ( address > _endOfCacheInMemory ) + return false; + + uint64_t unslidAddress = (uint64_t)address - _slide; + // linear search for now + const dyld_cache_range_entry* rangeTableEnd = &_rangeTable[_rangeTableCount]; + for (const dyld_cache_range_entry* r = _rangeTable; r < rangeTableEnd; ++r) { + if ( (r->startAddress <= unslidAddress) && (unslidAddress < r->startAddress+r->size) ) { + *index = r->imageIndex; + *mh = (mach_header*)getIndexedMachHeader(r->imageIndex); + *path = getIndexedPath(r->imageIndex); + return true; + } + } + + return false; +} + + +bool ImageLoaderMegaDylib::findUnwindSections(const void* address, dyld_unwind_sections* info) +{ + const char* path; + unsigned index; + if ( addressInCache(address, &info->mh, &path, &index) ) { + info->dwarf_section = NULL; + info->dwarf_section_length = 0; + ImageLoaderMachO::findSection(info->mh, "__TEXT", "__eh_frame", (void**)&info->dwarf_section, &info->dwarf_section_length); + + info->compact_unwind_section = NULL; + info->compact_unwind_section_length = 0; + ImageLoaderMachO::findSection(info->mh, "__TEXT", "__unwind_info", (void**)&info->compact_unwind_section, &info->compact_unwind_section_length); + + return true; + } + return false; +} + + +unsigned ImageLoaderMegaDylib::findImageIndex(const LinkContext& context, const char* path) const +{ + unsigned index; + if ( hasDylib(path, &index) ) + return index; + + // Somehow we found the dylib in the cache, but it is not this literal string, try simple expansions of @rpath + if ( strncmp(path, "@rpath/", 7) == 0 ) { + std::vector rpathsFromMainExecutable; + context.mainExecutable->getRPaths(context, rpathsFromMainExecutable); + const char* trailingPath = &path[7]; + for (const char* anRPath : rpathsFromMainExecutable) { + if ( anRPath[0] != '/' ) + continue; + char possiblePath[strlen(anRPath) + strlen(trailingPath)+2]; + strcpy(possiblePath, anRPath); + if ( possiblePath[strlen(possiblePath)-1] != '/' ) + strcat(possiblePath, "/"); + strcat(possiblePath, trailingPath); + if ( hasDylib(possiblePath, &index) ) { + return index; + } + } + } + dyld::throwf("no cache image with name (%s)", path); +} + +void ImageLoaderMegaDylib::initializeCoalIterator(CoalIterator& it, unsigned int loadOrder, unsigned imageIndex) +{ + it.image = this; + it.symbolName = " "; + it.loadOrder = loadOrder; + it.weakSymbol = false; + it.symbolMatches = false; + it.done = false; + it.curIndex = 0; + it.endIndex = _imageExtras[imageIndex].weakBindingsSize; + it.address = 0; + it.type = 0; + it.addend = 0; + it.imageIndex = imageIndex; +} + +bool ImageLoaderMegaDylib::incrementCoalIterator(CoalIterator& it) +{ + if ( it.done ) + return false; + + if ( _imageExtras[it.imageIndex].weakBindingsSize == 0 ) { + /// hmmm, ld set MH_WEAK_DEFINES or MH_BINDS_TO_WEAK, but there is no weak binding info + it.done = true; + it.symbolName = "~~~"; + return true; + } + const uint8_t* start = (uint8_t*)(_imageExtras[it.imageIndex].weakBindingsAddr + _slide); + const uint8_t* end = (uint8_t*)(_imageExtras[it.imageIndex].weakBindingsAddr + _slide + _imageExtras[it.imageIndex].weakBindingsSize); + const uint8_t* p = start + it.curIndex; + uintptr_t count; + uintptr_t skip; + uint64_t segOffset; + unsigned segIndex; + const mach_header* mh; + while ( p < end ) { + uint8_t immediate = *p & BIND_IMMEDIATE_MASK; + uint8_t opcode = *p & BIND_OPCODE_MASK; + ++p; + switch (opcode) { + case BIND_OPCODE_DONE: + it.done = true; + it.curIndex = p - start; + it.symbolName = "~~~"; // sorts to end + return true; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + it.symbolName = (char*)p; + it.weakSymbol = ((immediate & BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION) == 0); + it.symbolMatches = false; + while (*p != '\0') + ++p; + ++p; + it.curIndex = p - start; + return false; + case BIND_OPCODE_SET_TYPE_IMM: + it.type = immediate; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + it.addend = read_sleb128(p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segIndex = immediate; + segOffset = read_uleb128(p, end); + mh = (mach_header*)getIndexedMachHeader((unsigned)it.imageIndex); + if ( uintptr_t segPrefAddress = ImageLoaderMachO::segPreferredAddress(mh, segIndex) ) + it.address = segPrefAddress + segOffset + _slide; + else + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large", segIndex); + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + it.address += read_uleb128(p, end); + break; + case BIND_OPCODE_DO_BIND: + it.address += sizeof(intptr_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + it.address += read_uleb128(p, end) + sizeof(intptr_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + it.address += immediate*sizeof(intptr_t) + sizeof(intptr_t); + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(p, end); + skip = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + it.address += skip + sizeof(intptr_t); + } + break; + default: + dyld::throwf("bad weak bind opcode '%d' found after processing %d bytes in '%s'", *p, (int)(p-start), this->getPath()); + } + } + /// hmmm, BIND_OPCODE_DONE is missing... + it.done = true; + it.symbolName = "~~~"; + //dyld::log("missing BIND_OPCODE_DONE for image %s\n", this->getPath()); + return true; +} + +uintptr_t ImageLoaderMegaDylib::getAddressCoalIterator(CoalIterator& it, const LinkContext& context) +{ + //dyld::log("looking for %s in %s\n", it.symbolName, this->getPath()); + uintptr_t address; + if ( findInChainedTries(context, it.symbolName, (unsigned)it.imageIndex, NULL, false, &address) ) { + return address; + } + return 0; +} + +void ImageLoaderMegaDylib::updateUsesCoalIterator(CoalIterator& it, uintptr_t value, ImageLoader* targetImage, unsigned targetIndex, const LinkContext& context) +{ + + const uint8_t* start = (uint8_t*)(_imageExtras[it.imageIndex].weakBindingsAddr + _slide); + const uint8_t* end = (uint8_t*)(_imageExtras[it.imageIndex].weakBindingsAddr + _slide + _imageExtras[it.imageIndex].weakBindingsSize); + const uint8_t* p = start + it.curIndex; + + uint8_t type = it.type; + uintptr_t address = it.address; + const char* symbolName = it.symbolName; + intptr_t addend = it.addend; + uint64_t segOffset; + unsigned segIndex; + const mach_header* mh; + uintptr_t count; + uintptr_t skip; + bool done = false; + bool boundSomething = false; + const char* targetImagePath = targetImage ? targetImage->getIndexedPath(targetIndex) : NULL; + while ( !done && (p < end) ) { + uint8_t immediate = *p & BIND_IMMEDIATE_MASK; + uint8_t opcode = *p & BIND_OPCODE_MASK; + ++p; + switch (opcode) { + case BIND_OPCODE_DONE: + done = true; + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + done = true; + break; + case BIND_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + addend = read_sleb128(p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segIndex = immediate; + segOffset = read_uleb128(p, end); + mh = (mach_header*)getIndexedMachHeader((unsigned)it.imageIndex); + if ( uintptr_t segPrefAddress = ImageLoaderMachO::segPreferredAddress(mh, segIndex) ) + address = segPrefAddress + segOffset + _slide; + else + dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large", segIndex); + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + address += read_uleb128(p, end); + break; + case BIND_OPCODE_DO_BIND: + ImageLoaderMachO::bindLocation(context, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak "); + boundSomething = true; + address += sizeof(intptr_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + ImageLoaderMachO::bindLocation(context, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak "); + boundSomething = true; + address += read_uleb128(p, end) + sizeof(intptr_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + ImageLoaderMachO::bindLocation(context, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak "); + boundSomething = true; + address += immediate*sizeof(intptr_t) + sizeof(intptr_t); + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(p, end); + skip = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + ImageLoaderMachO::bindLocation(context, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak "); + boundSomething = true; + address += skip + sizeof(intptr_t); + } + break; + default: + dyld::throwf("bad bind opcode %d in weak binding info", *p); + } + } + // C++ weak coalescing cannot be tracked by reference counting. Error on side of never unloading. + if ( boundSomething && (targetImage != this) ) + context.addDynamicReference(this, targetImage); +} + + +void ImageLoaderMegaDylib::appendImagesNeedingCoalescing(ImageLoader* images[], unsigned imageIndexes[], unsigned& count) +{ + for (unsigned i=0; i < _imageCount; ++i) { + uint16_t index = _bottomUpArray[i]; + if ( _stateFlags[index] == kStateUnused ) + continue; + if ( _imageExtras[index].weakBindingsSize == 0 ) + continue; + images[count] = this; + imageIndexes[count] = index; + ++count; + } +} + + +bool ImageLoaderMegaDylib::weakSymbolsBound(unsigned index) +{ + return ( _stateFlags[index] >= kStateFlagWeakBound ); +} + +void ImageLoaderMegaDylib::setWeakSymbolsBound(unsigned index) +{ + if ( _stateFlags[index] == kStateFlagBound ) + _stateFlags[index] = kStateFlagWeakBound; +} + + +void ImageLoaderMegaDylib::recursiveMarkLoaded(const LinkContext& context, unsigned imageIndex) +{ + if ( _stateFlags[imageIndex] != kStateUnused ) + return; + + const macho_header* mh = getIndexedMachHeader(imageIndex); + const char* path = getIndexedPath(imageIndex); + + if ( context.verboseLoading ) + dyld::log("dyld: loaded: %s\n", path); + if ( context.verboseMapping ) { + dyld::log("dyld: Using shared cached for %s\n", path); + printSegments(mh); + } + + // change state to "loaded" before recursing to break cycles + _stateFlags[imageIndex] = kStateLoaded; + ++fgImagesUsedFromSharedCache; + + dyld_image_info debuggerInfo; + debuggerInfo.imageLoadAddress = (mach_header*)mh; + debuggerInfo.imageFilePath = path; + debuggerInfo.imageFileModDate = 0; + addImagesToAllImages(1, &debuggerInfo); + + if ( _imageExtras[imageIndex].weakBindingsSize != 0 ) { + ++fgImagesRequiringCoalescing; + ++fgImagesHasWeakDefinitions; + } + + unsigned startArrayIndex = _imageExtras[imageIndex].dependentsStartArrayIndex; + for (int i=startArrayIndex; _dependenciesArray[i] != 0xFFFF; ++i) { + unsigned subDep = (_dependenciesArray[i] & 0x7FFF); // mask off upward bit + recursiveMarkLoaded(context, subDep); + } +} + +void ImageLoaderMegaDylib::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath) +{ + unsigned index = findImageIndex(context, loadPath); + recursiveMarkLoaded(context, index); +} + +unsigned int ImageLoaderMegaDylib::recursiveUpdateDepth(unsigned int maxDepth) +{ + setDepth(maxDepth); + return maxDepth; +} + + +const ImageLoader::Symbol* ImageLoaderMegaDylib::findExportedSymbol(const char* name, bool searchReExports, const char* thisPath, const ImageLoader** foundIn) const +{ + unsigned index; + if ( !hasDylib(thisPath, &index) ) + return NULL; + const uint8_t* exportNode; + const uint8_t* exportTrieEnd; + unsigned foundinIndex; + // always search re-exports + // the point of searchReExports was to break cycles in dylibs, we don't have cycles in cache, so ok to search deep + searchReExports = true; + if ( searchReExports ) { + if ( !exportTrieHasNodeRecursive(name, index, &exportNode, &exportTrieEnd, &foundinIndex) ) + return NULL; + } + else { + if ( !exportTrieHasNode(name, index, &exportNode, &exportTrieEnd) ) + return NULL; + } + *foundIn = this; + return (ImageLoader::Symbol*)exportNode; +} + +bool ImageLoaderMegaDylib::exportTrieHasNode(const char* symbolName, unsigned index, + const uint8_t** exportNode, const uint8_t** exportTrieEnd) const +{ + const uint8_t* start = (uint8_t*)(_imageExtras[index].exportsTrieAddr + _slide); + const uint32_t size = _imageExtras[index].exportsTrieSize; + if ( size == 0 ) + return false; + const uint8_t* end = start + size; + const uint8_t* node = ImageLoader::trieWalk(start, end, symbolName); + if ( node == NULL ) + return false; + *exportNode = node; + *exportTrieEnd = end; + return true; +} + +bool ImageLoaderMegaDylib::exportTrieHasNodeRecursive(const char* symbolName, unsigned index, + const uint8_t** exportNode, const uint8_t** exportTrieEnd, + unsigned* foundinIndex) const +{ + // look in trie for image index + if ( exportTrieHasNode(symbolName, index, exportNode, exportTrieEnd) ) { + *foundinIndex = index; + return true; + } + // recursively look in all re-exported tries + unsigned startArrayIndex = _imageExtras[index].reExportsStartArrayIndex; + for (int i=startArrayIndex; _reExportsArray[i] != 0xFFFF; ++i) { + unsigned reExIndex = _reExportsArray[i]; + if ( exportTrieHasNodeRecursive(symbolName, reExIndex, exportNode, exportTrieEnd, foundinIndex) ) + return true; + } + return false; +} + +bool ImageLoaderMegaDylib::findExportedSymbolAddress(const LinkContext& context, const char* symbolName, + const ImageLoader* requestorImage, int requestorOrdinalOfDef, + bool runResolver, const ImageLoader** foundIn, uintptr_t* address) const +{ + const char* definedImagePath = requestorImage->libPath(requestorOrdinalOfDef-1); + unsigned index = findImageIndex(context, definedImagePath); + *foundIn = this; + return findInChainedTries(context, symbolName, index, requestorImage, runResolver, address); +} + +uintptr_t ImageLoaderMegaDylib::getExportedSymbolAddress(const Symbol* sym, const LinkContext& context, + const ImageLoader* requestor, bool runResolver, const char* symbolName) const +{ + // scan for with trie contains this node + const uint8_t* exportTrieEnd = NULL; + unsigned imageIndex = 0xFFFF; + const macho_header* mh = NULL; + uint64_t unslidTrieNode = ((uintptr_t)sym) - _slide; + for (unsigned i=0; i < _imageCount; ++i) { + uint64_t start = _imageExtras[i].exportsTrieAddr; + uint64_t end = _imageExtras[i].exportsTrieAddr + _imageExtras[i].exportsTrieSize; + if ( (start < unslidTrieNode) && (unslidTrieNode < end) ) { + exportTrieEnd = (uint8_t*)(end + _slide); + imageIndex = i; + mh = (macho_header*)(_images[imageIndex].address + _slide); + break; + } + } + + if ( mh == NULL ) + dyld::throwf("getExportedSymbolAddress(Symbol=%p) not in a cache trie", sym); + + const uint8_t* exportNode = (const uint8_t*)sym; + uintptr_t address; + processExportNode(context, symbolName ? symbolName : "unknown", imageIndex, exportNode, exportTrieEnd, requestor, runResolver, &address); + return address; +} + +void ImageLoaderMegaDylib::processExportNode(const LinkContext& context, const char* symbolName, unsigned definedImageIndex, + const uint8_t* exportNode, const uint8_t* exportTrieEnd, + const ImageLoader* requestorImage, bool runResolver, uintptr_t* address) const +{ + const macho_header* mh = getIndexedMachHeader(definedImageIndex); + uintptr_t flags = read_uleb128(exportNode, exportTrieEnd); + uintptr_t rawAddress; + switch ( flags & EXPORT_SYMBOL_FLAGS_KIND_MASK ) { + case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: + if ( runResolver && (flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) ) { + // this node has a stub and resolver, run the resolver to get target address + uintptr_t stub = read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)mh; // skip over stub + // interposing dylibs have the stub address as their replacee + uintptr_t interposedStub = interposedAddress(context, stub, requestorImage); + if ( interposedStub != stub ) { + *address = interposedStub; + return; + } + // stub was not interposed, so run resolver + typedef uintptr_t (*ResolverProc)(void); + ResolverProc resolver = (ResolverProc)(read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)mh); + *address = (*resolver)(); + if ( context.verboseBind ) + dyld::log("dyld: resolver at %p returned 0x%08lX\n", resolver, *address); + return; + } + if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { + // re-export from another dylib, lookup there + const uintptr_t ordinal = read_uleb128(exportNode, exportTrieEnd); + const char* importedName = (char*)exportNode; + if ( importedName[0] == '\0' ) + importedName = symbolName; + unsigned startArrayIndex = _imageExtras[definedImageIndex].dependentsStartArrayIndex; + unsigned reExImageIndex = _dependenciesArray[startArrayIndex + ordinal-1] & 0x7FFF; + if ( findInChainedTries(context, importedName, reExImageIndex, requestorImage, runResolver, address) ) + return; + dyld::throwf("re-exported symbol '%s' not found for image %s expected re-exported in %s, node=%p", + symbolName, getIndexedShortName(definedImageIndex), getIndexedShortName(reExImageIndex), exportNode); + } + rawAddress = read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)mh; + *address = interposedAddress(context, rawAddress, requestorImage); + return; + case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: + if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) + dyld::throwf("unsupported exported symbol kind. flags=%lu at node=%p", flags, exportNode); + *address = read_uleb128(exportNode, exportTrieEnd) + (uintptr_t)mh; + return; + case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: + if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) + dyld::throwf("unsupported exported symbol kind. flags=%lu at node=%p", flags, exportNode); + *address = read_uleb128(exportNode, exportTrieEnd); + return; + default: + dyld::throwf("unsupported exported symbol kind. flags=%lu at node=%p", flags, exportNode); + } + dyld::throwf("unsupported exported symbol node=%p", exportNode); +} + +bool ImageLoaderMegaDylib::findInChainedTries(const LinkContext& context, const char* symbolName, unsigned definedImageIndex, + const ImageLoader* requestorImage, bool runResolver, uintptr_t* address) const +{ + //dyld::log("findInChainedTries(sym=%s, index=%u, path=%s)\n", symbolName, definedImageIndex, getIndexedPath(definedImageIndex)); + const uint8_t* exportNode; + const uint8_t* exportTrieEnd; + unsigned foundinIndex; + if ( !exportTrieHasNodeRecursive(symbolName, definedImageIndex, &exportNode, &exportTrieEnd, &foundinIndex) ) + return false; + + processExportNode(context, symbolName, foundinIndex, exportNode, exportTrieEnd, requestorImage, runResolver, address); + return true; +} + + +bool ImageLoaderMegaDylib::findInChainedTriesAndDependentsExcept(const LinkContext& context, const char* symbolName, unsigned imageIndex, + const ImageLoader* requestorImage, bool runResolver, bool alreadyVisited[], uintptr_t* address) const +{ + //dyld::log("findInChainedTriesAndDependentsExcept(sym=%s, index=%u, path=%s)\n", symbolName, imageIndex, getIndexedPath(imageIndex)); + if ( alreadyVisited[imageIndex] ) + return false; + alreadyVisited[imageIndex] = true; + + if ( findInChainedTries(context, symbolName, imageIndex, requestorImage, runResolver, address) ) + return true; + + unsigned startArrayIndex = _imageExtras[imageIndex].dependentsStartArrayIndex; + for (int i=startArrayIndex; _dependenciesArray[i] != 0xFFFF; ++i) { + // ignore upward links + if ( (_dependenciesArray[i] & 0x8000) == 0 ) { + unsigned depIndex = _dependenciesArray[i] & 0x7FFF; + if ( _stateFlags[depIndex] != kStateFlagInitialized ) + continue; + if ( findInChainedTriesAndDependentsExcept(context, symbolName, depIndex, requestorImage, runResolver, alreadyVisited, address) ) + return true; + } + } + return false; +} + +bool ImageLoaderMegaDylib::findInChainedTriesAndDependents(const LinkContext& context, const char* symbolName, unsigned definedImageIndex, + const ImageLoader* requestorImage, bool runResolver, uintptr_t* address) const +{ + //dyld::log("findInChainedTriesAndDependents(sym=%s, index=%u, path=%s)\n", symbolName, definedImageIndex, getIndexedPath(definedImageIndex)); + if ( findInChainedTries(context, symbolName, definedImageIndex, requestorImage, runResolver, address) ) + return true; + + bool alreadyVisited[_header->imagesCount]; + bzero(alreadyVisited, sizeof(alreadyVisited)); + return findInChainedTriesAndDependentsExcept(context, symbolName, definedImageIndex, requestorImage, runResolver, alreadyVisited, address); +} + + +bool ImageLoaderMegaDylib::flatFindSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image) +{ + // check export trie of all in-use images + for (unsigned i=0; i < _imageCount ; ++i) { + uint16_t imageIndex = _bottomUpArray[i]; + if ( _stateFlags[imageIndex] == kStateUnused ) + continue; + if ( onlyInCoalesced && (_imageExtras[imageIndex].weakBindingsSize == 0) ) + continue; + const uint8_t* exportNode; + const uint8_t* exportTrieEnd; + if ( exportTrieHasNode(name, imageIndex, &exportNode, &exportTrieEnd) ) { + *sym = (Symbol*)exportNode; + *image = this; + return true; + } + } + return false; +} + + +void ImageLoaderMegaDylib::markAllbound(const LinkContext& context) +{ + for (unsigned i=0; i < _imageCount; ++i) { + uint16_t imageIndex = _bottomUpArray[i]; + if ( _stateFlags[imageIndex] == kStateLoaded ) { + _stateFlags[imageIndex] = kStateFlagBound; + context.notifySingleFromCache(dyld_image_state_bound, (mach_header*)getIndexedMachHeader(imageIndex), getIndexedPath(imageIndex)); + } + } +} + + +void ImageLoaderMegaDylib::recursiveSpinLockAcquire(unsigned int imageIndex, mach_port_t thisThread) +{ + pthread_mutex_lock(&_lockArrayGuard); + if ( _lockArray == NULL ) + _lockArray = (recursive_lock*)calloc(_imageCount, sizeof(recursive_lock)); + _lockArrayInUseCount++; + pthread_mutex_unlock(&_lockArrayGuard); + + recursive_lock* imagesLock = &_lockArray[imageIndex]; + while ( !OSAtomicCompareAndSwap32Barrier(0, thisThread, (int*)&imagesLock->thread) ) { + if ( imagesLock->thread == thisThread ) + break; + } + imagesLock->count++; +} + +void ImageLoaderMegaDylib::recursiveSpinLockRelease(unsigned int imageIndex, mach_port_t thisThread) +{ + recursive_lock* imagesLock = &_lockArray[imageIndex]; + if ( --imagesLock->count == 0 ) + imagesLock->thread = 0; + + pthread_mutex_lock(&_lockArrayGuard); + _lockArrayInUseCount--; + if ( _lockArrayInUseCount == 0 ) { + free((void*)_lockArray); + _lockArray = NULL; + } + pthread_mutex_unlock(&_lockArrayGuard); +} + + +void ImageLoaderMegaDylib::recursiveInitialization(const LinkContext& context, mach_port_t thisThread, unsigned int imageIndex, + InitializerTimingList& timingInfo) +{ + // Don't do any locking until libSystem.dylib is initialized, so we can malloc() the lock array + bool useLock = dyld::gProcessInfo->libSystemInitialized; + if ( useLock ) + recursiveSpinLockAcquire(imageIndex, thisThread); + + // only run initializers if currently in bound state + if ( (_stateFlags[imageIndex] == kStateFlagBound) || (_stateFlags[imageIndex] == kStateFlagWeakBound) ) { + + // Each image in cache has its own lock. We only set the state to Initialized if we hold the lock for the image. + _stateFlags[imageIndex] = kStateFlagInitialized; + + // first recursively init all dependents + unsigned startArrayIndex = _imageExtras[imageIndex].dependentsStartArrayIndex; + for (int i=startArrayIndex; _dependenciesArray[i] != 0xFFFF; ++i) { + unsigned subDepIndex = _dependenciesArray[i]; + // ignore upward links + if ( (subDepIndex & 0x8000) == 0 ) + recursiveInitialization(context, thisThread, subDepIndex, timingInfo); + } + + // notify objc about this image + context.notifySingleFromCache(dyld_image_state_dependents_initialized, (mach_header*)getIndexedMachHeader(imageIndex), getIndexedPath(imageIndex)); + + // run all initializers for imageIndex + const dyld_cache_accelerator_initializer* pInitStart = _initializers; + const dyld_cache_accelerator_initializer* pInitEnd = &pInitStart[_initializerCount]; + bool ranSomeInitializers = false; + uint64_t t1 = mach_absolute_time(); + for (const dyld_cache_accelerator_initializer* p=pInitStart; p < pInitEnd; ++p) { + if ( p->imageIndex == imageIndex ) { + Initializer func = (Initializer)(p->functionOffset + (uintptr_t)_header); + if ( context.verboseInit ) + dyld::log("dyld: calling initializer function %p in %s\n", func, getIndexedPath(imageIndex)); + bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL); + func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL); + ranSomeInitializers = true; + if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) { + // now safe to use malloc() and other calls in libSystem.dylib + dyld::gProcessInfo->libSystemInitialized = true; + } + } + } + if ( ranSomeInitializers ) { + uint64_t t2 = mach_absolute_time(); + const char* shortName = strrchr(getIndexedPath(imageIndex), '/'); + if ( shortName == NULL ) + shortName = getIndexedPath(imageIndex); + else + ++shortName; + timingInfo.images[timingInfo.count].shortName = shortName; + timingInfo.images[timingInfo.count].initTime = (t2-t1); + timingInfo.count++; + } + } + + // only unlock if this frame locked (note: libSystemInitialized changes after libSystem's initializer is run) + if ( useLock ) + recursiveSpinLockRelease(imageIndex, thisThread); +} + + +void ImageLoaderMegaDylib::recursiveInitialization(const LinkContext& context, mach_port_t thisThread, const char* pathToInitialize, + InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) +{ + unsigned imageIndex; + if ( hasDylib(pathToInitialize, &imageIndex) ) { + this->recursiveInitialization(context, thisThread, imageIndex, timingInfo); + } +} + +void ImageLoaderMegaDylib::recursiveBind(const LinkContext& context, bool forceLazysBound, bool neverUnload) +{ + markAllbound(context); +} + +uint8_t ImageLoaderMegaDylib::dyldStateToCacheState(dyld_image_states state) { + switch (state) { + case dyld_image_state_mapped: + case dyld_image_state_dependents_mapped: + return kStateLoaded; + case dyld_image_state_bound: + return kStateFlagBound; + case dyld_image_state_initialized: + return kStateFlagInitialized; + case dyld_image_state_rebased: + case dyld_image_state_dependents_initialized: + case dyld_image_state_terminated: + return kStateUnused; + } + return kStateUnused; +} + +void ImageLoaderMegaDylib::recursiveApplyInterposing(const LinkContext& context) +{ + if ( context.verboseInterposing ) + dyld::log("dyld: interposing %lu tuples onto shared cache\n", fgInterposingTuples.size()); + + +} + +unsigned ImageLoaderMegaDylib::appendImagesToNotify(dyld_image_states state, bool orLater, dyld_image_info* infos) +{ + uint8_t targetCacheState = dyldStateToCacheState(state); + if ( targetCacheState == kStateUnused ) + return 0; + + unsigned usedCount = 0; + for (int i=_imageCount-1; i > 0; --i) { + uint16_t index = _bottomUpArray[i]; + uint8_t imageState = _stateFlags[index]; + if ( imageState == kStateFlagWeakBound ) + imageState = kStateFlagBound; + if ( (imageState == targetCacheState) || (orLater && (imageState > targetCacheState)) ) { + infos[usedCount].imageLoadAddress = (mach_header*)getIndexedMachHeader(index); + infos[usedCount].imageFilePath = getIndexedPath(index); + infos[usedCount].imageFileModDate = 0; + ++usedCount; + } + } + return usedCount; +} + + +bool ImageLoaderMegaDylib::dlopenFromCache(const LinkContext& context, const char* path, int mode, void** handle) +{ + unsigned imageIndex; + if ( !hasDylib(path, &imageIndex) ) { + return false; + } + + // RTLD_NOLOAD means return handle if already loaded, but don't now load it + if ( mode & RTLD_NOLOAD ) { + dyld::gLibSystemHelpers->releaseGlobalDyldLock(); + if ( _stateFlags[imageIndex] == kStateUnused ) { + *handle = NULL; + return true; + } + } + else { + this->recursiveMarkLoaded(context, imageIndex); + context.notifyBatch(dyld_image_state_dependents_mapped, false); + this->markAllbound(context); + context.notifyBatch(dyld_image_state_bound, false); + + this->weakBind(context); + + // Release dyld global lock before running initializers in dlopen() with customer cache + dyld::gLibSystemHelpers->releaseGlobalDyldLock(); + + InitializerTimingList timingInfo[_initializerCount]; + timingInfo[0].count = 0; + mach_port_t thisThread = mach_thread_self(); + this->recursiveInitialization(context, thisThread, imageIndex, timingInfo[0]); + mach_port_deallocate(mach_task_self(), thisThread); + context.notifyBatch(dyld_image_state_initialized, false); + } + + *handle = makeCacheHandle(imageIndex, mode); + return true; +} + +bool ImageLoaderMegaDylib::makeCacheHandle(const LinkContext& context, unsigned cacheIndex, int mode, void** result) +{ + if ( cacheIndex >= _imageCount ) + return false; + + *result = makeCacheHandle(cacheIndex, mode); + return true; +} + +void* ImageLoaderMegaDylib::makeCacheHandle(unsigned index, int mode) +{ + uint8_t flags = ((mode & RTLD_FIRST) ? 1 : 0); + +#if __LP64__ + return (void*)(uintptr_t)( 0xFFEEDDCC00000000LL | (index << 8) | flags); +#else + return (void*)(uintptr_t)( 0xF8000000 | (index << 8) | flags); +#endif +} + +bool ImageLoaderMegaDylib::isCacheHandle(void* handle, unsigned* index, uint8_t* flags) +{ +#if __LP64__ + if ( (((uintptr_t)handle) >> 32) == 0xFFEEDDCC ) { + if ( index ) + *index = (((uintptr_t)handle) >> 8) & 0xFFFF; + if ( flags ) + *flags = ((uintptr_t)handle) & 0xFF; + return true; + } +#else + if ( (((uintptr_t)handle) >> 24) == 0xF8 ) { + if ( index ) + *index = (((uintptr_t)handle) >> 8) & 0xFFFF; + if ( flags ) + *flags = ((uintptr_t)handle) & 0xFF; + return true; + } +#endif + return false; +} + + +void* ImageLoaderMegaDylib::dlsymFromCache(const LinkContext& context, void* handle, const char* symbolName, unsigned imageIndex) +{ + unsigned indexInHandle; + uint8_t flags; + uintptr_t symAddress; + if ( handle == RTLD_SELF ) { + if ( findInChainedTriesAndDependents(context, symbolName, imageIndex, NULL, true, &symAddress) ) + return (void*)symAddress; + } + else if ( handle == RTLD_NEXT ) { + // FIXME: really need to not look in imageIndex, but look in others. + if ( findInChainedTriesAndDependents(context, symbolName, imageIndex, NULL, true, &symAddress) ) + return (void*)symAddress; + } + else if ( isCacheHandle(handle, &indexInHandle, &flags) ) { + bool searchOnlyFirst = (flags & 1); // RTLD_FIRST + // normal dlsym(handle,) semantics is that the handle is just the first place to search. RTLD_FIRST disables that + if ( searchOnlyFirst ) { + if ( findInChainedTries(context, symbolName, indexInHandle, NULL, true, &symAddress) ) + return (void*)symAddress; + } + else { + if ( findInChainedTriesAndDependents(context, symbolName, indexInHandle, NULL, true, &symAddress) ) + return (void*)symAddress; + } + } + + return NULL; +} + +bool ImageLoaderMegaDylib::dladdrFromCache(const void* address, Dl_info* info) +{ + const mach_header* mh; + unsigned index; + if ( !addressInCache(address, &mh, &info->dli_fname, &index) ) + return false; + + info->dli_fbase = (void*)mh; + if ( address == mh ) { + // special case lookup of header + info->dli_sname = "__dso_handle"; + info->dli_saddr = info->dli_fbase; + return true; + } + + // find closest symbol in the image + info->dli_sname = ImageLoaderMachO::findClosestSymbol(mh, address, (const void**)&info->dli_saddr); + + // never return the mach_header symbol + if ( info->dli_saddr == info->dli_fbase ) { + info->dli_sname = NULL; + info->dli_saddr = NULL; + return true; + } + + // strip off leading underscore + if ( info->dli_sname != NULL ) { + if ( info->dli_sname[0] == '_' ) + info->dli_sname = info->dli_sname +1; + } + return true; +} + + +uintptr_t ImageLoaderMegaDylib::bindLazy(uintptr_t lazyBindingInfoOffset, const LinkContext& context, const mach_header* mh, unsigned imageIndex) +{ + const dyld_info_command* dyldInfoCmd = ImageLoaderMachO::findDyldInfoLoadCommand(mh); + if ( dyldInfoCmd == NULL ) + return 0; + + const uint8_t* const lazyInfoStart = &_linkEditBias[dyldInfoCmd->lazy_bind_off]; + const uint8_t* const lazyInfoEnd = &lazyInfoStart[dyldInfoCmd->lazy_bind_size]; + uint32_t lbOffset = (uint32_t)lazyBindingInfoOffset; + uint8_t segIndex; + uintptr_t segOffset; + int libraryOrdinal; + const char* symbolName; + bool doneAfterBind; + if ( ImageLoaderMachO::getLazyBindingInfo(lbOffset, lazyInfoStart, lazyInfoEnd, &segIndex, &segOffset, &libraryOrdinal, &symbolName, &doneAfterBind) ) { + //const char* thisPath = getIndexedPath(imageIndex); + //dyld::log("%s needs symbol '%s' from ordinal=%d\n", thisPath, symbolName, libraryOrdinal); + unsigned startDepArrayIndex = _imageExtras[imageIndex].dependentsStartArrayIndex; + unsigned targetIndex; + if ( libraryOrdinal == BIND_SPECIAL_DYLIB_SELF ) + targetIndex = imageIndex; + else + targetIndex = _dependenciesArray[startDepArrayIndex+libraryOrdinal-1] & 0x7FFF; + //const char* targetPath = getIndexedPath(targetIndex); + //dyld::log("%s needs symbol '%s' from %s\n", thisPath, symbolName, targetPath); + uintptr_t targetAddress; + if ( findInChainedTries(context, symbolName, targetIndex, this, true, &targetAddress) ) { + if ( uintptr_t segPrefAddress = ImageLoaderMachO::segPreferredAddress(mh, segIndex) ) { + uintptr_t* lp = (uintptr_t*)(segPrefAddress + segOffset + _slide); + //dyld::log(" storing 0x%0lX to lp %p\n", targetAddress, lp); + *lp = targetAddress; + return targetAddress; + } + } + } + + return 0; +} + + +#endif // SUPPORT_ACCELERATE_TABLES + + + diff --git a/src/ImageLoaderMegaDylib.h b/src/ImageLoaderMegaDylib.h new file mode 100644 index 0000000..425ec8e --- /dev/null +++ b/src/ImageLoaderMegaDylib.h @@ -0,0 +1,255 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +#ifndef __IMAGELOADER_MEGADYLIB__ +#define __IMAGELOADER_MEGADYLIB__ + +#include +#include + +#include "ImageLoaderMachO.h" +#include "dyld_cache_format.h" + + +// +// ImageLoaderMegaDylib is the concrete subclass of ImageLoader which represents +// all dylibs in the shared cache. +// +class ImageLoaderMegaDylib : public ImageLoader { +public: + static ImageLoaderMegaDylib* makeImageLoaderMegaDylib(const dyld_cache_header*, long slide, const LinkContext&); + + + virtual ~ImageLoaderMegaDylib(); + + void appendImagesNeedingCoalescing(ImageLoader* images[], unsigned imageIndex[], unsigned& count); + virtual void initializeCoalIterator(CoalIterator&, unsigned int loadOrder, unsigned imageIndex); + virtual bool incrementCoalIterator(CoalIterator&); + virtual uintptr_t getAddressCoalIterator(CoalIterator&, const LinkContext& contex); + virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, unsigned imageIndex, const LinkContext& context); + + virtual const char* getIndexedPath(unsigned index) const; + virtual const char* getIndexedShortName(unsigned) const; + virtual const char* getInstallPath() const; + virtual bool inSharedCache() const { return true; } + virtual bool containsSymbol(const void* addr) const { unreachable(); } + virtual void* getThreadPC() const { unreachable(); } + virtual void* getMain() const { unreachable(); } + virtual const struct mach_header* machHeader() const { unreachable(); } + virtual uintptr_t getSlide() const { return _slide; } + virtual const void* getEnd() const { unreachable(); } + virtual bool hasCoalescedExports() const { unreachable(); } + virtual bool findExportedSymbolAddress(const LinkContext& context, const char* symbolName, + const ImageLoader* requestorImage, int requestorOrdinalOfDef, + bool runResolver, const ImageLoader** foundIn, uintptr_t* address) const; + virtual const Symbol* findExportedSymbol(const char* name, bool searchReExports, const char* thisPath, const ImageLoader** foundIn) const; + virtual uintptr_t getExportedSymbolAddress(const Symbol* sym, const LinkContext& context, + const ImageLoader* requestor, bool runResolver, const char* symbolName) const; + virtual DefinitionFlags getExportedSymbolInfo(const Symbol* sym) const { unreachable(); } + virtual const char* getExportedSymbolName(const Symbol* sym) const { unreachable(); } + virtual uint32_t getExportedSymbolCount() const { unreachable(); } + virtual const Symbol* getIndexedExportedSymbol(uint32_t index) const { unreachable(); } + + virtual uint32_t getImportedSymbolCount() const { unreachable(); } + virtual const Symbol* getIndexedImportedSymbol(uint32_t index) const { unreachable(); } + virtual ReferenceFlags getImportedSymbolInfo(const Symbol* sym) const { unreachable(); } + virtual const char* getImportedSymbolName(const Symbol* sym) const { unreachable(); } + virtual const char* findClosestSymbol(const void* addr, const void** closestAddr) const { unreachable(); } + virtual bool isBundle() const { return false; } + virtual bool isDylib() const { return true; } + virtual bool isExecutable() const { unreachable(); } + virtual bool isPositionIndependentExecutable() const { unreachable(); } + virtual bool forceFlat() const { unreachable(); } + virtual uintptr_t doBindLazySymbol(uintptr_t* lazyPointer, const LinkContext& context) { unreachable(); } + virtual uintptr_t doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context, + void (*lock)(), void (*unlock)()) { unreachable(); } + virtual void doTermination(const LinkContext& context) { unreachable(); } + virtual bool needsInitialization() { unreachable(); } + virtual bool getSectionContent(const char* segmentName, const char* sectionName, void** start, size_t* length) { unreachable(); } + virtual void getUnwindInfo(dyld_unwind_sections* info) { unreachable(); } + virtual bool findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) { unreachable(); } + virtual bool isPrebindable() const { unreachable(); } + virtual bool usablePrebinding(const LinkContext& context) const { unreachable(); } + virtual void getRPaths(const LinkContext& context, std::vector&) const { } + virtual bool participatesInCoalescing() const { unreachable(); } + virtual bool getUUID(uuid_t) const { unreachable(); } + virtual void dynamicInterpose(const LinkContext& context) { unreachable(); } + void addDynamicInterposingTuples(const struct dyld_interpose_tuple array[], size_t count) { unreachable(); } + virtual unsigned int segmentCount() const { unreachable(); } + virtual const char* segName(unsigned int) const { unreachable(); } + virtual uintptr_t segSize(unsigned int) const { unreachable(); } + virtual uintptr_t segFileSize(unsigned int) const { unreachable(); } + virtual bool segHasTrailingZeroFill(unsigned int) { unreachable(); } + virtual uintptr_t segFileOffset(unsigned int) const { unreachable(); } + virtual bool segReadable(unsigned int) const { unreachable(); } + virtual bool segWriteable(unsigned int) const { unreachable(); } + virtual bool segExecutable(unsigned int) const { unreachable(); } + virtual bool segUnaccessible(unsigned int) const { unreachable(); } + virtual bool segHasPreferredLoadAddress(unsigned int) const { unreachable(); } + virtual uintptr_t segPreferredLoadAddress(unsigned int) const { unreachable(); } + virtual uintptr_t segActualLoadAddress(unsigned int) const { unreachable(); } + virtual uintptr_t segActualEndAddress(unsigned int) const { unreachable(); } + + + // info from LC_VERSION_MIN_MACOSX or LC_VERSION_MIN_IPHONEOS + virtual uint32_t sdkVersion() const { unreachable(); } + virtual uint32_t minOSVersion() const { unreachable(); } + + // if the image contains interposing functions, register them + virtual void registerInterposing() { unreachable(); } + + virtual ImageLoader* libImage(unsigned int) const { unreachable(); } + virtual bool libReExported(unsigned int) const { unreachable(); } + virtual bool libIsUpward(unsigned int) const { unreachable(); } + virtual void setLibImage(unsigned int, ImageLoader*, bool, bool) { unreachable(); } + virtual const char* libPath(unsigned int) const { unreachable(); } + + unsigned appendImagesToNotify(dyld_image_states state, bool orLater, dyld_image_info* infos); + const char* notify(dyld_image_states state, bool orLater, dyld_image_state_change_handler); + bool dlopenFromCache(const LinkContext& context, const char* path, int mode, void** handle); + bool makeCacheHandle(const LinkContext& context, unsigned cacheIndex, int mode, void** result); + void* dlsymFromCache(const LinkContext& context, void* handle, const char* symName, unsigned index); + bool isCacheHandle(void* handle, unsigned* index, uint8_t* flags); + bool hasDylib(const char* path, unsigned* index) const; + bool addressInCache(const void* address, const mach_header** mh, const char** path, unsigned* index); + bool findUnwindSections(const void* addr, dyld_unwind_sections* info); + bool dladdrFromCache(const void* address, Dl_info* info); + uintptr_t bindLazy(uintptr_t lazyBindingInfoOffset, const LinkContext& context, const mach_header* mh, unsigned index); + bool flatFindSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image); + void getDylibUUID(unsigned int index, uuid_t) const; + +protected: + virtual void setDyldInfo(const dyld_info_command* dyldInfo) { unreachable(); } + virtual void setSymbolTableInfo(const macho_nlist*, const char*, const dysymtab_command*) { unreachable(); } + virtual uint32_t* segmentCommandOffsets() const { unreachable(); } + virtual void rebase(const LinkContext& context, uintptr_t slide) { unreachable(); } + virtual uintptr_t exportedSymbolAddress(const LinkContext& context, const Symbol* symbol, const ImageLoader* requestor, bool runResolver) const { unreachable(); } + virtual bool exportedSymbolIsWeakDefintion(const Symbol* symbol) const { unreachable(); } + virtual const char* exportedSymbolName(const Symbol* symbol) const { unreachable(); } + virtual unsigned int exportedSymbolCount() const { unreachable(); } + virtual const ImageLoader::Symbol* exportedSymbolIndexed(unsigned int) const { unreachable(); } + virtual unsigned int importedSymbolCount() const { unreachable(); } + virtual const ImageLoader::Symbol* importedSymbolIndexed(unsigned int) const { unreachable(); } + virtual const char* importedSymbolName(const Symbol* symbol) const { unreachable(); } +#if PREBOUND_IMAGE_SUPPORT + virtual void resetPreboundLazyPointers(const LinkContext& context) { unreachable(); } +#endif + + virtual void recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath); + virtual unsigned recursiveUpdateDepth(unsigned int maxDepth); + virtual void recursiveRebase(const LinkContext& context) { } + virtual void recursiveBind(const LinkContext& context, bool forceLazysBound, bool neverUnload); + virtual void recursiveApplyInterposing(const LinkContext& context); + virtual void recursiveGetDOFSections(const LinkContext& context, std::vector& dofs) { } + virtual void recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, + ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&); + + virtual void doGetDependentLibraries(DependentLibraryInfo libs[]) { unreachable(); } + virtual LibraryInfo doGetLibraryInfo(const LibraryInfo& requestorInfo) { return requestorInfo; } + virtual void doRebase(const LinkContext& context) { unreachable(); } + virtual void doBind(const LinkContext& context, bool forceLazysBound) { unreachable(); } + virtual void doBindJustLazies(const LinkContext& context) { unreachable(); } + virtual void doGetDOFSections(const LinkContext& context, std::vector& dofs) { unreachable(); } + virtual void doInterpose(const LinkContext& context) { unreachable(); } + virtual bool doInitialization(const LinkContext& context) { unreachable(); } + virtual bool needsTermination() { unreachable(); } + virtual bool segmentsMustSlideTogether() const { unreachable(); } + virtual bool segmentsCanSlide() const { unreachable(); } + virtual void setSlide(intptr_t slide) { unreachable(); } + bool allDependentLibrariesAsWhenPreBound() const { unreachable(); } + virtual bool isSubframeworkOf(const LinkContext& context, const ImageLoader* image) const { return false; } + virtual bool hasSubLibrary(const LinkContext& context, const ImageLoader* child) const { return false; } + virtual bool weakSymbolsBound(unsigned index); + virtual void setWeakSymbolsBound(unsigned index); + +private: + ImageLoaderMegaDylib(const dyld_cache_header*, long slide, const LinkContext&); + + const macho_header* getIndexedMachHeader(unsigned index) const; + const uint8_t* getIndexedTrie(unsigned index, uint32_t& trieSize) const; + unsigned findImageIndex(const LinkContext& context, const char* path) const; + void recursiveMarkLoaded(const LinkContext& context, unsigned imageIndex); + void markAllbound(const LinkContext& context); + bool findInChainedTries(const LinkContext& context, const char* symbolName, unsigned definedImageIndex, + const ImageLoader* requestorImage, bool runResolver, uintptr_t* address) const; + bool findInChainedTriesAndDependents(const LinkContext& context, const char* symbolName, unsigned definedImageIndex, + const ImageLoader* requestorImage, bool runResolver, uintptr_t* address) const; + bool findInChainedTriesAndDependentsExcept(const LinkContext& context, const char* symbolName, unsigned imageIndex, + const ImageLoader* requestorImage, bool runResolver, bool alreadyVisited[], uintptr_t* address) const; + bool exportTrieHasNodeRecursive(const char* symbolName, unsigned index, + const uint8_t** exportNode, const uint8_t** exportTrieEnd, + unsigned* foundinIndex) const; + bool exportTrieHasNode(const char* symbolName, unsigned index, + const uint8_t** exportNode, const uint8_t** exportTrieEnd) const; + + void initAllLoaded(const LinkContext& context, InitializerTimingList& timingInfo); + void printSegments(const macho_header* mh) const; + void* makeCacheHandle(unsigned index, int mode); + uint8_t flagsFromCacheHandle(void* handle); + void processExportNode(const LinkContext& context, const char* symbolName, unsigned definedImageIndex, + const uint8_t* exportNode, const uint8_t* exportTrieEnd, + const ImageLoader* requestorImage, bool runResolver, uintptr_t* address) const; + static uint8_t dyldStateToCacheState(dyld_image_states state); + void recursiveInitialization(const LinkContext& context, mach_port_t this_thread, unsigned int imageIndex, + InitializerTimingList& timingInfo); + void recursiveSpinLockAcquire(unsigned int imageIndex, mach_port_t thisThread); + void recursiveSpinLockRelease(unsigned int imageIndex, mach_port_t thisThread); + + __attribute__((noreturn)) + void unreachable() const; + + enum { kStateUnused=0, kStateLoaded=1, kStateFlagBound=2, kStateFlagWeakBound=3, kStateFlagInitialized=4 }; + + + const dyld_cache_header* _header; + const void* _endOfCacheInMemory; + const uint8_t* _linkEditBias; + const dyld_cache_image_info* _images; + const dyld_cache_image_info_extra* _imageExtras; + long _slide; + const dyld_cache_accelerator_initializer* _initializers; + const dyld_cache_range_entry* _rangeTable; + const uint16_t* _reExportsArray; + const uint16_t* _dependenciesArray; + const uint16_t* _bottomUpArray; + const uint8_t* _dylibsTrieStart; + const uint8_t* _dylibsTrieEnd; + const dyld_cache_image_text_info* _imageTextInfo; + uint8_t* _stateFlags; + uint32_t _imageCount; + uint32_t _initializerCount; + uint32_t _rangeTableCount; + pthread_mutex_t _lockArrayGuard; + ImageLoader::recursive_lock* _lockArray; + unsigned int _lockArrayInUseCount; +}; + + + +#endif // __IMAGELOADER_MEGADYLIB__ + + + + diff --git a/src/dyld.cpp b/src/dyld.cpp index 8fb0f24..cda4944 100644 --- a/src/dyld.cpp +++ b/src/dyld.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include // mach_absolute_time() #include @@ -53,7 +55,13 @@ #include #include <_simple.h> #include +#include +#include +#include +#include +#include +#include #ifndef CPU_SUBTYPE_ARM_V5TEJ #define CPU_SUBTYPE_ARM_V5TEJ ((cpu_subtype_t) 7) @@ -94,10 +102,10 @@ #include "ImageLoader.h" #include "ImageLoaderMachO.h" #include "dyldLibSystemInterface.h" -#include "dyldSyscallInterface.h" #if DYLD_SHARED_CACHE_SUPPORT #include "dyld_cache_format.h" #endif +#include "dyld_process_info_internal.h" #include #if TARGET_IPHONE_SIMULATOR extern "C" void xcoresymbolication_load_notifier(void *connection, uint64_t load_timestamp, const char *image_path, const struct mach_header *mach_header); @@ -106,6 +114,17 @@ #define coresymbolication_unload_notifier(c, t, p, h) xcoresymbolication_unload_notifier(c, t, p, h) #endif +#if SUPPORT_ACCELERATE_TABLES + #include "ImageLoaderMegaDylib.h" +#endif + +#if TARGET_IPHONE_SIMULATOR + extern "C" void* gSyscallHelpers; +#else + #include "dyldSyscallInterface.h" +#endif + + // not libc header for send() syscall interface extern "C" ssize_t __sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); @@ -116,11 +135,13 @@ extern "C" ssize_t __sendto(int, const void *, size_t, int, const struct sockadd #if __LP64__ #define LC_SEGMENT_COMMAND LC_SEGMENT_64 #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT + #define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO #define macho_segment_command segment_command_64 #define macho_section section_64 #else #define LC_SEGMENT_COMMAND LC_SEGMENT #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64 + #define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO_64 #define macho_segment_command segment_command #define macho_section section #endif @@ -131,18 +152,17 @@ extern "C" ssize_t __sendto(int, const void *, size_t, int, const struct sockadd /* implemented in dyld_gdb.cpp */ +extern void resetAllImages(); extern void addImagesToAllImages(uint32_t infoCount, const dyld_image_info info[]); extern void removeImageFromAllImages(const mach_header* mh); -extern void setAlImageInfosHalt(const char* message, uintptr_t flags); extern void addNonSharedCacheImageUUID(const dyld_uuid_info& info); extern const char* notifyGDB(enum dyld_image_states state, uint32_t infoCount, const dyld_image_info info[]); +extern size_t allImagesCount(); // magic so CrashReporter logs message extern "C" { char error_string[1024]; } -// implemented in dyldStartup.s for CrashReporter -extern "C" void dyld_fatal_error(const char* errString) __attribute__((noreturn)); // magic linker symbol for start of dyld binary extern "C" const macho_header __dso_handle; @@ -185,10 +205,10 @@ struct EnvironmentVariables { const char* const * LD_LIBRARY_PATH; // for unix conformance const char* const * DYLD_VERSIONED_LIBRARY_PATH; const char* const * DYLD_VERSIONED_FRAMEWORK_PATH; - bool DYLD_PRINT_LIBRARIES; bool DYLD_PRINT_LIBRARIES_POST_LAUNCH; bool DYLD_BIND_AT_LAUNCH; bool DYLD_PRINT_STATISTICS; + bool DYLD_PRINT_STATISTICS_DETAILS; bool DYLD_PRINT_OPTS; bool DYLD_PRINT_ENV; bool DYLD_DISABLE_DOFS; @@ -214,6 +234,7 @@ struct EnvironmentVariables { // DYLD_PRINT_WARNINGS ==> gLinkContext.verboseWarnings // DYLD_PRINT_RPATHS ==> gLinkContext.verboseRPaths // DYLD_PRINT_INTERPOSING ==> gLinkContext.verboseInterposing + // DYLD_PRINT_LIBRARIES ==> gLinkContext.verboseLoading }; @@ -221,7 +242,7 @@ struct EnvironmentVariables { typedef std::vector StateHandlers; -enum RestrictedReason { restrictedNot, restrictedBySetGUid, restrictedBySegment, restrictedByEntitlements }; +enum EnvVarMode { envNone, envPrintOnly, envAll }; // all global state static const char* sExecPath = NULL; @@ -231,10 +252,8 @@ static const macho_header* sMainExecutableMachHeader = NULL; static cpu_type_t sHostCPU; static cpu_subtype_t sHostCPUsubtype; #endif -static ImageLoader* sMainExecutable = NULL; -static bool sProcessIsRestricted = false; -static bool sProcessRequiresLibraryValidation = false; -static RestrictedReason sRestrictedReason = restrictedNot; +static ImageLoaderMachO* sMainExecutable = NULL; +static EnvVarMode sEnvMode = envNone; static size_t sInsertedDylibCount = 0; static std::vector sAllImages; static std::vector sImageRoots; @@ -254,6 +273,8 @@ static const char* sLibraryFallbackPaths[] = { "$HOME/lib", "/usr/local/lib" static const char* sFrameworkFallbackPaths[] = { "/System/Library/Frameworks", NULL }; static const char* sLibraryFallbackPaths[] = { "/usr/local/lib", "/usr/lib", NULL }; #endif +static const char* sRestrictedFrameworkFallbackPaths[] = { "/System/Library/Frameworks", NULL }; +static const char* sRestrictedLibraryFallbackPaths[] = { "/usr/lib", NULL }; static UndefinedHandler sUndefinedHandler = NULL; static ImageLoader* sBundleBeingLoaded = NULL; // hack until OFI is reworked #if DYLD_SHARED_CACHE_SUPPORT @@ -263,7 +284,6 @@ static bool sSharedCacheIgnoreInodeAndTimeStamp = false; bool gSharedCacheOverridden = false; #if __IPHONE_OS_VERSION_MIN_REQUIRED static const char* sSharedCacheDir = IPHONE_DYLD_SHARED_CACHE_DIR; - static bool sDylibsOverrideCache = false; #define ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE 1024 #else static const char* sSharedCacheDir = MACOSX_DYLD_SHARED_CACHE_DIR; @@ -271,6 +291,9 @@ static bool sSharedCacheIgnoreInodeAndTimeStamp = false; #endif ImageLoader::LinkContext gLinkContext; bool gLogAPIs = false; +#if SUPPORT_ACCELERATE_TABLES +bool gLogAppAPIs = false; +#endif const struct LibSystemHelpers* gLibSystemHelpers = NULL; #if SUPPORT_OLD_CRT_INITIALIZATION bool gRunInitializersOldWay = false; @@ -280,14 +303,31 @@ static std::vector sDylibOverrides; static int sLogSocket = -1; #endif static bool sFrameworksFoundAsDylibs = false; -#if __x86_64__ +#if __x86_64__ && DYLD_SHARED_CACHE_SUPPORT static bool sHaswell = false; #endif static std::vector sDynamicReferences; static OSSpinLock sDynamicReferencesLock = 0; +#if !TARGET_IPHONE_SIMULATOR static bool sLogToFile = false; +#endif static char sLoadingCrashMessage[1024] = "dyld: launch, loading dependent libraries"; +static _dyld_objc_notify_mapped sNotifyObjCMapped; +static _dyld_objc_notify_init sNotifyObjCInit; +static _dyld_objc_notify_unmapped sNotifyObjCUnmapped; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR +static bool sForceStderr = false; +#endif + + + +#if SUPPORT_ACCELERATE_TABLES +static ImageLoaderMegaDylib* sAllCacheImagesProxy = NULL; +static bool sDisableAcceleratorTables = false; +#endif + // // The MappedRanges structure is used for fast address->image lookups. // The table is only updated when the dyld lock is held, so we don't @@ -299,22 +339,22 @@ static char sLoadingCrashMessage[1024] = "dyld: launch, loading dependent // struct MappedRanges { - enum { count=400 }; + MappedRanges* next; + unsigned long count; struct { ImageLoader* image; uintptr_t start; uintptr_t end; - } array[count]; - MappedRanges* next; + } array[1]; }; -static MappedRanges sMappedRangesStart; +static MappedRanges* sMappedRangesStart; void addMappedRange(ImageLoader* image, uintptr_t start, uintptr_t end) { //dyld::log("addMappedRange(0x%lX->0x%lX) for %s\n", start, end, image->getShortName()); - for (MappedRanges* p = &sMappedRangesStart; p != NULL; p = p->next) { - for (int i=0; i < MappedRanges::count; ++i) { + for (MappedRanges* p = sMappedRangesStart; p != NULL; p = p->next) { + for (unsigned long i=0; i < p->count; ++i) { if ( p->array[i].image == NULL ) { p->array[i].start = start; p->array[i].end = end; @@ -326,24 +366,37 @@ void addMappedRange(ImageLoader* image, uintptr_t start, uintptr_t end) } } // table must be full, chain another - MappedRanges* newRanges = (MappedRanges*)malloc(sizeof(MappedRanges)); - bzero(newRanges, sizeof(MappedRanges)); +#if SUPPORT_ACCELERATE_TABLES + unsigned count = (sAllCacheImagesProxy != NULL) ? 16 : 400; +#else + unsigned count = 400; +#endif + size_t allocationSize = sizeof(MappedRanges) + (count-1)*3*sizeof(void*); + MappedRanges* newRanges = (MappedRanges*)malloc(allocationSize); + bzero(newRanges, allocationSize); + newRanges->count = count; newRanges->array[0].start = start; newRanges->array[0].end = end; newRanges->array[0].image = image; - for (MappedRanges* p = &sMappedRangesStart; p != NULL; p = p->next) { - if ( p->next == NULL ) { - OSMemoryBarrier(); - p->next = newRanges; - break; + OSMemoryBarrier(); + if ( sMappedRangesStart == NULL ) { + sMappedRangesStart = newRanges; + } + else { + for (MappedRanges* p = sMappedRangesStart; p != NULL; p = p->next) { + if ( p->next == NULL ) { + OSMemoryBarrier(); + p->next = newRanges; + break; + } } } } void removedMappedRanges(ImageLoader* image) { - for (MappedRanges* p = &sMappedRangesStart; p != NULL; p = p->next) { - for (int i=0; i < MappedRanges::count; ++i) { + for (MappedRanges* p = sMappedRangesStart; p != NULL; p = p->next) { + for (unsigned long i=0; i < p->count; ++i) { if ( p->array[i].image == image ) { // clear with a barrier so that any reader will see consistent records OSMemoryBarrier(); @@ -355,8 +408,8 @@ void removedMappedRanges(ImageLoader* image) ImageLoader* findMappedRange(uintptr_t target) { - for (MappedRanges* p = &sMappedRangesStart; p != NULL; p = p->next) { - for (int i=0; i < MappedRanges::count; ++i) { + for (MappedRanges* p = sMappedRangesStart; p != NULL; p = p->next) { + for (unsigned long i=0; i < p->count; ++i) { if ( p->array[i].image != NULL ) { if ( (p->array[i].start <= target) && (target < p->array[i].end) ) return p->array[i].image; @@ -406,44 +459,22 @@ void throwf(const char* format, ...) static int sLogfile = STDERR_FILENO; #endif -#if LOG_BINDINGS -static int sBindingsLogfile = -1; -static void mysprintf(char* dst, const char* format, ...) -{ - _SIMPLE_STRING buf = _simple_salloc(); - if ( buf != NULL ) { - va_list list; - va_start(list, format); - _simple_vsprintf(buf, format, list); - va_end(list); - strcpy(dst, _simple_string(buf)); - _simple_sfree(buf); - } - else { - strcpy(dst, "out of memory"); - } -} -void logBindings(const char* format, ...) -{ - if ( sBindingsLogfile != -1 ) { - va_list list; - va_start(list, format); - _simple_vdprintf(sBindingsLogfile, format, list); - va_end(list); - } -} -#endif - #if !TARGET_IPHONE_SIMULATOR // based on CFUtilities.c: also_do_stderr() static bool useSyslog() { // Use syslog() for processes managed by launchd - if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 11) ) { - if ( (*gLibSystemHelpers->isLaunchdOwned)() ) { - return true; + static bool launchdChecked = false; + static bool launchdOwned = false; + if ( !launchdChecked && gProcessInfo->libSystemInitialized ) { + if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 11) ) { + // only call isLaunchdOwned() after libSystem is initialized + launchdOwned = (*gLibSystemHelpers->isLaunchdOwned)(); + launchdChecked = true; } } + if ( launchdChecked && launchdOwned ) + return true; // If stderr is not available, use syslog() struct stat sb; @@ -487,9 +518,16 @@ static void socket_syslogv(int priority, const char* format, va_list list) _simple_sfree(buf); } + + void vlog(const char* format, va_list list) { - if ( !sLogToFile && useSyslog() ) +#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR + // log to console when running iOS app from Xcode + if ( !sLogToFile && !sForceStderr && useSyslog() ) +#else + if ( !sLogToFile && useSyslog() ) +#endif socket_syslogv(LOG_ERR, format, list); else { _simple_vdprintf(sLogfile, format, list); @@ -674,7 +712,165 @@ static StateHandlers* stateToHandlers(dyld_image_states state, void* handlersArr return NULL; } -static void notifySingle(dyld_image_states state, const ImageLoader* image) +#if SUPPORT_ACCELERATE_TABLES +static dyld_image_state_change_handler getPreInitNotifyHandler(unsigned index) +{ + std::vector* handlers = stateToHandlers(dyld_image_state_dependents_initialized, sSingleHandlers); + if ( index >= handlers->size() ) + return NULL; + return (*handlers)[index]; +} + +static dyld_image_state_change_handler getBoundBatchHandler(unsigned index) +{ + std::vector* handlers = stateToHandlers(dyld_image_state_bound, sBatchHandlers); + if ( index >= handlers->size() ) + return NULL; + return (*handlers)[index]; +} + +static void notifySingleFromCache(dyld_image_states state, const mach_header* mh, const char* path) +{ + //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath()); + std::vector* handlers = stateToHandlers(state, sSingleHandlers); + if ( handlers != NULL ) { + dyld_image_info info; + info.imageLoadAddress = mh; + info.imageFilePath = path; + info.imageFileModDate = 0; + for (dyld_image_state_change_handler handler : *handlers) { + const char* result = (*handler)(state, 1, &info); + if ( (result != NULL) && (state == dyld_image_state_mapped) ) { + //fprintf(stderr, " image rejected by handler=%p\n", *it); + // make copy of thrown string so that later catch clauses can free it + const char* str = strdup(result); + throw str; + } + } + } + if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && (mh->flags & MH_HAS_OBJC) ) { + (*sNotifyObjCInit)(path, mh); + } +} +#endif + +static mach_port_t sNotifyReplyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; + + +static void notifyMonitoringDyld(bool unloading, unsigned portSlot, unsigned imageCount, const dyld_image_info infos[]) +{ + unsigned entriesSize = imageCount*sizeof(dyld_process_info_image_entry); + unsigned pathsSize = 0; + for (unsigned j=0; j < imageCount; ++j) { + pathsSize += (strlen(infos[j].imageFilePath) + 1); + } + unsigned totalSize = (sizeof(dyld_process_info_notify_header) + entriesSize + pathsSize + 127) & -128; // align + if ( totalSize > DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE ) { + // Putting all image paths into one message would make buffer too big. + // Instead split into two messages. Recurse as needed until paths fit in buffer. + unsigned imageHalfCount = imageCount/2; + notifyMonitoringDyld(unloading, portSlot, imageHalfCount, infos); + notifyMonitoringDyld(unloading, portSlot, imageCount - imageHalfCount, &infos[imageHalfCount]); + return; + } + uint8_t buffer[totalSize]; + dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)buffer; + header->version = 1; + header->imageCount = imageCount; + header->imagesOffset = sizeof(dyld_process_info_notify_header); + header->stringsOffset = sizeof(dyld_process_info_notify_header) + entriesSize; + header->timestamp = mach_absolute_time(); + dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&buffer[header->imagesOffset]; + char* const pathPoolStart = (char*)&buffer[header->stringsOffset]; + char* pathPool = pathPoolStart; + for (unsigned j=0; j < imageCount; ++j) { + strcpy(pathPool, infos[j].imageFilePath); + uint32_t len = (uint32_t)strlen(pathPool); + bzero(entries->uuid, 16); + const ImageLoader* image = findImageByMachHeader(infos[j].imageLoadAddress); + if ( image != NULL ) { + image->getUUID(entries->uuid); + } +#if SUPPORT_ACCELERATE_TABLES + else if ( sAllCacheImagesProxy != NULL ) { + const mach_header* mh; + const char* path; + unsigned index; + if ( sAllCacheImagesProxy->addressInCache(infos[j].imageLoadAddress, &mh, &path, &index) ) { + sAllCacheImagesProxy->getDylibUUID(index, entries->uuid); + } + } +#endif + entries->loadAddress = (uint64_t)infos[j].imageLoadAddress; + entries->pathStringOffset = (uint32_t)(pathPool - pathPoolStart); + entries->pathLength = len; + pathPool += (len +1); + ++entries; + } + + if ( sNotifyReplyPorts[portSlot] == 0 ) { + if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[portSlot]) ) + mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[portSlot], sNotifyReplyPorts[portSlot], MACH_MSG_TYPE_MAKE_SEND); + //dyld::log("allocated reply port %d\n", sNotifyReplyPorts[portSlot]); + } + //dyld::log("found port to send to\n"); + mach_msg_header_t* h = (mach_msg_header_t*)buffer; + h->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND); // MACH_MSG_TYPE_MAKE_SEND_ONCE + h->msgh_id = unloading ? DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID : DYLD_PROCESS_INFO_NOTIFY_LOAD_ID; + h->msgh_local_port = sNotifyReplyPorts[portSlot]; + h->msgh_remote_port = dyld::gProcessInfo->notifyPorts[portSlot]; + h->msgh_reserved = 0; + h->msgh_size = (mach_msg_size_t)sizeof(buffer); + //dyld::log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", portSlot, dyld::gProcessInfo->notifyPorts[portSlot], h->msgh_size, sNotifyReplyPorts[portSlot], h->msgh_id); + kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_SEND_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[portSlot], 100, MACH_PORT_NULL); + //dyld::log("send result = 0x%X, msg_id=%d, msg_size=%d\n", sendResult, h->msgh_id, h->msgh_size); + if ( sendResult == MACH_SEND_INVALID_DEST ) { + // sender is not responding, detatch + //dyld::log("process requesting notification gone. deallocation send port %d and receive port %d\n", dyld::gProcessInfo->notifyPorts[portSlot], sNotifyReplyPorts[portSlot]); + mach_port_deallocate(mach_task_self(), dyld::gProcessInfo->notifyPorts[portSlot]); + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]); + dyld::gProcessInfo->notifyPorts[portSlot] = 0; + sNotifyReplyPorts[portSlot] = 0; + } +} + +#define MAX_KERNEL_IMAGES_PER_CALL (100) + +static void flushKernelNotifications(bool loading, bool force, std::array& kernelInfos, uint32_t &kernelInfoCount) { + if ((force && kernelInfoCount != 0) || kernelInfoCount == MAX_KERNEL_IMAGES_PER_CALL) { + if (loading) { + task_register_dyld_image_infos(mach_task_self(), kernelInfos.data(), kernelInfoCount); + } else { + task_unregister_dyld_image_infos(mach_task_self(), kernelInfos.data(), kernelInfoCount); + } + kernelInfoCount = 0; + } +} + +static +void queueKernelNotification(const ImageLoader& image, bool loading, std::array& kernelInfos, uint32_t &kernelInfoCount) { + if ( !image.inSharedCache() ) { + ino_t inode = image.getInode(); + image.getUUID(kernelInfos[kernelInfoCount].uuid); + memcpy(&kernelInfos[kernelInfoCount].fsobjid, &inode, 8); + kernelInfos[kernelInfoCount].load_addr = (uint64_t)image.machHeader(); + // FIXME we should also be grabbing the device ID, but that is not necessary yet, + // and requires threading it through the ImageLoader + kernelInfos[kernelInfoCount].fsid.val[0] = 0; + kernelInfos[kernelInfoCount].fsid.val[1] = 0; + kernelInfoCount++; + } + flushKernelNotifications(loading, false, kernelInfos, kernelInfoCount); +} + +void notifyKernel(const ImageLoader& image, bool loading) { + std::array kernelInfos; + uint32_t kernelInfoCount = 0; + queueKernelNotification(image, loading, kernelInfos, kernelInfoCount); + flushKernelNotifications(loading, true, kernelInfos, kernelInfoCount); +} + +static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo) { //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath()); std::vector* handlers = stateToHandlers(state, sSingleHandlers); @@ -703,8 +899,21 @@ static void notifySingle(dyld_image_states state, const ImageLoader* image) } } } + if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) { + uint64_t t0 = mach_absolute_time(); + (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); + uint64_t t1 = mach_absolute_time(); + uint64_t t2 = mach_absolute_time(); + uint64_t timeInObjC = t1-t0; + uint64_t emptyTime = (t2-t1)*100; + if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) { + timingInfo->addTime(image->getShortName(), timeInObjC); + } + } // mach message csdlc about dynamically unloaded images if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) { + notifyKernel(*image, false); + uint64_t loadTimestamp = mach_absolute_time(); if ( sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { dyld::log("dyld: coresymbolication_unload_notifier(%p, 0x%016llX, %p, %s)\n", @@ -713,10 +922,24 @@ static void notifySingle(dyld_image_states state, const ImageLoader* image) if ( dyld::gProcessInfo->coreSymbolicationShmPage != NULL) { coresymbolication_unload_notifier(dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, image->getPath(), image->machHeader()); } + for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { + if ( dyld::gProcessInfo->notifyPorts[slot] != 0 ) { + dyld_image_info info; + info.imageLoadAddress = image->machHeader(); + info.imageFilePath = image->getPath(); + info.imageFileModDate = 0; + notifyMonitoringDyld(true, slot, 1, &info); + } + else if ( sNotifyReplyPorts[slot] != 0 ) { + // monitoring process detached from this process, so release reply port + //dyld::log("deallocated reply port %d\n", sNotifyReplyPorts[slot]); + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); + sNotifyReplyPorts[slot] = 0; + } + } } -} - +} // @@ -764,13 +987,17 @@ static int imageSorter(const void* l, const void* r) return left->compare(right); } -static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler) +static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification) { std::vector* handlers = stateToHandlers(state, sBatchHandlers); - if ( handlers != NULL ) { + std::array kernelInfos; + uint32_t kernelInfoCount = 0; + + if ( (handlers != NULL) || ((state == dyld_image_state_bound) && (sNotifyObjCMapped != NULL)) ) { // don't use a vector because it will use malloc/free and we want notifcation to be low cost allImagesLock(); - ImageLoader* images[sAllImages.size()+1]; + dyld_image_info infos[allImagesCount()+1]; + ImageLoader* images[allImagesCount()+1]; ImageLoader** end = images; for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { dyld_image_states imageState = (*it)->getState(); @@ -783,48 +1010,111 @@ static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image *end++ = sBundleBeingLoaded; } const char* dontLoadReason = NULL; - uint32_t count = (uint32_t)(end-images); - if ( end != images ) { + uint32_t imageCount = (uint32_t)(end-images); + if ( imageCount != 0 ) { // sort bottom up - qsort(images, count, sizeof(ImageLoader*), &imageSorter); + qsort(images, imageCount, sizeof(ImageLoader*), &imageSorter); // build info array - dyld_image_info infos[count]; - for (unsigned int i=0; i < count; ++i) { + for (unsigned int i=0; i < imageCount; ++i) { dyld_image_info* p = &infos[i]; ImageLoader* image = images[i]; //dyld::log(" state=%d, name=%s\n", state, image->getPath()); p->imageLoadAddress = image->machHeader(); - p->imageFilePath = image->getRealPath(); + p->imageFilePath = image->getRealPath(); p->imageFileModDate = image->lastModified(); + // get these registered with the kernel as early as possible + if ( state == dyld_image_state_dependents_mapped) + queueKernelNotification(*image, true, kernelInfos, kernelInfoCount); // special case for add_image hook if ( state == dyld_image_state_bound ) notifyAddImageCallbacks(image); } - - if ( onlyHandler != NULL ) { - const char* result = (*onlyHandler)(state, count, infos); - if ( (result != NULL) && (state == dyld_image_state_dependents_mapped) ) { - //fprintf(stderr, " images rejected by handler=%p\n", onlyHandler); - // make copy of thrown string so that later catch clauses can free it - dontLoadReason = strdup(result); + flushKernelNotifications(true, true, kernelInfos, kernelInfoCount); + } +#if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + unsigned cacheCount = sAllCacheImagesProxy->appendImagesToNotify(state, orLater, &infos[imageCount]); + // support _dyld_register_func_for_add_image() + if ( state == dyld_image_state_bound ) { + for (ImageCallback callback : sAddImageCallbacks) { + for (unsigned i=0; i < cacheCount; ++i) + (*callback)(infos[imageCount+i].imageLoadAddress, sSharedCacheSlide); } } - else { - // call each handler with whole array - for (std::vector::iterator it = handlers->begin(); it != handlers->end(); ++it) { - const char* result = (*it)(state, count, infos); + imageCount += cacheCount; + } +#endif + if ( imageCount != 0 ) { + if ( !onlyObjCMappedNotification ) { + if ( onlyHandler != NULL ) { + const char* result = NULL; + if ( result == NULL ) { + result = (*onlyHandler)(state, imageCount, infos); + } if ( (result != NULL) && (state == dyld_image_state_dependents_mapped) ) { - //fprintf(stderr, " images rejected by handler=%p\n", *it); + //fprintf(stderr, " images rejected by handler=%p\n", onlyHandler); // make copy of thrown string so that later catch clauses can free it dontLoadReason = strdup(result); - break; } } + else { + // call each handler with whole array + if ( handlers != NULL ) { + for (std::vector::iterator it = handlers->begin(); it != handlers->end(); ++it) { + const char* result = (*it)(state, imageCount, infos); + if ( (result != NULL) && (state == dyld_image_state_dependents_mapped) ) { + //fprintf(stderr, " images rejected by handler=%p\n", *it); + // make copy of thrown string so that later catch clauses can free it + dontLoadReason = strdup(result); + break; + } + } + } + } + } + // tell objc about new images + if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) { + const char* paths[imageCount]; + const mach_header* mhs[imageCount]; + unsigned objcImageCount = 0; + for (int i=0; i < imageCount; ++i) { + const ImageLoader* image = findImageByMachHeader(infos[i].imageLoadAddress); + bool hasObjC = false; + if ( image != NULL ) { + hasObjC = image->notifyObjC(); + } +#if SUPPORT_ACCELERATE_TABLES + else if ( sAllCacheImagesProxy != NULL ) { + const mach_header* mh; + const char* path; + unsigned index; + if ( sAllCacheImagesProxy->addressInCache(infos[i].imageLoadAddress, &mh, &path, &index) ) { + hasObjC = (mh->flags & MH_HAS_OBJC); + } + } +#endif + if ( hasObjC ) { + paths[objcImageCount] = infos[i].imageFilePath; + mhs[objcImageCount] = infos[i].imageLoadAddress; + ++objcImageCount; + } + } + if ( objcImageCount != 0 ) { + uint64_t t0 = mach_absolute_time(); + (*sNotifyObjCMapped)(objcImageCount, paths, mhs); + uint64_t t1 = mach_absolute_time(); + ImageLoader::fgTotalObjCSetupTime += (t1-t0); + } } - if ( (state == dyld_image_state_dependents_mapped) && ((dyld::gProcessInfo->coreSymbolicationShmPage != NULL) || sEnv.DYLD_PRINT_CS_NOTIFICATIONS) ) { + } + allImagesUnlock(); + if ( dontLoadReason != NULL ) + throw dontLoadReason; + if ( !preflightOnly && (state == dyld_image_state_dependents_mapped) ) { + if ( (dyld::gProcessInfo->coreSymbolicationShmPage != NULL) || sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { // mach message csdlc about loaded images uint64_t loadTimestamp = mach_absolute_time(); - for (unsigned j=0; j < count; ++j) { + for (unsigned j=0; j < imageCount; ++j) { if ( sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { dyld::log("dyld: coresymbolication_load_notifier(%p, 0x%016llX, %p, %s)\n", dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, infos[j].imageLoadAddress, infos[j].imageFilePath); @@ -832,18 +1122,19 @@ static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image coresymbolication_load_notifier(dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, infos[j].imageFilePath, infos[j].imageLoadAddress); } } + for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { + if ( dyld::gProcessInfo->notifyPorts[slot] ) + notifyMonitoringDyld(false, slot, imageCount, infos); + } } - allImagesUnlock(); - if ( dontLoadReason != NULL ) - throw dontLoadReason; } } -static void notifyBatch(dyld_image_states state) +static void notifyBatch(dyld_image_states state, bool preflightOnly) { - notifyBatchPartial(state, false, NULL); + notifyBatchPartial(state, false, NULL, preflightOnly, false); } // In order for register_func_for_add_image() callbacks to to be called bottom up, @@ -873,7 +1164,10 @@ static void printAllDepths() static unsigned int imageCount() { - return (unsigned int)sAllImages.size(); + allImagesLock(); + unsigned int result = (unsigned int)sAllImages.size(); + allImagesUnlock(); + return (result); } @@ -896,11 +1190,36 @@ static void setRunInitialzersOldWay() } #endif +static bool sandboxBlocked(const char* path, const char* kind) +{ +#if TARGET_IPHONE_SIMULATOR + // sandbox calls not yet supported in simulator runtime + return false; +#else + sandbox_filter_type filter = (sandbox_filter_type)(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT); + return ( sandbox_check(getpid(), kind, filter, path) > 0 ); +#endif +} + +bool sandboxBlockedMmap(const char* path) +{ + return sandboxBlocked(path, "file-map-executable"); +} + +bool sandboxBlockedOpen(const char* path) +{ + return sandboxBlocked(path, "file-read-data"); +} + +bool sandboxBlockedStat(const char* path) +{ + return sandboxBlocked(path, "file-read-metadata"); +} + + static void addDynamicReference(ImageLoader* from, ImageLoader* to) { - // don't add dynamic reference if either are in the shared cache - if( from->inSharedCache() ) - return; - if( to->inSharedCache() ) + // don't add dynamic reference if target is in the shared cache (since it can't be unloaded) + if ( to->inSharedCache() ) return; // don't add dynamic reference if there already is a static one @@ -955,7 +1274,7 @@ static void addImage(ImageLoader* image) addMappedRange(image, lastSegStart, lastSegEnd); - if ( sEnv.DYLD_PRINT_LIBRARIES || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) { + if ( gLinkContext.verboseLoading || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) { dyld::log("dyld: loaded: %s\n", image->getPath()); } @@ -998,10 +1317,13 @@ void removeImage(ImageLoader* image) (*it)(image->machHeader(), image->getSlide()); } sRemoveImageCallbacksInUse = false; + + if ( sNotifyObjCUnmapped != NULL && image->notifyObjC() ) + (*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader()); } // notify - notifySingle(dyld_image_state_terminated, image); + notifySingle(dyld_image_state_terminated, image, NULL); // remove from mapped images table removedMappedRanges(image); @@ -1034,7 +1356,7 @@ void removeImage(ImageLoader* image) } // log if requested - if ( sEnv.DYLD_PRINT_LIBRARIES || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) { + if ( gLinkContext.verboseLoading || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) { dyld::log("dyld: unloaded: %s\n", image->getPath()); } @@ -1080,7 +1402,7 @@ static void runAllStaticTerminators(void* extra) image->doTermination(gLinkContext); } sImageFilesNeedingTermination.clear(); - notifyBatch(dyld_image_state_terminated); + notifyBatch(dyld_image_state_terminated, false); } catch (const char* msg) { halt(msg); @@ -1093,7 +1415,7 @@ void initializeMainExecutable() gLinkContext.startedInitializingMainExecutable = true; // run initialzers for any inserted dylibs - ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()]; + ImageLoader::InitializerTimingList initializerTimes[allImagesCount()]; initializerTimes[0].count = 0; const size_t rootCount = sImageRoots.size(); if ( rootCount > 1 ) { @@ -1111,7 +1433,9 @@ void initializeMainExecutable() // dump info if requested if ( sEnv.DYLD_PRINT_STATISTICS ) - ImageLoaderMachO::printStatistics((unsigned int)sAllImages.size(), initializerTimes[0]); + ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]); + if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS ) + ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]); } bool mainExecutablePrebound() @@ -1190,7 +1514,7 @@ static void checkDylibOverridesInDir(const char* dirPath) { //dyld::log("checkDylibOverridesInDir('%s')\n", dirPath); char dylibPath[PATH_MAX]; - int dirPathLen = strlcpy(dylibPath, dirPath, PATH_MAX-1); + long dirPathLen = strlcpy(dylibPath, dirPath, PATH_MAX-1); if ( dirPathLen >= PATH_MAX ) return; DIR* dirp = opendir(dirPath); @@ -1217,7 +1541,7 @@ static void checkFrameworkOverridesInDir(const char* dirPath) { //dyld::log("checkFrameworkOverridesInDir('%s')\n", dirPath); char frameworkPath[PATH_MAX]; - int dirPathLen = strlcpy(frameworkPath, dirPath, PATH_MAX-1); + long dirPathLen = strlcpy(frameworkPath, dirPath, PATH_MAX-1); if ( dirPathLen >= PATH_MAX ) return; DIR* dirp = opendir(dirPath); @@ -1276,6 +1600,12 @@ static const char** parseColonList(const char* list, const char* mainExecutableD if (*s == ':') { size_t len = s-start; if ( (mainExecutableDir != NULL) && (strncmp(start, "@loader_path/", 13) == 0) ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.processIsRestricted ) { + dyld::log("dyld: warning: @loader_path/ ignored in restricted process\n"); + continue; + } +#endif size_t mainExecDirLen = strlen(mainExecutableDir); char* str = new char[mainExecDirLen+len+1]; strcpy(str, mainExecutableDir); @@ -1285,6 +1615,12 @@ static const char** parseColonList(const char* list, const char* mainExecutableD result[index++] = str; } else if ( (mainExecutableDir != NULL) && (strncmp(start, "@executable_path/", 17) == 0) ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.processIsRestricted ) { + dyld::log("dyld: warning: @executable_path/ ignored in restricted process\n"); + continue; + } +#endif size_t mainExecDirLen = strlen(mainExecutableDir); char* str = new char[mainExecDirLen+len+1]; strcpy(str, mainExecutableDir); @@ -1304,20 +1640,36 @@ static const char** parseColonList(const char* list, const char* mainExecutableD } size_t len = strlen(start); if ( (mainExecutableDir != NULL) && (strncmp(start, "@loader_path/", 13) == 0) ) { - size_t mainExecDirLen = strlen(mainExecutableDir); - char* str = new char[mainExecDirLen+len+1]; - strcpy(str, mainExecutableDir); - strlcat(str, &start[13], mainExecDirLen+len+1); - str[mainExecDirLen+len-13] = '\0'; - result[index++] = str; +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.processIsRestricted ) { + dyld::log("dyld: warning: @loader_path/ ignored in restricted process\n"); + } + else +#endif + { + size_t mainExecDirLen = strlen(mainExecutableDir); + char* str = new char[mainExecDirLen+len+1]; + strcpy(str, mainExecutableDir); + strlcat(str, &start[13], mainExecDirLen+len+1); + str[mainExecDirLen+len-13] = '\0'; + result[index++] = str; + } } else if ( (mainExecutableDir != NULL) && (strncmp(start, "@executable_path/", 17) == 0) ) { - size_t mainExecDirLen = strlen(mainExecutableDir); - char* str = new char[mainExecDirLen+len+1]; - strcpy(str, mainExecutableDir); - strlcat(str, &start[17], mainExecDirLen+len+1); - str[mainExecDirLen+len-17] = '\0'; - result[index++] = str; +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.processIsRestricted ) { + dyld::log("dyld: warning: @executable_path/ ignored in restricted process\n"); + } + else +#endif + { + size_t mainExecDirLen = strlen(mainExecutableDir); + char* str = new char[mainExecDirLen+len+1]; + strcpy(str, mainExecutableDir); + strlcat(str, &start[17], mainExecDirLen+len+1); + str[mainExecDirLen+len-17] = '\0'; + result[index++] = str; + } } else { char* str = new char[len+1]; @@ -1359,7 +1711,7 @@ static void appendParsedColonList(const char* list, const char* mainExecutableDi } } - +#if __MAC_OS_X_VERSION_MIN_REQUIRED static void paths_expand_roots(const char **paths, const char *key, const char *val) { // assert(val != NULL); @@ -1391,6 +1743,7 @@ static void removePathWithPrefix(const char* paths[], const char* prefix) } paths[i-skip] = NULL; } +#endif #if 0 @@ -1438,6 +1791,7 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch else if ( strcmp(key, "DYLD_FALLBACK_LIBRARY_PATH") == 0 ) { appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_FALLBACK_LIBRARY_PATH); } +#if SUPPORT_ROOT_PATH else if ( (strcmp(key, "DYLD_ROOT_PATH") == 0) || (strcmp(key, "DYLD_PATHS_ROOT") == 0) ) { if ( strcmp(value, "/") != 0 ) { gLinkContext.rootPaths = parseColonList(value, mainExecutableDir); @@ -1449,12 +1803,16 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch } } } - } + } +#endif else if ( strcmp(key, "DYLD_IMAGE_SUFFIX") == 0 ) { gLinkContext.imageSuffix = value; } else if ( strcmp(key, "DYLD_INSERT_LIBRARIES") == 0 ) { sEnv.DYLD_INSERT_LIBRARIES = parseColonList(value, NULL); +#if SUPPORT_ACCELERATE_TABLES + sDisableAcceleratorTables = true; +#endif } else if ( strcmp(key, "DYLD_PRINT_OPTS") == 0 ) { sEnv.DYLD_PRINT_OPTS = true; @@ -1469,7 +1827,7 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch gLinkContext.preFetchDisabled = true; } else if ( strcmp(key, "DYLD_PRINT_LIBRARIES") == 0 ) { - sEnv.DYLD_PRINT_LIBRARIES = true; + gLinkContext.verboseLoading = true; } else if ( strcmp(key, "DYLD_PRINT_LIBRARIES_POST_LAUNCH") == 0 ) { sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH = true; @@ -1496,6 +1854,19 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch } else if ( strcmp(key, "DYLD_PRINT_STATISTICS") == 0 ) { sEnv.DYLD_PRINT_STATISTICS = true; +#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR + // DYLD_PRINT_STATISTICS no longer logs to xcode console for device apps + sForceStderr = true; +#endif + } + else if ( strcmp(key, "DYLD_PRINT_TO_STDERR") == 0 ) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR + // DYLD_PRINT_STATISTICS no longer logs to xcode console for device apps + sForceStderr = true; +#endif + } + else if ( strcmp(key, "DYLD_PRINT_STATISTICS_DETAILS") == 0 ) { + sEnv.DYLD_PRINT_STATISTICS_DETAILS = true; } else if ( strcmp(key, "DYLD_PRINT_SEGMENTS") == 0 ) { gLinkContext.verboseMapping = true; @@ -1512,6 +1883,11 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch else if ( strcmp(key, "DYLD_PRINT_APIS") == 0 ) { gLogAPIs = true; } +#if SUPPORT_ACCELERATE_TABLES + else if ( strcmp(key, "DYLD_PRINT_APIS_APP") == 0 ) { + gLogAppAPIs = true; + } +#endif else if ( strcmp(key, "DYLD_PRINT_WARNINGS") == 0 ) { gLinkContext.verboseWarnings = true; } @@ -1572,9 +1948,15 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch #if SUPPORT_VERSIONED_PATHS else if ( strcmp(key, "DYLD_VERSIONED_LIBRARY_PATH") == 0 ) { appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_VERSIONED_LIBRARY_PATH); + #if SUPPORT_ACCELERATE_TABLES + sDisableAcceleratorTables = true; + #endif } else if ( strcmp(key, "DYLD_VERSIONED_FRAMEWORK_PATH") == 0 ) { appendParsedColonList(value, mainExecutableDir, &sEnv.DYLD_VERSIONED_FRAMEWORK_PATH); + #if SUPPORT_ACCELERATE_TABLES + sDisableAcceleratorTables = true; + #endif } #endif #if !TARGET_IPHONE_SIMULATOR @@ -1675,6 +2057,7 @@ static void checkVersionedPaths() #endif +#if __MAC_OS_X_VERSION_MIN_REQUIRED // // For security, setuid programs ignore DYLD_* environment variables. // Additionally, the DYLD_* enviroment variables are removed @@ -1682,6 +2065,10 @@ static void checkVersionedPaths() // static void pruneEnvironmentVariables(const char* envp[], const char*** applep) { +#if SUPPORT_LC_DYLD_ENVIRONMENT + checkLoadCommandEnvironmentVariables(); +#endif + // delete all DYLD_* and LD_LIBRARY_PATH environment variables int removedCount = 0; const char** d = envp; @@ -1694,25 +2081,6 @@ static void pruneEnvironmentVariables(const char* envp[], const char*** applep) } } *d++ = NULL; -// Disable warnings about DYLD_ env vars being ignored. The warnings are causing too much confusion. -#if 0 - if ( removedCount != 0 ) { - dyld::log("dyld: DYLD_ environment variables being ignored because "); - switch (sRestrictedReason) { - case restrictedNot: - break; - case restrictedBySetGUid: - dyld::log("main executable (%s) is setuid or setgid\n", sExecPath); - break; - case restrictedBySegment: - dyld::log("main executable (%s) has __RESTRICT/__restrict section\n", sExecPath); - break; - case restrictedByEntitlements: - dyld::log("main executable (%s) is code signed with entitlements\n", sExecPath); - break; - } - } -#endif // slide apple parameters if ( removedCount > 0 ) { *applep = d; @@ -1730,10 +2098,17 @@ static void pruneEnvironmentVariables(const char* envp[], const char*** applep) if ( removedCount > 0 ) strlcat(sLoadingCrashMessage, ", ignoring DYLD_* env vars", sizeof(sLoadingCrashMessage)); } +#endif static void defaultUninitializedFallbackPaths(const char* envp[]) { #if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.processIsRestricted ) { + sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = sRestrictedFrameworkFallbackPaths; + sEnv.DYLD_FALLBACK_LIBRARY_PATH = sRestrictedLibraryFallbackPaths; + return; + } + // default value for DYLD_FALLBACK_FRAMEWORK_PATH, if not set in environment const char* home = _simple_getenv(envp, "HOME");; if ( sEnv.DYLD_FALLBACK_FRAMEWORK_PATH == NULL ) { @@ -1766,6 +2141,8 @@ static void defaultUninitializedFallbackPaths(const char* envp[]) static void checkEnvironmentVariables(const char* envp[]) { + if ( sEnvMode == envNone ) + return; const char** p; for(p = envp; *p != NULL; p++) { const char* keyEqualsValue = *p; @@ -1779,6 +2156,8 @@ static void checkEnvironmentVariables(const char* envp[]) char key[keyLen+1]; strncpy(key, keyEqualsValue, keyLen); key[keyLen] = '\0'; + if ( (sEnvMode == envPrintOnly) && (strncmp(key, "DYLD_PRINT_", 11) != 0) ) + continue; processDyldEnvironmentVariable(key, value, NULL); } } @@ -1792,14 +2171,16 @@ static void checkEnvironmentVariables(const char* envp[]) checkLoadCommandEnvironmentVariables(); #endif // SUPPORT_LC_DYLD_ENVIRONMENT +#if SUPPORT_ROOT_PATH // DYLD_IMAGE_SUFFIX and DYLD_ROOT_PATH cannot be used together if ( (gLinkContext.imageSuffix != NULL) && (gLinkContext.rootPaths != NULL) ) { dyld::warn("Ignoring DYLD_IMAGE_SUFFIX because DYLD_ROOT_PATH is used.\n"); gLinkContext.imageSuffix = NULL; } +#endif } -#if __x86_64__ +#if __x86_64__ && DYLD_SHARED_CACHE_SUPPORT static bool isGCProgram(const macho_header* mh, uintptr_t slide) { const uint32_t cmd_count = mh->ncmds; @@ -1828,6 +2209,7 @@ static bool isGCProgram(const macho_header* mh, uintptr_t slide) return false; } #endif + static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide) { #if CPU_SUBTYPES_SUPPORTED @@ -1857,9 +2239,7 @@ static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExec sHostCPUsubtype = info.cpu_subtype; mach_port_deallocate(mach_task_self(), hostPort); #if __x86_64__ - #if TARGET_IPHONE_SIMULATOR - sHaswell = false; - #else + #if DYLD_SHARED_CACHE_SUPPORT sHaswell = (sHostCPUsubtype == CPU_SUBTYPE_X86_64_H); // x86_64h: Fall back to the x86_64 slice if an app requires GC. if ( sHaswell ) { @@ -1887,7 +2267,7 @@ static void checkSharedRegionDisable() dyld::warn("disabling shared region because main executable overlaps\n"); } #if __i386__ - if ( sProcessIsRestricted ) { + if ( gLinkContext.processIsRestricted ) { // use private or no shared region for suid processes gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; } @@ -1927,6 +2307,15 @@ ImageLoader* findImageByMachHeader(const struct mach_header* target) ImageLoader* findImageContainingAddress(const void* addr) { + #if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + const mach_header* mh; + const char* path; + unsigned index; + if ( sAllCacheImagesProxy->addressInCache(addr, &mh, &path, &index) ) + return sAllCacheImagesProxy; + } + #endif return findMappedRange((uintptr_t)addr); } @@ -2191,6 +2580,66 @@ static bool fatFindRunsOnAllCPUs(cpu_type_t cpu, const fat_header* fh, uint64_t* #endif // CPU_SUBTYPES_SUPPORTED + +// +// Validate the fat_header and fat_arch array: +// +// 1) arch count would not cause array to extend past 4096 byte read buffer +// 2) no slice overlaps the fat_header and arch array +// 3) arch list does not contain duplicate cputype/cpusubtype tuples +// 4) arch list does not have two overlapping slices. +// +static bool fatValidate(const fat_header* fh) +{ + if ( fh->magic != OSSwapBigToHostInt32(FAT_MAGIC) ) + return false; + + // since only first 4096 bytes of file read, we can only handle up to 204 slices. + const uint32_t sliceCount = OSSwapBigToHostInt32(fh->nfat_arch); + if ( sliceCount > 204 ) + return false; + + // compare all slices looking for conflicts + const fat_arch* archs = (fat_arch*)(((char*)fh)+sizeof(fat_header)); + for (uint32_t i=0; i < sliceCount; ++i) { + uint32_t i_offset = OSSwapBigToHostInt32(archs[i].offset); + uint32_t i_size = OSSwapBigToHostInt32(archs[i].size); + uint32_t i_cputype = OSSwapBigToHostInt32(archs[i].cputype); + uint32_t i_cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype); + uint32_t i_end = i_offset + i_size; + // slice cannot overlap with header + if ( i_offset < 4096 ) + return false; + // slice size cannot overflow + if ( i_end < i_offset ) + return false; + for (uint32_t j=i+1; j < sliceCount; ++j) { + uint32_t j_offset = OSSwapBigToHostInt32(archs[j].offset); + uint32_t j_size = OSSwapBigToHostInt32(archs[j].size); + uint32_t j_cputype = OSSwapBigToHostInt32(archs[j].cputype); + uint32_t j_cpusubtype = OSSwapBigToHostInt32(archs[j].cpusubtype); + uint32_t j_end = j_offset + j_size; + // duplicate slices types not allowed + if ( (i_cputype == j_cputype) && (i_cpusubtype == j_cpusubtype) ) + return false; + // slice size cannot overflow + if ( j_end < j_offset ) + return false; + // check for overlap of slices + if ( i_offset <= j_offset ) { + if ( j_offset < i_end ) + return false; // j overlaps end of i + } + else { + // j overlaps end of i + if ( i_offset < j_end ) + return false; // i overlaps end of j + } + } + } + return true; +} + // // A fat file may contain multiple sub-images for the same cpu-type, // each optimized for a different cpu-sub-type (e.g G3 or G5). @@ -2198,6 +2647,9 @@ static bool fatFindRunsOnAllCPUs(cpu_type_t cpu, const fat_header* fh, uint64_t* // static bool fatFindBest(const fat_header* fh, uint64_t* offset, uint64_t* len) { + if ( !fatValidate(fh) ) + return false; + #if CPU_SUBTYPES_SUPPORTED // assume all dylibs loaded must have same cpu type as main executable const cpu_type_t cpu = sMainExecutableMachHeader->cputype; @@ -2208,9 +2660,11 @@ static bool fatFindBest(const fat_header* fh, uint64_t* offset, uint64_t* len) const cpu_subtype_t* subTypePreferenceList = findCPUSubtypeList(cpu, sHostCPUsubtype); // use ordered list to find best sub-image in fat file - if ( subTypePreferenceList != NULL ) - return fatFindBestFromOrderedList(cpu, subTypePreferenceList, fh, offset, len); - + if ( subTypePreferenceList != NULL ) { + if ( fatFindBestFromOrderedList(cpu, subTypePreferenceList, fh, offset, len) ) + return true; + } + // if running cpu is not in list, try for an exact match if ( fatFindExactMatch(cpu, sHostCPUsubtype, fh, offset, len) ) return true; @@ -2291,20 +2745,30 @@ bool isCompatibleMachO(const uint8_t* firstPage, const char* path) // The kernel maps in main executable before dyld gets control. We need to // make an ImageLoader* for the already mapped in main executable. -static ImageLoader* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path) +static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path) { // try mach-o loader if ( isCompatibleMachO((const uint8_t*)mh, path) ) { ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext); addImage(image); - return image; + return (ImageLoaderMachO*)image; } throw "main executable not a known format"; } - #if DYLD_SHARED_CACHE_SUPPORT + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +static bool dylibsCanOverrideCache() +{ + uint32_t devFlags = *((uint32_t*)_COMM_PAGE_DEV_FIRM); + if ( (devFlags & 1) == 0 ) + return false; + return ( (sSharedCache != NULL) && (sSharedCache->cacheType == kDyldSharedCacheTypeDevelopment) ); +} +#endif + static bool findInSharedCacheImage(const char* path, bool searchByPath, const struct stat* stat_buf, const macho_header** mh, const char** pathInCache, long* slide) { if ( sSharedCache != NULL ) { @@ -2341,6 +2805,15 @@ static bool findInSharedCacheImage(const char* path, bool searchByPath, const st *mh = (macho_header*)(p->address+sSharedCacheSlide); *pathInCache = aPath; *slide = sSharedCacheSlide; + if ( aPath < (char*)(*mh) ) { + // found alias, rescan list to get canonical name + for (const dyld_cache_image_info* p2 = start; p2 != end; ++p2) { + if ( p2->address == p->address ) { + *pathInCache = (char*)sSharedCache + p2->pathFileOffset; + break; + } + } + } return true; } #elif __MAC_OS_X_VERSION_MIN_REQUIRED @@ -2360,7 +2833,7 @@ static bool findInSharedCacheImage(const char* path, bool searchByPath, const st if ( cacheHit ) { // found image in cache, return info *mh = (macho_header*)(p->address+sSharedCacheSlide); - //dyld::log("findInSharedCacheImage(), mh=%p, p->address=0x%0llX, slid=0x%0lX, path=%p\n", + //dyld::log("findInSharedCacheImage(), mh=%p, p->address=0x%0llX, slid=0x%0lX, path=%s\n", // *mh, p->address, sSharedCacheSlide, aPath); *pathInCache = aPath; *slide = sSharedCacheSlide; @@ -2422,28 +2895,36 @@ static ImageLoader* checkandAddImage(ImageLoader* image, const LoadContext& cont } #if TARGET_IPHONE_SIMULATOR -static bool isSimulatorBinary(const uint8_t* firstPage, const char* path) +static bool isSimulatorBinary(const uint8_t* firstPages, const char* path) { - const macho_header* mh = (macho_header*)firstPage; + const macho_header* mh = (macho_header*)firstPages; const uint32_t cmd_count = mh->ncmds; - const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); - const struct load_command* const cmdsReadEnd = (struct load_command*)(((char*)mh)+4096); + const load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); + const load_command* const cmdsEnd = (load_command*)((char*)cmds + mh->sizeofcmds); const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { - case LC_VERSION_MIN_IPHONEOS: - case LC_VERSION_MIN_TVOS: + #if TARGET_OS_WATCH case LC_VERSION_MIN_WATCHOS: return true; + #elif TARGET_OS_TV + case LC_VERSION_MIN_TVOS: + return true; + #elif TARGET_OS_IOS + case LC_VERSION_MIN_IPHONEOS: + return true; + #endif case LC_VERSION_MIN_MACOSX: // grandfather in a few libSystem dylibs - if (strstr(path, "/usr/lib/system") || strstr(path, "/usr/lib/libSystem")) - return true; + if ((strcmp(path, "/usr/lib/system/libsystem_kernel.dylib") == 0) || + (strcmp(path, "/usr/lib/system/libsystem_platform.dylib") == 0) || + (strcmp(path, "/usr/lib/system/libsystem_pthread.dylib") == 0)) + return true; return false; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); - if ( cmd > cmdsReadEnd ) - return true; + if ( cmd > cmdsEnd ) + return false; } return false; } @@ -2460,27 +2941,30 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* if ( (stat_buf.st_mode & S_IFMT) != S_IFREG ) throw "not a file"; - uint8_t firstPage[4096]; + uint8_t firstPages[MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE]; bool shortPage = false; // min mach-o file is 4K if ( fileLength < 4096 ) { - if ( pread(fd, firstPage, fileLength, 0) != (ssize_t)fileLength ) + if ( pread(fd, firstPages, fileLength, 0) != (ssize_t)fileLength ) throwf("pread of short file failed: %d", errno); shortPage = true; } else { - if ( pread(fd, firstPage, 4096,0) != 4096 ) + // optimistically read only first 4KB + if ( pread(fd, firstPages, 4096, 0) != 4096 ) throwf("pread of first 4K failed: %d", errno); } // if fat wrapper, find usable sub-file - const fat_header* fileStartAsFat = (fat_header*)firstPage; + const fat_header* fileStartAsFat = (fat_header*)firstPages; if ( fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { + if ( OSSwapBigToHostInt32(fileStartAsFat->nfat_arch) > ((4096 - sizeof(fat_header)) / sizeof(fat_arch)) ) + throwf("fat header too large: %u entries", OSSwapBigToHostInt32(fileStartAsFat->nfat_arch)); if ( fatFindBest(fileStartAsFat, &fileOffset, &fileLength) ) { if ( (fileOffset+fileLength) > (uint64_t)(stat_buf.st_size) ) throwf("truncated fat file. file length=%llu, but needed slice goes to %llu", stat_buf.st_size, fileOffset+fileLength); - if (pread(fd, firstPage, 4096, fileOffset) != 4096) + if (pread(fd, firstPages, 4096, fileOffset) != 4096) throwf("pread of fat file failed: %d", errno); } else { @@ -2491,10 +2975,11 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* // try mach-o loader if ( shortPage ) throw "file too short"; - if ( isCompatibleMachO(firstPage, path) ) { + if ( isCompatibleMachO(firstPages, path) ) { // only MH_BUNDLE, MH_DYLIB, and some MH_EXECUTE can be dynamically loaded - switch ( ((mach_header*)firstPage)->filetype ) { + const mach_header* mh = (mach_header*)firstPages; + switch ( mh->filetype ) { case MH_EXECUTE: case MH_DYLIB: case MH_BUNDLE: @@ -2503,19 +2988,35 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* throw "mach-o, but wrong filetype"; } + uint32_t headerAndLoadCommandsSize = sizeof(macho_header) + mh->sizeofcmds; + if ( headerAndLoadCommandsSize > MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE ) + throwf("malformed mach-o: load commands size (%u) > %u", headerAndLoadCommandsSize, MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE); + + if ( headerAndLoadCommandsSize > fileLength ) + dyld::throwf("malformed mach-o: load commands size (%u) > mach-o file size (%llu)", headerAndLoadCommandsSize, fileLength); + + if ( headerAndLoadCommandsSize > 4096 ) { + // read more pages + unsigned readAmount = headerAndLoadCommandsSize - 4096; + if ( pread(fd, &firstPages[4096], readAmount, fileOffset+4096) != readAmount ) + throwf("pread of extra load commands past 4KB failed: %d", errno); + } + #if TARGET_IPHONE_SIMULATOR - #if TARGET_OS_WATCH || TARGET_OS_TV - // disable error during bring up of these simulators - #else // dyld_sim should restrict loading osx binaries - if ( !isSimulatorBinary(firstPage, path) ) { + if ( !isSimulatorBinary(firstPages, path) ) { + #if TARGET_OS_WATCH + throw "mach-o, but not built for watchOS simulator"; + #elif TARGET_OS_TV + throw "mach-o, but not built for tvOS simulator"; + #else throw "mach-o, but not built for iOS simulator"; - } #endif + } #endif // instantiate an image - ImageLoader* image = ImageLoaderMachO::instantiateFromFile(path, fd, firstPage, fileOffset, fileLength, stat_buf, gLinkContext); + ImageLoader* image = ImageLoaderMachO::instantiateFromFile(path, fd, firstPages, headerAndLoadCommandsSize, fileOffset, fileLength, stat_buf, gLinkContext); // validate return checkandAddImage(image, context); @@ -2525,7 +3026,7 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* // throw error about what was found - switch (*(uint32_t*)firstPage) { + switch (*(uint32_t*)firstPages) { case MH_MAGIC: case MH_CIGAM: case MH_MAGIC_64: @@ -2533,7 +3034,7 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* throw "mach-o, but wrong architecture"; default: throwf("unknown file type, first eight bytes: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X", - firstPage[0], firstPage[1], firstPage[2], firstPage[3], firstPage[4], firstPage[5], firstPage[6],firstPage[7]); + firstPages[0], firstPages[1], firstPages[2], firstPages[3], firstPages[4], firstPages[5], firstPages[6],firstPages[7]); } } @@ -2549,7 +3050,11 @@ static ImageLoader* loadPhase5open(const char* path, const LoadContext& context, if ( file.getFileDescriptor() == -1 ) { int err = errno; if ( err != ENOENT ) { - const char* newMsg = dyld::mkstringf("%s: open() failed with errno=%d", path, err); + const char* newMsg; + if ( (err == EPERM) && sandboxBlockedOpen(path) ) + newMsg = dyld::mkstringf("file system sandbox blocked open() of '%s'", path); + else + newMsg = dyld::mkstringf("%s: open() failed with errno=%d", path, err); exceptions->push_back(newMsg); } return NULL; @@ -2568,21 +3073,32 @@ static ImageLoader* loadPhase5open(const char* path, const LoadContext& context, #if __MAC_OS_X_VERSION_MIN_REQUIRED -static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions) +static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector* exceptions) { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); ImageLoader* image = NULL; + #if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + unsigned index; + if ( sAllCacheImagesProxy->hasDylib(path, &index) ) + return sAllCacheImagesProxy; + } + #endif + // just return NULL if file not found, but record any other errors struct stat stat_buf; if ( my_stat(path, &stat_buf) == -1 ) { int err = errno; if ( err != ENOENT ) { - exceptions->push_back(dyld::mkstringf("%s: stat() failed with errno=%d", path, err)); + if ( (err == EPERM) && sandboxBlockedStat(path) ) + exceptions->push_back(dyld::mkstringf("%s: file system sandbox blocked stat()", path)); + else + exceptions->push_back(dyld::mkstringf("%s: stat() failed with errno=%d", path, err)); } return NULL; } - + // in case image was renamed or found via symlinks, check for inode match image = findLoadedImage(stat_buf); if ( image != NULL ) @@ -2641,7 +3157,7 @@ static ImageLoader* loadPhase5stat(const char* path, const LoadContext& context, } // try to open file -static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions) +static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector* exceptions) { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); struct stat stat_buf; @@ -2649,7 +3165,13 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const int statErrNo; ImageLoader* image; #if DYLD_SHARED_CACHE_SUPPORT - if ( sDylibsOverrideCache ) { + #if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + if ( sAllCacheImagesProxy->hasDylib(path, &cacheIndex) ) + return sAllCacheImagesProxy; + } + #endif + if ( dylibsCanOverrideCache() ) { // flag is set that allows installed framework roots to override dyld shared cache image = loadPhase5stat(path, context, &stat_buf, &statErrNo, &imageFound, exceptions); if ( imageFound ) @@ -2676,7 +3198,7 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const return checkandAddImage(image, context); } - if ( !sDylibsOverrideCache ) { + if ( !dylibsCanOverrideCache() ) { // flag is not set, and not in cache to try opening it image = loadPhase5stat(path, context, &stat_buf, &statErrNo, &imageFound, exceptions); if ( imageFound ) @@ -2689,7 +3211,10 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const #endif // just return NULL if file not found, but record any other errors if ( (statErrNo != ENOENT) && (statErrNo != 0) ) { - exceptions->push_back(dyld::mkstringf("%s: stat() failed with errno=%d", path, statErrNo)); + if ( (statErrNo == EPERM) && sandboxBlockedStat(path) ) + exceptions->push_back(dyld::mkstringf("%s: file system sandbox blocked stat()", path)); + else + exceptions->push_back(dyld::mkstringf("%s: stat() failed with errno=%d", path, statErrNo)); } return NULL; } @@ -2742,7 +3267,7 @@ static ImageLoader* loadPhase5check(const char* path, const char* orgPath, const // open or check existing -static ImageLoader* loadPhase5(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions) +static ImageLoader* loadPhase5(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector* exceptions) { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); @@ -2755,40 +3280,42 @@ static ImageLoader* loadPhase5(const char* path, const char* orgPath, const Load } if ( exceptions != NULL ) - return loadPhase5load(path, orgPath, context, exceptions); + return loadPhase5load(path, orgPath, context, cacheIndex, exceptions); else return loadPhase5check(path, orgPath, context); } // try with and without image suffix -static ImageLoader* loadPhase4(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions) +static ImageLoader* loadPhase4(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector* exceptions) { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); ImageLoader* image = NULL; if ( gLinkContext.imageSuffix != NULL ) { char pathWithSuffix[strlen(path)+strlen( gLinkContext.imageSuffix)+2]; ImageLoader::addSuffix(path, gLinkContext.imageSuffix, pathWithSuffix); - image = loadPhase5(pathWithSuffix, orgPath, context, exceptions); + image = loadPhase5(pathWithSuffix, orgPath, context, cacheIndex, exceptions); } if ( image == NULL ) - image = loadPhase5(path, orgPath, context, exceptions); + image = loadPhase5(path, orgPath, context, cacheIndex, exceptions); return image; } -static ImageLoader* loadPhase2(const char* path, const char* orgPath, const LoadContext& context, - const char* const frameworkPaths[], const char* const libraryPaths[], - std::vector* exceptions); // forward reference +static ImageLoader* loadPhase2(const char* path, const char* orgPath, const LoadContext& context, + const char* const frameworkPaths[], const char* const libraryPaths[], + unsigned& cacheIndex, std::vector* exceptions); // forward reference // expand @ variables -static ImageLoader* loadPhase3(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions) +static ImageLoader* loadPhase3(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector* exceptions) { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); ImageLoader* image = NULL; if ( strncmp(path, "@executable_path/", 17) == 0 ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED // executable_path cannot be in used in any binary in a setuid process rdar://problem/4589305 - if ( sProcessIsRestricted && !sProcessRequiresLibraryValidation ) + if ( gLinkContext.processIsRestricted ) throwf("unsafe use of @executable_path in %s with restricted binary", context.origin); +#endif // handle @executable_path path prefix const char* executablePath = sExecPath; char newPath[strlen(executablePath) + strlen(path)]; @@ -2798,7 +3325,7 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load strcpy(&addPoint[1], &path[17]); else strcpy(newPath, &path[17]); - image = loadPhase4(newPath, orgPath, context, exceptions); + image = loadPhase4(newPath, orgPath, context, cacheIndex, exceptions); if ( image != NULL ) return image; @@ -2807,20 +3334,22 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load if ( realpath(sExecPath, resolvedPath) != NULL ) { char newRealPath[strlen(resolvedPath) + strlen(path)]; strcpy(newRealPath, resolvedPath); - char* addPoint = strrchr(newRealPath,'/'); + addPoint = strrchr(newRealPath,'/'); if ( addPoint != NULL ) strcpy(&addPoint[1], &path[17]); else strcpy(newRealPath, &path[17]); - image = loadPhase4(newRealPath, orgPath, context, exceptions); + image = loadPhase4(newRealPath, orgPath, context, cacheIndex, exceptions); if ( image != NULL ) return image; } } else if ( (strncmp(path, "@loader_path/", 13) == 0) && (context.origin != NULL) ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED // @loader_path cannot be used from the main executable of a setuid process rdar://problem/4589305 - if ( sProcessIsRestricted && (strcmp(context.origin, sExecPath) == 0) && !sProcessRequiresLibraryValidation ) + if ( gLinkContext.processIsRestricted && (strcmp(context.origin, sExecPath) == 0) ) throwf("unsafe use of @loader_path in %s with restricted binary", context.origin); +#endif // handle @loader_path path prefix char newPath[strlen(context.origin) + strlen(path)]; strcpy(newPath, context.origin); @@ -2829,7 +3358,7 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load strcpy(&addPoint[1], &path[13]); else strcpy(newPath, &path[13]); - image = loadPhase4(newPath, orgPath, context, exceptions); + image = loadPhase4(newPath, orgPath, context, cacheIndex, exceptions); if ( image != NULL ) return image; @@ -2838,12 +3367,12 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load if ( realpath(context.origin, resolvedPath) != NULL ) { char newRealPath[strlen(resolvedPath) + strlen(path)]; strcpy(newRealPath, resolvedPath); - char* addPoint = strrchr(newRealPath,'/'); + addPoint = strrchr(newRealPath,'/'); if ( addPoint != NULL ) strcpy(&addPoint[1], &path[13]); else strcpy(newRealPath, &path[13]); - image = loadPhase4(newRealPath, orgPath, context, exceptions); + image = loadPhase4(newRealPath, orgPath, context, cacheIndex, exceptions); if ( image != NULL ) return image; } @@ -2857,9 +3386,10 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load const char* anRPath = *it; char newPath[strlen(anRPath) + strlen(trailingPath)+2]; strcpy(newPath, anRPath); - strcat(newPath, "/"); + if ( newPath[strlen(newPath)-1] != '/' ) + strcat(newPath, "/"); strcat(newPath, trailingPath); - image = loadPhase4(newPath, orgPath, context, exceptions); + image = loadPhase4(newPath, orgPath, context, cacheIndex, exceptions); if ( gLinkContext.verboseRPaths && (exceptions != NULL) ) { if ( image != NULL ) dyld::log("RPATH successful expansion of %s to: %s\n", orgPath, newPath); @@ -2874,7 +3404,7 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load // substitute @rpath with LD_LIBRARY_PATH if ( sEnv.LD_LIBRARY_PATH != NULL ) { - image = loadPhase2(trailingPath, orgPath, context, NULL, sEnv.LD_LIBRARY_PATH, exceptions); + image = loadPhase2(trailingPath, orgPath, context, NULL, sEnv.LD_LIBRARY_PATH, cacheIndex, exceptions); if ( image != NULL ) return image; } @@ -2883,18 +3413,20 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load if ( (exceptions != NULL) && (trailingPath != path) ) return NULL; } - else if (sProcessIsRestricted && (path[0] != '/' ) && !sProcessRequiresLibraryValidation) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + else if ( gLinkContext.processIsRestricted && (path[0] != '/' ) ) { throwf("unsafe use of relative rpath %s in %s with restricted binary", path, context.origin); } +#endif - return loadPhase4(path, orgPath, context, exceptions); + return loadPhase4(path, orgPath, context, cacheIndex, exceptions); } // try search paths -static ImageLoader* loadPhase2(const char* path, const char* orgPath, const LoadContext& context, +static ImageLoader* loadPhase2(const char* path, const char* orgPath, const LoadContext& context, const char* const frameworkPaths[], const char* const libraryPaths[], - std::vector* exceptions) + unsigned& cacheIndex, std::vector* exceptions) { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); ImageLoader* image = NULL; @@ -2908,7 +3440,7 @@ static ImageLoader* loadPhase2(const char* path, const char* orgPath, const Load strcat(npath, "/"); strcat(npath, frameworkPartialPath); //dyld::log("dyld: fallback framework path used: %s() -> loadPhase4(\"%s\", ...)\n", __func__, npath); - image = loadPhase4(npath, orgPath, context, exceptions); + image = loadPhase4(npath, orgPath, context, cacheIndex, exceptions); if ( image != NULL ) return image; } @@ -2925,7 +3457,7 @@ static ImageLoader* loadPhase2(const char* path, const char* orgPath, const Load strcat(libpath, "/"); strcat(libpath, libraryLeafName); //dyld::log("dyld: fallback library path used: %s() -> loadPhase4(\"%s\", ...)\n", __func__, libpath); - image = loadPhase4(libpath, orgPath, context, exceptions); + image = loadPhase4(libpath, orgPath, context, cacheIndex, exceptions); if ( image != NULL ) return image; } @@ -2934,27 +3466,27 @@ static ImageLoader* loadPhase2(const char* path, const char* orgPath, const Load } // try search overrides and fallbacks -static ImageLoader* loadPhase1(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions) +static ImageLoader* loadPhase1(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector* exceptions) { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); ImageLoader* image = NULL; // handle LD_LIBRARY_PATH environment variables that force searching if ( context.useLdLibraryPath && (sEnv.LD_LIBRARY_PATH != NULL) ) { - image = loadPhase2(path, orgPath, context, NULL, sEnv.LD_LIBRARY_PATH, exceptions); + image = loadPhase2(path, orgPath, context, NULL, sEnv.LD_LIBRARY_PATH, cacheIndex,exceptions); if ( image != NULL ) return image; } // handle DYLD_ environment variables that force searching if ( context.useSearchPaths && ((sEnv.DYLD_FRAMEWORK_PATH != NULL) || (sEnv.DYLD_LIBRARY_PATH != NULL)) ) { - image = loadPhase2(path, orgPath, context, sEnv.DYLD_FRAMEWORK_PATH, sEnv.DYLD_LIBRARY_PATH, exceptions); + image = loadPhase2(path, orgPath, context, sEnv.DYLD_FRAMEWORK_PATH, sEnv.DYLD_LIBRARY_PATH, cacheIndex, exceptions); if ( image != NULL ) return image; } // try raw path - image = loadPhase3(path, orgPath, context, exceptions); + image = loadPhase3(path, orgPath, context, cacheIndex, exceptions); if ( image != NULL ) return image; @@ -2963,7 +3495,7 @@ static ImageLoader* loadPhase1(const char* path, const char* orgPath, const Load if ( (fallbackLibraryPaths != NULL) && !context.useFallbackPaths ) fallbackLibraryPaths = NULL; if ( !context.dontLoad && (exceptions != NULL) && ((sEnv.DYLD_FALLBACK_FRAMEWORK_PATH != NULL) || (fallbackLibraryPaths != NULL)) ) { - image = loadPhase2(path, orgPath, context, sEnv.DYLD_FALLBACK_FRAMEWORK_PATH, fallbackLibraryPaths, exceptions); + image = loadPhase2(path, orgPath, context, sEnv.DYLD_FALLBACK_FRAMEWORK_PATH, fallbackLibraryPaths, cacheIndex, exceptions); if ( image != NULL ) return image; } @@ -2972,24 +3504,26 @@ static ImageLoader* loadPhase1(const char* path, const char* orgPath, const Load } // try root substitutions -static ImageLoader* loadPhase0(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions) +static ImageLoader* loadPhase0(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector* exceptions) { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); +#if SUPPORT_ROOT_PATH // handle DYLD_ROOT_PATH which forces absolute paths to use a new root if ( (gLinkContext.rootPaths != NULL) && (path[0] == '/') ) { for(const char* const* rootPath = gLinkContext.rootPaths ; *rootPath != NULL; ++rootPath) { char newPath[strlen(*rootPath) + strlen(path)+2]; strcpy(newPath, *rootPath); strcat(newPath, path); - ImageLoader* image = loadPhase1(newPath, orgPath, context, exceptions); + ImageLoader* image = loadPhase1(newPath, orgPath, context, cacheIndex, exceptions); if ( image != NULL ) return image; } } +#endif // try raw path - return loadPhase1(path, orgPath, context, exceptions); + return loadPhase1(path, orgPath, context, cacheIndex, exceptions); } #if DYLD_SHARED_CACHE_SUPPORT @@ -3016,10 +3550,11 @@ static ImageLoader* loadPhase0(const char* path, const char* orgPath, const Load // the path. Either time, if an image is found, the phases all unwind without checking // for other paths. // -ImageLoader* load(const char* path, const LoadContext& context) +ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex) { CRSetCrashLogMessage2(path); const char* orgPath = path; + cacheIndex = UINT32_MAX; //dyld::log("%s(%s)\n", __func__ , path); char realPath[PATH_MAX]; @@ -3030,7 +3565,8 @@ ImageLoader* load(const char* path, const LoadContext& context) } // try all path permutations and check against existing loaded images - ImageLoader* image = loadPhase0(path, orgPath, context, NULL); + + ImageLoader* image = loadPhase0(path, orgPath, context, cacheIndex, NULL); if ( image != NULL ) { CRSetCrashLogMessage2(NULL); return image; @@ -3038,7 +3574,7 @@ ImageLoader* load(const char* path, const LoadContext& context) // try all path permutations and try open() until first success std::vector exceptions; - image = loadPhase0(path, orgPath, context, &exceptions); + image = loadPhase0(path, orgPath, context, cacheIndex, &exceptions); #if __IPHONE_OS_VERSION_MIN_REQUIRED && DYLD_SHARED_CACHE_SUPPORT && !TARGET_IPHONE_SIMULATOR // support symlinks on disk to a path in dyld shared cache if ( (image == NULL) && cacheablePath(path) && !context.dontLoad ) { @@ -3151,21 +3687,68 @@ static int __attribute__((noinline)) _shared_region_check_np(uint64_t* start_add } -static int __attribute__((noinline)) _shared_region_map_and_slide_np(int fd, uint32_t count, const shared_file_mapping_np mappings[], - int codeSignatureMappingIndex, long slide, void* slideInfo, unsigned long slideInfoSize) +static void rebaseChain(uint8_t* pageContent, uint16_t startOffset, uintptr_t slideAmount, const dyld_cache_slide_info2* slideInfo) +{ + const uintptr_t deltaMask = (uintptr_t)(slideInfo->delta_mask); + const uintptr_t valueMask = ~deltaMask; + const uintptr_t valueAdd = (uintptr_t)(slideInfo->value_add); + const unsigned deltaShift = __builtin_ctzll(deltaMask) - 2; + + uint32_t pageOffset = startOffset; + uint32_t delta = 1; + while ( delta != 0 ) { + uint8_t* loc = pageContent + pageOffset; + uintptr_t rawValue = *((uintptr_t*)loc); + delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); + uintptr_t value = (rawValue & valueMask); + if ( value != 0 ) { + value += valueAdd; + value += slideAmount; + } + *((uintptr_t*)loc) = value; + //dyld::log(" pageOffset=0x%03X, loc=%p, org value=0x%08llX, new value=0x%08llX, delta=0x%X\n", pageOffset, loc, (uint64_t)rawValue, (uint64_t)value, delta); + pageOffset += delta; + } +} + + +static void loadAndCheckCodeSignature(int fd, uint32_t count, const shared_file_mapping_np mappings[], + off_t codeSignatureOffset, size_t codeSignatureSize, + const void *firstPages, size_t firstPagesSize) { // register code signature blob for whole dyld cache - if ( codeSignatureMappingIndex != -1 ) { - fsignatures_t siginfo; - siginfo.fs_file_start = 0; // cache always starts at beginning of file - siginfo.fs_blob_start = (void*)mappings[codeSignatureMappingIndex].sfm_file_offset; - siginfo.fs_blob_size = mappings[codeSignatureMappingIndex].sfm_size; - int result = fcntl(fd, F_ADDFILESIGS, &siginfo); + fsignatures_t siginfo; + siginfo.fs_file_start = 0; // cache always starts at beginning of file + siginfo.fs_blob_start = (void*)codeSignatureOffset; + siginfo.fs_blob_size = codeSignatureSize; + + int result = fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo); // don't warn in chrooted case because mapping syscall is about to fail too - if ( (result == -1) && gLinkContext.verboseMapping ) + if ( result == -1 ) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED + throwf("code signature registration for shared cache failed with errno=%d\n", errno); +#else + if ( gLinkContext.verboseMapping ) dyld::log("dyld: code signature registration for shared cache failed with errno=%d\n", errno); +#endif } + uint64_t codeSignedLength = siginfo.fs_file_start; + for (uint32_t i = 0; i < count; ++i) { + if ( (mappings[i].sfm_size > codeSignedLength) || (mappings[i].sfm_file_offset > (codeSignedLength - mappings[i].sfm_size)) ) + throw "dyld shared cache mapping not covered by code signature"; + } + + void *fdata = xmmap(NULL, firstPagesSize, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0); + if ( fdata == MAP_FAILED ) + throwf("mmap() errno=%d validating first page of shared cache", errno); + if ( memcmp(fdata, firstPages, firstPagesSize) != 0 ) + throwf("mmap() page compare failed for shared cache"); + munmap(fdata, firstPagesSize); +} +static int __attribute__((noinline)) _shared_region_map_and_slide_np(int fd, uint32_t count, const shared_file_mapping_np mappings[], + long slide, void* slideInfo, unsigned long slideInfoSize) +{ if ( gLinkContext.sharedRegionMode == ImageLoader::kUseSharedRegion ) { return syscall(438, fd, count, mappings, slide, slideInfo, slideInfoSize); } @@ -3207,9 +3790,40 @@ static int __attribute__((noinline)) _shared_region_map_and_slide_np(int fd, uin } // update all __DATA pages with slide info - if ( slide != 0 ) { + const dyld_cache_slide_info* slideInfoHeader = (dyld_cache_slide_info*)slideInfo; + if ( slideInfoHeader->version == 2 ) { + const dyld_cache_slide_info2* slideHeader = (dyld_cache_slide_info2*)slideInfo; + const uint32_t page_size = slideHeader->page_size; + const uint16_t* page_starts = (uint16_t*)((long)(slideInfo) + slideHeader->page_starts_offset); + const uint16_t* page_extras = (uint16_t*)((long)(slideInfo) + slideHeader->page_extras_offset); + const uintptr_t dataPagesStart = mappings[1].sfm_address; + for (int i=0; i < slideHeader->page_starts_count; ++i) { + uint8_t* page = (uint8_t*)(long)(dataPagesStart + (page_size*i)); + uint16_t pageEntry = page_starts[i]; + //dyld::log("page[%d]: page_starts[i]=0x%04X\n", i, pageEntry); + if ( pageEntry == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE ) + continue; + if ( pageEntry & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA ) { + uint16_t chainIndex = (pageEntry & 0x3FFF); + bool done = false; + while ( !done ) { + uint16_t info = page_extras[chainIndex]; + uint16_t pageStartOffset = (info & 0x3FFF)*4; + //dyld::log(" chain[%d] pageOffset=0x%03X\n", chainIndex, pageStartOffset); + rebaseChain(page, pageStartOffset, slide, slideHeader); + done = (info & DYLD_CACHE_SLIDE_PAGE_ATTR_END); + ++chainIndex; + } + } + else { + uint32_t pageOffset = pageEntry * 4; + //dyld::log(" start pageOffset=0x%03X\n", pageOffset); + rebaseChain(page, pageOffset, slide, slideHeader); + } + } + } + else if ( slide != 0 ) { const uintptr_t dataPagesStart = mappings[1].sfm_address; - const dyld_cache_slide_info* slideInfoHeader = (dyld_cache_slide_info*)slideInfo; const uint16_t* toc = (uint16_t*)((long)(slideInfoHeader) + slideInfoHeader->toc_offset); const uint8_t* entries = (uint8_t*)((long)(slideInfoHeader) + slideInfoHeader->entries_offset); for(uint32_t i=0; i < slideInfoHeader->toc_count; ++i) { @@ -3280,7 +3894,17 @@ int openSharedCacheFile() } #endif strlcat(path, DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, MAXPATHLEN); - if ( gLinkContext.verboseMapping ) +#if __IPHONE_OS_VERSION_MIN_REQUIRED + struct stat enableStatBuf; + struct stat devCacheStatBuf; + struct stat prodCacheStatBuf; + if ( ((my_stat(IPHONE_DYLD_SHARED_CACHE_DIR "enable-dylibs-to-override-cache", &enableStatBuf) == 0) + && (enableStatBuf.st_size < ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE) + && (my_stat(IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME DYLD_SHARED_CACHE_DEVELOPMENT_EXT, &devCacheStatBuf) == 0)) + || (my_stat(IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &prodCacheStatBuf) != 0)) + strlcat(path, DYLD_SHARED_CACHE_DEVELOPMENT_EXT, MAXPATHLEN); +#endif + if ( gLinkContext.verboseMapping ) dyld::log("dyld: Mapping%s shared cache from %s\n", (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion) ? " private": "", path); return my_open(path, O_RDONLY, 0); } @@ -3342,10 +3966,15 @@ static long pickCacheSlide(uint32_t mappingsCount, shared_file_mapping_np mappin const uint64_t space = (SHARED_REGION_BASE + SHARED_REGION_SIZE) - highAddress; // choose new random slide +#if __arm__ + // change shared cache slide for 32-bit arm to always be 16k aligned + long slide = ((arc4random() % space) & (-16384)); +#else long slide = dyld_page_trunc(arc4random() % space); +#endif //dyld::log("slideSpace=0x%0llX\n", space); //dyld::log("slide=0x%0lX\n", slide); - + // update mappings for(uint32_t i=0; i < mappingsCount; ++i) { mappings[i].sfm_address += slide; @@ -3382,6 +4011,7 @@ static void mapSharedCache() const uint8_t* preferedLoadAddress = (uint8_t*)(long)(mappings[0].address); sSharedCacheSlide = loadedAddress - preferedLoadAddress; dyld::gProcessInfo->sharedCacheSlide = sSharedCacheSlide; + dyld::gProcessInfo->sharedCacheBaseAddress = cacheBaseAddress; //dyld::log("sSharedCacheSlide=0x%08lX, loadedAddress=%p, preferedLoadAddress=%p\n", sSharedCacheSlide, loadedAddress, preferedLoadAddress); } // if cache has a uuid, copy it @@ -3390,7 +4020,17 @@ static void mapSharedCache() } // verbose logging if ( gLinkContext.verboseMapping ) { - dyld::log("dyld: re-using existing shared cache mapping\n"); + dyld::log("dyld: re-using existing %s shared cache mapping\n", (header->cacheType == kDyldSharedCacheTypeDevelopment ? "development" : "production")); + } + if (header->mappingOffset >= 0x68) { + dyld_kernel_image_info_t kernelCacheInfo; + memcpy(&kernelCacheInfo.uuid[0], &sSharedCache->uuid[0], sizeof(uuid_t)); + kernelCacheInfo.load_addr = (uint64_t)sSharedCache; + kernelCacheInfo.fsobjid.fid_objno = 0; + kernelCacheInfo.fsobjid.fid_generation = 0; + kernelCacheInfo.fsid.val[0] = 0; + kernelCacheInfo.fsid.val[0] = 0; + task_register_dyld_shared_cache_image_info(mach_task_self(), kernelCacheInfo, false, false); } } else { @@ -3432,9 +4072,19 @@ static void mapSharedCache() if ( strcmp(header->magic, magic) == 0 ) { const dyld_cache_mapping_info* const fileMappingsStart = (dyld_cache_mapping_info*)&firstPages[header->mappingOffset]; const dyld_cache_mapping_info* const fileMappingsEnd = &fileMappingsStart[header->mappingCount]; - shared_file_mapping_np mappings[header->mappingCount+1]; // add room for code-sig + #if __IPHONE_OS_VERSION_MIN_REQUIRED + if ( (header->mappingCount != 3) + || (header->mappingOffset > 256) + || (fileMappingsStart[0].fileOffset != 0) + || (fileMappingsStart[0].address != SHARED_REGION_BASE) + || ((fileMappingsStart[0].address + fileMappingsStart[0].size) > fileMappingsStart[1].address) + || ((fileMappingsStart[1].address + fileMappingsStart[1].size) > fileMappingsStart[2].address) + || ((fileMappingsStart[0].fileOffset + fileMappingsStart[0].size) != fileMappingsStart[1].fileOffset) + || ((fileMappingsStart[1].fileOffset + fileMappingsStart[1].size) != fileMappingsStart[2].fileOffset) ) + throw "dyld shared cache file is invalid"; + #endif + shared_file_mapping_np mappings[header->mappingCount]; unsigned int mappingCount = header->mappingCount; - int codeSignatureMappingIndex = -1; int readWriteMappingIndex = -1; int readOnlyMappingIndex = -1; // validate that the cache file has not been truncated @@ -3468,18 +4118,19 @@ static void mapSharedCache() if ( signatureSize == 0 ) signatureSize = stat_buf.st_size - header->codeSignatureOffset; if ( signatureSize != 0 ) { - int linkeditMapping = mappingCount-1; - codeSignatureMappingIndex = mappingCount++; - mappings[codeSignatureMappingIndex].sfm_address = mappings[linkeditMapping].sfm_address + mappings[linkeditMapping].sfm_size; #if __arm__ || __arm64__ - mappings[codeSignatureMappingIndex].sfm_size = (signatureSize+16383) & (-16384); + size_t alignedSignatureSize = (signatureSize+16383) & (-16384); #else - mappings[codeSignatureMappingIndex].sfm_size = (signatureSize+4095) & (-4096); + size_t alignedSignatureSize = (signatureSize+4095) & (-4096); #endif - mappings[codeSignatureMappingIndex].sfm_file_offset = header->codeSignatureOffset; - mappings[codeSignatureMappingIndex].sfm_max_prot = VM_PROT_READ; - mappings[codeSignatureMappingIndex].sfm_init_prot = VM_PROT_READ; + // validate code signature covers entire shared cache + loadAndCheckCodeSignature(fd, mappingCount, mappings, header->codeSignatureOffset, alignedSignatureSize, firstPages, sizeof(firstPages)); + } +#if __IPHONE_OS_VERSION_MIN_REQUIRED + else { + throw "dyld shared cache file not code signed"; } +#endif } #if __MAC_OS_X_VERSION_MIN_REQUIRED // sanity check that /usr/lib/libSystem.B.dylib stat() info matches cache @@ -3522,22 +4173,23 @@ static void mapSharedCache() } if ( goodCache ) { long cacheSlide = 0; - void* slideInfo = NULL; - uint64_t slideInfoSize = 0; + void* slideInfo = (void*)(long)(mappings[readOnlyMappingIndex].sfm_address + (header->slideInfoOffset - mappings[readOnlyMappingIndex].sfm_file_offset));; + uint64_t slideInfoSize = header->slideInfoSize; // check if shared cache contains slid info - if ( header->slideInfoSize != 0 ) { + if ( slideInfoSize != 0 ) { // don't slide shared cache if ASLR disabled (main executable didn't slide) - if ( sMainExecutable->isPositionIndependentExecutable() && (sMainExecutable->getSlide() == 0) ) + if ( sMainExecutable->isPositionIndependentExecutable() && (sMainExecutable->getSlide() == 0) ) { cacheSlide = 0; + } else { // generate random slide amount cacheSlide = pickCacheSlide(mappingCount, mappings); - slideInfo = (void*)(long)(mappings[readOnlyMappingIndex].sfm_address + (header->slideInfoOffset - mappings[readOnlyMappingIndex].sfm_file_offset)); - slideInfoSize = header->slideInfoSize; - // add VM_PROT_SLIDE bit to __DATA area of cache - mappings[readWriteMappingIndex].sfm_max_prot |= VM_PROT_SLIDE; - mappings[readWriteMappingIndex].sfm_init_prot |= VM_PROT_SLIDE; } + + slideInfo = (void*)((uint8_t*)slideInfo + cacheSlide); + // add VM_PROT_SLIDE bit to __DATA area of cache + mappings[readWriteMappingIndex].sfm_max_prot |= VM_PROT_SLIDE; + mappings[readWriteMappingIndex].sfm_init_prot |= VM_PROT_SLIDE; } if ( gLinkContext.verboseMapping ) { dyld::log("dyld: calling _shared_region_map_and_slide_np() with regions:\n"); @@ -3545,20 +4197,43 @@ static void mapSharedCache() dyld::log(" address=0x%08llX, size=0x%08llX, fileOffset=0x%08llX\n", mappings[i].sfm_address, mappings[i].sfm_size, mappings[i].sfm_file_offset); } } - if (_shared_region_map_and_slide_np(fd, mappingCount, mappings, codeSignatureMappingIndex, cacheSlide, slideInfo, slideInfoSize) == 0) { + + if (_shared_region_map_and_slide_np(fd, mappingCount, mappings, cacheSlide, slideInfo, slideInfoSize) == 0) { // successfully mapped cache into shared region sSharedCache = (dyld_cache_header*)mappings[0].sfm_address; sSharedCacheSlide = cacheSlide; dyld::gProcessInfo->sharedCacheSlide = cacheSlide; + dyld::gProcessInfo->sharedCacheBaseAddress = mappings[0].sfm_address; //dyld::log("sSharedCache=%p sSharedCacheSlide=0x%08lX\n", sSharedCache, sSharedCacheSlide); // if cache has a uuid, copy it if ( header->mappingOffset >= 0x68 ) { + const bool privateSharedCache = gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion; memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16); + dyld_kernel_image_info_t kernelCacheInfo; + memcpy(&kernelCacheInfo.uuid[0], &sSharedCache->uuid[0], sizeof(uuid_t)); + kernelCacheInfo.load_addr = (uint64_t)sSharedCache; + kernelCacheInfo.fsobjid.fid_objno = 0; + kernelCacheInfo.fsobjid.fid_generation = 0; + kernelCacheInfo.fsid.val[0] = 0; + kernelCacheInfo.fsid.val[0] = 0; + if (privateSharedCache) { + kernelCacheInfo.fsobjid = *(fsobj_id_t*)(&stat_buf.st_ino); + struct statfs statfs_buf; + if ( fstatfs(fd, &statfs_buf) == 0 ) { + kernelCacheInfo.fsid = statfs_buf.f_fsid; + } + } + task_register_dyld_shared_cache_image_info(mach_task_self(), kernelCacheInfo, false, privateSharedCache); } } else { #if __IPHONE_OS_VERSION_MIN_REQUIRED - throw "dyld shared cache could not be mapped"; + throwf("dyld shared cache could not be mapped. errno=%d, slide=0x%08lX, slideInfo=%p, slideInfoSize=0x%08llX, mappingCount=%u, " + "address/size/off/init/max [0]=0x%0llX/0x%0llX/0x%0llX/0x%02X/0x%02X, [1]=0x%0llX/0x%0llX/0x%0llX/0x%02X/0x%02X, [2]=0x%0llX/0x%0llX/0x%0llX/0x%02X/0x%02X", + errno, cacheSlide, slideInfo, slideInfoSize, mappingCount, + mappings[0].sfm_address, mappings[0].sfm_size, mappings[0].sfm_file_offset, mappings[0].sfm_init_prot, mappings[0].sfm_max_prot, + mappings[1].sfm_address, mappings[1].sfm_size, mappings[1].sfm_file_offset, mappings[1].sfm_init_prot, mappings[1].sfm_max_prot, + mappings[2].sfm_address, mappings[2].sfm_size, mappings[2].sfm_file_offset, mappings[2].sfm_init_prot, mappings[2].sfm_max_prot); #endif if ( gLinkContext.verboseMapping ) dyld::log("dyld: shared cached file could not be mapped\n"); @@ -3639,14 +4314,11 @@ static void mapSharedCache() dyld::log(" 0x%08llX->0x%08llX (code signature)\n", codeBlobStart, codeBlobStart+signatureSize); } } -#if __IPHONE_OS_VERSION_MIN_REQUIRED - // check for file that enables dyld shared cache dylibs to be overridden - struct stat enableStatBuf; - // check file size to determine if correct file is in place. - // See Need a way to disable roots without removing /S/L/C/com.apple.dyld/enable... - sDylibsOverrideCache = ( (my_stat(IPHONE_DYLD_SHARED_CACHE_DIR "enable-dylibs-to-override-cache", &enableStatBuf) == 0) - && (enableStatBuf.st_size < ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE) ); -#endif + #if SUPPORT_ACCELERATE_TABLES + if ( !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCache->mappingOffset > 0x80) && (sSharedCache->accelerateInfoAddr != 0) ) { + sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(sSharedCache, sSharedCacheSlide, gLinkContext); + } + #endif } } #endif // #if DYLD_SHARED_CACHE_SUPPORT @@ -3731,6 +4403,15 @@ void registerAddCallback(ImageCallback func) if ( image->getState() >= dyld_image_state_bound && image->getState() < dyld_image_state_terminated ) (*func)(image->machHeader(), image->getSlide()); } +#if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + dyld_image_info infos[allImagesCount()+1]; + unsigned cacheCount = sAllCacheImagesProxy->appendImagesToNotify(dyld_image_state_bound, true, infos); + for (unsigned i=0; i < cacheCount; ++i) { + (*func)(infos[i].imageLoadAddress, sSharedCacheSlide); + } + } +#endif } void registerRemoveCallback(ImageCallback func) @@ -3757,16 +4438,40 @@ const char* getErrorMessage() return error_string; } - void halt(const char* message) { dyld::log("dyld: %s\n", message); setErrorMessage(message); - uintptr_t terminationFlags = 0; - if ( !gLinkContext.startedInitializingMainExecutable ) - terminationFlags = 1; - setAlImageInfosHalt(error_string, terminationFlags); - dyld_fatal_error(error_string); + dyld::gProcessInfo->errorMessage = error_string; + if ( !gLinkContext.startedInitializingMainExecutable ) + dyld::gProcessInfo->terminationFlags = 1; + else + dyld::gProcessInfo->terminationFlags = 0; + + char payloadBuffer[EXIT_REASON_PAYLOAD_MAX_LEN]; + dyld_abort_payload* payload = (dyld_abort_payload*)payloadBuffer; + payload->version = 1; + payload->flags = gLinkContext.startedInitializingMainExecutable ? 0 : 1; + payload->targetDylibPathOffset = 0; + payload->clientPathOffset = 0; + payload->symbolOffset = 0; + int payloadSize = sizeof(dyld_abort_payload); + + if ( dyld::gProcessInfo->errorTargetDylibPath != NULL ) { + payload->targetDylibPathOffset = payloadSize; + payloadSize += strlcpy(&payloadBuffer[payloadSize], dyld::gProcessInfo->errorTargetDylibPath, sizeof(payloadBuffer)-payloadSize) + 1; + } + if ( dyld::gProcessInfo->errorClientOfDylibPath != NULL ) { + payload->clientPathOffset = payloadSize; + payloadSize += strlcpy(&payloadBuffer[payloadSize], dyld::gProcessInfo->errorClientOfDylibPath, sizeof(payloadBuffer)-payloadSize) + 1; + } + if ( dyld::gProcessInfo->errorSymbol != NULL ) { + payload->symbolOffset = payloadSize; + payloadSize += strlcpy(&payloadBuffer[payloadSize], dyld::gProcessInfo->errorSymbol, sizeof(payloadBuffer)-payloadSize) + 1; + } + char truncMessage[EXIT_REASON_USER_DESC_MAX_LEN]; + strlcpy(truncMessage, message, EXIT_REASON_USER_DESC_MAX_LEN); + abort_with_payload(OS_REASON_DYLD, dyld::gProcessInfo->errorKind ? dyld::gProcessInfo->errorKind : DYLD_EXIT_REASON_OTHER, payloadBuffer, payloadSize, truncMessage, 0); } static void setErrorStrings(unsigned errorCode, const char* errorClientOfDylibPath, @@ -3826,6 +4531,20 @@ uintptr_t fastBindLazySymbol(ImageLoader** imageLoaderCache, uintptr_t lazyBindi // save in cache *imageLoaderCache = dyld::findMappedRange((uintptr_t)imageLoaderCache); if ( *imageLoaderCache == NULL ) { +#if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + const mach_header* mh; + const char* path; + unsigned index; + if ( sAllCacheImagesProxy->addressInCache(imageLoaderCache, &mh, &path, &index) ) { + result = sAllCacheImagesProxy->bindLazy(lazyBindingInfoOffset, gLinkContext, mh, index); + if ( result == 0 ) { + halt("dyld: lazy symbol binding failed for image in dyld shared\n"); + } + return result; + } + } +#endif const char* message = "fast lazy binding from unknown image"; dyld::log("dyld: %s\n", message); halt(message); @@ -3900,7 +4619,13 @@ static bool findExportedSymbol(const char* name, bool onlyInCoalesced, const Ima *image = firstWeakImage; return true; } - +#if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + if ( sAllCacheImagesProxy->flatFindSymbol(name, onlyInCoalesced, sym, image) ) + return true; + } +#endif + return false; } @@ -3932,16 +4657,23 @@ bool flatFindExportedSymbolWithHint(const char* name, const char* librarySubstri return false; } -unsigned int getCoalescedImages(ImageLoader* images[]) + +unsigned int getCoalescedImages(ImageLoader* images[], unsigned imageIndex[]) { unsigned int count = 0; for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { ImageLoader* image = *it; if ( image->participatesInCoalescing() ) { - *images++ = *it; + images[count] = *it; + imageIndex[count] = 0; ++count; } } +#if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + sAllCacheImagesProxy->appendImagesNeedingCoalescing(images, imageIndex, count); + } +#endif return count; } @@ -4002,7 +4734,7 @@ void registerImageStateBatchChangeHandler(dyld_image_states state, dyld_image_st // call callback with all existing images try { - notifyBatchPartial(state, true, handler); + notifyBatchPartial(state, true, handler, false, false); } catch (const char* msg) { // ignore request to abort during registration @@ -4010,7 +4742,113 @@ void registerImageStateBatchChangeHandler(dyld_image_states state, dyld_image_st } } -static ImageLoader* libraryLocator(const char* libraryName, bool search, const char* origin, const ImageLoader::RPathChain* rpaths) + +void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) +{ + // record functions to call + sNotifyObjCMapped = mapped; + sNotifyObjCInit = init; + sNotifyObjCUnmapped = unmapped; + + // call 'mapped' function with all images mapped so far + try { + notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true); + } + catch (const char* msg) { + // ignore request to abort during registration + } +} + +bool sharedCacheUUID(uuid_t uuid) +{ +#if DYLD_SHARED_CACHE_SUPPORT + if ( sSharedCache == NULL ) + return false; + + memcpy(uuid, sSharedCache->uuid, 16); + return true; +#else + return false; +#endif +} + +#if SUPPORT_ACCELERATE_TABLES + +bool dlopenFromCache(const char* path, int mode, void** handle) +{ + if ( sAllCacheImagesProxy == NULL ) + return false; + bool result = sAllCacheImagesProxy->dlopenFromCache(gLinkContext, path, mode, handle); + if ( !result && (strchr(path, '/') == NULL) ) { + // POSIX says you can call dlopen() with a leaf name (e.g. dlopen("libz.dylb")) + char fallbackPath[PATH_MAX]; + strcpy(fallbackPath, "/usr/lib/"); + strlcat(fallbackPath, path, PATH_MAX); + result = sAllCacheImagesProxy->dlopenFromCache(gLinkContext, fallbackPath, mode, handle); + } + return result; +} + +bool makeCacheHandle(ImageLoader* image, unsigned cacheIndex, int mode, void** result) +{ + if ( sAllCacheImagesProxy == NULL ) + return false; + return sAllCacheImagesProxy->makeCacheHandle(gLinkContext, cacheIndex, mode, result); +} + +bool isCacheHandle(void* handle) +{ + if ( sAllCacheImagesProxy == NULL ) + return false; + return sAllCacheImagesProxy->isCacheHandle(handle, NULL, NULL); +} + +bool isPathInCache(const char* path) +{ + if ( sAllCacheImagesProxy == NULL ) + return false; + unsigned index; + return sAllCacheImagesProxy->hasDylib(path, &index); +} + +const char* getPathFromIndex(unsigned cacheIndex) +{ + if ( sAllCacheImagesProxy == NULL ) + return NULL; + return sAllCacheImagesProxy->getIndexedPath(cacheIndex); +} + +void* dlsymFromCache(void* handle, const char* symName, unsigned index) +{ + if ( sAllCacheImagesProxy == NULL ) + return NULL; + return sAllCacheImagesProxy->dlsymFromCache(gLinkContext, handle, symName, index); +} + +bool addressInCache(const void* address, const mach_header** mh, const char** path, unsigned* index) +{ + if ( sAllCacheImagesProxy == NULL ) + return false; + unsigned ignore; + return sAllCacheImagesProxy->addressInCache(address, mh, path, index ? index : &ignore); +} + +bool findUnwindSections(const void* addr, dyld_unwind_sections* info) +{ + if ( sAllCacheImagesProxy == NULL ) + return false; + return sAllCacheImagesProxy->findUnwindSections(addr, info); +} + +bool dladdrFromCache(const void* address, Dl_info* info) +{ + if ( sAllCacheImagesProxy == NULL ) + return false; + return sAllCacheImagesProxy->dladdrFromCache(address, info); +} +#endif + +static ImageLoader* libraryLocator(const char* libraryName, bool search, const char* origin, const ImageLoader::RPathChain* rpaths, unsigned& cacheIndex) { dyld::LoadContext context; context.useSearchPaths = search; @@ -4024,7 +4862,7 @@ static ImageLoader* libraryLocator(const char* libraryName, bool search, const c context.canBePIE = false; context.origin = origin; context.rpath = rpaths; - return load(libraryName, context); + return load(libraryName, context, cacheIndex); } static const char* basename(const char* path) @@ -4064,6 +4902,11 @@ static void setContext(const macho_header* mainExecutableMH, int argc, const cha #endif gLinkContext.findImageContainingAddress = &findImageContainingAddress; gLinkContext.addDynamicReference = &addDynamicReference; +#if SUPPORT_ACCELERATE_TABLES + gLinkContext.notifySingleFromCache = ¬ifySingleFromCache; + gLinkContext.getPreInitNotifyHandler= &getPreInitNotifyHandler; + gLinkContext.getBoundBatchHandler = &getBoundBatchHandler; +#endif gLinkContext.bindingOptions = ImageLoader::kBindingNone; gLinkContext.argc = argc; gLinkContext.argv = argv; @@ -4123,6 +4966,23 @@ static bool hasRestrictedSegment(const macho_header* mh) return false; } +#if __IPHONE_OS_VERSION_MIN_REQUIRED +static bool isFairPlayEncrypted(const macho_header* mh) +{ + const uint32_t cmd_count = mh->ncmds; + const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_ENCRYPT_COMMAND ) { + const encryption_info_command* enc = (encryption_info_command*)cmd; + return (enc->cryptid != 0); + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + + return false; +} +#endif #if SUPPORT_VERSIONED_PATHS @@ -4223,7 +5083,7 @@ static void printAllImages() } #endif -void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths) +void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex) { // add to list of known images. This did not happen at creation time for bundles if ( image->isBundle() && !image->isLinked() ) @@ -4235,7 +5095,12 @@ void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const Imag // process images try { - image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths); + const char* path = image->getPath(); +#if SUPPORT_ACCELERATE_TABLES + if ( image == sAllCacheImagesProxy ) + path = sAllCacheImagesProxy->getIndexedPath(cacheIndex); +#endif + image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path); } catch (const char* msg) { garbageCollectImages(); @@ -4247,7 +5112,7 @@ void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const Imag void runInitializers(ImageLoader* image) { // do bottom up initialization - ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()]; + ImageLoader::InitializerTimingList initializerTimes[allImagesCount()]; initializerTimes[0].count = 0; image->runInitializers(gLinkContext, initializerTimes[0]); } @@ -4290,7 +5155,7 @@ void garbageCollectImages() // sweep phase: mark as in-use, images reachable from never-unload or in-use image for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { ImageLoader* image = *it; - if ( (image->dlopenCount() != 0) || image->neverUnload() ) { + if ( (image->dlopenCount() != 0) || image->neverUnload() || (image == sMainExecutable) ) { OSSpinLockLock(&sDynamicReferencesLock); image->markedUsedRecursive(sDynamicReferences); OSSpinLockUnlock(&sDynamicReferencesLock); @@ -4301,13 +5166,11 @@ void garbageCollectImages() ImageLoader* deadImages[sAllImages.size()]; unsigned deadCount = 0; int maxRangeCount = 0; - unsigned i = 0; for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { ImageLoader* image = *it; if ( ! image->isMarkedInUse() ) { - deadImages[i++] = image; + deadImages[deadCount++] = image; if (gLogAPIs) dyld::log("dlclose(), found unused image %p %s\n", image, image->getShortName()); - ++deadCount; maxRangeCount += image->segmentCount(); } } @@ -4377,12 +5240,17 @@ static void preflight_finally(ImageLoader* image) } -void preflight(ImageLoader* image, const ImageLoader::RPathChain& loaderRPaths) +void preflight(ImageLoader* image, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex) { try { if ( image->isBundle() ) sBundleBeingLoaded = image; // hack - image->link(gLinkContext, false, true, false, loaderRPaths); + const char* path = image->getPath(); +#if SUPPORT_ACCELERATE_TABLES + if ( image == sAllCacheImagesProxy ) + path = sAllCacheImagesProxy->getIndexedPath(cacheIndex); +#endif + image->link(gLinkContext, false, true, false, loaderRPaths, path); } catch (const char* msg) { preflight_finally(image); @@ -4394,6 +5262,7 @@ void preflight(ImageLoader* image, const ImageLoader::RPathChain& loaderRPaths) static void loadInsertedDylib(const char* path) { ImageLoader* image = NULL; + unsigned cacheIndex; try { LoadContext context; context.useSearchPaths = false; @@ -4407,15 +5276,17 @@ static void loadInsertedDylib(const char* path) context.canBePIE = false; context.origin = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES context.rpath = NULL; - image = load(path, context); + image = load(path, context, cacheIndex); } catch (const char* msg) { #if TARGET_IPHONE_SIMULATOR dyld::log("dyld: warning: could not load inserted library '%s' because %s\n", path, msg); #else - if ( sProcessRequiresLibraryValidation ) +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.processUsingLibraryValidation ) dyld::log("dyld: warning: could not load inserted library '%s' into library validated process because %s\n", path, msg); else +#endif halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg)); #endif } @@ -4424,53 +5295,79 @@ static void loadInsertedDylib(const char* path) } } -static bool processRestricted(const macho_header* mainExecutableMH, bool* ignoreEnvVars, bool* processRequiresLibraryValidation) -{ -#if TARGET_IPHONE_SIMULATOR - gLinkContext.codeSigningEnforced = true; -#else - // ask kernel if code signature of program makes it restricted + +// +// Sets: +// sEnvMode +// gLinkContext.requireCodeSignature +// gLinkContext.processIsRestricted // Mac OS X only +// gLinkContext.processUsingLibraryValidation // Mac OS X only +// +static void configureProcessRestrictions(const macho_header* mainExecutableMH) +{ uint32_t flags; +#if TARGET_IPHONE_SIMULATOR + sEnvMode = envAll; + gLinkContext.requireCodeSignature = true; +#elif __IPHONE_OS_VERSION_MIN_REQUIRED + sEnvMode = envNone; + gLinkContext.requireCodeSignature = true; if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { - if (flags & CS_REQUIRE_LV) - *processRequiresLibraryValidation = true; - - #if __MAC_OS_X_VERSION_MIN_REQUIRED if ( flags & CS_ENFORCEMENT ) { - gLinkContext.codeSigningEnforced = true; + if ( flags & CS_GET_TASK_ALLOW ) { + // Xcode built app for Debug allowed to use DYLD_* variables + sEnvMode = envAll; + } + else { + // Development kernel can use DYLD_PRINT_* variables on any FairPlay encrypted app + uint32_t secureValue = 0; + size_t secureValueSize = sizeof(secureValue); + if ( (sysctlbyname("kern.secure_kernel", &secureValue, &secureValueSize, NULL, 0) == 0) && (secureValue == 0) && isFairPlayEncrypted(mainExecutableMH) ) { + sEnvMode = envPrintOnly; + } + } } + else { + // Development kernel can run unsigned code + sEnvMode = envAll; + gLinkContext.requireCodeSignature = false; + } + } + if ( issetugid() ) { + sEnvMode = envNone; + } +#elif __MAC_OS_X_VERSION_MIN_REQUIRED + sEnvMode = envAll; + gLinkContext.requireCodeSignature = false; + gLinkContext.processIsRestricted = false; + gLinkContext.processUsingLibraryValidation = false; + // any processes with setuid or setgid bit set or with __RESTRICT segment is restricted + if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) { + gLinkContext.processIsRestricted = true; + } + if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { + // On OS X CS_RESTRICT means the program was signed with entitlements if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0) ) { - sRestrictedReason = restrictedByEntitlements; - return true; + gLinkContext.processIsRestricted = true; } - #else - if ((flags & CS_ENFORCEMENT) && !(flags & CS_GET_TASK_ALLOW)) { - *ignoreEnvVars = true; + // Library Validation loosens searching but requires everything to be code signed + if ( flags & CS_REQUIRE_LV ) { + gLinkContext.processIsRestricted = false; + //gLinkContext.requireCodeSignature = true; + gLinkContext.processUsingLibraryValidation = true; } - gLinkContext.codeSigningEnforced = true; - #endif } #endif - - // all processes with setuid or setgid bit set are restricted - if ( issetugid() ) { - sRestrictedReason = restrictedBySetGUid; - return true; - } - - // Respect __RESTRICT,__restrict section for root processes - if ( hasRestrictedSegment(mainExecutableMH) ) { - // existence of __RESTRICT/__restrict section make process restricted - sRestrictedReason = restrictedBySegment; - return true; - } - return false; } bool processIsRestricted() { - return sProcessIsRestricted; +#if __MAC_OS_X_VERSION_MIN_REQUIRED + return gLinkContext.processIsRestricted; +#else + return false; +#endif } @@ -4496,13 +5393,39 @@ static void addDyldImageToUUIDList() } } +void notifyKernelAboutDyld() +{ + const struct macho_header* mh = (macho_header*)&__dso_handle; + const uint32_t cmd_count = mh->ncmds; + const struct load_command* const cmds = (struct load_command*)((char*)mh + sizeof(macho_header)); + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_UUID: { + // Add dyld to the kernel image info + uuid_command* uc = (uuid_command*)cmd; + dyld_kernel_image_info_t kernelInfo; + memcpy(kernelInfo.uuid, uc->uuid, 16); + kernelInfo.load_addr = (uint64_t)mh; + kernelInfo.fsobjid.fid_objno = 0; + kernelInfo.fsobjid.fid_generation = 0; + kernelInfo.fsid.val[0] = 0; + kernelInfo.fsid.val[1] = 0; + task_register_dyld_image_infos(mach_task_self(), &kernelInfo, 1); + return; + } + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } +} + #if __MAC_OS_X_VERSION_MIN_REQUIRED typedef int (*open_proc_t)(const char*, int, int); typedef int (*fcntl_proc_t)(int, int, void*); typedef int (*ioctl_proc_t)(int, unsigned long, void*); static void* getProcessInfo() { return dyld::gProcessInfo; } static SyscallHelpers sSysCalls = { - 4, + 7, // added in version 1 (open_proc_t)&open, &close, @@ -4541,49 +5464,66 @@ static SyscallHelpers sSysCalls = { &closedir, // added in version 4 &coresymbolication_load_notifier, - &coresymbolication_unload_notifier + &coresymbolication_unload_notifier, + // Added in version 5 + &proc_regionfilename, + &getpid, + &mach_port_insert_right, + &mach_port_allocate, + &mach_msg, + // Added in version 6 + &abort_with_payload, + // Added in version 7 + &task_register_dyld_image_infos, + &task_unregister_dyld_image_infos, + &task_get_dyld_image_infos, + &task_register_dyld_shared_cache_image_info, + &task_register_dyld_set_dyld_state, + &task_register_dyld_get_process_state }; __attribute__((noinline)) -static uintptr_t useSimulatorDyld(int fd, const macho_header* mainExecutableMH, const char* dyldPath, - int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) +static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH, const char* dyldPath, + int argc, const char* argv[], const char* envp[], const char* apple[], + uintptr_t* startGlue, uintptr_t* mainAddr) { *startGlue = 0; + *mainAddr = 0; // simulator does not support restricted processes uint32_t flags; if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) == -1 ) - return 0; + return "csops() failed"; if ( (flags & CS_RESTRICT) == CS_RESTRICT ) - return 0; + return "dyld_sim cannot be loaded in a restricted process"; if ( issetugid() ) - return 0; + return "dyld_sim cannot be loaded in a setuid process"; if ( hasRestrictedSegment(mainExecutableMH) ) - return 0; + return "dyld_sim cannot be loaded in a restricted process"; - // verify simulator dyld file is owned by root + // get file size of dyld_sim struct stat sb; if ( fstat(fd, &sb) == -1 ) - return 0; + return "stat(dyld_sim) failed"; - // read first page of dyld file + // read first page of dyld_sim file uint8_t firstPage[4096]; if ( pread(fd, firstPage, 4096, 0) != 4096 ) - return 0; - + return "pread(dyld_sim) failed"; + // if fat file, pick matching slice uint64_t fileOffset = 0; uint64_t fileLength = sb.st_size; const fat_header* fileStartAsFat = (fat_header*)firstPage; if ( fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { if ( !fatFindBest(fileStartAsFat, &fileOffset, &fileLength) ) - return 0; + return "no matching arch in dyld_sim"; // re-read buffer from start of mach-o slice in fat file if ( pread(fd, firstPage, 4096, fileOffset) != 4096 ) - return 0; + return "pread(dyld_sim) failed"; } else if ( !isCompatibleMachO(firstPage, dyldPath) ) { - return 0; + return "dyld_sim not compatible mach-o"; } // calculate total size of dyld segments @@ -4593,65 +5533,69 @@ static uintptr_t useSimulatorDyld(int fd, const macho_header* mainExecutableMH, uintptr_t mappingSize = 0; uintptr_t preferredLoadAddress = 0; const uint32_t cmd_count = mh->ncmds; + if ( mh->sizeofcmds > 4096 ) + return "dyld_sim load commands to large"; if ( (sizeof(macho_header) + mh->sizeofcmds) > 4096 ) - return 0; + return "dyld_sim load commands to large"; const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); const struct load_command* const endCmds = (struct load_command*)(((char*)mh) + sizeof(macho_header) + mh->sizeofcmds); const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { uint32_t cmdLength = cmd->cmdsize; if ( cmdLength < 8 ) - return 0; + return "dyld_sim load command too small"; const struct load_command* const nextCmd = (const struct load_command*)(((char*)cmd)+cmdLength); if ( (nextCmd > endCmds) || (nextCmd < cmd) ) - return 0; + return "dyld_sim load command too large"; switch (cmd->cmd) { case LC_SEGMENT_COMMAND: { struct macho_segment_command* seg = (struct macho_segment_command*)cmd; if ( seg->vmaddr + seg->vmsize < seg->vmaddr ) - return 0; + return "dyld_sim seg wraps address space"; if ( seg->vmsize < seg->filesize ) - return 0; + return "dyld_sim seg vmsize too small"; + if ( (seg->fileoff + seg->filesize) < seg->fileoff ) + return "dyld_sim seg size wraps address space"; if ( lastSeg == NULL ) { // first segment must be __TEXT and start at beginning of file/slice firstSeg = seg; if ( strcmp(seg->segname, "__TEXT") != 0 ) - return 0; + return "dyld_sim first segment not __TEXT"; if ( seg->fileoff != 0 ) - return 0; + return "dyld_sim first segment not at file offset zero"; if ( seg->filesize < (sizeof(macho_header) + mh->sizeofcmds) ) - return 0; + return "dyld_sim first segment smaller than load commands"; preferredLoadAddress = seg->vmaddr; } else { // other sements must be continguous with previous segment and not executable if ( lastSeg->fileoff + lastSeg->filesize != seg->fileoff ) - return 0; + return "dyld_sim segments not contiguous"; if ( lastSeg->vmaddr + lastSeg->vmsize != seg->vmaddr ) - return 0; + return "dyld_sim segments not address contiguous"; if ( (seg->initprot & VM_PROT_EXECUTE) != 0 ) - return 0; + return "dyld_sim non-first segment is executable"; } mappingSize += seg->vmsize; lastSeg = seg; } break; case LC_SEGMENT_COMMAND_WRONG: - return 0; + return "dyld_sim wrong load segment load command"; } cmd = nextCmd; } // last segment must be named __LINKEDIT and not writable if ( strcmp(lastSeg->segname, "__LINKEDIT") != 0 ) - return 0; + return "dyld_sim last segment not __LINKEDIT"; if ( lastSeg->initprot & VM_PROT_WRITE ) - return 0; + return "dyld_sim __LINKEDIT segment writable"; // reserve space, then mmap each segment vm_address_t loadAddress = 0; if ( ::vm_allocate(mach_task_self(), &loadAddress, mappingSize, VM_FLAGS_ANYWHERE) != 0 ) - return 0; + return "dyld_sim cannot allocate space"; cmd = cmds; struct linkedit_data_command* codeSigCmd = NULL; struct source_version_command* dyldVersionCmd = NULL; @@ -4664,9 +5608,9 @@ static uintptr_t useSimulatorDyld(int fd, const macho_header* mainExecutableMH, void* segAddress = ::mmap((void*)requestedLoadAddress, seg->filesize, seg->initprot, MAP_FIXED | MAP_PRIVATE, fd, fileOffset + seg->fileoff); //dyld::log("dyld_sim %s mapped at %p\n", seg->segname, segAddress); if ( segAddress == (void*)(-1) ) - return 0; + return "dyld_sim mmap() of segment failed"; if ( ((uintptr_t)segAddress < loadAddress) || ((uintptr_t)segAddress+seg->filesize > loadAddress+mappingSize) ) - return 0; + return "dyld_sim mmap() to wrong location"; } break; case LC_CODE_SIGNATURE: @@ -4681,11 +5625,13 @@ static uintptr_t useSimulatorDyld(int fd, const macho_header* mainExecutableMH, // must have code signature which is contained within LINKEDIT segment if ( codeSigCmd == NULL ) - return 0; + return "dyld_sim not code signed"; if ( codeSigCmd->dataoff < lastSeg->fileoff ) - return 0; + return "dyld_sim code signature not in __LINKEDIT"; + if ( (codeSigCmd->dataoff + codeSigCmd->datasize) < codeSigCmd->dataoff ) + return "dyld_sim code signature size wraps"; if ( (codeSigCmd->dataoff + codeSigCmd->datasize) > (lastSeg->fileoff + lastSeg->filesize) ) - return 0; + return "dyld_sim code signature extends beyond __LINKEDIT"; fsignatures_t siginfo; siginfo.fs_file_start=fileOffset; // start of mach-o slice in fat file @@ -4693,13 +5639,13 @@ static uintptr_t useSimulatorDyld(int fd, const macho_header* mainExecutableMH, siginfo.fs_blob_size=codeSigCmd->datasize; // size of code-signature int result = fcntl(fd, F_ADDFILESIGS_FOR_DYLD_SIM, &siginfo); if ( result == -1 ) { - dyld::log("fcntl(F_ADDFILESIGS_FOR_DYLD_SIM) failed with errno=%d\n", errno); - return 0; + return mkstringf("dyld_sim fcntl(F_ADDFILESIGS_FOR_DYLD_SIM) failed with errno=%d", errno); } close(fd); // file range covered by code signature must extend up to code signature itself if ( siginfo.fs_file_start < codeSigCmd->dataoff ) - return 0; + return mkstringf("dyld_sim code signature does not cover all of dyld_sim. Signature covers up to 0x%08lX. Signature starts at 0x%08X", (unsigned long)siginfo.fs_file_start, codeSigCmd->dataoff); + // walk newly mapped dyld_sim __TEXT load commands to find entry point uintptr_t entry = 0; @@ -4711,17 +5657,17 @@ static uintptr_t useSimulatorDyld(int fd, const macho_header* mainExecutableMH, const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16); // entry point must be in first segment if ( registers->__eip < firstSeg->vmaddr ) - return 0; + return "dyld_sim entry point not in __TEXT segment"; if ( registers->__eip > (firstSeg->vmaddr + firstSeg->vmsize) ) - return 0; + return "dyld_sim entry point not in __TEXT segment"; entry = (registers->__eip + loadAddress - preferredLoadAddress); #elif __x86_64__ const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16); // entry point must be in first segment if ( registers->__rip < firstSeg->vmaddr ) - return 0; + return "dyld_sim entry point not in __TEXT segment"; if ( registers->__rip > (firstSeg->vmaddr + firstSeg->vmsize) ) - return 0; + return "dyld_sim entry point not in __TEXT segment"; entry = (registers->__rip + loadAddress - preferredLoadAddress); #endif } @@ -4742,9 +5688,10 @@ static uintptr_t useSimulatorDyld(int fd, const macho_header* mainExecutableMH, const macho_header* mainExecutableMH, const macho_header* dyldMH, uintptr_t dyldSlide, const dyld::SyscallHelpers* vtable, uintptr_t* startGlue); sim_entry_proc_t newDyld = (sim_entry_proc_t)entry; - return (*newDyld)(argc, argv, envp, appleParams, mainExecutableMH, (macho_header*)loadAddress, + *mainAddr = (*newDyld)(argc, argv, envp, appleParams, mainExecutableMH, (macho_header*)loadAddress, loadAddress - preferredLoadAddress, &sSysCalls, startGlue); + return NULL; } #endif @@ -4766,15 +5713,18 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, // if this is host dyld, check to see if iOS simulator is being run const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH"); if ( rootPath != NULL ) { + // Add dyld to the kernel image info before we jump to the sim + notifyKernelAboutDyld(); + // look to see if simulator has its own dyld char simDyldPath[PATH_MAX]; strlcpy(simDyldPath, rootPath, PATH_MAX); strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX); int fd = my_open(simDyldPath, O_RDONLY, 0); if ( fd != -1 ) { - result = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue); - if ( !result && (*startGlue == 0) ) - halt("problem loading iOS simulator dyld"); + const char* errMessage = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue, &result); + if ( errMessage != NULL ) + halt(errMessage); return result; } } @@ -4782,25 +5732,6 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, CRSetCrashLogMessage("dyld: launch started"); -#if LOG_BINDINGS - char bindingsLogPath[256]; - - const char* shortProgName = "unknown"; - if ( argc > 0 ) { - shortProgName = strrchr(argv[0], '/'); - if ( shortProgName == NULL ) - shortProgName = argv[0]; - else - ++shortProgName; - } - mysprintf(bindingsLogPath, "/tmp/bindings/%d-%s", getpid(), shortProgName); - sBindingsLogfile = open(bindingsLogPath, O_WRONLY | O_CREAT, 0666); - if ( sBindingsLogfile == -1 ) { - ::mkdir("/tmp/bindings", 0777); - sBindingsLogfile = open(bindingsLogPath, O_WRONLY | O_CREAT, 0666); - } - //dyld::log("open(%s) => %d, errno = %d\n", bindingsLogPath, sBindingsLogfile, errno); -#endif setContext(mainExecutableMH, argc, argv, envp, apple); // Pickup the pointer to the exec path. @@ -4809,7 +5740,6 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, // Remove interim apple[0] transition code from dyld if (!sExecPath) sExecPath = apple[0]; - bool ignoreEnvironmentVariables = false; if ( sExecPath[0] != '/' ) { // have relative path, use cwd to make absolute char cwdbuff[MAXPATHLEN]; @@ -4828,18 +5758,19 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, ++sExecShortName; else sExecShortName = sExecPath; - sProcessIsRestricted = processRestricted(mainExecutableMH, &ignoreEnvironmentVariables, &sProcessRequiresLibraryValidation); - if ( sProcessIsRestricted ) { -#if SUPPORT_LC_DYLD_ENVIRONMENT - checkLoadCommandEnvironmentVariables(); -#endif + + configureProcessRestrictions(mainExecutableMH); + +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.processIsRestricted ) { pruneEnvironmentVariables(envp, &apple); // set again because envp and apple may have changed or moved setContext(mainExecutableMH, argc, argv, envp, apple); } - else { - if ( !ignoreEnvironmentVariables ) - checkEnvironmentVariables(envp); + else +#endif + { + checkEnvironmentVariables(envp); defaultUninitializedFallbackPaths(envp); } if ( sEnv.DYLD_PRINT_OPTS ) @@ -4851,57 +5782,97 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB); stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages); // make initial allocations large enough that it is unlikely to need to be re-alloced - sAllImages.reserve(INITIAL_IMAGE_COUNT); sImageRoots.reserve(16); sAddImageCallbacks.reserve(4); sRemoveImageCallbacks.reserve(4); sImageFilesNeedingTermination.reserve(16); sImageFilesNeedingDOFUnregistration.reserve(8); - + +#if !TARGET_IPHONE_SIMULATOR #ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE // Add gating mechanism to dyld support system order file generation process WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag); #endif - +#endif + try { // add dyld itself to UUID list addDyldImageToUUIDList(); + notifyKernelAboutDyld(); + +#if SUPPORT_ACCELERATE_TABLES + bool mainExcutableAlreadyRebased = false; + +reloadAllImages: +#endif + CRSetCrashLogMessage(sLoadingCrashMessage); // instantiate ImageLoader for main executable sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath); gLinkContext.mainExecutable = sMainExecutable; - gLinkContext.processIsRestricted = sProcessIsRestricted; - gLinkContext.processRequiresLibraryValidation = sProcessRequiresLibraryValidation; gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH); #if TARGET_IPHONE_SIMULATOR - #if TARGET_OS_WATCH || TARGET_OS_TV - // disable error during bring up of these simulators - #else // check main executable is not too new for this OS { if ( ! isSimulatorBinary((uint8_t*)mainExecutableMH, sExecPath) ) { - throwf("program was built for Mac OS X and cannot be run in simulator"); + throwf("program was built for a platform that is not supported by this runtime"); } uint32_t mainMinOS = sMainExecutable->minOSVersion(); + // dyld is always built for the current OS, so we can get the current OS version // from the load command in dyld itself. uint32_t dyldMinOS = ImageLoaderMachO::minOSVersion((const mach_header*)&__dso_handle); if ( mainMinOS > dyldMinOS ) { - throwf("app was built for iOS %d.%d which is newer than this simulator %d.%d", + #if TARGET_OS_WATCH + throwf("app was built for watchOS %d.%d which is newer than this simulator %d.%d", + mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF), + dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF)); + #elif TARGET_OS_TV + throwf("app was built for tvOS %d.%d which is newer than this simulator %d.%d", + mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF), + dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF)); + #else + throwf("app was built for iOS %d.%d which is newer than this simulator %d.%d", mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF), dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF)); + #endif } } - #endif #endif + + #if __MAC_OS_X_VERSION_MIN_REQUIRED + // be less strict about old mach-o binaries + uint32_t mainSDK = sMainExecutable->sdkVersion(); + gLinkContext.strictMachORequired = (mainSDK >= DYLD_MACOSX_VERSION_10_12) || gLinkContext.processUsingLibraryValidation; + #else + // simulators, iOS, tvOS, and watchOS are always strict + gLinkContext.strictMachORequired = true; + #endif + // load shared cache checkSharedRegionDisable(); #if DYLD_SHARED_CACHE_SUPPORT - if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) + if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) { mapSharedCache(); + } else { + dyld_kernel_image_info_t kernelCacheInfo; + bzero(&kernelCacheInfo.uuid[0], sizeof(uuid_t)); + kernelCacheInfo.load_addr = 0; + kernelCacheInfo.fsobjid.fid_objno = 0; + kernelCacheInfo.fsobjid.fid_generation = 0; + kernelCacheInfo.fsid.val[0] = 0; + kernelCacheInfo.fsid.val[0] = 0; + task_register_dyld_shared_cache_image_info(mach_task_self(), kernelCacheInfo, true, false); + } + #endif + + #if SUPPORT_ACCELERATE_TABLES + sAllImages.reserve((sAllCacheImagesProxy != NULL) ? 16 : INITIAL_IMAGE_COUNT); + #else + sAllImages.reserve(INITIAL_IMAGE_COUNT); #endif // Now that shared cache is loaded, setup an versioned dylib overrides @@ -4909,6 +5880,23 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, checkVersionedPaths(); #endif + + // dyld_all_image_infos image list does not contain dyld + // add it as dyldPath field in dyld_all_image_infos + // for simulator, dyld_sim is in image list, need host dyld added +#if TARGET_IPHONE_SIMULATOR + // get path of host dyld from table of syscall vectors in host dyld + void* addressInDyld = gSyscallHelpers; +#else + // get path of dyld itself + void* addressInDyld = (void*)&__dso_handle; +#endif + char dyldPathBuffer[MAXPATHLEN+1]; + int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN); + if ( (len != 0) && (strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0) ) { + gProcessInfo->dyldPath = strdup(dyldPathBuffer); + } + // load any inserted libraries if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) @@ -4920,7 +5908,14 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, // link main executable gLinkContext.linkingMainExecutable = true; - link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL)); +#if SUPPORT_ACCELERATE_TABLES + if ( mainExcutableAlreadyRebased ) { + // previous link() on main executable has already adjusted its internal pointers for ASLR + // work around that by rebasing by inverse amount + sMainExecutable->rebase(gLinkContext, -mainExecutableSlide); + } +#endif + link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1); sMainExecutable->setNeverUnloadRecursive(); if ( sMainExecutable->forceFlat() ) { gLinkContext.bindFlat = true; @@ -4933,7 +5928,7 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, if ( sInsertedDylibCount > 0 ) { for(unsigned int i=0; i < sInsertedDylibCount; ++i) { ImageLoader* image = sAllImages[i+1]; - link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL)); + link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1); image->setNeverUnloadRecursive(); } // only INSERTED libraries can interpose @@ -4945,12 +5940,42 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, } // dyld should support interposition even without DYLD_INSERT_LIBRARIES - for (int i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) { + for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) { ImageLoader* image = sAllImages[i]; if ( image->inSharedCache() ) continue; image->registerInterposing(); } + #if SUPPORT_ACCELERATE_TABLES + if ( (sAllCacheImagesProxy != NULL) && ImageLoader::haveInterposingTuples() ) { + // Accelerator tables cannot be used with implicit interposing, so relaunch with accelerator tables disabled + ImageLoader::clearInterposingTuples(); + // unmap all loaded dylibs (but not main executable) + for (long i=1; i < sAllImages.size(); ++i) { + ImageLoader* image = sAllImages[i]; + if ( image == sMainExecutable ) + continue; + if ( image == sAllCacheImagesProxy ) + continue; + image->setCanUnload(); + ImageLoader::deleteImage(image); + } + // note: we don't need to worry about inserted images because if DYLD_INSERT_LIBRARIES was set we would not be using the accelerator table + sAllImages.clear(); + sImageRoots.clear(); + sImageFilesNeedingTermination.clear(); + sImageFilesNeedingDOFUnregistration.clear(); + sAddImageCallbacks.clear(); + sRemoveImageCallbacks.clear(); + sDisableAcceleratorTables = true; + sAllCacheImagesProxy = NULL; + sMappedRangesStart = NULL; + mainExcutableAlreadyRebased = true; + gLinkContext.linkingMainExecutable = false; + resetAllImages(); + goto reloadAllImages; + } + #endif // apply interposing to initial set of images for(int i=0; i < sImageRoots.size(); ++i) { @@ -4960,7 +5985,29 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, // do weak binding only after all inserted images linked sMainExecutable->weakBind(gLinkContext); - + + #if DYLD_SHARED_CACHE_SUPPORT + // If cache has branch island dylibs, tell debugger about them + if ( (sSharedCache != NULL) && (sSharedCache->mappingOffset >= 0x78) && (sSharedCache->branchPoolsOffset != 0) ) { + uint32_t count = sSharedCache->branchPoolsCount; + dyld_image_info info[count]; + const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCache + sSharedCache->branchPoolsOffset); + // empty branch pools can be in development cache + if ( ((mach_header*)poolAddress)->magic == sMainExecutableMachHeader->magic ) { + for (int poolIndex=0; poolIndex < count; ++poolIndex) { + uint64_t poolAddr = poolAddress[poolIndex] + sSharedCacheSlide; + info[poolIndex].imageLoadAddress = (mach_header*)(long)poolAddr; + info[poolIndex].imageFilePath = "dyld_shared_cache_branch_islands"; + info[poolIndex].imageFileModDate = 0; + } + // add to all_images list + addImagesToAllImages(count, info); + // tell gdb about new branch island images + gProcessInfo->notification(dyld_image_adding, count, info); + } + } + #endif + CRSetCrashLogMessage("dyld: launch, running initializers"); #if SUPPORT_OLD_CRT_INITIALIZATION // Old way is to run initializers via a callback from crt1.o diff --git a/src/dyld.exp b/src/dyld.exp index 22a1f6c..e5f4086 100644 --- a/src/dyld.exp +++ b/src/dyld.exp @@ -29,10 +29,10 @@ # gdb and Symbolication look at these to discover a process's loaded images _dyld_all_image_infos _dyld_shared_cache_ranges +__dyld_debugger_notification # CrashReporter uses this to get message as to why dyld terminated the process _error_string -_dyld_fatal_error # Used by various tools to see build number of dyld _dyldVersionString diff --git a/src/dyld.h b/src/dyld.h index fe6c9b6..d8bca0d 100644 --- a/src/dyld.h +++ b/src/dyld.h @@ -24,6 +24,7 @@ #include #include +#include #include "ImageLoader.h" #include "mach-o/dyld_priv.h" @@ -61,6 +62,9 @@ namespace dyld { extern ImageLoader::LinkContext gLinkContext; extern struct dyld_all_image_infos* gProcessInfo; extern bool gLogAPIs; +#if SUPPORT_ACCELERATE_TABLES + extern bool gLogAppAPIs; +#endif #if DYLD_SHARED_CACHE_SUPPORT extern bool gSharedCacheOverridden; #endif @@ -72,8 +76,8 @@ namespace dyld { extern void registerRemoveCallback(ImageCallback func); extern void registerUndefinedHandler(UndefinedHandler); extern void initializeMainExecutable(); - extern void preflight(ImageLoader* image, const ImageLoader::RPathChain& loaderRPaths); - extern void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths); + extern void preflight(ImageLoader* image, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex); + extern void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex); extern void runInitializers(ImageLoader* image); extern void runImageStaticTerminators(ImageLoader* image); extern const char* getExecutablePath(); @@ -87,7 +91,7 @@ namespace dyld { extern ImageLoader* findLoadedImageByInstallPath(const char* path); extern bool flatFindExportedSymbol(const char* name, const ImageLoader::Symbol** sym, const ImageLoader** image); extern bool flatFindExportedSymbolWithHint(const char* name, const char* librarySubstring, const ImageLoader::Symbol** sym, const ImageLoader** image); - extern ImageLoader* load(const char* path, const LoadContext& context); + extern ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex); extern ImageLoader* loadFromMemory(const uint8_t* mem, uint64_t len, const char* moduleName); extern void removeImage(ImageLoader* image); extern ImageLoader* cloneImage(ImageLoader* image); @@ -103,6 +107,8 @@ namespace dyld { extern void processDyldEnvironmentVariable(const char* key, const char* value, const char* mainDir); extern void registerImageStateSingleChangeHandler(dyld_image_states state, dyld_image_state_change_handler handler); extern void registerImageStateBatchChangeHandler(dyld_image_states state, dyld_image_state_change_handler handler); + extern void registerObjCNotifiers(_dyld_objc_notify_mapped, _dyld_objc_notify_init, _dyld_objc_notify_unmapped); + extern bool sharedCacheUUID(uuid_t uuid); extern void garbageCollectImages(); extern int openSharedCacheFile(); extern const void* imMemorySharedCacheHeader(); @@ -117,5 +123,20 @@ namespace dyld { extern const char* getStandardSharedCacheFilePath(); extern int my_stat(const char* path, struct stat* buf); extern int my_open(const char* path, int flag, int other); + bool sandboxBlockedOpen(const char* path); + bool sandboxBlockedMmap(const char* path); + bool sandboxBlockedStat(const char* path); + +#if SUPPORT_ACCELERATE_TABLES + bool dlopenFromCache(const char* path, int mode, void** handle); + bool makeCacheHandle(ImageLoader* image, unsigned cacheIndex, int mode, void** result); + void* dlsymFromCache(void* handle, const char* symName, unsigned index); + bool isCacheHandle(void* handle); + bool addressInCache(const void* address, const mach_header** mh, const char** path, unsigned* index=NULL); + bool findUnwindSections(const void* addr, dyld_unwind_sections* info); + bool dladdrFromCache(const void* address, Dl_info* info); + bool isPathInCache(const char* path); + const char* getPathFromIndex(unsigned cacheIndex); +#endif } diff --git a/src/dyld.order b/src/dyld.order index ba83697..4403065 100644 --- a/src/dyld.order +++ b/src/dyld.order @@ -24,27 +24,29 @@ # # Cluster these __DATA symbols in dyld to reduce pages dirtied # -_sDyldInfo.0 -_sDyldInfo.1 -_sDyldInfo.2 -_sDyldInfo.3 -_sDyldInfo.4 -_sDyldTextEnd -_mach_init_inited.7154.b +_mach_init.mach_init_inited __task_reply_port _mach_task_self_ _mach_host_self_ _vm_page_size _vm_page_mask _vm_page_shift +_vm_kernel_page_size +_vm_kernel_page_mask +_vm_kernel_page_shift +_exitf +___pthread_head ___stack_chk_guard __ZN4dyldL23sFrameworkFallbackPathsE __ZN4dyldL21sLibraryFallbackPathsE __ZL11initialPool __ZN4dyld12gLinkContextE __ZN4dyld17gLibSystemHelpersE +_sThreadChainKey +_sStaticThreadChain __ZN4dyldL17sSharedCacheSlideE _dyld_shared_cache_ranges +__ZN4dyldL20sAllCacheImagesProxyE __ZN11ImageLoader27fgImagesUsedFromSharedCacheE __ZN11ImageLoader19fgInterposingTuplesE __ZN11ImageLoader24fgTotalLoadLibrariesTimeE @@ -68,7 +70,7 @@ __ZN11ImageLoader21fgNextPIEDylibAddressE __ZN11ImageLoader26fgImagesWithUsedPrebindingE __ZN11ImageLoader26fgImagesHasWeakDefinitionsE __ZN16ImageLoaderMachO26fgSymbolTableBinarySearchsE -__ZN16ImageLoaderMachO19fgSymbolTrieSearchsE +__ZN11ImageLoader19fgSymbolTrieSearchsE __ZN11ImageLoader13fgLoadOrdinalE __ZN4dyldL9sExecPathE __ZN4dyldL25sMainExecutableMachHeaderE @@ -95,4 +97,12 @@ __ZN4dyldL15sDylibOverridesE __ZN4dyldL19sInsertedDylibCountE __ZN4dyldL20sProcessIsRestrictedE __ZN4dyldL18sMappedRangesStartE +__ZN4dyldL14sExecShortNameE +__ZN4dyld12gProcessInfoE +___cxa_terminate_handler +___cxa_unexpected_handler +__ZL11currentPool +__ZN4dyldL8sHostCPUE +__ZN4dyldL15sHostCPUsubtypeE + __thread diff --git a/src/dyldAPIs.cpp b/src/dyldAPIs.cpp index b64331a..31a5a8a 100644 --- a/src/dyldAPIs.cpp +++ b/src/dyldAPIs.cpp @@ -51,6 +51,7 @@ #include "mach-o/dyld_priv.h" #include "ImageLoader.h" +#include "ImageLoaderMachO.h" #include "dyld.h" #include "dyldLibSystemInterface.h" @@ -62,6 +63,10 @@ extern "C" void __Unwind_SjLj_SetThreadKey(pthread_key_t key); // from dyld_gdb.cpp extern void addImagesToAllImages(uint32_t infoCount, const dyld_image_info info[]); +extern uint32_t allImagesCount(); +extern const mach_header* allImagesIndexedMachHeader(uint32_t index); +extern const char* allImagesIndexedPath(uint32_t index); + // deprecated APIs are still availble on Mac OS X, but not on iPhone OS #if __IPHONE_OS_VERSION_MIN_REQUIRED @@ -104,6 +109,8 @@ static bool client_NSIsSymbolNameDefined(const char* symbolName); #if SUPPORT_ZERO_COST_EXCEPTIONS static bool client_dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info); #endif +#if DEPRECATED_APIS_SUPPORTED +#endif static void unimplemented() { @@ -132,7 +139,6 @@ static struct dyld_func dyld_funcs[] = { {"__dyld__NSGetExecutablePath", (void*)_NSGetExecutablePath }, // SPIs - {"__dyld_dyld_register_image_state_change_handler", (void*)dyld_register_image_state_change_handler }, {"__dyld_register_thread_helpers", (void*)registerThreadHelpers }, {"__dyld_fork_child", (void*)_dyld_fork_child }, {"__dyld_make_delayed_module_initializer_calls", (void*)_dyld_make_delayed_module_initializer_calls }, @@ -151,6 +157,10 @@ static struct dyld_func dyld_funcs[] = { {"__dyld_shared_cache_file_path", (void*)dyld::getStandardSharedCacheFilePath }, #endif {"__dyld_get_image_header_containing_address", (void*)dyld_image_header_containing_address }, + {"__dyld_is_memory_immutable", (void*)_dyld_is_memory_immutable }, + {"__dyld_objc_notify_register", (void*)_dyld_objc_notify_register }, + {"__dyld_get_shared_cache_uuid", (void*)_dyld_get_shared_cache_uuid }, + // deprecated #if DEPRECATED_APIS_SUPPORTED @@ -296,27 +306,23 @@ uint32_t _dyld_image_count(void) { if ( dyld::gLogAPIs ) dyld::log("%s()\n", __func__); - return dyld::getImageCount(); + return allImagesCount(); } const struct mach_header* _dyld_get_image_header(uint32_t image_index) { if ( dyld::gLogAPIs ) dyld::log("%s(%u)\n", __func__, image_index); - ImageLoader* image = dyld::getIndexedImage(image_index); - if ( image != NULL ) - return (struct mach_header*)image->machHeader(); - else - return NULL; + return allImagesIndexedMachHeader(image_index); } intptr_t _dyld_get_image_vmaddr_slide(uint32_t image_index) { if ( dyld::gLogAPIs ) dyld::log("%s(%u)\n", __func__, image_index); - ImageLoader* image = dyld::getIndexedImage(image_index); - if ( image != NULL ) - return image->getSlide(); + const struct mach_header* mh = allImagesIndexedMachHeader(image_index); + if ( mh != NULL ) + return ImageLoaderMachO::computeSlide(mh); else return 0; } @@ -325,11 +331,7 @@ intptr_t _dyld_get_image_slide(const struct mach_header* mh) { if ( dyld::gLogAPIs ) dyld::log("%s(%p)\n", __func__, mh); - ImageLoader* image = dyld::findImageByMachHeader(mh); - if ( image != NULL ) - return image->getSlide(); - else - return 0; + return ImageLoaderMachO::computeSlide(mh); } @@ -337,17 +339,19 @@ const char* _dyld_get_image_name(uint32_t image_index) { if ( dyld::gLogAPIs ) dyld::log("%s(%u)\n", __func__, image_index); - ImageLoader* image = dyld::getIndexedImage(image_index); - if ( image != NULL ) - return image->getRealPath(); - else - return NULL; + return allImagesIndexedPath(image_index); } const struct mach_header * dyld_image_header_containing_address(const void* address) { if ( dyld::gLogAPIs ) dyld::log("%s(%p)\n", __func__, address); +#if SUPPORT_ACCELERATE_TABLES + const mach_header* mh; + const char* path; + if ( dyld::addressInCache(address, &mh, &path) ) + return mh; +#endif ImageLoader* image = dyld::findImageContainingAddress(address); if ( image != NULL ) return image->machHeader(); @@ -543,12 +547,13 @@ const struct mach_header* addImage(void* callerAddress, const char* path, bool s context.canBePIE = false; context.origin = callerImage != NULL ? callerImage->getPath() : NULL; // caller's image's path context.rpath = &callersRPaths; // rpaths from caller and main executable - - image = load(path, context); + + unsigned cacheIndex; + image = load(path, context, cacheIndex); if ( image != NULL ) { if ( context.matchByInstallName ) image->setMatchInstallPath(true); - dyld::link(image, false, false, callersRPaths); + dyld::link(image, false, false, callersRPaths, cacheIndex); dyld::runInitializers(image); // images added with NSAddImage() can never be unloaded image->setNeverUnload(); @@ -793,7 +798,8 @@ NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* pathName context.origin = callerImage != NULL ? callerImage->getPath() : NULL; // caller's image's path context.rpath = NULL; // support not yet implemented - ImageLoader* image = dyld::load(pathName, context); + unsigned cacheIndex; + ImageLoader* image = dyld::load(pathName, context, cacheIndex); // Note: We DO NOT link the image! NSLinkModule will do that if ( image != NULL ) { if ( !image->isBundle() ) { @@ -1007,7 +1013,7 @@ NSModule NSLinkModule(NSObjectFileImage objectFileImage, const char* moduleName, bool forceLazysBound = ( (options & NSLINKMODULE_OPTION_BINDNOW) != 0 ); // load libraries, rebase, bind, to make this image usable - dyld::link(objectFileImage->image, forceLazysBound, false, ImageLoader::RPathChain(NULL,NULL)); + dyld::link(objectFileImage->image, forceLazysBound, false, ImageLoader::RPathChain(NULL,NULL), UINT32_MAX); // bump reference count to keep this bundle from being garbage collected objectFileImage->image->incrementDlopenReferenceCount(); @@ -1053,7 +1059,7 @@ static NSModule _dyld_link_module(NSObjectFileImage object_addr, size_t object_s bool forceLazysBound = ( (options & NSLINKMODULE_OPTION_BINDNOW) != 0 ); // load libraries, rebase, bind, to make this image usable - dyld::link(image, forceLazysBound, false, ImageLoader::RPathChain(NULL,NULL)); + dyld::link(image, forceLazysBound, false, ImageLoader::RPathChain(NULL,NULL), UINT32_MAX); // run initializers unless magic flag says not to if ( (options & NSLINKMODULE_OPTION_DONT_CALL_MOD_INIT_ROUTINES) == 0 ) @@ -1262,9 +1268,6 @@ static void registerThreadHelpers(const dyld::LibSystemHelpers* helpers) { dyld::gLibSystemHelpers = helpers; - // let gdb know it is safe to run code in inferior that might call malloc() - dyld::gProcessInfo->libSystemInitialized = true; - #if !SUPPORT_ZERO_COST_EXCEPTIONS if ( helpers->version >= 5 ) { // create key use by dyld exception handling @@ -1312,6 +1315,36 @@ bool dlopen_preflight(const char* path) dlerrorClear(); + CRSetCrashLogMessage("dyld: in dlopen_preflight()"); + + const bool leafName = (strchr(path, '/') == NULL); + const bool absolutePath = (path[0] == '/'); +#if __IPHONE_OS_VERSION_MIN_REQUIRED + char canonicalPath[PATH_MAX]; + // dlopen() not opening frameworks from shared cache with // or ./ in path + if ( !leafName ) { + // make path canonical if it contains a // or ./ + if ( (strstr(path, "//") != NULL) || (strstr(path, "./") != NULL) ) { + const char* lastSlash = strrchr(path, '/'); + char dirPath[PATH_MAX]; + if ( strlcpy(dirPath, path, sizeof(dirPath)) < sizeof(dirPath) ) { + dirPath[lastSlash-path] = '\0'; + if ( realpath(dirPath, canonicalPath) ) { + strlcat(canonicalPath, "/", sizeof(canonicalPath)); + if ( strlcat(canonicalPath, lastSlash+1, sizeof(canonicalPath)) < sizeof(canonicalPath) ) { + // if all fit in buffer, use new canonical path + path = canonicalPath; + } + } + } + } + } +#endif +#if SUPPORT_ACCELERATE_TABLES + if ( dyld::isPathInCache(path) ) + return true; +#endif + #if DYLD_SHARED_CACHE_SUPPORT // dlopen_preflight() on image in shared cache leaves it loaded but not objc initialized // if requested path is to something in the dyld shared cache, always succeed @@ -1319,8 +1352,6 @@ bool dlopen_preflight(const char* path) return true; #endif - CRSetCrashLogMessage("dyld: in dlopen_preflight()"); - bool result = false; std::vector rpathsFromCallerImage; try { @@ -1335,29 +1366,6 @@ bool dlopen_preflight(const char* path) } ImageLoader* image = NULL; - const bool leafName = (strchr(path, '/') == NULL); - const bool absolutePath = (path[0] == '/'); -#if __IPHONE_OS_VERSION_MIN_REQUIRED - char canonicalPath[PATH_MAX]; - // dlopen() not opening frameworks from shared cache with // or ./ in path - if ( !leafName ) { - // make path canonical if it contains a // or ./ - if ( (strstr(path, "//") != NULL) || (strstr(path, "./") != NULL) ) { - const char* lastSlash = strrchr(path, '/'); - char dirPath[PATH_MAX]; - if ( strlcpy(dirPath, path, sizeof(dirPath)) < sizeof(dirPath) ) { - dirPath[lastSlash-path] = '\0'; - if ( realpath(dirPath, canonicalPath) ) { - strlcat(canonicalPath, "/", sizeof(canonicalPath)); - if ( strlcat(canonicalPath, lastSlash+1, sizeof(canonicalPath)) < sizeof(canonicalPath) ) { - // if all fit in buffer, use new canonical path - path = canonicalPath; - } - } - } - } - } -#endif dyld::LoadContext context; context.useSearchPaths = true; context.useFallbackPaths= leafName; // a partial path implies don't use fallback paths @@ -1370,10 +1378,11 @@ bool dlopen_preflight(const char* path) context.canBePIE = true; context.origin = callerImage != NULL ? callerImage->getPath() : NULL; // caller's image's path context.rpath = &callersRPaths; // rpaths from caller and main executable - - image = load(path, context); + + unsigned cacheIndex; + image = load(path, context, cacheIndex); if ( image != NULL ) { - dyld::preflight(image, callersRPaths); // image object deleted by dyld::preflight() + dyld::preflight(image, callersRPaths, cacheIndex); // image object deleted by dyld::preflight() result = true; } } @@ -1392,14 +1401,43 @@ bool dlopen_preflight(const char* path) return result; } +#if SUPPORT_ACCELERATE_TABLES +bool static callerIsNonOSApp(void* callerAddress, const char** shortName) +{ + *shortName = NULL; + const mach_header* unusedMh; + const char* unusedPath; + unsigned unusedIndex; + // any address in shared cache is not from app + if ( dyld::addressInCache(callerAddress, &unusedMh, &unusedPath, &unusedIndex) ) + return false; + + ImageLoader* callerImage = dyld::findImageContainingAddress(callerAddress); + if ( callerImage == NULL ) + return false; + + *shortName = callerImage->getShortName(); + return ( strncmp(callerImage->getPath(), "/var/containers/", 16) == 0 ); +} +#endif void* dlopen(const char* path, int mode) { if ( dyld::gLogAPIs ) dyld::log("%s(%s, 0x%08X)\n", __func__, ((path==NULL) ? "NULL" : path), mode); +#if SUPPORT_ACCELERATE_TABLES + if ( dyld::gLogAppAPIs ) { + void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue + const char* shortName; + if ( callerIsNonOSApp(callerAddress, &shortName) ) { + dyld::log("%s: %s(%s, 0x%08X)\n", shortName, __func__, ((path==NULL) ? "NULL" : path), mode); + } + } +#endif + dlerrorClear(); - + // passing NULL for path means return magic object if ( path == NULL ) { // RTLD_FIRST means any dlsym() calls on the handle should only search that handle and not subsequent images @@ -1418,6 +1456,38 @@ void* dlopen(const char* path, int mode) } void* result = NULL; + const bool leafName = (strchr(path, '/') == NULL); + const bool absolutePath = (path[0] == '/'); +#if __IPHONE_OS_VERSION_MIN_REQUIRED + char canonicalPath[PATH_MAX]; + // dlopen() not opening frameworks from shared cache with // or ./ in path + if ( !leafName ) { + // make path canonical if it contains a // or ./ + if ( (strstr(path, "//") != NULL) || (strstr(path, "./") != NULL) ) { + const char* lastSlash = strrchr(path, '/'); + char dirPath[PATH_MAX]; + if ( strlcpy(dirPath, path, sizeof(dirPath)) < sizeof(dirPath) ) { + dirPath[lastSlash-path] = '\0'; + if ( realpath(dirPath, canonicalPath) ) { + strlcat(canonicalPath, "/", sizeof(canonicalPath)); + if ( strlcat(canonicalPath, lastSlash+1, sizeof(canonicalPath)) < sizeof(canonicalPath) ) { + // if all fit in buffer, use new canonical path + path = canonicalPath; + } + } + } + } + } +#endif +#if SUPPORT_ACCELERATE_TABLES + if ( dyld::dlopenFromCache(path, mode, &result) ) { + // Note: dlopenFromCache() releases the lock + if ( dyld::gLogAPIs ) + dyld::log(" %s(%s) ==> %p\n", __func__, path, result); + return result; + } +#endif + ImageLoader* image = NULL; std::vector rpathsFromCallerImage; ImageLoader::RPathChain callersRPaths(NULL, &rpathsFromCallerImage); @@ -1432,29 +1502,6 @@ void* dlopen(const char* path, int mode) dyld::mainExecutable()->getRPaths(dyld::gLinkContext, rpathsFromCallerImage); } - const bool leafName = (strchr(path, '/') == NULL); - const bool absolutePath = (path[0] == '/'); -#if __IPHONE_OS_VERSION_MIN_REQUIRED - char canonicalPath[PATH_MAX]; - // dlopen() not opening frameworks from shared cache with // or ./ in path - if ( !leafName ) { - // make path canonical if it contains a // or ./ - if ( (strstr(path, "//") != NULL) || (strstr(path, "./") != NULL) ) { - const char* lastSlash = strrchr(path, '/'); - char dirPath[PATH_MAX]; - if ( strlcpy(dirPath, path, sizeof(dirPath)) < sizeof(dirPath) ) { - dirPath[lastSlash-path] = '\0'; - if ( realpath(dirPath, canonicalPath) ) { - strlcat(canonicalPath, "/", sizeof(canonicalPath)); - if ( strlcat(canonicalPath, lastSlash+1, sizeof(canonicalPath)) < sizeof(canonicalPath) ) { - // if all fit in buffer, use new canonical path - path = canonicalPath; - } - } - } - } - } -#endif dyld::LoadContext context; context.useSearchPaths = true; context.useFallbackPaths= leafName; // a partial path means no fallback paths @@ -1467,8 +1514,20 @@ void* dlopen(const char* path, int mode) context.canBePIE = true; context.origin = callerImage != NULL ? callerImage->getPath() : NULL; // caller's image's path context.rpath = &callersRPaths; // rpaths from caller and main executable - - image = load(path, context); + + unsigned cacheIndex; + image = load(path, context, cacheIndex); +#if SUPPORT_ACCELERATE_TABLES + if ( (image != NULL) && (cacheIndex != UINT32_MAX) ) { + if ( dyld::makeCacheHandle(image, cacheIndex, mode, &result) ) { + if ( dyld::gLogAPIs ) + dyld::log(" %s(%s) ==> %p\n", __func__, path, result); + if ( lockHeld ) + dyld::gLibSystemHelpers->releaseGlobalDyldLock(); + return result; + } + } +#endif if ( image != NULL ) { // bump reference count. Do this before link() so that if an initializer calls dlopen and fails // this image is not garbage collected @@ -1477,7 +1536,7 @@ void* dlopen(const char* path, int mode) if ( (mode & RTLD_NOLOAD) == 0 ) { bool alreadyLinked = image->isLinked(); bool forceLazysBound = ( (mode & RTLD_NOW) != 0 ); - dyld::link(image, forceLazysBound, false, callersRPaths); + dyld::link(image, forceLazysBound, false, callersRPaths, cacheIndex); if ( ! alreadyLinked ) { // only hide exports if image is not already in use if ( (mode & RTLD_LOCAL) != 0 ) @@ -1594,6 +1653,13 @@ int dladdr(const void* address, Dl_info* info) dyld::log("%s(%p, %p)\n", __func__, address, info); CRSetCrashLogMessage("dyld: in dladdr()"); +#if SUPPORT_ACCELERATE_TABLES + if ( dyld::dladdrFromCache(address, info) ) { + CRSetCrashLogMessage(NULL); + return 1; // success + } +#endif + ImageLoader* image = dyld::findImageContainingAddress(address); if ( image != NULL ) { info->dli_fname = image->getRealPath(); @@ -1658,11 +1724,22 @@ void* dlsym(void* handle, const char* symbolName) if ( dyld::gLogAPIs ) dyld::log("%s(%p, %s)\n", __func__, handle, symbolName); +#if SUPPORT_ACCELERATE_TABLES + if ( dyld::gLogAppAPIs ) { + void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue + const char* shortName; + if ( callerIsNonOSApp(callerAddress, &shortName) ) { + dyld::log("%s: %s(%p, %s)\n", shortName, __func__, handle, symbolName); + } + } +#endif + CRSetCrashLogMessage("dyld: in dlsym()"); dlerrorClear(); const ImageLoader* image; const ImageLoader::Symbol* sym; + void* result; // dlsym() assumes symbolName passed in is same as in C source code // dyld assumes all symbol names have an underscore prefix @@ -1674,60 +1751,113 @@ void* dlsym(void* handle, const char* symbolName) if ( handle == RTLD_DEFAULT ) { if ( dyld::flatFindExportedSymbol(underscoredName, &sym, &image) ) { CRSetCrashLogMessage(NULL); - return (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext); + result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, NULL, false, underscoredName); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_DEFAULT, %s) ==> %p\n", __func__, symbolName, result); + return result; } const char* str = dyld::mkstringf("dlsym(RTLD_DEFAULT, %s): symbol not found", symbolName); dlerrorSet(str); free((void*)str); CRSetCrashLogMessage(NULL); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_DEFAULT, %s) ==> NULL\n", __func__, symbolName); return NULL; } // magic "search only main executable" handle - if ( handle == RTLD_MAIN_ONLY ) { + else if ( handle == RTLD_MAIN_ONLY ) { image = dyld::mainExecutable(); sym = image->findExportedSymbol(underscoredName, true, &image); // search RTLD_FIRST way if ( sym != NULL ) { CRSetCrashLogMessage(NULL); - return (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext); + result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, NULL, false, underscoredName); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_MAIN_ONLY, %s) ==> %p\n", __func__, symbolName, result); + return result; } const char* str = dyld::mkstringf("dlsym(RTLD_MAIN_ONLY, %s): symbol not found", symbolName); dlerrorSet(str); free((void*)str); CRSetCrashLogMessage(NULL); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_MAIN_ONLY, %s) ==> NULL\n", __func__, symbolName); return NULL; } // magic "search what I would see" handle - if ( handle == RTLD_NEXT ) { + else if ( handle == RTLD_NEXT ) { void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue +#if SUPPORT_ACCELERATE_TABLES + const mach_header* mh; + const char* path; + unsigned index; + if ( dyld::addressInCache(callerAddress, &mh, &path, &index) ) { + // if dylib in cache is calling dlsym(RTLD_NEXT,xxx) handle search differently + result = dyld::dlsymFromCache(RTLD_NEXT, underscoredName, index); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_NEXT, %s) ==> %p\n", __func__, symbolName, result); + return result; + } +#endif ImageLoader* callerImage = dyld::findImageContainingAddress(callerAddress); sym = callerImage->findExportedSymbolInDependentImages(underscoredName, dyld::gLinkContext, &image); // don't search image, but do search what it links against if ( sym != NULL ) { CRSetCrashLogMessage(NULL); - return (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext); + result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext , callerImage, false, underscoredName); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_NEXT, %s) ==> %p\n", __func__, symbolName, result); + return result; } const char* str = dyld::mkstringf("dlsym(RTLD_NEXT, %s): symbol not found", symbolName); dlerrorSet(str); free((void*)str); CRSetCrashLogMessage(NULL); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_NEXT, %s) ==> NULL\n", __func__, symbolName); return NULL; } // magic "search me, then what I would see" handle - if ( handle == RTLD_SELF ) { + else if ( handle == RTLD_SELF ) { void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue +#if SUPPORT_ACCELERATE_TABLES + const mach_header* mh; + const char* path; + unsigned index; + if ( dyld::addressInCache(callerAddress, &mh, &path, &index) ) { + // if dylib in cache is calling dlsym(RTLD_SELF,xxx) handle search differently + result = dyld::dlsymFromCache(RTLD_SELF, underscoredName, index); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_SELF, %s) ==> %p\n", __func__, symbolName, result); + return result; + } +#endif ImageLoader* callerImage = dyld::findImageContainingAddress(callerAddress); sym = callerImage->findExportedSymbolInImageOrDependentImages(underscoredName, dyld::gLinkContext, &image); // search image and what it links against if ( sym != NULL ) { CRSetCrashLogMessage(NULL); - return (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext); + result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, callerImage, false, underscoredName); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_SELF, %s) ==> %p\n", __func__, symbolName, result); + return result; } const char* str = dyld::mkstringf("dlsym(RTLD_SELF, %s): symbol not found", symbolName); dlerrorSet(str); free((void*)str); CRSetCrashLogMessage(NULL); + if ( dyld::gLogAPIs ) + dyld::log(" %s(RTLD_SELF, %s) ==> NULL\n", __func__, symbolName); return NULL; } +#if SUPPORT_ACCELERATE_TABLES + // check for mega dylib handle + else if ( dyld::isCacheHandle(handle) ) { + result = dyld::dlsymFromCache(handle, underscoredName, 0); + if ( dyld::gLogAPIs ) + dyld::log(" %s(%p, %s) ==> %p\n", __func__, handle, symbolName, result); + return result; + } +#endif // real handle image = (ImageLoader*)(((uintptr_t)handle) & (-4)); // clear mode bits if ( dyld::validImage(image) ) { @@ -1744,7 +1874,10 @@ void* dlsym(void* handle, const char* symbolName) void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue callerImage = dyld::findImageContainingAddress(callerAddress); } - return (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, callerImage); + result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, callerImage, false, underscoredName); + if ( dyld::gLogAPIs ) + dyld::log(" %s(%p, %s) ==> %p\n", __func__, handle, symbolName, result); + return result; } const char* str = dyld::mkstringf("dlsym(%p, %s): symbol not found", handle, symbolName); dlerrorSet(str); @@ -1754,6 +1887,8 @@ void* dlsym(void* handle, const char* symbolName) dlerrorSet("invalid handle passed to dlsym()"); } CRSetCrashLogMessage(NULL); + if ( dyld::gLogAPIs ) + dyld::log(" %s(%p, %s) ==> NULL\n", __func__, handle, symbolName); return NULL; } @@ -1771,12 +1906,17 @@ const struct dyld_all_image_infos* _dyld_get_all_image_infos() return dyld::gProcessInfo; } + #if SUPPORT_ZERO_COST_EXCEPTIONS static bool client_dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info) { //if ( dyld::gLogAPIs ) // dyld::log("%s(%p, %p)\n", __func__, addr, info); +#if SUPPORT_ACCELERATE_TABLES + if ( dyld::findUnwindSections(addr, info) ) + return true; +#endif ImageLoader* image = dyld::findImageContainingAddress(addr); if ( image != NULL ) { image->getUnwindInfo(info); @@ -1787,22 +1927,18 @@ static bool client_dyld_find_unwind_sections(void* addr, dyld_unwind_sections* i #endif -void dyld_register_image_state_change_handler(dyld_image_states state, bool batch, - dyld_image_state_change_handler handler) -{ - if ( dyld::gLogAPIs ) - dyld::log("%s(%d, %d, %p)\n", __func__, state, batch, handler); - if ( batch ) - dyld::registerImageStateBatchChangeHandler(state, handler); - else - dyld::registerImageStateSingleChangeHandler(state, handler); -} - const char* dyld_image_path_containing_address(const void* address) { if ( dyld::gLogAPIs ) dyld::log("%s(%p)\n", __func__, address); +#if SUPPORT_ACCELERATE_TABLES + const mach_header* mh; + const char* path; + if ( dyld::addressInCache(address, &mh, &path) ) + return path; +#endif + ImageLoader* image = dyld::findImageContainingAddress(address); if ( image != NULL ) return image->getRealPath(); @@ -1847,4 +1983,52 @@ void dyld_dynamic_interpose(const struct mach_header* mh, const struct dyld_inte } +bool _dyld_is_memory_immutable(const void* addr, size_t length) +{ + if ( dyld::gLogAPIs ) + dyld::log("%s(%p, %ld)\n", __func__, addr, length); + + uintptr_t checkStart = (uintptr_t)addr; + uintptr_t checkEnd = checkStart + length; + +#if DYLD_SHARED_CACHE_SUPPORT + // quick check to see if in r/o region of shared cache. If so return true. + if ( dyld_shared_cache_ranges.sharedRegionsCount > 2 ) { + uintptr_t roStart = dyld_shared_cache_ranges.ranges[0].start; + uintptr_t roEnd = roStart + dyld_shared_cache_ranges.ranges[0].length; + if ( (roStart < checkStart) && (checkEnd < roEnd) ) + return true; + } +#endif + // Otherwise find if addr is in a dyld loaded image + ImageLoader* image = dyld::findImageContainingAddress(addr); + if ( image != NULL ) { + // already checked for r/o portion of cache + if ( image->inSharedCache() ) + return false; + if ( !image->neverUnload() ) + return false; + for (unsigned i=0, e=image->segmentCount(); i < e; ++i) { + if ( (image->segActualLoadAddress(i) < checkStart) && (checkEnd < image->segActualEndAddress(i)) ) { + return !image->segWriteable(i); + } + } + } + return false; +} + + + +void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, + _dyld_objc_notify_init init, + _dyld_objc_notify_unmapped unmapped) +{ + dyld::registerObjCNotifiers(mapped, init, unmapped); +} + + +bool _dyld_get_shared_cache_uuid(uuid_t uuid) +{ + return dyld::sharedCacheUUID(uuid); +} diff --git a/src/dyldAPIsInLibSystem.cpp b/src/dyldAPIsInLibSystem.cpp index d1e48f5..46c5c3a 100644 --- a/src/dyldAPIsInLibSystem.cpp +++ b/src/dyldAPIsInLibSystem.cpp @@ -31,8 +31,13 @@ #include #include +#include +#include + +#include "mach-o/dyld_images.h" #include "mach-o/dyld.h" #include "mach-o/dyld_priv.h" +#include "dyld_cache_format.h" #include "ImageLoader.h" #include "dyldLock.h" @@ -595,6 +600,21 @@ uint32_t dyld_get_program_sdk_watch_os_version() } return 0; } + +uint32_t dyld_get_program_min_watch_os_version() +{ + const mach_header* mh = (mach_header*)_NSGetMachExecuteHeader(); + uint32_t loadCommand; + uint32_t minOS; + uint32_t sdk; + + if ( getVersionLoadCommandInfo(mh, &loadCommand, &minOS, &sdk) ) { + if ( loadCommand == LC_VERSION_MIN_WATCHOS ) + return minOS; // return raw minOS (not mapped to iOS version) + } + return 0; +} + #endif /* @@ -691,6 +711,36 @@ uint32_t dyld_get_program_min_os_version() } +bool _dyld_get_image_uuid(const struct mach_header* mh, uuid_t uuid) +{ + const load_command* startCmds = NULL; + if ( mh->magic == MH_MAGIC_64 ) + startCmds = (load_command*)((char *)mh + sizeof(mach_header_64)); + else if ( mh->magic == MH_MAGIC ) + startCmds = (load_command*)((char *)mh + sizeof(mach_header)); + else + return false; // not a mach-o file, or wrong endianness + + const load_command* const cmdsEnd = (load_command*)((char*)startCmds + mh->sizeofcmds); + const load_command* cmd = startCmds; + for(uint32_t i = 0; i < mh->ncmds; ++i) { + const load_command* nextCmd = (load_command*)((char *)cmd + cmd->cmdsize); + if ( (cmd->cmdsize < 8) || (nextCmd > cmdsEnd) || (nextCmd < startCmds)) { + return false; + } + if ( cmd->cmd == LC_UUID ) { + const uuid_command* uuidCmd = (uuid_command*)cmd; + memcpy(uuid, uuidCmd->uuid, 16); + return true; + } + cmd = nextCmd; + } + bzero(uuid, 16); + return false; +} + + + #if DEPRECATED_APIS_SUPPORTED /* * NSCreateObjectFileImageFromFile() creates an NSObjectFileImage for the @@ -795,7 +845,7 @@ NSSymbolDefinitionCountInObjectFileImage( NSObjectFileImage objectFileImage) { DYLD_LOCK_THIS_BLOCK; - static unsigned long (*p)(NSObjectFileImage) = NULL; + static uint32_t (*p)(NSObjectFileImage) = NULL; if(p == NULL) _dyld_func_lookup("__dyld_NSSymbolDefinitionCountInObjectFileImage", (void**)&p); @@ -832,7 +882,7 @@ NSSymbolReferenceCountInObjectFileImage( NSObjectFileImage objectFileImage) { DYLD_LOCK_THIS_BLOCK; - static unsigned long (*p)(NSObjectFileImage) = NULL; + static uint32_t (*p)(NSObjectFileImage) = NULL; if(p == NULL) _dyld_func_lookup("__dyld_NSSymbolReferenceCountInObjectFileImage", (void**)&p); @@ -964,7 +1014,7 @@ _NSGetExecutablePath( char *buf, uint32_t *bufsize) { - DYLD_LOCK_THIS_BLOCK; + DYLD_NO_LOCK_THIS_BLOCK; static int (*p)(char *buf, uint32_t *bufsize) = NULL; if(p == NULL) @@ -1350,19 +1400,13 @@ static bool hasPerThreadBufferFor_dlerror() typedef vproc_err_t (*vswapproc)(vproc_t vp, vproc_gsk_t key,int64_t *inval, int64_t *outval); static vswapproc swapProc = &vproc_swap_integer; -static bool isLaunchdOwned() { - static bool first = true; - static bool result; - if ( first ) { - int64_t val = 0; - (*swapProc)(NULL, VPROC_GSK_IS_MANAGED, NULL, &val); - result = ( val != 0 ); - first = false; - } - return result; +static bool isLaunchdOwned() +{ + int64_t val = 0; + (*swapProc)(NULL, VPROC_GSK_IS_MANAGED, NULL, &val); + return ( val != 0 ); } - #if DYLD_SHARED_CACHE_SUPPORT static void shared_cache_missing() { @@ -1394,7 +1438,8 @@ static dyld::LibSystemHelpers sHelpers = { 13, &dyldGlobalLockAcquire, &dyldGlob &isLaunchdOwned, &vm_allocate, &mmap, - &__cxa_finalize_ranges}; + &__cxa_finalize_ranges + }; // @@ -1483,17 +1528,6 @@ void* dlsym(void* handle, const char* symbol) return(p(handle, symbol)); } -void dyld_register_image_state_change_handler(dyld_image_states state, - bool batch, dyld_image_state_change_handler handler) -{ - DYLD_LOCK_THIS_BLOCK; - static void* (*p)(dyld_image_states, bool, dyld_image_state_change_handler) = NULL; - - if(p == NULL) - _dyld_func_lookup("__dyld_dyld_register_image_state_change_handler", (void**)&p); - p(state, batch, handler); -} - const struct dyld_all_image_infos* _dyld_get_all_image_infos() { @@ -1563,6 +1597,16 @@ bool dyld_shared_cache_some_image_overridden() return p(); } +bool _dyld_get_shared_cache_uuid(uuid_t uuid) +{ + DYLD_NO_LOCK_THIS_BLOCK; + static bool (*p)(uuid_t) = NULL; + + if(p == NULL) + _dyld_func_lookup("__dyld_get_shared_cache_uuid", (void**)&p); + return p(uuid); +} + bool dyld_process_is_restricted() { @@ -1610,4 +1654,154 @@ void _dyld_fork_child() +static void* mapStartOfCache(const char* path, size_t length) +{ + struct stat statbuf; + if ( ::stat(path, &statbuf) == -1 ) + return NULL; + + if ( statbuf.st_size < length ) + return NULL; + + int cache_fd = ::open(path, O_RDONLY); + if ( cache_fd < 0 ) + return NULL; + + void* result = ::mmap(NULL, length, PROT_READ, MAP_PRIVATE, cache_fd, 0); + close(cache_fd); + + if ( result == MAP_FAILED ) + return NULL; + + return result; +} + + +static const dyld_cache_header* findCacheInDirAndMap(const uuid_t cacheUuid, const char* dirPath) +{ + DIR* dirp = ::opendir(dirPath); + if ( dirp != NULL) { + dirent entry; + dirent* entp = NULL; + char cachePath[PATH_MAX]; + while ( ::readdir_r(dirp, &entry, &entp) == 0 ) { + if ( entp == NULL ) + break; + if ( entp->d_type != DT_REG ) + continue; + if ( strlcpy(cachePath, dirPath, PATH_MAX) >= PATH_MAX ) + continue; + if ( strlcat(cachePath, "/", PATH_MAX) >= PATH_MAX ) + continue; + if ( strlcat(cachePath, entp->d_name, PATH_MAX) >= PATH_MAX ) + continue; + if ( const dyld_cache_header* cacheHeader = (dyld_cache_header*)mapStartOfCache(cachePath, 0x00100000) ) { + if ( ::memcmp(cacheHeader->uuid, cacheUuid, 16) != 0 ) { + // wrong uuid, unmap and keep looking + ::munmap((void*)cacheHeader, 0x00100000); + } + else { + // found cache + closedir(dirp); + return cacheHeader; + } + } + } + closedir(dirp); + } + return NULL; +} + +int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extraSearchDirs[], void (^callback)(const dyld_shared_cache_dylib_text_info* info)) +{ + const dyld_cache_header* cacheHeader = NULL; + bool needToUnmap = true; + + // get info from dyld about this process, to see if requested cache is already mapped into this process + const dyld_all_image_infos* allInfo = _dyld_get_all_image_infos(); + if ( (allInfo != NULL) && (memcmp(allInfo->sharedCacheUUID, cacheUuid, 16) == 0) ) { + // requested cache is already mapped, just re-use it + cacheHeader = (dyld_cache_header*)(SHARED_REGION_BASE + allInfo->sharedCacheSlide); + needToUnmap = false; + } + else { + // look first is default location for cache files + #if __IPHONE_OS_VERSION_MIN_REQUIRED + const char* defaultSearchDir = IPHONE_DYLD_SHARED_CACHE_DIR; + #else + const char* defaultSearchDir = MACOSX_DYLD_SHARED_CACHE_DIR; + #endif + cacheHeader = findCacheInDirAndMap(cacheUuid, defaultSearchDir); + // if not there, look in extra search locations + if ( cacheHeader == NULL ) { + for (const char** p = extraSearchDirs; *p != NULL; ++p) { + cacheHeader = findCacheInDirAndMap(cacheUuid, *p); + if ( cacheHeader != NULL ) + break; + } + } + } + + if ( cacheHeader == NULL ) + return -1; + + if ( cacheHeader->mappingOffset < sizeof(dyld_cache_header) ) { + // old cache without imagesText array + if ( needToUnmap ) + ::munmap((void*)cacheHeader, 0x00100000); + return -1; + } + + // walk imageText table and call callback for each entry + const dyld_cache_image_text_info* imagesText = (dyld_cache_image_text_info*)((char*)cacheHeader + cacheHeader->imagesTextOffset); + const dyld_cache_image_text_info* imagesTextEnd = &imagesText[cacheHeader->imagesTextCount]; + for (const dyld_cache_image_text_info* p=imagesText; p < imagesTextEnd; ++p) { + dyld_shared_cache_dylib_text_info dylibTextInfo; + dylibTextInfo.version = 1; + dylibTextInfo.loadAddressUnslid = p->loadAddress; + dylibTextInfo.textSegmentSize = p->textSegmentSize; + dylibTextInfo.path = (char*)cacheHeader + p->pathOffset; + ::memcpy(dylibTextInfo.dylibUuid, p->uuid, 16); + callback(&dylibTextInfo); + } + + if ( needToUnmap ) + ::munmap((void*)cacheHeader, 0x00100000); + + return 0; +} + +int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callback)(const dyld_shared_cache_dylib_text_info* info)) +{ + const char* extraSearchDirs[] = { NULL }; + return dyld_shared_cache_find_iterate_text(cacheUuid, extraSearchDirs, callback); +} + + +bool _dyld_is_memory_immutable(const void* addr, size_t length) +{ + DYLD_NO_LOCK_THIS_BLOCK; + static bool (*p)(const void*, size_t) = NULL; + + if(p == NULL) + _dyld_func_lookup("__dyld_is_memory_immutable", (void**)&p); + return p(addr, length); +} + + +void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, + _dyld_objc_notify_init init, + _dyld_objc_notify_unmapped unmapped) +{ + DYLD_LOCK_THIS_BLOCK; + static bool (*p)(_dyld_objc_notify_mapped, _dyld_objc_notify_init, _dyld_objc_notify_unmapped) = NULL; + + if(p == NULL) + _dyld_func_lookup("__dyld_objc_notify_register", (void**)&p); + p(mapped, init, unmapped); +} + + + + diff --git a/src/dyldInitialization.cpp b/src/dyldInitialization.cpp index 50d758d..692b271 100644 --- a/src/dyldInitialization.cpp +++ b/src/dyldInitialization.cpp @@ -115,7 +115,7 @@ static uintptr_t slideOfMainExecutable(const struct macho_header* mh) for (uint32_t i = 0; i < cmd_count; ++i) { if ( cmd->cmd == LC_SEGMENT_COMMAND ) { const struct macho_segment_command* segCmd = (struct macho_segment_command*)cmd; - if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { + if ( (segCmd->fileoff == 0) && (segCmd->filesize != 0)) { return (uintptr_t)mh - segCmd->vmaddr; } } diff --git a/src/dyldNew.cpp b/src/dyldNew.cpp index ebccc77..cf64f58 100644 --- a/src/dyldNew.cpp +++ b/src/dyldNew.cpp @@ -62,7 +62,7 @@ static dyld_static_pool* currentPool = &initialPool; void* malloc(size_t size) { - if ( dyld::gLibSystemHelpers != NULL) { + if ( (dyld::gLibSystemHelpers != NULL) && dyld::gProcessInfo->libSystemInitialized ) { void* p = dyld::gLibSystemHelpers->malloc(size); //dyld::log("malloc(%lu) => %p from libSystem\n", size, p); return p; diff --git a/src/dyldStartup.s b/src/dyldStartup.s index 71d45a3..8f71e0c 100644 --- a/src/dyldStartup.s +++ b/src/dyldStartup.s @@ -324,36 +324,17 @@ Lapple: ldr x4, [x3] #endif // __arm64__ -/* - * dyld calls this function to terminate a process. - * It has a label so that CrashReporter can distinguish this - * termination from a random crash. rdar://problem/4764143 - */ +// When iOS 10.0 simulator runs on 10.11, abort_with_payload() does not exist, +// so it falls back and uses dyld_fatal_error(). +#if TARGET_IPHONE_SIMULATOR .text .align 2 .globl _dyld_fatal_error _dyld_fatal_error: -#if __arm__ - trap - nop -#elif __x86_64__ || __i386__ int3 nop -#elif __arm64__ - brk #3 -#else - #error unknown architecture #endif -#if __arm__ - // work around for: gdb-1109: notifier in dyld does not work if it is in thumb - .text - .align 2 - .globl _gdb_image_notifier - .private_extern _gdb_image_notifier -_gdb_image_notifier: - bx lr -#endif diff --git a/src/dyldSyscallInterface.h b/src/dyldSyscallInterface.h index 01a8e80..947d39a 100644 --- a/src/dyldSyscallInterface.h +++ b/src/dyldSyscallInterface.h @@ -78,6 +78,21 @@ namespace dyld { // Added in version 4 void (*coresymbolication_load_notifier)(void *connection, uint64_t load_timestamp, const char *image_path, const struct mach_header *mach_header); void (*coresymbolication_unload_notifier)(void *connection, uint64_t unload_timestamp, const char *image_path, const struct mach_header *mach_header); + // Added in version 5 + int (*proc_regionfilename)(int pid, uint64_t address, void* buffer, uint32_t buffersize); + int (*getpid)(); + kern_return_t (*mach_port_insert_right)(ipc_space_t task, mach_port_name_t name, mach_port_t poly, mach_msg_type_name_t polyPoly); + kern_return_t (*mach_port_allocate)(ipc_space_t, mach_port_right_t, mach_port_name_t*); + kern_return_t (*mach_msg)(mach_msg_header_t *, mach_msg_option_t , mach_msg_size_t , mach_msg_size_t , mach_port_name_t , mach_msg_timeout_t , mach_port_name_t); + // Added in version 6 + void (*abort_with_payload)(uint32_t reason_namespace, uint64_t reason_code, void* payload, uint32_t payload_size, const char* reason_string, uint64_t reason_flags); + // Add in version 7 + kern_return_t (*task_register_dyld_image_infos)(task_t task, dyld_kernel_image_info_array_t dyld_images, mach_msg_type_number_t dyld_imagesCnt); + kern_return_t (*task_unregister_dyld_image_infos)(task_t task, dyld_kernel_image_info_array_t dyld_images, mach_msg_type_number_t dyld_imagesCnt); + kern_return_t (*task_get_dyld_image_infos)(task_t task, dyld_kernel_image_info_array_t *dyld_images, mach_msg_type_number_t *dyld_imagesCnt); + kern_return_t (*task_register_dyld_shared_cache_image_info)(task_t task, dyld_kernel_image_info_t dyld_cache_image, boolean_t no_cache, boolean_t private_cache); + kern_return_t (*task_register_dyld_set_dyld_state)(task_t task, uint8_t dyld_state); + kern_return_t (*task_register_dyld_get_process_state)(task_t task, dyld_kernel_process_info_t *dyld_process_state); }; extern const struct SyscallHelpers* gSyscallHelpers; @@ -87,4 +102,4 @@ namespace dyld { } #endif -#endif \ No newline at end of file +#endif diff --git a/src/dyld_gdb.cpp b/src/dyld_gdb.cpp index 5f07dcf..f13064d 100644 --- a/src/dyld_gdb.cpp +++ b/src/dyld_gdb.cpp @@ -32,6 +32,7 @@ #include "mach-o/dyld_gdb.h" #include "mach-o/dyld_images.h" +#include "mach-o/dyld_process_info.h" #include "ImageLoader.h" #include "dyld.h" @@ -47,6 +48,27 @@ VECTOR_NEVER_DESTRUCTED(dyld_uuid_info); static std::vector sImageInfos; static std::vector sImageUUIDs; +size_t allImagesCount() +{ + return sImageInfos.size(); +} + +const mach_header* allImagesIndexedMachHeader(uint32_t index) +{ + if ( index < sImageInfos.size() ) + return sImageInfos[index].imageLoadAddress; + else + return NULL; +} + +const char* allImagesIndexedPath(uint32_t index) +{ + if ( index < sImageInfos.size() ) + return sImageInfos[index].imageFilePath; + else + return NULL; +} + void addImagesToAllImages(uint32_t infoCount, const dyld_image_info info[]) { @@ -62,7 +84,8 @@ void addImagesToAllImages(uint32_t infoCount, const dyld_image_info info[]) for (uint32_t i=0; i < infoCount; ++i) sImageInfos.push_back(info[i]); dyld::gProcessInfo->infoArrayCount = (uint32_t)sImageInfos.size(); - + dyld::gProcessInfo->infoArrayChangeTimestamp = mach_absolute_time(); + // set infoArray back to base address of vector (other process can now read) dyld::gProcessInfo->infoArray = &sImageInfos[0]; } @@ -89,11 +112,15 @@ void syncProcessInfo() const char* notifyGDB(enum dyld_image_states state, uint32_t infoCount, const dyld_image_info info[]) { // tell gdb that about the new images + uint64_t t0 = mach_absolute_time(); dyld::gProcessInfo->notification(dyld_image_adding, infoCount, info); + uint64_t t1 = mach_absolute_time(); + ImageLoader::fgTotalDebuggerPausedTime += (t1-t0); + // record initial count of images // so CrashReporter can note which images were dynamically loaded if ( dyld::gProcessInfo->initialImageCount == 0 ) - dyld::gProcessInfo->initialImageCount = infoCount; + dyld::gProcessInfo->initialImageCount = dyld::gProcessInfo->infoArrayCount; return NULL; } @@ -144,7 +171,8 @@ void removeImageFromAllImages(const struct mach_header* loadAddress) } } dyld::gProcessInfo->uuidArrayCount = sImageUUIDs.size(); - + dyld::gProcessInfo->infoArrayChangeTimestamp = mach_absolute_time(); + // set infoArray back to base address of vector dyld::gProcessInfo->uuidArray = &sImageUUIDs[0]; @@ -152,12 +180,6 @@ void removeImageFromAllImages(const struct mach_header* loadAddress) dyld::gProcessInfo->notification(dyld_image_removing, 1, &goingAway); } -void setAlImageInfosHalt(const char* message, uintptr_t flags) -{ - dyld::gProcessInfo->errorMessage = message; - dyld::gProcessInfo->terminationFlags = flags; -} - #if TARGET_IPHONE_SIMULATOR namespace dyld { @@ -165,21 +187,38 @@ void setAlImageInfosHalt(const char* message, uintptr_t flags) } #else - #if __arm__ - // work around for: gdb-1109: notifier in dyld does not work if it is in thumb - extern "C" void gdb_image_notifier(enum dyld_image_mode mode, uint32_t infoCount, const dyld_image_info info[]); - #else - static void gdb_image_notifier(enum dyld_image_mode mode, uint32_t infoCount, const dyld_image_info info[]) - { - // do nothing - // gdb sets a break point here to catch notifications - //dyld::log("dyld: gdb_image_notifier(%s, %d, ...)\n", mode ? "dyld_image_removing" : "dyld_image_adding", infoCount); - //for (uint32_t i=0; i < infoCount; ++i) - // dyld::log("dyld: %d loading at %p %s\n", i, info[i].imageLoadAddress, info[i].imageFilePath); - //for (uint32_t i=0; i < dyld::gProcessInfo->infoArrayCount; ++i) - // dyld::log("dyld: %d loading at %p %s\n", i, dyld::gProcessInfo->infoArray[i].imageLoadAddress, dyld::gProcessInfo->infoArray[i].imageFilePath); + static void gdb_image_notifier(enum dyld_image_mode mode, uint32_t infoCount, const dyld_image_info info[]) + { + uint64_t machHeaders[infoCount]; + for (uint32_t i=0; i < infoCount; ++i) { + machHeaders[i] = (uintptr_t)(info[i].imageLoadAddress); + } + switch ( mode ) { + case dyld_image_adding: + _dyld_debugger_notification(dyld_notify_adding, infoCount, machHeaders); + break; + case dyld_image_removing: + _dyld_debugger_notification(dyld_notify_removing, infoCount, machHeaders); + break; + default: + break; } - #endif + // do nothing + // gdb sets a break point here to catch notifications + //dyld::log("dyld: gdb_image_notifier(%s, %d, ...)\n", mode ? "dyld_image_removing" : "dyld_image_adding", infoCount); + //for (uint32_t i=0; i < infoCount; ++i) + // dyld::log("dyld: %d loading at %p %s\n", i, info[i].imageLoadAddress, info[i].imageFilePath); + //for (uint32_t i=0; i < dyld::gProcessInfo->infoArrayCount; ++i) + // dyld::log("dyld: %d loading at %p %s\n", i, dyld::gProcessInfo->infoArray[i].imageLoadAddress, dyld::gProcessInfo->infoArray[i].imageFilePath); + } + + // only used with accelerator tables and ASan which images need to be re-loaded + void resetAllImages() + { + sImageInfos.clear(); + sImageUUIDs.clear(); + _dyld_debugger_notification(dyld_notify_remove_all, 0, NULL); + } extern void* __dso_handle; #define STR(s) # s @@ -187,10 +226,10 @@ void setAlImageInfosHalt(const char* message, uintptr_t flags) struct dyld_all_image_infos dyld_all_image_infos __attribute__ ((section ("__DATA,__all_image_info"))) = { - 14, 0, NULL, &gdb_image_notifier, false, false, (const mach_header*)&__dso_handle, NULL, + 15, 0, NULL, &gdb_image_notifier, false, false, (const mach_header*)&__dso_handle, NULL, XSTR(DYLD_VERSION), NULL, 0, NULL, 0, 0, NULL, &dyld_all_image_infos, - 0, dyld_error_kind_none, NULL, NULL, NULL, 0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}, - {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,} + 0, 0, NULL, NULL, NULL, 0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}, + 0, 0, "/usr/lib/dyld", {0}, {0} }; struct dyld_shared_cache_ranges dyld_shared_cache_ranges; diff --git a/src/dyld_process_info.cpp b/src/dyld_process_info.cpp new file mode 100644 index 0000000..849f17c --- /dev/null +++ b/src/dyld_process_info.cpp @@ -0,0 +1,648 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2016 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dyld_process_info.h" +#include "dyld_process_info_internal.h" +#include "dyld_images.h" +#include "dyld_priv.h" + + +// +// Opaque object returned by _dyld_process_info_create() +// + +struct __attribute__((visibility("hidden"))) dyld_process_info_base { + static dyld_process_info_base* make(task_t task, const dyld_all_image_infos_64& allImageInfo, const dyld_image_info_64 imageArray[], kern_return_t* kr); + static dyld_process_info_base* makeSuspended(task_t task, kern_return_t* kr); + + uint32_t& retainCount() const { return _retainCount; } + dyld_process_cache_info* cacheInfo() const { return (dyld_process_cache_info*)(((char*)this) + _cacheInfoOffset); } + dyld_process_state_info* stateInfo() const { return (dyld_process_state_info*)(((char*)this) + _stateInfoOffset); } + void forEachImage(void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)) const; + void forEachSegment(uint64_t machHeaderAddress, void (^callback)(uint64_t segmentAddress, uint64_t segmentSize, const char* segmentName)) const; + +private: + struct ImageInfo { + uuid_t uuid; + uint64_t loadAddress; + const char* path; + uint32_t segmentStartIndex; + uint32_t segmentsCount; + }; + + struct SegmentInfo { + const char* name; + uint64_t addr; + uint64_t size; + }; + + dyld_process_info_base(unsigned imageCount, size_t totalSize); + void* operator new (size_t, void* buf) { return buf; } + + static bool inCache(uint64_t addr) { return (addr > SHARED_REGION_BASE) && (addr < SHARED_REGION_BASE+SHARED_REGION_SIZE); } + kern_return_t addImage(task_t task, bool sameCacheAsThisProcess, uint64_t imageAddress, uint64_t imagePath, const char* imagePathLocal); + kern_return_t addDyldImage(task_t task, uint64_t dyldAddress, uint64_t dyldPathAddress, const char* localPath); + + bool invalid() { return ((char*)_stringRevBumpPtr < (char*)_curSegment); } + const char* copyPath(task_t task, uint64_t pathAddr, kern_return_t* kr); + const char* addString(const char*); + const char* copySegmentName(const char*); + + void addInfoFromLoadCommands(const mach_header* mh, uint64_t addressInTask, size_t size); + + void inspectLocalImageLoadCommands(uint64_t imageAddress, void* func); + kern_return_t inspectRemoteImageLoadCommands(task_t task, uint64_t imageAddress, void* func); + + mutable uint32_t _retainCount; + const uint32_t _cacheInfoOffset; + const uint32_t _stateInfoOffset; + const uint32_t _imageInfosOffset; + const uint32_t _segmentInfosOffset; + ImageInfo* const _firstImage; + ImageInfo* _curImage; + SegmentInfo* const _firstSegment; + SegmentInfo* _curSegment; + uint32_t _curSegmentIndex; + char* _stringRevBumpPtr; + + // dyld_process_cache_info cacheInfo; + // dyld_process_state_info stateInfo; + // ImageInfo images[]; + // SegmentInfo segments[]; + // char stringPool[] +}; + +dyld_process_info_base::dyld_process_info_base(unsigned imageCount, size_t totalSize) + : _retainCount(1), _cacheInfoOffset(sizeof(dyld_process_info_base)), + _stateInfoOffset(sizeof(dyld_process_info_base) + sizeof(dyld_process_cache_info)), + _imageInfosOffset(sizeof(dyld_process_info_base) + sizeof(dyld_process_cache_info) + sizeof(dyld_process_state_info)), + _segmentInfosOffset(sizeof(dyld_process_info_base) + sizeof(dyld_process_cache_info) + sizeof(dyld_process_state_info) + imageCount*sizeof(ImageInfo)), + _firstImage((ImageInfo*)(((uint8_t*)this) + _imageInfosOffset)), + _curImage((ImageInfo*)(((uint8_t*)this) + _imageInfosOffset)), + _firstSegment((SegmentInfo*)(((uint8_t*)this) + _segmentInfosOffset)), + _curSegment((SegmentInfo*)(((uint8_t*)this) + _segmentInfosOffset)), + _curSegmentIndex(0), + _stringRevBumpPtr((char*)(this)+totalSize) +{ +} + + +dyld_process_info_base* dyld_process_info_base::make(task_t task, const dyld_all_image_infos_64& allImageInfo, const dyld_image_info_64 imageArray[], kern_return_t* kr) +{ + // figure out how many path strings will need to be copied and their size + const dyld_all_image_infos* myInfo = _dyld_get_all_image_infos(); + bool sameCacheAsThisProcess = ((memcmp(myInfo->sharedCacheUUID, allImageInfo.sharedCacheUUID, 16) == 0) && (myInfo->sharedCacheSlide == allImageInfo.sharedCacheSlide)); + unsigned countOfPathsNeedingCopying = 0; + if ( sameCacheAsThisProcess ) { + for (int i=0; i < allImageInfo.infoArrayCount; ++i) { + if ( !inCache(imageArray[i].imageFilePath) ) + ++countOfPathsNeedingCopying; + } + } + else { + countOfPathsNeedingCopying = allImageInfo.infoArrayCount+1; + } + unsigned imageCountWithDyld = allImageInfo.infoArrayCount+1; + + // allocate result object + size_t allocationSize = sizeof(dyld_process_info_base) + + sizeof(dyld_process_cache_info) + + sizeof(dyld_process_state_info) + + sizeof(ImageInfo)*(imageCountWithDyld) + + sizeof(SegmentInfo)*imageCountWithDyld*5 + + countOfPathsNeedingCopying*PATH_MAX; + void* storage = malloc(allocationSize); + dyld_process_info_base* obj = new (storage) dyld_process_info_base(imageCountWithDyld, allocationSize); // placement new() + + // fill in base info + dyld_process_cache_info* cacheInfo = obj->cacheInfo(); + memcpy(cacheInfo->cacheUUID, allImageInfo.sharedCacheUUID, 16); + cacheInfo->cacheBaseAddress = allImageInfo.sharedCacheBaseAddress; + cacheInfo->privateCache = allImageInfo.processDetachedFromSharedRegion; + // if no cache is used, allImageInfo has all zeros for cache UUID + cacheInfo->noCache = true; + for (int i=0; i < 16; ++i) { + if ( cacheInfo->cacheUUID[i] != 0 ) { + cacheInfo->noCache = false; + } + } + + dyld_process_state_info* stateInfo = obj->stateInfo(); + stateInfo->timestamp = allImageInfo.infoArrayChangeTimestamp; + stateInfo->imageCount = imageCountWithDyld; + stateInfo->initialImageCount = (uint32_t)(allImageInfo.initialImageCount+1); + if ( allImageInfo.infoArray != 0 ) + stateInfo->dyldState = dyld_process_state_dyld_initialized; + if ( allImageInfo.libSystemInitialized != 0 ) { + stateInfo->dyldState = dyld_process_state_libSystem_initialized; + if ( allImageInfo.initialImageCount != allImageInfo.infoArrayCount ) + stateInfo->dyldState = dyld_process_state_program_running; + } + if ( allImageInfo.errorMessage != 0 ) + stateInfo->dyldState = allImageInfo.terminationFlags ? dyld_process_state_terminated_before_inits : dyld_process_state_dyld_terminated; + + // fill in info for dyld + if ( allImageInfo.dyldPath != 0 ) { + if ( kern_return_t r = obj->addDyldImage(task, allImageInfo.dyldImageLoadAddress, allImageInfo.dyldPath, NULL) ) { + if ( kr != NULL ) + *kr = r; + goto fail; + } + } + + // fill in info for each image + for (uint32_t i=0; i < allImageInfo.infoArrayCount; ++i) { + if ( kern_return_t r = obj->addImage(task, sameCacheAsThisProcess, imageArray[i].imageLoadAddress, imageArray[i].imageFilePath, NULL) ) { + if ( kr != NULL ) + *kr = r; + goto fail; + } + } + + // sanity check internal data did not overflow + if ( obj->invalid() ) + goto fail; + + return obj; + +fail: + free(obj); + return NULL; +} + +dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_return_t* kr) +{ + pid_t pid; + kern_return_t result = pid_for_task(task, &pid); + if ( result != KERN_SUCCESS ) { + if ( kr != NULL ) + *kr = result; + return NULL; + } + + unsigned imageCount = 0; // main executable and dyld + uint64_t mainExecutableAddress = 0; + uint64_t dyldAddress = 0; + char dyldPathBuffer[PATH_MAX+1]; + char mainExecutablePathBuffer[PATH_MAX+1]; + mach_vm_size_t size; + for (mach_vm_address_t address = 0; ; address += size) { + vm_region_basic_info_data_64_t info; + mach_port_t objectName; + unsigned int infoCount = VM_REGION_BASIC_INFO_COUNT_64; + result = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO, + (vm_region_info_t)&info, &infoCount, &objectName); + if ( result != KERN_SUCCESS ) + break; + if ( info.protection == (VM_PROT_READ|VM_PROT_EXECUTE) ) { + if ( mainExecutableAddress == 0 ) { + mainExecutableAddress = address; + int len = proc_regionfilename(pid, mainExecutableAddress, mainExecutablePathBuffer, PATH_MAX); + if ( len != 0 ) + mainExecutablePathBuffer[len] = '\0'; + ++imageCount; + } + else if ( dyldAddress == 0 ) { + dyldAddress = address; + int len = proc_regionfilename(pid, dyldAddress, dyldPathBuffer, PATH_MAX); + if ( len != 0 ) + dyldPathBuffer[len] = '\0'; + ++imageCount; + } + //fprintf(stderr, "vm region: addr=0x%llX, size=0x%llX, prot=0x%X\n", (uint64_t)address, (uint64_t)size, info.protection); + } + } + //fprintf(stderr, "dyld: addr=0x%llX, path=%s\n", dyldAddress, dyldPathBuffer); + //fprintf(stderr, "app: addr=0x%llX, path=%s\n", mainExecutableAddress, mainExecutablePathBuffer); + + // allocate result object + size_t allocationSize = sizeof(dyld_process_info_base) + + sizeof(dyld_process_cache_info) + + sizeof(dyld_process_state_info) + + sizeof(ImageInfo)*(imageCount) + + sizeof(SegmentInfo)*imageCount*5 + + imageCount*PATH_MAX; + void* storage = malloc(allocationSize); + dyld_process_info_base* obj = new (storage) dyld_process_info_base(imageCount, allocationSize); // placement new() + + // fill in base info + dyld_process_cache_info* cacheInfo = obj->cacheInfo(); + bzero(cacheInfo->cacheUUID, 16); + cacheInfo->cacheBaseAddress = 0; + cacheInfo->noCache = true; + cacheInfo->privateCache = false; + + dyld_process_state_info* stateInfo = obj->stateInfo(); + stateInfo->timestamp = 0; + stateInfo->imageCount = imageCount; + stateInfo->initialImageCount = imageCount; + stateInfo->dyldState = dyld_process_state_not_started; + + // fill in info for dyld + if ( dyldAddress != 0 ) { + if ( kern_return_t r = obj->addDyldImage(task, dyldAddress, 0, dyldPathBuffer) ) { + if ( kr != NULL ) + *kr = r; + free(obj); + return NULL; + } + } + + // fill in info for each image + if ( mainExecutableAddress != 0 ) { + if ( kern_return_t r = obj->addImage(task, false, mainExecutableAddress, 0, mainExecutablePathBuffer) ) { + if ( kr != NULL ) + *kr = r; + free(obj); + return NULL; + } + } + + return obj; +} + + + +const char* dyld_process_info_base::addString(const char* str) +{ + size_t len = strlen(str) + 1; + _stringRevBumpPtr -= len; + strcpy(_stringRevBumpPtr, str); + return _stringRevBumpPtr; +} + +const char* dyld_process_info_base::copyPath(task_t task, uint64_t stringAddressInTask, kern_return_t* kr) +{ + char temp[PATH_MAX+8]; // +8 is to allow '\0' at temp[PATH_MAX] + mach_vm_size_t readSize = PATH_MAX; + if ( ((stringAddressInTask & 0xFFF) + PATH_MAX) < 4096 ) { + // string fits within page, only one vm_read needed + if ( kern_return_t r = mach_vm_read_overwrite(task, stringAddressInTask, PATH_MAX, (vm_address_t)&temp, &readSize) ) { + if ( kr != NULL ) + *kr = r; + return NULL; + } + } + else { + // string may cross page boundary, split into two reads + size_t firstLen = 4096 - (stringAddressInTask & 0xFFF); + readSize = firstLen; + if ( kern_return_t r = mach_vm_read_overwrite(task, stringAddressInTask, firstLen, (vm_address_t)&temp, &readSize) ) { + if ( kr != NULL ) + *kr = r; + return NULL; + } + temp[firstLen] = '\0'; + if ( strlen(temp) >= firstLen ) { + readSize = PATH_MAX-firstLen; + if ( kern_return_t r = mach_vm_read_overwrite(task, stringAddressInTask+firstLen, PATH_MAX-firstLen, (vm_address_t)&temp+firstLen, &readSize) ) { + if ( kr != NULL ) + *kr = r; + return NULL; + temp[PATH_MAX] = '\0'; // truncate any string that is too long + } + } + } + if ( kr != NULL ) + *kr = KERN_SUCCESS; + return addString(temp); +} + + +kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThisProcess, uint64_t imageAddress, uint64_t imagePath, const char* imagePathLocal) +{ + _curImage->loadAddress = imageAddress; + _curImage->segmentStartIndex = _curSegmentIndex; + if ( imagePathLocal != NULL ) { + _curImage->path = addString(imagePathLocal); + } + else if ( sameCacheAsThisProcess && inCache(imagePath) ) { + _curImage->path = (const char*)imagePath; + } + else { + kern_return_t kr; + _curImage->path = copyPath(task, imagePath, &kr); + if ( kr ) + return kr; + } + if ( sameCacheAsThisProcess && inCache(imageAddress) ) { + addInfoFromLoadCommands((mach_header*)imageAddress, imageAddress, 32*1024); + } + else { + mach_vm_size_t readSize = sizeof(mach_header_64); + mach_header_64 mhBuffer; + if ( kern_return_t r = mach_vm_read_overwrite(task, imageAddress, sizeof(mach_header_64), (vm_address_t)&mhBuffer, &readSize) ) { + return r; + } + size_t headerPagesSize = (sizeof(mach_header_64) + mhBuffer.sizeofcmds + 4095) & (-4096); + vm_address_t localCopyBuffer; + unsigned int localCopyBufferSize; + if ( kern_return_t r = mach_vm_read(task, imageAddress, headerPagesSize, &localCopyBuffer, &localCopyBufferSize) ) { + return r; + } + addInfoFromLoadCommands((mach_header*)localCopyBuffer, imageAddress, localCopyBufferSize); + vm_deallocate(mach_task_self(), localCopyBuffer, localCopyBufferSize); + } + _curImage->segmentsCount = _curSegmentIndex - _curImage->segmentStartIndex; + _curImage++; + return KERN_SUCCESS; +} + + +kern_return_t dyld_process_info_base::addDyldImage(task_t task, uint64_t dyldAddress, uint64_t dyldPathAddress, const char* localPath) +{ + kern_return_t kr; + _curImage->loadAddress = dyldAddress; + _curImage->segmentStartIndex = _curSegmentIndex; + if ( localPath != NULL ) { + _curImage->path = addString(localPath); + } + else { + _curImage->path = copyPath(task, dyldPathAddress, &kr); + if ( kr ) + return kr; + } + + mach_vm_size_t readSize = sizeof(mach_header_64); + mach_header_64 mhBuffer; + if ( kern_return_t r = mach_vm_read_overwrite(task, dyldAddress, sizeof(mach_header_64), (vm_address_t)&mhBuffer, &readSize) ) { + return r; + } + size_t headerPagesSize = (sizeof(mach_header_64) + mhBuffer.sizeofcmds + 4095) & (-4096); + vm_address_t localCopyBuffer; + unsigned int localCopyBufferSize; + if ( kern_return_t r = mach_vm_read(task, dyldAddress, headerPagesSize, &localCopyBuffer, &localCopyBufferSize) ) { + return r; + } + addInfoFromLoadCommands((mach_header*)localCopyBuffer, dyldAddress, localCopyBufferSize); + vm_deallocate(mach_task_self(), localCopyBuffer, localCopyBufferSize); + _curImage->segmentsCount = _curSegmentIndex - _curImage->segmentStartIndex; + _curImage++; + return KERN_SUCCESS; +} + + +void dyld_process_info_base::addInfoFromLoadCommands(const mach_header* mh, uint64_t addressInTask, size_t size) +{ + const load_command* startCmds = NULL; + if ( mh->magic == MH_MAGIC_64 ) + startCmds = (load_command*)((char *)mh + sizeof(mach_header_64)); + else if ( mh->magic == MH_MAGIC ) + startCmds = (load_command*)((char *)mh + sizeof(mach_header)); + else + return; // not a mach-o file, or wrong endianness + + const load_command* const cmdsEnd = (load_command*)((char*)startCmds + mh->sizeofcmds); + const load_command* cmd = startCmds; + for(uint32_t i = 0; i < mh->ncmds; ++i) { + const load_command* nextCmd = (load_command*)((char *)cmd + cmd->cmdsize); + if ( (cmd->cmdsize < 8) || (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) { + return; // malformed load command + } + if ( cmd->cmd == LC_UUID ) { + const uuid_command* uuidCmd = (uuid_command*)cmd; + memcpy(_curImage->uuid, uuidCmd->uuid, 16); + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + _curSegment->name = copySegmentName(segCmd->segname); + _curSegment->addr = segCmd->vmaddr; + _curSegment->size = segCmd->vmsize; + _curSegment++; + _curSegmentIndex++; + } + else if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* segCmd = (segment_command_64*)cmd; + _curSegment->name = copySegmentName(segCmd->segname); + _curSegment->addr = segCmd->vmaddr; + _curSegment->size = segCmd->vmsize; + _curSegment++; + _curSegmentIndex++; + } + cmd = nextCmd; + } +} + +const char* dyld_process_info_base::copySegmentName(const char* name) +{ + // don't copy names of standard segments into string pool + static const char* stdSegNames[] = {"__TEXT", "__DATA", "__LINKEDIT", "__DATA_DIRTY", "__DATA_CONST", "__OBJC", NULL }; + for (const char** s=stdSegNames; *s != NULL; ++s) { + if ( strcmp(name, *s) == 0 ) + return *s; + } + // copy custom segment names into string pool + return addString(name); +} + +void dyld_process_info_base::forEachImage(void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)) const +{ + for (const ImageInfo* p = _firstImage; p < _curImage; ++p) { + callback(p->loadAddress, p->uuid, p->path); + } +} + +void dyld_process_info_base::forEachSegment(uint64_t machHeaderAddress, void (^callback)(uint64_t segmentAddress, uint64_t segmentSize, const char* segmentName)) const +{ + for (const ImageInfo* p = _firstImage; p < _curImage; ++p) { + if ( p->loadAddress == machHeaderAddress ) { + uint64_t slide = 0; + for (int i=0; i < p->segmentsCount; ++i) { + const SegmentInfo* seg = &_firstSegment[p->segmentStartIndex+i]; + if ( strcmp(seg->name, "__TEXT") == 0 ) { + slide = machHeaderAddress - seg->addr; + break; + } + } + for (int i=0; i < p->segmentsCount; ++i) { + const SegmentInfo* seg = &_firstSegment[p->segmentStartIndex+i]; + callback(seg->addr + slide, seg->size, seg->name); + } + break; + } + } +} + + + + + +// Implementation that works with existing dyld data structures +dyld_process_info _dyld_process_info_create(task_t task, uint64_t timestamp, kern_return_t* kr) +{ + if ( kr != NULL ) + *kr = KERN_SUCCESS; + + task_dyld_info_data_t task_dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + if ( kern_return_t r = task_info(task, TASK_DYLD_INFO, (task_info_t)&task_dyld_info, &count) ) { + if ( kr != NULL ) + *kr = r; + return NULL; + } + + //The kernel will return MACH_VM_MIN_ADDRESS for an executable that has not had dyld loaded + if (task_dyld_info.all_image_info_addr == MACH_VM_MIN_ADDRESS) + return NULL; + + if ( task_dyld_info.all_image_info_size > sizeof(dyld_all_image_infos_64) ) + return NULL; + + // read all_image_infos struct + dyld_all_image_infos_64 allImageInfo64; + mach_vm_size_t readSize = task_dyld_info.all_image_info_size; + if ( kern_return_t r = mach_vm_read_overwrite(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, (vm_address_t)&allImageInfo64, &readSize) ) { + if ( kr != NULL ) + *kr = r; + return NULL; + } + if ( allImageInfo64.infoArrayCount == 0 ) { + // could be task was launch suspended or still launching, wait a moment to see + usleep(1000 * 50); // 50ms + if ( kern_return_t r = mach_vm_read_overwrite(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, (vm_address_t)&allImageInfo64, &readSize) ) { + if ( kr != NULL ) + *kr = r; + return NULL; + } + // if infoArrayCount is still zero, then target was most likely launched suspended + if ( allImageInfo64.infoArrayCount == 0 ) + return dyld_process_info_base::makeSuspended(task, kr); + } + + // bail out of dyld is too old + if ( allImageInfo64.version < 15 ) { + if ( kr != NULL ) + *kr = KERN_INVALID_HOST; + return NULL; + } + + // normalize by expanding 32-bit all_image_infos into 64-bit one + uint32_t imageCount = allImageInfo64.infoArrayCount; + size_t imageArraySize = imageCount * sizeof(dyld_image_info_64); + if ( task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { + const dyld_all_image_infos_32* allImageInfo32 = (dyld_all_image_infos_32*)&allImageInfo64; + dyld_all_image_infos_64 info64; + bzero(&info64, sizeof(info64)); + info64.version = allImageInfo32->version; + info64.infoArrayCount = allImageInfo32->infoArrayCount; + info64.infoArray = allImageInfo32->infoArray; + info64.processDetachedFromSharedRegion = allImageInfo32->processDetachedFromSharedRegion; + info64.libSystemInitialized = allImageInfo32->libSystemInitialized; + info64.dyldImageLoadAddress = allImageInfo32->dyldImageLoadAddress; + info64.initialImageCount = allImageInfo32->initialImageCount; + info64.uuidArrayCount = allImageInfo32->uuidArrayCount; + info64.uuidArray = allImageInfo32->uuidArray; + info64.dyldAllImageInfosAddress = allImageInfo32->dyldAllImageInfosAddress; + info64.sharedCacheSlide = allImageInfo32->sharedCacheSlide; + info64.infoArrayChangeTimestamp = allImageInfo32->infoArrayChangeTimestamp; + info64.sharedCacheBaseAddress = allImageInfo32->sharedCacheBaseAddress; + info64.dyldPath = allImageInfo32->dyldPath; + memcpy((void*)(info64.sharedCacheUUID), (void*)(allImageInfo32->sharedCacheUUID), 16); + allImageInfo64 = info64; + imageCount = allImageInfo64.infoArrayCount; + imageArraySize = imageCount * sizeof(dyld_image_info_32); + } + + // don't do any (more) work if target process's dyld timestamp has not changed since previous query + if ( (timestamp != 0) && (timestamp == allImageInfo64.infoArrayChangeTimestamp) ) { + if ( kr != NULL ) + *kr = KERN_SUCCESS; + return NULL; + } + + // For the moment we are going to truncate any image list longer than 8192 because some programs do + // terrible things that corrupt their own image lists and we need to stop clients from crashing + // reading them. We can try to do something more advanced in the future. rdar://27446361 + imageCount = MIN(imageCount, 8192); + + // read image array + dyld_image_info_64 imageArray64[imageCount]; + if ( kern_return_t r = mach_vm_read_overwrite(task, allImageInfo64.infoArray, imageArraySize, (vm_address_t)&imageArray64, &readSize) ) { + if ( kr != NULL ) + *kr = r; + return NULL; + } + // normalize by expanding 32-bit image_infos into 64-bit ones + if ( task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { + const dyld_image_info_32* imageArray32 = (dyld_image_info_32*)&imageArray64; + dyld_image_info_64 tempArray[imageCount]; + for (uint32_t i=0; i < imageCount; ++i) { + tempArray[i].imageLoadAddress = imageArray32[i].imageLoadAddress; + tempArray[i].imageFilePath = imageArray32[i].imageFilePath; + tempArray[i].imageFileModDate = imageArray32[i].imageFileModDate; + } + memcpy(imageArray64, tempArray, sizeof(dyld_image_info_64)*imageCount); + } + + // create object based on local copy of all image infos and image array + return dyld_process_info_base::make(task, allImageInfo64, imageArray64, kr); +} + + +void _dyld_process_info_get_state(dyld_process_info info, dyld_process_state_info* stateInfo) +{ + *stateInfo = *info->stateInfo(); +} + +void _dyld_process_info_get_cache(dyld_process_info info, dyld_process_cache_info* cacheInfo) +{ + *cacheInfo = *info->cacheInfo(); +} + +void _dyld_process_info_retain(dyld_process_info info) +{ + info->retainCount() += 1; +} + +void _dyld_process_info_release(dyld_process_info info) +{ + info->retainCount() -= 1; + if ( info->retainCount() == 0 ) + free((void*)info); +} + +void _dyld_process_info_for_each_image(dyld_process_info info, void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)) +{ + info->forEachImage(callback); +} + + +void _dyld_process_info_for_each_segment(dyld_process_info info, uint64_t machHeaderAddress, void (^callback)(uint64_t segmentAddress, uint64_t segmentSize, const char* segmentName)) +{ + info->forEachSegment(machHeaderAddress, callback); +} + + + diff --git a/src/dyld_process_info_internal.h b/src/dyld_process_info_internal.h new file mode 100644 index 0000000..23f614b --- /dev/null +++ b/src/dyld_process_info_internal.h @@ -0,0 +1,135 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2016 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _DYLD_PROCESS_INFO_INTERNAL_H_ +#define _DYLD_PROCESS_INFO_INTERNAL_H_ + + +#include +#include +#include +#include + + +struct dyld_all_image_infos_32 { + uint32_t version; + uint32_t infoArrayCount; + uint32_t infoArray; + uint32_t notification; + bool processDetachedFromSharedRegion; + bool libSystemInitialized; + uint32_t dyldImageLoadAddress; + uint32_t jitInfo; + uint32_t dyldVersion; + uint32_t errorMessage; + uint32_t terminationFlags; + uint32_t coreSymbolicationShmPage; + uint32_t systemOrderFlag; + uint32_t uuidArrayCount; + uint32_t uuidArray; + uint32_t dyldAllImageInfosAddress; + uint32_t initialImageCount; + uint32_t errorKind; + uint32_t errorClientOfDylibPath; + uint32_t errorTargetDylibPath; + uint32_t errorSymbol; + uint32_t sharedCacheSlide; + uint8_t sharedCacheUUID[16]; + uint32_t sharedCacheBaseAddress; + uint64_t infoArrayChangeTimestamp; + uint32_t dyldPath; + uint32_t notifyMachPorts[2]; + uint32_t reserved[11]; +}; + +struct dyld_all_image_infos_64 { + uint32_t version; + uint32_t infoArrayCount; + uint64_t infoArray; + uint64_t notification; + bool processDetachedFromSharedRegion; + bool libSystemInitialized; + uint32_t paddingToMakeTheSizeCorrectOn32bitAndDoesntAffect64b; // NOT PART OF DYLD_ALL_IMAGE_INFOS! + uint64_t dyldImageLoadAddress; + uint64_t jitInfo; + uint64_t dyldVersion; + uint64_t errorMessage; + uint64_t terminationFlags; + uint64_t coreSymbolicationShmPage; + uint64_t systemOrderFlag; + uint64_t uuidArrayCount; + uint64_t uuidArray; + uint64_t dyldAllImageInfosAddress; + uint64_t initialImageCount; + uint64_t errorKind; + uint64_t errorClientOfDylibPath; + uint64_t errorTargetDylibPath; + uint64_t errorSymbol; + uint64_t sharedCacheSlide; + uint8_t sharedCacheUUID[16]; + uint64_t sharedCacheBaseAddress; + uint64_t infoArrayChangeTimestamp; + uint64_t dyldPath; + uint32_t notifyMachPorts[2]; + uint64_t reserved[12]; +}; + +struct dyld_image_info_32 { + uint32_t imageLoadAddress; + uint32_t imageFilePath; + uint32_t imageFileModDate; +}; +struct dyld_image_info_64 { + uint64_t imageLoadAddress; + uint64_t imageFilePath; + uint64_t imageFileModDate; +}; + +#define DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE (32*1024) +#define DYLD_PROCESS_INFO_NOTIFY_LOAD_ID 0x1000 +#define DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID 0x2000 + + +struct dyld_process_info_image_entry { + uuid_t uuid; + uint64_t loadAddress; + uint32_t pathStringOffset; + uint32_t pathLength; +}; + +struct dyld_process_info_notify_header { + mach_msg_header_t header; + uint32_t version; + uint32_t imageCount; + uint32_t imagesOffset; + uint32_t stringsOffset; + uint64_t timestamp; +}; + + + + +#endif // _DYLD_PROCESS_INFO_INTERNAL_H_ + + diff --git a/src/dyld_process_info_notify.cpp b/src/dyld_process_info_notify.cpp new file mode 100644 index 0000000..3cfa788 --- /dev/null +++ b/src/dyld_process_info_notify.cpp @@ -0,0 +1,310 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2016 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dyld_process_info.h" +#include "dyld_process_info_internal.h" +#include "dyld_images.h" +#include "dyld_priv.h" + +typedef void (^Notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path); +typedef void (^NotifyExit)(); + + +// +// Object used for monitoring another processes dyld loads +// +struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base +{ + static dyld_process_info_notify_base* make(task_t task, dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, kern_return_t* kr); + ~dyld_process_info_notify_base(); + + uint32_t& retainCount() const { return _retainCount; } + +private: + dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task); + kern_return_t makePorts(); + kern_return_t pokeSendPortIntoTarget(); + kern_return_t unpokeSendPortInTarget(); + void setMachSourceOnQueue(); + void* operator new (size_t, void* buf) { return buf; } + + mutable uint32_t _retainCount; + dispatch_queue_t _queue; + Notify _notify; + NotifyExit _notifyExit; + task_t _targetTask; + dispatch_source_t _machSource; + uint64_t _portAddressInTarget; + mach_port_t _sendPortInTarget; // target is process being watched for image loading/unloading + mach_port_t _receivePortInMonitor; // monitor is process being notified of image loading/unloading +}; + + +dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task) + : _retainCount(1), _queue(queue), _notify(notify), _notifyExit(notifyExit), _targetTask(task), _machSource(NULL), _portAddressInTarget(0), _sendPortInTarget(0), _receivePortInMonitor(0) +{ + dispatch_retain(_queue); +} + +dyld_process_info_notify_base::~dyld_process_info_notify_base() +{ + if ( _machSource ) { + dispatch_release(_machSource); + _machSource = NULL; + } + if ( _portAddressInTarget ) { + unpokeSendPortInTarget(); + _portAddressInTarget = 0; + } + if ( _sendPortInTarget ) { + _sendPortInTarget = 0; + } + dispatch_release(_queue); + if ( _receivePortInMonitor != 0 ) { + mach_port_deallocate(mach_task_self(), _receivePortInMonitor); + _receivePortInMonitor = 0; + } +} + + +dyld_process_info_notify_base* dyld_process_info_notify_base::make(task_t task, dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, kern_return_t* kr) +{ + void* storage = malloc(sizeof(dyld_process_info_notify_base)); + dyld_process_info_notify_base* obj = new (storage) dyld_process_info_notify_base(queue, notify, notifyExit, task); + + if ( kern_return_t r = obj->makePorts() ) { + if ( kr != NULL ) + *kr = r; + goto fail; + } + + obj->setMachSourceOnQueue(); + + if ( kern_return_t r = obj->pokeSendPortIntoTarget() ) { + if ( kr != NULL ) + *kr = r; + goto fail; + } + + if ( kr != NULL ) + *kr = KERN_SUCCESS; + return obj; + +fail: + free(obj); + return NULL; +} + + +kern_return_t dyld_process_info_notify_base::makePorts() +{ + // Allocate a port to listen on in this monitoring task + if ( kern_return_t r = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &_receivePortInMonitor) ) + return r; + + // Add send rights for replying + if ( kern_return_t r = mach_port_insert_right(mach_task_self(), _receivePortInMonitor, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND) ) + return r; + + // Allocate a name in the target. We need a new name to add send rights to + if ( kern_return_t r = mach_port_allocate(_targetTask, MACH_PORT_RIGHT_DEAD_NAME, &_sendPortInTarget) ) + return r; + + // Deallocate the dead name + if ( kern_return_t r = mach_port_mod_refs(_targetTask, _sendPortInTarget, MACH_PORT_RIGHT_DEAD_NAME, -1) ) + return r; + + // Make the dead name a send right to our listening port + if ( kern_return_t r = mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND) ) + return r; + + // Notify us if the target dies + mach_port_t previous = MACH_PORT_NULL; + if ( kern_return_t r = mach_port_request_notification(_targetTask, _sendPortInTarget, MACH_NOTIFY_DEAD_NAME, 0, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous)) + return r; + + //fprintf(stderr, "_sendPortInTarget=%d, _receivePortInMonitor=%d\n", _sendPortInTarget, _receivePortInMonitor); + return KERN_SUCCESS; +} + + + +void dyld_process_info_notify_base::setMachSourceOnQueue() +{ + NotifyExit exitHandler = _notifyExit; + _machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _receivePortInMonitor, 0, _queue); + dispatch_source_set_event_handler(_machSource, ^{ + uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE]; + mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer; + + kern_return_t r = mach_msg(h, MACH_RCV_MSG, 0, sizeof(messageBuffer), _receivePortInMonitor, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if ( r == KERN_SUCCESS ) { + //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); + if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_LOAD_ID || h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID ) { + // run notifier block for each [un]load image + const dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)messageBuffer; + const dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&messageBuffer[header->imagesOffset]; + const char* const stringPool = (char*)&messageBuffer[header->stringsOffset]; + for (unsigned i=0; i < header->imageCount; ++i) { + bool isUnload = (h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID); + _notify(isUnload, header->timestamp, entries[i].loadAddress, entries[i].uuid, stringPool + entries[i].pathStringOffset); + } + // reply to dyld, so it can continue + mach_msg_header_t replyHeader; + replyHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND); + replyHeader.msgh_id = 0; + replyHeader.msgh_local_port = MACH_PORT_NULL; + replyHeader.msgh_remote_port = h->msgh_remote_port; + replyHeader.msgh_reserved = 0; + replyHeader.msgh_size = sizeof(replyHeader); + mach_msg(&replyHeader, MACH_SEND_MSG | MACH_SEND_TIMEOUT, replyHeader.msgh_size, 0, MACH_PORT_NULL, 100, MACH_PORT_NULL); + } + else if ( h->msgh_id == MACH_NOTIFY_PORT_DELETED ) { + mach_port_t deadPort = ((mach_port_deleted_notification_t *)h)->not_port; + //fprintf(stderr, "received message id=MACH_NOTIFY_PORT_DELETED, size=%d, deadPort=%d\n", h->msgh_size, deadPort); + if ( deadPort == _sendPortInTarget ) { + // target process died. Clean up ports + _sendPortInTarget = 0; + mach_port_deallocate(mach_task_self(), _receivePortInMonitor); + _receivePortInMonitor = 0; + _portAddressInTarget = 0; + // notify that target is gone + exitHandler(); + } + } + else { + fprintf(stderr, "received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); + } + } + }); + dispatch_resume(_machSource); +} + + +kern_return_t dyld_process_info_notify_base::pokeSendPortIntoTarget() +{ + // get location on all_image_infos in target task + task_dyld_info_data_t taskDyldInfo; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + kern_return_t r = task_info(_targetTask, TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &count); + if ( r ) + return r; + + // remap the page containing all_image_infos into this process r/w + mach_vm_address_t mappedAddress = 0; + mach_vm_size_t mappedSize = taskDyldInfo.all_image_info_size; + vm_prot_t curProt = VM_PROT_NONE; + vm_prot_t maxProt = VM_PROT_NONE; + r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, + _targetTask, taskDyldInfo.all_image_info_addr, false, &curProt, &maxProt, VM_INHERIT_NONE); + if ( r ) + return r; + if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) ) + return KERN_PROTECTION_FAILURE; + + // atomically set port into all_image_info_struct + static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits"); + + mach_vm_address_t mappedAddressToPokePort = 0; + if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) + mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_32,notifyMachPorts); + else + mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_64,notifyMachPorts); + + // use first available slot + bool slotFound = false; + for (int slotIndex=0; slotIndex < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slotIndex) { + if ( OSAtomicCompareAndSwap32Barrier(0, _sendPortInTarget, (volatile int32_t*)mappedAddressToPokePort) ) { + slotFound = true; + break; + } + mappedAddressToPokePort += sizeof(uint32_t); + } + if ( !slotFound ) { + mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); + return KERN_UREFS_OVERFLOW; + } + _portAddressInTarget = taskDyldInfo.all_image_info_addr + mappedAddressToPokePort - mappedAddress; + //fprintf(stderr, "poked port %d into target at address 0x%llX\n", _sendPortInTarget, _portAddressInTarget); + mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); + return r; +} + + + +kern_return_t dyld_process_info_notify_base::unpokeSendPortInTarget() +{ + // remap the page containing all_image_infos into this process r/w + mach_vm_address_t mappedAddress = 0; + mach_vm_size_t mappedSize = sizeof(mach_port_t); + vm_prot_t curProt = VM_PROT_NONE; + vm_prot_t maxProt = VM_PROT_NONE; + kern_return_t r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, + _targetTask, _portAddressInTarget, false, &curProt, &maxProt, VM_INHERIT_NONE); + if ( r ) + return r; + if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) ) + return KERN_PROTECTION_FAILURE; + + OSAtomicCompareAndSwap32Barrier(_sendPortInTarget, 0, (volatile int32_t*)mappedAddress); + + //fprintf(stderr, "cleared port %d from target\n", _sendPortInTarget); + mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); + return r; +} + + + +dyld_process_info_notify _dyld_process_info_notify(task_t task, dispatch_queue_t queue, + void (^notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path), + void (^notifyExit)(), + kern_return_t* kr) +{ + return dyld_process_info_notify_base::make(task, queue, notify, notifyExit, kr); +} + +void _dyld_process_info_notify_retain(dyld_process_info_notify object) +{ + object->retainCount() += 1; +} + +void _dyld_process_info_notify_release(dyld_process_info_notify object) +{ + object->retainCount() -= 1; + if ( object->retainCount() == 0 ) { + object->~dyld_process_info_notify_base(); + free((void*)object); + } +} + + + diff --git a/src/glue.c b/src/glue.c index bc7741f..6eeed0a 100644 --- a/src/glue.c +++ b/src/glue.c @@ -75,6 +75,8 @@ extern void _ZN4dyld3logEPKcz(const char*, ...); // dyld::halt(const char* msg); extern void _ZN4dyld4haltEPKc(const char* msg) __attribute__((noreturn)); +extern void dyld_fatal_error(const char* errString) __attribute__((noreturn)); + // abort called by C++ unwinding code void abort() @@ -492,59 +494,43 @@ int closedir(DIR* dirp) { return gSyscallHelpers->closedir(dirp); } -#define SUPPORT_HOST_10_10 1 - -#if SUPPORT_HOST_10_10 -typedef int (*FuncPtr_nanosleep)(const struct timespec*, struct timespec*); -typedef kern_return_t (*FuncPtr_mach_port_allocate)(ipc_space_t, mach_port_right_t, mach_port_name_t*); -typedef mach_msg_return_t (*FuncPtr_mach_msg)(mach_msg_header_t *, mach_msg_option_t , mach_msg_size_t , mach_msg_size_t , mach_port_name_t , mach_msg_timeout_t , mach_port_name_t); -typedef int (*FuncPtr_kill)(pid_t pid, int sig); -typedef pid_t (*FuncPtr_getpid)(); -typedef bool (*FuncPtr_OSAtomicCompareAndSwap32)(int32_t, int32_t, volatile int32_t*); - -static FuncPtr_nanosleep proc_nanosleep = NULL; -static FuncPtr_mach_port_allocate proc_mach_port_allocate = NULL; -static FuncPtr_mach_msg proc_mach_msg = NULL; -static FuncPtr_kill proc_kill = NULL; -static FuncPtr_getpid proc_getpid = NULL; -static FuncPtr_OSAtomicCompareAndSwap32 proc_OSAtomicCompareAndSwap32 = NULL; - - -int nanosleep(const struct timespec* p1, struct timespec* p2) +void xcoresymbolication_load_notifier(void* connection, uint64_t timestamp, const char* path, const struct mach_header* mh) { - return (*proc_nanosleep)(p1, p2); + // if host dyld supports this notifier, call into host dyld + if ( gSyscallHelpers->version >= 4 ) + return gSyscallHelpers->coresymbolication_load_notifier(connection, timestamp, path, mh); } -kern_return_t mach_port_allocate(ipc_space_t p1, mach_port_right_t p2, mach_port_name_t* p3) +void xcoresymbolication_unload_notifier(void* connection, uint64_t timestamp, const char* path, const struct mach_header* mh) { - return (*proc_mach_port_allocate)(p1, p2, p3); + // if host dyld supports this notifier, call into host dyld + if ( gSyscallHelpers->version >= 4 ) + return gSyscallHelpers->coresymbolication_unload_notifier(connection, timestamp, path, mh); } -mach_msg_return_t mach_msg(mach_msg_header_t* p1, mach_msg_option_t p2, mach_msg_size_t p3, mach_msg_size_t p4, mach_port_name_t p5, mach_msg_timeout_t p6, mach_port_name_t p7) -{ - return (*proc_mach_msg)(p1, p2, p3, p4, p5, p6, p7); -} -int kill(pid_t p1, int p2) -{ - return (*proc_kill)(p1, p2); -} -pid_t getpid() -{ - return (*proc_getpid)(); -} +#define SUPPORT_HOST_10_11 1 + +#if SUPPORT_HOST_10_11 +typedef int (*FuncPtr_proc_regionfilename)(int pid, uint64_t address, void* buffer, uint32_t bufferSize); +typedef pid_t (*FuncPtr_getpid)(); +typedef bool (*FuncPtr_mach_port_insert_right)(ipc_space_t task, mach_port_name_t name, mach_port_t poly, mach_msg_type_name_t polyPoly); +typedef kern_return_t (*FuncPtr_mach_port_allocate)(ipc_space_t, mach_port_right_t, mach_port_name_t*); +typedef mach_msg_return_t (*FuncPtr_mach_msg)(mach_msg_header_t *, mach_msg_option_t , mach_msg_size_t , mach_msg_size_t , mach_port_name_t , mach_msg_timeout_t , mach_port_name_t); + +static FuncPtr_proc_regionfilename proc_proc_regionfilename = NULL; +static FuncPtr_getpid proc_getpid = NULL; +static FuncPtr_mach_port_insert_right proc_mach_port_insert_right = NULL; +static FuncPtr_mach_port_allocate proc_mach_port_allocate = NULL; +static FuncPtr_mach_msg proc_mach_msg = NULL; -bool OSAtomicCompareAndSwap32(int32_t p1, int32_t p2, volatile int32_t* p3) -{ - return (*proc_OSAtomicCompareAndSwap32)(p1, p2, p3); -} // Look up sycalls in host dyld needed by coresymbolication_ routines in dyld_sim static void findHostFunctions() { // Only look up symbols once - if ( proc_nanosleep != NULL ) + if ( proc_mach_msg != NULL ) return; struct dyld_all_image_infos* imageInfo = (struct dyld_all_image_infos*)(gSyscallHelpers->getProcessInfo()); @@ -596,47 +582,131 @@ static void findHostFunctions() { for (const macho_nlist* s = localsStart; s < localsEnd; ++s) { if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) { const char* name = &symbolTableStrings[s->n_un.n_strx]; - if ( strcmp(name, "_nanosleep") == 0 ) - proc_nanosleep = (FuncPtr_nanosleep)(s->n_value + slide); + if ( strcmp(name, "_proc_regionfilename") == 0 ) + proc_proc_regionfilename = (FuncPtr_proc_regionfilename)(s->n_value + slide); + else if ( strcmp(name, "_getpid") == 0 ) + proc_getpid = (FuncPtr_getpid)(s->n_value + slide); + else if ( strcmp(name, "mach_port_insert_right") == 0 ) + proc_mach_port_insert_right = (FuncPtr_mach_port_insert_right)(s->n_value + slide); else if ( strcmp(name, "_mach_port_allocate") == 0 ) proc_mach_port_allocate = (FuncPtr_mach_port_allocate)(s->n_value + slide); else if ( strcmp(name, "_mach_msg") == 0 ) proc_mach_msg = (FuncPtr_mach_msg)(s->n_value + slide); - else if ( strcmp(name, "_kill") == 0 ) - proc_kill = (FuncPtr_kill)(s->n_value + slide); - else if ( strcmp(name, "_getpid") == 0 ) - proc_getpid = (FuncPtr_getpid)(s->n_value + slide); - else if ( strcmp(name, "_OSAtomicCompareAndSwap32") == 0 ) - proc_OSAtomicCompareAndSwap32 = (FuncPtr_OSAtomicCompareAndSwap32)(s->n_value + slide); } } } #endif -void xcoresymbolication_load_notifier(void* connection, uint64_t timestamp, const char* path, const struct mach_header* mh) + +int proc_regionfilename(int pid, uint64_t address, void* buffer, uint32_t bufferSize) { - // if host dyld supports this notifier, call into host dyld - if ( gSyscallHelpers->version >= 4 ) - return gSyscallHelpers->coresymbolication_load_notifier(connection, timestamp, path, mh); -#if SUPPORT_HOST_10_10 - // otherwise use notifier code in dyld_sim + if ( gSyscallHelpers->version >= 5 ) + return gSyscallHelpers->proc_regionfilename(pid, address, buffer, bufferSize); +#if SUPPORT_HOST_10_11 + findHostFunctions(); + if ( proc_proc_regionfilename ) + return (*proc_proc_regionfilename)(pid, address, buffer, bufferSize); + else + return 0; +#else + return 0; +#endif +} + +pid_t getpid() +{ + if ( gSyscallHelpers->version >= 5 ) + return gSyscallHelpers->getpid(); +#if SUPPORT_HOST_10_11 findHostFunctions(); - coresymbolication_load_notifier(connection, timestamp, path, mh); + return (*proc_getpid)(); +#else + return 0; #endif } -void xcoresymbolication_unload_notifier(void* connection, uint64_t timestamp, const char* path, const struct mach_header* mh) +kern_return_t mach_port_insert_right(ipc_space_t task, mach_port_name_t name, mach_port_t poly, mach_msg_type_name_t polyPoly) { - // if host dyld supports this notifier, call into host dyld - if ( gSyscallHelpers->version >= 4 ) - return gSyscallHelpers->coresymbolication_unload_notifier(connection, timestamp, path, mh); -#if SUPPORT_HOST_10_10 - // otherwise use notifier code in dyld_sim + if ( gSyscallHelpers->version >= 5 ) + return gSyscallHelpers->mach_port_insert_right(task, name, poly, polyPoly); +#if SUPPORT_HOST_10_11 findHostFunctions(); - coresymbolication_unload_notifier(connection, timestamp, path, mh); + if ( proc_mach_port_insert_right ) + return (*proc_mach_port_insert_right)(task, name, poly, polyPoly); + else + return KERN_NOT_SUPPORTED; +#else + return KERN_NOT_SUPPORTED; #endif } +kern_return_t mach_port_allocate(ipc_space_t task, mach_port_right_t right, mach_port_name_t* name) +{ + if ( gSyscallHelpers->version >= 5 ) + return gSyscallHelpers->mach_port_allocate(task, right, name); +#if SUPPORT_HOST_10_11 + findHostFunctions(); + return (*proc_mach_port_allocate)(task, right, name); +#else + return KERN_NOT_SUPPORTED; +#endif +} + +kern_return_t mach_msg(mach_msg_header_t* msg, mach_msg_option_t option, mach_msg_size_t send_size, mach_msg_size_t rcv_size, mach_port_name_t rcv_name, mach_msg_timeout_t timeout, mach_port_name_t notify) +{ + if ( gSyscallHelpers->version >= 5 ) + return gSyscallHelpers->mach_msg(msg, option, send_size, rcv_size, rcv_name, timeout, notify); +#if SUPPORT_HOST_10_11 + findHostFunctions(); + return (*proc_mach_msg)(msg, option, send_size, rcv_size, rcv_name, timeout, notify); +#else + return KERN_NOT_SUPPORTED; +#endif +} + + +void abort_with_payload(uint32_t reason_namespace, uint64_t reason_code, void* payload, uint32_t payload_size, const char* reason_string, uint64_t reason_flags) +{ + if ( gSyscallHelpers->version >= 6 ) + gSyscallHelpers->abort_with_payload(reason_namespace, reason_code, payload, payload_size, reason_string, reason_flags); + dyld_fatal_error(reason_string); +} + +kern_return_t task_register_dyld_image_infos(task_t task, dyld_kernel_image_info_array_t dyld_images, mach_msg_type_number_t dyld_imagesCnt) { + if ( gSyscallHelpers->version >= 7 ) + return gSyscallHelpers->task_register_dyld_image_infos(task, dyld_images, dyld_imagesCnt); + return KERN_NOT_SUPPORTED; +} + +kern_return_t task_unregister_dyld_image_infos(task_t task, dyld_kernel_image_info_array_t dyld_images, mach_msg_type_number_t dyld_imagesCnt) { + if ( gSyscallHelpers->version >= 7 ) + return gSyscallHelpers->task_unregister_dyld_image_infos(task, dyld_images, dyld_imagesCnt); + return KERN_NOT_SUPPORTED; +} + +kern_return_t task_get_dyld_image_infos(task_t task, dyld_kernel_image_info_array_t *dyld_images, mach_msg_type_number_t *dyld_imagesCnt) { + if ( gSyscallHelpers->version >= 7 ) + return gSyscallHelpers->task_get_dyld_image_infos(task, dyld_images, dyld_imagesCnt); + return KERN_NOT_SUPPORTED; +} + +kern_return_t task_register_dyld_shared_cache_image_info(task_t task, dyld_kernel_image_info_t dyld_cache_image, boolean_t no_cache, boolean_t private_cache) { + if ( gSyscallHelpers->version >= 7 ) + return gSyscallHelpers->task_register_dyld_shared_cache_image_info(task, dyld_cache_image, no_cache, private_cache); + return KERN_NOT_SUPPORTED; +} + +kern_return_t task_register_dyld_set_dyld_state(task_t task, uint8_t dyld_state) { + if ( gSyscallHelpers->version >= 7 ) + return gSyscallHelpers->task_register_dyld_set_dyld_state(task, dyld_state); + return KERN_NOT_SUPPORTED; +} + +kern_return_t task_register_dyld_get_process_state(task_t task, dyld_kernel_process_info_t *dyld_process_state) { + if ( gSyscallHelpers->version >= 7 ) + return gSyscallHelpers->task_register_dyld_get_process_state(task, dyld_process_state); + return KERN_NOT_SUPPORTED; +} int* __error(void) { return gSyscallHelpers->errnoAddress(); @@ -655,3 +725,15 @@ int myerrno_fallback = 0; #endif // TARGET_IPHONE_SIMULATOR +#if ! TARGET_IPHONE_SIMULATOR + #include "mach-o/dyld_process_info.h" + + void _dyld_debugger_notification(enum dyld_notify_mode mode, unsigned long count, uint64_t machHeaders[]) + { + // Do nothing. This exists for the debugger to set a break point on to see what images have been loaded or unloaded. + } +#endif + + + + diff --git a/src/threadLocalHelpers.s b/src/threadLocalHelpers.s index 683c5a8..fb63817 100644 --- a/src/threadLocalHelpers.s +++ b/src/threadLocalHelpers.s @@ -282,6 +282,7 @@ LlazyAllocate: #if __arm__ // returns address of TLV in r0, all other registers preserved + .align 2 .globl _tlv_get_addr .private_extern _tlv_get_addr _tlv_get_addr: diff --git a/src/threadLocalVariables.c b/src/threadLocalVariables.c index e0e2884..8132225 100644 --- a/src/threadLocalVariables.c +++ b/src/threadLocalVariables.c @@ -238,8 +238,8 @@ void* tlv_allocate_and_initialize_for_key(pthread_key_t key) typedef void (*InitFunc)(void); InitFunc* funcs = (InitFunc*)(sect->addr + slide); const size_t count = sect->size / sizeof(uintptr_t); - for (size_t i=count; i > 0; --i) { - InitFunc func = funcs[i-1]; + for (size_t j=count; j > 0; --j) { + InitFunc func = funcs[j-1]; func(); } } @@ -305,17 +305,13 @@ static void tlv_initialize_descriptors(const struct mach_header* mh) } } -// called by dyld when a image is loaded -static const char* tlv_load_notification(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) + +void tlv_load_notification(const struct mach_header* mh, intptr_t slide) { - // this is called on all images, even those without TLVs, so we want - // this to be fast. The linker sets MH_HAS_TLV_DESCRIPTORS so we don't - // have to search images just to find the don't have TLVs. - for (uint32_t i=0; i < infoCount; ++i) { - if ( info[i].imageLoadAddress->flags & MH_HAS_TLV_DESCRIPTORS ) - tlv_initialize_descriptors(info[i].imageLoadAddress); - } - return NULL; + // This is called on all images, even those without TLVs. So we want this to be fast. + // The linker sets MH_HAS_TLV_DESCRIPTORS so we don't have to search images just to find the don't have TLVs. + if ( mh->flags & MH_HAS_TLV_DESCRIPTORS ) + tlv_initialize_descriptors(mh); } @@ -453,7 +449,8 @@ void tlv_initializer() (void)pthread_key_create(&tlv_terminators_key, &tlv_finalize); // register with dyld for notification when images are loaded - dyld_register_image_state_change_handler(dyld_image_state_bound, true, tlv_load_notification); + _dyld_register_func_for_add_image(tlv_load_notification); + } diff --git a/testing/README.txt b/testing/README.txt new file mode 100755 index 0000000..f94a6f6 --- /dev/null +++ b/testing/README.txt @@ -0,0 +1,47 @@ + +When the dyld_tests target is built, all test cases are built into /AppleInternal/. +A test case is a directory in $SRCROOT/testing/test-cases/ whose name ends in ".dtest". +The build system scraps any .c or .cxx files in the .dtest directory looking for BUILD: or RUN: lines. +The BUILD: lines are use to build the test case binaries. +The RUN: lines are used to build the information needed for BATS to run the test cases. +Example, main.c may contain: + + // BUILD: $CC main.c -o $BUILD_DIR/example.exe + // RUN: ./example.exe + int main() { return 0; } + +When build lines are executed, the current directory is set to the test case's .dtest dir. +Build lines may contain the follow variables: + $BUILD_DIR - expands to the directory in $DSTROOT where this test case binaries are installed + $RUN_DIR - expands to the directory in /AppleInternal/ where this test case binaries will be run + $TEMP_DIR - expands to a temporary directory that will be delete after this test case is built + $CC - expands to the C compiler command line for the current platform. It includes + the min-os-version option, then SDK option, and the architectures. + $CXX - expands to the C++ compiler plus standard options + +When run lines are executed, the current directory is set to what $RUN_DIR was during build. +Run lines may contain the follow variables: + $RUN_DIR - expands to the directory in /AppleInternal/ where this test case binaries are installed + $REQUIRE_CRASH - expands to the 'nocr' tool which is used when a program is expected to "crash" in order to pass + + +When a test program runs, it should initially print out the name of the test case. Ex: + [BEGIN] dlfoo-thread-safe +Then it should print a pass or fail message with the same test name. Ex: + [PASS] dlfoo-thread-safe + + +To support tests that dyld is supposed to terminate, use $REQUIRE_CRASH on the RUN: line +along with setting env var NOCR_TEST_NAME to the name of the test case. When that env +var is set, the nocr tool wil print out the [BEGIN], then [PASS] if the test crashes +otherwise [FAIL]. Ex: + // RUN: NOCR_TEST_NAME="dylib-static-link missing" $REQUIRE_CRASH ./dylib-static-missing.exe + + +To support tests that are platform specific, add the BUILD_ONLY: line which specifies the platform. +Valid platforms are: MacOSX, iOS, watchOS, and tvOS. When a specific platform is specified, a +new min OS version can also be specified via the BUILD_MIN_OS option. For instance: + // BUILD_ONLY: MacOSX + // BUILD_MIN_OS: 10.5 + + diff --git a/testing/build_tests.py b/testing/build_tests.py new file mode 100755 index 0000000..fd7874a --- /dev/null +++ b/testing/build_tests.py @@ -0,0 +1,199 @@ +#!/usr/bin/python2.7 + +import plistlib +import string +import argparse +import sys +import os +import tempfile +import shutil +import subprocess + + +# +# Scan files in .dtest directory looking for BUILD: or RUN: directives. +# Return a dictionary of all directives. +# +def parseDirectives(testCaseSourceDir): + onlyLines = [] + buildLines = [] + runLines = [] + minOS = "" + timeout = "" + for file in os.listdir(testCaseSourceDir): + if file.endswith((".c", ".cpp", ".cxx")): + with open(testCaseSourceDir + "/" + file) as f: + for line in f.read().splitlines(): + buildIndex = string.find(line, "BUILD:") + if buildIndex != -1: + buildLines.append(line[buildIndex + 6:].lstrip()) + runIndex = string.find(line, "RUN:") + if runIndex != -1: + runLines.append(line[runIndex+4:].lstrip()) + onlyIndex = string.find(line, "BUILD_ONLY:") + if onlyIndex != -1: + onlyLines.append(line[onlyIndex+11:].lstrip()) + minOsIndex = string.find(line, "BUILD_MIN_OS:") + if minOsIndex != -1: + minOS = line[minOsIndex+13:].lstrip() + timeoutIndex = string.find(line, "RUN_TIMEOUT:") + if timeoutIndex != -1: + timeout = line[timeoutIndex+12:].lstrip() + + return { + "BUILD": buildLines, + "BUILD_ONLY": onlyLines, + "BUILD_MIN_OS": minOS, + "RUN": runLines, + "RUN_TIMEOUT": timeout + } + + +# +# Look at directives dictionary to see if this test should be skipped for this platform +# +def useTestCase(testCaseDirectives, platformName): + onlyLines = testCaseDirectives["BUILD_ONLY"] + for only in onlyLines: + if only == "MacOSX" and platformName != "macosx": + return False + if only == "iOS" and platformName != "iphoneos": + return False + return True + + +# +# Use BUILD directives to construct the test case +# Use RUN directives to generate a shell script to run test(s) +# +def buildTestCase(testCaseDirectives, testCaseSourceDir, toolsDir, sdkDir, minOsOptionsName, defaultMinOS, archOptions, testCaseDestDirBuild, testCaseDestDirRun): + scratchDir = tempfile.mkdtemp() + if testCaseDirectives["BUILD_MIN_OS"]: + minOS = testCaseDirectives["BUILD_MIN_OS"] + else: + minOS = defaultMinOS + compilerSearchOptions = " -isysroot " + sdkDir + " -I" + sdkDir + "/System/Library/Frameworks/System.framework/PrivateHeaders" + if minOsOptionsName == "mmacosx-version-min": + taskForPidCommand = "touch " + else: + taskForPidCommand = "codesign --force --sign - --entitlements " + testCaseSourceDir + "/../../task_for_pid_entitlement.plist " + buildSubs = { + "CC": toolsDir + "/usr/bin/clang " + archOptions + " -" + minOsOptionsName + "=" + str(minOS) + compilerSearchOptions, + "CXX": toolsDir + "/usr/bin/clang++ " + archOptions + " -" + minOsOptionsName + "=" + str(minOS) + compilerSearchOptions, + "BUILD_DIR": testCaseDestDirBuild, + "RUN_DIR": testCaseDestDirRun, + "TEMP_DIR": scratchDir, + "TASK_FOR_PID_ENABLE": taskForPidCommand + } + os.makedirs(testCaseDestDirBuild) + os.chdir(testCaseSourceDir) + print >> sys.stderr, "cd " + testCaseSourceDir + for line in testCaseDirectives["BUILD"]: + cmd = string.Template(line).safe_substitute(buildSubs) + print >> sys.stderr, cmd + cmdList = [] + cmdList = string.split(cmd) + result = subprocess.call(cmdList) + if result: + return result + shutil.rmtree(scratchDir, ignore_errors=True) + sudoSub = "" + if minOsOptionsName == "mmacosx-version-min": + sudoSub = "sudo" + runSubs = { + "RUN_DIR": testCaseDestDirRun, + "REQUIRE_CRASH": "nocr -require_crash", + "SUDO": sudoSub, + } + runFilePath = testCaseDestDirBuild + "/run.sh" + with open(runFilePath, "a") as runFile: + runFile.write("#!/bin/sh\n") + runFile.write("cd " + testCaseDestDirRun + "\n") + os.chmod(runFilePath, 0755) + for runline in testCaseDirectives["RUN"]: + runFile.write(string.Template(runline).safe_substitute(runSubs) + "\n") + runFile.write("\n") + runFile.close() + return 0 + + + +# +# Use XCode build settings to build all unit tests for specified platform +# Generate a .plist for BATS to use to run all tests +# +if __name__ == "__main__": + dstDir = os.getenv("DSTROOT", "/tmp/dyld_tests/") + testsRunDstTopDir = "/AppleInternal/CoreOS/tests/dyld/" + testsBuildDstTopDir = dstDir + testsRunDstTopDir + shutil.rmtree(testsBuildDstTopDir, ignore_errors=True) + testsSrcTopDir = os.getenv("SRCROOT", "./") + "/testing/test-cases/" + sdkDir = os.getenv("SDKROOT", "/") + toolsDir = os.getenv("TOOLCHAIN_DIR", "/") + defaultMinOS = "" + minOSOption = os.getenv("DEPLOYMENT_TARGET_CLANG_FLAG_NAME", "") + if minOSOption: + minOSVersName = os.getenv("DEPLOYMENT_TARGET_CLANG_ENV_NAME", "") + if minOSVersName: + minVersNum = os.getenv(minOSVersName, "") + platformName = os.getenv("PLATFORM_NAME", "osx") + archOptions = "" + archList = os.getenv("RC_ARCHS", "") + if archList: + for arch in string.split(archList, " "): + archOptions = archOptions + " -arch " + arch + else: + archList = os.getenv("ARCHS_STANDARD_32_64_BIT", "") + if platformName == "watchos": + archOptions = "-arch armv7k" + elif platformName == "appletvos": + archOptions = "-arch arm64" + else: + archOptions = "" + for arch in string.split(archList, " "): + archOptions = archOptions + " -arch " + arch + allTests = [] + for f in os.listdir(testsSrcTopDir): + if f.endswith(".dtest"): + testName = f[0:-6] + outDirBuild = testsBuildDstTopDir + testName + outDirRun = testsRunDstTopDir + testName + testCaseDir = testsSrcTopDir + f + testCaseDirectives = parseDirectives(testCaseDir) + if useTestCase(testCaseDirectives, platformName): + result = buildTestCase(testCaseDirectives, testCaseDir, toolsDir, sdkDir, minOSOption, minVersNum, archOptions, outDirBuild, outDirRun) + if result: + sys.exit(result) + mytest = {} + mytest["TestName"] = testName + mytest["Arch"] = "platform-native" + mytest["WorkingDirectory"] = testsRunDstTopDir + testName + mytest["Command"] = [] + mytest["Command"].append("./run.sh") + for runline in testCaseDirectives["RUN"]: + if "$SUDO" in runline: + mytest["AsRoot"] = 1 + if testCaseDirectives["RUN_TIMEOUT"]: + mytest["Timeout"] = testCaseDirectives["RUN_TIMEOUT"] + allTests.append(mytest) + batsInfo = { "BATSConfigVersion": "0.1.0", + "Project": "dyld_tests", + "Tests": allTests } + batsFileDir = dstDir + "/AppleInternal/CoreOS/BATS/unit_tests/" + shutil.rmtree(batsFileDir, ignore_errors=True) + os.makedirs(batsFileDir) + batsFilePath = batsFileDir + "dyld.plist" + with open(batsFilePath, "w") as batsFile: + batsFile.write(plistlib.writePlistToString(batsInfo)) + batsFile.close() + os.system('plutil -convert binary1 ' + batsFilePath) # convert the plist in place to binary + runHelper = dstDir + "/AppleInternal/CoreOS/tests/dyld/run_all_dyld_tests.sh" + print runHelper + with open(runHelper, "w") as shFile: + shFile.write("#!/bin/sh\n") + for test in allTests: + shFile.write(test["WorkingDirectory"] + "/run.sh\n") + shFile.close() + os.chmod(runHelper, 0755) + + diff --git a/testing/nocr/execserver.defs b/testing/nocr/execserver.defs new file mode 100644 index 0000000..e528df4 --- /dev/null +++ b/testing/nocr/execserver.defs @@ -0,0 +1 @@ +#include diff --git a/testing/nocr/nocr.1 b/testing/nocr/nocr.1 new file mode 100644 index 0000000..2c2bd3e --- /dev/null +++ b/testing/nocr/nocr.1 @@ -0,0 +1,26 @@ +.Dd Dec 15, 2015 +.Dt nocr 1 +.Os Darwin +.Sh NAME +.Nm nocr +.Nd "runs a command with crash reporter disabled" +.Sh SYNOPSIS +.HP 5n +\fBnocr\fR +\fIcommand\fR +.br +.Sh DESCRIPTION +\fBnocr\fR +executes +\fIcommand\fR +in a way that blocks crash reporter should the command process crash. +This is useful when running test suites +.Pp +.Sh "EXIT VALUE" +Upon successful execution of a program, the exit status from +\fInocr\fR +will simply be the exit status of the program that was executed. +.Pp +If the program +\fIcommand\fR +crashed, the exit value will be 1 diff --git a/testing/nocr/nocr.c b/testing/nocr/nocr.c new file mode 100644 index 0000000..74c1e88 --- /dev/null +++ b/testing/nocr/nocr.c @@ -0,0 +1,189 @@ +#include "execserverServer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +static pid_t sChildPid; +static dispatch_semaphore_t sServerRunning; +static bool sChildCrashed = false; +static bool sChildTerminatedByDyld = false; + +/* + * setup exception handling port for EXC_CRASH and EXC_CORPSE_NOTIFY. + * runs mach_msg_server once for receiving exception messages from kernel. + */ +static void* serverCode(void* arg) +{ + mach_port_t exception_port; + + kern_return_t kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exception_port); + if (kret != KERN_SUCCESS) + errx(1, "mach_port_allocate: %s (%d)", mach_error_string(kret), kret); + + kret = mach_port_insert_right(mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND); + if (kret != KERN_SUCCESS) + errx(1, "mach_port_insert_right: %s (%d)", mach_error_string(kret), kret); + + kret = task_set_exception_ports(mach_task_self(), EXC_MASK_CRASH | EXC_MASK_CORPSE_NOTIFY, exception_port, + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, 0); + if (kret != KERN_SUCCESS) + errx(1, "task_set_exception_ports: %s (%d)", mach_error_string(kret), kret); + + dispatch_semaphore_signal(sServerRunning); + + kret = mach_msg_server(mach_exc_server, MACH_MSG_SIZE_RELIABLE, exception_port, 0); + if (kret != KERN_SUCCESS) + errx(1, "mach_msg_server: %s (%d)", mach_error_string(kret), kret); + + return NULL; +} + + +static void childDied(int sig) +{ + struct proc_exitreasoninfo info; + bzero(&info, sizeof(info)); + uint8_t packReasonData[OS_REASON_BUFFER_MAX_SIZE]; + bzero(packReasonData, OS_REASON_BUFFER_MAX_SIZE); + info.eri_reason_buf_size = OS_REASON_BUFFER_MAX_SIZE; + info.eri_kcd_buf = (user_addr_t)packReasonData; + //fprintf(stderr, "info=%p\n", &info); + if ( proc_pidinfo(sChildPid, PROC_PIDEXITREASONINFO, 1, &info, PROC_PIDEXITREASONINFO_SIZE) != sizeof(struct proc_exitreasoninfo) ) { + printf("bad return size from proc_pidinfo()\n"); + return; + } + sChildTerminatedByDyld = (info.eri_namespace == OS_REASON_DYLD); + } + + +int main(int argc, const char* argv[]) +{ + if ( argc < 2 ) { + fprintf(stderr, "usage: nocr [-require_crash] prog args...\n"); + return EXIT_FAILURE; + } + unsigned progArgIndex = 1; + bool requireCrash = false; + const char* testName = NULL; + if ( strcmp(argv[1], "-require_crash") == 0 ) { + progArgIndex = 2; + requireCrash = true; + testName = getenv("NOCR_TEST_NAME"); + if ( testName ) + printf("[BEGIN] %s\n", testName); + } + + signal(SIGCHLD, childDied); + + sServerRunning = dispatch_semaphore_create(0); + + // start up thread for mach server which handles mach exception ports + pthread_t serverThread; + int result = pthread_create(&serverThread, NULL, serverCode, NULL); + if ( result ) + err(EXIT_FAILURE, "pthread_create"); + + // wait until server is up before starting child + dispatch_semaphore_wait(sServerRunning, DISPATCH_TIME_FOREVER); + + // fork and exec child + sChildPid = fork(); + if ( sChildPid < 0 ) + err(EXIT_FAILURE, "fork"); + if ( sChildPid == 0 ) { + // child side + result = execvp(argv[progArgIndex], (char**)&argv[progArgIndex]); + err(EXIT_FAILURE, "exec(\"%s\",...)", argv[progArgIndex]); + } + + // wait for child to finish (including crash) + int status; + int waitResult; + int childResult = EXIT_FAILURE; + do { + waitResult = waitpid(sChildPid, &status, 0); + } while ( (waitResult == -1) && (errno == EINTR) ); + if ( waitResult != -1 ) { + if ( WIFEXITED(status) ) { + childResult = WEXITSTATUS(status); + } + } + + if ( requireCrash ) { + if ( testName ) { + if ( sChildCrashed || sChildTerminatedByDyld ) + printf("[PASS] %s\n", testName); + else + printf("[FAIL] %s\n", testName); + } + return sChildCrashed ? EXIT_SUCCESS : EXIT_FAILURE; + } + else + return childResult; +} + + + + +// Mach exception handler routines needed by execserverServer.c + +kern_return_t +catch_mach_exception_raise(mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt) +{ + //fprintf(stderr, "child crashed\n"); + sChildCrashed = true; + return KERN_SUCCESS; +} + +kern_return_t +catch_mach_exception_raise_state(mach_port_t exception_port, + exception_type_t exception, + const mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int * flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t * new_stateCnt) +{ + errx(1, "Unsupported catch_mach_exception_raise_state"); + return KERN_NOT_SUPPORTED; +} + +kern_return_t +catch_mach_exception_raise_state_identity(mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int * flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t * new_stateCnt) +{ + errx(1, "Unsupported catch_mach_exception_raise_state_identity"); + return KERN_NOT_SUPPORTED; +} + + + diff --git a/testing/run_all_dyld_tests.py b/testing/run_all_dyld_tests.py new file mode 100755 index 0000000..14ebc60 --- /dev/null +++ b/testing/run_all_dyld_tests.py @@ -0,0 +1,114 @@ +#!/usr/bin/python2.7 + +import plistlib +import string +import sys +import os +import shutil +import subprocess + + + + +# +# Parse dyld's BATS input file and run each test +# +if __name__ == "__main__": + batsPlist = plistlib.readPlist("/AppleInternal/CoreOS/BATS/unit_tests/dyld.plist") + tests = batsPlist["Tests"] + passedCount = 0 + failedCount = 0 + for test in tests: + cwd = test["WorkingDirectory"] + if cwd: + os.chdir(cwd) + cmd = test["Command"] + testName = test["TestName"] + if cwd: + try: + runOutput = subprocess.check_output(cmd,stderr= subprocess.STDOUT) + lines = runOutput.splitlines() + foundBegin = False + passed = False + failed = False + currentTestName = "" + for line in lines: + line = line.lstrip().rstrip() + #print testName + ": " + line + beginIndex = string.find(line, "[BEGIN]") + if beginIndex != -1: + beginName = line[beginIndex + 7:].lstrip() + foundBegin = True + currentTestName = beginName + passIndex = string.find(line, "[PASS]") + if passIndex != -1: + passName = line[passIndex + 6:].lstrip() + passed = True + if passName != currentTestName: + print >> sys.stderr, "[PASS] name does not match [BEGIN] name for test " + testName + failIndex = string.find(line, "[FAIL]") + if failIndex != -1: + failName = line[failIndex + 6:].lstrip() + failed = True + if failName != currentTestName: + print >> sys.stderr, "[FAIL] name does not match [BEGIN] name for test " + testName + if foundBegin: + if not passed and not failed: + print >> sys.stderr, "[BEGIN] found [PASS] or [FAIL] for test " + testName + else: + print >> sys.stderr, "Missing [BEGIN] for test " + testName + if passed: + print "PASSED: " + testName + passedCount = passedCount + 1 + elif failed: + print "FAILED: " + testName + failedCount = failedCount + 1 + except subprocess.CalledProcessError as e: + print >> sys.stderr, "FAILED: " + testName + " (execution failure)" + print "Total PASS count: " + str(passedCount) + print "Total FAIL count: " + str(failedCount) + + +def alt(): + testsTopDir = "/AppleInternal/CoreOS/tests/dyld/" + for f in os.listdir(testsTopDir): + testRunner = testsTopDir + f + "/run.sh" + if os.path.isfile(testRunner): + try: + runOutput = subprocess.check_output([testRunner],stderr= subprocess.STDOUT) + lines = runOutput.splitlines() + foundBegin = False + passed = False + failed = False + currentTestName = "" + for line in lines: + line = line.lstrip().rstrip() + #print f + ": " + line + beginIndex = string.find(line, "[BEGIN]") + if beginIndex != -1: + beginName = line[beginIndex + 7:].lstrip() + foundBegin = True + currentTestName = beginName + passIndex = string.find(line, "[PASS]") + if passIndex != -1: + passName = line[passIndex + 6:].lstrip() + passed = True + if passName != currentTestName: + print >> sys.stderr, "[PASS] name does not match [BEGIN] name for test " + f + failIndex = string.find(line, "[FAIL]") + if failIndex != -1: + failName = line[failIndex + 6:].lstrip() + failed = True + if failName != currentTestName: + print >> sys.stderr, "[FAIL] name does not match [BEGIN] name for test " + f + if foundBegin: + if not passed and not failed: + print >> sys.stderr, "[BEGIN] found [PASS] or [FAIL] for test " + f + else: + print >> sys.stderr, "Missing [BEGIN] for test " + f + if passed: + print "PASSED: " + f + elif failed: + print "FAILED: " + f + except subprocess.CalledProcessError as e: + print >> sys.stderr, "FAILED: " + f + " (execution failure)" diff --git a/testing/task_for_pid_entitlement.plist b/testing/task_for_pid_entitlement.plist new file mode 100644 index 0000000..2398d67 --- /dev/null +++ b/testing/task_for_pid_entitlement.plist @@ -0,0 +1,10 @@ + + + + + com.apple.system-task-ports + + task_for_pid-allow + + + diff --git a/testing/test-cases/NSAddImage-basic.dtest/main.c b/testing/test-cases/NSAddImage-basic.dtest/main.c new file mode 100644 index 0000000..5c870c4 --- /dev/null +++ b/testing/test-cases/NSAddImage-basic.dtest/main.c @@ -0,0 +1,28 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC zzz.c -dynamiclib -o $BUILD_DIR/libzzz.dylib -install_name $RUN_DIR/libzzz.dylib +// BUILD: $CC main.c -o $BUILD_DIR/NSAddImage-basic.exe -Wno-deprecated-declarations + +// RUN: ./NSAddImage-basic.exe $RUN_DIR/libzzz.dylib +// RUN: ./NSAddImage-basic.exe libzzz.dylib + + +#include +#include +#include + + +int main(int arg, const char* argv[]) +{ + const char* path = argv[1]; + printf("[BEGIN] NSAddImage-basic %s\n", path); + + const struct mach_header* mh = NSAddImage(path, NSADDIMAGE_OPTION_WITH_SEARCHING); + if ( mh == NULL ) + printf("[FAIL] NSAddImage-basic %s\n", path); + else + printf("[PASS] NSAddImage-basic %s\n", path); + + return 0; +} + diff --git a/testing/test-cases/NSAddImage-basic.dtest/zzz.c b/testing/test-cases/NSAddImage-basic.dtest/zzz.c new file mode 100644 index 0000000..c02be2d --- /dev/null +++ b/testing/test-cases/NSAddImage-basic.dtest/zzz.c @@ -0,0 +1 @@ +void zzz() {} diff --git a/testing/test-cases/dlopen-basic.dtest/foo.c b/testing/test-cases/dlopen-basic.dtest/foo.c new file mode 100644 index 0000000..c8e9924 --- /dev/null +++ b/testing/test-cases/dlopen-basic.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlopen-basic.dtest/main.c b/testing/test-cases/dlopen-basic.dtest/main.c new file mode 100644 index 0000000..e506236 --- /dev/null +++ b/testing/test-cases/dlopen-basic.dtest/main.c @@ -0,0 +1,48 @@ + +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/test.dylib +// BUILD: $CC foo.c -bundle -o $BUILD_DIR/test.bundle +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-basic.exe + +// RUN: ./dlopen-basic.exe + +#include +#include + + +static void tryImage(const char* path) +{ + printf("[BEGIN] dlopen-basic %s\n", path); + void* handle = dlopen(path, RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-basic %s\n", path); + return; + } + + void* sym = dlsym(handle, "foo"); + if ( sym == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-basic %s\n", path); + return; + } + + int result = dlclose(handle); + if ( result != 0 ) { + printf("dlclose() returned %c\n", result); + printf("[FAIL] dlopen-basic %s\n", path); + return; + } + + printf("[PASS] dlopen-basic %s\n", path); +} + + + +int main() +{ + tryImage("test.bundle"); + tryImage("test.dylib"); + + return 0; +} + diff --git a/testing/test-cases/dlopen-framework-fallback.dtest/main.c b/testing/test-cases/dlopen-framework-fallback.dtest/main.c new file mode 100644 index 0000000..02c467e --- /dev/null +++ b/testing/test-cases/dlopen-framework-fallback.dtest/main.c @@ -0,0 +1,35 @@ + +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-framework-fallback.exe + +// RUN: ./dlopen-framework-fallback.exe + +#include +#include + + + +int main() +{ + printf("[BEGIN] dlopen-framework-fallback\n"); + + // Verify dyld will fallback and look for framework in /System/Library/Frameworks/ + void* handle = dlopen("/System/Library/BadPath/CoreFoundation.framework/CoreFoundation", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-framework-fallback\n"); + return 0; + } + + // validate handle works to find symbols + void* sym = dlsym(handle, "CFRetain"); + if ( sym == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-framework-fallback\n"); + return 0; + } + + printf("[PASS] dlopen-framework-fallback\n"); + + return 0; +} + diff --git a/testing/test-cases/dlopen-signing.dtest/dylib.c b/testing/test-cases/dlopen-signing.dtest/dylib.c new file mode 100644 index 0000000..9841a54 --- /dev/null +++ b/testing/test-cases/dlopen-signing.dtest/dylib.c @@ -0,0 +1,3 @@ +int foo() { + return 10; +} diff --git a/testing/test-cases/dlopen-signing.dtest/main.c b/testing/test-cases/dlopen-signing.dtest/main.c new file mode 100644 index 0000000..eaaa69b --- /dev/null +++ b/testing/test-cases/dlopen-signing.dtest/main.c @@ -0,0 +1,48 @@ +// BUILD: $CC dylib.c -dynamiclib -o $BUILD_DIR/signed.dylib +// BUILD: $CC dylib.c -dynamiclib -o $BUILD_DIR/unsigned.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-signed.exe +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-unsigned.exe + +// FIXME: add builds that sign the executable and the dylib in in various ways +// At this time we don't have a way to do that, so this test must be run +// manually. + +#include +#include + +int main() { + printf("[BEGIN] dlopen-signing\n"); + void* handle = dlopen("signed.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-signing (signed loading signed)\n"); + return 0; + } else { + int result = dlclose(handle); + if ( result != 0 ) { + printf("dlclose() returned %c\n", result); + printf("[FAIL] dlopen-signing (signed unloading signed)\n"); + return 0; + } + } + + handle = dlopen("unsigned.dylib", RTLD_LAZY); + if ( handle != NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-signing (signed loading unsigned)\n"); + return 0; + } else { + int result = dlclose(handle); + if ( result != 0 ) { + printf("dlclose() returned %c\n", result); + printf("[FAIL] dlopen-signing (signed unloading signed)\n"); + return 0; + } + } + + printf("[PASS] dlopen-signing\n"); + + return 0; +} + + diff --git a/testing/test-cases/dyld_abort_payload.dtest/defSymbol.c b/testing/test-cases/dyld_abort_payload.dtest/defSymbol.c new file mode 100644 index 0000000..2e575f3 --- /dev/null +++ b/testing/test-cases/dyld_abort_payload.dtest/defSymbol.c @@ -0,0 +1,5 @@ + + +#if HAS_SYMBOL +int slipperySymbol = 5; +#endif diff --git a/testing/test-cases/dyld_abort_payload.dtest/emptyMain.c b/testing/test-cases/dyld_abort_payload.dtest/emptyMain.c new file mode 100644 index 0000000..0ba8a5b --- /dev/null +++ b/testing/test-cases/dyld_abort_payload.dtest/emptyMain.c @@ -0,0 +1,7 @@ +#include + +int main(int argc, const char* argv[]) +{ + return 1; +} + diff --git a/testing/test-cases/dyld_abort_payload.dtest/foo.c b/testing/test-cases/dyld_abort_payload.dtest/foo.c new file mode 100644 index 0000000..bd89548 --- /dev/null +++ b/testing/test-cases/dyld_abort_payload.dtest/foo.c @@ -0,0 +1,5 @@ + +void foo() +{ +} + diff --git a/testing/test-cases/dyld_abort_payload.dtest/main.c b/testing/test-cases/dyld_abort_payload.dtest/main.c new file mode 100644 index 0000000..e01234d --- /dev/null +++ b/testing/test-cases/dyld_abort_payload.dtest/main.c @@ -0,0 +1,173 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name /cant/find/me.dylib -o $BUILD_DIR/libmissing.dylib +// BUILD: $CC emptyMain.c $BUILD_DIR/libmissing.dylib -o $BUILD_DIR/prog_missing_dylib.exe +// BUILD: $CC defSymbol.c -dynamiclib -install_name libMissingSymbols.dylib -o $BUILD_DIR/libMissingSymbols.dylib +// BUILD: $CC defSymbol.c -dynamiclib -install_name libMissingSymbols.dylib -o $BUILD_DIR/libHasSymbols.dylib -DHAS_SYMBOL +// BUILD: $CC useSymbol.c $BUILD_DIR/libHasSymbols.dylib -o $BUILD_DIR/prog_missing_symbol.exe +// BUILD: $CC main.c -o $BUILD_DIR/dyld_abort_tests.exe + +// RUN: ./dyld_abort_tests.exe + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static bool sSignalCaught = false; +static bool sChildAbortInfoCorrect = false; +static pid_t sChildPid = 0; +static uint64_t sExpectedDyldReason = 0; +static const char* sExpectedDylibPath = NULL; +static const char* sExpectedSymbol = NULL; + + +static void childDied(int sig) +{ + sSignalCaught = true; + //printf("sigchld for pid=%d\n", sChildPid); + + struct proc_exitreasoninfo info; + bzero(&info, sizeof(info)); + uint8_t packReasonData[OS_REASON_BUFFER_MAX_SIZE]; + bzero(packReasonData, OS_REASON_BUFFER_MAX_SIZE); + info.eri_reason_buf_size = OS_REASON_BUFFER_MAX_SIZE; + info.eri_kcd_buf = (user_addr_t)packReasonData; + //fprintf(stderr, "info=%p\n", &info); + if ( proc_pidinfo(sChildPid, PROC_PIDEXITREASONINFO, 1, &info, PROC_PIDEXITREASONINFO_SIZE) != sizeof(struct proc_exitreasoninfo) ) { + printf("bad return size from proc_pidinfo()\n"); + return; + } + if ( info.eri_namespace != OS_REASON_DYLD ) { + printf("eri_namespace != OS_REASON_DYLD\n"); + return; + } + if ( info.eri_code != sExpectedDyldReason ) { + printf("eri_code != %lld\n", sExpectedDyldReason); + return; + } + kcdata_iter_t iter = kcdata_iter(packReasonData, info.eri_reason_buf_size); + + if ( !kcdata_iter_valid(iter) ) { + printf("invalid kcdata iterator from payload data\n"); + return; + } + + if ( kcdata_iter_type(iter) != KCDATA_BUFFER_BEGIN_OS_REASON ){ + printf("first kcdata from payload data is not KCDATA_BUFFER_BEGIN_OS_REASON\n"); + return; + } + + kcdata_iter_t payloadIter = kcdata_iter_find_type(iter, EXIT_REASON_USER_PAYLOAD); + if ( !kcdata_iter_valid(payloadIter) ) { + printf("invalid kcdata payload iterator from payload data\n"); + return; + } + const dyld_abort_payload* dyldInfo = (dyld_abort_payload*)kcdata_iter_payload(payloadIter); + + if ( dyldInfo->version != 1 ) { + printf("dyld payload is not version 1\n"); + return; + } + + if ( (dyldInfo->flags & 1) == 0 ) { + printf("dyld flags should have low bit set to me process terminated at launch\n"); + return; + } + + if ( sExpectedDylibPath != NULL ) { + if ( dyldInfo->targetDylibPathOffset != 0 ) { + const char* targetDylib = (char*)dyldInfo + dyldInfo->targetDylibPathOffset; + if ( strcmp(sExpectedDylibPath, targetDylib) != 0 ) { + printf("dylib path (%s) not what expected (%s)\n", targetDylib, sExpectedDylibPath); + return; + } + } + else { + printf("dylib path (%s) not provided by dyld\n", sExpectedDylibPath); + return; + } + } + + if ( sExpectedSymbol != NULL ) { + if ( dyldInfo->targetDylibPathOffset != 0 ) { + const char* missingSymbol = (char*)dyldInfo + dyldInfo->symbolOffset; + if ( strcmp(sExpectedSymbol, missingSymbol) != 0 ) { + printf("symbol (%s) not what expected (%s)\n", missingSymbol, sExpectedSymbol); + return; + } + } + else { + printf("symbol (%s) not provided by dyld\n", sExpectedSymbol); + return; + } + } + + sChildAbortInfoCorrect = true; +} + + +bool runTest(const char* prog, uint64_t dyldReason, const char* expectedDylibPath, const char* expectedSymbol) +{ + sSignalCaught = false; + sChildAbortInfoCorrect = false; + sExpectedDyldReason = dyldReason; + sExpectedDylibPath = expectedDylibPath; + sExpectedSymbol = expectedSymbol; + + // fork and exec child + sChildPid = fork(); + if ( sChildPid < 0 ) + err(EXIT_FAILURE, "fork"); + if ( sChildPid == 0 ) { + // child side + char* childArgv[] = { (char*)prog, NULL }; + int result = execvp(prog, childArgv); + err(EXIT_FAILURE, "exec(\"%s\",...)", prog); + } + for(int i=0; i < 10; ++i) { + if ( sSignalCaught ) + break; + sleep(1); + } + + return sChildAbortInfoCorrect; +} + + +int main(int argc, const char* argv[]) +{ + bool someTestFailed = false; + printf("[BEGIN] dyld_abort_payload\n"); + + // set up signal handler for catching child terminations + signal(SIGCHLD, childDied); + + // test launch program with missing library + if ( !runTest("./prog_missing_dylib.exe", DYLD_EXIT_REASON_DYLIB_MISSING, "/cant/find/me.dylib", NULL) ) { + printf("[FAIL] dyld_abort_payload DYLD_EXIT_REASON_DYLIB_MISSING\n"); + someTestFailed = true; + } + + // test launch program with missing symbol + if ( !runTest("./prog_missing_symbol.exe", DYLD_EXIT_REASON_SYMBOL_MISSING, "libMissingSymbols.dylib", "_slipperySymbol") ) { + printf("[FAIL] dyld_abort_payload DYLD_EXIT_REASON_SYMBOL_MISSING\n"); + someTestFailed = true; + } + + if ( !someTestFailed ) + printf("[PASS] dyld_abort_payload\n"); + + return 0; +} + diff --git a/testing/test-cases/dyld_abort_payload.dtest/useSymbol.c b/testing/test-cases/dyld_abort_payload.dtest/useSymbol.c new file mode 100644 index 0000000..3aada36 --- /dev/null +++ b/testing/test-cases/dyld_abort_payload.dtest/useSymbol.c @@ -0,0 +1,8 @@ + + +extern int slipperySymbol; + +int main() +{ + return slipperySymbol; +} diff --git a/testing/test-cases/dyld_process_info.dtest/linksWithCF.c b/testing/test-cases/dyld_process_info.dtest/linksWithCF.c new file mode 100644 index 0000000..ffbe74e --- /dev/null +++ b/testing/test-cases/dyld_process_info.dtest/linksWithCF.c @@ -0,0 +1,8 @@ +#include + +int main() +{ + task_suspend(mach_task_self()); + return 0; +} + diff --git a/testing/test-cases/dyld_process_info.dtest/main.c b/testing/test-cases/dyld_process_info.dtest/main.c new file mode 100644 index 0000000..9e5b41e --- /dev/null +++ b/testing/test-cases/dyld_process_info.dtest/main.c @@ -0,0 +1,177 @@ + +// BUILD: $CC linksWithCF.c -o $BUILD_DIR/linksWithCF.exe -framework CoreFoundation +// BUILD: $CC main.c -o $BUILD_DIR/dyld_process_info.exe +// BUILD: $TASK_FOR_PID_ENABLE $BUILD_DIR/dyld_process_info.exe + +// RUN: $SUDO ./dyld_process_info.exe $RUN_DIR/linksWithCF.exe + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +extern char** environ; + +#if __x86_64__ + cpu_type_t otherArch[] = { CPU_TYPE_I386 }; +#elif __i386__ + cpu_type_t otherArch[] = { CPU_TYPE_X86_64 }; +#elif __arm64__ + cpu_type_t otherArch[] = { CPU_TYPE_ARM }; +#elif __arm__ + cpu_type_t otherArch[] = { CPU_TYPE_ARM64 }; +#endif + +static task_t launchTest(const char* testProgPath, bool launchOtherArch, bool launchSuspended) +{ + posix_spawnattr_t attr; + if ( posix_spawnattr_init(&attr) != 0 ) { + printf("[FAIL] dyld_process_info posix_spawnattr_init()\n"); + exit(0); + } + if ( launchSuspended ) { + if ( posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED) != 0 ) { + printf("[FAIL] dyld_process_info POSIX_SPAWN_START_SUSPENDED\n"); + exit(0); + } + } + if ( launchOtherArch ) { + size_t copied; + if ( posix_spawnattr_setbinpref_np(&attr, 1, otherArch, &copied) != 0 ) { + printf("[FAIL] dyld_process_info posix_spawnattr_setbinpref_np()\n"); + exit(0); + } + } + + pid_t childPid; + const char* argv[] = { testProgPath, NULL }; + int psResult = posix_spawn(&childPid, testProgPath, NULL, &attr, (char**)argv, environ); + if ( psResult != 0 ) { + printf("[FAIL] dyld_process_info posix_spawn(%s) failed, err=%d\n", testProgPath, psResult); + exit(0); + } + //printf("child pid=%d\n", childPid); + + task_t childTask = 0; + if ( task_for_pid(mach_task_self(), childPid, &childTask) != KERN_SUCCESS ) { + printf("[FAIL] dyld_process_info task_for_pid()\n"); + kill(childPid, SIGKILL); + exit(0); + } + + // wait until process is up and has suspended itself + struct task_basic_info info; + do { + unsigned count = TASK_BASIC_INFO_COUNT; + kern_return_t kr = task_info(childTask, TASK_BASIC_INFO, (task_info_t)&info, &count); + sleep(1); + } while ( info.suspend_count == 0 ); + + return childTask; +} + +static bool hasCF(task_t task, bool launchedSuspended) +{ + kern_return_t result; + dyld_process_info info = _dyld_process_info_create(task, 0, &result); + if ( info == NULL ) { + printf("[FAIL] dyld_process_info _dyld_process_info_create(), kern_return_t=%d\n", result); + return false; + } + + dyld_process_state_info stateInfo; + _dyld_process_info_get_state(info, &stateInfo); + bool valueSaysLaunchedSuspended = (stateInfo.dyldState == dyld_process_state_not_started); + if ( valueSaysLaunchedSuspended != launchedSuspended ) { + printf("[FAIL] dyld_process_info suspend state mismatch\n"); + return false; + } + + __block bool foundDyld = false; + _dyld_process_info_for_each_image(info, ^(uint64_t machHeaderAddress, const uuid_t uuid, const char* path) { + //fprintf(stderr, "0x%llX %s\n", machHeaderAddress, path); + if ( strstr(path, "/usr/lib/dyld") != NULL ) + foundDyld = true; + }); + + if ( launchedSuspended ) { + // fprintf(stderr, "launched suspended image list:\n"); + __block bool foundMain = false; + _dyld_process_info_for_each_image(info, ^(uint64_t machHeaderAddress, const uuid_t uuid, const char* path) { + //fprintf(stderr, "0x%llX %s\n", machHeaderAddress, path); + if ( strstr(path, "/linksWithCF.exe") != NULL ) + foundMain = true; + }); + return foundMain && foundDyld; + } + + __block bool foundCF = false; + _dyld_process_info_for_each_image(info, ^(uint64_t machHeaderAddress, const uuid_t uuid, const char* path) { + //fprintf(stderr, "0x%llX %s\n", machHeaderAddress, path); + if ( strstr(path, "/CoreFoundation.framework/") != NULL ) + foundCF = true; + }); + + _dyld_process_info_release(info); + + return foundCF && foundDyld; +} + + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] dyld_process_info\n"); + + if ( argc < 2 ) { + printf("[FAIL] dyld_process_info missing argument\n"); + exit(0); + } + const char* testProgPath = argv[1]; + task_t childTask; + + // launch test program same arch as this program + childTask = launchTest(testProgPath, false, false); + if ( ! hasCF(childTask, false) ) { + printf("[FAIL] dyld_process_info same arch does not link with CF and dyld\n"); + task_terminate(childTask); + exit(0); + } + task_terminate(childTask); + + // launch test program suspended + childTask = launchTest(testProgPath, false, true); + if ( ! hasCF(childTask, true) ) { + printf("[FAIL] dyld_process_info suspended does not link with CF and dyld\n"); + task_terminate(childTask); + exit(0); + } + task_resume(childTask); + task_terminate(childTask); + +#if !TARGET_OS_WATCH && !TARGET_OS_TV && __LP64__ + // on 64/32 devices, run test program as other arch too + childTask = launchTest(testProgPath, true, false); + if ( ! hasCF(childTask, false) ) { + printf("[FAIL] dyld_process_info other arch does not link with CF and dyld\n"); + task_terminate(childTask); + exit(0); + } + task_terminate(childTask); +#endif + + // verify this program does not use CF + if ( hasCF(mach_task_self(), false) ) { + printf("[FAIL] dyld_process_info self links with CF and dyld\n"); + exit(0); + } + + printf("[PASS] dyld_process_info\n"); + return 0; +} diff --git a/testing/test-cases/dyld_process_info_notify.dtest/foo.c b/testing/test-cases/dyld_process_info_notify.dtest/foo.c new file mode 100644 index 0000000..ec11833 --- /dev/null +++ b/testing/test-cases/dyld_process_info_notify.dtest/foo.c @@ -0,0 +1,4 @@ + +void foo() { +} + diff --git a/testing/test-cases/dyld_process_info_notify.dtest/main.c b/testing/test-cases/dyld_process_info_notify.dtest/main.c new file mode 100644 index 0000000..04d391d --- /dev/null +++ b/testing/test-cases/dyld_process_info_notify.dtest/main.c @@ -0,0 +1,294 @@ + +// BUILD: $CC target.c -o $BUILD_DIR/target.exe +// BUILD: $CC foo.c -o $BUILD_DIR/libfoo.dylib -dynamiclib +// BUILD: $CC main.c -o $BUILD_DIR/dyld_process_info_notify.exe +// BUILD: $TASK_FOR_PID_ENABLE $BUILD_DIR/dyld_process_info_notify.exe + +// RUN_TIMEOUT: 2400 +// RUN: $SUDO ./dyld_process_info_notify.exe $RUN_DIR/target.exe + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +extern char** environ; + +#if __x86_64__ + cpu_type_t otherArch[] = { CPU_TYPE_I386 }; +#elif __i386__ + cpu_type_t otherArch[] = { CPU_TYPE_X86_64 }; +#elif __arm64__ + cpu_type_t otherArch[] = { CPU_TYPE_ARM }; +#elif __arm__ + cpu_type_t otherArch[] = { CPU_TYPE_ARM64 }; +#endif + +static task_t launchTest(const char* testProgPath, const char* arg1, bool launchOtherArch, bool launchSuspended) +{ + //fprintf(stderr, "launchTest() launchOtherArch=%d, launchSuspended=%d, arg=%s\n", launchOtherArch, launchSuspended, arg1); + posix_spawnattr_t attr; + if ( posix_spawnattr_init(&attr) != 0 ) { + printf("[FAIL] dyld_process_info_notify posix_spawnattr_init()\n"); + exit(0); + } + if ( launchSuspended ) { + if ( posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED) != 0 ) { + printf("[FAIL] dyld_process_info_notify POSIX_SPAWN_START_SUSPENDED\n"); + exit(0); + } + } + if ( launchOtherArch ) { + size_t copied; + if ( posix_spawnattr_setbinpref_np(&attr, 1, otherArch, &copied) != 0 ) { + printf("[FAIL] dyld_process_info_notify posix_spawnattr_setbinpref_np()\n"); + exit(0); + } + } + + pid_t childPid; + const char* argv[] = { testProgPath, arg1, NULL }; + int psResult = posix_spawn(&childPid, testProgPath, NULL, &attr, (char**)argv, environ); + if ( psResult != 0 ) { + printf("[FAIL] dyld_process_info_notify posix_spawn(%s) failed, err=%d\n", testProgPath, psResult); + exit(0); + } + //printf("child pid=%d\n", childPid); + + task_t childTask = 0; + if ( task_for_pid(mach_task_self(), childPid, &childTask) != KERN_SUCCESS ) { + printf("[FAIL] dyld_process_info_notify task_for_pid()\n"); + kill(childPid, SIGKILL); + exit(0); + } + + return childTask; +} + +static void wait_util_task_suspended(task_t task) +{ + struct task_basic_info info; + do { + unsigned count = TASK_BASIC_INFO_COUNT; + kern_return_t kr = task_info(task, TASK_BASIC_INFO, (task_info_t)&info, &count); + //fprintf(stderr, "task_info() => %d, suspendCount=%d\n", kr, info.suspend_count); + sleep(1); + } while ( info.suspend_count == 0 ); +} + + +static bool monitor(task_t task, bool disconnectEarly, bool attachLate) +{ + kern_return_t kr; + __block bool sawMainExecutable = false; + __block bool sawlibSystem = false; + __block bool gotTerminationNotice = false; + __block bool gotEarlyNotice = false; + __block int libFooLoadCount = 0; + __block int libFooUnloadCount = 0; + dispatch_semaphore_t taskDone = dispatch_semaphore_create(0); + + dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + + unsigned count = 0; + dyld_process_info_notify handle; + do { + handle = _dyld_process_info_notify(task, serviceQueue, + ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) { + if ( strstr(path, "/target.exe") != NULL ) + sawMainExecutable = true; + if ( strstr(path, "/libSystem") != NULL ) + sawlibSystem = true; + if ( strstr(path, "/libfoo.dylib") != NULL ) { + if ( unload ) + ++libFooUnloadCount; + else + ++libFooLoadCount; + if ( disconnectEarly ) { + gotEarlyNotice = true; + dispatch_semaphore_signal(taskDone); + } + } + //fprintf(stderr, "unload=%d, 0x%012llX <%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> %s\n", + // unload, machHeader, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], + // uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], path); + }, + ^{ + //fprintf(stderr, "target exited\n"); + gotTerminationNotice = true; + dispatch_semaphore_signal(taskDone); + }, + &kr); + ++count; + if ( handle == NULL ) + fprintf(stderr, "_dyld_process_info_notify() returned NULL, result=%d, count=%d\n", kr, count); + } while ( (handle == NULL) && (count < 5) ); + + if ( handle == NULL ) { + return false; + } + + // if process suspends itself, wait until it has done so + if ( attachLate ) + wait_util_task_suspended(task); + + // resume from initial suspend + task_resume(task); + + // block waiting for notification that target has exited + bool gotSignal = (dispatch_semaphore_wait(taskDone, dispatch_time(DISPATCH_TIME_NOW, 10LL * NSEC_PER_SEC)) == 0); + _dyld_process_info_notify_release(handle); + + + if ( !gotSignal ) { + fprintf(stderr, "did not get exit signal\n"); + return false; + } + + if ( !attachLate && !sawMainExecutable ) { + fprintf(stderr, "did not get load notification of main executable\n"); + return false; + } + + if ( !attachLate && !sawlibSystem ) { + fprintf(stderr, "did not get load notification of libSystem\n"); + return false; + } + + if ( disconnectEarly ) { + if ( libFooLoadCount != 1 ) { + fprintf(stderr, "got %d load notifications about libFoo instead of 1\n", libFooLoadCount); + return false; + } + if ( libFooUnloadCount != 0 ) { + fprintf(stderr, "got %d unload notifications about libFoo instead of 1\n", libFooUnloadCount); + return false; + } + } + else { + if ( libFooLoadCount != 3 ) { + fprintf(stderr, "got %d load notifications about libFoo instead of 3\n", libFooLoadCount); + return false; + } + if ( libFooUnloadCount != 3 ) { + fprintf(stderr, "got %d unload notifications about libFoo instead of 3\n", libFooUnloadCount); + return false; + } + } + + return true; +} + +static void validateMaxNotifies(task_t task) +{ + dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + dyld_process_info_notify handles[10]; + for (int i=0; i < 10; ++i) { + kern_return_t kr; + handles[i] = _dyld_process_info_notify(task, serviceQueue, + ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) { + //fprintf(stderr, "unload=%d, 0x%012llX <%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> %s\n", + // unload, machHeader, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], + // uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], path); + }, + ^{ + //fprintf(stderr, "target exited\n"); + }, + &kr); + if ( handles[i] == NULL ) { + if ( i == 8 ) { + // expected failure, because only 8 simultaneous connections allowed + // release one and try again + _dyld_process_info_notify_release(handles[4]); + handles[4] = NULL; + } + else { + fprintf(stderr, "_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d\n", kr, i); + task_terminate(task); + exit(0); + } + } + } + // release all + for (int i=0; i < 10; ++i) { + if ( handles[i] != NULL ) { + _dyld_process_info_notify_release(handles[i]); + } + } + dispatch_release(serviceQueue); +} + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] dyld_process_info_notify\n"); + if ( argc < 2 ) { + printf("[FAIL] dyld_process_info_notify missing argument\n"); + exit(0); + } + const char* testProgPath = argv[1]; + + dispatch_async(dispatch_get_main_queue(), ^{ + task_t childTask; + + // test 1) launch test program suspended in same arch as this program + childTask = launchTest(testProgPath, "", false, true); + if ( ! monitor(childTask, false, false) ) { + printf("[FAIL] dyld_process_info_notify launch suspended missed some notifications\n"); + task_terminate(childTask); + exit(0); + } + task_terminate(childTask); + + // test 2) launch test program in same arch as this program where it sleeps itself + childTask = launchTest(testProgPath, "suspend-in-main", false, false); + validateMaxNotifies(childTask); + if ( ! monitor(childTask, false, true) ) { + printf("[FAIL] dyld_process_info_notify launch suspend-in-main missed some notifications\n"); + task_terminate(childTask); + exit(0); + } + task_terminate(childTask); + +#if !TARGET_OS_WATCH && !TARGET_OS_TV && __LP64__ + // test 3) launch test program suspended in opposite arch as this program + childTask = launchTest(testProgPath, "", true, true); + if ( ! monitor(childTask, false, false) ) { + printf("[FAIL] dyld_process_info_notify launch suspended other arch missed some notifications\n"); + task_terminate(childTask); + exit(0); + } + task_terminate(childTask); + + // test 4) launch test program in opposite arch as this program where it sleeps itself + childTask = launchTest(testProgPath, "suspend-in-main", true, false); + if ( ! monitor(childTask, false, true) ) { + printf("[FAIL] dyld_process_info_notify launch other arch suspend-in-main missed some notifications\n"); + task_terminate(childTask); + exit(0); + } + task_terminate(childTask); +#endif + + // test 5) launch test program where we disconnect from it after first dlopen + childTask = launchTest(testProgPath, "", false, true); + if ( ! monitor(childTask, true, false) ) { + printf("[FAIL] dyld_process_info_notify connect/disconnect missed some notifications\n"); + task_terminate(childTask); + exit(0); + } + task_terminate(childTask); + + printf("[PASS] dyld_process_info_notify\n"); + exit(0); + }); + + dispatch_main(); +} diff --git a/testing/test-cases/dyld_process_info_notify.dtest/target.c b/testing/test-cases/dyld_process_info_notify.dtest/target.c new file mode 100644 index 0000000..f73e5a0 --- /dev/null +++ b/testing/test-cases/dyld_process_info_notify.dtest/target.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include +#include + + + +int main(int argc, const char* argv[]) +{ + if ( (argc > 1) && (strcmp(argv[1], "suspend-in-main") == 0) ) + task_suspend(mach_task_self()); + + for (int i=0; i < 3; ++i) { + void* h = dlopen("./libfoo.dylib", 0); + dlclose(h); + } + + return 0; +} + diff --git a/testing/test-cases/dylib-re-export-old-format.dtest/bar.c b/testing/test-cases/dylib-re-export-old-format.dtest/bar.c new file mode 100644 index 0000000..c78b45d --- /dev/null +++ b/testing/test-cases/dylib-re-export-old-format.dtest/bar.c @@ -0,0 +1,5 @@ + +int bar() { + return 42; +} + diff --git a/testing/test-cases/dylib-re-export-old-format.dtest/foo.c b/testing/test-cases/dylib-re-export-old-format.dtest/foo.c new file mode 100644 index 0000000..54c3a28 --- /dev/null +++ b/testing/test-cases/dylib-re-export-old-format.dtest/foo.c @@ -0,0 +1,3 @@ + +int foo = 1; + diff --git a/testing/test-cases/dylib-re-export-old-format.dtest/main.c b/testing/test-cases/dylib-re-export-old-format.dtest/main.c new file mode 100644 index 0000000..06e73a7 --- /dev/null +++ b/testing/test-cases/dylib-re-export-old-format.dtest/main.c @@ -0,0 +1,26 @@ +// BUILD_ONLY: MacOSX +// BUILD_MIN_OS: 10.5 +// BUILD: $CC bar.c -dynamiclib -install_name $RUN_DIR/libbar.dylib -o $BUILD_DIR/libbar.dylib +// BUILD: $CC foo.c -dynamiclib $BUILD_DIR/libbar.dylib -sub_library libbar -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dylib-re-export.exe $BUILD_DIR/libfoo.dylib -L$BUILD_DIR + +// RUN: ./dylib-re-export.exe + + +#include + +extern int bar(); + + +int main() +{ + printf("[BEGIN] dylib-re-export\n"); + if ( bar() == 42 ) + printf("[PASS] dylib-re-export\n"); + else + printf("[FAIL] dylib-re-export, wrong value\n"); + + return 0; +} + + diff --git a/testing/test-cases/dylib-re-export.dtest/bar.c b/testing/test-cases/dylib-re-export.dtest/bar.c new file mode 100644 index 0000000..c78b45d --- /dev/null +++ b/testing/test-cases/dylib-re-export.dtest/bar.c @@ -0,0 +1,5 @@ + +int bar() { + return 42; +} + diff --git a/testing/test-cases/dylib-re-export.dtest/foo.c b/testing/test-cases/dylib-re-export.dtest/foo.c new file mode 100644 index 0000000..54c3a28 --- /dev/null +++ b/testing/test-cases/dylib-re-export.dtest/foo.c @@ -0,0 +1,3 @@ + +int foo = 1; + diff --git a/testing/test-cases/dylib-re-export.dtest/main.c b/testing/test-cases/dylib-re-export.dtest/main.c new file mode 100644 index 0000000..879b0b6 --- /dev/null +++ b/testing/test-cases/dylib-re-export.dtest/main.c @@ -0,0 +1,26 @@ + + +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/libbar.dylib -install_name $RUN_DIR/libbar.dylib +// BUILD: $CC foo.c -dynamiclib $BUILD_DIR/libbar.dylib -sub_library libbar -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dylib-re-export.exe $BUILD_DIR/libfoo.dylib -L$BUILD_DIR + +// RUN: ./dylib-re-export.exe + + +#include + +extern int bar(); + + +int main() +{ + printf("[BEGIN] dylib-re-export\n"); + if ( bar() == 42 ) + printf("[PASS] dylib-re-export\n"); + else + printf("[FAIL] dylib-re-export, wrong value\n"); + + return 0; +} + + diff --git a/testing/test-cases/dylib-static-link.dtest/foo.c b/testing/test-cases/dylib-static-link.dtest/foo.c new file mode 100644 index 0000000..c42b10f --- /dev/null +++ b/testing/test-cases/dylib-static-link.dtest/foo.c @@ -0,0 +1,3 @@ + +int foo = 42; + diff --git a/testing/test-cases/dylib-static-link.dtest/missing.c b/testing/test-cases/dylib-static-link.dtest/missing.c new file mode 100644 index 0000000..6a4a62d --- /dev/null +++ b/testing/test-cases/dylib-static-link.dtest/missing.c @@ -0,0 +1,12 @@ +#include +#include + +int main() +{ + printf("[BEGIN] dylib-static-link missing\n"); + printf("[FAIL] dylib-static-link missing, program should not have launched\n"); + + return 0; +} + + diff --git a/testing/test-cases/dylib-static-link.dtest/present.c b/testing/test-cases/dylib-static-link.dtest/present.c new file mode 100644 index 0000000..4e7aa2d --- /dev/null +++ b/testing/test-cases/dylib-static-link.dtest/present.c @@ -0,0 +1,28 @@ + + +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name libfoo.dylib +// BUILD: $CC present.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/dylib-static-present.exe +// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/libfoo2.dylib -install_name libfoomissing.dylib +// BUILD: $CC missing.c $TEMP_DIR/libfoo2.dylib -o $BUILD_DIR/dylib-static-missing.exe + +// RUN: ./dylib-static-present.exe +// RUN: NOCR_TEST_NAME="dylib-static-link missing" $REQUIRE_CRASH ./dylib-static-missing.exe + + +#include + +extern int foo; + + +int main() +{ + printf("[BEGIN] dylib-static-link present\n"); + if ( foo == 42 ) + printf("[PASS] dylib-static-link present\n"); + else + printf("[FAIL] dylib-static-link present, wrong value\n"); + + return 0; +} + + diff --git a/testing/test-cases/dylib-static-weak-link.dtest/foo.c b/testing/test-cases/dylib-static-weak-link.dtest/foo.c new file mode 100644 index 0000000..c42b10f --- /dev/null +++ b/testing/test-cases/dylib-static-weak-link.dtest/foo.c @@ -0,0 +1,3 @@ + +int foo = 42; + diff --git a/testing/test-cases/dylib-static-weak-link.dtest/missing.c b/testing/test-cases/dylib-static-weak-link.dtest/missing.c new file mode 100644 index 0000000..c633047 --- /dev/null +++ b/testing/test-cases/dylib-static-weak-link.dtest/missing.c @@ -0,0 +1,19 @@ +#include +#include + +extern int foo __attribute__((weak_import)); + + +int main() +{ + printf("[BEGIN] dylib-static-weak-link missing\n"); + // dylib won't be found at runtime, so &foo should be NULL + if ( &foo == NULL ) + printf("[PASS] dylib-static-weak-link missing\n"); + else + printf("[FAIL] dylib-static-weak-link missing, &foo != NULL\n"); + + return 0; +} + + diff --git a/testing/test-cases/dylib-static-weak-link.dtest/present.c b/testing/test-cases/dylib-static-weak-link.dtest/present.c new file mode 100644 index 0000000..1f4a6e2 --- /dev/null +++ b/testing/test-cases/dylib-static-weak-link.dtest/present.c @@ -0,0 +1,33 @@ +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name libfoo.dylib +// BUILD: $CC present.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/dylib-static-weak-present.exe +// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/libfoo2.dylib -install_name libfoomissing.dylib +// BUILD: $CC missing.c $TEMP_DIR/libfoo2.dylib -o $BUILD_DIR/dylib-static-weak-missing.exe + +// RUN: ./dylib-static-weak-present.exe +// RUN: ./dylib-static-weak-missing.exe + + +#include +#include + +extern int foo __attribute__((weak_import)); + + +int main() +{ + printf("[BEGIN] dylib-static-weak-link present\n"); + // dylib will be found at runtime, so &foo should never be NULL + if ( &foo != NULL ) { + if ( foo == 42 ) + printf("[PASS] dylib-static-weak-link present\n"); + else + printf("[FAIL] dylib-static-weak-link present, wrong value\n"); + } + else { + printf("[FAIL] dylib-static-weak-link present, &foo == NULL\n"); + } + + return 0; +} + + diff --git a/testing/test-cases/interpose-weak.dtest/foo.c b/testing/test-cases/interpose-weak.dtest/foo.c new file mode 100644 index 0000000..ae32a9f --- /dev/null +++ b/testing/test-cases/interpose-weak.dtest/foo.c @@ -0,0 +1,12 @@ + + + +int foo1() { return 1; } + +int foo2() { return 2; } + +#ifndef NO_FOO34 + int foo3() { return 3; } + int foo4() { return 4; } +#endif + diff --git a/testing/test-cases/interpose-weak.dtest/interposer.c b/testing/test-cases/interpose-weak.dtest/interposer.c new file mode 100644 index 0000000..cf92d90 --- /dev/null +++ b/testing/test-cases/interpose-weak.dtest/interposer.c @@ -0,0 +1,15 @@ + +#include + +extern int foo2(); +extern int foo4() __attribute__((weak_import)); + + + +int myfoo2() { return 12; } +int myfoo4() { return 14; } + + + +DYLD_INTERPOSE(myfoo2, foo2) +DYLD_INTERPOSE(myfoo4, foo4) diff --git a/testing/test-cases/interpose-weak.dtest/main.c b/testing/test-cases/interpose-weak.dtest/main.c new file mode 100644 index 0000000..f167a6a --- /dev/null +++ b/testing/test-cases/interpose-weak.dtest/main.c @@ -0,0 +1,70 @@ +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib +// BUILD: $CC main.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/interpose-weak-present.exe +// BUILD: $CC interposer.c -dynamiclib $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/libinterposer.dylib -install_name libinterposer.dylib + +// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/libfoo2.dylib -install_name $RUN_DIR/libfoo2.dylib +// BUILD: $CC foo.c -DNO_FOO34 -dynamiclib -o $BUILD_DIR/libfoo2.dylib -install_name $RUN_DIR/libfoo2.dylib +// BUILD: $CC main.c -DNO_FOO34 $TEMP_DIR/libfoo2.dylib -o $BUILD_DIR/interpose-weak-missing.exe +// BUILD: $CC interposer.c -dynamiclib $TEMP_DIR/libfoo2.dylib -o $BUILD_DIR/libinterposer2.dylib -install_name libinterposer.dylib + + +// RUN: DYLD_INSERT_LIBRARIES=libinterposer.dylib ./interpose-weak-present.exe +// RUN: DYLD_INSERT_LIBRARIES=libinterposer2.dylib ./interpose-weak-missing.exe + + + +#include +#include +#include + + +extern int foo1(); +extern int foo2(); +extern int foo3() __attribute__((weak_import)); +extern int foo4() __attribute__((weak_import)); + +#ifndef NO_FOO34 + #define MODE "present" +#else + #define MODE "missing" +#endif + +int main() +{ + printf("[BEGIN] interpose-weak-" MODE "\n"); + + if ( foo1() != 1 ) { + printf("[FAIL] interpose-weak-" MODE ", foo1() != 1\n"); + return 0; + } + + if ( foo2() != 12 ) { + printf("[FAIL] interpose-weak-" MODE ", foo2() != 12\n"); + return 0; + } + +#ifndef NO_FOO34 + if ( foo3() != 3 ) { + printf("[FAIL] interpose-weak-" MODE ", foo3() != 3\n"); + return 0; + } + + if ( foo4() != 14 ) { + printf("[FAIL] interpose-weak-" MODE ", foo4() != 14\n"); + return 0; + } +#else + if ( &foo3 != NULL ) { + printf("[FAIL] interpose-weak-" MODE ", &foo3 != NULL\n"); + return 0; + } + + if ( &foo4 != NULL ) { + printf("[FAIL] interpose-weak-" MODE ", &foo4 != NULL\n"); + return 0; + } +#endif + + printf("[PASS] interpose-weak-" MODE "\n"); + return 0; +} diff --git a/testing/test-cases/restrict-search.dtest/foo.c b/testing/test-cases/restrict-search.dtest/foo.c new file mode 100644 index 0000000..c8e9924 --- /dev/null +++ b/testing/test-cases/restrict-search.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/restrict-search.dtest/main.c b/testing/test-cases/restrict-search.dtest/main.c new file mode 100644 index 0000000..e8355ca --- /dev/null +++ b/testing/test-cases/restrict-search.dtest/main.c @@ -0,0 +1,54 @@ +// BUILD_ONLY: MacOSX + +// BUILD: mkdir $BUILD_DIR/lc +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/lc/libfoo.dylib -install_name /blah/libfoo.dylib +// BUILD: $CC main.c $BUILD_DIR/lc/libfoo.dylib -o $BUILD_DIR/restrict-search-lc-find.exe -Wl,-dyld_env,DYLD_LIBRARY_PATH=@loader_path/lc -DMODE=lc-find -DSHOULD_BE_FOUND=1 +// BUILD: $CC main.c $BUILD_DIR/lc/libfoo.dylib -o $BUILD_DIR/restrict-search-lc-no-find.exe -Wl,-dyld_env,DYLD_LIBRARY_PATH=@loader_path/lc -DMODE=lc-no-find -sectcreate __RESTRICT __restrict /dev/null +// BUILD: mkdir $BUILD_DIR/rpath +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/rpath/libfoo.dylib -install_name @rpath/libfoo.dylib +// BUILD: $CC main.c $BUILD_DIR/rpath/libfoo.dylib -o $BUILD_DIR/restrict-search-rpath-find.exe -Wl,-rpath,@loader_path/rpath/ -DMODE=rpath-find -DSHOULD_BE_FOUND=1 +// BUILD: $CC main.c $BUILD_DIR/rpath/libfoo.dylib -o $BUILD_DIR/restrict-search-rpath-no-find.exe -Wl,-rpath,@loader_path/rpath/ -DMODE=rpath-no-find -sectcreate __RESTRICT __restrict /dev/null + + +// RUN: ./restrict-search-lc-find.exe +// RUN: ./restrict-search-lc-no-find.exe +// RUN: ./restrict-search-rpath-find.exe +// RUN: ./restrict-search-rpath-no-find.exe + + + +#include +#include + +// Two ways to find libfoo.dylb: @rpath or DYLD_LIBRARY_PATH (set via LC_DYLD_ENVIRONMENT) +// These should work for non-restrictured programs. +// These should fail for restricted programs. +// By weak linking we can test if libfoo.dylib was found or not. + + +extern int foo() __attribute__((weak_import)); + + +#define STRINGIFY2( x) #x +#define STRINGIFY(x) STRINGIFY2(x) + + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] restrict-search %s\n", STRINGIFY(MODE)); +#if SHOULD_BE_FOUND + if ( &foo == NULL ) + printf("[FAIL] restrict-search %s\n", STRINGIFY(MODE)); + else + printf("[PASS] restrict-search %s\n", STRINGIFY(MODE)); +#else + // dylib won't be found at runtime, so &foo should be NULL + if ( &foo == NULL ) + printf("[PASS] restrict-search %s\n", STRINGIFY(MODE)); + else + printf("[FAIL] restrict-search %s\n", STRINGIFY(MODE)); +#endif + + return 0; +} + diff --git a/testing/test-cases/thread-local-variables.dtest/foo.c b/testing/test-cases/thread-local-variables.dtest/foo.c new file mode 100644 index 0000000..60bbbb4 --- /dev/null +++ b/testing/test-cases/thread-local-variables.dtest/foo.c @@ -0,0 +1,8 @@ + + + +__thread int a; +__thread int b = 5; + + + diff --git a/testing/test-cases/thread-local-variables.dtest/main.c b/testing/test-cases/thread-local-variables.dtest/main.c new file mode 100644 index 0000000..d7c73b2 --- /dev/null +++ b/testing/test-cases/thread-local-variables.dtest/main.c @@ -0,0 +1,115 @@ + + +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib +// BUILD: $CC main.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/thread-local-variables.exe + + +// RUN: ./thread-local-variables.exe + + +#include +#include +#include +#include +#include +#include + + +extern __thread int a; +extern __thread int b; +static __thread int c; +static __thread int d = 5; + + +static int* sAddr1[4]; +static int* sAddr2[4]; +static int* sAddr3[4]; + +static pthread_t sWorker1; +static pthread_t sWorker2; + + +// verify all initial TLV values are correct +static void checkValues() +{ + assert(a == 0); + assert(b == 5); + assert(c == 0); + assert(d == 5); +} + + +// return addresses of all TLVs +static void getAddresses(int* array[]) +{ + array[0] = &a; + array[1] = &b; + array[2] = &c; + array[3] = &d; +} + +static void* work2(void* arg) +{ + checkValues(); + getAddresses(sAddr2); + + return NULL; +} + +static void* work1(void* arg) +{ + checkValues(); + + if ( pthread_create(&sWorker2, NULL, work2, NULL) != 0 ) { + printf("[FAIL] thread-local-variables, pthread_create\n"); + exit(0); + } + void* dummy; + pthread_join(sWorker2, &dummy); + + getAddresses(sAddr1); + + return NULL; +} + +static bool someMatch(int* t1, int* t2, int* t3) +{ + if ( t1 == t2 ) + return true; + if ( t1 == t3 ) + return true; + if ( t2 == t3 ) + return true; + return false; +} + +int main() +{ + printf("[BEGIN] thread-local-variables\n"); + + checkValues(); + + if ( pthread_create(&sWorker1, NULL, work1, NULL) != 0 ) { + printf("[FAIL] thread-local-variables, pthread_create\n"); + return 0; + } + + getAddresses(sAddr3); + + void* dummy; + pthread_join(sWorker1, &dummy); + + // validate each thread had different addresses for all TLVs + if ( someMatch(sAddr1[0], sAddr2[0], sAddr3[0]) ) + printf("[FAIL] thread-local-variables, &a is same across some threads\n"); + else if ( someMatch(sAddr1[1], sAddr2[1], sAddr3[1]) ) + printf("[FAIL] thread-local-variables, &b is same across some threads\n"); + else if ( someMatch(sAddr1[2], sAddr2[2], sAddr3[2]) ) + printf("[FAIL] thread-local-variables, &c is same across some threads\n"); + else if ( someMatch(sAddr1[3], sAddr2[3], sAddr3[3]) ) + printf("[FAIL] thread-local-variables, &d is same across some threads\n"); + else + printf("[PASS] thread-local-variables\n"); + return 0; +} + diff --git a/unit-tests/test-cases/coreSymbolication-notify/Makefile b/unit-tests/test-cases/coreSymbolication-notify/Makefile index 60cedcd..d0b5b87 100644 --- a/unit-tests/test-cases/coreSymbolication-notify/Makefile +++ b/unit-tests/test-cases/coreSymbolication-notify/Makefile @@ -29,10 +29,10 @@ all-check: all check check: export DYLD_PRINT_CS_NOTIFICATIONS=1 && ./main 2> notifications.log - grep " load" notifications.log | grep foo.bundle | wc -l | grep 1 >/dev/null - grep " load" notifications.log | grep bar.dylib | wc -l | grep 1 >/dev/null - grep " unload" notifications.log | grep foo.bundle | wc -l | grep 1 >/dev/null - grep " unload" notifications.log | grep bar.dylib | wc -l | grep 1 >/dev/null + grep "_load" notifications.log | grep foo.bundle | wc -l | grep 1 >/dev/null + grep "_load" notifications.log | grep bar.dylib | wc -l | grep 1 >/dev/null + grep "_unload" notifications.log | grep foo.bundle | wc -l | grep 1 >/dev/null + grep "_unload" notifications.log | grep bar.dylib | wc -l | grep 1 >/dev/null echo "PASS coreSymbolication-notify" all: main foo.bundle diff --git a/unit-tests/test-cases/crt-apple/main.c b/unit-tests/test-cases/crt-apple/main.c index 8a56fd8..73a1922 100644 --- a/unit-tests/test-cases/crt-apple/main.c +++ b/unit-tests/test-cases/crt-apple/main.c @@ -26,6 +26,7 @@ #include #include "test.h" // PASS(), FAIL(), XPASS(), XFAIL() +#include <_simple.h> /// /// verify parameters passed to initializer are same as passed to main() @@ -70,10 +71,13 @@ main(int argc, const char* argv[], const char* env[], const char* apple[]) exit(EXIT_SUCCESS); } - if ( strcmp(apple[0], argv[1]) == 0 ) - PASS("crt-apple %s", apple[0]); + const char* execPath = _simple_getenv(apple, "executable_path"); + if ( execPath == NULL ) + FAIL("crt-apple apple[] missing executable_path="); + else if ( strcmp(execPath, argv[1]) == 0 ) + PASS("crt-apple %s", execPath); else - FAIL("crt-apple %s", apple[0]); + FAIL("crt-apple %s", execPath); return EXIT_SUCCESS; } diff --git a/unit-tests/test-cases/dlopen-sandbox/Makefile b/unit-tests/test-cases/dlopen-sandbox/Makefile new file mode 100644 index 0000000..f52f1b2 --- /dev/null +++ b/unit-tests/test-cases/dlopen-sandbox/Makefile @@ -0,0 +1,44 @@ +## +# Copyright (c) 2015 Apple Computer, Inc. All rights reserved. +# +# @APPLE_LICENSE_HEADER_START@ +# +# This file contains Original Code and/or Modifications of Original Code +# as defined in and that are subject to the Apple Public Source License +# Version 2.0 (the 'License'). You may not use this file except in +# compliance with the License. Please obtain a copy of the License at +# http://www.opensource.apple.com/apsl/ and read it before using this +# file. +# +# The Original Code and all software distributed under the License are +# distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +# EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +# INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +# Please see the License for the specific language governing rights and +# limitations under the License. +# +# @APPLE_LICENSE_HEADER_END@ +## +TESTROOT = ../.. +include ${TESTROOT}/include/common.makefile + +all-check: all check + +check: + sandbox-exec -f sbs/deny-open.sb ./main "file system sandbox blocked open" + sandbox-exec -f sbs/deny-stat.sb ./main "file system sandbox blocked stat" + sandbox-exec -f sbs/deny-mmap.sb ./main "file system sandbox blocked mmap" + +all: + ${CC} ${CCFLAGS} foo.c -dynamiclib -o libfoo.dylib + ${CC} ${CCFLAGS} -I${TESTROOT}/include -o main main.c + mkdir -p sbs + sed -e 's/FILTER/file-read-data/' -e "s|DIR|$$PWD|" < base.sb > sbs/deny-open.sb + sed -e 's/FILTER/file-read-metadata/' -e "s|DIR|$$PWD|" < base.sb > sbs/deny-stat.sb + sed -e 's/FILTER/file-map-executable/' -e "s|DIR|$$PWD|" < base.sb > sbs/deny-mmap.sb + +clean: + ${RM} -rf *~ main sbs libfoo.dylib + + diff --git a/unit-tests/test-cases/dlopen-sandbox/base.sb b/unit-tests/test-cases/dlopen-sandbox/base.sb new file mode 100644 index 0000000..c97f7e8 --- /dev/null +++ b/unit-tests/test-cases/dlopen-sandbox/base.sb @@ -0,0 +1,9 @@ + +(version 1) +(allow default) +(debug deny) + +(deny FILTER + (literal "DIR/libfoo.dylib")) + + diff --git a/unit-tests/test-cases/dlopen-sandbox/foo.c b/unit-tests/test-cases/dlopen-sandbox/foo.c new file mode 100644 index 0000000..8f1b3d8 --- /dev/null +++ b/unit-tests/test-cases/dlopen-sandbox/foo.c @@ -0,0 +1,7 @@ + + +int foo() +{ + return 42; +} + diff --git a/unit-tests/test-cases/dlopen-sandbox/main.c b/unit-tests/test-cases/dlopen-sandbox/main.c new file mode 100644 index 0000000..f3ade8f --- /dev/null +++ b/unit-tests/test-cases/dlopen-sandbox/main.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#include // fprintf(), NULL +#include // exit(), EXIT_SUCCESS +#include +#include + +#include "test.h" // PASS(), FAIL(), XPASS(), XFAIL() + + +int main(int argc, const char* argv[]) +{ + void* handle = dlopen("./libfoo.dylib", RTLD_LAZY); + if ( handle != NULL ) { + FAIL("dlopen-sandbox dylib should not have loaded"); + return EXIT_SUCCESS; + } + const char* errorMsg = dlerror(); + const char* shouldContain = argv[1]; + if ( strstr(errorMsg, shouldContain) == NULL ) + FAIL("dlopen-sandbox dylib correctly failed to loaded, but with wrong error message: %s", errorMsg); + else + PASS("dlopen-sandbox: %s", shouldContain); + + return EXIT_SUCCESS; +} diff --git a/unit-tests/test-cases/dyld_is_memory_immutable/Makefile b/unit-tests/test-cases/dyld_is_memory_immutable/Makefile new file mode 100644 index 0000000..80fae8e --- /dev/null +++ b/unit-tests/test-cases/dyld_is_memory_immutable/Makefile @@ -0,0 +1,21 @@ +TESTROOT = ../.. +include ${TESTROOT}/include/common.makefile + +# +# verify addends work +# + +all-check: all check + +check: + ./main + +all: + ${CC} ${CCFLAGS} bar.c -dynamiclib -o libbar.dylib + ${CC} ${CCFLAGS} foo.c -dynamiclib -o libfoo.dylib + ${CC} ${CCFLAGS} -I${TESTROOT}/include libfoo.dylib -o main main.c + + +clean: + ${RM} ${RMFLAGS} *~ main libfoo.dylib libbar.dylib + diff --git a/unit-tests/test-cases/dyld_is_memory_immutable/bar.c b/unit-tests/test-cases/dyld_is_memory_immutable/bar.c new file mode 100644 index 0000000..7bf381e --- /dev/null +++ b/unit-tests/test-cases/dyld_is_memory_immutable/bar.c @@ -0,0 +1,7 @@ + + + +const char* bar() +{ + return "bar"; +} diff --git a/unit-tests/test-cases/dyld_is_memory_immutable/foo.c b/unit-tests/test-cases/dyld_is_memory_immutable/foo.c new file mode 100644 index 0000000..ab30bb4 --- /dev/null +++ b/unit-tests/test-cases/dyld_is_memory_immutable/foo.c @@ -0,0 +1,6 @@ + + +const char* foo() +{ + return "foo"; +} \ No newline at end of file diff --git a/unit-tests/test-cases/dyld_is_memory_immutable/main.c b/unit-tests/test-cases/dyld_is_memory_immutable/main.c new file mode 100644 index 0000000..aa3a9d4 --- /dev/null +++ b/unit-tests/test-cases/dyld_is_memory_immutable/main.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#include // fprintf(), NULL +#include // exit(), EXIT_SUCCESS +#include +#include +#include + +#include "test.h" // PASS(), FAIL(), XPASS(), XFAIL() + +extern uint32_t _cpu_capabilities; + +extern const char* foo(); + +typedef const char* (*BarProc)(void); + +const char* myStr = "myStr"; + +int myInt; + +int main() +{ + if ( !_dyld_is_memory_immutable(myStr, 6) ) { + FAIL("_dyld_is_memory_immutable() returned false for string in main executable"); + return EXIT_SUCCESS; + } + + if ( _dyld_is_memory_immutable(strdup("hello"), 6) ) { + FAIL("_dyld_is_memory_immutable() returned true for result from strdup()"); + return EXIT_SUCCESS; + } + + if ( _dyld_is_memory_immutable(&myInt, 4) ) { + FAIL("_dyld_is_memory_immutable() returned true for global variabe in main executable"); + return EXIT_SUCCESS; + } + + if ( !_dyld_is_memory_immutable(foo(), 4) ) { + FAIL("_dyld_is_memory_immutable() returned false for string in statically linked dylib"); + return EXIT_SUCCESS; + } + + if ( !_dyld_is_memory_immutable(&strcpy, 4) ) { + FAIL("_dyld_is_memory_immutable() returned false for function in dyld shared cache"); + return EXIT_SUCCESS; + } + + if ( _dyld_is_memory_immutable(&_cpu_capabilities, 4) ) { + FAIL("_dyld_is_memory_immutable() returned true for global variabe in shared cache"); + return EXIT_SUCCESS; + } + + void* handle = dlopen("libbar.dylib", 0); + if ( handle == NULL ) { + FAIL("dlopen(libbar.dylib) failed"); + return EXIT_SUCCESS; + } + + BarProc proc = dlsym(handle, "bar"); + if ( proc == NULL ) { + FAIL("dlsym(bar) failed"); + return EXIT_SUCCESS; + } + const char* barStr = (*proc)(); + if ( _dyld_is_memory_immutable(barStr, 4) ) { + FAIL("_dyld_is_memory_immutable() returned true for string in unloadable dylib"); + return EXIT_SUCCESS; + } + + + PASS("_dyld_is_memory_immutable"); + return 0; +} diff --git a/unit-tests/test-cases/dyld_shared_cache_iterate_text/Makefile b/unit-tests/test-cases/dyld_shared_cache_iterate_text/Makefile new file mode 100644 index 0000000..8f07d7f --- /dev/null +++ b/unit-tests/test-cases/dyld_shared_cache_iterate_text/Makefile @@ -0,0 +1,37 @@ +## +# Copyright (c) 2015 Apple Inc. All rights reserved. +# +# @APPLE_LICENSE_HEADER_START@ +# +# This file contains Original Code and/or Modifications of Original Code +# as defined in and that are subject to the Apple Public Source License +# Version 2.0 (the 'License'). You may not use this file except in +# compliance with the License. Please obtain a copy of the License at +# http://www.opensource.apple.com/apsl/ and read it before using this +# file. +# +# The Original Code and all software distributed under the License are +# distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +# EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +# INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +# Please see the License for the specific language governing rights and +# limitations under the License. +# +# @APPLE_LICENSE_HEADER_END@ +## +TESTROOT = ../.. +include ${TESTROOT}/include/common.makefile + + + +all-check: all check + +check: + ./main + +all: + ${CC} ${CCFLAGS} -I${TESTROOT}/include -I../../../launch-cache/ -o main main.c + +clean: + ${RM} ${RMFLAGS} main diff --git a/unit-tests/test-cases/dyld_shared_cache_iterate_text/main.c b/unit-tests/test-cases/dyld_shared_cache_iterate_text/main.c new file mode 100644 index 0000000..228935b --- /dev/null +++ b/unit-tests/test-cases/dyld_shared_cache_iterate_text/main.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#include // EXIT_SUCCESS +#include +#include +#include +#include +#include +#include +#include + +#include "test.h" +#include "dyld_cache_format.h" + + + +int main() +{ + const struct dyld_all_image_infos* allInfo = _dyld_get_all_image_infos(); + if ( allInfo == NULL ) { + FAIL("dyld_shared_cache_iterate_text: _dyld_get_all_image_infos() failed"); + exit(0); + } + uuid_t curUuid; + memcpy(curUuid, allInfo->sharedCacheUUID, 16); + + int __block imageCount = 0; + int result = dyld_shared_cache_iterate_text(curUuid, ^(const dyld_shared_cache_dylib_text_info* info) { + ++imageCount; + //printf(" cur: 0x%09llX -> 0x%09llX %s\n", info->loadAddressUnslid, info->loadAddressUnslid + info->textSegmentSize, info->path); + }); + if ( result != 0 ) { + FAIL("dyld_shared_cache_iterate_text: dyld_shared_cache_iterate_text() failed"); + exit(0); + } + + if ( imageCount < 500 ) { + FAIL("dyld_shared_cache_iterate_text: dyld_shared_cache_iterate_text() iterated less than 500 dylibs (%d)", imageCount); + exit(0); + } +#if 0 + //uuid_t fixedUUID = {0x3E, 0xDE, 0x37, 0x05, 0x81, 0x68, 0x33, 0xEF, 0xBF, 0x97, 0xC0, 0xF6, 0xD2, 0x4D, 0x93, 0xEC}; + uuid_t fixedUUID = {0xD4, 0x3B, 0x31, 0x2B, 0xA5, 0xA7, 0x3C, 0x55, 0x90, 0xA0, 0x9A, 0x37, 0x60, 0x7D, 0x70, 0xAF}; + result = dyld_shared_cache_iterate_text(fixedUUID, ^(const dyld_shared_cache_dylib_text_info* info) { + printf(" my: 0x%09llX -> 0x%09llX %s\n", info->loadAddressUnslid, info->loadAddressUnslid + info->textSegmentSize, info->path); + }); +#endif + + PASS("dyld_shared_cache_iterate_text"); + return EXIT_SUCCESS; +} + + diff --git a/unit-tests/test-cases/image-state-change/main.c b/unit-tests/test-cases/image-state-change/main.c index 6ee6a62..3b636aa 100644 --- a/unit-tests/test-cases/image-state-change/main.c +++ b/unit-tests/test-cases/image-state-change/main.c @@ -35,7 +35,9 @@ static int singleMappedCount = 0; static int batchMappedCount = 0; static int singleUnMappedCount = 0; - +static int batchBoundCount = 0; +static int singleDepsInitedCount = 0; +static int singleBoundCount = 0; static const char* batchMappedHandler(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) { @@ -49,6 +51,18 @@ static const char* batchMappedHandler(enum dyld_image_states state, uint32_t inf return NULL; } +static const char* batchBoundHandler(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) +{ + for (uint32_t i=0; i < infoCount; ++i) + printf("batchBoundHandler(): %u/%u -> %s\n", i, infoCount, info[i].imageFilePath); + if ( state != dyld_image_state_bound ) { + FAIL("image-state-change: batchBoundHandler passed state %d", state); + exit(0); + } + batchBoundCount += infoCount; + return NULL; +} + static const char* singleMappedHandler(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) { printf("singleMappedHandler(%s)\n", info[0].imageFilePath); @@ -64,6 +78,41 @@ static const char* singleMappedHandler(enum dyld_image_states state, uint32_t in return NULL; } + +static const char* singleBoundHandler(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) +{ + printf("singleBoundHandler(%s)\n", info[0].imageFilePath); + if ( state != dyld_image_state_bound ) { + FAIL("image-state-change: singleBoundHandler passed state %d", state); + exit(0); + } + if ( infoCount != 1 ) { + FAIL("image-state-change: singleBoundHandler given %d images", infoCount); + exit(0); + } + ++singleBoundCount; + return NULL; +} + + + +static const char* singleDepsInitedHandler(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) +{ + printf("singleDepsInitedHandler(%s)\n", info[0].imageFilePath); + if ( state != dyld_image_state_dependents_initialized ) { + FAIL("image-state-change: singleDepsInitedHandler passed state %d", state); + exit(0); + } + if ( infoCount != 1 ) { + FAIL("image-state-change: singleDepsInitedHandler given %d images", infoCount); + exit(0); + } + ++singleDepsInitedCount; + return NULL; +} + + + static const char* singleUnmappedHandler(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) { printf("singleUnmappedHandler(%s)\n", info[0].imageFilePath); @@ -100,7 +149,11 @@ int main(int argc, const char* argv[]) // tell dyld we want to know when images are mapped dyld_register_image_state_change_handler(dyld_image_state_dependents_mapped, true, batchMappedHandler); dyld_register_image_state_change_handler(dyld_image_state_mapped, false, singleMappedHandler); + dyld_register_image_state_change_handler(dyld_image_state_bound, true, batchBoundHandler); + dyld_register_image_state_change_handler(dyld_image_state_bound, false, singleBoundHandler); + dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, false, singleDepsInitedHandler); dyld_register_image_state_change_handler(dyld_image_state_terminated, false, singleUnmappedHandler); + // with batch mode we get notified of existing images, but not with single mode, so re-sync counts batchMappedCount=0; -- 2.45.2