From: Apple Date: Tue, 26 Sep 2017 16:36:15 +0000 (+0000) Subject: dyld-519.2.1.tar.gz X-Git-Tag: macos-1013^0 X-Git-Url: https://git.saurik.com/apple/dyld.git/commitdiff_plain/10b92d3b56e938a5c1f5e708ddd1c5d0c2c807d1 dyld-519.2.1.tar.gz --- diff --git a/configs/closured.xcconfig b/configs/closured.xcconfig new file mode 100644 index 0000000..215e217 --- /dev/null +++ b/configs/closured.xcconfig @@ -0,0 +1,2 @@ + +CODE_SIGN_ENTITLEMENTS[sdk=embedded*] = dyld3/closured/closured_entitlements.plist diff --git a/configs/dyld.xcconfig b/configs/dyld.xcconfig index 13863b8..d1ec099 100644 --- a/configs/dyld.xcconfig +++ b/configs/dyld.xcconfig @@ -13,3 +13,9 @@ PRODUCT_NAME[sdk=iphoneos*] = dyld PRODUCT_NAME[sdk=macosx*] = dyld INSTALL_PATH = /usr/lib + +//:configuration = Debug +GCC_PREPROCESSOR_DEFINITIONS = DYLD_IN_PROCESS=1 DYLD_VERSION=$(RC_ProjectSourceVersion) BUILDING_DYLD=1 DEBUG=1 + +//:configuration = Release +GCC_PREPROCESSOR_DEFINITIONS = DYLD_IN_PROCESS=1 DYLD_VERSION=$(RC_ProjectSourceVersion) BUILDING_DYLD=1 diff --git a/configs/libdyld.xcconfig b/configs/libdyld.xcconfig index d3c6d1c..8f06e5a 100644 --- a/configs/libdyld.xcconfig +++ b/configs/libdyld.xcconfig @@ -1,8 +1,14 @@ -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 +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 -Wl,-upward-lcommonCrypto -Wl,-upward-lclosured +LIBSYSTEM_LIBS[sdk=embedded*] = -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 -Wl,-upward-lcommonCrypto -Wl,-upward-lclosured -Wl,-upward-lcompiler_rt +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 -Wl,-upward-lcommonCrypto -Wl,-upward-lclosured INSTALL_PATH = /usr/lib/system +//:configuration = Debug +GCC_PREPROCESSOR_DEFINITIONS = DYLD_IN_PROCESS=1 BUILDING_LIBDYLD=1 DEBUG=1 + +//:configuration = Release +GCC_PREPROCESSOR_DEFINITIONS = DYLD_IN_PROCESS=1 BUILDING_LIBDYLD=1 + diff --git a/configs/update_dyld_shared_cache.xcconfig b/configs/update_dyld_shared_cache.xcconfig index 1bed864..e45c714 100644 --- a/configs/update_dyld_shared_cache.xcconfig +++ b/configs/update_dyld_shared_cache.xcconfig @@ -1,18 +1,4 @@ -//:configuration = Release -LOCAL_YES = local -//:configuration = Release -LOCAL = $(LOCAL_$(RC_PURPLE)) - -//:configuration = Release -INSTALL_PATH = $(INSTALL_LOCATION)/usr/$(LOCAL)/bin - -// don't iOS tool -MY_RELEASE_CODE_SIGN_IDENTITY_YES = -MY_RELEASE_CODE_SIGN_IDENTITY_ = - -CODE_SIGN_IDENTITY = $(MY_RELEASE_CODE_SIGN_IDENTITY_$(RC_PURPLE)) - - -CODE_SIGN_ENTITLEMENTS = launch-cache/update_dyld_shared_cache_entitlements.plist +CODE_SIGN_ENTITLEMENTS = dyld3/shared-cache/update_dyld_shared_cache_entitlements.plist diff --git a/configs/update_dyld_sim_shared_cache.xcconfig b/configs/update_dyld_sim_shared_cache.xcconfig new file mode 100644 index 0000000..e4f7bfa --- /dev/null +++ b/configs/update_dyld_sim_shared_cache.xcconfig @@ -0,0 +1,3 @@ + +#include "/AppleInternal/XcodeConfig/PlatformSupportHost.xcconfig" + diff --git a/doc/man/man1/closured.1 b/doc/man/man1/closured.1 new file mode 100644 index 0000000..a13b005 --- /dev/null +++ b/doc/man/man1/closured.1 @@ -0,0 +1,10 @@ +.Dd 3/1/17 +.Dt closured 1 +.Os Darwin +.Sh NAME +.Nm closured +.Nd Daemon for building dyld closures. +.Sh SYNOPSIS +.Nm closured is a launchd managed daemon. +.Sh DESCRIPTION +.Nm closured is a launchd managed daemon. diff --git a/doc/man/man1/dyld.1 b/doc/man/man1/dyld.1 index ad1cec3..3c58b80 100644 --- a/doc/man/man1/dyld.1 +++ b/doc/man/man1/dyld.1 @@ -1,6 +1,6 @@ -.TH DYLD 1 "December 14, 2009" "Apple Inc." +.TH DYLD 1 "June 1, 2017" "Apple Inc." .SH NAME -dyld \- the dynamic link editor +dyld \- the dynamic linker .SH SYNOPSIS DYLD_FRAMEWORK_PATH .br @@ -30,8 +30,6 @@ DYLD_PRINT_ENV .br DYLD_PRINT_LIBRARIES .br -DYLD_PRINT_LIBRARIES_POST_LAUNCH -.br DYLD_BIND_AT_LAUNCH .br DYLD_DISABLE_DOFS @@ -56,8 +54,12 @@ DYLD_SHARED_CACHE_DIR .br DYLD_SHARED_CACHE_DONT_VALIDATE .SH DESCRIPTION -The dynamic linker uses the following environment variables. -They affect any program that uses the dynamic linker. +The dynamic linker checks the following environment variables during the launch +of each process. +.br +.br +Note: If System Integrity Protection is enabled, these environment variables are ignored +when executing binaries protected by System Integrity Protection. .TP .B DYLD_FRAMEWORK_PATH This is a colon separated list of directories that contain frameworks. @@ -197,11 +199,6 @@ This is useful to make sure that the use of .SM DYLD_LIBRARY_PATH is getting what you want. .TP -.B DYLD_PRINT_LIBRARIES_POST_LAUNCH -This does the same as -.SM DYLD_PRINT_LIBRARIES -but the printing starts after the program gets to its entry point. -.TP .B DYLD_BIND_AT_LAUNCH When this is set, the dynamic linker binds all undefined symbols the program needs at launch time. This includes function symbols that can are normally @@ -300,4 +297,4 @@ with -rpath @loader_path/zzz, where zzz is the path from the executable to the a At runtime dyld sets it run path to be the anchor point, then each dylib is found relative to the anchor point. .SH "SEE ALSO" -libtool(1), ld(1), otool(1) +dyldinfo(1), ld(1), otool(1) diff --git a/doc/man/man1/update_dyld_shared_cache.1 b/doc/man/man1/update_dyld_shared_cache.1 index 7161fc5..f1c2330 100644 --- a/doc/man/man1/update_dyld_shared_cache.1 +++ b/doc/man/man1/update_dyld_shared_cache.1 @@ -1,4 +1,4 @@ -.Dd June 23, 2016 +.Dd June 1, 2017 .Dt update_dyld_shared_cache 1 .Os Darwin .Sh NAME @@ -12,7 +12,6 @@ .Op Fl force .Op Fl debug .Op Fl universal_boot -.Op Fl verify .Sh DESCRIPTION .Nm update_dyld_shared_cache ensures that dyld's shared cache is up-to-date. This tool is normally @@ -23,9 +22,6 @@ used another mechanism to alter an OS dylib, you should manually run .Pp Note that the new cache does not take effect until the OS is rebooted. .Pp -If a safe-boot is -done (booting with shift key held down) the cache is deleted. -.Pp The dyld shared cache is mapped by dyld into a process at launch time. Later, when loading any mach-o image, dyld will first check if is in the share cache, and if @@ -34,11 +30,8 @@ the original file. This results in significant performance improvements to launch time. .Pp .Nm update_dyld_shared_cache -scans the directory /var/db/dyld/shared_region_roots for text files containing paths to -mach-o executables. The full dependencies of all dylibs required by those executables is -used to determine which libraries are commonly used and should be placed in the -shared cache. If one of the text files contains a path to a dylib, that dylib and its -dependents will be forced into the cache. +scans the directory /System/Library/Receipts/ for .bom files which list all files +installed. From that info it creates the set of OS dylibs to build into the dyld cache. .Pp .Nm update_dyld_shared_cache builds a separate cache file for each architecture. The cache files and a readable text @@ -77,14 +70,6 @@ to regenerated the shared cache files even if they appear to be already up-to-da This option prints out additional information about the work being done. .It Fl universal_boot 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. .El -.Sh FILES -.Tp -/var/db/dyld/shared_region_roots -directory of text files with paths to mach-o images used to determine what should be in shared cache. .Sh SEE ALSO .Xr dyld 1 diff --git a/dyld.xcodeproj/project.pbxproj b/dyld.xcodeproj/project.pbxproj index 49d05ff..121b7fa 100644 --- a/dyld.xcodeproj/project.pbxproj +++ b/dyld.xcodeproj/project.pbxproj @@ -11,14 +11,13 @@ isa = PBXAggregateTarget; buildConfigurationList = 37A0AD0B1C15FFF500731E50 /* Build configuration list for PBXAggregateTarget "update_dyld_shared_cache" */; buildPhases = ( + F94182D61E60E74E00D8EF25 /* pre-platform builds */, ); dependencies = ( - 37A0AD1B1C16004600731E50 /* PBXTargetDependency */, - 37A0AD131C16003600731E50 /* PBXTargetDependency */, - 37A0AD151C16003600731E50 /* PBXTargetDependency */, - 37A0AD171C16003600731E50 /* PBXTargetDependency */, - 37A0AD191C16003600731E50 /* PBXTargetDependency */, - 37A0AD111C16003600731E50 /* PBXTargetDependency */, + D8668AD01ECE335F005E7D31 /* PBXTargetDependency */, + F94182D81E60F0BE00D8EF25 /* PBXTargetDependency */, + F94182DA1E60F0C000D8EF25 /* PBXTargetDependency */, + F94182DC1E60F16900D8EF25 /* PBXTargetDependency */, ); name = update_dyld_shared_cache; productName = update_dyld_shared_cache; @@ -27,13 +26,9 @@ isa = PBXAggregateTarget; buildConfigurationList = F908135211D3ED9000626CC1 /* Build configuration list for PBXAggregateTarget "libdyld" */; buildPhases = ( - F908135111D3ED9000626CC1 /* usr|include|mach-o */, - F908137011D3FB5000626CC1 /* usr|include */, F9C69EFC14EC8AB8009CAE2E /* usr|local|include */, - F908137111D3FB5000626CC1 /* usr|local|include|mach-o */, F908137211D3FB5000626CC1 /* usr|share|man|man1 */, F908137311D3FB5000626CC1 /* usr|share|man|man3 */, - F96D19701D7F62C3007AF3CE /* Install dyld_priv.h */, ); dependencies = ( F9B4D78012AD9736000605A6 /* PBXTargetDependency */, @@ -70,56 +65,46 @@ /* 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 */; }; + 37554F3B1E3F0FD200407388 /* Manifest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37908A281E3A853E009613FA /* Manifest.mm */; }; + 37554F3C1E3F0FD200407388 /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + 37554F3D1E3F0FD200407388 /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + 37554F3E1E3F0FD200407388 /* multi_dyld_shared_cache_builder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37908A291E3A853E009613FA /* multi_dyld_shared_cache_builder.mm */; }; + 37554F3F1E3F165100407388 /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + 37554F401E3F167A00407388 /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; + 37554F411E3F169500407388 /* CacheBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921C1DC3F86C00CBEDE6 /* CacheBuilder.cpp */; }; + 37554F421E3F169600407388 /* CacheBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921C1DC3F86C00CBEDE6 /* CacheBuilder.cpp */; }; + 37554F431E3F16A800407388 /* OptimizerObjC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692131DC3EF6C00CBEDE6 /* OptimizerObjC.cpp */; }; + 37554F441E3F16A900407388 /* OptimizerObjC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692131DC3EF6C00CBEDE6 /* OptimizerObjC.cpp */; }; + 37554F451E3F16B500407388 /* OptimizerLinkedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692121DC3EF6C00CBEDE6 /* OptimizerLinkedit.cpp */; }; + 37554F461E3F16B600407388 /* OptimizerLinkedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692121DC3EF6C00CBEDE6 /* OptimizerLinkedit.cpp */; }; + 37554F471E3F16B900407388 /* OptimizerBranches.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692111DC3EF6C00CBEDE6 /* OptimizerBranches.cpp */; }; + 37554F481E3F16BA00407388 /* OptimizerBranches.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692111DC3EF6C00CBEDE6 /* OptimizerBranches.cpp */; }; + 37554F491E3F76E400407388 /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + 37554F4A1E3F76E800407388 /* AdjustDylibSegments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692091DC3EF6C00CBEDE6 /* AdjustDylibSegments.cpp */; }; + 37554F4B1E3F76E900407388 /* AdjustDylibSegments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692091DC3EF6C00CBEDE6 /* AdjustDylibSegments.cpp */; }; + 37554F511E3F78EB00407388 /* ImageProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F963546B1DD8F2A800895049 /* ImageProxy.cpp */; }; + 37554F521E3F78EB00407388 /* ImageProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F963546B1DD8F2A800895049 /* ImageProxy.cpp */; }; + 37554F531E3F7B1E00407388 /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + 37554F541E3F7B1F00407388 /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + 37554F551E3F7B4200407388 /* LaunchCacheWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */; }; + 37554F561E3F7B4300407388 /* LaunchCacheWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */; }; + 37554F571E3F7B6400407388 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; + 37554F581E3F7B6500407388 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.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 */; }; - F908135D11D3FACD00626CC1 /* dyld-interposing.h in usr|local|include|mach-o */ = {isa = PBXBuildFile; fileRef = F918691408B16D2500E0F9DB /* dyld-interposing.h */; }; - F908135E11D3FACD00626CC1 /* dyld_cache_format.h in usr|local|include|mach-o */ = {isa = PBXBuildFile; fileRef = F93937400A94FC4700070A07 /* dyld_cache_format.h */; }; - F908135F11D3FACD00626CC1 /* dyld_gdb.h in usr|local|include|mach-o */ = {isa = PBXBuildFile; fileRef = F9ED4CE80630A80600DF4E74 /* dyld_gdb.h */; }; + 37908A2E1E3A8632009613FA /* Manifest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37908A281E3A853E009613FA /* Manifest.mm */; }; + 37908A2F1E3A864E009613FA /* dyld_shared_cache_builder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37908A271E3A853E009613FA /* dyld_shared_cache_builder.mm */; }; + 37908A301E3ADD15009613FA /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + 37908A311E3EB585009613FA /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; + 37908A321E3ED667009613FA /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + 37C5C2FD1E5CD154006B32C9 /* BuilderUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C5C2FB1E5CD154006B32C9 /* BuilderUtils.mm */; }; + 37C5C2FE1E5CD154006B32C9 /* BuilderUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C5C2FB1E5CD154006B32C9 /* BuilderUtils.mm */; }; + 37C5C2FF1E60D7DE006B32C9 /* OptimizerBranches.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692111DC3EF6C00CBEDE6 /* OptimizerBranches.cpp */; }; + 37D7DB001E96F0ED00D52CEA /* Tracing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 37D7DAFE1E96F0ED00D52CEA /* Tracing.cpp */; }; + 37D7DB011E96F3EB00D52CEA /* Tracing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 37D7DAFE1E96F0ED00D52CEA /* Tracing.cpp */; }; + F90108611E2AD96000870568 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; F908136411D3FB0300626CC1 /* dyld.1 in usr|share|man|man1 */ = {isa = PBXBuildFile; fileRef = EF799FE9070D27BB00F78484 /* dyld.1 */; }; F908136811D3FB3A00626CC1 /* dladdr.3 in usr|share|man|man3 */ = {isa = PBXBuildFile; fileRef = EF799FEB070D27BB00F78484 /* dladdr.3 */; }; F908136911D3FB3A00626CC1 /* dlclose.3 in usr|share|man|man3 */ = {isa = PBXBuildFile; fileRef = EF799FEC070D27BB00F78484 /* dlclose.3 */; }; @@ -128,29 +113,128 @@ F908136C11D3FB3A00626CC1 /* dlsym.3 in usr|share|man|man3 */ = {isa = PBXBuildFile; fileRef = EF799FEF070D27BB00F78484 /* dlsym.3 */; }; 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 */; }; + F90F7C081E9C6B8B00535722 /* libCrashReporterClient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F90F7C071E9C6B8B00535722 /* libCrashReporterClient.a */; }; F913FADA0630A8AE00B7AE9D /* dyldAPIsInLibSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F913FAD90630A8AE00B7AE9D /* dyldAPIsInLibSystem.cpp */; }; + F92015701DDFEBAF00816A4A /* Bom.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37F7A5961BB363820039043A /* Bom.framework */; }; + F92015711DE3F3B000816A4A /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + F922AE581EF0D3C300926F9D /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; + F922AE591EF0DBA500926F9D /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; + F922AE5A1EF0DC7200926F9D /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; + F922C81C1F33B88400D8F479 /* libclosured-stub.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F922C81B1F33B86300D8F479 /* libclosured-stub.cpp */; }; + F926C0471DDBFB7A00941CB1 /* ImageProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F963546B1DD8F2A800895049 /* ImageProxy.cpp */; }; F9280B7B1AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9280B791AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp */; }; + F94182D51E60A2F100D8EF25 /* OptimizerBranches.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692111DC3EF6C00CBEDE6 /* OptimizerBranches.cpp */; }; + F9460DCD1E09FFFC00FEC613 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; + F9460DCE1E0A000600FEC613 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; + F94942B31E6796D70019AE08 /* closured.1 in install man page */ = {isa = PBXBuildFile; fileRef = F94942B21E6796D40019AE08 /* closured.1 */; }; + F94C22251E513CA90079E5DD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F94C22241E513CA90079E5DD /* CoreFoundation.framework */; }; 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, ); }; }; + F960A78A1E40569400840176 /* dyld-interposing.h in Headers */ = {isa = PBXBuildFile; fileRef = F918691408B16D2500E0F9DB /* dyld-interposing.h */; settings = {ATTRIBUTES = (Private, ); }; }; + F960A78B1E405DE300840176 /* dyld_cache_format.h in Headers */ = {isa = PBXBuildFile; fileRef = F986921E1DC3F86C00CBEDE6 /* dyld_cache_format.h */; settings = {ATTRIBUTES = (Private, ); }; }; + F96354331DCD74A400895049 /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + F96354341DCD74A400895049 /* CacheBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921C1DC3F86C00CBEDE6 /* CacheBuilder.cpp */; }; + F96354351DCD74A400895049 /* AdjustDylibSegments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692091DC3EF6C00CBEDE6 /* AdjustDylibSegments.cpp */; }; + F96354361DCD74A400895049 /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + F96354371DCD74A400895049 /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + F96354381DCD74A400895049 /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; + F96354391DCD74A400895049 /* OptimizerObjC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692131DC3EF6C00CBEDE6 /* OptimizerObjC.cpp */; }; + F963543A1DCD74A400895049 /* LaunchCacheWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */; }; + F963543B1DCD74A400895049 /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + F963543C1DCD74A400895049 /* OptimizerLinkedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692121DC3EF6C00CBEDE6 /* OptimizerLinkedit.cpp */; }; + F96354461DCD74BC00895049 /* update_dyld_sim_shared_cache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F963542E1DCD736000895049 /* update_dyld_sim_shared_cache.cpp */; }; + F963546C1DD8F38300895049 /* ImageProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F963546B1DD8F2A800895049 /* ImageProxy.cpp */; }; + F96D19A81D93661A007AF3CE /* APIs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19A51D9363D6007AF3CE /* APIs.cpp */; }; + F96D19BF1D94A6DC007AF3CE /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; + F96D19C01D94BFCE007AF3CE /* AllImages.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19A61D9363D6007AF3CE /* AllImages.cpp */; }; + F977DDCB1E53BF5500609230 /* SharedCacheRuntime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F977DDC91E53BEA700609230 /* SharedCacheRuntime.cpp */; }; + F97C619F1D9829AA00A84CD7 /* libdyldEntryVector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F97C619E1D98292700A84CD7 /* libdyldEntryVector.cpp */; }; + F97C61A21D9CAE3500A84CD7 /* Logging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F97C61A01D9CA6B800A84CD7 /* Logging.cpp */; }; + F97C61B31DBAE14200A84CD7 /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; F97FF3611C23640C000ACDD2 /* nocr.c in Sources */ = {isa = PBXBuildFile; fileRef = F97FF35F1C236402000ACDD2 /* nocr.c */; }; F97FF3641C237F68000ACDD2 /* nocr.1 in install man page */ = {isa = PBXBuildFile; fileRef = F97FF3631C237F5C000ACDD2 /* nocr.1 */; }; + F981C8BD1EEF447500452F35 /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; + F981C8BE1EEF733800452F35 /* ClosureBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9E5E2C41EB00A870013A0BB /* ClosureBuffer.cpp */; }; + F981C8BF1EEF733C00452F35 /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; + F981C8C01EEF7D4100452F35 /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; + F981C8C11EF06A7800452F35 /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; + F98692171DC3EFD500CBEDE6 /* update_dyld_shared_cache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692151DC3EF6C00CBEDE6 /* update_dyld_shared_cache.cpp */; }; + F98692181DC3EFD700CBEDE6 /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + F98692191DC3EFDA00CBEDE6 /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + F986921F1DC3F98700CBEDE6 /* CacheBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921C1DC3F86C00CBEDE6 /* CacheBuilder.cpp */; }; + F98692201DC3F99300CBEDE6 /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + F98692211DC401B900CBEDE6 /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; + F98692231DC403F900CBEDE6 /* AdjustDylibSegments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692091DC3EF6C00CBEDE6 /* AdjustDylibSegments.cpp */; }; + F988F0BB1E2FDF5B003AED79 /* execserver.defs in Sources */ = {isa = PBXBuildFile; fileRef = F988F0BA1E2FDF5B003AED79 /* execserver.defs */; settings = {ATTRIBUTES = (Server, ); }; }; F98C78F00F7C02E8006257D2 /* dsc_iterator.h in usr|local|include|mach-o */ = {isa = PBXBuildFile; fileRef = F9F2A56F0F7AEEE300B7C9EB /* dsc_iterator.h */; }; + F98F1FBD1E4029E400EF868D /* dyld_priv.h in Headers */ = {isa = PBXBuildFile; fileRef = F9ED4CE90630A80600DF4E74 /* dyld_priv.h */; settings = {ATTRIBUTES = (Private, ); }; }; + F98F1FBF1E4031F800EF868D /* dyld_process_info.h in Headers */ = {isa = PBXBuildFile; fileRef = F95090D01C5AB89A0031F81D /* dyld_process_info.h */; settings = {ATTRIBUTES = (Private, ); }; }; + F99006DD1E411BA70013456D /* dyld_images.h in Headers */ = {isa = PBXBuildFile; fileRef = F98D274C0AA79D7400416316 /* dyld_images.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F99006DE1E411BBC0013456D /* dyld.h in Headers */ = {isa = PBXBuildFile; fileRef = F9ED4CEA0630A80600DF4E74 /* dyld.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F99006E01E4130AE0013456D /* dyld_gdb.h in Headers */ = {isa = PBXBuildFile; fileRef = F9ED4CE80630A80600DF4E74 /* dyld_gdb.h */; settings = {ATTRIBUTES = (Private, ); }; }; 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 */; }; F9A221E70F3A6D7C00D15F73 /* dyldLibSystemGlue.c in Sources */ = {isa = PBXBuildFile; fileRef = F9A221E60F3A6D7C00D15F73 /* dyldLibSystemGlue.c */; }; + F9A548B31DDBBC75002B4422 /* ImageProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F963546B1DD8F2A800895049 /* ImageProxy.cpp */; }; F9A6D6E4116F9DF20051CC16 /* threadLocalVariables.c in Sources */ = {isa = PBXBuildFile; fileRef = F9A6D6E2116F9DF20051CC16 /* threadLocalVariables.c */; }; F9A6D70C116FBBD10051CC16 /* threadLocalHelpers.s in Sources */ = {isa = PBXBuildFile; fileRef = F9A6D70B116FBBD10051CC16 /* threadLocalHelpers.s */; }; + F9AB02C31F329FE000EE96C4 /* ImageProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F963546B1DD8F2A800895049 /* ImageProxy.cpp */; }; + F9AB02C41F329FF400EE96C4 /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + F9AB02C51F329FFE00EE96C4 /* LaunchCacheWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */; }; + F9AB02C61F32A1F400EE96C4 /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + F9AB02C71F32A22B00EE96C4 /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + F9AB02C81F32A23B00EE96C4 /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; + F9AB02C91F32A24B00EE96C4 /* ClosureBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9E5E2C41EB00A870013A0BB /* ClosureBuffer.cpp */; }; + F9AB02CA1F32A25F00EE96C4 /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; + F9AB02CB1F32A26700EE96C4 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; + F9AB02CC1F32A33C00EE96C4 /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + F9ABA06E1E289B72000F21B4 /* closuredProtocol.defs in Sources */ = {isa = PBXBuildFile; fileRef = F9DDEDAB1E28787900A753DC /* closuredProtocol.defs */; }; + F9B3CAEA1EE20FE200C9A48B /* ClosureBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9E5E2C41EB00A870013A0BB /* ClosureBuffer.cpp */; }; + F9B3CAEC1EEB5CFB00C9A48B /* DyldCacheParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */; }; F9BA514B0ECE4F4200D1D62E /* dyld_stub_binder.s in Sources */ = {isa = PBXBuildFile; fileRef = F99EFC0D0EAD60E8001032B8 /* dyld_stub_binder.s */; }; + F9C15A4A1E1F7DAC0006E570 /* APIs_macOS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9C15A491E1F7D960006E570 /* APIs_macOS.cpp */; }; + F9C275571DA5D67F007A5D8A /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; + F9C2755A1DA71CE8007A5D8A /* Loading.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9C275581DA71A13007A5D8A /* Loading.cpp */; }; + F9C2755B1DA73EA1007A5D8A /* Loading.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9C275581DA71A13007A5D8A /* Loading.cpp */; }; F9C69EFE14EC8AD2009CAE2E /* objc-shared-cache.h in usr|local|include */ = {isa = PBXBuildFile; fileRef = F9C69EFD14EC8ABF009CAE2E /* objc-shared-cache.h */; }; + F9C73AC21E2992730025C89E /* closuredProtocol.defs in Sources */ = {isa = PBXBuildFile; fileRef = F9DDEDAB1E28787900A753DC /* closuredProtocol.defs */; }; + F9C86B651E2B16C600FD8669 /* closuredProtocol.defs in Sources */ = {isa = PBXBuildFile; fileRef = F9DDEDAB1E28787900A753DC /* closuredProtocol.defs */; }; F9CE307A1208F1B50098B590 /* dsc_extractor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9CE30781208F1B50098B590 /* dsc_extractor.cpp */; }; F9CE307B1208F1C60098B590 /* dsc_extractor.h in usr|local|include|mach-o */ = {isa = PBXBuildFile; fileRef = F9CE30791208F1B50098B590 /* dsc_extractor.h */; }; F9D1001814D8D13D00099D91 /* dsc_extractor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9CE30781208F1B50098B590 /* dsc_extractor.cpp */; }; F9D1001D14D8D19500099D91 /* dsc_iterator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */; }; F9D238DB0A9E2FD0002B55C7 /* update_dyld_shared_cache.1 in usr|share|man|man1 */ = {isa = PBXBuildFile; fileRef = F9D238D90A9E19A0002B55C7 /* update_dyld_shared_cache.1 */; }; F9D49CCC1458B95200F86ADD /* start_glue.s in Sources */ = {isa = PBXBuildFile; fileRef = F9D49CCB1458B95200F86ADD /* start_glue.s */; }; + F9D8623F1DC41043000A199A /* OptimizerLinkedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692121DC3EF6C00CBEDE6 /* OptimizerLinkedit.cpp */; }; + F9D862401DC57A27000A199A /* OptimizerObjC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692131DC3EF6C00CBEDE6 /* OptimizerObjC.cpp */; }; + F9D862411DC65A4E000A199A /* LaunchCacheWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */; }; + F9D862421DC65A53000A199A /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + F9D862431DC90A4F000A199A /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + F9D862451DC975A5000A199A /* dyld_closure_util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9D862441DC9759C000A199A /* dyld_closure_util.cpp */; }; + F9D862461DC975AA000A199A /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + F9D862471DC975B1000A199A /* LaunchCacheWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */; }; + F9D862481DC975B3000A199A /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + F9D8624B1DC976E4000A199A /* LaunchCachePrinter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692051DC3EF4800CBEDE6 /* LaunchCachePrinter.cpp */; }; + F9D8624C1DC97717000A199A /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + F9D8624D1DC9783E000A199A /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + F9D8624E1DCBD06A000A199A /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + F9D8624F1DCBD318000A199A /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + F9D862501DCBD31D000A199A /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + F9D862511DCBD330000A199A /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + F9DDEDB91E2878EC00A753DC /* closured.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DDEDAA1E28787900A753DC /* closured.cpp */; }; + F9DDEDBA1E2878F100A753DC /* closuredProtocol.defs in Sources */ = {isa = PBXBuildFile; fileRef = F9DDEDAB1E28787900A753DC /* closuredProtocol.defs */; settings = {ATTRIBUTES = (Server, ); }; }; + F9DDEDBB1E287C9500A753DC /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + F9DDEDBC1E287CA100A753DC /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + F9DDEDBD1E287CB100A753DC /* LaunchCacheWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */; }; + F9DDEDBE1E287CF600A753DC /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + F9DDEDBF1E287CF600A753DC /* ImageProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F963546B1DD8F2A800895049 /* ImageProxy.cpp */; }; + F9DDEDC01E287CF600A753DC /* LaunchCacheReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */; }; + F9DDEDC11E287D3C00A753DC /* MachOParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */; }; + F9DDEDC21E287D8A00A753DC /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; + F9E5E2C61EB00A9F0013A0BB /* ClosureBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9E5E2C41EB00A870013A0BB /* ClosureBuffer.cpp */; }; + F9E5E2C71EB00AAA0013A0BB /* ClosureBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9E5E2C41EB00A870013A0BB /* ClosureBuffer.cpp */; }; F9ED4CD60630A7F100DF4E74 /* dyld_gdb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CC60630A7F100DF4E74 /* dyld_gdb.cpp */; }; F9ED4CD70630A7F100DF4E74 /* dyld.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CC70630A7F100DF4E74 /* dyld.cpp */; }; F9ED4CD90630A7F100DF4E74 /* dyldAPIs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CC90630A7F100DF4E74 /* dyldAPIs.cpp */; }; @@ -164,7 +248,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 */; }; + F9F76FB01E09CDF400828678 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -221,54 +305,61 @@ remoteGlobalIDString = 37A0AD0A1C15FFF500731E50; remoteInfo = update_dyld_shared_cache; }; - 37A0AD101C16003600731E50 /* PBXContainerItemProxy */ = { + D8668ACF1ECE335F005E7D31 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F93937310A94FAF700070A07; - remoteInfo = update_dyld_shared_cache_tool; + remoteGlobalIDString = F97C61A61DBAD1A900A84CD7; + remoteInfo = dyld_closure_util; }; - 37A0AD121C16003600731E50 /* PBXContainerItemProxy */ = { + F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F9F2A5580F7AEE9800B7C9EB; - remoteInfo = libdsc; + remoteGlobalIDString = F9ED4C9E0630A76B00DF4E74; + remoteInfo = libdyld.dylib; }; - 37A0AD141C16003600731E50 /* PBXContainerItemProxy */ = { + F922C8111F33B62700D8F479 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F9D1001114D8D0BA00099D91; - remoteInfo = dsc_extractor; + remoteGlobalIDString = F9AB02B71F329FAA00EE96C4; + remoteInfo = libclosured; }; - 37A0AD161C16003600731E50 /* PBXContainerItemProxy */ = { + F922C81D1F33B96300D8F479 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = 377685F21AC4B27D00026E6C; - remoteInfo = multi_dyld_shared_cache_builder; + remoteGlobalIDString = F922C8161F33B73800D8F479; + remoteInfo = "libclosured-stub"; }; - 37A0AD181C16003600731E50 /* PBXContainerItemProxy */ = { + F94182D71E60F0BE00D8EF25 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = 3703A1111B38C1B300ADBA7F; - remoteInfo = dyld_shared_cache_builder; + remoteGlobalIDString = F99B8E550FEC10F600701838; + remoteInfo = dyld_shared_cache_util; }; - 37A0AD1A1C16004600731E50 /* PBXContainerItemProxy */ = { + F94182D91E60F0C000D8EF25 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F99B8E550FEC10F600701838; - remoteInfo = dyld_shared_cache_util; + remoteGlobalIDString = F9F2A5580F7AEE9800B7C9EB; + remoteInfo = libdsc; }; - F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */ = { + F94182DB1E60F16900D8EF25 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; proxyType = 1; - remoteGlobalIDString = F9ED4C9E0630A76B00DF4E74; - remoteInfo = libdyld.dylib; + remoteGlobalIDString = F9D1001114D8D0BA00099D91; + remoteInfo = dsc_extractor; + }; + F96543A01E343601003C5540 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F97C61A61DBAD1A900A84CD7; + remoteInfo = dyld_closure_util; }; F97FF3651C237F97000ACDD2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -326,43 +417,6 @@ ); runOnlyForDeploymentPostprocessing = 1; }; - F908135111D3ED9000626CC1 /* usr|include|mach-o */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 8; - dstPath = "$(INSTALL_PATH_PREFIX)/usr/include/mach-o"; - dstSubfolderSpec = 0; - files = ( - F908134C11D3ED6200626CC1 /* dyld.h in usr|include|mach-o */, - F908134D11D3ED6200626CC1 /* dyld_images.h in usr|include|mach-o */, - ); - name = "usr|include|mach-o"; - runOnlyForDeploymentPostprocessing = 1; - }; - F908137011D3FB5000626CC1 /* usr|include */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 8; - dstPath = "$(INSTALL_PATH_PREFIX)/usr/include"; - dstSubfolderSpec = 0; - files = ( - F908135911D3FA8700626CC1 /* dlfcn.h in usr|include */, - ); - name = "usr|include"; - runOnlyForDeploymentPostprocessing = 1; - }; - F908137111D3FB5000626CC1 /* usr|local|include|mach-o */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 8; - dstPath = "$(INSTALL_PATH_PREFIX)/usr/local/include/mach-o"; - dstSubfolderSpec = 0; - 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 */, - ); - name = "usr|local|include|mach-o"; - runOnlyForDeploymentPostprocessing = 1; - }; F908137211D3FB5000626CC1 /* usr|share|man|man1 */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; @@ -391,6 +445,27 @@ name = "usr|share|man|man3"; runOnlyForDeploymentPostprocessing = 1; }; + F94942B11E67965C0019AE08 /* install man page */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1; + dstSubfolderSpec = 0; + files = ( + F94942B31E6796D70019AE08 /* closured.1 in install man page */, + ); + name = "install man page"; + runOnlyForDeploymentPostprocessing = 1; + }; + F97C61A51DBAD1A900A84CD7 /* Copy Files */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + name = "Copy Files"; + runOnlyForDeploymentPostprocessing = 1; + }; F97FF3541C23638F000ACDD2 /* install man page */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -440,21 +515,19 @@ /* Begin PBXFileReference section */ 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; }; + 37908A271E3A853E009613FA /* dyld_shared_cache_builder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = dyld_shared_cache_builder.mm; path = "dyld3/shared-cache/dyld_shared_cache_builder.mm"; sourceTree = ""; }; + 37908A281E3A853E009613FA /* Manifest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = Manifest.mm; path = "dyld3/shared-cache/Manifest.mm"; sourceTree = ""; }; + 37908A291E3A853E009613FA /* multi_dyld_shared_cache_builder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = multi_dyld_shared_cache_builder.mm; path = "dyld3/shared-cache/multi_dyld_shared_cache_builder.mm"; sourceTree = ""; }; + 37908A2A1E3A85A4009613FA /* FileAbstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = FileAbstraction.hpp; path = "dyld3/shared-cache/FileAbstraction.hpp"; sourceTree = ""; }; + 37908A2B1E3A85A4009613FA /* MachOFileAbstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MachOFileAbstraction.hpp; path = "dyld3/shared-cache/MachOFileAbstraction.hpp"; sourceTree = ""; }; + 37908A2C1E3A85A4009613FA /* Manifest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Manifest.h; path = "dyld3/shared-cache/Manifest.h"; sourceTree = ""; }; + 37908A2D1E3A85A4009613FA /* Trie.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Trie.hpp; path = "dyld3/shared-cache/Trie.hpp"; sourceTree = ""; }; + 37C5C2FB1E5CD154006B32C9 /* BuilderUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BuilderUtils.mm; path = "dyld3/shared-cache/BuilderUtils.mm"; sourceTree = ""; }; + 37C5C2FC1E5CD154006B32C9 /* BuilderUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BuilderUtils.h; path = "dyld3/shared-cache/BuilderUtils.h"; sourceTree = ""; }; + 37D7DAFE1E96F0ED00D52CEA /* Tracing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tracing.cpp; path = dyld3/Tracing.cpp; sourceTree = ""; }; + 37D7DAFF1E96F0ED00D52CEA /* Tracing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Tracing.h; path = dyld3/Tracing.h; sourceTree = ""; }; 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; }; @@ -463,9 +536,13 @@ EF799FEE070D27BB00F78484 /* dlopen.3 */ = {isa = PBXFileReference; explicitFileType = text.man; fileEncoding = 30; name = dlopen.3; path = doc/man/man3/dlopen.3; sourceTree = SOURCE_ROOT; }; EF799FEF070D27BB00F78484 /* dlsym.3 */ = {isa = PBXFileReference; explicitFileType = text.man; fileEncoding = 30; name = dlsym.3; path = doc/man/man3/dlsym.3; sourceTree = SOURCE_ROOT; }; EF799FF0070D27BB00F78484 /* dyld.3 */ = {isa = PBXFileReference; explicitFileType = text.man; fileEncoding = 30; name = dyld.3; path = doc/man/man3/dyld.3; sourceTree = SOURCE_ROOT; }; - F90C540A1A82D78000558E8C /* update_dyld_shared_cache_entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = update_dyld_shared_cache_entitlements.plist; sourceTree = ""; }; + F902031F1DEE83C000AC3F76 /* StringUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StringUtils.h; path = "dyld3/shared-cache/StringUtils.h"; sourceTree = ""; }; + F90F7C071E9C6B8B00535722 /* libCrashReporterClient.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libCrashReporterClient.a; path = usr/local/lib/libCrashReporterClient.a; sourceTree = SDKROOT; }; + F913C8501E9312A100458AA3 /* com.apple.dyld.closured.sb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = com.apple.dyld.closured.sb; path = dyld3/closured/com.apple.dyld.closured.sb; 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 = ""; }; + F922C8171F33B73800D8F479 /* libclosured.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libclosured.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + F922C81B1F33B86300D8F479 /* libclosured-stub.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "libclosured-stub.cpp"; path = "dyld3/libclosured-stub.cpp"; 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; }; @@ -474,7 +551,9 @@ F93937400A94FC4700070A07 /* dyld_cache_format.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = dyld_cache_format.h; sourceTree = ""; }; 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 = ""; }; + F94182DE1E60FFDC00D8EF25 /* update_dyld_sim_shared_cache.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = update_dyld_sim_shared_cache.xcconfig; sourceTree = ""; }; + F94942B21E6796D40019AE08 /* closured.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = closured.1; sourceTree = ""; }; + F94C22241E513CA90079E5DD /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 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 = ""; }; @@ -482,29 +561,62 @@ 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 = ""; }; + 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 = ""; usesTabs = 0; }; F95C95160E994796007B7CB8 /* MachOTrie.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MachOTrie.hpp; sourceTree = ""; }; + F963542E1DCD736000895049 /* update_dyld_sim_shared_cache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = update_dyld_sim_shared_cache.cpp; path = "dyld3/shared-cache/update_dyld_sim_shared_cache.cpp"; sourceTree = ""; usesTabs = 0; }; + F96354451DCD74A400895049 /* update_dyld_sim_shared_cache */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = update_dyld_sim_shared_cache; sourceTree = BUILT_PRODUCTS_DIR; }; + F963546A1DD8D8D300895049 /* ImageProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ImageProxy.h; path = "dyld3/shared-cache/ImageProxy.h"; sourceTree = ""; usesTabs = 0; }; + F963546B1DD8F2A800895049 /* ImageProxy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImageProxy.cpp; path = "dyld3/shared-cache/ImageProxy.cpp"; sourceTree = ""; usesTabs = 0; }; F96D19711D7F63EE007AF3CE /* expand.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; name = expand.pl; path = bin/expand.pl; sourceTree = ""; }; + F96D19A51D9363D6007AF3CE /* APIs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = APIs.cpp; path = dyld3/APIs.cpp; sourceTree = ""; usesTabs = 0; }; + F96D19A61D9363D6007AF3CE /* AllImages.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AllImages.cpp; path = dyld3/AllImages.cpp; sourceTree = ""; usesTabs = 0; }; + F96D19A71D9363D6007AF3CE /* AllImages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AllImages.h; path = dyld3/AllImages.h; sourceTree = ""; usesTabs = 0; }; + F96D19A91D94576E007AF3CE /* MachOParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MachOParser.h; path = dyld3/MachOParser.h; sourceTree = ""; usesTabs = 0; }; + F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MachOParser.cpp; path = dyld3/MachOParser.cpp; sourceTree = ""; usesTabs = 0; }; + F96D19C11D95C6D6007AF3CE /* APIs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = APIs.h; path = dyld3/APIs.h; sourceTree = ""; usesTabs = 0; }; 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 = ""; }; + F977DDC91E53BEA700609230 /* SharedCacheRuntime.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SharedCacheRuntime.cpp; path = dyld3/SharedCacheRuntime.cpp; sourceTree = ""; }; + F977DDCA1E53BEA700609230 /* SharedCacheRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SharedCacheRuntime.h; path = dyld3/SharedCacheRuntime.h; sourceTree = ""; }; + F97C619D1D96C5BE00A84CD7 /* libdyldEntryVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = libdyldEntryVector.h; path = dyld3/libdyldEntryVector.h; sourceTree = ""; usesTabs = 0; }; + F97C619E1D98292700A84CD7 /* libdyldEntryVector.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = libdyldEntryVector.cpp; path = dyld3/libdyldEntryVector.cpp; sourceTree = ""; usesTabs = 0; }; + F97C61A01D9CA6B800A84CD7 /* Logging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Logging.cpp; path = dyld3/Logging.cpp; sourceTree = ""; usesTabs = 0; }; + F97C61A11D9CA6B800A84CD7 /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = dyld3/Logging.h; sourceTree = ""; usesTabs = 0; }; + F97C61A71DBAD1A900A84CD7 /* dyld_closure_util */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dyld_closure_util; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + F98692001DC3EF4800CBEDE6 /* Diagnostics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Diagnostics.h; path = dyld3/Diagnostics.h; sourceTree = ""; usesTabs = 0; }; + F98692021DC3EF4800CBEDE6 /* LaunchCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LaunchCache.h; path = dyld3/LaunchCache.h; sourceTree = ""; usesTabs = 0; }; + F98692041DC3EF4800CBEDE6 /* LaunchCacheFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LaunchCacheFormat.h; path = dyld3/LaunchCacheFormat.h; sourceTree = ""; usesTabs = 0; }; + F98692051DC3EF4800CBEDE6 /* LaunchCachePrinter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LaunchCachePrinter.cpp; path = dyld3/LaunchCachePrinter.cpp; sourceTree = ""; usesTabs = 0; }; + F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LaunchCacheReader.cpp; path = dyld3/LaunchCacheReader.cpp; sourceTree = ""; usesTabs = 0; }; + F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LaunchCacheWriter.cpp; path = dyld3/LaunchCacheWriter.cpp; sourceTree = ""; usesTabs = 0; }; + F98692081DC3EF4800CBEDE6 /* LaunchCacheWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LaunchCacheWriter.h; path = dyld3/LaunchCacheWriter.h; sourceTree = ""; usesTabs = 0; }; + F98692091DC3EF6C00CBEDE6 /* AdjustDylibSegments.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AdjustDylibSegments.cpp; path = "dyld3/shared-cache/AdjustDylibSegments.cpp"; sourceTree = ""; usesTabs = 0; }; + F986920C1DC3EF6C00CBEDE6 /* DyldSharedCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DyldSharedCache.h; path = "dyld3/shared-cache/DyldSharedCache.h"; sourceTree = ""; usesTabs = 0; }; + F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FileUtils.cpp; path = "dyld3/shared-cache/FileUtils.cpp"; sourceTree = ""; usesTabs = 0; }; + F986920E1DC3EF6C00CBEDE6 /* FileUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FileUtils.h; path = "dyld3/shared-cache/FileUtils.h"; sourceTree = ""; usesTabs = 0; }; + F986920F1DC3EF6C00CBEDE6 /* ObjC1Abstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ObjC1Abstraction.hpp; path = "dyld3/shared-cache/ObjC1Abstraction.hpp"; sourceTree = ""; usesTabs = 0; }; + F98692101DC3EF6C00CBEDE6 /* ObjC2Abstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ObjC2Abstraction.hpp; path = "dyld3/shared-cache/ObjC2Abstraction.hpp"; sourceTree = ""; usesTabs = 0; }; + F98692111DC3EF6C00CBEDE6 /* OptimizerBranches.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OptimizerBranches.cpp; path = "dyld3/shared-cache/OptimizerBranches.cpp"; sourceTree = ""; usesTabs = 0; }; + F98692121DC3EF6C00CBEDE6 /* OptimizerLinkedit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OptimizerLinkedit.cpp; path = "dyld3/shared-cache/OptimizerLinkedit.cpp"; sourceTree = ""; usesTabs = 0; }; + F98692131DC3EF6C00CBEDE6 /* OptimizerObjC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OptimizerObjC.cpp; path = "dyld3/shared-cache/OptimizerObjC.cpp"; sourceTree = ""; usesTabs = 0; }; + F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DyldSharedCache.cpp; path = "dyld3/shared-cache/DyldSharedCache.cpp"; sourceTree = ""; usesTabs = 0; }; + F98692151DC3EF6C00CBEDE6 /* update_dyld_shared_cache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = update_dyld_shared_cache.cpp; path = "dyld3/shared-cache/update_dyld_shared_cache.cpp"; sourceTree = ""; usesTabs = 0; }; + F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Diagnostics.cpp; path = dyld3/Diagnostics.cpp; sourceTree = ""; usesTabs = 0; }; + F986921C1DC3F86C00CBEDE6 /* CacheBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CacheBuilder.cpp; path = "dyld3/shared-cache/CacheBuilder.cpp"; sourceTree = ""; usesTabs = 0; }; + F986921D1DC3F86C00CBEDE6 /* CacheBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CacheBuilder.h; path = "dyld3/shared-cache/CacheBuilder.h"; sourceTree = ""; usesTabs = 0; }; + F986921E1DC3F86C00CBEDE6 /* dyld_cache_format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dyld_cache_format.h; path = "dyld3/shared-cache/dyld_cache_format.h"; sourceTree = ""; usesTabs = 0; }; + F98692221DC4028B00CBEDE6 /* CodeSigningTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CodeSigningTypes.h; path = dyld3/CodeSigningTypes.h; sourceTree = ""; usesTabs = 0; }; + F988F0BA1E2FDF5B003AED79 /* execserver.defs */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.mig; name = execserver.defs; path = testing/nocr/execserver.defs; 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; }; + F99986FA1F198C0C00D523F5 /* dyld-potential-framework-overrides */ = {isa = PBXFileReference; explicitFileType = text; name = "dyld-potential-framework-overrides"; path = "dyld3/dyld-potential-framework-overrides"; sourceTree = ""; }; 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 = ""; }; @@ -513,23 +625,32 @@ F9A221E60F3A6D7C00D15F73 /* dyldLibSystemGlue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dyldLibSystemGlue.c; path = src/dyldLibSystemGlue.c; sourceTree = ""; }; F9A6D6E2116F9DF20051CC16 /* threadLocalVariables.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = threadLocalVariables.c; path = src/threadLocalVariables.c; sourceTree = ""; }; F9A6D70B116FBBD10051CC16 /* threadLocalHelpers.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = threadLocalHelpers.s; path = src/threadLocalHelpers.s; sourceTree = ""; }; + F9AB02B81F329FAA00EE96C4 /* libclosured.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libclosured.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F9AB709D0BA75730002F6068 /* dyldLibSystemInterface.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = dyldLibSystemInterface.h; path = src/dyldLibSystemInterface.h; sourceTree = ""; }; 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; }; + F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DyldCacheParser.cpp; path = dyld3/DyldCacheParser.cpp; sourceTree = ""; }; + F9B3CAED1EEB5D0D00C9A48B /* DyldCacheParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DyldCacheParser.h; path = dyld3/DyldCacheParser.h; sourceTree = ""; }; + F9C15A451E19C2F50006E570 /* make_ios_dyld_cache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = make_ios_dyld_cache.cpp; path = "dyld3/shared-cache/make_ios_dyld_cache.cpp"; sourceTree = ""; }; + F9C15A491E1F7D960006E570 /* APIs_macOS.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = APIs_macOS.cpp; path = dyld3/APIs_macOS.cpp; sourceTree = ""; }; + F9C275581DA71A13007A5D8A /* Loading.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Loading.cpp; path = dyld3/Loading.cpp; sourceTree = ""; usesTabs = 0; }; + F9C275591DA71A13007A5D8A /* Loading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Loading.h; path = dyld3/Loading.h; sourceTree = ""; usesTabs = 0; }; 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 = ""; }; + F9D862441DC9759C000A199A /* dyld_closure_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dyld_closure_util.cpp; path = "dyld3/shared-cache/dyld_closure_util.cpp"; sourceTree = ""; usesTabs = 0; }; + F9DDEDAA1E28787900A753DC /* closured.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = closured.cpp; path = dyld3/closured/closured.cpp; sourceTree = ""; }; + F9DDEDAB1E28787900A753DC /* closuredProtocol.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = closuredProtocol.defs; path = dyld3/closured/closuredProtocol.defs; sourceTree = ""; }; + F9DDEDAC1E28787900A753DC /* closuredtypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = closuredtypes.h; path = dyld3/closured/closuredtypes.h; sourceTree = ""; }; + F9DDEDAD1E28787900A753DC /* com.apple.dyld.closured.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = com.apple.dyld.closured.plist; path = dyld3/closured/com.apple.dyld.closured.plist; sourceTree = ""; }; + F9DDEDB21E2878CA00A753DC /* closured */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = closured; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + F9E5E2C41EB00A870013A0BB /* ClosureBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ClosureBuffer.cpp; path = dyld3/ClosureBuffer.cpp; sourceTree = ""; }; + F9E5E2C51EB00A870013A0BB /* ClosureBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClosureBuffer.h; path = dyld3/ClosureBuffer.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; }; @@ -551,10 +672,15 @@ F9ED4CE80630A80600DF4E74 /* dyld_gdb.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = dyld_gdb.h; path = "include/mach-o/dyld_gdb.h"; sourceTree = SOURCE_ROOT; }; F9ED4CE90630A80600DF4E74 /* dyld_priv.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = dyld_priv.h; path = "include/mach-o/dyld_priv.h"; sourceTree = SOURCE_ROOT; }; F9ED4CEA0630A80600DF4E74 /* dyld.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = dyld.h; path = "include/mach-o/dyld.h"; sourceTree = SOURCE_ROOT; }; + F9EDC09E1F04767300B030F4 /* update_dyld_shared_cache_entitlements.plist */ = {isa = PBXFileReference; explicitFileType = text.plist.info; name = update_dyld_shared_cache_entitlements.plist; path = "dyld3/shared-cache/update_dyld_shared_cache_entitlements.plist"; sourceTree = ""; }; + F9EDC09F1F0478A300B030F4 /* closured_entitlements.plist */ = {isa = PBXFileReference; explicitFileType = text.plist.info; name = closured_entitlements.plist; path = dyld3/closured/closured_entitlements.plist; sourceTree = ""; }; + F9EDC0A01F0481B400B030F4 /* closured.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = closured.xcconfig; sourceTree = ""; }; 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 = ""; }; + F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PathOverrides.cpp; path = dyld3/PathOverrides.cpp; sourceTree = ""; }; + F9F76FAF1E08CFF200828678 /* PathOverrides.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PathOverrides.h; path = dyld3/PathOverrides.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -576,11 +702,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F922C8141F33B73800D8F479 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; F93937300A94FAF700070A07 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 370C6E541BDEF08000387223 /* libspindump.dylib in Frameworks */, + F94C22251E513CA90079E5DD /* CoreFoundation.framework in Frameworks */, + F92015701DDFEBAF00816A4A /* Bom.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F963543D1DCD74A400895049 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F97C61A41DBAD1A900A84CD7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -598,6 +746,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F9AB02B51F329FAA00EE96C4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; F9D1001014D8D0BA00099D91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -605,6 +760,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F9DDEDAF1E2878CA00A753DC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F90F7C081E9C6B8B00535722 /* libCrashReporterClient.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F9F2A5570F7AEE9800B7C9EB /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -628,6 +791,7 @@ EF799FE8070D27BB00F78484 /* man1 */ = { isa = PBXGroup; children = ( + F94942B21E6796D40019AE08 /* closured.1 */, EF799FE9070D27BB00F78484 /* dyld.1 */, F97FF3631C237F5C000ACDD2 /* nocr.1 */, F9D238D90A9E19A0002B55C7 /* update_dyld_shared_cache.1 */, @@ -654,15 +818,11 @@ F939373D0A94FC4700070A07 /* launch-cache */ = { isa = PBXGroup; children = ( - F90C540A1A82D78000558E8C /* update_dyld_shared_cache_entitlements.plist */, F939373E0A94FC4700070A07 /* Architectures.hpp */, F939373F0A94FC4700070A07 /* CacheFileAbstraction.hpp */, F93937400A94FC4700070A07 /* dyld_cache_format.h */, F93937410A94FC4700070A07 /* FileAbstraction.hpp */, F93937430A94FC4700070A07 /* MachOFileAbstraction.hpp */, - F93937440A94FC4700070A07 /* MachOLayout.hpp */, - F98935BA0A9A412B00FB6228 /* MachORebaser.hpp */, - F98935B90A9A412B00FB6228 /* MachOBinder.hpp */, F95C95160E994796007B7CB8 /* MachOTrie.hpp */, F9F2A56E0F7AEEE300B7C9EB /* dsc_iterator.cpp */, F9F2A56F0F7AEEE300B7C9EB /* dsc_iterator.h */, @@ -673,6 +833,60 @@ path = "launch-cache"; sourceTree = ""; }; + F94C22231E513CA90079E5DD /* Frameworks */ = { + isa = PBXGroup; + children = ( + F90F7C071E9C6B8B00535722 /* libCrashReporterClient.a */, + 37F7A5961BB363820039043A /* Bom.framework */, + 376ED1D71C46F2710051DD54 /* Metabom.framework */, + F94C22241E513CA90079E5DD /* CoreFoundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + F96D19A41D9363B7007AF3CE /* dyld3 */ = { + isa = PBXGroup; + children = ( + F99986FA1F198C0C00D523F5 /* dyld-potential-framework-overrides */, + F9DDEDA91E28785800A753DC /* closured */, + F98692161DC3EF7700CBEDE6 /* shared-cache */, + F977DDC91E53BEA700609230 /* SharedCacheRuntime.cpp */, + F977DDCA1E53BEA700609230 /* SharedCacheRuntime.h */, + F9E5E2C41EB00A870013A0BB /* ClosureBuffer.cpp */, + F9E5E2C51EB00A870013A0BB /* ClosureBuffer.h */, + F9C275581DA71A13007A5D8A /* Loading.cpp */, + F9C275591DA71A13007A5D8A /* Loading.h */, + F97C61A01D9CA6B800A84CD7 /* Logging.cpp */, + F97C61A11D9CA6B800A84CD7 /* Logging.h */, + 37D7DAFE1E96F0ED00D52CEA /* Tracing.cpp */, + 37D7DAFF1E96F0ED00D52CEA /* Tracing.h */, + F98692221DC4028B00CBEDE6 /* CodeSigningTypes.h */, + F97C619D1D96C5BE00A84CD7 /* libdyldEntryVector.h */, + F97C619E1D98292700A84CD7 /* libdyldEntryVector.cpp */, + F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */, + F98692001DC3EF4800CBEDE6 /* Diagnostics.h */, + F98692021DC3EF4800CBEDE6 /* LaunchCache.h */, + F98692041DC3EF4800CBEDE6 /* LaunchCacheFormat.h */, + F98692051DC3EF4800CBEDE6 /* LaunchCachePrinter.cpp */, + F98692061DC3EF4800CBEDE6 /* LaunchCacheReader.cpp */, + F98692071DC3EF4800CBEDE6 /* LaunchCacheWriter.cpp */, + F98692081DC3EF4800CBEDE6 /* LaunchCacheWriter.h */, + F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */, + F9F76FAF1E08CFF200828678 /* PathOverrides.h */, + F96D19C11D95C6D6007AF3CE /* APIs.h */, + F96D19A51D9363D6007AF3CE /* APIs.cpp */, + F9C15A491E1F7D960006E570 /* APIs_macOS.cpp */, + F96D19A71D9363D6007AF3CE /* AllImages.h */, + F96D19A61D9363D6007AF3CE /* AllImages.cpp */, + F9B3CAED1EEB5D0D00C9A48B /* DyldCacheParser.h */, + F9B3CAEB1EEB5CFA00C9A48B /* DyldCacheParser.cpp */, + F96D19A91D94576E007AF3CE /* MachOParser.h */, + F96D19BE1D94A6DC007AF3CE /* MachOParser.cpp */, + F922C81B1F33B86300D8F479 /* libclosured-stub.cpp */, + ); + name = dyld3; + sourceTree = ""; + }; F971DD121A4A0E0700BBDD52 /* configs */ = { isa = PBXGroup; children = ( @@ -680,6 +894,8 @@ F971DD141A4A0E0700BBDD52 /* dyld.xcconfig */, F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */, F971DD161A4A0E0700BBDD52 /* update_dyld_shared_cache.xcconfig */, + F94182DE1E60FFDC00D8EF25 /* update_dyld_sim_shared_cache.xcconfig */, + F9EDC0A01F0481B400B030F4 /* closured.xcconfig */, ); path = configs; sourceTree = SOURCE_ROOT; @@ -692,43 +908,60 @@ path = nocr; sourceTree = ""; }; - F9D0FE6C1A367093001E839B /* interlinked-dylibs */ = { + F98692161DC3EF7700CBEDE6 /* shared-cache */ = { 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; + 37908A2A1E3A85A4009613FA /* FileAbstraction.hpp */, + 37908A2B1E3A85A4009613FA /* MachOFileAbstraction.hpp */, + F986921E1DC3F86C00CBEDE6 /* dyld_cache_format.h */, + F98692091DC3EF6C00CBEDE6 /* AdjustDylibSegments.cpp */, + 37C5C2FC1E5CD154006B32C9 /* BuilderUtils.h */, + 37C5C2FB1E5CD154006B32C9 /* BuilderUtils.mm */, + F986921D1DC3F86C00CBEDE6 /* CacheBuilder.h */, + F986921C1DC3F86C00CBEDE6 /* CacheBuilder.cpp */, + F986920C1DC3EF6C00CBEDE6 /* DyldSharedCache.h */, + F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */, + F986920E1DC3EF6C00CBEDE6 /* FileUtils.h */, + F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */, + F963546A1DD8D8D300895049 /* ImageProxy.h */, + F963546B1DD8F2A800895049 /* ImageProxy.cpp */, + 37908A2C1E3A85A4009613FA /* Manifest.h */, + 37908A281E3A853E009613FA /* Manifest.mm */, + F986920F1DC3EF6C00CBEDE6 /* ObjC1Abstraction.hpp */, + F98692101DC3EF6C00CBEDE6 /* ObjC2Abstraction.hpp */, + F98692111DC3EF6C00CBEDE6 /* OptimizerBranches.cpp */, + F98692121DC3EF6C00CBEDE6 /* OptimizerLinkedit.cpp */, + F98692131DC3EF6C00CBEDE6 /* OptimizerObjC.cpp */, + F902031F1DEE83C000AC3F76 /* StringUtils.h */, + 37908A2D1E3A85A4009613FA /* Trie.hpp */, + F9D862441DC9759C000A199A /* dyld_closure_util.cpp */, + 37908A271E3A853E009613FA /* dyld_shared_cache_builder.mm */, + 37908A291E3A853E009613FA /* multi_dyld_shared_cache_builder.mm */, + F9C15A451E19C2F50006E570 /* make_ios_dyld_cache.cpp */, + F963542E1DCD736000895049 /* update_dyld_sim_shared_cache.cpp */, + F98692151DC3EF6C00CBEDE6 /* update_dyld_shared_cache.cpp */, + F9EDC09E1F04767300B030F4 /* update_dyld_shared_cache_entitlements.plist */, + ); + name = "shared-cache"; + sourceTree = ""; + }; + F9DDEDA91E28785800A753DC /* closured */ = { + isa = PBXGroup; + children = ( + F9DDEDAA1E28787900A753DC /* closured.cpp */, + F9EDC09F1F0478A300B030F4 /* closured_entitlements.plist */, + F9DDEDAB1E28787900A753DC /* closuredProtocol.defs */, + F9DDEDAC1E28787900A753DC /* closuredtypes.h */, + F913C8501E9312A100458AA3 /* com.apple.dyld.closured.sb */, + F9DDEDAD1E28787900A753DC /* com.apple.dyld.closured.plist */, + ); + name = closured; + sourceTree = ""; }; F9ED4C870630A72200DF4E74 = { isa = PBXGroup; children = ( + F988F0BA1E2FDF5B003AED79 /* execserver.defs */, F9F6F4261C1FAF8000BD8FED /* testing */, F971DD121A4A0E0700BBDD52 /* configs */, F9ED4CBB0630A7AA00DF4E74 /* src */, @@ -736,13 +969,14 @@ F9ED4CBE0630A7B100DF4E74 /* include */, F97FF3571C23638F000ACDD2 /* nocr */, F9ED4C990630A76000DF4E74 /* Products */, - F9D0FE6C1A367093001E839B /* interlinked-dylibs */, + F96D19A41D9363B7007AF3CE /* dyld3 */, F939373D0A94FC4700070A07 /* launch-cache */, + F94C22231E513CA90079E5DD /* Frameworks */, ); indentWidth = 4; sourceTree = ""; tabWidth = 4; - usesTabs = 1; + usesTabs = 0; }; F9ED4C990630A76000DF4E74 /* Products */ = { isa = PBXGroup; @@ -756,6 +990,11 @@ 377686021AC4B27D00026E6C /* multi_dyld_shared_cache_builder */, 3703A1241B38C1B300ADBA7F /* dyld_shared_cache_builder */, F97FF3561C23638F000ACDD2 /* nocr */, + F97C61A71DBAD1A900A84CD7 /* dyld_closure_util */, + F96354451DCD74A400895049 /* update_dyld_sim_shared_cache */, + F9DDEDB21E2878CA00A753DC /* closured */, + F9AB02B81F329FAA00EE96C4 /* libclosured.dylib */, + F922C8171F33B73800D8F479 /* libclosured.dylib */, ); name = Products; sourceTree = ""; @@ -763,7 +1002,6 @@ F9ED4CBB0630A7AA00DF4E74 /* src */ = { isa = PBXGroup; children = ( - F97FF35E1C236402000ACDD2 /* execserver.defs */, F97FF35F1C236402000ACDD2 /* nocr.c */, F9ED4CC60630A7F100DF4E74 /* dyld_gdb.cpp */, F9ED4CC70630A7F100DF4E74 /* dyld.cpp */, @@ -790,8 +1028,8 @@ F94DB9010F0A9B1700323715 /* ImageLoaderMachOClassic.h */, F94DB9020F0A9B1700323715 /* ImageLoaderMachOCompressed.cpp */, F94DB9030F0A9B1700323715 /* ImageLoaderMachOCompressed.h */, - F9280B791AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp */, F9280B7A1AB9DCA000B18AEC /* ImageLoaderMegaDylib.h */, + F9280B791AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp */, F99DE0361AAE4F0400669496 /* libdyld_data_symbols.dirty */, F9ED4CD50630A7F100DF4E74 /* stub_binding_helper.s */, F9B01E3D0739ABDE00CF981B /* dyld.exp */, @@ -812,11 +1050,11 @@ children = ( F96D19711D7F63EE007AF3CE /* expand.pl */, F95090D01C5AB89A0031F81D /* dyld_process_info.h */, - F918691408B16D2500E0F9DB /* dyld-interposing.h */, F98D274C0AA79D7400416316 /* dyld_images.h */, + F918691408B16D2500E0F9DB /* dyld-interposing.h */, + F9ED4CEA0630A80600DF4E74 /* dyld.h */, F9ED4CE80630A80600DF4E74 /* dyld_gdb.h */, F9ED4CE90630A80600DF4E74 /* dyld_priv.h */, - F9ED4CEA0630A80600DF4E74 /* dyld.h */, F99EE6AE06B48D4200BF1992 /* dlfcn.h */, F9C69EFD14EC8ABF009CAE2E /* objc-shared-cache.h */, ); @@ -833,6 +1071,37 @@ }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + F922C8151F33B73800D8F479 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F98F1FBB1E4029CA00EF868D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F99006DD1E411BA70013456D /* dyld_images.h in Headers */, + F99006DE1E411BBC0013456D /* dyld.h in Headers */, + F98F1FBD1E4029E400EF868D /* dyld_priv.h in Headers */, + F960A78A1E40569400840176 /* dyld-interposing.h in Headers */, + F98F1FBF1E4031F800EF868D /* dyld_process_info.h in Headers */, + F99006E01E4130AE0013456D /* dyld_gdb.h in Headers */, + F960A78B1E405DE300840176 /* dyld_cache_format.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9AB02B61F329FAA00EE96C4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ 3703A1111B38C1B300ADBA7F /* dyld_shared_cache_builder */ = { isa = PBXNativeTarget; @@ -870,6 +1139,23 @@ productReference = 377686021AC4B27D00026E6C /* multi_dyld_shared_cache_builder */; productType = "com.apple.product-type.tool"; }; + F922C8161F33B73800D8F479 /* libclosured-stub */ = { + isa = PBXNativeTarget; + buildConfigurationList = F922C8181F33B73800D8F479 /* Build configuration list for PBXNativeTarget "libclosured-stub" */; + buildPhases = ( + F922C8131F33B73800D8F479 /* Sources */, + F922C8141F33B73800D8F479 /* Frameworks */, + F922C8151F33B73800D8F479 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "libclosured-stub"; + productName = "libclosured-stub"; + productReference = F922C8171F33B73800D8F479 /* libclosured.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; F93937310A94FAF700070A07 /* update_dyld_shared_cache_tool */ = { isa = PBXNativeTarget; buildConfigurationList = F93937340A94FB2900070A07 /* Build configuration list for PBXNativeTarget "update_dyld_shared_cache_tool" */; @@ -879,6 +1165,7 @@ F93937300A94FAF700070A07 /* Frameworks */, F9D238DD0A9E2FEE002B55C7 /* usr|share|man|man1 */, F991E3030FF1A4EC0082CCC9 /* do not install duplicates */, + F981C8C21F058F8200452F35 /* mkdir /var/db/dyld */, ); buildRules = ( ); @@ -889,6 +1176,40 @@ productReference = F93937320A94FAF700070A07 /* update_dyld_shared_cache */; productType = "com.apple.product-type.tool"; }; + F963542F1DCD74A400895049 /* update_dyld_sim_shared_cache */ = { + isa = PBXNativeTarget; + buildConfigurationList = F96354421DCD74A400895049 /* Build configuration list for PBXNativeTarget "update_dyld_sim_shared_cache" */; + buildPhases = ( + F96354301DCD74A400895049 /* create dyld_cache_config.h */, + F96354311DCD74A400895049 /* Sources */, + F963543D1DCD74A400895049 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = update_dyld_sim_shared_cache; + productName = update_dyld_shared_cache; + productReference = F96354451DCD74A400895049 /* update_dyld_sim_shared_cache */; + productType = "com.apple.product-type.tool"; + }; + F97C61A61DBAD1A900A84CD7 /* dyld_closure_util */ = { + isa = PBXNativeTarget; + buildConfigurationList = F97C61AB1DBAD1A900A84CD7 /* Build configuration list for PBXNativeTarget "dyld_closure_util" */; + buildPhases = ( + F97C61A31DBAD1A900A84CD7 /* Sources */, + F97C61A41DBAD1A900A84CD7 /* Frameworks */, + F97C61A51DBAD1A900A84CD7 /* Copy Files */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = dyld_closure_util; + productName = dyld_closure_util; + productReference = F97C61A71DBAD1A900A84CD7 /* dyld_closure_util */; + productType = "com.apple.product-type.tool"; + }; F97FF3551C23638F000ACDD2 /* nocr */ = { isa = PBXNativeTarget; buildConfigurationList = F97FF35C1C23638F000ACDD2 /* Build configuration list for PBXNativeTarget "nocr" */; @@ -922,6 +1243,23 @@ productReference = F99B8E670FEC121100701838 /* dyld_shared_cache_util */; productType = "com.apple.product-type.tool"; }; + F9AB02B71F329FAA00EE96C4 /* libclosured */ = { + isa = PBXNativeTarget; + buildConfigurationList = F9AB02C21F329FAA00EE96C4 /* Build configuration list for PBXNativeTarget "libclosured" */; + buildPhases = ( + F9AB02B41F329FAA00EE96C4 /* Sources */, + F9AB02B51F329FAA00EE96C4 /* Frameworks */, + F9AB02B61F329FAA00EE96C4 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = libclosured; + productName = libclosured; + productReference = F9AB02B81F329FAA00EE96C4 /* libclosured.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; F9D1001114D8D0BA00099D91 /* dsc_extractor */ = { isa = PBXNativeTarget; buildConfigurationList = F9D1001714D8D0F100099D91 /* Build configuration list for PBXNativeTarget "dsc_extractor" */; @@ -938,6 +1276,25 @@ productReference = F9D1001214D8D0BA00099D91 /* dsc_extractor.bundle */; productType = "com.apple.product-type.library.dynamic"; }; + F9DDEDB11E2878CA00A753DC /* closured */ = { + isa = PBXNativeTarget; + buildConfigurationList = F9DDEDB61E2878CB00A753DC /* Build configuration list for PBXNativeTarget "closured" */; + buildPhases = ( + F9DDEDAE1E2878CA00A753DC /* Sources */, + F9DDEDAF1E2878CA00A753DC /* Frameworks */, + F94942B01E6794650019AE08 /* installl plist */, + F94942B11E67965C0019AE08 /* install man page */, + F913C8511E93137700458AA3 /* Install sandbox profile */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = closured; + productName = closured; + productReference = F9DDEDB21E2878CA00A753DC /* closured */; + productType = "com.apple.product-type.tool"; + }; F9ED4C970630A76000DF4E74 /* dyld */ = { isa = PBXNativeTarget; buildConfigurationList = F9D8C7DD087B087300E93EFB /* Build configuration list for PBXNativeTarget "dyld" */; @@ -955,7 +1312,9 @@ F921D3160703769A000D1056 /* PBXBuildRule */, ); dependencies = ( + F922C8121F33B62700D8F479 /* PBXTargetDependency */, F99B8EB20FEC220C00701838 /* PBXTargetDependency */, + F96543A11E343601003C5540 /* PBXTargetDependency */, ); name = dyld; productName = dyld; @@ -968,12 +1327,16 @@ buildPhases = ( F9ED4C9C0630A76B00DF4E74 /* Sources */, F959621018849DF20003E4D4 /* add dyld symlink */, + F98F1FBB1E4029CA00EF868D /* Headers */, + F960A78C1E405E2300840176 /* expand dyld_priv.h macros */, + F99006DF1E411C500013456D /* install dlfcn.h */, ); buildRules = ( F921D31E070376F1000D1056 /* PBXBuildRule */, F9574C4906C94DA700142BFA /* PBXBuildRule */, ); dependencies = ( + F922C81E1F33B96300D8F479 /* PBXTargetDependency */, ); name = libdyld.dylib; productName = libdyld; @@ -1003,16 +1366,32 @@ F9ED4C8B0630A72300DF4E74 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0630; + LastUpgradeCheck = 0900; TargetAttributes = { 37A0AD0A1C15FFF500731E50 = { - CreatedOnToolsVersion = 7.0; + CreatedOnToolsVersion = 8.0; + }; + F922C8161F33B73800D8F479 = { + CreatedOnToolsVersion = 9.0; + }; + F97C61A61DBAD1A900A84CD7 = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 59GAB85EFG; + ProvisioningStyle = Automatic; }; F97FF3551C23638F000ACDD2 = { - CreatedOnToolsVersion = 7.1; + CreatedOnToolsVersion = 8.0; + }; + F9AB02B71F329FAA00EE96C4 = { + CreatedOnToolsVersion = 9.0; + }; + F9DDEDB11E2878CA00A753DC = { + CreatedOnToolsVersion = 8.2; + DevelopmentTeam = 59GAB85EFG; + ProvisioningStyle = Automatic; }; F9F6F4271C1FB0A700BD8FED = { - CreatedOnToolsVersion = 7.1; + CreatedOnToolsVersion = 8.0; }; }; }; @@ -1033,17 +1412,22 @@ targets = ( F9ED4C920630A73900DF4E74 /* all */, 37A0AD0A1C15FFF500731E50 /* update_dyld_shared_cache */, - F9ED4C970630A76000DF4E74 /* dyld */, F908134211D3ED0B00626CC1 /* libdyld */, + F9ED4C970630A76000DF4E74 /* dyld */, + F9DDEDB11E2878CA00A753DC /* closured */, F9ED4C9E0630A76B00DF4E74 /* libdyld.dylib */, 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 */, + F963542F1DCD74A400895049 /* update_dyld_sim_shared_cache */, + F99B8E550FEC10F600701838 /* dyld_shared_cache_util */, + F97C61A61DBAD1A900A84CD7 /* dyld_closure_util */, + F9F2A5580F7AEE9800B7C9EB /* libdsc */, + F9D1001114D8D0BA00099D91 /* dsc_extractor */, F9F6F4271C1FB0A700BD8FED /* dyld_tests */, F97FF3551C23638F000ACDD2 /* nocr */, + F9AB02B71F329FAA00EE96C4 /* libclosured */, + F922C8161F33B73800D8F479 /* libclosured-stub */, ); }; /* End PBXProject section */ @@ -1112,6 +1496,23 @@ 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; }; + F913C8511E93137700458AA3 /* Install sandbox profile */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/dyld3/closured/com.apple.dyld.closured.sb", + ); + name = "Install sandbox profile"; + outputPaths = ( + "${DSTROOT}/System/Library/Sandbox/Profiles/com.apple.dyld.closured.sb", + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = /bin/sh; + shellScript = "if [ ${OS} = \"MACOS\" ]; then\n mkdir -p ${DSTROOT}/System/Library/Sandbox/Profiles\n cp ${SRCROOT}/dyld3/closured/com.apple.dyld.closured.sb ${DSTROOT}/System/Library/Sandbox/Profiles/com.apple.dyld.closured.sb\nfi"; + showEnvVarsInLog = 0; + }; F9213B3F18BFC9CB001CB6E8 /* simulator entitlement */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; @@ -1127,6 +1528,38 @@ shellScript = "if [ \"${PRODUCT_NAME}\" = \"dyld_sim\" ]\nthen\n /usr/bin/codesign --force --sign - --entitlements ${SRCROOT}/dyld_sim-entitlements.plist ${INSTALL_DIR}/dyld_sim\nfi\n"; showEnvVarsInLog = 0; }; + F94182D61E60E74E00D8EF25 /* pre-platform builds */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + ); + name = "pre-platform builds"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = /bin/sh; + shellScript = "xcodebuild install -target multi_dyld_shared_cache_builder SDKROOT=\"${SDKROOT}\" MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} OBJROOT=\"${OBJROOT}\" SRCROOT=\"${SRCROOT}\" DSTROOT=\"${DSTROOT}\" SYMROOT=\"${SYMROOT}\" RC_ProjectSourceVersion=\"${RC_ProjectSourceVersion}\"\n\nif [ \"${RC_PURPLE}\" = \"YES\" ]\nthen\n\txcodebuild install -target dyld_shared_cache_builder SDKROOT=\"${SDKROOT}\" MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} OBJROOT=\"${OBJROOT}\" SRCROOT=\"${SRCROOT}\" DSTROOT=\"${DSTROOT}\" SYMROOT=\"${SYMROOT}\" RC_ProjectSourceVersion=\"${RC_ProjectSourceVersion}\"\n\txcodebuild install -target update_dyld_sim_shared_cache SDKROOT=\"${SDKROOT}\" MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} OBJROOT=\"${OBJROOT}\" SRCROOT=\"${SRCROOT}\" DSTROOT=\"${DSTROOT}\" SYMROOT=\"${SYMROOT}\" RC_ProjectSourceVersion=\"${RC_ProjectSourceVersion}\"\nelse\n\txcodebuild install -target update_dyld_shared_cache_tool SDKROOT=\"${SDKROOT}\" MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} OBJROOT=\"${OBJROOT}\" SRCROOT=\"${SRCROOT}\" DSTROOT=\"${DSTROOT}\" SYMROOT=\"${SYMROOT}\" RC_ProjectSourceVersion=\"${RC_ProjectSourceVersion}\"\nfi"; + showEnvVarsInLog = 0; + }; + F94942B01E6794650019AE08 /* installl plist */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/dyld3/closured/com.apple.dyld.closured.plist", + ); + name = "installl plist"; + outputPaths = ( + "${DSTROOT}/System/Library/LaunchDaemons/com.apple.dyld.closured.plist", + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = /bin/sh; + shellScript = "mkdir -p ${DSTROOT}/System/Library/LaunchDaemons\nplutil -convert binary1 -o ${DSTROOT}/System/Library/LaunchDaemons/com.apple.dyld.closured.plist ${SRCROOT}/dyld3/closured/com.apple.dyld.closured.plist"; + showEnvVarsInLog = 0; + }; F959621018849DF20003E4D4 /* add dyld symlink */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; @@ -1142,19 +1575,37 @@ shellScript = "if [[ \"${PLATFORM_NAME}\" == *simulator* ]]\nthen\n\tcd ${DSTROOT}/${INSTALL_PATH}\n\tln -s libdyld.dylib libdyld_sim.dylib\nfi\n"; showEnvVarsInLog = 0; }; - F96D19701D7F62C3007AF3CE /* Install dyld_priv.h */ = { + F960A78C1E405E2300840176 /* expand dyld_priv.h macros */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( ); inputPaths = ( + "${SRCROOT}/include/mach-o/dyld_priv.h", ); - name = "Install dyld_priv.h"; + name = "expand dyld_priv.h macros"; outputPaths = ( + "${DSTROOT}/usr/local/include/mach-o/dyld_priv.h", ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "${SRCROOT}/bin/expand.pl < ${SRCROOT}/include/mach-o/dyld_priv.h > ${DSTROOT}/${INSTALL_PATH_PREFIX}/usr/local/include/mach-o/dyld_priv.h\n"; + shellScript = "${SRCROOT}/bin/expand.pl < ${SRCROOT}/include/mach-o/dyld_priv.h > ${DSTROOT}/usr/local/include/mach-o/dyld_priv.h\n"; + showEnvVarsInLog = 0; + }; + F96354301DCD74A400895049 /* create dyld_cache_config.h */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "create 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\n"; showEnvVarsInLog = 0; }; F96D19A31D91D733007AF3CE /* make dyld_priv.h */ = { @@ -1174,53 +1625,83 @@ shellScript = "mkdir -p ${DERIVED_FILE_DIR}/mach-o\n${SRCROOT}/bin/expand.pl < ${SRCROOT}/include/mach-o/dyld_priv.h > ${DERIVED_FILE_DIR}/mach-o/dyld_priv.h\n"; showEnvVarsInLog = 0; }; - F991E3030FF1A4EC0082CCC9 /* do not install duplicates */ = { + F981C8C21F058F8200452F35 /* mkdir /var/db/dyld */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( ); inputPaths = ( ); - name = "do not install duplicates"; + name = "mkdir /var/db/dyld"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "if [ \"${INSTALL_LOCATION}\" = \"\" ] \nthen\n # on iOS, libdyld builds arm libdsc.a and u_d_s_c builds intel libdsc.a\n # on MacOSX, to avoid collision, u_d_s_c does not install libdsc.a\n\trm -rf ${DSTROOT}/usr/local/include\n\trm -rf ${DSTROOT}/usr/local/lib\n\trm -rf ${DSTROOT}/usr/lib\nfi\n"; + shellScript = "mkdir -p ${DSTROOT}/private/var/db/dyld"; showEnvVarsInLog = 0; }; - F99B8EB60FEC236500701838 /* suppress macosx dyld_shared_cache_util */ = { + F99006DF1E411C500013456D /* install dlfcn.h */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( ); inputPaths = ( ); - name = "suppress macosx dyld_shared_cache_util"; + name = "install dlfcn.h"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "# iPhone wants a copy of dyld_shared_cache_util on the device\n# MacOSX does not need a copy because update_dyld_shared_cache target already installed a copy\nif [ \"${PLATFORM_NAME}\" = \"macosx\" ] \nthen\n\trm -rf ${DSTROOT}/usr/local\nfi\n"; + shellScript = "# xcode only lets you install public headers to one directory\ncp ${SRCROOT}/include/dlfcn.h ${DSTROOT}/usr/include/\n"; showEnvVarsInLog = 0; }; - F9D050C811DD701A00FB0A29 /* configure archives */ = { + F991E3030FF1A4EC0082CCC9 /* do not install duplicates */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 12; + buildActionMask = 8; files = ( ); inputPaths = ( ); - name = "configure archives"; + name = "do not install duplicates"; outputPaths = ( - "$(DERIVED_SOURCES_DIR)/archives.txt", ); - runOnlyForDeploymentPostprocessing = 0; + runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - 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"; + shellScript = "if [ \"${INSTALL_LOCATION}\" = \"\" ] \nthen\n # on iOS, libdyld builds arm libdsc.a and u_d_s_c builds intel libdsc.a\n # on MacOSX, to avoid collision, u_d_s_c does not install libdsc.a\n\trm -rf ${DSTROOT}/usr/local/include\n\trm -rf ${DSTROOT}/usr/local/lib\n\trm -rf ${DSTROOT}/usr/lib\nfi"; showEnvVarsInLog = 0; }; - F9F6F42B1C1FB0AE00BD8FED /* build */ = { + F99B8EB60FEC236500701838 /* suppress macosx dyld_shared_cache_util */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + ); + name = "suppress macosx dyld_shared_cache_util"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = /bin/sh; + shellScript = "# iPhone wants a copy of dyld_shared_cache_util on the device\n# MacOSX does not need a copy because update_dyld_shared_cache target already installed a copy\nif [ \"${PLATFORM_NAME}\" = \"macosx\" ] \nthen\n\trm -rf ${DSTROOT}/usr/local\n mkdir -p ${DSTROOT}/AppleInternal/Library/Preferences/\n cp dyld3/dyld-potential-framework-overrides ${DSTROOT}/AppleInternal/Library/Preferences/\nfi\n"; + showEnvVarsInLog = 0; + }; + F9D050C811DD701A00FB0A29 /* configure archives */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputPaths = ( + ); + name = "configure archives"; + outputPaths = ( + "$(DERIVED_SOURCES_DIR)/archives.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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 = ( @@ -1242,19 +1723,23 @@ 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 */, + 37554F521E3F78EB00407388 /* ImageProxy.cpp in Sources */, + 37554F421E3F169600407388 /* CacheBuilder.cpp in Sources */, + 37554F481E3F16BA00407388 /* OptimizerBranches.cpp in Sources */, + 37554F441E3F16A900407388 /* OptimizerObjC.cpp in Sources */, + 37554F581E3F7B6500407388 /* PathOverrides.cpp in Sources */, + 37554F561E3F7B4300407388 /* LaunchCacheWriter.cpp in Sources */, + 37908A301E3ADD15009613FA /* Diagnostics.cpp in Sources */, + 37554F491E3F76E400407388 /* DyldSharedCache.cpp in Sources */, + 37908A311E3EB585009613FA /* MachOParser.cpp in Sources */, + F922AE581EF0D3C300926F9D /* DyldCacheParser.cpp in Sources */, + 37C5C2FE1E5CD154006B32C9 /* BuilderUtils.mm in Sources */, + 37908A2F1E3A864E009613FA /* dyld_shared_cache_builder.mm in Sources */, + 37554F541E3F7B1F00407388 /* LaunchCacheReader.cpp in Sources */, + 37554F461E3F16B600407388 /* OptimizerLinkedit.cpp in Sources */, + 37908A321E3ED667009613FA /* FileUtils.cpp in Sources */, + 37908A2E1E3A8632009613FA /* Manifest.mm in Sources */, + 37554F4B1E3F76E900407388 /* AdjustDylibSegments.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1262,19 +1747,31 @@ 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 */, + 37554F511E3F78EB00407388 /* ImageProxy.cpp in Sources */, + 37554F3F1E3F165100407388 /* Diagnostics.cpp in Sources */, + 37554F471E3F16B900407388 /* OptimizerBranches.cpp in Sources */, + 37554F451E3F16B500407388 /* OptimizerLinkedit.cpp in Sources */, + 37554F571E3F7B6400407388 /* PathOverrides.cpp in Sources */, + 37554F551E3F7B4200407388 /* LaunchCacheWriter.cpp in Sources */, + 37554F401E3F167A00407388 /* MachOParser.cpp in Sources */, + F981C8C11EF06A7800452F35 /* DyldCacheParser.cpp in Sources */, + 37554F411E3F169500407388 /* CacheBuilder.cpp in Sources */, + 37554F431E3F16A800407388 /* OptimizerObjC.cpp in Sources */, + 37C5C2FD1E5CD154006B32C9 /* BuilderUtils.mm in Sources */, + 37554F3B1E3F0FD200407388 /* Manifest.mm in Sources */, + 37554F531E3F7B1E00407388 /* LaunchCacheReader.cpp in Sources */, + 37554F3C1E3F0FD200407388 /* DyldSharedCache.cpp in Sources */, + 37554F3D1E3F0FD200407388 /* FileUtils.cpp in Sources */, + 37554F3E1E3F0FD200407388 /* multi_dyld_shared_cache_builder.mm in Sources */, + 37554F4A1E3F76E800407388 /* AdjustDylibSegments.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F922C8131F33B73800D8F479 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F922C81C1F33B88400D8F479 /* libclosured-stub.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1282,19 +1779,64 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 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 */, + F98692171DC3EFD500CBEDE6 /* update_dyld_shared_cache.cpp in Sources */, + F98692181DC3EFD700CBEDE6 /* DyldSharedCache.cpp in Sources */, + F981C8BD1EEF447500452F35 /* DyldCacheParser.cpp in Sources */, + F986921F1DC3F98700CBEDE6 /* CacheBuilder.cpp in Sources */, + F98692231DC403F900CBEDE6 /* AdjustDylibSegments.cpp in Sources */, + F98692191DC3EFDA00CBEDE6 /* FileUtils.cpp in Sources */, + F98692201DC3F99300CBEDE6 /* Diagnostics.cpp in Sources */, + F9460DCD1E09FFFC00FEC613 /* PathOverrides.cpp in Sources */, + F98692211DC401B900CBEDE6 /* MachOParser.cpp in Sources */, + F9D862401DC57A27000A199A /* OptimizerObjC.cpp in Sources */, + F94182D51E60A2F100D8EF25 /* OptimizerBranches.cpp in Sources */, + F9A548B31DDBBC75002B4422 /* ImageProxy.cpp in Sources */, + F9D862411DC65A4E000A199A /* LaunchCacheWriter.cpp in Sources */, + F9D862421DC65A53000A199A /* LaunchCacheReader.cpp in Sources */, + F9D8623F1DC41043000A199A /* OptimizerLinkedit.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F96354311DCD74A400895049 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F96354461DCD74BC00895049 /* update_dyld_sim_shared_cache.cpp in Sources */, + F96354331DCD74A400895049 /* DyldSharedCache.cpp in Sources */, + F981C8BF1EEF733C00452F35 /* DyldCacheParser.cpp in Sources */, + F981C8BE1EEF733800452F35 /* ClosureBuffer.cpp in Sources */, + F96354341DCD74A400895049 /* CacheBuilder.cpp in Sources */, + F96354351DCD74A400895049 /* AdjustDylibSegments.cpp in Sources */, + F963546C1DD8F38300895049 /* ImageProxy.cpp in Sources */, + F96354361DCD74A400895049 /* FileUtils.cpp in Sources */, + F96354371DCD74A400895049 /* Diagnostics.cpp in Sources */, + F9460DCE1E0A000600FEC613 /* PathOverrides.cpp in Sources */, + F96354381DCD74A400895049 /* MachOParser.cpp in Sources */, + F96354391DCD74A400895049 /* OptimizerObjC.cpp in Sources */, + F963543A1DCD74A400895049 /* LaunchCacheWriter.cpp in Sources */, + 37C5C2FF1E60D7DE006B32C9 /* OptimizerBranches.cpp in Sources */, + F963543B1DCD74A400895049 /* LaunchCacheReader.cpp in Sources */, + F963543C1DCD74A400895049 /* OptimizerLinkedit.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F97C61A31DBAD1A900A84CD7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9D862451DC975A5000A199A /* dyld_closure_util.cpp in Sources */, + F97C61B31DBAE14200A84CD7 /* MachOParser.cpp in Sources */, + F9D8624D1DC9783E000A199A /* FileUtils.cpp in Sources */, + F9D862461DC975AA000A199A /* Diagnostics.cpp in Sources */, + F926C0471DDBFB7A00941CB1 /* ImageProxy.cpp in Sources */, + F9F76FB01E09CDF400828678 /* PathOverrides.cpp in Sources */, + F9D8624C1DC97717000A199A /* DyldSharedCache.cpp in Sources */, + F9D862471DC975B1000A199A /* LaunchCacheWriter.cpp in Sources */, + F9B3CAEC1EEB5CFB00C9A48B /* DyldCacheParser.cpp in Sources */, + F9D8624B1DC976E4000A199A /* LaunchCachePrinter.cpp in Sources */, + F9D862481DC975B3000A199A /* LaunchCacheReader.cpp in Sources */, + F9E5E2C61EB00A9F0013A0BB /* ClosureBuffer.cpp in Sources */, + F9ABA06E1E289B72000F21B4 /* closuredProtocol.defs in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1302,8 +1844,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F97FF3601C236408000ACDD2 /* execserver.defs in Sources */, F97FF3611C23640C000ACDD2 /* nocr.c in Sources */, + F988F0BB1E2FDF5B003AED79 /* execserver.defs in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1316,6 +1858,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F9AB02B41F329FAA00EE96C4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9AB02CC1F32A33C00EE96C4 /* FileUtils.cpp in Sources */, + F9AB02CB1F32A26700EE96C4 /* PathOverrides.cpp in Sources */, + F9AB02CA1F32A25F00EE96C4 /* DyldCacheParser.cpp in Sources */, + F9AB02C91F32A24B00EE96C4 /* ClosureBuffer.cpp in Sources */, + F9AB02C81F32A23B00EE96C4 /* MachOParser.cpp in Sources */, + F9AB02C71F32A22B00EE96C4 /* LaunchCacheReader.cpp in Sources */, + F9AB02C61F32A1F400EE96C4 /* Diagnostics.cpp in Sources */, + F9AB02C41F329FF400EE96C4 /* DyldSharedCache.cpp in Sources */, + F9AB02C31F329FE000EE96C4 /* ImageProxy.cpp in Sources */, + F9AB02C51F329FFE00EE96C4 /* LaunchCacheWriter.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F9D1000F14D8D0BA00099D91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1325,10 +1884,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F9DDEDAE1E2878CA00A753DC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9DDEDB91E2878EC00A753DC /* closured.cpp in Sources */, + F9DDEDBA1E2878F100A753DC /* closuredProtocol.defs in Sources */, + F9DDEDBB1E287C9500A753DC /* DyldSharedCache.cpp in Sources */, + F9DDEDBC1E287CA100A753DC /* Diagnostics.cpp in Sources */, + F9DDEDC01E287CF600A753DC /* LaunchCacheReader.cpp in Sources */, + F9DDEDBD1E287CB100A753DC /* LaunchCacheWriter.cpp in Sources */, + F9DDEDC21E287D8A00A753DC /* PathOverrides.cpp in Sources */, + F9DDEDC11E287D3C00A753DC /* MachOParser.cpp in Sources */, + F981C8C01EEF7D4100452F35 /* DyldCacheParser.cpp in Sources */, + F9DDEDBE1E287CF600A753DC /* FileUtils.cpp in Sources */, + F9DDEDBF1E287CF600A753DC /* ImageProxy.cpp in Sources */, + F9B3CAEA1EE20FE200C9A48B /* ClosureBuffer.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F9ED4C950630A76000DF4E74 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 37D7DB001E96F0ED00D52CEA /* Tracing.cpp in Sources */, F9ED4CDF0630A7F100DF4E74 /* dyldStartup.s in Sources */, F9ED4CDB0630A7F100DF4E74 /* dyldInitialization.cpp in Sources */, F9ED4CD70630A7F100DF4E74 /* dyld.cpp in Sources */, @@ -1343,6 +1922,14 @@ F9ED4CDE0630A7F100DF4E74 /* dyldNew.cpp in Sources */, F94DB9040F0A9B1700323715 /* ImageLoaderMachOClassic.cpp in Sources */, F94DB9050F0A9B1700323715 /* ImageLoaderMachOCompressed.cpp in Sources */, + F9C2755B1DA73EA1007A5D8A /* Loading.cpp in Sources */, + F9C275571DA5D67F007A5D8A /* MachOParser.cpp in Sources */, + F922AE5A1EF0DC7200926F9D /* DyldCacheParser.cpp in Sources */, + F9D8624F1DCBD318000A199A /* Diagnostics.cpp in Sources */, + F9D862501DCBD31D000A199A /* LaunchCacheReader.cpp in Sources */, + F9C73AC21E2992730025C89E /* closuredProtocol.defs in Sources */, + F977DDCB1E53BF5500609230 /* SharedCacheRuntime.cpp in Sources */, + F9D862511DCBD330000A199A /* DyldSharedCache.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1350,6 +1937,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F9D49CCC1458B95200F86ADD /* start_glue.s in Sources */, + 37D7DB011E96F3EB00D52CEA /* Tracing.cpp in Sources */, F9F256360639DBCC00A7427D /* dyldLock.cpp in Sources */, F9BA514B0ECE4F4200D1D62E /* dyld_stub_binder.s in Sources */, F9A221E70F3A6D7C00D15F73 /* dyldLibSystemGlue.c in Sources */, @@ -1358,7 +1947,20 @@ 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 */, + F96D19BF1D94A6DC007AF3CE /* MachOParser.cpp in Sources */, + F922AE591EF0DBA500926F9D /* DyldCacheParser.cpp in Sources */, + F9D8624E1DCBD06A000A199A /* Diagnostics.cpp in Sources */, + F92015711DE3F3B000816A4A /* DyldSharedCache.cpp in Sources */, + F96D19C01D94BFCE007AF3CE /* AllImages.cpp in Sources */, + F96D19A81D93661A007AF3CE /* APIs.cpp in Sources */, + F9C15A4A1E1F7DAC0006E570 /* APIs_macOS.cpp in Sources */, + F97C61A21D9CAE3500A84CD7 /* Logging.cpp in Sources */, + F9C2755A1DA71CE8007A5D8A /* Loading.cpp in Sources */, + F90108611E2AD96000870568 /* PathOverrides.cpp in Sources */, + F9D862431DC90A4F000A199A /* LaunchCacheReader.cpp in Sources */, + F9E5E2C71EB00AAA0013A0BB /* ClosureBuffer.cpp in Sources */, + F9C86B651E2B16C600FD8669 /* closuredProtocol.defs in Sources */, + F97C619F1D9829AA00A84CD7 /* libdyldEntryVector.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1379,40 +1981,45 @@ target = 37A0AD0A1C15FFF500731E50 /* update_dyld_shared_cache */; targetProxy = 37A0AD0E1C16000F00731E50 /* PBXContainerItemProxy */; }; - 37A0AD111C16003600731E50 /* PBXTargetDependency */ = { + D8668AD01ECE335F005E7D31 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F93937310A94FAF700070A07 /* update_dyld_shared_cache_tool */; - targetProxy = 37A0AD101C16003600731E50 /* PBXContainerItemProxy */; + target = F97C61A61DBAD1A900A84CD7 /* dyld_closure_util */; + targetProxy = D8668ACF1ECE335F005E7D31 /* PBXContainerItemProxy */; }; - 37A0AD131C16003600731E50 /* PBXTargetDependency */ = { + F908134811D3ED1A00626CC1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F9F2A5580F7AEE9800B7C9EB /* libdsc */; - targetProxy = 37A0AD121C16003600731E50 /* PBXContainerItemProxy */; + target = F9ED4C9E0630A76B00DF4E74 /* libdyld.dylib */; + targetProxy = F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */; }; - 37A0AD151C16003600731E50 /* PBXTargetDependency */ = { + F922C8121F33B62700D8F479 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F9D1001114D8D0BA00099D91 /* dsc_extractor */; - targetProxy = 37A0AD141C16003600731E50 /* PBXContainerItemProxy */; + target = F9AB02B71F329FAA00EE96C4 /* libclosured */; + targetProxy = F922C8111F33B62700D8F479 /* PBXContainerItemProxy */; }; - 37A0AD171C16003600731E50 /* PBXTargetDependency */ = { + F922C81E1F33B96300D8F479 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 377685F21AC4B27D00026E6C /* multi_dyld_shared_cache_builder */; - targetProxy = 37A0AD161C16003600731E50 /* PBXContainerItemProxy */; + target = F922C8161F33B73800D8F479 /* libclosured-stub */; + targetProxy = F922C81D1F33B96300D8F479 /* PBXContainerItemProxy */; }; - 37A0AD191C16003600731E50 /* PBXTargetDependency */ = { + F94182D81E60F0BE00D8EF25 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 3703A1111B38C1B300ADBA7F /* dyld_shared_cache_builder */; - targetProxy = 37A0AD181C16003600731E50 /* PBXContainerItemProxy */; + target = F99B8E550FEC10F600701838 /* dyld_shared_cache_util */; + targetProxy = F94182D71E60F0BE00D8EF25 /* PBXContainerItemProxy */; }; - 37A0AD1B1C16004600731E50 /* PBXTargetDependency */ = { + F94182DA1E60F0C000D8EF25 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F99B8E550FEC10F600701838 /* dyld_shared_cache_util */; - targetProxy = 37A0AD1A1C16004600731E50 /* PBXContainerItemProxy */; + target = F9F2A5580F7AEE9800B7C9EB /* libdsc */; + targetProxy = F94182D91E60F0C000D8EF25 /* PBXContainerItemProxy */; }; - F908134811D3ED1A00626CC1 /* PBXTargetDependency */ = { + F94182DC1E60F16900D8EF25 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F9ED4C9E0630A76B00DF4E74 /* libdyld.dylib */; - targetProxy = F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */; + target = F9D1001114D8D0BA00099D91 /* dsc_extractor */; + targetProxy = F94182DB1E60F16900D8EF25 /* PBXContainerItemProxy */; + }; + F96543A11E343601003C5540 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F97C61A61DBAD1A900A84CD7 /* dyld_closure_util */; + targetProxy = F96543A01E343601003C5540 /* PBXContainerItemProxy */; }; F97FF3661C237F97000ACDD2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -1457,10 +2064,12 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + DEAD_CODE_STRIPPING = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -1474,6 +2083,7 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", + "BUILDING_CACHE_BUILDER=1", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1483,13 +2093,16 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-DBOM_SUPPORT=1"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = macosx; + TOOLCHAINS = default; USER_HEADER_SEARCH_PATHS = "../launch-cache/"; - VALID_ARCHS = "armv6 armv7 x86_64 x86_64h"; + VALID_ARCHS = "x86_64 x86_64h"; }; name = Debug; }; @@ -1508,10 +2121,12 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1522,6 +2137,7 @@ "$(SDKROOT)$(APPLE_INTERNAL_LIBRARY_DIR)/Frameworks", ); GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREPROCESSOR_DEFINITIONS = "BUILDING_CACHE_BUILDER=1"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -1529,12 +2145,15 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "-DBOM_SUPPORT=1"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = macosx; + TOOLCHAINS = default; USER_HEADER_SEARCH_PATHS = "../launch-cache/"; - VALID_ARCHS = "armv6 armv7 x86_64 x86_64h"; + VALID_ARCHS = "x86_64 x86_64h"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -1558,6 +2177,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + DEAD_CODE_STRIPPING = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -1571,6 +2191,7 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", + "BUILDING_CACHE_BUILDER=1", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1580,6 +2201,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-DBOM_SUPPORT=1"; @@ -1587,7 +2209,7 @@ SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = NO; USER_HEADER_SEARCH_PATHS = "../launch-cache/"; - VALID_ARCHS = "armv6 armv7 x86_64 x86_64h"; + VALID_ARCHS = "x86_64 x86_64h"; }; name = Debug; }; @@ -1610,6 +2232,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1620,6 +2243,7 @@ "$(SDKROOT)/AppleInternal/Library/Frameworks", ); GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREPROCESSOR_DEFINITIONS = "BUILDING_CACHE_BUILDER=1"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -1627,13 +2251,14 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MACOSX_DEPLOYMENT_TARGET = 10.11; 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"; + VALID_ARCHS = "x86_64 x86_64h"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -1674,6 +2299,90 @@ }; name = Release; }; + F922C8191F33B73800D8F479 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_TESTABILITY = YES; + EXECUTABLE_PREFIX = lib; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LD_DYLIB_INSTALL_NAME = /usr/lib/closure/libclosured.dylib; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = "-nostdlib"; + PRODUCT_NAME = closured; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + F922C81A1F33B73800D8F479 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_NS_ASSERTIONS = NO; + EXECUTABLE_PREFIX = lib; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LD_DYLIB_INSTALL_NAME = /usr/lib/closure/libclosured.dylib; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = "-nostdlib"; + PRODUCT_NAME = closured; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Release; + }; F93937350A94FB2900070A07 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1681,6 +2390,7 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -1691,18 +2401,27 @@ GCC_OPTIMIZATION_LEVEL = 0; GCC_THREADSAFE_STATICS = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_EFFECTIVE_CPLUSPLUS_VIOLATIONS = NO; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; GCC_WARN_SHADOW = YES; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/include", + "$(SRCROOT)/dyld3", + "$(SRCROOT)/dyld3/shared-cache", + ); + INSTALL_PATH = /usr/bin; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = "-stdlib=libc++"; PRODUCT_NAME = update_dyld_shared_cache; SDKROOT = macosx.internal; + USE_HEADERMAP = NO; VALID_ARCHS = x86_64; }; name = Debug; @@ -1714,8 +2433,10 @@ CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -1725,19 +2446,185 @@ GCC_OPTIMIZATION_LEVEL = s; GCC_THREADSAFE_STATICS = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; + GCC_WARN_SHADOW = YES; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; - HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/include", + "$(SRCROOT)/dyld3", + "$(SRCROOT)/dyld3/shared-cache", + ); + INSTALL_PATH = /usr/bin; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = "-stdlib=libc++"; PRODUCT_NAME = update_dyld_shared_cache; SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; + USE_HEADERMAP = NO; VALID_ARCHS = x86_64; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; + F96354431DCD74A400895049 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + 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; + GCC_THREADSAFE_STATICS = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_EFFECTIVE_CPLUSPLUS_VIOLATIONS = NO; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/include", + "$(SRCROOT)/dyld3", + "$(SRCROOT)/dyld3/shared-cache", + ); + INSTALL_PATH = "$(DEVICE_PLATFORM_INSTALL_DIR)/Developer/Library/CoreSimulator/Profiles/Runtimes/$(SIMULATOR_DIR_NAME).simruntime/Contents/Resources"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; + OTHER_LDFLAGS = "-stdlib=libc++"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + SIMULATOR_DIR_NAME = "$(PLATFORM_FAMILY_NAME_$(DEVICE_PLATFORM_NAME)) $(IPHONE_SDK_MARKETING_VERSION)"; + USE_HEADERMAP = NO; + VALID_ARCHS = x86_64; + }; + name = Debug; + }; + F96354441DCD74A400895049 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F94182DE1E60FFDC00D8EF25 /* update_dyld_sim_shared_cache.xcconfig */; + buildSettings = { + 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_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/include", + "$(SRCROOT)/dyld3", + "$(SRCROOT)/dyld3/shared-cache", + ); + INSTALL_PATH = "$(DEVICE_PLATFORM_INSTALL_DIR)/Developer/Library/CoreSimulator/Profiles/Runtimes/$(SIMULATOR_DIR_NAME).simruntime/Contents/Resources"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; + OTHER_LDFLAGS = "-stdlib=libc++"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + SIMULATOR_DIR_NAME = "$(PLATFORM_FAMILY_NAME_$(DEVICE_PLATFORM_NAME)) $(IPHONE_SDK_MARKETING_VERSION)"; + STRIP_INSTALLED_PRODUCT = YES; + STRIP_STYLE = debugging; + USE_HEADERMAP = NO; + VALID_ARCHS = x86_64; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + F97C61AC1DBAD1A900A84CD7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 59GAB85EFG; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + }; + name = Debug; + }; + F97C61AD1DBAD1A900A84CD7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 59GAB85EFG; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + }; + name = Release; + }; F97FF35A1C23638F000ACDD2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1804,6 +2691,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1823,6 +2711,7 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; @@ -1843,6 +2732,8 @@ ); INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; PRODUCT_NAME = dyld_shared_cache_util; + SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = macosx; }; name = Debug; }; @@ -1862,7 +2753,105 @@ ); INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; PRODUCT_NAME = dyld_shared_cache_util; + SDKROOT = macosx.internal; SKIP_INSTALL = NO; + SUPPORTED_PLATFORMS = macosx; + }; + name = Release; + }; + F9AB02C01F329FAA00EE96C4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_TESTABILITY = YES; + EXECUTABLE_PREFIX = lib; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_CPP_EXCEPTIONS = YES; + GCC_ENABLE_CPP_RTTI = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DYLD_IN_PROCESS=0", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = /usr/lib/closure; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ( + "-Wl,-exported_symbol,__ZN5dyld325closured_CreateImageGroupERKNS_13ClosureBufferE", + "-Wl,-no_inits", + ); + PRODUCT_NAME = closured; + SDKROOT = macosx.internal; + }; + name = Debug; + }; + F9AB02C11F329FAA00EE96C4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_NS_ASSERTIONS = NO; + EXECUTABLE_PREFIX = lib; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_ENABLE_CPP_EXCEPTIONS = YES; + GCC_ENABLE_CPP_RTTI = YES; + GCC_PREPROCESSOR_DEFINITIONS = "DYLD_IN_PROCESS=0"; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INSTALL_PATH = /usr/lib/closure; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "-Wl,-exported_symbol,__ZN5dyld325closured_CreateImageGroupERKNS_13ClosureBufferE", + "-Wl,-no_inits", + ); + PRODUCT_NAME = closured; + SDKROOT = macosx.internal; }; name = Release; }; @@ -1923,7 +2912,7 @@ baseConfigurationReference = F971DD141A4A0E0700BBDD52 /* dyld.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_EMPTY_BODY = YES; CODE_SIGN_IDENTITY = "-"; @@ -1934,7 +2923,11 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_BUILTIN_FUNCTIONS = NO; GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = "DYLD_VERSION=$(RC_ProjectSourceVersion)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DYLD_VERSION=$(RC_ProjectSourceVersion)", + "_LIBCPP_NO_EXCEPTIONS=1", + "$(inherited)", + ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; @@ -1962,7 +2955,6 @@ "$(ALIGNMENT)", "$(ENTRY)", ); - SDKROOT = macosx.internal; STRIPFLAGS = "-S"; UNSTRIPPED_PRODUCT = NO; VERSIONING_SYSTEM = "apple-generic"; @@ -1977,7 +2969,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD141A4A0E0700BBDD52 /* dyld.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_EMPTY_BODY = YES; CODE_SIGN_IDENTITY = "-"; @@ -1988,7 +2980,11 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_OPTIMIZATION_LEVEL = s; - GCC_PREPROCESSOR_DEFINITIONS = "DYLD_VERSION=$(RC_ProjectSourceVersion)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DYLD_VERSION=$(RC_ProjectSourceVersion)", + "_LIBCPP_NO_EXCEPTIONS=1", + "$(inherited)", + ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_SHADOW = YES; @@ -2015,9 +3011,8 @@ "$(ALIGNMENT)", "$(ENTRY)", "-Wl,-no_data_const", - "-Wl,-section_order,__DATA,__all_image_info:__nl_symbol_ptr:__got:__const:__crash_info:__data:__common:__bss", + "-Wl,-section_order,__DATA,__all_image_info:__nl_symbol_ptr:__got:__const:__crash_info:__data:__bss:__common", ); - SDKROOT = macosx.internal; STRIPFLAGS = "-S"; UNSTRIPPED_PRODUCT = NO; VERSIONING_SYSTEM = "apple-generic"; @@ -2033,10 +3028,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_WARN_EMPTY_BODY = YES; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 500; DEAD_CODE_STRIPPING = YES; EXECUTABLE_PREFIX = lib; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -2048,18 +3044,33 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_SHADOW = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + GENERATE_TEXT_BASED_STUBS = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; INSTALLHDRS_COPY_PHASE = YES; + INSTALLHDRS_SCRIPT_PHASE = YES; + ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ""; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-Wglobal-constructors", + ); OTHER_LDFLAGS = ( + "-Wl,-no_inits", "-nostdlib", "$(LIBSYSTEM_LIBS)", "-umbrella", System, "-L$(SDKROOT)/usr/lib/system", ); + OTHER_TAPI_FLAGS = "-extra-public-header ./include/dlfcn.h -extra-private-header ./dyld3/libdyldEntryVector.h -ObjC++ -std=c++11 "; + PRIVATE_HEADERS_FOLDER_PATH = "/usr/local/include/mach-o"; PRODUCT_NAME = dyld; + PUBLIC_HEADERS_FOLDER_PATH = "/usr//include/mach-o"; SKIP_INSTALL = NO; + SUPPORTS_TEXT_BASED_API = YES; + TAPI_VERIFY_MODE = Pedantic; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_EXPORT_DECL = "__attribute__((visibility(\"default\")))"; WARNING_CFLAGS = ( "-Wmost", "-Wno-four-char-constants", @@ -2072,9 +3083,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_WARN_EMPTY_BODY = YES; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; @@ -2091,21 +3102,26 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_SHADOW = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; + GENERATE_TEXT_BASED_STUBS = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; INSTALLHDRS_COPY_PHASE = YES; + INSTALLHDRS_SCRIPT_PHASE = YES; OTHER_CFLAGS = ""; OTHER_CPLUSPLUSFLAGS = ( "-fno-exceptions", "$(OTHER_CFLAGS)", ); OTHER_LDFLAGS = ( + "-Wl,-no_inits", "-nostdlib", "$(LIBSYSTEM_LIBS)", "-umbrella", System, "-L$(SDKROOT)/usr/lib/system", + "-Wl,-no_inits", ); "OTHER_LDFLAGS[sdk=iphoneos*]" = ( + "-Wl,-no_inits", "-nostdlib", "$(LIBSYSTEM_LIBS)", "-umbrella", @@ -2114,17 +3130,24 @@ "-Wl,-dirty_data_list,$(SRCROOT)/src/libdyld_data_symbols.dirty", ); "OTHER_LDFLAGS[sdk=macosx*]" = ( + "-Wl,-no_inits", "-nostdlib", "$(LIBSYSTEM_LIBS)", "-umbrella", System, "-L$(SDKROOT)/usr/lib/system", ); + OTHER_TAPI_FLAGS = "-extra-public-header ./include/dlfcn.h -extra-private-header ./dyld3/libdyldEntryVector.h -ObjC++ -std=c++11 "; + PRIVATE_HEADERS_FOLDER_PATH = "/usr/local/include/mach-o"; PRODUCT_NAME = dyld; + PUBLIC_HEADERS_FOLDER_PATH = "/usr//include/mach-o"; SEPARATE_STRIP = YES; SKIP_INSTALL = NO; STRIP_INSTALLED_PRODUCT = YES; + SUPPORTS_TEXT_BASED_API = YES; + TAPI_VERIFY_MODE = Pedantic; VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_EXPORT_DECL = "__attribute__((visibility(\"default\")))"; WARNING_CFLAGS = ( "-Wmost", "-Wno-four-char-constants", @@ -2151,10 +3174,30 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD131A4A0E0700BBDD52 /* base.xcconfig */; buildSettings = { + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LIBRARY = "compiler-default"; - ONLY_ACTIVE_ARCH = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = NO; + EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS = "$(inherited) build DerivedData closure-tests"; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx.internal; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx watchos appletvos"; }; name = Debug; }; @@ -2162,9 +3205,104 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD131A4A0E0700BBDD52 /* base.xcconfig */; buildSettings = { + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LIBRARY = "compiler-default"; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = NO; + EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS = "$(inherited) build DerivedData closure-tests"; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; SDKROOT = macosx.internal; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx watchos appletvos"; + }; + name = Release; + }; + F9DDEDB71E2878CB00A753DC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F9EDC0A01F0481B400B030F4 /* closured.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 59GAB85EFG; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "BUILDING_CLOSURED=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; + INSTALL_PATH = /usr/libexec/; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + PLIST_FILE_OUTPUT_FORMAT = binary; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIPFLAGS = "-S"; + VERSIONING_SYSTEM = ""; + }; + name = Debug; + }; + F9DDEDB81E2878CB00A753DC /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F9EDC0A01F0481B400B030F4 /* closured.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 59GAB85EFG; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREPROCESSOR_DEFINITIONS = "BUILDING_CLOSURED=1"; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; + INSTALL_PATH = /usr/libexec/; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + PLIST_FILE_OUTPUT_FORMAT = binary; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIPFLAGS = "-S"; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; @@ -2270,6 +3408,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F922C8181F33B73800D8F479 /* Build configuration list for PBXNativeTarget "libclosured-stub" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F922C8191F33B73800D8F479 /* Debug */, + F922C81A1F33B73800D8F479 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F93937340A94FB2900070A07 /* Build configuration list for PBXNativeTarget "update_dyld_shared_cache_tool" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2279,6 +3426,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F96354421DCD74A400895049 /* Build configuration list for PBXNativeTarget "update_dyld_sim_shared_cache" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F96354431DCD74A400895049 /* Debug */, + F96354441DCD74A400895049 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F97C61AB1DBAD1A900A84CD7 /* Build configuration list for PBXNativeTarget "dyld_closure_util" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F97C61AC1DBAD1A900A84CD7 /* Debug */, + F97C61AD1DBAD1A900A84CD7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F97FF35C1C23638F000ACDD2 /* Build configuration list for PBXNativeTarget "nocr" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2297,6 +3462,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F9AB02C21F329FAA00EE96C4 /* Build configuration list for PBXNativeTarget "libclosured" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F9AB02C01F329FAA00EE96C4 /* Debug */, + F9AB02C11F329FAA00EE96C4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F9D1001714D8D0F100099D91 /* Build configuration list for PBXNativeTarget "dsc_extractor" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2342,6 +3516,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F9DDEDB61E2878CB00A753DC /* Build configuration list for PBXNativeTarget "closured" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F9DDEDB71E2878CB00A753DC /* Debug */, + F9DDEDB81E2878CB00A753DC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F9F2A56B0F7AEEB100B7C9EB /* Build configuration list for PBXNativeTarget "libdsc" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/dyld3/APIs.cpp b/dyld3/APIs.cpp new file mode 100644 index 0000000..360a1cd --- /dev/null +++ b/dyld3/APIs.cpp @@ -0,0 +1,1474 @@ +/* + * Copyright (c) 2017 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 <_simple.h> +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dlfcn.h" +#include "dyld_priv.h" + +#include "AllImages.h" +#include "MachOParser.h" +#include "Loading.h" +#include "Logging.h" +#include "Diagnostics.h" +#include "DyldSharedCache.h" +#include "PathOverrides.h" +#include "APIs.h" +#include "StringUtils.h" + + + +extern "C" { + #include "closuredProtocol.h" +} + + +namespace dyld { + extern dyld_all_image_infos dyld_all_image_infos; +} + + +namespace dyld3 { + + +uint32_t _dyld_image_count(void) +{ + log_apis("_dyld_image_count()\n"); + + return gAllImages.count(); +} + +const mach_header* _dyld_get_image_header(uint32_t imageIndex) +{ + log_apis("_dyld_get_image_header(%d)\n", imageIndex); + + const mach_header* loadAddress; + launch_cache::Image image = gAllImages.findByLoadOrder(imageIndex, &loadAddress); + if ( image.valid() ) + return loadAddress; + return nullptr; +} + +intptr_t _dyld_get_image_slide(const mach_header* mh) +{ + log_apis("_dyld_get_image_slide(%p)\n", mh); + + MachOParser parser(mh); + return parser.getSlide(); +} + +intptr_t _dyld_get_image_vmaddr_slide(uint32_t imageIndex) +{ + log_apis("_dyld_get_image_vmaddr_slide(%d)\n", imageIndex); + + const mach_header* mh = _dyld_get_image_header(imageIndex); + if ( mh != nullptr ) + return dyld3::_dyld_get_image_slide(mh); + return 0; +} + +const char* _dyld_get_image_name(uint32_t imageIndex) +{ + log_apis("_dyld_get_image_name(%d)\n", imageIndex); + + const mach_header* loadAddress; + launch_cache::Image image = gAllImages.findByLoadOrder(imageIndex, &loadAddress); + if ( image.valid() ) + return gAllImages.imagePath(image.binaryData()); + return nullptr; +} + + +static bool nameMatch(const char* installName, const char* libraryName) +{ + const char* leafName = strrchr(installName, '/'); + if ( leafName == NULL ) + leafName = installName; + else + leafName++; + + // -framework case is exact match of leaf name + if ( strcmp(leafName, libraryName) == 0 ) + return true; + + // -lxxx case: leafName must match "lib" ["." ?] ".dylib" + size_t leafNameLen = strlen(leafName); + size_t libraryNameLen = strlen(libraryName); + if ( leafNameLen < (libraryNameLen+9) ) + return false; + if ( strncmp(leafName, "lib", 3) != 0 ) + return false; + if ( strcmp(&leafName[leafNameLen-6], ".dylib") != 0 ) + return false; + if ( strncmp(&leafName[3], libraryName, libraryNameLen) != 0 ) + return false; + return (leafName[libraryNameLen+3] == '.'); +} + + +// +// BETTER, USE: dyld_get_program_sdk_version() +// +// Scans the main executable and returns the version of the specified dylib the program was built against. +// +// The library to find is the leaf name that would have been passed to linker tool +// (e.g. -lfoo or -framework foo would use "foo"). +// +// Returns -1 if the main executable did not link against the specified library, or is malformed. +// +int32_t NSVersionOfLinkTimeLibrary(const char* libraryName) +{ + log_apis("NSVersionOfLinkTimeLibrary(\"%s\")\n", libraryName); + + __block int32_t result = -1; + MachOParser parser(gAllImages.mainExecutable()); + parser.forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t compatVersion, uint32_t currentVersion, bool& stop) { + if ( nameMatch(loadPath, libraryName) ) + result = currentVersion; + }); + log_apis(" NSVersionOfLinkTimeLibrary() => 0x%08X\n", result); + return result; +} + + +// +// Searches loaded images for the requested dylib and returns its current version. +// +// The library to find is the leaf name that would have been passed to linker tool +// (e.g. -lfoo or -framework foo would use "foo"). +// +// If the specified library is not loaded, -1 is returned. +// +int32_t NSVersionOfRunTimeLibrary(const char* libraryName) +{ + log_apis("NSVersionOfRunTimeLibrary(\"%s\")\n", libraryName); + + uint32_t count = gAllImages.count(); + for (uint32_t i=0; i < count; ++i) { + const mach_header* loadAddress; + launch_cache::Image image = gAllImages.findByLoadOrder(i, &loadAddress); + if ( image.valid() ) { + MachOParser parser(loadAddress); + const char* installName; + uint32_t currentVersion; + uint32_t compatVersion; + if ( parser.getDylibInstallName(&installName, &compatVersion, ¤tVersion) && nameMatch(installName, libraryName) ) { + log_apis(" NSVersionOfRunTimeLibrary() => 0x%08X\n", currentVersion); + return currentVersion; + } + } + } + log_apis(" NSVersionOfRunTimeLibrary() => -1\n"); + return -1; +} + + +#if __WATCH_OS_VERSION_MIN_REQUIRED + +static uint32_t watchVersToIOSVers(uint32_t vers) +{ + return vers + 0x00070000; +} + +uint32_t dyld_get_program_sdk_watch_os_version() +{ + log_apis("dyld_get_program_sdk_watch_os_version()\n"); + + Platform platform; + uint32_t minOS; + uint32_t sdk; + + MachOParser parser(gAllImages.mainExecutable()); + if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { + if ( platform == Platform::watchOS ) + return sdk; + } + return 0; +} + +uint32_t dyld_get_program_min_watch_os_version() +{ + log_apis("dyld_get_program_min_watch_os_version()\n"); + + Platform platform; + uint32_t minOS; + uint32_t sdk; + + MachOParser parser(gAllImages.mainExecutable()); + if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { + if ( platform == Platform::watchOS ) + return minOS; // return raw minOS (not mapped to iOS version) + } + return 0; +} +#endif + + +#if TARGET_OS_BRIDGE + +static uint32_t bridgeVersToIOSVers(uint32_t vers) +{ + return vers + 0x00090000; +} + +uint32_t dyld_get_program_sdk_bridge_os_version() +{ + log_apis("dyld_get_program_sdk_bridge_os_version()\n"); + + Platform platform; + uint32_t minOS; + uint32_t sdk; + + MachOParser parser(gAllImages.mainExecutable()); + if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { + if ( platform == Platform::bridgeOS ) + return sdk; + } + return 0; +} + +uint32_t dyld_get_program_min_bridge_os_version() +{ + log_apis("dyld_get_program_min_bridge_os_version()\n"); + + Platform platform; + uint32_t minOS; + uint32_t sdk; + + MachOParser parser(gAllImages.mainExecutable()); + if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { + if ( platform == Platform::bridgeOS ) + return minOS; // return raw minOS (not mapped to iOS version) + } + return 0; +} + +#endif + + +#if !__WATCH_OS_VERSION_MIN_REQUIRED && !__TV_OS_VERSION_MIN_REQUIRED && !TARGET_OS_BRIDGE + +#define PACKED_VERSION(major, minor, tiny) ((((major) & 0xffff) << 16) | (((minor) & 0xff) << 8) | ((tiny) & 0xff)) + +static uint32_t deriveSDKVersFromDylibs(const mach_header* mh) +{ + __block uint32_t foundationVers = 0; + __block uint32_t libSystemVers = 0; + MachOParser parser(mh); + parser.forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t compatVersion, uint32_t currentVersion, bool& stop) { + if ( strcmp(loadPath, "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation") == 0 ) + foundationVers = currentVersion; + else if ( strcmp(loadPath, "/usr/lib/libSystem.B.dylib") == 0 ) + libSystemVers = currentVersion; + }); + + struct DylibToOSMapping { + uint32_t dylibVersion; + uint32_t osVersion; + }; + + #if __IPHONE_OS_VERSION_MIN_REQUIRED + static const DylibToOSMapping foundationMapping[] = { + { PACKED_VERSION(678,24,0), 0x00020000 }, + { PACKED_VERSION(678,26,0), 0x00020100 }, + { PACKED_VERSION(678,29,0), 0x00020200 }, + { PACKED_VERSION(678,47,0), 0x00030000 }, + { PACKED_VERSION(678,51,0), 0x00030100 }, + { PACKED_VERSION(678,60,0), 0x00030200 }, + { PACKED_VERSION(751,32,0), 0x00040000 }, + { PACKED_VERSION(751,37,0), 0x00040100 }, + { PACKED_VERSION(751,49,0), 0x00040200 }, + { PACKED_VERSION(751,58,0), 0x00040300 }, + { PACKED_VERSION(881,0,0), 0x00050000 }, + { PACKED_VERSION(890,1,0), 0x00050100 }, + { PACKED_VERSION(992,0,0), 0x00060000 }, + { PACKED_VERSION(993,0,0), 0x00060100 }, + { PACKED_VERSION(1038,14,0),0x00070000 }, + { PACKED_VERSION(0,0,0), 0x00070000 } + // We don't need to expand this table because all recent + // binaries have LC_VERSION_MIN_ load command. + }; + + if ( foundationVers != 0 ) { + uint32_t lastOsVersion = 0; + for (const DylibToOSMapping* p=foundationMapping; ; ++p) { + if ( p->dylibVersion == 0 ) + return p->osVersion; + if ( foundationVers < p->dylibVersion ) + return lastOsVersion; + lastOsVersion = p->osVersion; + } + } + + #else + // Note: versions are for the GM release. The last entry should + // always be zero. At the start of the next major version, + // a new last entry needs to be added and the previous zero + // updated to the GM dylib version. + static const DylibToOSMapping libSystemMapping[] = { + { PACKED_VERSION(88,1,3), 0x000A0400 }, + { PACKED_VERSION(111,0,0), 0x000A0500 }, + { PACKED_VERSION(123,0,0), 0x000A0600 }, + { PACKED_VERSION(159,0,0), 0x000A0700 }, + { PACKED_VERSION(169,3,0), 0x000A0800 }, + { PACKED_VERSION(1197,0,0), 0x000A0900 }, + { PACKED_VERSION(0,0,0), 0x000A0900 } + // We don't need to expand this table because all recent + // binaries have LC_VERSION_MIN_ load command. + }; + + if ( libSystemVers != 0 ) { + uint32_t lastOsVersion = 0; + for (const DylibToOSMapping* p=libSystemMapping; ; ++p) { + if ( p->dylibVersion == 0 ) + return p->osVersion; + if ( libSystemVers < p->dylibVersion ) + return lastOsVersion; + lastOsVersion = p->osVersion; + } + } + #endif + return 0; +} +#endif + + +// +// Returns the sdk version (encode as nibble XXXX.YY.ZZ) that the +// specified binary was built against. +// +// First looks for LC_VERSION_MIN_* in binary and if sdk field is +// not zero, return that value. +// Otherwise, looks for the libSystem.B.dylib the binary linked +// against and uses a table to convert that to an sdk version. +// +uint32_t dyld_get_sdk_version(const mach_header* mh) +{ + log_apis("dyld_get_sdk_version(%p)\n", mh); + + Platform platform; + uint32_t minOS; + uint32_t sdk; + + if ( !MachOParser::wellFormedMachHeaderAndLoadCommands(mh) ) + return 0; + MachOParser parser(mh); + if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { + switch (platform) { +#if TARGET_OS_BRIDGE + case Platform::bridgeOS: + // new binary. sdk version looks like "2.0" but API wants "11.0" + return bridgeVersToIOSVers(sdk); + case Platform::iOS: + // old binary. sdk matches API semantics so can return directly. + return sdk; +#elif __WATCH_OS_VERSION_MIN_REQUIRED + case Platform::watchOS: + // new binary. sdk version looks like "2.0" but API wants "9.0" + return watchVersToIOSVers(sdk); + case Platform::iOS: + // old binary. sdk matches API semantics so can return directly. + return sdk; +#elif __TV_OS_VERSION_MIN_REQUIRED + case Platform::tvOS: + case Platform::iOS: + return sdk; +#elif __IPHONE_OS_VERSION_MIN_REQUIRED + case Platform::iOS: + if ( sdk != 0 ) // old binaries might not have SDK set + return sdk; + break; +#else + case Platform::macOS: + if ( sdk != 0 ) // old binaries might not have SDK set + return sdk; + break; +#endif + default: + // wrong binary for this platform + break; + } + } + +#if __WATCH_OS_VERSION_MIN_REQUIRED ||__TV_OS_VERSION_MIN_REQUIRED || TARGET_OS_BRIDGE + // All watchOS and tvOS binaries should have version load command. + return 0; +#else + // MacOSX and iOS have old binaries without version load commmand. + return deriveSDKVersFromDylibs(mh); +#endif +} + +uint32_t dyld_get_program_sdk_version() +{ + log_apis("dyld_get_program_sdk_version()\n"); + + return dyld3::dyld_get_sdk_version(gAllImages.mainExecutable()); +} + +uint32_t dyld_get_min_os_version(const mach_header* mh) +{ + log_apis("dyld_get_min_os_version(%p)\n", mh); + + Platform platform; + uint32_t minOS; + uint32_t sdk; + + if ( !MachOParser::wellFormedMachHeaderAndLoadCommands(mh) ) + return 0; + MachOParser parser(mh); + if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { + switch (platform) { +#if TARGET_OS_BRIDGE + case Platform::bridgeOS: + // new binary. sdk version looks like "2.0" but API wants "11.0" + return bridgeVersToIOSVers(minOS); + case Platform::iOS: + // old binary. sdk matches API semantics so can return directly. + return minOS; +#elif __WATCH_OS_VERSION_MIN_REQUIRED + case Platform::watchOS: + // new binary. OS version looks like "2.0" but API wants "9.0" + return watchVersToIOSVers(minOS); + case Platform::iOS: + // old binary. OS matches API semantics so can return directly. + return minOS; +#elif __TV_OS_VERSION_MIN_REQUIRED + case Platform::tvOS: + case Platform::iOS: + return minOS; +#elif __IPHONE_OS_VERSION_MIN_REQUIRED + case Platform::iOS: + return minOS; +#else + case Platform::macOS: + return minOS; +#endif + default: + // wrong binary for this platform + break; + } + } + return 0; +} + + +uint32_t dyld_get_program_min_os_version() +{ + log_apis("dyld_get_program_min_os_version()\n"); + + return dyld3::dyld_get_min_os_version(gAllImages.mainExecutable()); +} + + +bool _dyld_get_image_uuid(const mach_header* mh, uuid_t uuid) +{ + log_apis("_dyld_get_image_uuid(%p, %p)\n", mh, uuid); + + if ( !MachOParser::wellFormedMachHeaderAndLoadCommands(mh) ) + return false; + MachOParser parser(mh); + return parser.getUuid(uuid); +} + +// +// _NSGetExecutablePath() copies the path of the main executable into the buffer. The bufsize parameter +// should initially be the size of the buffer. The function returns 0 if the path was successfully copied, +// and *bufsize is left unchanged. It returns -1 if the buffer is not large enough, and *bufsize is set +// to the size required. +// +int _NSGetExecutablePath(char* buf, uint32_t* bufsize) +{ + log_apis("_NSGetExecutablePath(%p, %p)\n", buf, bufsize); + + launch_cache::Image image = gAllImages.mainExecutableImage(); + if ( image.valid() ) { + const char* path = gAllImages.imagePath(image.binaryData()); + size_t pathSize = strlen(path) + 1; + if ( *bufsize >= pathSize ) { + strcpy(buf, path); + return 0; + } + *bufsize = (uint32_t)pathSize; + } + return -1; +} + +void _dyld_register_func_for_add_image(void (*func)(const mach_header *mh, intptr_t vmaddr_slide)) +{ + log_apis("_dyld_register_func_for_add_image(%p)\n", func); + + gAllImages.addLoadNotifier(func); +} + +void _dyld_register_func_for_remove_image(void (*func)(const mach_header *mh, intptr_t vmaddr_slide)) +{ + log_apis("_dyld_register_func_for_remove_image(%p)\n", func); + + gAllImages.addUnloadNotifier(func); +} + +void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, + _dyld_objc_notify_init init, + _dyld_objc_notify_unmapped unmapped) +{ + log_apis("_dyld_objc_notify_register(%p, %p, %p)\n", mapped, init, unmapped); + + gAllImages.setObjCNotifiers(mapped, init, unmapped); +} + + +const mach_header* dyld_image_header_containing_address(const void* addr) +{ + log_apis("dyld_image_header_containing_address(%p)\n", addr); + + const mach_header* loadAddress; + launch_cache::Image image = gAllImages.findByOwnedAddress(addr, &loadAddress); + if ( image.valid() ) + return loadAddress; + return nullptr; +} + + +const char* dyld_image_path_containing_address(const void* addr) +{ + log_apis("dyld_image_path_containing_address(%p)\n", addr); + + const mach_header* loadAddress; + launch_cache::Image image = gAllImages.findByOwnedAddress(addr, &loadAddress); + if ( image.valid() ) { + const char* path = gAllImages.imagePath(image.binaryData()); + log_apis(" dyld_image_path_containing_address() => %s\n", path); + return path; + } + log_apis(" dyld_image_path_containing_address() => NULL\n"); + return nullptr; +} + +bool _dyld_is_memory_immutable(const void* addr, size_t length) +{ + uintptr_t checkStart = (uintptr_t)addr; + uintptr_t checkEnd = checkStart + length; + + // quick check to see if in r/o region of shared cache. If so return true. + const DyldSharedCache* cache = (DyldSharedCache*)gAllImages.cacheLoadAddress(); + if ( cache != nullptr ) { + __block bool firstVMAddr = 0; + __block bool isReadOnlyInCache = false; + __block bool isInCache = false; + cache->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + if ( firstVMAddr == 0 ) + firstVMAddr = vmAddr; + uintptr_t regionStart = (uintptr_t)cache + (uintptr_t)(vmAddr - firstVMAddr); + uintptr_t regionEnd = regionStart + (uintptr_t)size; + if ( (regionStart < checkStart) && (checkEnd < regionEnd) ) { + isInCache = true; + isReadOnlyInCache = ((permissions & VM_PROT_WRITE) != 0); + } + }); + if ( isInCache ) + return isReadOnlyInCache; + } + + // go slow route of looking at each image's segments + const mach_header* loadAddress; + uint8_t permissions; + launch_cache::Image image = gAllImages.findByOwnedAddress(addr, &loadAddress, &permissions); + if ( !image.valid() ) + return false; + if ( (permissions & VM_PROT_WRITE) != 0 ) + return false; + return !gAllImages.imageUnloadable(image, loadAddress); +} + + +int dladdr(const void* addr, Dl_info* info) +{ + log_apis("dladdr(%p, %p)\n", addr, info); + + const mach_header* loadAddress; + launch_cache::Image image = gAllImages.findByOwnedAddress(addr, &loadAddress); + if ( !image.valid() ) { + log_apis(" dladdr() => 0\n"); + return 0; + } + MachOParser parser(loadAddress); + info->dli_fname = gAllImages.imagePath(image.binaryData()); + info->dli_fbase = (void*)(loadAddress); + if ( addr == info->dli_fbase ) { + // special case lookup of header + info->dli_sname = "__dso_handle"; + info->dli_saddr = info->dli_fbase; + } + else if ( parser.findClosestSymbol(addr, &(info->dli_sname), (const void**)&(info->dli_saddr)) ) { + // never return the mach_header symbol + if ( info->dli_saddr == info->dli_fbase ) { + info->dli_sname = nullptr; + info->dli_saddr = nullptr; + } + // strip off leading underscore + else if ( (info->dli_sname != nullptr) && (info->dli_sname[0] == '_') ) { + info->dli_sname = info->dli_sname + 1; + } + } + else { + info->dli_sname = nullptr; + info->dli_saddr = nullptr; + } + log_apis(" dladdr() => 1, { \"%s\", %p, \"%s\", %p }\n", info->dli_fname, info->dli_fbase, info->dli_sname, info->dli_saddr); + return 1; +} + + +struct PerThreadErrorMessage +{ + size_t sizeAllocated; + bool valid; + char message[1]; +}; + +static pthread_key_t dlerror_perThreadKey() +{ + static dispatch_once_t onceToken; + static pthread_key_t dlerrorPThreadKey; + dispatch_once(&onceToken, ^{ + pthread_key_create(&dlerrorPThreadKey, &free); + }); + return dlerrorPThreadKey; +} + +static void clearErrorString() +{ + PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)pthread_getspecific(dlerror_perThreadKey()); + if ( errorBuffer != nullptr ) + errorBuffer->valid = false; +} + +__attribute__((format(printf, 1, 2))) +static void setErrorString(const char* format, ...) +{ + _SIMPLE_STRING buf = _simple_salloc(); + if ( buf != nullptr ) { + va_list list; + va_start(list, format); + _simple_vsprintf(buf, format, list); + va_end(list); + size_t strLen = strlen(_simple_string(buf)) + 1; + size_t sizeNeeded = sizeof(PerThreadErrorMessage) + strLen; + PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)pthread_getspecific(dlerror_perThreadKey()); + if ( errorBuffer != nullptr ) { + if ( errorBuffer->sizeAllocated < sizeNeeded ) { + free(errorBuffer); + errorBuffer = nullptr; + } + } + if ( errorBuffer == nullptr ) { + size_t allocSize = std::max(sizeNeeded, (size_t)256); + PerThreadErrorMessage* p = (PerThreadErrorMessage*)malloc(allocSize); + p->sizeAllocated = allocSize; + p->valid = false; + pthread_setspecific(dlerror_perThreadKey(), p); + errorBuffer = p; + } + strcpy(errorBuffer->message, _simple_string(buf)); + errorBuffer->valid = true; + _simple_sfree(buf); + } +} + +char* dlerror() +{ + log_apis("dlerror()\n"); + + PerThreadErrorMessage* errorBuffer = (PerThreadErrorMessage*)pthread_getspecific(dlerror_perThreadKey()); + if ( errorBuffer != nullptr ) { + if ( errorBuffer->valid ) { + // you can only call dlerror() once, then the message is cleared + errorBuffer->valid = false; + return errorBuffer->message; + } + } + return nullptr; +} + +#if __arm64__ + #define CURRENT_CPU_TYPE CPU_TYPE_ARM64 +#elif __arm__ + #define CURRENT_CPU_TYPE CPU_TYPE_ARM +#endif + + +class VIS_HIDDEN RecursiveAutoLock +{ +public: + RecursiveAutoLock() { + pthread_mutex_lock(&_sMutex); + } + ~RecursiveAutoLock() { + pthread_mutex_unlock(&_sMutex); + } +private: + static pthread_mutex_t _sMutex; +}; + +pthread_mutex_t RecursiveAutoLock::_sMutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; + +static void* makeDlHandle(const mach_header* mh, bool dontContinue) +{ + uintptr_t flags = (dontContinue ? 1 : 0); + return (void*)((((uintptr_t)mh) >> 5) | flags); +} + +VIS_HIDDEN +void parseDlHandle(void* h, const mach_header** mh, bool* dontContinue) +{ + *dontContinue = (((uintptr_t)h) & 1); + *mh = (const mach_header*)((((uintptr_t)h) & (-2)) << 5); +} + +int dlclose(void* handle) +{ + log_apis("dlclose(%p)\n", handle); + + // silently accept magic handles for main executable + if ( handle == RTLD_MAIN_ONLY ) + return 0; + if ( handle == RTLD_DEFAULT ) + return 0; + + // from here on, serialize all dlopen()s + RecursiveAutoLock dlopenSerializer; + + const mach_header* mh; + bool dontContinue; + parseDlHandle(handle, &mh, &dontContinue); + launch_cache::Image image = gAllImages.findByLoadAddress(mh); + if ( image.valid() ) { + // removes image if reference count went to zero + if ( !image.neverUnload() ) + gAllImages.decRefCount(mh); + clearErrorString(); + return 0; + } + else { + setErrorString("invalid handle passed to dlclose()"); + return -1; + } +} + + + +VIS_HIDDEN +const mach_header* loadImageAndDependents(Diagnostics& diag, const launch_cache::binary_format::Image* imageToLoad, bool bumpDlopenCount) +{ + launch_cache::Image topImage(imageToLoad); + uint32_t maxLoad = topImage.maxLoadCount(); + // first construct array of all BinImage* objects that dlopen'ed image depends on + const dyld3::launch_cache::binary_format::Image* fullImageList[maxLoad]; + dyld3::launch_cache::SlowLoadSet imageSet(&fullImageList[0], &fullImageList[maxLoad]); + imageSet.add(imageToLoad); + STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, gAllImages.currentGroupsCount(), currentGroupsList); + gAllImages.copyCurrentGroups(currentGroupsList); + if ( !topImage.recurseAllDependentImages(currentGroupsList, imageSet, nullptr) ) { + diag.error("unexpected > %d images loaded", maxLoad); + return nullptr; + } + + // build array of BinImage* that are not already loaded + const dyld3::launch_cache::binary_format::Image* toLoadImageList[maxLoad]; + const dyld3::launch_cache::binary_format::Image** toLoadImageArray = toLoadImageList; + __block int needToLoadCount = 0; + imageSet.forEach(^(const dyld3::launch_cache::binary_format::Image* aBinImage) { + if ( gAllImages.findLoadAddressByImage(aBinImage) == nullptr ) + toLoadImageArray[needToLoadCount++] = aBinImage; + }); + assert(needToLoadCount > 0); + + // build one array of all existing and to-be-loaded images + uint32_t alreadyLoadImageCount = gAllImages.count(); + STACK_ALLOC_DYNARRAY(loader::ImageInfo, alreadyLoadImageCount + needToLoadCount, allImages); + loader::ImageInfo* allImagesArray = &allImages[0]; + gAllImages.forEachImage(^(uint32_t imageIndex, const mach_header* loadAddress, const launch_cache::Image image, bool& stop) { + launch_cache::ImageGroup grp = image.group(); + loader::ImageInfo& info= allImagesArray[imageIndex]; + info.imageData = image.binaryData(); + info.loadAddress = loadAddress; + info.groupNum = grp.groupNum(); + info.indexInGroup = grp.indexInGroup(info.imageData); + info.previouslyFixedUp = true; + info.justMapped = false; + info.justUsedFromDyldCache = false; + info.neverUnload = false; + }); + for (int i=0; i < needToLoadCount; ++i) { + launch_cache::Image img(toLoadImageArray[i]); + launch_cache::ImageGroup grp = img.group(); + loader::ImageInfo& info= allImages[alreadyLoadImageCount+i]; + info.imageData = toLoadImageArray[i]; + info.loadAddress = nullptr; + info.groupNum = grp.groupNum(); + info.indexInGroup = grp.indexInGroup(img.binaryData()); + info.previouslyFixedUp = false; + info.justMapped = false; + info.justUsedFromDyldCache = false; + info.neverUnload = false; + } + + // map new images and apply all fixups + mapAndFixupImages(diag, allImages, (const uint8_t*)gAllImages.cacheLoadAddress(), &dyld3::log_loads, &dyld3::log_segments, &dyld3::log_fixups, &dyld3::log_dofs); + if ( diag.hasError() ) + return nullptr; + const mach_header* topLoadAddress = allImages[alreadyLoadImageCount].loadAddress; + + // bump dlopen refcount of image directly loaded + if ( bumpDlopenCount ) + gAllImages.incRefCount(topLoadAddress); + + // tell gAllImages about new images + dyld3::launch_cache::DynArray newImages(needToLoadCount, &allImages[alreadyLoadImageCount]); + gAllImages.addImages(newImages); + + // tell gAllImages about any old images which now have never unload set + for (int i=0; i < alreadyLoadImageCount; ++i) { + if (allImages[i].neverUnload && !allImages[i].imageData->neverUnload) + gAllImages.setNeverUnload(allImages[i]); + } + + // run initializers + gAllImages.runInitialzersBottomUp(topLoadAddress); + + return topLoadAddress; +} + + +void* dlopen(const char* path, int mode) +{ + log_apis("dlopen(\"%s\", 0x%08X)\n", ((path==NULL) ? "NULL" : path), mode); + + clearErrorString(); + + // 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 + if ( (mode & RTLD_FIRST) != 0 ) + return RTLD_MAIN_ONLY; + else + return RTLD_DEFAULT; + } + + // from here on, serialize all dlopen()s + RecursiveAutoLock dlopenSerializer; + + const char* leafName = strrchr(path, '/'); + if ( leafName != nullptr ) + ++leafName; + else + leafName = path; + + // RTLD_FIRST means when dlsym() is called with handle, only search the image and not those loaded after it + bool dontContinue = (mode & RTLD_FIRST); + bool bumpRefCount = true; + + // check if dylib with same inode/mtime is already loaded + __block const mach_header* alreadyLoadMH = nullptr; + struct stat statBuf; + if ( stat(path, &statBuf) == 0 ) { + alreadyLoadMH = gAllImages.alreadyLoaded(statBuf.st_ino, statBuf.st_mtime, bumpRefCount); + if ( alreadyLoadMH != nullptr) { + log_apis(" dlopen: path inode/mtime matches already loaded image\n"); + void* result = makeDlHandle(alreadyLoadMH, dontContinue); + log_apis(" dlopen(%s) => %p\n", leafName, result); + return result; + } + } + + // check if already loaded, and if so, just bump ref-count + gPathOverrides.forEachPathVariant(path, ^(const char* possiblePath, bool& stop) { + alreadyLoadMH = gAllImages.alreadyLoaded(possiblePath, bumpRefCount); + if ( alreadyLoadMH != nullptr ) { + log_apis(" dlopen: matches already loaded image %s\n", possiblePath); + stop = true; + } + }); + if ( alreadyLoadMH != nullptr) { + void* result = makeDlHandle(alreadyLoadMH, dontContinue); + log_apis(" dlopen(%s) => %p\n", leafName, result); + return result; + } + + // it may be that the path supplied is a symlink to something already loaded + char resolvedPath[PATH_MAX]; + const char* realPathResult = realpath(path, resolvedPath); + // If realpath() resolves to a path which does not exist on disk, errno is set to ENOENT + bool checkRealPathToo = ((realPathResult != nullptr) || (errno == ENOENT)) && (strcmp(path, resolvedPath) != 0); + if ( checkRealPathToo ) { + alreadyLoadMH = gAllImages.alreadyLoaded(resolvedPath, bumpRefCount); + log_apis(" dlopen: real path=%s\n", resolvedPath); + if ( alreadyLoadMH != nullptr) { + void* result = makeDlHandle(alreadyLoadMH, dontContinue); + log_apis(" dlopen(%s) => %p\n", leafName, result); + return result; + } + } + + // check if image is in a known ImageGroup + __block const launch_cache::binary_format::Image* imageToLoad = nullptr; + gPathOverrides.forEachPathVariant(path, ^(const char* possiblePath, bool& stop) { + log_apis(" dlopen: checking for pre-built closure for path: %s\n", possiblePath); + imageToLoad = gAllImages.findImageInKnownGroups(possiblePath); + if ( imageToLoad != nullptr ) + stop = true; + }); + if ( (imageToLoad == nullptr) && checkRealPathToo ) { + gPathOverrides.forEachPathVariant(resolvedPath, ^(const char* possiblePath, bool& stop) { + log_apis(" dlopen: checking for pre-built closure for real path: %s\n", possiblePath); + imageToLoad = gAllImages.findImageInKnownGroups(possiblePath); + if ( imageToLoad != nullptr ) + stop = true; + }); + } + + // check if image from a known ImageGroup is already loaded (via a different path) + if ( imageToLoad != nullptr ) { + alreadyLoadMH = gAllImages.alreadyLoaded(imageToLoad, bumpRefCount); + if ( alreadyLoadMH != nullptr) { + void* result = makeDlHandle(alreadyLoadMH, dontContinue); + log_apis(" dlopen(%s) => %p\n", leafName, result); + return result; + } + } + + // RTLD_NOLOAD means do nothing if image not already loaded + if ( mode & RTLD_NOLOAD ) { + log_apis(" dlopen(%s) => NULL\n", leafName); + return nullptr; + } + + // if we have a closure, optimistically use it. If out of date, it will fail + if ( imageToLoad != nullptr ) { + log_apis(" dlopen: trying existing closure image=%p\n", imageToLoad); + Diagnostics diag; + const mach_header* topLoadAddress = loadImageAndDependents(diag, imageToLoad, true); + if ( diag.noError() ) { + void* result = makeDlHandle(topLoadAddress, dontContinue); + log_apis(" dlopen(%s) => %p\n", leafName, result); + return result; + } + // image is no longer valid, will need to build one + imageToLoad = nullptr; + log_apis(" dlopen: existing closure no longer valid\n"); + } + + // if no existing closure, RPC to closured to create one + const char* closuredErrorMessages[3]; + int closuredErrorMessagesCount = 0; + if ( imageToLoad == nullptr ) { + imageToLoad = gAllImages.messageClosured(path, "dlopen", closuredErrorMessages, closuredErrorMessagesCount); + } + + // load images using new closure + if ( imageToLoad != nullptr ) { + log_apis(" dlopen: using closured built image=%p\n", imageToLoad); + Diagnostics diag; + const mach_header* topLoadAddress = loadImageAndDependents(diag, imageToLoad, true); + if ( diag.noError() ) { + void* result = makeDlHandle(topLoadAddress, dontContinue); + log_apis(" dlopen(%s) => %p\n", leafName, result); + return result; + } + if ( closuredErrorMessagesCount < 3 ) { + closuredErrorMessages[closuredErrorMessagesCount++] = strdup(diag.errorMessage()); + } + } + + // otherwise, closured failed to build needed load info + switch ( closuredErrorMessagesCount ) { + case 0: + setErrorString("dlopen(%s, 0x%04X): closured error", path, mode); + log_apis(" dlopen: closured error\n"); + break; + case 1: + setErrorString("dlopen(%s, 0x%04X): %s", path, mode, closuredErrorMessages[0]); + log_apis(" dlopen: closured error: %s\n", closuredErrorMessages[0]); + break; + case 2: + setErrorString("dlopen(%s, 0x%04X): %s %s", path, mode, closuredErrorMessages[0], closuredErrorMessages[1]); + log_apis(" dlopen: closured error: %s %s\n", closuredErrorMessages[0], closuredErrorMessages[1]); + break; + case 3: + setErrorString("dlopen(%s, 0x%04X): %s %s %s", path, mode, closuredErrorMessages[0], closuredErrorMessages[1], closuredErrorMessages[2]); + log_apis(" dlopen: closured error: %s %s %s\n", closuredErrorMessages[0], closuredErrorMessages[1], closuredErrorMessages[2]); + break; + } + for (int i=0; i < closuredErrorMessagesCount;++i) + free((void*)closuredErrorMessages[i]); + + log_apis(" dlopen(%s) => NULL\n", leafName); + + return nullptr; +} + +bool dlopen_preflight(const char* path) +{ + log_apis("dlopen_preflight(%s)\n", path); + + if ( gAllImages.alreadyLoaded(path, false) != nullptr ) + return true; + + if ( gAllImages.findImageInKnownGroups(path) != nullptr ) + return true; + + // map whole file + struct stat statBuf; + if ( ::stat(path, &statBuf) != 0 ) + return false; + int fd = ::open(path, O_RDONLY); + if ( fd < 0 ) + return false; + const void* fileBuffer = ::mmap(NULL, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + ::close(fd); + if ( fileBuffer == MAP_FAILED ) + return false; + size_t mappedSize = (size_t)statBuf.st_size; + + // check if it is current arch mach-o or fat with slice for current arch + __block bool result = false; + __block Diagnostics diag; + if ( MachOParser::isMachO(diag, fileBuffer, mappedSize) ) { + result = true; + } + else { + if ( FatUtil::isFatFile(fileBuffer) ) { + FatUtil::forEachSlice(diag, fileBuffer, mappedSize, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSz, bool& stop) { + if ( MachOParser::isMachO(diag, sliceStart, sliceSz) ) { + result = true; + stop = true; + } + }); + } + } + ::munmap((void*)fileBuffer, mappedSize); + + // FIXME: may be symlink to something in dyld cache + + // FIXME: maybe ask closured + + return result; +} + +static void* dlsym_search(const char* symName, const mach_header* startImageLoadAddress, const launch_cache::Image& startImage, bool searchStartImage, MachOParser::DependentFinder reExportFollower) +{ + // construct array of all BinImage* objects that dlopen'ed image depends on + uint32_t maxLoad = startImage.maxLoadCount(); + const dyld3::launch_cache::binary_format::Image* fullImageList[maxLoad]; + dyld3::launch_cache::SlowLoadSet imageSet(&fullImageList[0], &fullImageList[maxLoad]); + imageSet.add(startImage.binaryData()); + STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, gAllImages.currentGroupsCount(), currentGroupsList); + gAllImages.copyCurrentGroups(currentGroupsList); + + __block void* result = nullptr; + auto handler = ^(const dyld3::launch_cache::binary_format::Image* aBinImage, bool& stop) { + const mach_header* loadAddress = gAllImages.findLoadAddressByImage(aBinImage); + if ( !searchStartImage && (loadAddress == startImageLoadAddress) ) + return; + if ( loadAddress != nullptr ) { + MachOParser parser(loadAddress); + if ( parser.hasExportedSymbol(symName, reExportFollower, &result) ) { + stop = true; + } + } + }; + + bool stop = false; + handler(startImage.binaryData(), stop); + if (stop) + return result; + + // check each dependent image for symbol + if ( !startImage.recurseAllDependentImages(currentGroupsList, imageSet, handler) ) { + setErrorString("unexpected > %d images loaded", maxLoad); + return nullptr; + } + return result; +} + +void* dlsym(void* handle, const char* symbolName) +{ + log_apis("dlsym(%p, \"%s\")\n", handle, symbolName); + + clearErrorString(); + + // dlsym() assumes symbolName passed in is same as in C source code + // dyld assumes all symbol names have an underscore prefix + char underscoredName[strlen(symbolName)+2]; + underscoredName[0] = '_'; + strcpy(&underscoredName[1], symbolName); + + // this block is only used if hasExportedSymbol() needs to trace re-exported dylibs to find a symbol + MachOParser::DependentFinder reExportFollower = ^(uint32_t targetDepIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) { + if ( (strncmp(depLoadPath, "@rpath/", 7) == 0) && (extra != nullptr) ) { + const mach_header* parentMH = (mach_header*)extra; + launch_cache::Image parentImage = gAllImages.findByLoadAddress(parentMH); + if ( parentImage.valid() ) { + STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, gAllImages.currentGroupsCount(), currentGroupsList); + gAllImages.copyCurrentGroups(currentGroupsList); + parentImage.forEachDependentImage(currentGroupsList, ^(uint32_t parentDepIndex, dyld3::launch_cache::Image parentDepImage, dyld3::launch_cache::Image::LinkKind kind, bool &stop) { + if ( parentDepIndex != targetDepIndex ) + return; + const mach_header* parentDepMH = gAllImages.findLoadAddressByImage(parentDepImage.binaryData()); + if ( parentDepMH != nullptr ) { + *foundMH = parentDepMH; + stop = true; + } + }); + } + } + else { + *foundMH = gAllImages.alreadyLoaded(depLoadPath, false); + } + return (*foundMH != nullptr); + }; + + if ( handle == RTLD_DEFAULT ) { + // magic "search all in load order" handle + for (uint32_t index=0; index < gAllImages.count(); ++index) { + const mach_header* loadAddress; + launch_cache::Image image = gAllImages.findByLoadOrder(index, &loadAddress); + if ( image.valid() ) { + MachOParser parser(loadAddress); + void* result; + //log_apis(" dlsym(): index=%d, loadAddress=%p\n", index, loadAddress); + if ( parser.hasExportedSymbol(underscoredName, reExportFollower, &result) ) { + log_apis(" dlsym() => %p\n", result); + return result; + } + } + } + setErrorString("dlsym(RTLD_DEFAULT, %s): symbol not found", symbolName); + log_apis(" dlsym() => NULL\n"); + return nullptr; + } + else if ( handle == RTLD_MAIN_ONLY ) { + // magic "search only main executable" handle + MachOParser parser(gAllImages.mainExecutable()); + //log_apis(" dlsym(): index=%d, loadAddress=%p\n", index, loadAddress); + void* result; + if ( parser.hasExportedSymbol(underscoredName, reExportFollower, &result) ) { + log_apis(" dlsym() => %p\n", result); + return result; + } + setErrorString("dlsym(RTLD_MAIN_ONLY, %s): symbol not found", symbolName); + log_apis(" dlsym() => NULL\n"); + return nullptr; + } + + // rest of cases search in dependency order + const mach_header* startImageLoadAddress; + launch_cache::Image startImage(nullptr); + void* result = nullptr; + if ( handle == RTLD_NEXT ) { + // magic "search what I would see" handle + void* callerAddress = __builtin_return_address(0); + startImage = gAllImages.findByOwnedAddress(callerAddress, &startImageLoadAddress); + if ( ! startImage.valid() ) { + setErrorString("dlsym(RTLD_NEXT, %s): called by unknown image (caller=%p)", symbolName, callerAddress); + return nullptr; + } + result = dlsym_search(underscoredName, startImageLoadAddress, startImage, false, reExportFollower); + } + else if ( handle == RTLD_SELF ) { + // magic "search me, then what I would see" handle + void* callerAddress = __builtin_return_address(0); + startImage = gAllImages.findByOwnedAddress(callerAddress, &startImageLoadAddress); + if ( ! startImage.valid() ) { + setErrorString("dlsym(RTLD_SELF, %s): called by unknown image (caller=%p)", symbolName, callerAddress); + return nullptr; + } + result = dlsym_search(underscoredName, startImageLoadAddress, startImage, true, reExportFollower); + } + else { + // handle value was something returned by dlopen() + bool dontContinue; + parseDlHandle(handle, &startImageLoadAddress, &dontContinue); + startImage = gAllImages.findByLoadAddress(startImageLoadAddress); + if ( !startImage.valid() ) { + setErrorString("dlsym(%p, %s): invalid handle", handle, symbolName); + log_apis(" dlsym() => NULL\n"); + return nullptr; + } + if ( dontContinue ) { + // RTLD_FIRST only searches one place + MachOParser parser(startImageLoadAddress); + parser.hasExportedSymbol(underscoredName, reExportFollower, &result); + } + else { + result = dlsym_search(underscoredName, startImageLoadAddress, startImage, true, reExportFollower); + } + } + + if ( result != nullptr ) { + log_apis(" dlsym() => %p\n", result); + return result; + } + + setErrorString("dlsym(%p, %s): symbol not found", handle, symbolName); + log_apis(" dlsym() => NULL\n"); + return nullptr; +} + + +const struct dyld_all_image_infos* _dyld_get_all_image_infos() +{ + return gAllImages.oldAllImageInfo(); +} + +bool dyld_shared_cache_some_image_overridden() +{ + log_apis("dyld_shared_cache_some_image_overridden()\n"); + + assert(0 && "not implemented yet"); +} + +bool _dyld_get_shared_cache_uuid(uuid_t uuid) +{ + log_apis("_dyld_get_shared_cache_uuid()\n"); + + if ( gAllImages.oldAllImageInfo() != nullptr ) { + memcpy(uuid, gAllImages.oldAllImageInfo()->sharedCacheUUID, sizeof(uuid_t)); + return true; + } + return false; +} + +const void* _dyld_get_shared_cache_range(size_t* mappedSize) +{ + log_apis("_dyld_get_shared_cache_range()\n"); + + const DyldSharedCache* sharedCache = (DyldSharedCache*)gAllImages.cacheLoadAddress(); + if ( sharedCache != nullptr ) { + *mappedSize = (size_t)sharedCache->mappedSize(); + return sharedCache; + } + *mappedSize = 0; + return NULL; +} + +bool _dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info) +{ + log_apis("_dyld_find_unwind_sections(%p, %p)\n", addr, info); + + const mach_header* mh = dyld_image_header_containing_address(addr); + if ( mh == nullptr ) + return false; + + info->mh = mh; + info->dwarf_section = nullptr; + info->dwarf_section_length = 0; + info->compact_unwind_section = nullptr; + info->compact_unwind_section_length = 0; + + MachOParser parser(mh); + parser.forEachSection(^(const char* segName, const char* sectName, uint32_t flags, const void* content, size_t sectSize, bool illegalSectionSize, bool& stop) { + if ( strcmp(segName, "__TEXT") == 0 ) { + if ( strcmp(sectName, "__eh_frame") == 0 ) { + info->dwarf_section = content; + info->dwarf_section_length = sectSize; + } + else if ( strcmp(sectName, "__unwind_info") == 0 ) { + info->compact_unwind_section = content; + info->compact_unwind_section_length = sectSize; + } + } + }); + + return true; +} + + +bool dyld_process_is_restricted() +{ + log_apis("dyld_process_is_restricted()\n"); + + launch_cache::Closure closure(gAllImages.mainClosure()); + return closure.isRestricted(); +} + + +const char* dyld_shared_cache_file_path() +{ + log_apis("dyld_shared_cache_file_path()\n"); + + return gAllImages.dyldCachePath(); +} + + +void dyld_dynamic_interpose(const mach_header* mh, const dyld_interpose_tuple array[], size_t count) +{ + log_apis("dyld_dynamic_interpose(%p, %p, %lu)\n", mh, array, count); + // FIXME +} + + +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 DyldSharedCache* findCacheInDirAndMap(const uuid_t cacheUuid, const char* dirPath, size_t& sizeMapped) +{ + 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 DyldSharedCache* cache = (DyldSharedCache*)mapStartOfCache(cachePath, 0x00100000) ) { + uuid_t foundUuid; + cache->getUUID(foundUuid); + if ( ::memcmp(foundUuid, cacheUuid, 16) != 0 ) { + // wrong uuid, unmap and keep looking + ::munmap((void*)cache, 0x00100000); + } + else { + // found cache + closedir(dirp); + sizeMapped = 0x00100000; + return cache; + } + } + } + closedir(dirp); + } + return nullptr; +} + +int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extraSearchDirs[], void (^callback)(const dyld_shared_cache_dylib_text_info* info)) +{ + log_apis("dyld_shared_cache_find_iterate_text()\n"); + + // see if requested cache is the active one in this process + size_t sizeMapped = 0; + const DyldSharedCache* sharedCache = (DyldSharedCache*)gAllImages.cacheLoadAddress(); + if ( sharedCache != nullptr ) { + uuid_t runningUuid; + sharedCache->getUUID(runningUuid); + if ( ::memcmp(runningUuid, cacheUuid, 16) != 0 ) + sharedCache = nullptr; + } + if ( sharedCache == nullptr ) { + // if not, look in 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 + sharedCache = findCacheInDirAndMap(cacheUuid, defaultSearchDir, sizeMapped); + // if not there, look in extra search locations + if ( sharedCache == nullptr ) { + for (const char** p = extraSearchDirs; *p != nullptr; ++p) { + sharedCache = findCacheInDirAndMap(cacheUuid, *p, sizeMapped); + if ( sharedCache != nullptr ) + break; + } + } + } + if ( sharedCache == nullptr ) + return -1; + + // get base address of cache + __block uint64_t cacheUnslidBaseAddress = 0; + sharedCache->forEachRegion(^(const void *content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + if ( cacheUnslidBaseAddress == 0 ) + cacheUnslidBaseAddress = vmAddr; + }); + + // iterate all images + sharedCache->forEachImageTextSegment(^(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const uuid_t dylibUUID, const char* installName) { + dyld_shared_cache_dylib_text_info dylibTextInfo; + dylibTextInfo.version = 2; + dylibTextInfo.loadAddressUnslid = loadAddressUnslid; + dylibTextInfo.textSegmentSize = textSegmentSize; + dylibTextInfo.path = installName; + ::memcpy(dylibTextInfo.dylibUuid, dylibUUID, 16); + dylibTextInfo.textSegmentOffset = loadAddressUnslid - cacheUnslidBaseAddress; + callback(&dylibTextInfo); + }); + + if ( sizeMapped != 0 ) + ::munmap((void*)sharedCache, sizeMapped); + + return 0; +} + +int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callback)(const dyld_shared_cache_dylib_text_info* info)) +{ + log_apis("dyld_shared_cache_iterate_text()\n"); + + const char* extraSearchDirs[] = { NULL }; + return dyld3::dyld_shared_cache_find_iterate_text(cacheUuid, extraSearchDirs, callback); +} + + + +} // namespace dyld3 + diff --git a/dyld3/APIs.h b/dyld3/APIs.h new file mode 100644 index 0000000..544f090 --- /dev/null +++ b/dyld3/APIs.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2017 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_APIS_H__ +#define __DYLD_APIS_H__ + +#include +#include + +#include "dlfcn.h" +#include "dyld_priv.h" + + +#define TEMP_HIDDEN __attribute__((visibility("hidden"))) + +namespace dyld3 { + + +uint32_t _dyld_image_count() TEMP_HIDDEN; + +const mach_header* _dyld_get_image_header(uint32_t imageIndex) TEMP_HIDDEN; + +intptr_t _dyld_get_image_slide(const mach_header* mh) TEMP_HIDDEN; + +intptr_t _dyld_get_image_vmaddr_slide(uint32_t imageIndex) TEMP_HIDDEN; + +const char* _dyld_get_image_name(uint32_t imageIndex) TEMP_HIDDEN; + +int32_t NSVersionOfLinkTimeLibrary(const char* libraryName) TEMP_HIDDEN; + +int32_t NSVersionOfRunTimeLibrary(const char* libraryName) TEMP_HIDDEN; + +#if __WATCH_OS_VERSION_MIN_REQUIRED +uint32_t dyld_get_program_sdk_watch_os_version() TEMP_HIDDEN; + +uint32_t dyld_get_program_min_watch_os_version() TEMP_HIDDEN; +#endif + +#if TARGET_OS_BRIDGE +uint32_t dyld_get_program_sdk_bridge_os_version() TEMP_HIDDEN; + +uint32_t dyld_get_program_min_bridge_os_version() TEMP_HIDDEN; +#endif + + +uint32_t dyld_get_sdk_version(const mach_header* mh) TEMP_HIDDEN; + + +uint32_t dyld_get_program_sdk_version() TEMP_HIDDEN; +uint32_t dyld_get_min_os_version(const mach_header* mh) TEMP_HIDDEN; + +uint32_t dyld_get_program_min_os_version() TEMP_HIDDEN; + + +bool _dyld_get_image_uuid(const mach_header* mh, uuid_t uuid) TEMP_HIDDEN; + +int _NSGetExecutablePath(char* buf, uint32_t* bufsize) TEMP_HIDDEN; + +void _dyld_register_func_for_add_image(void (*func)(const mach_header *mh, intptr_t vmaddr_slide)) TEMP_HIDDEN; + +void _dyld_register_func_for_remove_image(void (*func)(const mach_header *mh, intptr_t vmaddr_slide)) TEMP_HIDDEN; + +void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, + _dyld_objc_notify_init init, + _dyld_objc_notify_unmapped unmapped) TEMP_HIDDEN; + +const mach_header* dyld_image_header_containing_address(const void* addr) TEMP_HIDDEN; + +const mach_header* _dyld_get_image_header_containing_address(const void* address) TEMP_HIDDEN; + +bool _dyld_image_containing_address(const void* address) TEMP_HIDDEN; + +const char* dyld_image_path_containing_address(const void* addr) TEMP_HIDDEN; + +bool _dyld_is_memory_immutable(const void* addr, size_t length) TEMP_HIDDEN; + + +int dladdr(const void* addr, Dl_info* info) TEMP_HIDDEN; + +char* dlerror() TEMP_HIDDEN; + +int dlclose(void* handle) TEMP_HIDDEN; + +void* dlopen(const char* path, int mode) TEMP_HIDDEN; + +bool dlopen_preflight(const char* path) TEMP_HIDDEN; + +void* dlsym(void* handle, const char* symbolName) TEMP_HIDDEN; + +const struct dyld_all_image_infos* _dyld_get_all_image_infos() TEMP_HIDDEN; + +bool dyld_shared_cache_some_image_overridden() TEMP_HIDDEN; + +bool _dyld_get_shared_cache_uuid(uuid_t uuid) TEMP_HIDDEN; + +const void* _dyld_get_shared_cache_range(size_t* length) TEMP_HIDDEN; + +bool _dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info) TEMP_HIDDEN; + +bool dyld_process_is_restricted() TEMP_HIDDEN; + +const char* dyld_shared_cache_file_path() TEMP_HIDDEN; + +void dyld_dynamic_interpose(const mach_header* mh, const dyld_interpose_tuple array[], size_t count) TEMP_HIDDEN; + +int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extraSearchDirs[], void (^callback)(const dyld_shared_cache_dylib_text_info* info)) TEMP_HIDDEN; + +int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callback)(const dyld_shared_cache_dylib_text_info* info)) TEMP_HIDDEN; + +void _dyld_fork_child() TEMP_HIDDEN; + +// only in macOS and deprecated +#if __MAC_OS_X_VERSION_MIN_REQUIRED +NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* pathName, NSObjectFileImage *objectFileImage) TEMP_HIDDEN; +NSObjectFileImageReturnCode NSCreateObjectFileImageFromMemory(const void *address, size_t size, NSObjectFileImage *objectFileImage) TEMP_HIDDEN; +bool NSDestroyObjectFileImage(NSObjectFileImage objectFileImage) TEMP_HIDDEN; +uint32_t NSSymbolDefinitionCountInObjectFileImage(NSObjectFileImage objectFileImage) TEMP_HIDDEN; +const char* NSSymbolDefinitionNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal) TEMP_HIDDEN; +uint32_t NSSymbolReferenceCountInObjectFileImage(NSObjectFileImage objectFileImage) TEMP_HIDDEN; +const char* NSSymbolReferenceNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal, bool *tentative_definition) TEMP_HIDDEN; +bool NSIsSymbolDefinedInObjectFileImage(NSObjectFileImage objectFileImage, const char* symbolName) TEMP_HIDDEN; +void* NSGetSectionDataInObjectFileImage(NSObjectFileImage objectFileImage, const char* segmentName, const char* sectionName, size_t *size) TEMP_HIDDEN; +const char* NSNameOfModule(NSModule m) TEMP_HIDDEN; +const char* NSLibraryNameForModule(NSModule m) TEMP_HIDDEN; +NSModule NSLinkModule(NSObjectFileImage objectFileImage, const char* moduleName, uint32_t options) TEMP_HIDDEN; +bool NSUnLinkModule(NSModule module, uint32_t options) TEMP_HIDDEN; +bool NSIsSymbolNameDefined(const char* symbolName) TEMP_HIDDEN; +bool NSIsSymbolNameDefinedWithHint(const char* symbolName, const char* libraryNameHint) TEMP_HIDDEN; +bool NSIsSymbolNameDefinedInImage(const struct mach_header* image, const char* symbolName) TEMP_HIDDEN; +NSSymbol NSLookupAndBindSymbol(const char* symbolName) TEMP_HIDDEN; +NSSymbol NSLookupAndBindSymbolWithHint(const char* symbolName, const char* libraryNameHint) TEMP_HIDDEN; +NSSymbol NSLookupSymbolInModule(NSModule module, const char* symbolName) TEMP_HIDDEN; +NSSymbol NSLookupSymbolInImage(const struct mach_header* image, const char* symbolName, uint32_t options) TEMP_HIDDEN; +const char* NSNameOfSymbol(NSSymbol symbol) TEMP_HIDDEN; +void* NSAddressOfSymbol(NSSymbol symbol) TEMP_HIDDEN; +NSModule NSModuleForSymbol(NSSymbol symbol) TEMP_HIDDEN; +void NSLinkEditError(NSLinkEditErrors *c, int *errorNumber, const char** fileName, const char** errorString) TEMP_HIDDEN; +bool NSAddLibrary(const char* pathName) TEMP_HIDDEN; +bool NSAddLibraryWithSearching(const char* pathName) TEMP_HIDDEN; +const struct mach_header* NSAddImage(const char* image_name, uint32_t options) TEMP_HIDDEN; +void NSInstallLinkEditErrorHandlers(const NSLinkEditErrorHandlers *handlers) TEMP_HIDDEN; +bool _dyld_present(void) TEMP_HIDDEN; +bool _dyld_launched_prebound(void) TEMP_HIDDEN; +bool _dyld_all_twolevel_modules_prebound(void) TEMP_HIDDEN; +bool _dyld_bind_fully_image_containing_address(const void* address) TEMP_HIDDEN; +bool _dyld_image_containing_address(const void* address) TEMP_HIDDEN; +void _dyld_lookup_and_bind(const char* symbol_name, void **address, NSModule* module) TEMP_HIDDEN; +void _dyld_lookup_and_bind_with_hint(const char* symbol_name, const char* library_name_hint, void** address, NSModule* module) TEMP_HIDDEN; +void _dyld_lookup_and_bind_fully(const char* symbol_name, void** address, NSModule* module) TEMP_HIDDEN; +const struct mach_header* _dyld_get_image_header_containing_address(const void* address) TEMP_HIDDEN; +#endif + +} // namespace dyld3 + +#endif // __DYLD_APIS_H__ + + diff --git a/dyld3/APIs_macOS.cpp b/dyld3/APIs_macOS.cpp new file mode 100644 index 0000000..41fdff3 --- /dev/null +++ b/dyld3/APIs_macOS.cpp @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2017 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 <_simple.h> +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dlfcn.h" +#include "dyld_priv.h" + +#include "AllImages.h" +#include "MachOParser.h" +#include "Loading.h" +#include "Logging.h" +#include "Diagnostics.h" +#include "DyldSharedCache.h" +#include "APIs.h" + + + +typedef dyld3::launch_cache::binary_format::Image BinaryImage; + + +namespace dyld3 { + +// from APIs.cpp +void parseDlHandle(void* h, const mach_header** mh, bool* dontContinue); +const mach_header* loadImageAndDependents(Diagnostics& diag, const launch_cache::binary_format::Image* imageToLoad, bool bumpDlopenCount); + + +// only in macOS and deprecated +#if __MAC_OS_X_VERSION_MIN_REQUIRED + +// macOS needs to support an old API that only works with fileype==MH_BUNDLE. +// In this deprecated API (unlike dlopen), loading and linking are separate steps. +// NSCreateObjectFileImageFrom*() just maps in the bundle mach-o file. +// NSLinkModule() does the load of dependent modules and rebasing/binding. +// To unload one of these, you must call NSUnLinkModule() and NSDestroyObjectFileImage() in any order! +// + +NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* path, NSObjectFileImage* ofi) +{ + log_apis("NSCreateObjectFileImageFromFile(\"%s\", %p)\n", path, ofi); + + // verify path exists + struct stat statbuf; + if ( ::stat(path, &statbuf) == -1 ) + return NSObjectFileImageFailure; + + // create ofi that just contains path. NSLinkModule does all the work + __NSObjectFileImage* result = gAllImages.addNSObjectFileImage(); + result->path = strdup(path); + result->memSource = nullptr; + result->memLength = 0; + result->loadAddress = nullptr; + result->binImage = nullptr; + *ofi = result; + + log_apis("NSCreateObjectFileImageFromFile() => %p\n", result); + + return NSObjectFileImageSuccess; +} + +NSObjectFileImageReturnCode NSCreateObjectFileImageFromMemory(const void* memImage, size_t memImageSize, NSObjectFileImage *ofi) +{ + log_apis("NSCreateObjectFileImageFromMemory(%p, 0x%0lX, %p)\n", memImage, memImageSize, ofi); + + // sanity check the buffer is a mach-o file + __block Diagnostics diag; + __block const mach_header* foundMH = nullptr; + if ( MachOParser::isMachO(diag, memImage, memImageSize) ) { + foundMH = (mach_header*)memImage; + } + else { + FatUtil::forEachSlice(diag, memImage, memImageSize, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSize, bool& stop) { + if ( MachOParser::isMachO(diag, sliceStart, sliceSize) ) { + foundMH = (mach_header*)sliceStart; + stop = true; + } + }); + } + if ( foundMH == nullptr ) { + log_apis("NSCreateObjectFileImageFromMemory() not mach-o\n"); + return NSObjectFileImageFailure; + } + + // this API can only be used with bundles + if ( foundMH->filetype != MH_BUNDLE ) { + log_apis("NSCreateObjectFileImageFromMemory() not a bundle, filetype=%d\n", foundMH->filetype); + return NSObjectFileImageInappropriateFile; + } + + // allocate ofi that just lists the memory range + __NSObjectFileImage* result = gAllImages.addNSObjectFileImage(); + result->path = nullptr; + result->memSource = memImage; + result->memLength = memImageSize; + result->loadAddress = nullptr; + result->binImage = nullptr; + *ofi = result; + + log_apis("NSCreateObjectFileImageFromMemory() => %p\n", result); + + return NSObjectFileImageSuccess; +} + +NSModule NSLinkModule(NSObjectFileImage ofi, const char* moduleName, uint32_t options) +{ + log_apis("NSLinkModule(%p, \"%s\", 0x%08X)\n", ofi, moduleName, options); + + // ofi is invalid if not in list + if ( !gAllImages.hasNSObjectFileImage(ofi) ) { + log_apis("NSLinkModule() => NULL (invalid NSObjectFileImage)\n"); + return nullptr; + } + + // if this is memory based image, write to temp file, then use file based loading + const BinaryImage* imageToLoad = nullptr; + if ( ofi->memSource != nullptr ) { + // make temp file with content of memory buffer + bool successfullyWritten = false; + ofi->path = ::tempnam(nullptr, "NSCreateObjectFileImageFromMemory-"); + if ( ofi->path != nullptr ) { + int fd = ::open(ofi->path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if ( fd != -1 ) { + ssize_t writtenSize = ::pwrite(fd, ofi->memSource, ofi->memLength, 0); + if ( writtenSize == ofi->memLength ) + successfullyWritten = true; + ::close(fd); + } + } + if ( !successfullyWritten ) { + if ( ofi->path != nullptr ) { + free((void*)ofi->path); + ofi->path = nullptr; + } + log_apis("NSLinkModule() => NULL (could not save memory image to temp file)\n"); + return nullptr; + } + } + else { + // check if image is in a known ImageGroup, but not loaded. if so, load using existing closure info + log_apis(" NSLinkModule: checking for pre-built closure for path: %s\n", ofi->path); + imageToLoad = gAllImages.findImageInKnownGroups(ofi->path); + // TODO: check symlinks, realpath + } + + // if no existing closure, RPC to closured to create one + if ( imageToLoad == nullptr ) { + const char* closuredErrorMessages[3]; + int closuredErrorMessagesCount = 0; + if ( imageToLoad == nullptr ) { + imageToLoad = gAllImages.messageClosured(ofi->path, "NSLinkModule", closuredErrorMessages, closuredErrorMessagesCount); + } + for (int i=0; i < closuredErrorMessagesCount; ++i) { + log_apis(" NSLinkModule: failed: %s\n", closuredErrorMessages[i]); + free((void*)closuredErrorMessages[i]); + } + } + + // use Image info to load and fixup image and all its dependents + if ( imageToLoad != nullptr ) { + Diagnostics diag; + ofi->loadAddress = loadImageAndDependents(diag, imageToLoad, true); + if ( diag.hasError() ) + log_apis(" NSLinkModule: failed: %s\n", diag.errorMessage()); + } + + // if memory based load, delete temp file + if ( ofi->memSource != nullptr ) { + log_apis(" NSLinkModule: delete temp file: %s\n", ofi->path); + ::unlink(ofi->path); + } + + log_apis("NSLinkModule() => %p\n", ofi->loadAddress); + return (NSModule)ofi->loadAddress; +} + +// NSUnLinkModule unmaps the image, but does not release the NSObjectFileImage +bool NSUnLinkModule(NSModule module, uint32_t options) +{ + log_apis("NSUnLinkModule(%p, 0x%08X)\n", module, options); + + bool result = false; + const mach_header* mh = (mach_header*)module; + launch_cache::Image image = gAllImages.findByLoadAddress(mh); + if ( image.valid() ) { + // removes image if reference count went to zero + gAllImages.decRefCount(mh); + result = true; + } + + log_apis("NSUnLinkModule() => %d\n", result); + + return result; +} + +// NSDestroyObjectFileImage releases the NSObjectFileImage, but the mapped image may remain in use +bool NSDestroyObjectFileImage(NSObjectFileImage ofi) +{ + log_apis("NSDestroyObjectFileImage(%p)\n", ofi); + + // ofi is invalid if not in list + if ( !gAllImages.hasNSObjectFileImage(ofi) ) + return false; + + // keep copy of info + const void* memSource = ofi->memSource; + size_t memLength = ofi->memLength; + const char* path = ofi->path; + + // remove from list + gAllImages.removeNSObjectFileImage(ofi); + + // if object was created from a memory, release that memory + // NOTE: this is the way dyld has always done this. NSCreateObjectFileImageFromMemory() hands ownership of the memory to dyld + if ( memSource != nullptr ) { + // we don't know if memory came from malloc or vm_allocate, so ask malloc + if ( malloc_size(memSource) != 0 ) + free((void*)(memSource)); + else + vm_deallocate(mach_task_self(), (vm_address_t)memSource, memLength); + } + free((void*)path); + + return true; +} + +uint32_t NSSymbolDefinitionCountInObjectFileImage(NSObjectFileImage objectFileImage) +{ + halt("NSSymbolDefinitionCountInObjectFileImage() is obsolete"); +} + +const char* NSSymbolDefinitionNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal) +{ + halt("NSSymbolDefinitionNameInObjectFileImage() is obsolete"); +} + +uint32_t NSSymbolReferenceCountInObjectFileImage(NSObjectFileImage objectFileImage) +{ + halt("NSSymbolReferenceCountInObjectFileImage() is obsolete"); +} + +const char* NSSymbolReferenceNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal, bool *tentative_definition) +{ + halt("NSSymbolReferenceNameInObjectFileImage() is obsolete"); +} + +bool NSIsSymbolDefinedInObjectFileImage(NSObjectFileImage ofi, const char* symbolName) +{ + log_apis("NSIsSymbolDefinedInObjectFileImage(%p, %s)\n", ofi, symbolName); + + // ofi is invalid if not in list + if ( !gAllImages.hasNSObjectFileImage(ofi) ) + return false; + + void* addr; + MachOParser parser(ofi->loadAddress); + return parser.hasExportedSymbol(symbolName, ^(uint32_t , const char*, void*, const mach_header**, void**) { + return false; + }, &addr); +} + +void* NSGetSectionDataInObjectFileImage(NSObjectFileImage ofi, const char* segmentName, const char* sectionName, size_t* size) +{ + // ofi is invalid if not in list + if ( !gAllImages.hasNSObjectFileImage(ofi) ) + return nullptr; + + __block void* result = nullptr; + MachOParser parser(ofi->loadAddress); + parser.forEachSection(^(const char* aSegName, const char* aSectName, uint32_t flags, const void* content, size_t aSize, bool illegalSectionSize, bool& stop) { + if ( (strcmp(sectionName, aSectName) == 0) && (strcmp(segmentName, aSegName) == 0) ) { + result = (void*)content; + if ( size != nullptr ) + *size = aSize; + stop = true; + } + }); + return result; +} + +const char* NSNameOfModule(NSModule m) +{ + log_apis("NSNameOfModule(%p)\n", m); + + const mach_header* foundInLoadAddress; + launch_cache::Image image = gAllImages.findByOwnedAddress(m, &foundInLoadAddress); + if ( image.valid() ) { + return gAllImages.imagePath(image.binaryData()); + } + return nullptr; +} + +const char* NSLibraryNameForModule(NSModule m) +{ + log_apis("NSLibraryNameForModule(%p)\n", m); + + const mach_header* foundInLoadAddress; + launch_cache::Image image = gAllImages.findByOwnedAddress(m, &foundInLoadAddress); + if ( image.valid() ) { + return gAllImages.imagePath(image.binaryData()); + } + return nullptr; +} + + +static bool flatFindSymbol(const char* symbolName, void** symbolAddress, const mach_header** foundInImageAtLoadAddress) +{ + for (uint32_t index=0; index < gAllImages.count(); ++index) { + const mach_header* loadAddress; + launch_cache::Image image = gAllImages.findByLoadOrder(index, &loadAddress); + if ( image.valid() ) { + MachOParser parser(loadAddress); + if ( parser.hasExportedSymbol(symbolName, ^(uint32_t , const char* , void* , const mach_header** , void**) { return false; }, symbolAddress) ) { + *foundInImageAtLoadAddress = loadAddress; + return true; + } + } + } + return false; +} + +bool NSIsSymbolNameDefined(const char* symbolName) +{ + log_apis("NSIsSymbolNameDefined(%s)\n", symbolName); + + const mach_header* foundInImageAtLoadAddress; + void* address; + return flatFindSymbol(symbolName, &address, &foundInImageAtLoadAddress); +} + +bool NSIsSymbolNameDefinedWithHint(const char* symbolName, const char* libraryNameHint) +{ + log_apis("NSIsSymbolNameDefinedWithHint(%s, %s)\n", symbolName, libraryNameHint); + + const mach_header* foundInImageAtLoadAddress; + void* address; + return flatFindSymbol(symbolName, &address, &foundInImageAtLoadAddress); +} + +bool NSIsSymbolNameDefinedInImage(const struct mach_header* mh, const char* symbolName) +{ + log_apis("NSIsSymbolNameDefinedInImage(%p, %s)\n", mh, symbolName); + + MachOParser::DependentFinder reExportFollower = ^(uint32_t depIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) { + *foundMH = gAllImages.alreadyLoaded(depLoadPath, false); + return (*foundMH != nullptr); + }; + + MachOParser parser(mh); + void* result; + return parser.hasExportedSymbol(symbolName, reExportFollower, &result); +} + +NSSymbol NSLookupAndBindSymbol(const char* symbolName) +{ + log_apis("NSLookupAndBindSymbol(%s)\n", symbolName); + + const mach_header* foundInImageAtLoadAddress; + void* symbolAddress; + if ( flatFindSymbol(symbolName, &symbolAddress, &foundInImageAtLoadAddress) ) { + return (NSSymbol)symbolAddress; + } + return nullptr; +} + +NSSymbol NSLookupAndBindSymbolWithHint(const char* symbolName, const char* libraryNameHint) +{ + log_apis("NSLookupAndBindSymbolWithHint(%s, %s)\n", symbolName, libraryNameHint); + + const mach_header* foundInImageAtLoadAddress; + void* symbolAddress; + if ( flatFindSymbol(symbolName, &symbolAddress, &foundInImageAtLoadAddress) ) { + return (NSSymbol)symbolAddress; + } + return nullptr; +} + +NSSymbol NSLookupSymbolInModule(NSModule module, const char* symbolName) +{ + log_apis("NSLookupSymbolInModule(%p. %s)\n", module, symbolName); + + MachOParser::DependentFinder reExportFollower = ^(uint32_t depIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) { + *foundMH = gAllImages.alreadyLoaded(depLoadPath, false); + return (*foundMH != nullptr); + }; + + const mach_header* mh = (const mach_header*)module; + uint32_t loadIndex; + if ( gAllImages.findIndexForLoadAddress(mh, loadIndex) ) { + MachOParser parser(mh); + void* symAddress; + if ( parser.hasExportedSymbol(symbolName, reExportFollower, &symAddress) ) { + return (NSSymbol)symAddress; + } + } + return nullptr; +} + +NSSymbol NSLookupSymbolInImage(const struct mach_header* mh, const char* symbolName, uint32_t options) +{ + log_apis("NSLookupSymbolInImage(%p, \"%s\", 0x%08X)\n", mh, symbolName, options); + + MachOParser::DependentFinder reExportFollower = ^(uint32_t depIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) { + *foundMH = gAllImages.alreadyLoaded(depLoadPath, false); + return (*foundMH != nullptr); + }; + + MachOParser parser(mh); + void* result; + if ( parser.hasExportedSymbol(symbolName, reExportFollower, &result) ) { + log_apis(" NSLookupSymbolInImage() => %p\n", result); + return (NSSymbol)result; + } + + if ( options & NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR ) { + log_apis(" NSLookupSymbolInImage() => NULL\n"); + return nullptr; + } + return nullptr; +} + +const char* NSNameOfSymbol(NSSymbol symbol) +{ + halt("NSNameOfSymbol() is obsolete"); +} + +void* NSAddressOfSymbol(NSSymbol symbol) +{ + log_apis("NSAddressOfSymbol(%p)\n", symbol); + + // in dyld 1.0, NSSymbol was a pointer to the nlist entry in the symbol table + return (void*)symbol; +} + +NSModule NSModuleForSymbol(NSSymbol symbol) +{ + log_apis("NSModuleForSymbol(%p)\n", symbol); + + const mach_header* foundInLoadAddress; + launch_cache::Image image = gAllImages.findByOwnedAddress(symbol, &foundInLoadAddress); + if ( image.valid() ) { + return (NSModule)foundInLoadAddress; + } + return nullptr; +} + +void NSLinkEditError(NSLinkEditErrors *c, int *errorNumber, const char** fileName, const char** errorString) +{ + log_apis("NSLinkEditError(%p, %p, %p, %p)\n", c, errorNumber, fileName, errorString); + *c = NSLinkEditOtherError; + *errorNumber = 0; + *fileName = NULL; + *errorString = NULL; +} + +bool NSAddLibrary(const char* pathName) +{ + log_apis("NSAddLibrary(%s)\n", pathName); + + return ( dlopen(pathName, 0) != nullptr); +} + +bool NSAddLibraryWithSearching(const char* pathName) +{ + log_apis("NSAddLibraryWithSearching(%s)\n", pathName); + + return ( dlopen(pathName, 0) != nullptr); +} + +const mach_header* NSAddImage(const char* imageName, uint32_t options) +{ + log_apis("NSAddImage(\"%s\", 0x%08X)\n", imageName, options); + + // Note: this is a quick and dirty implementation that just uses dlopen() and ignores some option flags + uint32_t dloptions = 0; + if ( (options & NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED) != 0 ) + dloptions |= RTLD_NOLOAD; + + void* h = dlopen(imageName, dloptions); + if ( h != nullptr ) { + const mach_header* mh; + bool dontContinue; + parseDlHandle(h, &mh, &dontContinue); + return mh; + } + + if ( (options & (NSADDIMAGE_OPTION_RETURN_ON_ERROR|NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED)) == 0 ) { + halt("NSAddImage() image not found"); + } + return nullptr; +} + +void NSInstallLinkEditErrorHandlers(const NSLinkEditErrorHandlers *handlers) +{ + halt("NSInstallLinkEditErrorHandlers() is obsolete"); +} + +bool _dyld_present(void) +{ + log_apis("_dyld_present()\n"); + + return true; +} + +bool _dyld_launched_prebound(void) +{ + halt("_dyld_launched_prebound() is obsolete"); +} + +bool _dyld_all_twolevel_modules_prebound(void) +{ + halt("_dyld_all_twolevel_modules_prebound() is obsolete"); +} + +bool _dyld_bind_fully_image_containing_address(const void* address) +{ + log_apis("_dyld_bind_fully_image_containing_address(%p)\n", address); + + // in dyld3, everything is always fully bound + return true; +} + +bool _dyld_image_containing_address(const void* address) +{ + log_apis("_dyld_image_containing_address(%p)\n", address); + + return (dyld_image_header_containing_address(address) != nullptr); +} + +void _dyld_lookup_and_bind(const char* symbolName, void **address, NSModule* module) +{ + log_apis("_dyld_lookup_and_bind(%s, %p, %p)\n", symbolName, address, module); + + const mach_header* foundInImageAtLoadAddress; + if ( flatFindSymbol(symbolName, address, &foundInImageAtLoadAddress) ) { + *module = (NSModule)foundInImageAtLoadAddress; + return; + } + + *address = 0; + *module = 0; +} + +void _dyld_lookup_and_bind_with_hint(const char* symbolName, const char* libraryNameHint, void** address, NSModule* module) +{ + log_apis("_dyld_lookup_and_bind_with_hint(%s, %s, %p, %p)\n", symbolName, libraryNameHint, address, module); + + const mach_header* foundInImageAtLoadAddress; + if ( flatFindSymbol(symbolName, address, &foundInImageAtLoadAddress) ) { + *module = (NSModule)foundInImageAtLoadAddress; + return; + } + + *address = 0; + *module = 0; +} + + +void _dyld_lookup_and_bind_fully(const char* symbolName, void** address, NSModule* module) +{ + log_apis("_dyld_lookup_and_bind_fully(%s, %p, %p)\n", symbolName, address, module); + + const mach_header* foundInImageAtLoadAddress; + if ( flatFindSymbol(symbolName, address, &foundInImageAtLoadAddress) ) { + *module = (NSModule)foundInImageAtLoadAddress; + return; + } + + *address = 0; + *module = 0; +} + +const struct mach_header* _dyld_get_image_header_containing_address(const void* address) +{ + log_apis("_dyld_get_image_header_containing_address(%p)\n", address); + + return dyld_image_header_containing_address(address); +} + +#endif + + +} // namespace dyld3 + diff --git a/dyld3/AllImages.cpp b/dyld3/AllImages.cpp new file mode 100644 index 0000000..6d64e36 --- /dev/null +++ b/dyld3/AllImages.cpp @@ -0,0 +1,1562 @@ +/* + * Copyright (c) 2017 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 // mach_absolute_time() +#include +#include + +#include +#include + +#include "AllImages.h" +#include "MachOParser.h" +#include "libdyldEntryVector.h" +#include "Logging.h" +#include "Loading.h" +#include "Tracing.h" +#include "LaunchCache.h" +#include "DyldSharedCache.h" +#include "PathOverrides.h" +#include "DyldCacheParser.h" + +extern const char** appleParams; + +// should be a header for these +struct __cxa_range_t { + const void* addr; + size_t length; +}; +extern "C" void __cxa_finalize_ranges(const __cxa_range_t ranges[], unsigned int count); + +VIS_HIDDEN bool gUseDyld3 = false; + + +namespace dyld3 { + +class VIS_HIDDEN LoadedImage { +public: + enum class State { uninited=3, beingInited=2, inited=0 }; + typedef launch_cache::binary_format::Image BinaryImage; + + LoadedImage(const mach_header* mh, const BinaryImage* bi); + bool operator==(const LoadedImage& rhs) const; + void init(const mach_header* mh, const BinaryImage* bi); + const mach_header* loadedAddress() const { return (mach_header*)((uintptr_t)_loadAddress & ~0x7ULL); } + State state() const { return (State)((uintptr_t)_loadAddress & 0x3ULL); } + const BinaryImage* image() const { return _image; } + bool neverUnload() const { return ((uintptr_t)_loadAddress & 0x4ULL); } + void setState(State s) { _loadAddress = (mach_header*)((((uintptr_t)_loadAddress) & ~0x3ULL) | (uintptr_t)s); } + void setNeverUnload() { _loadAddress = (mach_header*)(((uintptr_t)_loadAddress) | 0x4ULL); } + +private: + const mach_header* _loadAddress; // low bits: bit2=neverUnload, bit1/bit0 contain State + const BinaryImage* _image; +}; + + +bool LoadedImage::operator==(const LoadedImage& rhs) const +{ + return (_image == rhs._image) && (loadedAddress() == rhs.loadedAddress()); +} + + + +struct VIS_HIDDEN DlopenCount { + bool operator==(const DlopenCount& rhs) const; + const mach_header* loadAddress; + uintptr_t refCount; +}; + +bool DlopenCount::operator==(const DlopenCount& rhs) const +{ + return (loadAddress == rhs.loadAddress) && (refCount == rhs.refCount); +} + +LoadedImage::LoadedImage(const mach_header* mh, const BinaryImage* bi) + : _loadAddress(mh), _image(bi) +{ + assert(loadedAddress() == mh); + setState(State::uninited); +} + +void LoadedImage::init(const mach_header* mh, const BinaryImage* bi) +{ + _loadAddress = mh; + _image = bi; + assert(loadedAddress() == mh); + setState(State::uninited); +} + +// forward reference +template class ReaderWriterChunkedVector; + +template +class VIS_HIDDEN ChunkedVector { +public: + static ChunkedVector* make(uint32_t count); + + void forEach(uint32_t& startIndex, bool& outerStop, void (^callback)(uint32_t index, const T& value, bool& stop)) const; + void forEach(uint32_t& startIndex, bool& outerStop, void (^callback)(uint32_t index, T& value, bool& stop)); + T* add(const T& value); + T* add(uint32_t count, const T values[]); + void remove(uint32_t index); + uint32_t count() const { return _inUseCount; } + uint32_t freeCount() const { return _allocCount - _inUseCount; } +private: + T& element(uint32_t index) { return ((T*)_elements)[index]; } + const T& element(uint32_t index) const { return ((T*)_elements)[index]; } + + friend class ReaderWriterChunkedVector; + + ChunkedVector* _next = nullptr; + uint32_t _allocCount = C; + uint32_t _inUseCount = 0; + uint8_t _elements[C*sizeof(T)] = { 0 }; +}; + +template +class VIS_HIDDEN ReaderWriterChunkedVector { +public: + T* add(uint32_t count, const T values[]); + T* add(const T& value) { return add(1, &value); } + T* addNoLock(uint32_t count, const T values[]); + T* addNoLock(const T& value) { return addNoLock(1, &value); } + void remove(const T& value); + uint32_t count() const; + void forEachWithReadLock(void (^callback)(uint32_t index, const T& value, bool& stop)) const; + void forEachWithWriteLock(void (^callback)(uint32_t index, T& value, bool& stop)); + void forEachNoLock(void (^callback)(uint32_t index, const T& value, bool& stop)) const; + T& operator[](size_t index); + uint32_t countNoLock() const; + + void withReadLock(void (^withLock)()) const; + void withWriteLock(void (^withLock)()) const; + void acquireWriteLock(); + void releaseWriteLock(); + void dump(void (^callback)(const T& value)) const; + +private: + mutable pthread_rwlock_t _lock = PTHREAD_RWLOCK_INITIALIZER; + ChunkedVector _firstChunk; +}; + + +typedef void (*NotifyFunc)(const mach_header* mh, intptr_t slide); + +static ReaderWriterChunkedVector sLoadNotifiers; +static ReaderWriterChunkedVector sUnloadNotifiers; +static ReaderWriterChunkedVector sLoadedImages; +static ReaderWriterChunkedVector sDlopenRefCounts; +static ReaderWriterChunkedVector sKnownGroups; +#if __MAC_OS_X_VERSION_MIN_REQUIRED +static ReaderWriterChunkedVector<__NSObjectFileImage, 2> sNSObjectFileImages; +#endif + + +///////////////////// ChunkedVector //////////////////////////// + +template +ChunkedVector* ChunkedVector::make(uint32_t count) +{ + size_t size = sizeof(ChunkedVector) + sizeof(T) * (count-C); + ChunkedVector* result = (ChunkedVector*)malloc(size); + result->_next = nullptr; + result->_allocCount = count; + result->_inUseCount = 0; + return result; +} + +template +void ChunkedVector::forEach(uint32_t& outerIndex, bool& outerStop, void (^callback)(uint32_t index, const T& value, bool& stop)) const +{ + for (uint32_t i=0; i < _inUseCount; ++i) { + callback(outerIndex, element(i), outerStop); + ++outerIndex; + if ( outerStop ) + break; + } +} + +template +void ChunkedVector::forEach(uint32_t& outerIndex, bool& outerStop, void (^callback)(uint32_t index, T& value, bool& stop)) +{ + for (uint32_t i=0; i < _inUseCount; ++i) { + callback(outerIndex, element(i), outerStop); + ++outerIndex; + if ( outerStop ) + break; + } +} + +template +T* ChunkedVector::add(const T& value) +{ + return add(1, &value); +} + +template +T* ChunkedVector::add(uint32_t count, const T values[]) +{ + assert(count <= (_allocCount - _inUseCount)); + T* result = &element(_inUseCount); + memmove(result, values, sizeof(T)*count); + _inUseCount += count; + return result; +} + +template +void ChunkedVector::remove(uint32_t index) +{ + assert(index < _inUseCount); + int moveCount = _inUseCount - index - 1; + if ( moveCount >= 1 ) { + memmove(&element(index), &element(index+1), sizeof(T)*moveCount); + } + _inUseCount--; +} + + +///////////////////// ReaderWriterChunkedVector //////////////////////////// + + + +template +void ReaderWriterChunkedVector::withReadLock(void (^work)()) const +{ + assert(pthread_rwlock_rdlock(&_lock) == 0); + work(); + assert(pthread_rwlock_unlock(&_lock) == 0); +} + +template +void ReaderWriterChunkedVector::withWriteLock(void (^work)()) const +{ + assert(pthread_rwlock_wrlock(&_lock) == 0); + work(); + assert(pthread_rwlock_unlock(&_lock) == 0); +} + +template +void ReaderWriterChunkedVector::acquireWriteLock() +{ + assert(pthread_rwlock_wrlock(&_lock) == 0); +} + +template +void ReaderWriterChunkedVector::releaseWriteLock() +{ + assert(pthread_rwlock_unlock(&_lock) == 0); +} + +template +uint32_t ReaderWriterChunkedVector::count() const +{ + __block uint32_t result = 0; + withReadLock(^() { + for (const ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { + result += chunk->count(); + } + }); + return result; +} + +template +uint32_t ReaderWriterChunkedVector::countNoLock() const +{ + uint32_t result = 0; + for (const ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { + result += chunk->count(); + } + return result; +} + +template +T* ReaderWriterChunkedVector::addNoLock(uint32_t count, const T values[]) +{ + T* result = nullptr; + ChunkedVector* lastChunk = &_firstChunk; + while ( lastChunk->_next != nullptr ) + lastChunk = lastChunk->_next; + + if ( lastChunk->freeCount() >= count ) { + // append to last chunk + result = lastChunk->add(count, values); + } + else { + // append new chunk + uint32_t allocCount = count; + uint32_t remainder = count % C; + if ( remainder != 0 ) + allocCount = count + C - remainder; + ChunkedVector* newChunk = ChunkedVector::make(allocCount); + result = newChunk->add(count, values); + lastChunk->_next = newChunk; + } + + return result; +} + +template +T* ReaderWriterChunkedVector::add(uint32_t count, const T values[]) +{ + __block T* result = nullptr; + withWriteLock(^() { + result = addNoLock(count, values); + }); + return result; +} + +template +void ReaderWriterChunkedVector::remove(const T& valueToRemove) +{ + __block bool stopStorage = false; + withWriteLock(^() { + ChunkedVector* chunkNowEmpty = nullptr; + __block uint32_t indexStorage = 0; + __block bool found = false; + for (ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { + uint32_t chunkStartIndex = indexStorage; + __block uint32_t foundIndex = 0; + chunk->forEach(indexStorage, stopStorage, ^(uint32_t index, const T& value, bool& stop) { + if ( value == valueToRemove ) { + foundIndex = index - chunkStartIndex; + found = true; + stop = true; + } + }); + if ( found ) { + chunk->remove(foundIndex); + found = false; + if ( chunk->count() == 0 ) + chunkNowEmpty = chunk; + } + } + // if chunk is now empty, remove from linked list and free + if ( chunkNowEmpty ) { + for (ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { + if ( chunk->_next == chunkNowEmpty ) { + chunk->_next = chunkNowEmpty->_next; + if ( chunkNowEmpty != &_firstChunk ) + free(chunkNowEmpty); + break; + } + } + } + }); +} + +template +void ReaderWriterChunkedVector::forEachWithReadLock(void (^callback)(uint32_t index, const T& value, bool& stop)) const +{ + __block uint32_t index = 0; + __block bool stop = false; + withReadLock(^() { + for (const ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { + chunk->forEach(index, stop, callback); + if ( stop ) + break; + } + }); +} + +template +void ReaderWriterChunkedVector::forEachWithWriteLock(void (^callback)(uint32_t index, T& value, bool& stop)) +{ + __block uint32_t index = 0; + __block bool stop = false; + withReadLock(^() { + for (ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { + chunk->forEach(index, stop, callback); + if ( stop ) + break; + } + }); +} + +template +void ReaderWriterChunkedVector::forEachNoLock(void (^callback)(uint32_t index, const T& value, bool& stop)) const +{ + uint32_t index = 0; + bool stop = false; + for (const ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { + chunk->forEach(index, stop, callback); + if ( stop ) + break; + } +} + +template +T& ReaderWriterChunkedVector::operator[](size_t targetIndex) +{ + __block T* result = nullptr; + forEachNoLock(^(uint32_t index, T const& value, bool& stop) { + if ( index == targetIndex ) { + result = (T*)&value; + stop = true; + } + }); + return *result; +} + +template +void ReaderWriterChunkedVector::dump(void (^callback)(const T& value)) const +{ + log("dump ReaderWriterChunkedVector at %p\n", this); + __block uint32_t index = 0; + __block bool stop = false; + withReadLock(^() { + for (const ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { + log(" chunk at %p\n", chunk); + chunk->forEach(index, stop, ^(uint32_t i, const T& value, bool& s) { + callback(value); + }); + } + }); +} + + + +///////////////////// AllImages //////////////////////////// + + +AllImages gAllImages; + + + +void AllImages::init(const BinaryClosure* closure, const void* dyldCacheLoadAddress, const char* dyldCachePath, + const dyld3::launch_cache::DynArray& initialImages) +{ + _mainClosure = closure; + _initialImages = &initialImages; + _dyldCacheAddress = dyldCacheLoadAddress; + _dyldCachePath = dyldCachePath; + + // Make temporary old image array, so libSystem initializers can be debugged + uint32_t count = (uint32_t)initialImages.count(); + dyld_image_info oldDyldInfo[count]; + for (int i=0; i < count; ++i) { + launch_cache::Image img(initialImages[i].imageData); + oldDyldInfo[i].imageLoadAddress = initialImages[i].loadAddress; + oldDyldInfo[i].imageFilePath = img.path(); + oldDyldInfo[i].imageFileModDate = 0; + } + _oldAllImageInfos->infoArray = oldDyldInfo; + _oldAllImageInfos->infoArrayCount = count; + _oldAllImageInfos->notification(dyld_image_adding, count, oldDyldInfo); + _oldAllImageInfos->infoArray = nullptr; + _oldAllImageInfos->infoArrayCount = 0; +} + +void AllImages::setProgramVars(ProgramVars* vars) +{ + _programVars = vars; +} + +void AllImages::applyInitialImages() +{ + addImages(*_initialImages); + _initialImages = nullptr; // this was stack allocated +} + +void AllImages::mirrorToOldAllImageInfos() +{ + // set infoArray to NULL to denote it is in-use + _oldAllImageInfos->infoArray = nullptr; + + // if array not large enough, re-alloc it + uint32_t imageCount = sLoadedImages.countNoLock(); + if ( _oldArrayAllocCount < imageCount ) { + uint32_t newAllocCount = imageCount + 16; + dyld_image_info* newArray = (dyld_image_info*)malloc(sizeof(dyld_image_info)*newAllocCount); + if ( _oldAllImageArray != nullptr ) { + memcpy(newArray, _oldAllImageArray, sizeof(dyld_image_info)*_oldAllImageInfos->infoArrayCount); + free(_oldAllImageArray); + } + _oldAllImageArray = newArray; + _oldArrayAllocCount = newAllocCount; + } + + // fill out array to mirror current image list + sLoadedImages.forEachNoLock(^(uint32_t index, const LoadedImage& loadedImage, bool& stop) { + launch_cache::Image img(loadedImage.image()); + _oldAllImageArray[index].imageLoadAddress = loadedImage.loadedAddress(); + _oldAllImageArray[index].imageFilePath = imagePath(loadedImage.image()); + _oldAllImageArray[index].imageFileModDate = 0; + }); + + // set infoArray back to base address of array (so other process can now read) + _oldAllImageInfos->infoArrayCount = imageCount; + _oldAllImageInfos->infoArrayChangeTimestamp = mach_absolute_time(); + _oldAllImageInfos->infoArray = _oldAllImageArray; +} + +void AllImages::addImages(const launch_cache::DynArray& newImages) +{ + uint32_t count = (uint32_t)newImages.count(); + assert(count != 0); + + // build stack array of LoadedImage to copy into sLoadedImages + STACK_ALLOC_DYNARRAY(LoadedImage, count, loadedImagesArray); + for (uint32_t i=0; i < count; ++i) { + loadedImagesArray[i].init(newImages[i].loadAddress, newImages[i].imageData); + if (newImages[i].neverUnload) + loadedImagesArray[i].setNeverUnload(); + } + sLoadedImages.add(count, &loadedImagesArray[0]); + + if ( _oldAllImageInfos != nullptr ) { + // sync to old all image infos struct + if ( _initialImages != nullptr ) { + // libSystem not initialized yet, don't use locks + mirrorToOldAllImageInfos(); + } + else { + sLoadedImages.withReadLock(^{ + mirrorToOldAllImageInfos(); + }); + } + + // tell debugger about new images + dyld_image_info oldDyldInfo[count]; + for (int i=0; i < count; ++i) { + launch_cache::Image img(newImages[i].imageData); + oldDyldInfo[i].imageLoadAddress = newImages[i].loadAddress; + oldDyldInfo[i].imageFilePath = imagePath(newImages[i].imageData); + oldDyldInfo[i].imageFileModDate = 0; + } + _oldAllImageInfos->notification(dyld_image_adding, count, oldDyldInfo); + } + + // log loads + for (int i=0; i < count; ++i) { + launch_cache::Image img(newImages[i].imageData); + log_loads("dyld: %s\n", imagePath(newImages[i].imageData)); + } + +#if !TARGET_IPHONE_SIMULATOR + // call kdebug trace for each image + if (kdebug_is_enabled(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, DBG_DYLD_UUID_MAP_A))) { + for (uint32_t i=0; i < count; ++i) { + launch_cache::Image img(newImages[i].imageData); + struct stat stat_buf; + fsid_t fsid = {{ 0, 0 }}; + fsobj_id_t fsobjid = { 0, 0 }; + if (img.isDiskImage() && stat(imagePath(newImages[i].imageData), &stat_buf) == 0 ) { + fsobjid = *(fsobj_id_t*)&stat_buf.st_ino; + fsid = {{ stat_buf.st_dev, 0 }}; + } + kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, img.uuid(), fsobjid, fsid, newImages[i].loadAddress); + } + } +#endif + // call each _dyld_register_func_for_add_image function with each image + const uint32_t existingNotifierCount = sLoadNotifiers.count(); + NotifyFunc existingNotifiers[existingNotifierCount]; + NotifyFunc* existingNotifierArray = existingNotifiers; + sLoadNotifiers.forEachWithReadLock(^(uint32_t index, const NotifyFunc& func, bool& stop) { + if ( index < existingNotifierCount ) + existingNotifierArray[index] = func; + }); + // we don't want to hold lock while calling out, so prebuild array (with lock) then do calls on that array (without lock) + for (uint32_t j=0; j < existingNotifierCount; ++j) { + NotifyFunc func = existingNotifierArray[j]; + for (uint32_t i=0; i < count; ++i) { + MachOParser parser(newImages[i].loadAddress); + log_notifications("dyld: add notifier %p called with mh=%p\n", func, newImages[i].loadAddress); + func(newImages[i].loadAddress, parser.getSlide()); + } + } + + // call objc about images that use objc + if ( _objcNotifyMapped != nullptr ) { + const char* pathsBuffer[count]; + const mach_header* mhBuffer[count]; + uint32_t imagesWithObjC = 0; + for (uint32_t i=0; i < count; ++i) { + launch_cache::Image img(newImages[i].imageData); + if ( img.hasObjC() ) { + pathsBuffer[imagesWithObjC] = imagePath(newImages[i].imageData); + mhBuffer[imagesWithObjC] = newImages[i].loadAddress; + ++imagesWithObjC; + } + } + if ( imagesWithObjC != 0 ) { + (*_objcNotifyMapped)(imagesWithObjC, pathsBuffer, mhBuffer); + if ( log_notifications("dyld: objc-mapped-notifier called with %d images:\n", imagesWithObjC) ) { + for (uint32_t i=0; i < imagesWithObjC; ++i) { + log_notifications("dyld: objc-mapped: %p %s\n", mhBuffer[i], pathsBuffer[i]); + } + } + } + } + + // notify any processes tracking loads in this process + notifyMonitorLoads(newImages); +} + +void AllImages::removeImages(const launch_cache::DynArray& unloadImages) +{ + uint32_t count = (uint32_t)unloadImages.count(); + assert(count != 0); + + // call each _dyld_register_func_for_remove_image function with each image + // do this before removing image from internal data structures so that the callback can query dyld about the image + const uint32_t existingNotifierCount = sUnloadNotifiers.count(); + NotifyFunc existingNotifiers[existingNotifierCount]; + NotifyFunc* existingNotifierArray = existingNotifiers; + sUnloadNotifiers.forEachWithReadLock(^(uint32_t index, const NotifyFunc& func, bool& stop) { + if ( index < existingNotifierCount ) + existingNotifierArray[index] = func; + }); + // we don't want to hold lock while calling out, so prebuild array (with lock) then do calls on that array (without lock) + for (uint32_t j=0; j < existingNotifierCount; ++j) { + NotifyFunc func = existingNotifierArray[j]; + for (uint32_t i=0; i < count; ++i) { + MachOParser parser(unloadImages[i].loadAddress); + log_notifications("dyld: remove notifier %p called with mh=%p\n", func, unloadImages[i].loadAddress); + func(unloadImages[i].loadAddress, parser.getSlide()); + } + } + + // call objc about images going away + if ( _objcNotifyUnmapped != nullptr ) { + for (uint32_t i=0; i < count; ++i) { + launch_cache::Image img(unloadImages[i].imageData); + if ( img.hasObjC() ) { + (*_objcNotifyUnmapped)(imagePath(unloadImages[i].imageData), unloadImages[i].loadAddress); + log_notifications("dyld: objc-unmapped-notifier called with image %p %s\n", unloadImages[i].loadAddress, imagePath(unloadImages[i].imageData)); + } + } + } + +#if !TARGET_IPHONE_SIMULATOR + // call kdebug trace for each image + if (kdebug_is_enabled(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, DBG_DYLD_UUID_MAP_A))) { + for (uint32_t i=0; i < count; ++i) { + launch_cache::Image img(unloadImages[i].imageData); + struct stat stat_buf; + fsid_t fsid = {{ 0, 0 }}; + fsobj_id_t fsobjid = { 0, 0 }; + if (stat(imagePath(unloadImages[i].imageData), &stat_buf) == 0 ) { + fsobjid = *(fsobj_id_t*)&stat_buf.st_ino; + fsid = {{ stat_buf.st_dev, 0 }}; + } + kdebug_trace_dyld_image(DBG_DYLD_UUID_UNMAP_A, img.uuid(), fsobjid, fsid, unloadImages[i].loadAddress); + } + } +#endif + + // remove each from sLoadedImages + for (uint32_t i=0; i < count; ++i) { + LoadedImage info(unloadImages[i].loadAddress, unloadImages[i].imageData); + sLoadedImages.remove(info); + } + + // sync to old all image infos struct + sLoadedImages.withReadLock(^{ + mirrorToOldAllImageInfos(); + }); + + // tell debugger about removed images + dyld_image_info oldDyldInfo[count]; + for (int i=0; i < count; ++i) { + launch_cache::Image img(unloadImages[i].imageData); + oldDyldInfo[i].imageLoadAddress = unloadImages[i].loadAddress; + oldDyldInfo[i].imageFilePath = imagePath(unloadImages[i].imageData); + oldDyldInfo[i].imageFileModDate = 0; + } + _oldAllImageInfos->notification(dyld_image_removing, count, oldDyldInfo); + + // unmap images + for (int i=0; i < count; ++i) { + launch_cache::Image img(unloadImages[i].imageData); + loader::unmapImage(unloadImages[i].imageData, unloadImages[i].loadAddress); + log_loads("dyld: unloaded %s\n", imagePath(unloadImages[i].imageData)); + } + + // notify any processes tracking loads in this process + notifyMonitorUnloads(unloadImages); +} + +void AllImages::setNeverUnload(const loader::ImageInfo& existingImage) +{ + sLoadedImages.forEachWithWriteLock(^(uint32_t index, dyld3::LoadedImage &value, bool &stop) { + if (value.image() == existingImage.imageData) { + value.setNeverUnload(); + stop = true; + } + }); +} + +uint32_t AllImages::count() const +{ + return sLoadedImages.count(); +} + + +launch_cache::Image AllImages::findByLoadOrder(uint32_t index, const mach_header** loadAddress) const +{ + __block const BinaryImage* foundImage = nullptr; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + if ( anIndex == index ) { + foundImage = loadedImage.image(); + *loadAddress = loadedImage.loadedAddress(); + stop = true; + } + }); + return launch_cache::Image(foundImage); +} + +launch_cache::Image AllImages::findByLoadAddress(const mach_header* loadAddress) const +{ + __block const BinaryImage* foundImage = nullptr; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + if ( loadedImage.loadedAddress() == loadAddress ) { + foundImage = loadedImage.image(); + stop = true; + } + }); + return launch_cache::Image(foundImage); +} + +bool AllImages::findIndexForLoadAddress(const mach_header* loadAddress, uint32_t& index) +{ + __block bool result = false; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + if ( loadedImage.loadedAddress() == loadAddress ) { + index = anIndex; + result = true; + stop = true; + } + }); + return result; +} + +void AllImages::forEachImage(void (^handler)(uint32_t imageIndex, const mach_header* loadAddress, const launch_cache::Image image, bool& stop)) const +{ + sLoadedImages.forEachWithReadLock(^(uint32_t imageIndex, const LoadedImage& loadedImage, bool& stop) { + handler(imageIndex, loadedImage.loadedAddress(), launch_cache::Image(loadedImage.image()), stop); + }); +} + +launch_cache::Image AllImages::findByOwnedAddress(const void* addr, const mach_header** loadAddress, uint8_t* permissions) const +{ + if ( _initialImages != nullptr ) { + // being called during libSystem initialization, so sLoadedImages not allocated yet + for (int i=0; i < _initialImages->count(); ++i) { + const loader::ImageInfo& entry = (*_initialImages)[i]; + launch_cache::Image anImage(entry.imageData); + if ( anImage.containsAddress(addr, entry.loadAddress, permissions) ) { + *loadAddress = entry.loadAddress; + return entry.imageData; + } + } + return launch_cache::Image(nullptr); + } + + // if address is in cache, do fast search of cache + if ( (_dyldCacheAddress != nullptr) && (addr > _dyldCacheAddress) ) { + const DyldSharedCache* dyldCache = (DyldSharedCache*)_dyldCacheAddress; + if ( addr < (void*)((uint8_t*)_dyldCacheAddress+dyldCache->mappedSize()) ) { + size_t cacheVmOffset = ((uint8_t*)addr - (uint8_t*)_dyldCacheAddress); + DyldCacheParser cacheParser(dyldCache, false); + launch_cache::ImageGroup cachedDylibsGroup(cacheParser.cachedDylibsGroup()); + uint32_t mhCacheOffset; + uint8_t foundPermissions; + launch_cache::Image image(cachedDylibsGroup.findImageByCacheOffset(cacheVmOffset, mhCacheOffset, foundPermissions)); + if ( image.valid() ) { + *loadAddress = (mach_header*)((uint8_t*)_dyldCacheAddress + mhCacheOffset); + if ( permissions != nullptr ) + *permissions = foundPermissions; + return image; + } + } + } + + __block const BinaryImage* foundImage = nullptr; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + launch_cache::Image anImage(loadedImage.image()); + if ( anImage.containsAddress(addr, loadedImage.loadedAddress(), permissions) ) { + *loadAddress = loadedImage.loadedAddress(); + foundImage = loadedImage.image(); + stop = true; + } + }); + return launch_cache::Image(foundImage); +} + +const mach_header* AllImages::findLoadAddressByImage(const BinaryImage* targetImage) const +{ + __block const mach_header* foundAddress = nullptr; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + if ( targetImage == loadedImage.image() ) { + foundAddress = loadedImage.loadedAddress(); + stop = true; + } + }); + return foundAddress; +} + +const mach_header* AllImages::mainExecutable() const +{ + assert(_programVars != nullptr); + return (const mach_header*)_programVars->mh; +} + +launch_cache::Image AllImages::mainExecutableImage() const +{ + assert(_mainClosure != nullptr); + const launch_cache::Closure mainClosure(_mainClosure); + const dyld3::launch_cache::ImageGroup mainGroup = mainClosure.group(); + const uint32_t mainExecutableIndex = mainClosure.mainExecutableImageIndex(); + const dyld3::launch_cache::Image mainImage = mainGroup.image(mainExecutableIndex); + return mainImage; +} + +void AllImages::setMainPath(const char* path ) +{ + _mainExeOverridePath = path; +} + +const char* AllImages::imagePath(const BinaryImage* binImage) const +{ +#if __IPHONE_OS_VERSION_MIN_REQUIRED + // on iOS and watchOS, apps may be moved on device after closure built + if ( _mainExeOverridePath != nullptr ) { + if ( binImage == mainExecutableImage().binaryData() ) + return _mainExeOverridePath; + } +#endif + launch_cache::Image image(binImage); + return image.path(); +} + +void AllImages::setInitialGroups() +{ + DyldCacheParser cacheParser((DyldSharedCache*)_dyldCacheAddress, false); + sKnownGroups.addNoLock(cacheParser.cachedDylibsGroup()); + sKnownGroups.addNoLock(cacheParser.otherDylibsGroup()); + launch_cache::Closure closure(_mainClosure); + sKnownGroups.addNoLock(closure.group().binaryData()); +} + +const launch_cache::binary_format::ImageGroup* AllImages::cachedDylibsGroup() +{ + return sKnownGroups[0]; +} + +const launch_cache::binary_format::ImageGroup* AllImages::otherDylibsGroup() +{ + return sKnownGroups[1]; +} + +const AllImages::BinaryImageGroup* AllImages::mainClosureGroup() +{ + return sKnownGroups[2]; +} + +uint32_t AllImages::currentGroupsCount() const +{ + return sKnownGroups.count(); +} + +void AllImages::copyCurrentGroups(ImageGroupList& groups) const +{ + sKnownGroups.forEachWithReadLock(^(uint32_t index, const dyld3::launch_cache::binary_format::ImageGroup* const &grpData, bool &stop) { + if ( index < groups.count() ) + groups[index] = grpData; + }); +} + +void AllImages::copyCurrentGroupsNoLock(ImageGroupList& groups) const +{ + sKnownGroups.forEachNoLock(^(uint32_t index, const dyld3::launch_cache::binary_format::ImageGroup* const &grpData, bool &stop) { + if ( index < groups.count() ) + groups[index] = grpData; + }); +} + +const mach_header* AllImages::alreadyLoaded(uint64_t inode, uint64_t mtime, bool bumpRefCount) +{ + __block const mach_header* result = nullptr; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + launch_cache::Image img(loadedImage.image()); + if ( img.validateUsingModTimeAndInode() ) { + if ( (img.fileINode() == inode) && (img.fileModTime() == mtime) ) { + result = loadedImage.loadedAddress(); + if ( bumpRefCount && !loadedImage.neverUnload() ) + incRefCount(loadedImage.loadedAddress()); + stop = true; + } + } + }); + return result; +} + +const mach_header* AllImages::alreadyLoaded(const char* path, bool bumpRefCount) +{ + __block const mach_header* result = nullptr; + uint32_t targetHash = launch_cache::ImageGroup::hashFunction(path); + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + launch_cache::Image img(loadedImage.image()); + if ( (img.pathHash() == targetHash) && (strcmp(path, imagePath(loadedImage.image())) == 0) ) { + result = loadedImage.loadedAddress(); + if ( bumpRefCount && !loadedImage.neverUnload() ) + incRefCount(loadedImage.loadedAddress()); + stop = true; + } + }); + if ( result == nullptr ) { + // perhaps there was an image override + launch_cache::ImageGroup mainGroup(mainClosureGroup()); + STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, currentGroupsCount(), currentGroupsList); + copyCurrentGroups(currentGroupsList); + mainGroup.forEachImageRefOverride(currentGroupsList, ^(launch_cache::Image standardDylib, launch_cache::Image overrideDyilb, bool& stop) { + if ( strcmp(standardDylib.path(), path) == 0 ) { + result = alreadyLoaded(overrideDyilb.path(), bumpRefCount); + stop = true; + } + }); + } + return result; +} + +const mach_header* AllImages::alreadyLoaded(const BinaryImage* binImage, bool bumpRefCount) +{ + const mach_header* result = findLoadAddressByImage(binImage); + if ( result != nullptr ) { + launch_cache::Image loadedImage(binImage); + if ( bumpRefCount && !loadedImage.neverUnload() ) + incRefCount(result); + } + return result; +} + +void AllImages::incRefCount(const mach_header* loadAddress) +{ + __block bool found = false; + sDlopenRefCounts.forEachWithWriteLock(^(uint32_t index, DlopenCount& entry, bool& stop) { + if ( entry.loadAddress == loadAddress ) { + found = true; + entry.refCount += 1; + stop = true; + } + }); + if ( !found ) { + DlopenCount newEnty = { loadAddress, 1 }; + sDlopenRefCounts.add(newEnty); + } +} + +void AllImages::decRefCount(const mach_header* loadAddress) +{ + __block bool refCountNowZero = false; + sDlopenRefCounts.forEachWithWriteLock(^(uint32_t index, DlopenCount& entry, bool& stop) { + if ( entry.loadAddress == loadAddress ) { + entry.refCount -= 1; + stop = true; + if ( entry.refCount == 0 ) + refCountNowZero = true; + } + }); + if ( refCountNowZero ) { + DlopenCount delEnty = { loadAddress, 0 }; + sDlopenRefCounts.remove(delEnty); + garbageCollectImages(); + } +} + + +#if __MAC_OS_X_VERSION_MIN_REQUIRED +__NSObjectFileImage* AllImages::addNSObjectFileImage() +{ + // look for empty slot first + __block __NSObjectFileImage* result = nullptr; + sNSObjectFileImages.forEachWithWriteLock(^(uint32_t index, __NSObjectFileImage& value, bool& stop) { + if ( (value.path == nullptr) && (value.memSource == nullptr) ) { + result = &value; + stop = true; + } + }); + if ( result != nullptr ) + return result; + + // otherwise allocate new slot + __NSObjectFileImage empty; + return sNSObjectFileImages.add(empty); +} + +bool AllImages::hasNSObjectFileImage(__NSObjectFileImage* ofi) +{ + __block bool result = false; + sNSObjectFileImages.forEachNoLock(^(uint32_t index, const __NSObjectFileImage& value, bool& stop) { + if ( &value == ofi ) { + result = ((value.memSource != nullptr) || (value.path != nullptr)); + stop = true; + } + }); + return result; +} + +void AllImages::removeNSObjectFileImage(__NSObjectFileImage* ofi) +{ + sNSObjectFileImages.forEachWithWriteLock(^(uint32_t index, __NSObjectFileImage& value, bool& stop) { + if ( &value == ofi ) { + // mark slot as empty + ofi->path = nullptr; + ofi->memSource = nullptr; + ofi->memLength = 0; + ofi->loadAddress = nullptr; + ofi->binImage = nullptr; + stop = true; + } + }); +} +#endif + + +class VIS_HIDDEN Reaper +{ +public: + Reaper(uint32_t count, const LoadedImage** unloadables, bool* inUseArray); + void garbageCollect(); + void finalizeDeadImages(); + +private: + typedef launch_cache::binary_format::Image BinaryImage; + + void markDirectlyDlopenedImagesAsUsed(); + void markDependentOfInUseImages(); + void markDependentsOf(const LoadedImage*); + bool loadAddressIsUnloadable(const mach_header* loadAddr, uint32_t& index); + bool imageIsUnloadable(const BinaryImage* binImage, uint32_t& foundIndex); + uint32_t inUseCount(); + void dump(const char* msg); + + const LoadedImage** _unloadablesArray; + bool* _inUseArray; + uint32_t _arrayCount; + uint32_t _deadCount; +}; + +Reaper::Reaper(uint32_t count, const LoadedImage** unloadables, bool* inUseArray) + : _unloadablesArray(unloadables), _inUseArray(inUseArray),_arrayCount(count) +{ +} + + +bool Reaper::loadAddressIsUnloadable(const mach_header* loadAddr, uint32_t& foundIndex) +{ + for (uint32_t i=0; i < _arrayCount; ++i) { + if ( _unloadablesArray[i]->loadedAddress() == loadAddr ) { + foundIndex = i; + return true; + } + } + return false; +} + +bool Reaper::imageIsUnloadable(const BinaryImage* binImage, uint32_t& foundIndex) +{ + for (uint32_t i=0; i < _arrayCount; ++i) { + if ( _unloadablesArray[i]->image() == binImage ) { + foundIndex = i; + return true; + } + } + return false; +} + +void Reaper::markDirectlyDlopenedImagesAsUsed() +{ + sDlopenRefCounts.forEachWithReadLock(^(uint32_t refCountIndex, const dyld3::DlopenCount& dlEntry, bool& stop) { + if ( dlEntry.refCount != 0 ) { + uint32_t foundIndex; + if ( loadAddressIsUnloadable(dlEntry.loadAddress, foundIndex) ) { + _inUseArray[foundIndex] = true; + } + } + }); +} + +uint32_t Reaper::inUseCount() +{ + uint32_t count = 0; + for (uint32_t i=0; i < _arrayCount; ++i) { + if ( _inUseArray[i] ) + ++count; + } + return count; +} + +void Reaper::markDependentsOf(const LoadedImage* entry) +{ + const launch_cache::Image image(entry->image()); + STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, gAllImages.currentGroupsCount(), currentGroupsList); + gAllImages.copyCurrentGroups(currentGroupsList); + image.forEachDependentImage(currentGroupsList, ^(uint32_t depIndex, dyld3::launch_cache::Image depImage, dyld3::launch_cache::Image::LinkKind kind, bool& stop) { + uint32_t foundIndex; + if ( !depImage.neverUnload() && imageIsUnloadable(depImage.binaryData(), foundIndex) ) { + _inUseArray[foundIndex] = true; + } + }); +} + +void Reaper::markDependentOfInUseImages() +{ + for (uint32_t i=0; i < _arrayCount; ++i) { + if ( _inUseArray[i] ) + markDependentsOf(_unloadablesArray[i]); + } +} + +void Reaper::dump(const char* msg) +{ + //log("%s:\n", msg); + for (uint32_t i=0; i < _arrayCount; ++i) { + dyld3::launch_cache::Image image(_unloadablesArray[i]->image()); + //log(" in-used=%d %s\n", _inUseArray[i], image.path()); + } +} + +void Reaper::garbageCollect() +{ + //dump("all unloadable images"); + + // mark all dylibs directly dlopen'ed as in use + markDirectlyDlopenedImagesAsUsed(); + + //dump("directly dlopen()'ed marked"); + + // iteratively mark dependents of in-use dylibs as in-use until in-use count stops changing + uint32_t lastCount = inUseCount(); + bool countChanged = false; + do { + markDependentOfInUseImages(); + //dump("dependents marked"); + uint32_t newCount = inUseCount(); + countChanged = (newCount != lastCount); + lastCount = newCount; + } while (countChanged); + + _deadCount = _arrayCount - inUseCount(); +} + +void Reaper::finalizeDeadImages() +{ + if ( _deadCount == 0 ) + return; + __cxa_range_t ranges[_deadCount]; + __cxa_range_t* rangesArray = ranges; + __block unsigned int rangesCount = 0; + for (uint32_t i=0; i < _arrayCount; ++i) { + if ( _inUseArray[i] ) + continue; + dyld3::launch_cache::Image image(_unloadablesArray[i]->image()); + image.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool &stop) { + if ( permissions & VM_PROT_EXECUTE ) { + rangesArray[rangesCount].addr = (char*)(_unloadablesArray[i]->loadedAddress()) + vmOffset; + rangesArray[rangesCount].length = (size_t)vmSize; + ++rangesCount; + } + }); + } + __cxa_finalize_ranges(ranges, rangesCount); +} + + +// This function is called at the end of dlclose() when the reference count goes to zero. +// The dylib being unloaded may have brought in other dependent dylibs when it was loaded. +// Those dependent dylibs need to be unloaded, but only if they are not referenced by +// something else. We use a standard mark and sweep garbage collection. +// +// The tricky part is that when a dylib is unloaded it may have a termination function that +// can run and itself call dlclose() on yet another dylib. The problem is that this +// sort of gabage collection is not re-entrant. Instead a terminator's call to dlclose() +// which calls garbageCollectImages() will just set a flag to re-do the garbage collection +// when the current pass is done. +// +// Also note that this is done within the sLoadedImages writer lock, so any dlopen/dlclose +// on other threads are blocked while this garbage collections runs +// +void AllImages::garbageCollectImages() +{ + // if some other thread is currently GC'ing images, let other thread do the work + int32_t newCount = OSAtomicIncrement32(&_gcCount); + if ( newCount != 1 ) + return; + + do { + const uint32_t loadedImageCount = sLoadedImages.count(); + const LoadedImage* unloadables[loadedImageCount]; + bool unloadableInUse[loadedImageCount]; + const LoadedImage** unloadablesArray = unloadables; + bool* unloadableInUseArray = unloadableInUse; + __block uint32_t unloadableCount = 0; + // do GC with lock, so no other images can be added during GC + sLoadedImages.withReadLock(^() { + sLoadedImages.forEachNoLock(^(uint32_t index, const LoadedImage& entry, bool& stop) { + const launch_cache::Image image(entry.image()); + if ( !image.neverUnload() && !entry.neverUnload() ) { + unloadablesArray[unloadableCount] = &entry; + unloadableInUseArray[unloadableCount] = false; + //log("unloadable[%d] %p %s\n", unloadableCount, entry.loadedAddress(), image.path()); + ++unloadableCount; + } + }); + // make reaper object to do garbage collection and notifications + Reaper reaper(unloadableCount, unloadablesArray, unloadableInUseArray); + reaper.garbageCollect(); + + // FIXME: we should sort dead images so higher level ones are terminated first + + // call cxa_finalize_ranges of dead images + reaper.finalizeDeadImages(); + + // FIXME: call static terminators of dead images + + // FIXME: DOF unregister + }); + + //log("sLoadedImages before GC removals:\n"); + //sLoadedImages.dump(^(const LoadedImage& entry) { + // const launch_cache::Image image(entry.image()); + // log(" loadAddr=%p, path=%s\n", entry.loadedAddress(), image.path()); + //}); + + // make copy of LoadedImages we want to remove + // because unloadables[] points into ChunkVector we are shrinking + uint32_t removalCount = 0; + for (uint32_t i=0; i < unloadableCount; ++i) { + if ( !unloadableInUse[i] ) + ++removalCount; + } + if ( removalCount > 0 ) { + STACK_ALLOC_DYNARRAY(loader::ImageInfo, removalCount, unloadImages); + uint32_t removalIndex = 0; + for (uint32_t i=0; i < unloadableCount; ++i) { + if ( !unloadableInUse[i] ) { + unloadImages[removalIndex].loadAddress = unloadables[i]->loadedAddress(); + unloadImages[removalIndex].imageData = unloadables[i]->image(); + ++removalIndex; + } + } + // remove entries from sLoadedImages + removeImages(unloadImages); + + //log("sLoadedImages after GC removals:\n"); + //sLoadedImages.dump(^(const LoadedImage& entry) { + // const launch_cache::Image image(entry.image()); + // //log(" loadAddr=%p, path=%s\n", entry.loadedAddress(), image.path()); + //}); + } + + // if some other thread called GC during our work, redo GC on its behalf + newCount = OSAtomicDecrement32(&_gcCount); + } + while (newCount > 0); +} + + + +VIS_HIDDEN +const launch_cache::binary_format::Image* AllImages::messageClosured(const char* path, const char* apiName, const char* closuredErrorMessages[3], int& closuredErrorMessagesCount) +{ + __block const launch_cache::binary_format::Image* result = nullptr; + sKnownGroups.withWriteLock(^() { + ClosureBuffer::CacheIdent cacheIdent; + bzero(&cacheIdent, sizeof(cacheIdent)); + if ( _dyldCacheAddress != nullptr ) { + const DyldSharedCache* dyldCache = (DyldSharedCache*)_dyldCacheAddress; + dyldCache->getUUID(cacheIdent.cacheUUID); + cacheIdent.cacheAddress = (unsigned long)_dyldCacheAddress; + cacheIdent.cacheMappedSize = dyldCache->mappedSize(); + } + gPathOverrides.forEachPathVariant(path, ^(const char* possiblePath, bool& stopVariants) { + struct stat statBuf; + if ( stat(possiblePath, &statBuf) == 0 ) { + if ( S_ISDIR(statBuf.st_mode) ) { + log_apis(" %s: path is directory: %s\n", apiName, possiblePath); + if ( closuredErrorMessagesCount < 3 ) + closuredErrorMessages[closuredErrorMessagesCount++] = strdup("not a file"); + } + else { + // file exists, ask closured to build info for it + STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, sKnownGroups.countNoLock(), currentGroupsList); + gAllImages.copyCurrentGroupsNoLock(currentGroupsList); + dyld3::launch_cache::DynArray nonCacheGroupList(currentGroupsList.count()-2, ¤tGroupsList[2]); + const dyld3::launch_cache::binary_format::ImageGroup* closuredCreatedGroupData = nullptr; + ClosureBuffer closureBuilderInput(cacheIdent, path, nonCacheGroupList, gPathOverrides); + ClosureBuffer closureBuilderOutput = dyld3::closured_CreateImageGroup(closureBuilderInput); + if ( !closureBuilderOutput.isError() ) { + vm_protect(mach_task_self(), closureBuilderOutput.vmBuffer(), closureBuilderOutput.vmBufferSize(), false, VM_PROT_READ); + closuredCreatedGroupData = closureBuilderOutput.imageGroup(); + log_apis(" %s: closured built ImageGroup for path: %s\n", apiName, possiblePath); + sKnownGroups.addNoLock(closuredCreatedGroupData); + launch_cache::ImageGroup group(closuredCreatedGroupData); + result = group.imageBinary(0); + stopVariants = true; + } + else { + log_apis(" %s: closured failed for path: %s, error: %s\n", apiName, possiblePath, closureBuilderOutput.errorMessage()); + if ( closuredErrorMessagesCount < 3 ) { + closuredErrorMessages[closuredErrorMessagesCount++] = strdup(closureBuilderOutput.errorMessage()); + } + closureBuilderOutput.free(); + } + } + } + else { + log_apis(" %s: file does not exist for path: %s\n", apiName, possiblePath); + } + }); + }); + + return result; +} + +const AllImages::BinaryImage* AllImages::findImageInKnownGroups(const char* path) +{ + __block const AllImages::BinaryImage* result = nullptr; + sKnownGroups.forEachWithReadLock(^(uint32_t index, const dyld3::launch_cache::binary_format::ImageGroup* const& grpData, bool& stop) { + launch_cache::ImageGroup group(grpData); + uint32_t ignore; + if ( const AllImages::BinaryImage* binImage = group.findImageByPath(path, ignore) ) { + result = binImage; + stop = true; + } + }); + return result; +} + +bool AllImages::imageUnloadable(const launch_cache::Image& image, const mach_header* loadAddress) const +{ + // check if statically determined in clousre that this can never be unloaded + if ( image.neverUnload() ) + return false; + + // check if some runtime decision made this be never-unloadable + __block bool foundAsNeverUnload = false; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + if ( loadedImage.loadedAddress() == loadAddress ) { + stop = true; + if ( loadedImage.neverUnload() ) + foundAsNeverUnload = true; + } + }); + if ( foundAsNeverUnload ) + return false; + + return true; +} + +void AllImages::addLoadNotifier(NotifyFunc func) +{ + // callback about already loaded images + const uint32_t existingCount = sLoadedImages.count(); + const mach_header* existingMHs[existingCount]; + const mach_header** existingArray = existingMHs; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + if ( anIndex < existingCount ) + existingArray[anIndex] = loadedImage.loadedAddress(); + }); + // we don't want to hold lock while calling out, so prebuild array (with lock) then do calls on that array (without lock) + for (uint32_t i=0; i < existingCount; i++) { + MachOParser parser(existingArray[i]); + log_notifications("dyld: add notifier %p called with mh=%p\n", func, existingArray[i]); + func(existingArray[i], parser.getSlide()); + } + + // add to list of functions to call about future loads + sLoadNotifiers.add(func); +} + +void AllImages::addUnloadNotifier(NotifyFunc func) +{ + // add to list of functions to call about future unloads + sUnloadNotifiers.add(func); +} + +void AllImages::setObjCNotifiers(_dyld_objc_notify_mapped map, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmap) +{ + _objcNotifyMapped = map; + _objcNotifyInit = init; + _objcNotifyUnmapped = unmap; + + // callback about already loaded images + uint32_t maxCount = count(); + const char* pathsBuffer[maxCount]; + const mach_header* mhBuffer[maxCount]; + __block const char** paths = pathsBuffer; + __block const mach_header** mhs = mhBuffer; + __block uint32_t imagesWithObjC = 0; + sLoadedImages.forEachWithReadLock(^(uint32_t anIndex, const LoadedImage& loadedImage, bool& stop) { + launch_cache::Image img(loadedImage.image()); + if ( img.hasObjC() ) { + mhs[imagesWithObjC] = loadedImage.loadedAddress(); + paths[imagesWithObjC] = imagePath(loadedImage.image()); + ++imagesWithObjC; + } + }); + if ( imagesWithObjC != 0 ) { + (*map)(imagesWithObjC, pathsBuffer, mhBuffer); + if ( log_notifications("dyld: objc-mapped-notifier called with %d images:\n", imagesWithObjC) ) { + for (uint32_t i=0; i < imagesWithObjC; ++i) { + log_notifications("dyld: objc-mapped: %p %s\n", mhBuffer[i], pathsBuffer[i]); + } + } + } +} + +void AllImages::vmAccountingSetSuspended(bool suspend) +{ +#if __arm__ || __arm64__ + // dyld should tell the kernel when it is doing fix-ups caused by roots + log_fixups("vm.footprint_suspend=%d\n", suspend); + int newValue = suspend ? 1 : 0; + int oldValue = 0; + size_t newlen = sizeof(newValue); + size_t oldlen = sizeof(oldValue); + sysctlbyname("vm.footprint_suspend", &oldValue, &oldlen, &newValue, newlen); +#endif +} + +void AllImages::applyInterposingToDyldCache(const launch_cache::binary_format::Closure* closure, const dyld3::launch_cache::DynArray& initialImages) +{ + launch_cache::Closure mainClosure(closure); + launch_cache::ImageGroup mainGroup = mainClosure.group(); + DyldCacheParser cacheParser((DyldSharedCache*)_dyldCacheAddress, false); + const launch_cache::binary_format::ImageGroup* dylibsGroupData = cacheParser.cachedDylibsGroup(); + launch_cache::ImageGroup dyldCacheDylibGroup(dylibsGroupData); + __block bool suspendedAccounting = false; + mainGroup.forEachDyldCacheSymbolOverride(^(uint32_t patchTableIndex, const launch_cache::binary_format::Image* imageData, uint32_t imageOffset, bool& stop) { + bool foundInImages = false; + for (int i=0; i < initialImages.count(); ++i) { + if ( initialImages[i].imageData == imageData ) { + foundInImages = true; + uintptr_t replacement = (uintptr_t)(initialImages[i].loadAddress) + imageOffset; + dyldCacheDylibGroup.forEachDyldCachePatchLocation(_dyldCacheAddress, patchTableIndex, ^(uintptr_t* locationToPatch, uintptr_t addend, bool& innerStop) { + if ( !suspendedAccounting ) { + vmAccountingSetSuspended(true); + suspendedAccounting = true; + } + log_fixups("dyld: cache fixup: *%p = %p\n", locationToPatch, (void*)replacement); + *locationToPatch = replacement + addend; + }); + break; + } + } + if ( !foundInImages ) { + launch_cache::Image img(imageData); + log_fixups("did not find loaded image to patch into cache: %s\n", img.path()); + } + }); + if ( suspendedAccounting ) + vmAccountingSetSuspended(false); +} + +void AllImages::runLibSystemInitializer(const mach_header* libSystemAddress, const launch_cache::binary_format::Image* libSystemBinImage) +{ + // run all initializers in image + launch_cache::Image libSystemImage(libSystemBinImage); + libSystemImage.forEachInitializer(libSystemAddress, ^(const void* func) { + Initializer initFunc = (Initializer)func; + dyld3::kdebug_trace_dyld_duration(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)func, 0, ^{ + initFunc(NXArgc, NXArgv, environ, appleParams, _programVars); + }); + log_initializers("called initialzer %p in %s\n", initFunc, libSystemImage.path()); + }); + + // mark libSystem.dylib as being init, so later recursive-init would re-run it + sLoadedImages.forEachWithWriteLock(^(uint32_t anIndex, LoadedImage& loadedImage, bool& stop) { + if ( loadedImage.loadedAddress() == libSystemAddress ) { + loadedImage.setState(LoadedImage::State::inited); + stop = true; + } + }); +} + +void AllImages::runInitialzersBottomUp(const mach_header* imageLoadAddress) +{ + launch_cache::Image topImage = findByLoadAddress(imageLoadAddress); + if ( topImage.isInvalid() ) + return; + + // closure contains list of intializers to run in-order + STACK_ALLOC_DYNARRAY(const launch_cache::BinaryImageGroupData*, currentGroupsCount(), currentGroupsList); + copyCurrentGroups(currentGroupsList); + topImage.forEachInitBefore(currentGroupsList, ^(launch_cache::Image imageToInit) { + // find entry + __block LoadedImage* foundEntry = nullptr; + sLoadedImages.forEachWithReadLock(^(uint32_t index, const LoadedImage& entry, bool& stop) { + if ( entry.image() == imageToInit.binaryData() ) { + foundEntry = (LoadedImage*)&entry; + stop = true; + } + }); + assert(foundEntry != nullptr); + pthread_mutex_lock(&_initializerLock); + // Note, due to the large lock in dlopen, we can't be waiting on another thread + // here, but its possible that we are in a dlopen which is initialising us again + if ( foundEntry->state() == LoadedImage::State::beingInited ) { + log_initializers("dyld: already initializing '%s'\n", imagePath(imageToInit.binaryData())); + } + // at this point, the image is either initialized or not + // if not, initialize it on this thread + if ( foundEntry->state() == LoadedImage::State::uninited ) { + foundEntry->setState(LoadedImage::State::beingInited); + // release initializer lock, so other threads can run initializers + pthread_mutex_unlock(&_initializerLock); + // tell objc to run any +load methods in image + if ( (_objcNotifyInit != nullptr) && imageToInit.mayHavePlusLoads() ) { + log_notifications("dyld: objc-init-notifier called with mh=%p, path=%s\n", foundEntry->loadedAddress(), imagePath(imageToInit.binaryData())); + (*_objcNotifyInit)(imagePath(imageToInit.binaryData()), foundEntry->loadedAddress()); + } + // run all initializers in image + imageToInit.forEachInitializer(foundEntry->loadedAddress(), ^(const void* func) { + Initializer initFunc = (Initializer)func; + dyld3::kdebug_trace_dyld_duration(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)func, 0, ^{ + initFunc(NXArgc, NXArgv, environ, appleParams, _programVars); + }); + log_initializers("dyld: called initialzer %p in %s\n", initFunc, imageToInit.path()); + }); + // reaquire initializer lock to switch state to inited + pthread_mutex_lock(&_initializerLock); + foundEntry->setState(LoadedImage::State::inited); + } + pthread_mutex_unlock(&_initializerLock); + }); +} + + +} // namespace dyld3 + + + + + + diff --git a/dyld3/AllImages.h b/dyld3/AllImages.h new file mode 100644 index 0000000..1f644bc --- /dev/null +++ b/dyld3/AllImages.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2017 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 __ALL_IMAGES_H__ +#define __ALL_IMAGES_H__ + +#include +#include + +#include "dyld_priv.h" + +#include "LaunchCache.h" +#include "Loading.h" + + + +#if __MAC_OS_X_VERSION_MIN_REQUIRED +// only in macOS and deprecated +struct VIS_HIDDEN __NSObjectFileImage +{ + const char* path = nullptr; + const void* memSource = nullptr; + size_t memLength = 0; + const mach_header* loadAddress = nullptr; + const dyld3::launch_cache::binary_format::Image* binImage = nullptr; +}; +#endif + +namespace dyld3 { + +class VIS_HIDDEN AllImages +{ +public: + typedef launch_cache::binary_format::Closure BinaryClosure; + typedef launch_cache::binary_format::ImageGroup BinaryImageGroup; + typedef launch_cache::binary_format::Image BinaryImage; + typedef launch_cache::ImageGroupList ImageGroupList; + typedef void (*NotifyFunc)(const mach_header* mh, intptr_t slide); + + void init(const BinaryClosure* closure, const void* dyldCacheLoadAddress, const char* dyldCachePath, + const dyld3::launch_cache::DynArray& initialImages); + void setMainPath(const char* path); + void applyInitialImages(); + + void addImages(const dyld3::launch_cache::DynArray& newImages); + void removeImages(const launch_cache::DynArray& unloadImages); + void setNeverUnload(const loader::ImageInfo& existingImage); + void applyInterposingToDyldCache(const launch_cache::binary_format::Closure* closure, const dyld3::launch_cache::DynArray& initialImages); + void runInitialzersBottomUp(const mach_header* imageLoadAddress); + void setInitialGroups(); + + uint32_t count() const; + const BinaryImageGroup* cachedDylibsGroup(); + const BinaryImageGroup* otherDylibsGroup(); + const BinaryImageGroup* mainClosureGroup(); + const BinaryClosure* mainClosure() { return _mainClosure; } + uint32_t currentGroupsCount() const; + void copyCurrentGroups(ImageGroupList& groups) const; + + const BinaryImage* messageClosured(const char* path, const char* apiName, const char* closuredErrorMessages[3], int& closuredErrorMessagesCount); + + launch_cache::Image findByLoadOrder(uint32_t index, const mach_header** loadAddress) const; + launch_cache::Image findByLoadAddress(const mach_header* loadAddress) const; + launch_cache::Image findByOwnedAddress(const void* addr, const mach_header** loadAddress, uint8_t* permissions=nullptr) const; + const mach_header* findLoadAddressByImage(const BinaryImage*) const; + bool findIndexForLoadAddress(const mach_header* loadAddress, uint32_t& index); + void forEachImage(void (^handler)(uint32_t imageIndex, const mach_header* loadAddress, const launch_cache::Image image, bool& stop)) const; + + const mach_header* mainExecutable() const; + launch_cache::Image mainExecutableImage() const; + const void* cacheLoadAddress() const { return _dyldCacheAddress; } + const char* dyldCachePath() const { return _dyldCachePath; } + const char* imagePath(const BinaryImage*) const; + + const mach_header* alreadyLoaded(const char* path, bool bumpRefCount); + const mach_header* alreadyLoaded(const BinaryImage*, bool bumpRefCount); + const mach_header* alreadyLoaded(uint64_t inode, uint64_t mtime, bool bumpRefCount); + const BinaryImage* findImageInKnownGroups(const char* path); + + bool imageUnloadable(const launch_cache::Image& image, const mach_header* loadAddress) const; + void incRefCount(const mach_header* loadAddress); + void decRefCount(const mach_header* loadAddress); + + void addLoadNotifier(NotifyFunc); + void addUnloadNotifier(NotifyFunc); + void setObjCNotifiers(_dyld_objc_notify_mapped, _dyld_objc_notify_init, _dyld_objc_notify_unmapped); + void notifyObjCUnmap(const char* path, const struct mach_header* mh); + + void runLibSystemInitializer(const mach_header* imageLoadAddress, const launch_cache::binary_format::Image* binImage); + + void setOldAllImageInfo(dyld_all_image_infos* old) { _oldAllImageInfos = old; } + dyld_all_image_infos* oldAllImageInfo() const { return _oldAllImageInfos;} + + void notifyMonitorMain(); + void notifyMonitorLoads(const launch_cache::DynArray& newImages); + void notifyMonitorUnloads(const launch_cache::DynArray& unloadingImages); + +#if __MAC_OS_X_VERSION_MIN_REQUIRED + __NSObjectFileImage* addNSObjectFileImage(); + bool hasNSObjectFileImage(__NSObjectFileImage*); + void removeNSObjectFileImage(__NSObjectFileImage*); +#endif + + struct ProgramVars + { + const void* mh; + int* NXArgcPtr; + const char*** NXArgvPtr; + const char*** environPtr; + const char** __prognamePtr; + }; + void setProgramVars(ProgramVars* vars); + +private: + + typedef void (*Initializer)(int argc, const char* argv[], char* envp[], const char* apple[], const ProgramVars* vars); + typedef const launch_cache::DynArray StartImageArray; + + void runInitialzersInImage(const mach_header* imageLoadAddress, const launch_cache::binary_format::Image* binImage); + void mirrorToOldAllImageInfos(); + void garbageCollectImages(); + void vmAccountingSetSuspended(bool suspend); + void copyCurrentGroupsNoLock(ImageGroupList& groups) const; + + const BinaryClosure* _mainClosure = nullptr; + const void* _dyldCacheAddress = nullptr; + const char* _dyldCachePath = nullptr; + StartImageArray* _initialImages = nullptr; + const char* _mainExeOverridePath = nullptr; + _dyld_objc_notify_mapped _objcNotifyMapped = nullptr; + _dyld_objc_notify_init _objcNotifyInit = nullptr; + _dyld_objc_notify_unmapped _objcNotifyUnmapped = nullptr; + ProgramVars* _programVars = nullptr; + dyld_all_image_infos* _oldAllImageInfos = nullptr; + dyld_image_info* _oldAllImageArray = nullptr; + uint32_t _oldArrayAllocCount = 0; + pthread_mutex_t _initializerLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; + pthread_cond_t _initializerCondition= PTHREAD_COND_INITIALIZER; + int32_t _gcCount = 0; +}; + +extern AllImages gAllImages; + + +} // dyld3 + + +#endif // __ALL_IMAGES_H__ diff --git a/dyld3/ClosureBuffer.cpp b/dyld3/ClosureBuffer.cpp new file mode 100644 index 0000000..0231827 --- /dev/null +++ b/dyld3/ClosureBuffer.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2017 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 "ClosureBuffer.h" +#include "PathOverrides.h" + + +namespace dyld3 { + +TypedContentBuffer::TypedContentBuffer(size_t elementsCount, size_t elementsTotalSize) +{ + _size = elementsTotalSize + (elementsCount+1)*(sizeof(Element)+4); // worst case padding, plus "end" element + vm_address_t bufferAddress = 0; + assert(::vm_allocate(mach_task_self(), &bufferAddress, _size, VM_FLAGS_ANYWHERE) == 0); + _buffer = (Element*)bufferAddress; + _currentEnd = _buffer; + _readOnly = false; +} + +void TypedContentBuffer::free() +{ + if ( _buffer != nullptr ) + vm_deallocate(mach_task_self(), (long)_buffer, _size); + _buffer = nullptr; +} + +void TypedContentBuffer::addItem(uint32_t k, const void* content, size_t len) +{ + assert(!_readOnly); + assert(((char*)_currentEnd + len) < ((char*)_buffer + _size)); + _currentEnd->kind = k; + _currentEnd->contentLength = (uint32_t)len; + if ( len != 0 ) + memmove(&(_currentEnd->content), content, len); + size_t delta = (sizeof(Element) + len + 3) & (-4); + _currentEnd = (Element*)((char*)_currentEnd + delta); +} + +vm_address_t TypedContentBuffer::vmBuffer() const +{ + assert(_readOnly); + return (vm_address_t)_buffer; +} + +uint32_t TypedContentBuffer::vmBufferSize() const +{ + assert(_readOnly); + return (uint32_t)_size; +} + +void TypedContentBuffer::doneBuilding() +{ + _readOnly = true; +} + + +const TypedContentBuffer::Element* TypedContentBuffer::Element::next() const +{ + return (Element*)((char*)this + sizeof(Element) + ((contentLength + 3) & -4)); +} + +TypedContentBuffer::TypedContentBuffer(const void* buff, size_t buffSize) + : _size(buffSize), _buffer((Element*)buff), _currentEnd((Element*)((char*)buff+buffSize)), _readOnly(true) +{ +} + +unsigned TypedContentBuffer::count(uint32_t kind) const +{ + assert(_readOnly); + unsigned count = 0; + for (const Element* e = _buffer; e->kind != 0; e = e->next()) { + if ( e->kind == kind ) + ++count; + } + return count; +} + +void TypedContentBuffer::forEach(uint32_t kind, void (^callback)(const void* content, size_t length)) const +{ + assert(_readOnly); + for (const Element* e = _buffer; e->kind != 0; e = e->next()) { + if ( e->kind == kind ) { + callback(&(e->content), e->contentLength); + } + } +} + +#if !BUILDING_CLOSURED + +ClosureBuffer::ClosureBuffer(const CacheIdent& cacheIdent, const char* path, const launch_cache::ImageGroupList& groups, const PathOverrides& envVars) + : TypedContentBuffer(2 + envVars.envVarCount() + groups.count(), computeSize(path, groups, envVars)) +{ + addItem(kindCacheIdent, &cacheIdent, sizeof(CacheIdent)); + addItem(kindTargetPath, path, strlen(path)+1); + envVars.forEachEnvVar(^(const char* envVar) { + addItem(kindEnvVar, envVar, strlen(envVar)+1); + }); + for (size_t i=0; i < groups.count(); ++i) { + launch_cache::ImageGroup group(groups[i]); + addItem(kindImageGroup, group.binaryData(), group.size()); + } + addItem(kindEnd, nullptr, 0); + doneBuilding(); +} + +size_t ClosureBuffer::computeSize(const char* path, const launch_cache::ImageGroupList& groups, const PathOverrides& envVars) +{ + __block size_t result = sizeof(CacheIdent); + result += (strlen(path) + 1); + envVars.forEachEnvVar(^(const char* envVar) { + result += (strlen(envVar) + 1); + }); + for (size_t i=0; i < groups.count(); ++i) { + launch_cache::ImageGroup group(groups[i]); + result += group.size(); + } + return result; +} + +#endif + +ClosureBuffer::ClosureBuffer(const char* errorMessage) + : TypedContentBuffer(1, strlen(errorMessage+2)) +{ + addItem(kindErrorMessage, errorMessage, strlen(errorMessage)+1); + doneBuilding(); +} + +ClosureBuffer::ClosureBuffer(const launch_cache::BinaryImageGroupData* imageGroup) + : TypedContentBuffer(1, launch_cache::ImageGroup(imageGroup).size()) +{ + addItem(kindImageGroup, imageGroup, launch_cache::ImageGroup(imageGroup).size()); + doneBuilding(); +} + +ClosureBuffer::ClosureBuffer(const launch_cache::BinaryClosureData* closure) + : TypedContentBuffer(1, launch_cache::Closure(closure).size()) +{ + addItem(kindClosure, closure, launch_cache::Closure(closure).size()); + doneBuilding(); +} + + +ClosureBuffer::ClosureBuffer(const void* buff, size_t buffSize) + : TypedContentBuffer(buff, buffSize) +{ +} + +const ClosureBuffer::CacheIdent& ClosureBuffer::cacheIndent() const +{ + __block CacheIdent* ident = nullptr; + forEach(kindCacheIdent, ^(const void* content, size_t length) { + ident = (CacheIdent*)content; + assert(length == sizeof(CacheIdent)); + }); + assert(ident != nullptr); + return *ident; +} + +const char* ClosureBuffer::targetPath() const +{ + __block char* path = nullptr; + forEach(kindTargetPath, ^(const void* content, size_t length) { + path = (char*)content; + }); + assert(path != nullptr); + return path; +} + +uint32_t ClosureBuffer::envVarCount() const +{ + __block uint32_t count = 0; + forEach(kindEnvVar, ^(const void* content, size_t length) { + ++count; + }); + return count; +} + +void ClosureBuffer::copyImageGroups(const char* envVars[]) const +{ + __block uint32_t index = 0; + forEach(kindEnvVar, ^(const void* content, size_t length) { + envVars[index] = (char*)content; + ++index; + }); +} + +uint32_t ClosureBuffer::imageGroupCount() const +{ + __block uint32_t count = 0; + forEach(kindImageGroup, ^(const void* content, size_t length) { + ++count; + }); + return count; +} + +void ClosureBuffer::copyImageGroups(const launch_cache::BinaryImageGroupData* imageGroups[]) const +{ + __block uint32_t index = 0; + forEach(kindImageGroup, ^(const void* content, size_t length) { + imageGroups[index] = (launch_cache::BinaryImageGroupData*)content; + ++index; + }); +} + +bool ClosureBuffer::isError() const +{ + return ( errorMessage() != nullptr ); +} + +const char* ClosureBuffer::errorMessage() const +{ + __block char* message = nullptr; + forEach(kindErrorMessage, ^(const void* content, size_t length) { + message = (char*)content; + }); + return message; +} + +const launch_cache::BinaryClosureData* ClosureBuffer::closure() const +{ + __block const launch_cache::BinaryClosureData* result = nullptr; + forEach(kindClosure, ^(const void* content, size_t length) { + result = (const launch_cache::BinaryClosureData*)content; + }); + assert(result != nullptr); + return result; +} + + +const launch_cache::BinaryImageGroupData* ClosureBuffer::imageGroup() const +{ + __block const launch_cache::BinaryImageGroupData* result = nullptr; + forEach(kindImageGroup, ^(const void* content, size_t length) { + result = (const launch_cache::BinaryImageGroupData*)content; + }); + assert(result != nullptr); + return result; +} + + + + + + +} // namespace dyld3 + diff --git a/dyld3/ClosureBuffer.h b/dyld3/ClosureBuffer.h new file mode 100644 index 0000000..c1ab2c3 --- /dev/null +++ b/dyld3/ClosureBuffer.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017 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_CLOSURE_BUFFER_H__ +#define __DYLD_CLOSURE_BUFFER_H__ + +#include "Logging.h" +#include "LaunchCache.h" +#include "PathOverrides.h" + +namespace dyld3 { + + +// simple class for packing typed content into a vm_allocated buffer +class VIS_HIDDEN TypedContentBuffer +{ +public: + // buffer creation + TypedContentBuffer(size_t elementsCount, size_t elementsTotalSize); + void addItem(uint32_t k, const void* content, size_t len); + void doneBuilding(); + vm_address_t vmBuffer() const; + uint32_t vmBufferSize() const; + + // buffer parsing + TypedContentBuffer(const void* buff, size_t buffSize); + unsigned count(uint32_t) const; + void forEach(uint32_t, void (^callback)(const void* content, size_t length)) const; + + void free(); + +private: + struct Element + { + uint32_t kind; + uint32_t contentLength; + uint8_t content[]; + + const Element* next() const; + }; + + size_t _size; + Element* _buffer; + Element* _currentEnd; + bool _readOnly; +}; + + +class VIS_HIDDEN ClosureBuffer : public TypedContentBuffer +{ +public: + + struct CacheIdent + { + uint8_t cacheUUID[16]; + uint64_t cacheAddress; + uint64_t cacheMappedSize; + }; + + // client creation + ClosureBuffer(const CacheIdent&, const char* path, const launch_cache::ImageGroupList& groups, const PathOverrides& envVars); + + // closured creation + ClosureBuffer(const char* errorMessage); + ClosureBuffer(const launch_cache::BinaryImageGroupData* imageGroupResult); + ClosureBuffer(const launch_cache::BinaryClosureData* closureResult); + + // client extraction + bool isError() const; + const char* errorMessage() const; + const launch_cache::BinaryClosureData* closure() const; + const launch_cache::BinaryImageGroupData* imageGroup() const; + + // closure builder usage + ClosureBuffer(const void* buff, size_t buffSize); + const CacheIdent& cacheIndent() const; + const char* targetPath() const; + uint32_t envVarCount() const; + void copyImageGroups(const char* envVars[]) const; + uint32_t imageGroupCount() const; + void copyImageGroups(const launch_cache::BinaryImageGroupData* imageGroups[]) const; + +private: + enum { kindEnd=0, kindCacheIdent, kindTargetPath, kindEnvVar, kindImageGroup, kindClosure, kindErrorMessage }; + static size_t computeSize(const char* path, const launch_cache::ImageGroupList& groups, const PathOverrides& envVars); + +}; + + + + +} // namespace dyld3 + +#endif // __DYLD_CLOSURE_BUFFER_H__ diff --git a/dyld3/CodeSigningTypes.h b/dyld3/CodeSigningTypes.h new file mode 100644 index 0000000..c84a708 --- /dev/null +++ b/dyld3/CodeSigningTypes.h @@ -0,0 +1,156 @@ +/* -*- 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_ALTERNATE_CODEDIRECTORIES = 0x1000, + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, + CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = + CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, + CSSLOT_CMS_SIGNATURE = 0x10000, + + kSecCodeSignatureAdhoc = 2 +}; + +enum { + CS_REQUIRE_LV = 0x0002000 // require library validation +}; + +// +// 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) + + char end_earliest[0]; + + /* Version 0x20100 */ + uint32_t scatterOffset; /* offset of optional scatter vector */ + char end_withScatter[0]; + + /* Version 0x20200 */ + uint32_t teamOffset; /* offset of optional team identifier */ + char end_withTeam[0]; + + /* Version 0x20300 */ + uint32_t spare3; /* unused (must be zero) */ + uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ + char end_withCodeLimit64[0]; + + /* Version 0x20400 */ + uint64_t execSegBase; /* offset of executable segment */ + uint64_t execSegLimit; /* limit of executable segment */ + uint64_t execSegFlags; /* exec segment flags */ + char end_withExecSeg[0]; + + /* 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/dyld3/Diagnostics.cpp b/dyld3/Diagnostics.cpp new file mode 100644 index 0000000..a8e4bca --- /dev/null +++ b/dyld3/Diagnostics.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2017 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 <_simple.h> +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Diagnostics.h" + +#if BUILDING_CACHE_BUILDER + #include + dispatch_queue_t sWarningQueue = dispatch_queue_create("com.apple.dyld.cache-builder.warnings", NULL); +#endif + +Diagnostics::Diagnostics(bool verbose) +#if !DYLD_IN_PROCESS + : _verbose(verbose) + , _prefix("") +#endif +{ +} + +#if !DYLD_IN_PROCESS +Diagnostics::Diagnostics(const std::string& prefix, bool verbose) + : _verbose(verbose) + , _prefix(prefix) +{ +} +#endif + +Diagnostics::~Diagnostics() +{ + clearError(); +} + +void Diagnostics::error(const char* format, ...) +{ + _buffer = _simple_salloc(); + va_list list; + va_start(list, format); + _simple_vsprintf(_buffer, format, list); + va_end(list); + +#if !DYLD_IN_PROCESS + if ( !_verbose ) + return; + + char *output_string; + va_start(list, format); + vasprintf(&output_string, format, list); + va_end(list); + + if (_prefix.empty()) { + fprintf(stderr, "%s", output_string); + } else { + fprintf(stderr, "[%s] %s", _prefix.c_str(), output_string); + } +#endif +} + +bool Diagnostics::hasError() const +{ + return _buffer != nullptr; +} + +bool Diagnostics::noError() const +{ + return _buffer == nullptr; +} + +void Diagnostics::clearError() +{ + if ( _buffer ) + _simple_sfree(_buffer); + _buffer = nullptr; +} + +void Diagnostics::assertNoError() const +{ + if ( _buffer != nullptr ) + abort_report_np("%s", _simple_string(_buffer)); +} + +#if DYLD_IN_PROCESS +const char* Diagnostics::errorMessage() const +{ + return _simple_string(_buffer); +} + +#else +void Diagnostics::warning(const char* format, ...) +{ + _SIMPLE_STRING tmp = _simple_salloc(); + va_list list; + va_start(list, format); + _simple_vsprintf(tmp, format, list); + va_end(list); +#if BUILDING_CACHE_BUILDER + dispatch_sync(sWarningQueue, ^{ + _warnings.insert(_simple_string(tmp)); + }); +#else + _warnings.insert(_simple_string(tmp)); +#endif + _simple_sfree(tmp); +} + +void Diagnostics::verbose(const char* format, ...) +{ + if ( !_verbose ) + return; + + char* output_string; + va_list list; + va_start(list, format); + vasprintf(&output_string, format, list); + va_end(list); + + if (_prefix.empty()) { + fprintf(stderr, "%s", output_string); + } else { + fprintf(stderr, "[%s] %s", _prefix.c_str(), output_string); + } +} + +const std::string Diagnostics::prefix() const +{ + return _prefix; +} + +void Diagnostics::copy(const Diagnostics& other) +{ + if ( other.hasError() ) + error("%s", other.errorMessage().c_str()); + for (const std::string& warn : other.warnings()) + warning("%s", warn.c_str()); +} + +std::string Diagnostics::errorMessage() const +{ + if ( _buffer != nullptr ) + return _simple_string(_buffer); + else + return std::string(); +} + +const std::set Diagnostics::warnings() const +{ +#if BUILDING_CACHE_BUILDER + __block std::set retval; + dispatch_sync(sWarningQueue, ^{ + retval = _warnings; + }); + return retval; +#else + return _warnings; +#endif +} + +void Diagnostics::clearWarnings() +{ +#if BUILDING_CACHE_BUILDER + dispatch_sync(sWarningQueue, ^{ + _warnings.clear(); + }); +#else + _warnings.clear(); +#endif +} + +#endif + diff --git a/dyld3/Diagnostics.h b/dyld3/Diagnostics.h new file mode 100644 index 0000000..81fcf82 --- /dev/null +++ b/dyld3/Diagnostics.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017 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 Diagnostics_h +#define Diagnostics_h + +#include + +#if !DYLD_IN_PROCESS +#include +#include +#include +#include +#endif + +#include "Logging.h" + + +class VIS_HIDDEN Diagnostics +{ +public: + Diagnostics(bool verbose=false); + ~Diagnostics(); + + void error(const char* format, ...) __attribute__((format(printf, 2, 3))); +#if !DYLD_IN_PROCESS + Diagnostics(const std::string& prefix, bool verbose=false); + + void warning(const char* format, ...) __attribute__((format(printf, 2, 3))); + void verbose(const char* format, ...) __attribute__((format(printf, 2, 3))); + void copy(const Diagnostics&); +#endif + + bool hasError() const; + bool noError() const; + void clearError(); + void assertNoError() const; + +#if DYLD_IN_PROCESS + const char* errorMessage() const; +#else + const std::string prefix() const; + std::string errorMessage() const; + const std::set warnings() const; + void clearWarnings(); +#endif + +private: + void* _buffer = nullptr; +#if !DYLD_IN_PROCESS + std::string _prefix; + std::set _warnings; + bool _verbose = false; +#endif +}; + + + + +#endif // Diagnostics_h diff --git a/dyld3/DyldCacheParser.cpp b/dyld3/DyldCacheParser.cpp new file mode 100644 index 0000000..4547027 --- /dev/null +++ b/dyld3/DyldCacheParser.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2017 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 "DyldCacheParser.h" +#include "Trie.hpp" + + +namespace dyld3 { + +DyldCacheParser::DyldCacheParser(const DyldSharedCache* cacheHeader, bool rawFile) +{ + _data = (long)cacheHeader; + if ( rawFile ) + _data |= 1; +} + +const dyld_cache_header* DyldCacheParser::header() const +{ + return (dyld_cache_header*)(_data & -2); +} + +const DyldSharedCache* DyldCacheParser::cacheHeader() const +{ + return (DyldSharedCache*)header(); +} + +bool DyldCacheParser::cacheIsMappedRaw() const +{ + return (_data & 1); +} + + +uint64_t DyldCacheParser::dataRegionRuntimeVmOffset() const +{ + const dyld_cache_header* cacheHeader = header(); + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)cacheHeader + cacheHeader->mappingOffset); + return (mappings[1].address - mappings[0].address); +} + +const dyld3::launch_cache::binary_format::ImageGroup* DyldCacheParser::cachedDylibsGroup() const +{ + const dyld_cache_header* cacheHeader = header(); + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)cacheHeader + cacheHeader->mappingOffset); + + if ( cacheIsMappedRaw() ) { + // Whole file is mapped read-only. Use mapping file-offsets to find ImageGroup + uint64_t offsetInLinkEditRegion = (cacheHeader->dylibsImageGroupAddr - mappings[2].address); + return (dyld3::launch_cache::binary_format::ImageGroup*)((uint8_t*)cacheHeader + mappings[2].fileOffset + offsetInLinkEditRegion); + } + else { + // Cache file is mapped in three non-contiguous ranges. Use mapping addresses to find ImageGroup + return (dyld3::launch_cache::binary_format::ImageGroup*)((uint8_t*)cacheHeader + (cacheHeader->dylibsImageGroupAddr - mappings[0].address)); + } +} + + +const dyld3::launch_cache::binary_format::ImageGroup* DyldCacheParser::otherDylibsGroup() const +{ + const dyld_cache_header* cacheHeader = header(); + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)cacheHeader + cacheHeader->mappingOffset); + + if ( cacheIsMappedRaw() ) { + // Whole file is mapped read-only. Use mapping file-offsets to find ImageGroup + uint64_t offsetInLinkEditRegion = (cacheHeader->otherImageGroupAddr - mappings[2].address); + return (dyld3::launch_cache::binary_format::ImageGroup*)((uint8_t*)cacheHeader + mappings[2].fileOffset + offsetInLinkEditRegion); + } + else { + // Cache file is mapped in three non-contiguous ranges. Use mapping addresses to find ImageGroup + return (dyld3::launch_cache::binary_format::ImageGroup*)((uint8_t*)cacheHeader + (cacheHeader->otherImageGroupAddr - mappings[0].address)); + } +} + +const dyld3::launch_cache::binary_format::Closure* DyldCacheParser::findClosure(const char* path) const +{ + const dyld_cache_header* cacheHeader = header(); + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)cacheHeader + cacheHeader->mappingOffset); + + const uint8_t* executableTrieStart = nullptr; + const uint8_t* executableTrieEnd = nullptr; + const uint8_t* closuresStart = nullptr; + + if ( cacheIsMappedRaw() ) { + // Whole file is mapped read-only. Use mapping file-offsets to find trie and closures + executableTrieStart = (uint8_t*)cacheHeader + cacheHeader->progClosuresTrieAddr - mappings[2].address + mappings[2].fileOffset; + executableTrieEnd = executableTrieStart + cacheHeader->progClosuresTrieSize; + closuresStart = (uint8_t*)cacheHeader + cacheHeader->progClosuresAddr - mappings[2].address + mappings[2].fileOffset; + } + else { + // Cache file is mapped in three non-contiguous ranges. Use mapping addresses to find trie and closures + uintptr_t slide = (uintptr_t)cacheHeader - (uintptr_t)(mappings[0].address); + executableTrieStart = (uint8_t*)(cacheHeader->progClosuresTrieAddr + slide); + executableTrieEnd = executableTrieStart + cacheHeader->progClosuresTrieSize; + closuresStart = (uint8_t*)(cacheHeader->progClosuresAddr + slide); + } + Diagnostics diag; + const uint8_t* imageNode = dyld3::MachOParser::trieWalk(diag, executableTrieStart, executableTrieEnd, path); + if ( imageNode != NULL ) { + uint32_t closureOffset = (uint32_t)dyld3::MachOParser::read_uleb128(diag, imageNode, executableTrieEnd); + return (const dyld3::launch_cache::BinaryClosureData*)((uint8_t*)closuresStart + closureOffset); + } + return nullptr; +} + + +#if !DYLD_IN_PROCESS +void DyldCacheParser::forEachClosure(void (^handler)(const char* runtimePath, const dyld3::launch_cache::binary_format::Closure* cls)) const +{ + const dyld_cache_header* cacheHeader = header(); + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)cacheHeader + cacheHeader->mappingOffset); + + const uint8_t* executableTrieStart = nullptr; + const uint8_t* executableTrieEnd = nullptr; + const uint8_t* closuresStart = nullptr; + + if ( cacheIsMappedRaw() ) { + // Whole file is mapped read-only. Use mapping file-offsets to find trie and closures + executableTrieStart = (uint8_t*)cacheHeader + cacheHeader->progClosuresTrieAddr - mappings[2].address + mappings[2].fileOffset; + executableTrieEnd = executableTrieStart + cacheHeader->progClosuresTrieSize; + closuresStart = (uint8_t*)cacheHeader + cacheHeader->progClosuresAddr - mappings[2].address + mappings[2].fileOffset; + } + else { + // Cache file is mapped in three non-contiguous ranges. Use mapping addresses to find trie and closures + uintptr_t slide = (uintptr_t)cacheHeader - (uintptr_t)(mappings[0].address); + executableTrieStart = (uint8_t*)(cacheHeader->progClosuresTrieAddr + slide); + executableTrieEnd = executableTrieStart + cacheHeader->progClosuresTrieSize; + closuresStart = (uint8_t*)(cacheHeader->progClosuresAddr + slide); + } + + std::vector closureEntries; + if ( Trie::parseTrie(executableTrieStart, executableTrieEnd, closureEntries) ) { + for (DylibIndexTrie::Entry& entry : closureEntries ) { + uint32_t offset = entry.info.index; + if ( offset < cacheHeader->progClosuresSize ) + handler(entry.name.c_str(), (const dyld3::launch_cache::binary_format::Closure*)(closuresStart+offset)); + } + } +} +#endif + + + + +} // namespace dyld3 + diff --git a/dyld3/DyldCacheParser.h b/dyld3/DyldCacheParser.h new file mode 100644 index 0000000..34deee4 --- /dev/null +++ b/dyld3/DyldCacheParser.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017 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 DyldCacheParser_h +#define DyldCacheParser_h + +#include +#include +#include + +#include "Diagnostics.h" +#include "DyldSharedCache.h" +#include "LaunchCacheFormat.h" + +namespace dyld3 { + +class VIS_HIDDEN DyldCacheParser +{ +public: +#if !DYLD_IN_PROCESS + static bool isValidDyldCache(Diagnostics& diag, const std::string& archName, Platform platform, const void* fileContent, size_t fileLength, const std::string& pathOpened, bool ignoreMainExecutables); +#endif + + DyldCacheParser(const DyldSharedCache* cacheHeader, bool rawFile); + const DyldSharedCache* cacheHeader() const; + bool cacheIsMappedRaw() const; + + + + // + // Get ImageGroup for cached dylibs built into this cache files + // + const dyld3::launch_cache::binary_format::ImageGroup* cachedDylibsGroup() const; + + + // + // Get ImageGroup for other OS dylibs and bundles built into this cache files + // + const dyld3::launch_cache::binary_format::ImageGroup* otherDylibsGroup() const; + + + // + // returns closure for given path, or nullptr if no closure found + // + const dyld3::launch_cache::binary_format::Closure* findClosure(const char* path) const; + + // + // returns what vmOffset of data (r/w) region from cache header will be when cache is used in a process + // + uint64_t dataRegionRuntimeVmOffset() const; + +#if !DYLD_IN_PROCESS + // + // Iterates over closure of OS programs built into shared cache + // + void forEachClosure(void (^handler)(const char* runtimePath, const dyld3::launch_cache::binary_format::Closure*)) const; +#endif + +private: + const dyld_cache_header* header() const; + + long _data; // low bit means rawFile +}; + +} // namespace dyld3 + +#endif // DyldCacheParser_h diff --git a/dyld3/LaunchCache.h b/dyld3/LaunchCache.h new file mode 100644 index 0000000..447a2c7 --- /dev/null +++ b/dyld3/LaunchCache.h @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2017 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 LaunchCache_h +#define LaunchCache_h + + +#include +#include +#include +#include +#include +#include + +#if !DYLD_IN_PROCESS + #include + #include + #include + #include "shared-cache/DyldSharedCache.h" +#endif + +#include "Diagnostics.h" + +#define VIS_HIDDEN __attribute__((visibility("hidden"))) + + +namespace dyld3 { + +class DyldCacheParser; + +namespace launch_cache { + + +namespace binary_format { + struct Image; + struct ImageGroup; + union ImageRef; + struct Closure; + struct DiskImage; + struct CachedImage; + struct AllFixupsBySegment; + struct SegmentFixupsByPage; +} + +typedef binary_format::Image BinaryImageData; +typedef binary_format::ImageGroup BinaryImageGroupData; +typedef binary_format::Closure BinaryClosureData; + + +struct VIS_HIDDEN MemoryRange +{ + bool contains(const MemoryRange& other) const; + bool intersects(const MemoryRange& other) const; + + const void* address; + uint64_t size; +}; + + +class VIS_HIDDEN SlowLoadSet +{ +public: + SlowLoadSet(const BinaryImageData** start, const BinaryImageData** end) : _start(start), _end(end), _current(start) { } + bool contains(const BinaryImageData*); + bool add(const BinaryImageData*); + void forEach(void (^handler)(const BinaryImageData*)); + void forEach(void (^handler)(const BinaryImageData*, bool& stop)); + long count() const; +private: + const BinaryImageData** const _start; + const BinaryImageData** const _end; + const BinaryImageData** _current; +}; + +struct ImageGroup; + + +template +class VIS_HIDDEN DynArray +{ +public: + DynArray(uintptr_t count, T* storage) : _count(count), _elements(storage) { } +#if !DYLD_IN_PROCESS + DynArray(const std::vector& vec) : _count(vec.size()), _elements((T*)&vec[0]) { } +#endif + + T& operator[](size_t idx) { assert(idx < _count); return _elements[idx]; } + const T& operator[](size_t idx) const { assert(idx < _count); return _elements[idx]; } + uintptr_t count() const { return _count; } +private: + uintptr_t _count; + T* _elements; +}; + + +// STACK_ALLOC_DYNARRAY(foo, 10, myarray); +#define STACK_ALLOC_DYNARRAY(_type, _count, _name) \ + uintptr_t __##_name##_array_alloc[1 + ((sizeof(_type)*(_count))/sizeof(uintptr_t))]; \ + dyld3::launch_cache::DynArray<_type> _name(_count, (_type*)__##_name##_array_alloc); + + +typedef DynArray ImageGroupList; + + +// In the pre-computed fixups for an Image, each fixup location is set to a TargetSymbolValue +// which is an abstract encoding of a resolved symbol in an image that can be turned into a +// real address once all ASLR slides are known. +struct VIS_HIDDEN TargetSymbolValue +{ +#if DYLD_IN_PROCESS + class LoadedImages + { + public: + virtual const uint8_t* dyldCacheLoadAddressForImage() = 0; + virtual const mach_header* loadAddressFromGroupAndIndex(uint32_t groupNum, uint32_t indexInGroup) = 0; + virtual void forEachImage(void (^handler)(uint32_t anIndex, const BinaryImageData*, const mach_header*, bool& stop)) = 0; + virtual void setAsNeverUnload(uint32_t anIndex) = 0; + }; + + uintptr_t resolveTarget(Diagnostics& diag, const ImageGroup& inGroup, LoadedImages& images) const; +#else + static TargetSymbolValue makeInvalid(); + static TargetSymbolValue makeAbsolute(uint64_t value); + static TargetSymbolValue makeSharedCacheOffset(uint32_t offset); + static TargetSymbolValue makeGroupValue(uint32_t groupIndex, uint32_t imageIndexInGroup, uint64_t offsetInImage, bool isIndirectGroupNum); + static TargetSymbolValue makeDynamicGroupValue(uint32_t imagePathPoolOffset, uint32_t imageSymbolPoolOffset, bool weakImport); + std::string asString(ImageGroup group) const; + bool operator==(const TargetSymbolValue& other) const { return (_data.raw == other._data.raw); } + bool isSharedCacheTarget(uint64_t& offsetInCache) const; + bool isGroupImageTarget(uint32_t& groupNum, uint32_t& indexInGroup, uint64_t& offsetInImage) const; + bool isInvalid() const; +#endif +private: + TargetSymbolValue(); + + enum Kinds { kindSharedCache, kindAbsolute, kindGroup, kindDynamicGroup }; + + + struct SharedCacheOffsetTarget { + uint64_t kind : 2, // kindSharedCache + offsetIntoCache : 62; + }; + struct AbsoluteTarget { + uint64_t kind : 2, // kindAbsolute + value : 62; + }; + struct GroupImageTarget { + uint64_t kind : 2, // kindGroup + isIndirectGroup : 1, // 0 => use groupNum directly. 1 => index indirect side table + groupNum : 7, // 0 not used, 1 => other dylibs, 2 => main closure, 3 => first dlopen group + indexInGroup : 12, + offsetInImage : 42; + }; + struct DynamicGroupImageTarget { + uint64_t kind : 2, // kindDynamicGroup + weakImport : 1, + imagePathOffset : 30, + symbolNameOffset: 31; + }; + union { + SharedCacheOffsetTarget sharedCache; + AbsoluteTarget absolute; + GroupImageTarget group; + DynamicGroupImageTarget dynamicGroup; + uint64_t raw; + } _data; + + static_assert(sizeof(_data) == 8, "Overflow in size of TargetSymbolValue"); +}; + + +struct VIS_HIDDEN Image +{ + enum class LinkKind { regular=0, weak=1, upward=2, reExport=3 }; + enum class FixupKind { rebase32, rebase64, bind32, bind64, rebaseText32, bindText32, bindTextRel32, bindImportJmp32 }; + + Image(const BinaryImageData* binaryData) : _binaryData(binaryData) { } + + bool valid() const { return (_binaryData != nullptr); } + const BinaryImageData* binaryData() const { return _binaryData; } + const ImageGroup group() const; + uint32_t maxLoadCount() const; + const char* path() const; + const char* leafName() const; + uint32_t pathHash() const; + const uuid_t* uuid() const; + bool isInvalid() const; + bool hasObjC() const; + bool isBundle() const; + bool hasWeakDefs() const; + bool mayHavePlusLoads() const; + bool hasTextRelocs() const; + bool neverUnload() const; + bool cwdMustBeThisDir() const; + bool isPlatformBinary() const; + bool overridableDylib() const; + bool validateUsingModTimeAndInode() const; + bool validateUsingCdHash() const; + uint64_t fileModTime() const; + uint64_t fileINode() const; + const uint8_t* cdHash16() const; + void forEachDependentImage(const ImageGroupList& groups, void (^handler)(uint32_t depIndex, Image depImage, LinkKind kind, bool& stop)) const; +#if !DYLD_IN_PROCESS + bool recurseAllDependentImages(const ImageGroupList& groups, std::unordered_set& allDependents) const; +#endif + bool recurseAllDependentImages(const ImageGroupList& groups, SlowLoadSet& allDependents, + void (^handler)(const dyld3::launch_cache::binary_format::Image* aBinImage, bool& stop)) const; + bool containsAddress(const void* addr, const void* imageLoadAddress, uint8_t* permissions) const; + bool segmentHasFixups(uint32_t segIndex) const; + void forEachInitializer(const void* imageLoadAddress, void (^handler)(const void* initializer)) const; + void forEachInitBefore(const ImageGroupList& groups, void (^handler)(Image imageToInit)) const; + void forEachInitBefore(void (^handler)(binary_format::ImageRef imageToInit)) const; + void forEachDOF(const void* imageLoadAddress, void (^handler)(const void* initializer)) const; + + bool isDiskImage() const; + + // the following are only valid if isDiskImage() returns false + const binary_format::CachedImage* asCachedImage() const; + uint32_t cacheOffset() const; + uint32_t patchStartIndex() const; + uint32_t patchCount() const; + + + // the following are only valid if isDiskImage() returns true + const binary_format::DiskImage* asDiskImage() const; + uint64_t sliceOffsetInFile() const; + bool hasCodeSignature(uint32_t& fileOffset, uint32_t& size) const; + bool isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const; + uint64_t vmSizeToMap() const; + void forEachDiskSegment(void (^handler)(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop)) const; + void forEachCacheSegment(void (^handler)(uint32_t segIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop)) const; + void forEachFixup(uint32_t segIndex, MemoryRange segContent, + void (^handler)(uint64_t segOffset, FixupKind kind, TargetSymbolValue value, bool& stop)) const; + +#if !DYLD_IN_PROCESS + void printAsJSON(const ImageGroupList& groupList, bool printFixups=false, bool printDependentsDetails=false, FILE* out=stdout) const; +#endif + +// todo: fairPlayTextPages + +private: + friend struct ImageGroup; + friend struct Closure; + + bool recurseAllDependentImages(const ImageGroupList& groups, SlowLoadSet& allDependents, bool& stopped, + void (^handler)(const dyld3::launch_cache::binary_format::Image* aBinImage, bool& stop)) const; + uint32_t pageSize() const; + const binary_format::SegmentFixupsByPage* segmentFixups(uint32_t segIndex) const; + static void forEachFixup(const uint8_t* pageFixups, const void* segContent, uint32_t& offset, uint32_t& ordinal, + void (^handler)(uint32_t pageOffset, FixupKind kind, uint32_t targetOrdinal, bool& stop)); + static Image resolveImageRef(const ImageGroupList& groups, binary_format::ImageRef ref, bool applyOverrides=true); + + + const BinaryImageData* _binaryData; +}; + + +struct VIS_HIDDEN ImageGroup +{ + ImageGroup(const BinaryImageGroupData* binaryData) : _binaryData(binaryData) { } + + size_t size() const; + uint32_t imageCount() const; + uint32_t groupNum() const; + bool dylibsExpectedOnDisk() const; + const Image image(uint32_t index) const; + uint32_t indexInGroup(const BinaryImageData* image) const; + const BinaryImageData* findImageByPath(const char* path, uint32_t& foundIndex) const; + const BinaryImageData* findImageByCacheOffset(size_t cacheVmOffset, uint32_t& mhCacheOffset, uint8_t& foundPermissions) const; + const BinaryImageData* imageBinary(uint32_t index) const; + binary_format::ImageRef dependentPool(uint32_t index) const; + const BinaryImageGroupData* binaryData() const { return _binaryData; } + const char* stringFromPool(uint32_t offset) const; + uint32_t indirectGroupNum(uint32_t index) const; + void forEachImageRefOverride(void (^handler)(binary_format::ImageRef standardDylibRef, binary_format::ImageRef overrideDyilbRef, bool& stop)) const; + void forEachImageRefOverride(const ImageGroupList& groupList, void (^handler)(Image standardDylib, Image overrideDyilb, bool& stop)) const; + void forEachAliasOf(uint32_t imageIndex, void (^handler)(const char* aliasPath, uint32_t aliasPathHash, bool& stop)) const; +#if DYLD_IN_PROCESS + void forEachDyldCacheSymbolOverride(void (^handler)(uint32_t patchTableIndex, const BinaryImageData* image, uint32_t imageOffset, bool& stop)) const; + void forEachDyldCachePatchLocation(const void* dyldCacheLoadAddress, uint32_t patchTargetIndex, + void (^handler)(uintptr_t* locationToPatch, uintptr_t addend, bool& stop)) const; +#else + void forEachDyldCacheSymbolOverride(void (^handler)(uint32_t patchTableIndex, uint32_t imageIndexInClosure, uint32_t imageOffset, bool& stop)) const; + void forEachDyldCachePatchLocation(const DyldCacheParser& cacheParser, void (^handler)(uint32_t targetCacheOffset, const std::vector& usesPointersCacheOffsets, bool& stop)) const; + bool hasPatchTableIndex(uint32_t targetCacheOffset, uint32_t& index) const; +#endif + + static uint32_t hashFunction(const char* s); +#if !DYLD_IN_PROCESS + void printAsJSON(const ImageGroupList& groupList, bool printFixups=false, bool printDependentsDetails=false, FILE* out=stdout) const; + void printStatistics(FILE* out=stderr) const; +#endif + +private: + friend struct Image; + + const char* stringPool() const; + uint32_t stringPoolSize() const; + const uint64_t* segmentPool(uint32_t index) const; + const binary_format::AllFixupsBySegment* fixUps(uint32_t offset) const; + const TargetSymbolValue* targetValuesArray() const; + uint32_t targetValuesCount() const; + uint32_t initializersPoolCount() const; + const uint32_t* initializerOffsetsPool() const; + const uint32_t initializerOffsetsCount() const; + const binary_format::ImageRef* intializerListPool() const; + const uint32_t intializerListPoolCount() const; + const uint32_t* dofOffsetsPool() const; + const uint32_t dofOffsetsCount() const; + const uint32_t* indirectGroupNumsPool() const; + const uint32_t indirectGroupNumsCount() const; + void forEachDyldCachePatch(uint32_t patchTargetIndex, uint32_t cacheDataVmOffset, + void (^handler)(uint32_t targetCacheOffset, uint32_t usePointersCacheOffset, bool hasAddend, bool& stop)) const; + + const BinaryImageGroupData* _binaryData; +}; + + + +struct VIS_HIDDEN Closure +{ + Closure(const BinaryClosureData* binaryData); + + size_t size() const; + const uuid_t* dyldCacheUUID() const; + const uint8_t* cdHash() const; + uint32_t initialImageCount() const; + uint32_t mainExecutableImageIndex() const; + uint32_t mainExecutableEntryOffset() const; + bool mainExecutableUsesCRT() const; + bool isRestricted() const; + bool usesLibraryValidation() const; + const BinaryImageData* libSystem(const ImageGroupList& groups); + const BinaryImageData* libDyld(const ImageGroupList& groups); + uint32_t libdyldVectorOffset() const; + const ImageGroup group() const; + const BinaryClosureData* binaryData() const { return _binaryData; } + void forEachMustBeMissingFile(void (^handler)(const char* path, bool& stop)) const; + void forEachEnvVar(void (^handler)(const char* keyEqualValue, bool& stop)) const; + +#if !DYLD_IN_PROCESS + void printAsJSON(const ImageGroupList& groupList, bool printFixups=true, bool printDependentsDetails=false, FILE* out=stdout) const; + void printStatistics(FILE* out=stderr) const; +#endif + +private: + const BinaryClosureData* _binaryData; +}; + + + + + + +} // namespace launch_cache +} // namespace dyld3 + + +#endif // LaunchCache_h + + diff --git a/dyld3/LaunchCacheFormat.h b/dyld3/LaunchCacheFormat.h new file mode 100644 index 0000000..bea67ad --- /dev/null +++ b/dyld3/LaunchCacheFormat.h @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2017 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 LaunchCacheFormat_h +#define LaunchCacheFormat_h + + +#include +#include +#include +#include + +#include "LaunchCache.h" + + +namespace dyld3 { +namespace launch_cache { +namespace binary_format { + + +// bump this number each time binary format changes +enum { kFormatVersion = 8 }; + +union VIS_HIDDEN ImageRef { + ImageRef() : val(0xFFFFFFFF) { } + ImageRef(uint8_t kind, uint32_t groupNum, uint32_t indexInGroup) : _linkKind(kind), _groupNum(groupNum), _indexInGroup(indexInGroup) { + assert(groupNum < (1 << 18)); + assert(indexInGroup < (1 << 12)); + } + uint8_t kind() const { return _linkKind; } + uint32_t groupNum() const { return _groupNum; } + uint32_t indexInGroup() const { return _indexInGroup; } + uint16_t value() const { return val; } + void clearKind() { _linkKind = 0; } + + bool operator==(const ImageRef& rhs) const { + return (val == rhs.val); + } + bool operator!=(const ImageRef& rhs) const { + return (val != rhs.val); + } + static ImageRef weakImportMissing(); + static ImageRef makeEmptyImageRef() { return ImageRef(); } + +private: + ImageRef(uint32_t v) : val(v) { } + + uint32_t val; + struct { + uint32_t _linkKind : 2, // Image::LinkKind + _groupNum : 18, // 0 => cached dylib group, 1 => other dylib group, 2 => main closure, etc + _indexInGroup : 12; // max 64K images in group + }; +}; + + + + +// In disk based images, all segments are multiples of page size +// This struct just tracks the size (disk and vm) of each segment. +// This is compact for most every image which have contiguous segments. +// If the image does not have contiguous segments (rare), an extra +// DiskSegment is inserted with the paddingNotSeg bit set. +struct DiskSegment +{ + uint64_t filePageCount : 30, + vmPageCount : 30, + permissions : 3, + paddingNotSeg : 1; +}; + + +// In cache DATA_DIRTY is not page aligned or sized +// This struct allows segments with any alignment and up to 256MB in size +struct DyldCacheSegment +{ + uint64_t cacheOffset : 32, + size : 28, + permissions : 4; +}; + +// When an Image is built on the device, the mtime and inode are recorded. +// When built off device, the first 16 bytes of SHA1 of CodeDirectory is recorded. +union FileInfo +{ + struct { + uint64_t mtime; + uint64_t inode; + } statInfo; + struct { + uint8_t bytes[16]; + } cdHash16; +}; + +struct Image +{ + uint32_t isDiskImage : 1, // images are DiskImage - not Image + isInvalid : 1, // an error occurred creating the info for this image + has16KBpages : 1, + hasTextRelocs : 1, + hasObjC : 1, + mayHavePlusLoads : 1, + isEncrypted : 1, // image is DSMOS or FairPlay encrypted + hasWeakDefs : 1, + neverUnload : 1, + cwdSameAsThis : 1, // dylibs use file system relative paths, cwd must be main's dir + isPlatformBinary : 1, // part of OS - can be loaded into LV process + isBundle : 1, + overridableDylib : 1, // only applicable to group 0 + padding : 7, + maxLoadCount : 12; + int32_t groupOffset; // back pointer to containing ImageGroup (from start of Image) + uint32_t pathPoolOffset; + uint32_t pathHash; + FileInfo fileInfo; + uuid_t uuid; + uint16_t dependentsArrayStartIndex; + uint16_t dependentsArrayCount; + uint16_t segmentsArrayStartIndex; + uint16_t segmentsArrayCount; + uint16_t initBeforeArrayStartIndex; + uint16_t initBeforeArrayCount; + uint16_t initOffsetsArrayStartIndex; + uint16_t initOffsetsArrayCount; + uint16_t dofOffsetsArrayStartIndex; + uint16_t dofOffsetsArrayCount; +}; + +// an image in the dyld shared cache +struct CachedImage : public Image +{ + uint32_t patchStartIndex; + uint32_t patchCount; +}; + +// an image not in the dyld shared cache (loaded from disk at runtime) +struct DiskImage : public Image +{ + uint32_t totalVmPages; + uint32_t sliceOffsetIn4K; + uint32_t codeSignFileOffset; + uint32_t codeSignFileSize; + uint32_t fixupsPoolOffset : 28, // offset in ImageGroup's pool for AllFixupsBySegment + fixupsPoolSegCount : 4; // count of segments in AllFixupsBySegment for this image + uint32_t fairPlayTextPageCount : 28, + fairPlayTextStartPage : 4; + uint32_t targetsArrayStartIndex; // index in ImageGroup's pool of OrdinalEntry + uint32_t targetsArrayCount; +}; + + +// if an Image has an alias (symlink to it), the Image does not record the alias, but the ImageGroup does +struct AliasEntry +{ + uint32_t aliasHash; + uint32_t imageIndexInGroup; + uint32_t aliasOffsetInStringPool; +}; + +// each DiskImage points to an array of these, one per segment with fixups +struct AllFixupsBySegment +{ + uint32_t segIndex : 4, + offset : 28; // from start of AllFixupsBySegment to this seg's SegmentFixupsByPage +}; + + +// This struct is suitable for passing into kernel when kernel supports fixups on page-in. +struct SegmentFixupsByPage +{ + uint32_t size; // of this struct, including fixup opcodes + uint32_t pageSize; // 0x1000 or 0x4000 + uint32_t pageCount; + uint32_t pageInfoOffsets[1]; // array size is pageCount + // each page info is a FixUpOpcode[] +}; + +enum class FixUpOpcode : uint8_t { + done = 0x00, +// apply = 0x10, + rebase32 = 0x10, // add32 slide at current pageOffset, increment pageOffset by 4 + rebase64 = 0x11, // add64 slide at current pageOffset, increment pageOffset by 8 + bind32 = 0x12, // set 32-bit ordinal value at current pageOffset, increment pageOffset by 4 + bind64 = 0x13, // set 64-bit ordinal value at current pageOffset, increment pageOffset by 8 + rebaseText32 = 0x14, // add32 slide at current text pageOffset, increment pageOffset by 4 + bindText32 = 0x15, // set 32-bit ordinal value at current text pageOffset, increment pageOffset by 4 + bindTextRel32 = 0x16, // set delta to 32-bit ordinal value at current text pageOffset, increment pageOffset by 4 (i386 CALL to dylib) + bindImportJmp32 = 0x17, // set delta to 32-bit ordinal value at current text pageOffset, increment pageOffset by 4 (i386 JMP to dylib) +// fixupChain64 = 0x18, // current page offset is start of a chain of locations to fix up +// adjPageOffset = 0x20, + setPageOffset = 0x20, // low 4-bits is amount to increment (1 to 15). If zero, then add next ULEB (note: can set offset for unaligned pointer) + incPageOffset = 0x30, // low 4-bits *4 is amount to increment (4 to 60). If zero, then add next ULEB * 4 +// adjOrdinal = 0x40, + setOrdinal = 0x40, // low 4-bits is ordinal (1-15). If zero, then ordinal is next ULEB + incOrdinal = 0x50, // low 4-bits is ordinal inc amount (1-15). If zero, then ordinal is next ULEB + repeat = 0x60 // low 5-bits is how many next bytes to repeat. next ULEB is repeat count +}; + +// If a closure uses DYLD_LIBRARY_PATH to override an OS dylib, there is an +// ImageRefOverride entry to redirect uses of the OS dylib. +struct ImageRefOverride +{ + ImageRef standardDylib; + ImageRef overrideDylib; +}; + +// If a closure interposes on, or has a dylib that overrides, something in the dyld shared cache, +// then closure's ImageGroup contains an array of these +struct DyldCacheOverride +{ + uint64_t patchTableIndex : 24, // index into PatchTable array of group 0 + imageIndex : 8, // index in this group (2) of what to replace with + imageOffset : 32; // offset within image to override something in cache +}; + + +// The ImageGroup for the dyld shared cache dylibs contains and array of these +// with one entry for each symbol in a cached dylib that is used by some other cached dylib. +struct PatchTable +{ + uint32_t targetCacheOffset; // delta from the base address of the cache to the address of the symbol to patch + uint32_t offsetsStartIndex; // index into the PatchOffset array of first location to patch, last offset has low bit set +}; + +struct PatchOffset +{ + uint32_t last : 1, + hasAddend : 1, + dataRegionOffset : 30; +}; + +struct ImageGroup +{ + uint32_t imagesEntrySize : 8, + dylibsExpectedOnDisk : 1, + imageFileInfoIsCdHash : 1, + padding : 14; + uint32_t groupNum; + uint32_t imagesPoolCount; + uint32_t imagesPoolOffset; // offset to array of Image or DiskImage + uint32_t imageAliasCount; + uint32_t imageAliasOffset; // offset to array of AliasEntry + uint32_t segmentsPoolCount; + uint32_t segmentsPoolOffset; // offset to array of Segment or DyldCacheSegment + uint32_t dependentsPoolCount; + uint32_t dependentsPoolOffset; // offset to array of ImageRef + uint32_t intializerOffsetPoolCount; + uint32_t intializerOffsetPoolOffset; // offset to array of uint32_t + uint32_t intializerListPoolCount; + uint32_t intializerListPoolOffset; // offset to array of ImageRef + uint32_t targetsPoolCount; + uint32_t targetsOffset; // offset to array of TargetSymbolValue + uint32_t fixupsPoolSize; + uint32_t fixupsOffset; // offset to list of AllFixupsBySegment + uint32_t cachePatchTableCount; + uint32_t cachePatchTableOffset; // offset to array of PatchTable (used only in group 0) + uint32_t cachePatchOffsetsCount; + uint32_t cachePatchOffsetsOffset; // offset to array of PatchOffset cache offsets (used only in group 0) + uint32_t symbolOverrideTableCount; + uint32_t symbolOverrideTableOffset; // offset to array of DyldCacheOverride (used only in group 2) + uint32_t imageOverrideTableCount; + uint32_t imageOverrideTableOffset; // offset to array of ImageRefOverride (used only in group 2) + uint32_t dofOffsetPoolCount; + uint32_t dofOffsetPoolOffset; // offset to array of uint32_t + uint32_t indirectGroupNumPoolCount; + uint32_t indirectGroupNumPoolOffset; // offset to array of uint32_t + uint32_t stringsPoolSize; + uint32_t stringsPoolOffset; + // Image array + // Alias array + // Segment array + // ImageRef array + // Initializer offsets array + // Initializer ImageRef array + // TargetSymbolValue array + // AllFixupsBySegment pool + // PatchTable array + // PatchOffset array + // DyldCacheOverride array + // ImageRefOverride array + // string pool + // DOF offsets array +}; + + +struct Closure +{ + enum { magicV1 = 0x31646c6e }; + + uint32_t magic; + uint32_t usesCRT : 1, + isRestricted : 1, + usesLibraryValidation : 1, + padding : 29; + uint32_t missingFileComponentsOffset; // offset to array of 16-bit string pool offset of path components + uint32_t dyldEnvVarsOffset; + uint32_t dyldEnvVarsCount; + uint32_t stringPoolOffset; + uint32_t stringPoolSize; + ImageRef libSystemRef; + ImageRef libDyldRef; + uint32_t libdyldVectorOffset; + uint32_t mainExecutableIndexInGroup; + uint32_t mainExecutableEntryOffset; + uint32_t initialImageCount; + uuid_t dyldCacheUUID; // all zero if this closure is embedded in a dyld cache + uint8_t mainExecutableCdHash[20]; // or UUID if not code signed + ImageGroup group; + // MissingFile array + // env vars array + // string pool +}; + + + +} // namespace binary_format + +} // namespace launch_cache +} // namespace dyld + + +#endif // LaunchCacheFormat_h + + diff --git a/dyld3/LaunchCachePrinter.cpp b/dyld3/LaunchCachePrinter.cpp new file mode 100644 index 0000000..f5efd16 --- /dev/null +++ b/dyld3/LaunchCachePrinter.cpp @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2017 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 "LaunchCache.h" +#include "LaunchCacheFormat.h" + +#if !DYLD_IN_PROCESS + +namespace dyld3 { +namespace launch_cache { + +struct Node +{ + std::string value; + std::map map; + std::vector array; +}; + +static std::string hex(uint64_t value) { + char buff[64]; + sprintf(buff, "0x%llX", value); + return buff; +} + +static std::string hex5(uint64_t value) { + char buff[64]; + sprintf(buff, "0x%05llX", value); + return buff; +} + +static std::string decimal(uint64_t value) { + char buff[64]; + sprintf(buff, "%llu", value); + return buff; +} + +static Node buildImageNode(const Image& image, const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails) +{ + __block Node imageNode; + + if ( image.isInvalid() ) + return imageNode; + + const ImageGroup group = image.group(); + imageNode.map["path"].value = image.path(); + __block Node imageAliases; + group.forEachAliasOf(group.indexInGroup(image.binaryData()), ^(const char* aliasPath, uint32_t aliasPathHash, bool& stop) { + Node anAlias; + anAlias.value = aliasPath; + imageAliases.array.push_back(anAlias); + }); + if ( !imageAliases.array.empty() ) + imageNode.map["aliases"] = imageAliases; + uuid_string_t uuidStr; + uuid_unparse(*image.uuid(), uuidStr); + imageNode.map["uuid"].value = uuidStr; + imageNode.map["has-objc"].value = (image.hasObjC() ? "true" : "false"); + imageNode.map["has-weak-defs"].value = (image.hasWeakDefs() ? "true" : "false"); + imageNode.map["never-unload"].value = (image.neverUnload() ? "true" : "false"); + imageNode.map["platform-binary"].value = (image.isPlatformBinary() ? "true" : "false"); + if ( group.groupNum() == 0 ) + imageNode.map["overridable-dylib"].value = (image.overridableDylib() ? "true" : "false"); + if ( image.cwdMustBeThisDir() ) + imageNode.map["cwd-must-be-this-dir"].value = "true"; + if ( image.isDiskImage() ) { + uint32_t csFileOffset; + uint32_t csSize; + if ( image.hasCodeSignature(csFileOffset, csSize) ) { + imageNode.map["code-sign-location"].map["offset"].value = hex(csFileOffset); + imageNode.map["code-sign-location"].map["size"].value = hex(csSize); + } + uint32_t fpTextOffset; + uint32_t fpSize; + if ( image.isFairPlayEncrypted(fpTextOffset, fpSize) ) { + imageNode.map["fairplay-encryption-location"].map["offset"].value = hex(fpTextOffset); + imageNode.map["fairplay-encryption-location"].map["size"].value = hex(fpSize); + } + if ( image.validateUsingModTimeAndInode() ) { + imageNode.map["file-mod-time"].value = hex(image.fileModTime()); + imageNode.map["file-inode"].value = hex(image.fileINode()); + } + else { + const uint8_t* cdHash = image.cdHash16(); + std::string cdHashStr; + cdHashStr.reserve(32); + for (int j=0; j < 16; ++j) { + uint8_t byte = cdHash[j]; + uint8_t nibbleL = byte & 0x0F; + uint8_t nibbleH = byte >> 4; + if ( nibbleH < 10 ) + cdHashStr += '0' + nibbleH; + else + cdHashStr += 'a' + (nibbleH-10); + if ( nibbleL < 10 ) + cdHashStr += '0' + nibbleL; + else + cdHashStr += 'a' + (nibbleL-10); + } + imageNode.map["file-cd-hash-16"].value = cdHashStr; + } + imageNode.map["total-vm-size"].value = hex(image.vmSizeToMap()); + uint64_t sliceOffset = image.sliceOffsetInFile(); + if ( sliceOffset != 0 ) + imageNode.map["file-offset-of-slice"].value = hex(sliceOffset); + if ( image.hasTextRelocs() ) + imageNode.map["has-text-relocs"].value = "true"; + image.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop) { + Node segInfoNode; + segInfoNode.map["file-offset"].value = hex(fileOffset); + segInfoNode.map["file-size"].value = hex(fileSize); + segInfoNode.map["vm-size"].value = hex(vmSize); + segInfoNode.map["permissions"].value = hex(permissions); + imageNode.map["mappings"].array.push_back(segInfoNode); + }); + if ( printFixups ) { + image.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool &segStop) { + MemoryRange segContent = { nullptr, vmSize }; + std::string segName = "segment-" + decimal(segIndex); + __block Node segmentFixupsNode; + image.forEachFixup(segIndex, segContent, ^(uint64_t segOffset, Image::FixupKind kind, TargetSymbolValue value, bool& stop) { + switch ( kind ) { + case Image::FixupKind::rebase32: + segmentFixupsNode.map[segName].map[hex5(segOffset)].value = "32-bit rebase"; + break; + case Image::FixupKind::rebase64: + segmentFixupsNode.map[segName].map[hex5(segOffset)].value = "64-bit rebase"; + break; + case Image::FixupKind::rebaseText32 : + segmentFixupsNode.map[segName].map[hex5(segOffset)].value = "32-bit text rebase"; + break; + case Image::FixupKind::bind32: + segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("32-bit bind, target=") + value.asString(group); + break; + case Image::FixupKind::bind64: + segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("64-bit bind, target=") + value.asString(group); + break; + case Image::FixupKind::bindText32 : + segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("32-bit text abs bind, target=") + value.asString(group); + break; + case Image::FixupKind::bindTextRel32 : + segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("32-bit text rel bind, target=") + value.asString(group); + break; + case Image::FixupKind::bindImportJmp32 : + segmentFixupsNode.map[segName].map[hex5(segOffset)].value = std::string("32-bit IMPORT JMP rel bind, target=") + value.asString(group); + break; + } + }); + if ( segmentFixupsNode.map[segName].map.size() != 0 ) { + imageNode.map["fixups"].array.push_back(segmentFixupsNode); + } + }); + } + } + else { + imageNode.map["patch-start-index"].value = decimal(image.patchStartIndex()); + imageNode.map["patch-count"].value = decimal(image.patchCount()); + } + + // add dependents + image.forEachDependentImage(groupList, ^(uint32_t depIndex, Image depImage, Image::LinkKind kind, bool& stop) { + Node depMapNode; + depMapNode.map["path"].value = depImage.path(); + if ( printDependentsDetails ) { + ImageGroup depGroup = depImage.group(); + uint32_t indexInGroup = depGroup.indexInGroup(depImage.binaryData()); + depMapNode.map["group-index"].value = decimal(depGroup.groupNum()); + depMapNode.map["index-in-group"].value = decimal(indexInGroup); + } + switch ( kind ) { + case Image::LinkKind::regular: + depMapNode.map["link"].value = "regular"; + break; + case Image::LinkKind::reExport: + depMapNode.map["link"].value = "re-export"; + break; + case Image::LinkKind::upward: + depMapNode.map["link"].value = "upward"; + break; + case Image::LinkKind::weak: + depMapNode.map["link"].value = "weak"; + break; + } + imageNode.map["dependents"].array.push_back(depMapNode); + }); + // add things to init before this image + __block Node initBeforeNode; + image.forEachInitBefore(groupList, ^(Image beforeImage) { + Node beforeNode; + beforeNode.value = beforeImage.path(); + imageNode.map["initializer-order"].array.push_back(beforeNode); + }); + + // add initializers + image.forEachInitializer(nullptr, ^(const void* initializer) { + Node initNode; + initNode.value = hex((long)initializer); + imageNode.map["initializer-offsets"].array.push_back(initNode); + }); + + // add override info if relevant + group.forEachImageRefOverride(groupList, ^(Image standardDylib, Image overrideDylib, bool& stop) { + if ( overrideDylib.binaryData() == image.binaryData() ) { + imageNode.map["override-of-cached-dylib"].value = standardDylib.path(); + } + }); + + // add dtrace info + image.forEachDOF(nullptr, ^(const void* section) { + Node initNode; + initNode.value = hex((long)section); + imageNode.map["dof-offsets"].array.push_back(initNode); + }); + + return imageNode; +} + + +static Node buildImageGroupNode(const ImageGroup& group, const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails) +{ + Node images; + uint32_t imageCount = group.imageCount(); + images.array.reserve(imageCount); + for (uint32_t i=0; i < imageCount; ++i) { + images.array.push_back(buildImageNode(group.image(i), groupList, printFixups, printDependentsDetails)); + } + return images; +} + +static Node buildClosureNode(const Closure& closure, const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails) +{ + __block Node root; + + // add env-vars if they exist + closure.forEachEnvVar(^(const char* keyEqualValue, bool& stop) { + const char* equ = strchr(keyEqualValue, '='); + if ( equ != nullptr ) { + char key[512]; + strncpy(key, keyEqualValue, equ-keyEqualValue); + key[equ-keyEqualValue] = '\0'; + root.map["env-vars"].map[key].value = equ+1; + } + }); + + // add missing files array if they exist + closure.forEachMustBeMissingFile(^(const char* path, bool& stop) { + Node fileNode; + fileNode.value = path; + root.map["must-be-missing-files"].array.push_back(fileNode); + }); + + const uint8_t* cdHash = closure.cdHash(); + std::string cdHashStr; + cdHashStr.reserve(24); + for (int i=0; i < 20; ++i) { + uint8_t byte = cdHash[i]; + uint8_t nibbleL = byte & 0x0F; + uint8_t nibbleH = byte >> 4; + if ( nibbleH < 10 ) + cdHashStr += '0' + nibbleH; + else + cdHashStr += 'a' + (nibbleH-10); + if ( nibbleL < 10 ) + cdHashStr += '0' + nibbleL; + else + cdHashStr += 'a' + (nibbleL-10); + } + if ( cdHashStr != "0000000000000000000000000000000000000000" ) + root.map["cd-hash"].value = cdHashStr; + + // add uuid of dyld cache this closure requires + closure.dyldCacheUUID(); + uuid_string_t cacheUuidStr; + uuid_unparse(*closure.dyldCacheUUID(), cacheUuidStr); + root.map["dyld-cache-uuid"].value = cacheUuidStr; + + // add top level images + Node& rootImages = root.map["root-images"]; + uint32_t initImageCount = closure.mainExecutableImageIndex(); + rootImages.array.resize(initImageCount+1); + for (uint32_t i=0; i <= initImageCount; ++i) { + const Image image = closure.group().image(i); + uuid_string_t uuidStr; + uuid_unparse(*image.uuid(), uuidStr); + rootImages.array[i].value = uuidStr; + } + root.map["initial-image-count"].value = decimal(closure.initialImageCount()); + + // add images + root.map["images"] = buildImageGroupNode(closure.group(), groupList, printFixups, printDependentsDetails); + root.map["group-num"].value = decimal(closure.group().groupNum()); + + if ( closure.mainExecutableUsesCRT() ) + root.map["main-offset"].value = hex(closure.mainExecutableEntryOffset()); + else + root.map["start-offset"].value = hex(closure.mainExecutableEntryOffset()); + + root.map["libdyld-entry-offset"].value = hex(closure.libdyldVectorOffset()); + + root.map["restricted"].value = (closure.isRestricted() ? "true" : "false"); + + root.map["library-validation"].value = (closure.usesLibraryValidation() ? "true" : "false"); + + __block Node cacheOverrides; + closure.group().forEachDyldCacheSymbolOverride(^(uint32_t patchTableIndex, uint32_t imageIndexInClosure, uint32_t imageOffset, bool& stop) { + Node patch; + patch.map["patch-index"].value = decimal(patchTableIndex); + patch.map["replacement"].value = "{closure[" + decimal(imageIndexInClosure) + "]+" + hex(imageOffset) + "}"; + cacheOverrides.array.push_back(patch); + }); + if ( !cacheOverrides.array.empty() ) + root.map["dyld-cache-overrides"].array = cacheOverrides.array; + + return root; +} + +static void indentBy(uint32_t spaces, FILE* out) { + for (int i=0; i < spaces; ++i) { + fprintf(out, " "); + } +} + +static void printJSON(const Node& node, uint32_t indent, FILE* out) +{ + if ( !node.map.empty() ) { + fprintf(out, "{"); + bool needComma = false; + for (const auto& entry : node.map) { + if ( needComma ) + fprintf(out, ","); + fprintf(out, "\n"); + indentBy(indent+2, out); + fprintf(out, "\"%s\": ", entry.first.c_str()); + printJSON(entry.second, indent+2, out); + needComma = true; + } + fprintf(out, "\n"); + indentBy(indent, out); + fprintf(out, "}"); + } + else if ( !node.array.empty() ) { + fprintf(out, "["); + bool needComma = false; + for (const auto& entry : node.array) { + if ( needComma ) + fprintf(out, ","); + fprintf(out, "\n"); + indentBy(indent+2, out); + printJSON(entry, indent+2, out); + needComma = true; + } + fprintf(out, "\n"); + indentBy(indent, out); + fprintf(out, "]"); + } + else { + fprintf(out, "\"%s\"", node.value.c_str()); + } + if ( indent == 0 ) + fprintf(out, "\n"); +} + + +void Image::printAsJSON(const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails, FILE* out) const +{ + Node image = buildImageNode(*this, groupList, printFixups, printDependentsDetails); + printJSON(image, 0, out); +} + +void ImageGroup::printAsJSON(const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails, FILE* out) const +{ + Node root; + root.map["images"] = buildImageGroupNode(*this, groupList, printFixups, printDependentsDetails); + root.map["group-num"].value = decimal(groupNum()); + root.map["dylibs-expected-on-disk"].value = (dylibsExpectedOnDisk() ? "true" : "false"); + printJSON(root, 0, out); +} + +void ImageGroup::printStatistics(FILE* out) const +{ + __block uint32_t totalRebases = 0; + __block uint32_t totalBinds = 0; + for (uint32_t i=0; i < imageCount(); ++i) { + Image img(image(i)); + img.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool &segStop) { + MemoryRange segContent = { nullptr, vmSize }; + img.forEachFixup(segIndex, segContent, ^(uint64_t segOffset, Image::FixupKind kind, TargetSymbolValue value, bool& stop) { + if ( kind == Image::FixupKind::rebase64 ) + ++totalRebases; + else + ++totalBinds; + }); + }); + } + + fprintf(out, "ImageGroup:\n"); + fprintf(out, " image-count: % 5d\n", _binaryData->imagesPoolCount); + fprintf(out, " alias-count: % 5d\n", _binaryData->imageAliasCount); + fprintf(out, " segments-count: % 5d\n", _binaryData->segmentsPoolCount); + fprintf(out, " dependents-count: % 5d\n", _binaryData->dependentsPoolCount); + fprintf(out, " targets-count: % 5d\n", _binaryData->targetsPoolCount); + fprintf(out, " rebase-count: % 5d\n", totalRebases); + fprintf(out, " bind-count: % 5d\n", totalBinds); + fprintf(out, " fixups-size: % 8d bytes\n", _binaryData->fixupsPoolSize); + fprintf(out, " targets-size: % 8ld bytes\n", _binaryData->targetsPoolCount * sizeof(uint64_t)); + fprintf(out, " strings-size: % 8d bytes\n", _binaryData->stringsPoolSize); + fprintf(out, " dofs-size: % 8ld bytes\n", _binaryData->dofOffsetPoolCount * sizeof(uint32_t)); + fprintf(out, " indirect-groups-size: % 8ld bytes\n", _binaryData->indirectGroupNumPoolCount * sizeof(uint32_t)); +} + + +void Closure::printAsJSON(const ImageGroupList& groupList, bool printFixups, bool printDependentsDetails, FILE* out) const +{ + Node root = buildClosureNode(*this, groupList, printFixups, printDependentsDetails); + printJSON(root, 0, out); +} + +void Closure::printStatistics(FILE* out) const +{ + fprintf(out, "closure size: %lu\n", size()); + group().printStatistics(out); +} + + + +} // namespace launch_cache +} // namespace dyld3 + +#endif + + diff --git a/dyld3/LaunchCacheReader.cpp b/dyld3/LaunchCacheReader.cpp new file mode 100644 index 0000000..06c73e1 --- /dev/null +++ b/dyld3/LaunchCacheReader.cpp @@ -0,0 +1,1471 @@ +/* + * Copyright (c) 2017 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 "LaunchCacheFormat.h" +#include "LaunchCache.h" +#include "MachOParser.h" +#include "DyldCacheParser.h" + +namespace dyld { + extern void log(const char* format, ...) __attribute__((format(printf, 1, 2))); +} + +namespace dyld3 { +namespace launch_cache { + +static uintptr_t read_uleb128(const uint8_t*& p, const uint8_t* end) +{ + uint64_t result = 0; + int bit = 0; + do { + if (p == end) { + assert("malformed uleb128"); + break; + } + uint64_t slice = *p & 0x7f; + + if (bit > 63) { + assert("uleb128 too big for uint64"); + break; + } + else { + result |= (slice << bit); + bit += 7; + } + } while (*p++ & 0x80); + return (uintptr_t)result; +} + + +bool MemoryRange::contains(const MemoryRange& other) const +{ + if ( this->address > other.address ) + return false; + const uint8_t* thisEnd = (uint8_t*)address + size; + const uint8_t* otherEnd = (uint8_t*)other.address + other.size; + return (thisEnd >= otherEnd); +} + +bool MemoryRange::intersects(const MemoryRange& other) const +{ + const uint8_t* thisEnd = (uint8_t*)address + size; + const uint8_t* otherEnd = (uint8_t*)other.address + other.size; + if ( otherEnd < this->address ) + return false; + return ( other.address < thisEnd ); +} + + +//////////////////////////// SlowLoadSet //////////////////////////////////////// + +bool SlowLoadSet::contains(const BinaryImageData* image) +{ + for (const BinaryImageData** p=_start; p < _current; ++p) { + if ( *p == image ) + return true; + } + return false; +} + +bool SlowLoadSet::add(const BinaryImageData* image) +{ + if ( _current < _end ) { + *_current++ = image; + return true; + } + return false; +} + +void SlowLoadSet::forEach(void (^handler)(const BinaryImageData*)) +{ + for (const BinaryImageData** p=_start; p < _current; ++p) { + handler(*p); + } +} + +void SlowLoadSet::forEach(void (^handler)(const BinaryImageData*, bool& stop)) +{ + bool stop = false; + for (const BinaryImageData** p=_start; p < _current; ++p) { + handler(*p, stop); + if ( stop ) + break; + } +} + + +long SlowLoadSet::count() const +{ + return (_current - _start); +} + + +//////////////////////////// TargetSymbolValue //////////////////////////////////////// + + +#if DYLD_IN_PROCESS + +uintptr_t TargetSymbolValue::resolveTarget(Diagnostics& diag, const ImageGroup& inGroup, LoadedImages& images) const +{ + // this block is only used if findExportedSymbol() needs to trace re-exported dylibs to find a symbol + MachOParser::DependentFinder reExportFollower = ^(uint32_t depIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) { + *foundMH = nullptr; + images.forEachImage(^(uint32_t idx, const BinaryImageData* binImage, const mach_header* mh, bool& stop) { + Image anImage(binImage); + if ( strcmp(depLoadPath, anImage.path()) == 0 ) { + *foundMH = mh; + stop = true; + } + }); + return (*foundMH != nullptr); + }; + + uintptr_t offset; + switch ( _data.sharedCache.kind ) { + + case TargetSymbolValue::kindSharedCache: + assert(_data.sharedCache.offsetIntoCache != 0); + return (uintptr_t)(images.dyldCacheLoadAddressForImage() + _data.sharedCache.offsetIntoCache); + + case TargetSymbolValue::kindAbsolute: + offset = (uintptr_t)_data.absolute.value; + // sign extend 42 bit value + if ( offset & 0x2000000000000000ULL ) + offset |= 0xC000000000000000ULL; + return offset; + + case TargetSymbolValue::kindGroup: { + uint32_t groupNum = _data.group.isIndirectGroup ? inGroup.indirectGroupNum(_data.group.groupNum) : _data.group.groupNum; + uintptr_t targetImageLoadAddress = (uintptr_t)(images.loadAddressFromGroupAndIndex(groupNum, _data.group.indexInGroup)); + if ( targetImageLoadAddress == 0 ) + diag.error("image for groupNum=%d, indexInGroup=%d not found", groupNum, _data.group.indexInGroup); + offset = (uintptr_t)_data.group.offsetInImage; + // sign extend 42 bit offset + if ( offset & 0x0000020000000000ULL ) + offset |= 0xFFFFFC0000000000ULL; + return targetImageLoadAddress + offset; + } + + case TargetSymbolValue::kindDynamicGroup: { + const char* imagePath = inGroup.stringFromPool(_data.dynamicGroup.imagePathOffset); + const char* symbolName = inGroup.stringFromPool(_data.dynamicGroup.symbolNameOffset); + __block uintptr_t result = 0; + __block bool found = false; + if ( strcmp(imagePath, "@flat") == 0 ) { + // search all images in load order + images.forEachImage(^(uint32_t idx, const BinaryImageData* binImage, const mach_header* mh, bool& stop) { + Diagnostics findSymbolDiag; + dyld3::MachOParser parser(mh); + dyld3::MachOParser::FoundSymbol foundInfo; + if ( parser.findExportedSymbol(findSymbolDiag, symbolName, nullptr, foundInfo, ^(uint32_t, const char* depLoadPath, void*, const mach_header** foundMH, void**) { + // need to follow re-exported symbols to support libc renamed and reexported symbols + *foundMH = nullptr; + images.forEachImage(^(uint32_t innerIndex, const BinaryImageData* innerBinImage, const mach_header* innerMH, bool& innerStop) { + Image innerImage(innerBinImage); + if ( strcmp(depLoadPath, innerImage.path()) == 0 ) { + *foundMH = innerMH; + innerStop = true; + } + }); + return (*foundMH != nullptr); + }) ) { + result = ((uintptr_t)(foundInfo.foundInDylib) + (uintptr_t)foundInfo.value); + images.setAsNeverUnload(idx); + found = true; + stop = true; + } + }); + // bind unfound flat symbols to NULL to support lazy binding semantics + if ( !found ) { + result = 0; + found = true; + } + } + else if ( strcmp(imagePath, "@main") == 0 ) { + // search only main executable + images.forEachImage(^(uint32_t idx, const BinaryImageData* binImage, const mach_header* mh, bool& stop) { + if ( mh->filetype == MH_EXECUTE ) { + Diagnostics findSymbolDiag; + dyld3::MachOParser parser(mh); + dyld3::MachOParser::FoundSymbol foundInfo; + if ( parser.findExportedSymbol(findSymbolDiag, symbolName, nullptr, foundInfo, nullptr) ) { + result = ((uintptr_t)(foundInfo.foundInDylib) + (uintptr_t)foundInfo.value); + found = true; + stop = true; + } + } + }); + } + else if ( strcmp(imagePath, "@weak_def") == 0 ) { + // search images with weak definitions in load order + images.forEachImage(^(uint32_t idx, const BinaryImageData* binImage, const mach_header* mh, bool& stop) { + Image anImage(binImage); + if ( anImage.hasWeakDefs() ) { + Diagnostics findSymbolDiag; + dyld3::MachOParser parser(mh); + dyld3::MachOParser::FoundSymbol foundInfo; + if ( parser.findExportedSymbol(findSymbolDiag, symbolName, nullptr, foundInfo, nullptr) ) { + result = ((uintptr_t)(foundInfo.foundInDylib) + (uintptr_t)foundInfo.value); + found = true; + images.setAsNeverUnload(idx); + stop = true; + } + } + }); + } + else { + // search only image the matches supplied path + images.forEachImage(^(uint32_t idx, const BinaryImageData* binImage, const mach_header* mh, bool& stop) { + Image anImage(binImage); + if ( strcmp(anImage.path(), imagePath) == 0 ) { + Diagnostics findSymbolDiag; + dyld3::MachOParser parser(mh); + dyld3::MachOParser::FoundSymbol foundInfo; + if ( parser.findExportedSymbol(findSymbolDiag, symbolName, nullptr, foundInfo, reExportFollower) ) { + result = ((uintptr_t)(foundInfo.foundInDylib) + (uintptr_t)foundInfo.value); + found = true; + stop = true; + } + } + }); + } + if ( found ) + return result; + if ( _data.dynamicGroup.weakImport ) + return 0; + diag.error("dynamic symbol '%s' not found for %s", symbolName, imagePath); + return 0; + } + } + assert(0 && "resolveTarget() not reachable"); +} + +#else + +TargetSymbolValue::TargetSymbolValue() +{ + _data.raw = 0; +} + +TargetSymbolValue TargetSymbolValue::makeInvalid() +{ + return TargetSymbolValue(); +} + +TargetSymbolValue TargetSymbolValue::makeSharedCacheOffset(uint32_t offset) +{ + TargetSymbolValue t; + t._data.sharedCache.kind = kindSharedCache; + t._data.sharedCache.offsetIntoCache = offset; + return t; +} + +TargetSymbolValue TargetSymbolValue::makeAbsolute(uint64_t value) +{ + TargetSymbolValue t; + t._data.absolute.kind = kindAbsolute; + t._data.absolute.value = value; + return t; +} + +TargetSymbolValue TargetSymbolValue::makeGroupValue(uint32_t groupIndex, uint32_t imageIndexInGroup, uint64_t offsetInImage, bool isIndirectGroupNum) +{ + assert(groupIndex != 0 || isIndirectGroupNum); + assert(groupIndex < 128); + assert(imageIndexInGroup < 4096); + TargetSymbolValue t; + t._data.group.kind = kindGroup; + t._data.group.isIndirectGroup = isIndirectGroupNum; + t._data.group.groupNum = groupIndex; + t._data.group.indexInGroup = imageIndexInGroup; + t._data.group.offsetInImage = offsetInImage; + return t; +} + +TargetSymbolValue TargetSymbolValue::makeDynamicGroupValue(uint32_t imagePathPoolOffset, uint32_t imageSymbolPoolOffset, bool weakImport) +{ + TargetSymbolValue t; + t._data.dynamicGroup.kind = kindDynamicGroup; + t._data.dynamicGroup.weakImport = weakImport; + t._data.dynamicGroup.imagePathOffset = imagePathPoolOffset; + t._data.dynamicGroup.symbolNameOffset = imageSymbolPoolOffset; + return t; +} + +bool TargetSymbolValue::isSharedCacheTarget(uint64_t& offsetInCache) const +{ + if ( _data.sharedCache.kind != kindSharedCache ) + return false; + offsetInCache = _data.sharedCache.offsetIntoCache; + return true; +} + +bool TargetSymbolValue::isGroupImageTarget(uint32_t& groupNum, uint32_t& indexInGroup, uint64_t& offsetInImage) const +{ + if ( _data.sharedCache.kind != kindGroup ) + return false; + // This is only used for interposing, so refuse to allow indirect for group 2 + assert(!_data.group.isIndirectGroup); + groupNum = _data.group.groupNum; + indexInGroup = _data.group.indexInGroup; + offsetInImage = _data.group.offsetInImage; + return true; +} + +bool TargetSymbolValue::isInvalid() const +{ + return (_data.raw == 0); +} + +static std::string hex8(uint64_t value) { + char buff[64]; + sprintf(buff, "0x%08llX", value); + return buff; +} + +static std::string decimal(uint64_t value) { + char buff[64]; + sprintf(buff, "%llu", value); + return buff; +} + +std::string TargetSymbolValue::asString(ImageGroup group) const +{ + int64_t offset; + switch ( _data.sharedCache.kind ) { + case kindSharedCache: + if ( _data.sharedCache.offsetIntoCache == 0 ) + return "{invalid target}"; + else + return "{cache+" + hex8(_data.sharedCache.offsetIntoCache) + "}"; + case kindAbsolute: + offset = (uintptr_t)_data.absolute.value; + // sign extend 42 bit value + if ( offset & 0x2000000000000000ULL ) + offset |= 0xC000000000000000ULL; + return "{absolute:" + hex8(offset) + "}"; + case kindGroup: + offset = _data.group.offsetInImage; + // sign extend 42 bit offset + if ( offset & 0x0000020000000000ULL ) + offset |= 0xFFFFFC0000000000ULL; + if ( _data.group.groupNum == 1 ) + return "{otherDylib[" + decimal(_data.group.indexInGroup) +"]+" + hex8(offset) + "}"; + if ( _data.group.groupNum == 2 ) + return "{closure[" + decimal(_data.group.indexInGroup) +"]+" + hex8(offset) + "}"; + else { + uint32_t groupNum = _data.group.isIndirectGroup ? group.indirectGroupNum(_data.group.groupNum) : _data.group.groupNum; + return "{dlopen-group-" + decimal(groupNum-2) + "[" + decimal(_data.group.indexInGroup) +"]+" + hex8(offset) + "}"; + } + case kindDynamicGroup: + return "{dynamic image='" + std::string(group.stringFromPool(_data.dynamicGroup.imagePathOffset)) + + "' symbol='" + std::string(group.stringFromPool(_data.dynamicGroup.symbolNameOffset)) + "'}"; + } + assert(0 && "unreachable"); + return "xx"; +} + +#endif + +//////////////////////////// ImageRef //////////////////////////////////////// + +binary_format::ImageRef binary_format::ImageRef::weakImportMissing() +{ + ImageRef missing(0xFFFFFFFF); + return missing; +} + + + +//////////////////////////// Closure //////////////////////////////////////// + +Closure::Closure(const binary_format::Closure* closure) + : _binaryData(closure) +{ + assert(closure->magic == binary_format::Closure::magicV1); +} + +size_t Closure::size() const +{ + return _binaryData->stringPoolOffset + _binaryData->stringPoolSize; +} + +const ImageGroup Closure::group() const +{ + return ImageGroup(&_binaryData->group); +} + +void Closure::forEachEnvVar(void (^handler)(const char* keyEqualValue, bool& stop)) const +{ + const uint32_t* envVarStringOffsets = (uint32_t*)((uint8_t*)_binaryData + _binaryData->dyldEnvVarsOffset); + const char* stringPool = (char*)_binaryData + _binaryData->stringPoolOffset; + bool stop = false; + for (uint32_t i=0; i < _binaryData->dyldEnvVarsCount; ++i) { + handler(&stringPool[envVarStringOffsets[i]], stop); + if ( stop ) + break; + } +} + +void Closure::forEachMustBeMissingFile(void (^handler)(const char* path, bool& stop)) const +{ + const uint16_t* offsets = (uint16_t*)((uint8_t*)_binaryData + _binaryData->missingFileComponentsOffset); + if ( *offsets == 0 ) + return; + const char* stringPool = (char*)_binaryData + _binaryData->stringPoolOffset; + bool stop = false; + while ( !stop ) { + char path[PATH_MAX]; + path[0] = '\0'; + while ( *offsets != 0 ) { + const char* component = &stringPool[*offsets++]; + strlcat(path, "/", PATH_MAX); + strlcat(path, component, PATH_MAX); + } + handler(path, stop); + ++offsets; // move to next path + if ( *offsets == 0 ) // if no next path, then end of list of strings + stop = true; + } +} + +const uuid_t* Closure::dyldCacheUUID() const +{ + return &(_binaryData->dyldCacheUUID); +} + + +const uint8_t* Closure::cdHash() const +{ + return _binaryData->mainExecutableCdHash; +} + + +uint32_t Closure::initialImageCount() const +{ + return _binaryData->initialImageCount; +} + + +uint32_t Closure::mainExecutableImageIndex() const +{ + return _binaryData->mainExecutableIndexInGroup; +} + + +uint32_t Closure::mainExecutableEntryOffset() const +{ + return _binaryData->mainExecutableEntryOffset; +} + +bool Closure::mainExecutableUsesCRT() const +{ + return _binaryData->usesCRT; +} + +bool Closure::isRestricted() const +{ + return _binaryData->isRestricted; +} + +bool Closure::usesLibraryValidation() const +{ + return _binaryData->usesLibraryValidation; +} + +uint32_t Closure::libdyldVectorOffset() const +{ + return _binaryData->libdyldVectorOffset; +} + +const BinaryImageData* Closure::libSystem(const ImageGroupList& groups) +{ + return Image::resolveImageRef(groups, _binaryData->libSystemRef).binaryData(); +} + +const BinaryImageData* Closure::libDyld(const ImageGroupList& groups) +{ + return Image::resolveImageRef(groups, _binaryData->libDyldRef).binaryData(); +} + + +//////////////////////////// ImageGroup //////////////////////////////////////// + +size_t ImageGroup::size() const +{ + return (_binaryData->stringsPoolOffset + _binaryData->stringsPoolSize + 3) & (-4); +} + +uint32_t ImageGroup::groupNum() const +{ + return _binaryData->groupNum; +} + +bool ImageGroup::dylibsExpectedOnDisk() const +{ + return _binaryData->dylibsExpectedOnDisk; +} + +uint32_t ImageGroup::imageCount() const +{ + return _binaryData->imagesPoolCount; +} + +const binary_format::Image* ImageGroup::imageBinary(uint32_t index) const +{ + assert(index <_binaryData->imagesPoolCount); + return (binary_format::Image*)((char*)_binaryData + _binaryData->imagesPoolOffset + (index * _binaryData->imagesEntrySize)); +} + + +const Image ImageGroup::image(uint32_t index) const +{ + return Image(imageBinary(index)); +} + +uint32_t ImageGroup::indexInGroup(const binary_format::Image* img) const +{ + long delta = (char*)img - ((char*)_binaryData + _binaryData->imagesPoolOffset); + uint32_t index = (uint32_t)(delta /_binaryData->imagesEntrySize); + assert(image(index)._binaryData == img); + return index; +} + +const binary_format::Image* ImageGroup::findImageByPath(const char* path, uint32_t& foundIndex) const +{ + // check path of each image in group + uint32_t targetHash = hashFunction(path); + const uint8_t* p = (uint8_t*)_binaryData + _binaryData->imagesPoolOffset; + for (uint32_t i=0; i < _binaryData->imagesPoolCount; ++i) { + const binary_format::Image* binImage = (binary_format::Image*)p; + if ( binImage->pathHash == targetHash ) { + Image img(binImage); + if ( !img.isInvalid() && (strcmp(img.path(), path) == 0) ) { + foundIndex = i; + return binImage; + } + } + p += _binaryData->imagesEntrySize; + } + // check each alias + const binary_format::AliasEntry* aliasEntries = (binary_format::AliasEntry*)((uint8_t*)_binaryData + _binaryData->imageAliasOffset); + for (uint32_t i=0; i < _binaryData->imageAliasCount; ++i) { + const char* aliasPath = stringFromPool(aliasEntries[i].aliasOffsetInStringPool); + if ( aliasEntries[i].aliasHash == targetHash ) { + if ( strcmp(aliasPath, path) == 0 ) { + Image img = image(aliasEntries[i].imageIndexInGroup); + if ( !img.isInvalid() ) { + foundIndex = aliasEntries[i].imageIndexInGroup; + return img.binaryData(); + } + } + } + } + return nullptr; +} + +const binary_format::Image* ImageGroup::findImageByCacheOffset(size_t cacheVmOffset, uint32_t& mhCacheOffset, uint8_t& foundPermissions) const +{ + assert(groupNum() == 0); + + const binary_format::DyldCacheSegment* cacheSegs = (binary_format::DyldCacheSegment*)segmentPool(0); + const binary_format::Image* image = (binary_format::Image*)((char*)_binaryData + _binaryData->imagesPoolOffset); + // most address lookups are in TEXT, so just search first segment in first pass + for (uint32_t imageIndex=0; imageIndex < _binaryData->imagesPoolCount; ++imageIndex) { + const binary_format::DyldCacheSegment* segInfo = &cacheSegs[image->segmentsArrayStartIndex]; + if ( (cacheVmOffset >= segInfo->cacheOffset) && (cacheVmOffset < (segInfo->cacheOffset + segInfo->size)) ) { + mhCacheOffset = segInfo->cacheOffset; + foundPermissions = segInfo->permissions; + return image; + } + image = (binary_format::Image*)((char*)image + _binaryData->imagesEntrySize); + } + // second pass, skip TEXT segment + image = (binary_format::Image*)((char*)_binaryData + _binaryData->imagesPoolOffset); + for (uint32_t imageIndex=0; imageIndex < _binaryData->imagesPoolCount; ++imageIndex) { + for (uint32_t segIndex=1; segIndex < image->segmentsArrayCount; ++segIndex) { + const binary_format::DyldCacheSegment* segInfo = &cacheSegs[image->segmentsArrayStartIndex+segIndex]; + if ( (cacheVmOffset >= segInfo->cacheOffset) && (cacheVmOffset < (segInfo->cacheOffset + segInfo->size)) ) { + mhCacheOffset = cacheSegs[image->segmentsArrayStartIndex].cacheOffset; + foundPermissions = segInfo->permissions; + return image; + } + } + image = (binary_format::Image*)((char*)image + _binaryData->imagesEntrySize); + } + return nullptr; +} + +void ImageGroup::forEachAliasOf(uint32_t imageIndex, void (^handler)(const char* aliasPath, uint32_t aliasPathHash, bool& stop)) const +{ + bool stop = false; + const binary_format::AliasEntry* aliasEntries = (binary_format::AliasEntry*)((uint8_t*)_binaryData + _binaryData->imageAliasOffset); + for (uint32_t i=0; i < _binaryData->imageAliasCount; ++i) { + if ( aliasEntries[i].imageIndexInGroup == imageIndex ) { + const char* aliasPath = stringFromPool(aliasEntries[i].aliasOffsetInStringPool); + handler(aliasPath, aliasEntries[i].aliasHash, stop); + if ( stop ) + break; + } + } +} + +const char* ImageGroup::stringPool() const +{ + return (char*)_binaryData + _binaryData->stringsPoolOffset; +} + +const char* ImageGroup::stringFromPool(uint32_t offset) const +{ + assert(offset < _binaryData->stringsPoolSize); + return (char*)_binaryData + _binaryData->stringsPoolOffset + offset; +} + +uint32_t ImageGroup::stringPoolSize() const +{ + return _binaryData->stringsPoolSize;; +} + +binary_format::ImageRef ImageGroup::dependentPool(uint32_t index) const +{ + assert(index < _binaryData->dependentsPoolCount); + const binary_format::ImageRef* depArray = (binary_format::ImageRef*)((char*)_binaryData + _binaryData->dependentsPoolOffset); + return depArray[index]; +} + +const uint64_t* ImageGroup::segmentPool(uint32_t index) const +{ + assert(index < _binaryData->segmentsPoolCount); + const uint64_t* segArray = (uint64_t*)((char*)_binaryData + _binaryData->segmentsPoolOffset); + return &segArray[index]; +} + + +const uint32_t* ImageGroup::initializerOffsetsPool() const +{ + return (uint32_t*)((char*)_binaryData + _binaryData->intializerOffsetPoolOffset); +} + +const uint32_t ImageGroup::initializerOffsetsCount() const +{ + return _binaryData->intializerOffsetPoolCount; +} + +const binary_format::ImageRef* ImageGroup::intializerListPool() const +{ + return (binary_format::ImageRef*)((char*)_binaryData + _binaryData->intializerListPoolOffset); +} + +const uint32_t ImageGroup::intializerListPoolCount() const +{ + return _binaryData->intializerListPoolCount; +} + +const binary_format::AllFixupsBySegment* ImageGroup::fixUps(uint32_t offset) const +{ + return (binary_format::AllFixupsBySegment*)((char*)_binaryData + _binaryData->fixupsOffset + offset); +} + +const TargetSymbolValue* ImageGroup::targetValuesArray() const +{ + return (TargetSymbolValue*)((char*)_binaryData + _binaryData->targetsOffset); +} + +uint32_t ImageGroup::targetValuesCount() const +{ + return _binaryData->targetsPoolCount; +} + + +const uint32_t* ImageGroup::dofOffsetsPool() const +{ + return (uint32_t*)((char*)_binaryData + _binaryData->dofOffsetPoolOffset); +} + +const uint32_t ImageGroup::dofOffsetsCount() const +{ + return _binaryData->dofOffsetPoolCount; +} + + +const uint32_t* ImageGroup::indirectGroupNumsPool() const +{ + return (uint32_t*)((char*)_binaryData + _binaryData->indirectGroupNumPoolOffset); +} + +const uint32_t ImageGroup::indirectGroupNumsCount() const +{ + return _binaryData->indirectGroupNumPoolCount; +} + +uint32_t ImageGroup::indirectGroupNum(uint32_t offset) const +{ + assert(offset < _binaryData->indirectGroupNumPoolCount); + return indirectGroupNumsPool()[offset]; +} + +uint32_t ImageGroup::hashFunction(const char* str) +{ + uint32_t h = 0; + for (const char* s=str; *s != '\0'; ++s) + h = h*5 + *s; + return h; +} + + +void ImageGroup::forEachDyldCachePatch(uint32_t patchTargetIndex, uint32_t cacheDataVmOffset, void (^handler)(uint32_t targetCacheOffset, uint32_t usePointersCacheOffset, bool hasAddend, bool& stop)) const +{ + assert(_binaryData->imagesEntrySize == sizeof(binary_format::CachedImage) && "only callable on group-0 in shared cache"); + assert(patchTargetIndex < _binaryData->cachePatchTableCount); + const binary_format::PatchTable* patches = (binary_format::PatchTable*)((char*)_binaryData + _binaryData->cachePatchTableOffset); + uint32_t offsetsIndex = patches[patchTargetIndex].offsetsStartIndex; + uint32_t targetCacheOffset = patches[patchTargetIndex].targetCacheOffset; + const binary_format::PatchOffset* patchLocationOffsets = (binary_format::PatchOffset*)((char*)_binaryData + _binaryData->cachePatchOffsetsOffset); + bool stop = false; + while ( !stop ) { + assert(offsetsIndex < _binaryData->cachePatchOffsetsCount); + binary_format::PatchOffset entry = patchLocationOffsets[offsetsIndex]; + ++offsetsIndex; + handler(targetCacheOffset, cacheDataVmOffset+entry.dataRegionOffset, entry.hasAddend, stop); + if ( entry.last ) + stop = true; + } +} + +void ImageGroup::forEachImageRefOverride(void (^handler)(binary_format::ImageRef standardDylibRef, binary_format::ImageRef overrideDylibRef, bool& stop)) const +{ + bool stop = false; + const binary_format::ImageRefOverride* entries = (binary_format::ImageRefOverride*)((char*)_binaryData + _binaryData->imageOverrideTableOffset); + for (uint32_t i=0; (i < _binaryData->imageOverrideTableCount) && !stop; ++i) { + handler(entries[i].standardDylib, entries[i].overrideDylib, stop); + } +} + +void ImageGroup::forEachImageRefOverride(const ImageGroupList& groupList, void (^handler)(Image standardDylib, Image overrideDylib, bool& stop)) const +{ + forEachImageRefOverride(^(binary_format::ImageRef standardDylibRef, binary_format::ImageRef overrideDylibRef, bool& stop) { + Image standardDylib = Image::resolveImageRef(groupList, standardDylibRef, false); + Image overrideDylib = Image::resolveImageRef(groupList, overrideDylibRef, false); + handler(standardDylib, overrideDylib, stop); + }); +} + + +#if DYLD_IN_PROCESS + +void ImageGroup::forEachDyldCachePatchLocation(const void* dyldCacheLoadAddress, uint32_t patchTargetIndex, void (^handler)(uintptr_t* locationToPatch, uintptr_t addend, bool&)) const +{ + DyldCacheParser cacheParser((DyldSharedCache*)dyldCacheLoadAddress, false); + uint32_t cacheDataVmOffset = (uint32_t)cacheParser.dataRegionRuntimeVmOffset(); + forEachDyldCachePatch(patchTargetIndex, cacheDataVmOffset, ^(uint32_t targetCacheOffset, uint32_t usePointersCacheOffset, bool hasAddend, bool& stop) { + uintptr_t addend = 0; + uintptr_t* fixupLoc = (uintptr_t*)((char*)dyldCacheLoadAddress + usePointersCacheOffset); + if ( hasAddend ) { + uintptr_t currentValue = *fixupLoc; + uintptr_t expectedValue = (uintptr_t)dyldCacheLoadAddress + targetCacheOffset; + uintptr_t delta = currentValue - expectedValue; + assert(delta < 32); + addend = delta; + } + handler(fixupLoc, addend, stop); + }); +} + +void ImageGroup::forEachDyldCacheSymbolOverride(void (^handler)(uint32_t patchTableIndex, const BinaryImageData* image, uint32_t imageOffset, bool& stop)) const +{ + bool stop = false; + const binary_format::DyldCacheOverride* entries = (binary_format::DyldCacheOverride*)((char*)_binaryData + _binaryData->symbolOverrideTableOffset); + for (uint32_t i=0; (i < _binaryData->symbolOverrideTableCount) && !stop; ++i) { + handler(entries[i].patchTableIndex, imageBinary(entries[i].imageIndex), entries[i].imageOffset, stop); + } +} + +#else + +void ImageGroup::forEachDyldCacheSymbolOverride(void (^handler)(uint32_t patchTableIndex, uint32_t imageIndexInClosure, uint32_t imageOffset, bool& stop)) const +{ + bool stop = false; + const binary_format::DyldCacheOverride* entries = (binary_format::DyldCacheOverride*)((char*)_binaryData + _binaryData->symbolOverrideTableOffset); + for (uint32_t i=0; (i < _binaryData->symbolOverrideTableCount) && !stop; ++i) { + handler(entries[i].patchTableIndex, entries[i].imageIndex, entries[i].imageOffset, stop); + } +} + +void ImageGroup::forEachDyldCachePatchLocation(const DyldCacheParser& cacheParser, void (^handler)(uint32_t targetCacheOffset, const std::vector& usesPointersCacheOffsets, bool& stop)) const +{ + uint32_t cacheDataVmOffset = (uint32_t)cacheParser.dataRegionRuntimeVmOffset(); + __block std::vector pointerCacheOffsets; + bool stop = false; + for (uint32_t patchIndex=0; patchIndex < _binaryData->cachePatchTableCount; ++patchIndex) { + pointerCacheOffsets.clear(); + __block uint32_t targetCacheOffset = 0; + forEachDyldCachePatch(patchIndex, cacheDataVmOffset, ^(uint32_t targetCacheOff, uint32_t usePointersCacheOffset, bool hasAddend, bool&) { + targetCacheOffset = targetCacheOff; + pointerCacheOffsets.push_back(usePointersCacheOffset); + }); + std::sort(pointerCacheOffsets.begin(), pointerCacheOffsets.end(), [&](uint32_t a, uint32_t b) { return a < b; }); + handler(targetCacheOffset, pointerCacheOffsets, stop); + if ( stop ) + break; + } +} + +bool ImageGroup::hasPatchTableIndex(uint32_t targetCacheOffset, uint32_t& foundIndex) const +{ + const binary_format::PatchTable* patches = (binary_format::PatchTable*)((char*)_binaryData + _binaryData->cachePatchTableOffset); + for (uint32_t i=0; i < _binaryData->cachePatchTableCount; ++i) { + if ( patches[i].targetCacheOffset == targetCacheOffset ) { + foundIndex = i; + return true; + } + } + return false; +} + +#endif + + +//////////////////////////// Image //////////////////////////////////////// + + + +const ImageGroup Image::group() const +{ + return ImageGroup((binary_format::ImageGroup*)(((char*)_binaryData) + (_binaryData->groupOffset))); +} + +uint32_t Image::maxLoadCount() const +{ + return _binaryData->maxLoadCount; +} + +const char* Image::path() const +{ + return group().stringFromPool(_binaryData->pathPoolOffset); +} + +uint32_t Image::pathHash() const +{ + return _binaryData->pathHash; +} + +const char* Image::leafName() const +{ + const char* path = group().stringFromPool(_binaryData->pathPoolOffset); + const char* lastSlash = strrchr(path, '/'); + if ( lastSlash != nullptr ) + return lastSlash+1; + else + return path; +} + +const uuid_t* Image::uuid() const +{ + return &(_binaryData->uuid); +} + +bool Image::isInvalid() const +{ + return (_binaryData == nullptr) || _binaryData->isInvalid; +} + +bool Image::hasObjC() const +{ + return _binaryData->hasObjC; +} + +bool Image::isBundle() const +{ + return _binaryData->isBundle; +} + +bool Image::hasWeakDefs() const +{ + return _binaryData->hasWeakDefs; +} + +bool Image::mayHavePlusLoads() const +{ + return _binaryData->mayHavePlusLoads; +} + +bool Image::hasTextRelocs() const +{ + return _binaryData->hasTextRelocs; +} + +bool Image::neverUnload() const +{ + return _binaryData->neverUnload; +} + +bool Image::cwdMustBeThisDir() const +{ + return _binaryData->cwdSameAsThis; +} + +bool Image::isPlatformBinary() const +{ + return _binaryData->isPlatformBinary; +} + +bool Image::overridableDylib() const +{ + return _binaryData->overridableDylib; +} + +void Image::forEachDependentImage(const ImageGroupList& groups, void (^handler)(uint32_t depIndex, Image depImage, LinkKind kind, bool& stop)) const +{ + assert(!_binaryData->isInvalid); + binary_format::ImageRef missingRef = binary_format::ImageRef::weakImportMissing(); + __block bool stop = false; + for (uint32_t depIndex=0; (depIndex < _binaryData->dependentsArrayCount) && !stop; ++depIndex) { + binary_format::ImageRef ref = group().dependentPool(_binaryData->dependentsArrayStartIndex + depIndex); + if ( ref != missingRef ) { + Image depImage(resolveImageRef(groups, ref)); + handler(depIndex, depImage, (LinkKind)ref.kind(), stop); + } + } +} + + +#if !DYLD_IN_PROCESS +bool Image::recurseAllDependentImages(const ImageGroupList& groups, std::unordered_set& allDependents) const +{ + if ( isInvalid() ) + return false; + __block bool result = true; + forEachDependentImage(groups, ^(uint32_t depIndex, Image depImage, LinkKind kind, bool& stop) { + if ( allDependents.count(depImage.binaryData()) == 0 ) { + allDependents.insert(depImage.binaryData()); + if ( !depImage.recurseAllDependentImages(groups, allDependents) ) { + result = false; + stop = true; + } + } + }); + return result; +} +#endif + +bool Image::recurseAllDependentImages(const ImageGroupList& groups, SlowLoadSet& allDependents, bool& stopped, + void (^handler)(const dyld3::launch_cache::binary_format::Image* aBinImage, bool& stop)) const +{ + __block bool result = true; + // breadth first, add all directly dependent images + const dyld3::launch_cache::binary_format::Image* needToProcessArray[_binaryData->dependentsArrayCount]; + memset((void*)needToProcessArray, 0, _binaryData->dependentsArrayCount * sizeof(*needToProcessArray)); + const dyld3::launch_cache::binary_format::Image** const needToProcess = needToProcessArray; + forEachDependentImage(groups, ^(uint32_t depIndex, Image depImage, LinkKind kind, bool& stop) { + const dyld3::launch_cache::binary_format::Image* depImageData = depImage.binaryData(); + if ( allDependents.contains(depImageData) ) { + needToProcess[depIndex] = nullptr; + } + else { + needToProcess[depIndex] = depImageData; + if ( !allDependents.add(depImageData) ) { + result = false; + stop = true; + return; + } + if (handler) { + handler(depImageData, stop); + if ( stop ) + stopped = true; + } + } + }); + + // recurse on each dependent image + for (int i=0; !stopped && (i < _binaryData->dependentsArrayCount); ++i) { + if ( const dyld3::launch_cache::binary_format::Image* depImageData = needToProcess[i] ) { + Image depImage(depImageData); + if ( !depImage.recurseAllDependentImages(groups, allDependents, stopped, handler) ) { + return false; + } + } + } + + return result; +} + +bool Image::recurseAllDependentImages(const ImageGroupList& groups, SlowLoadSet& allDependents, + void (^handler)(const dyld3::launch_cache::binary_format::Image* aBinImage, bool& stop)) const +{ + bool stopped = false; + return recurseAllDependentImages(groups, allDependents, stopped, handler); +} + +void Image::forEachDiskSegment(void (^handler)(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop)) const +{ + assert(isDiskImage()); + const uint32_t pageSize = (_binaryData->has16KBpages ? 0x4000 : 0x1000); + const uint64_t* rawSegs = group().segmentPool(_binaryData->segmentsArrayStartIndex); + const binary_format::DiskSegment* diskSegs = (binary_format::DiskSegment*)rawSegs; + uint32_t segIndex = 0; + uint32_t fileOffset = 0; + int64_t vmOffset = 0; + // decrement vmOffset by all segments before TEXT (e.g. PAGEZERO) + for (uint32_t i=0; i < _binaryData->segmentsArrayCount; ++i) { + const binary_format::DiskSegment* seg = &diskSegs[i]; + if ( seg->filePageCount != 0 ) { + break; + } + vmOffset -= (uint64_t)seg->vmPageCount * pageSize; + } + // walk each segment and call handler + for (uint32_t i=0; i < _binaryData->segmentsArrayCount; ++i) { + const binary_format::DiskSegment* seg = &diskSegs[i]; + uint64_t vmSize = (uint64_t)seg->vmPageCount * pageSize; + uint32_t fileSize = seg->filePageCount * pageSize; + if ( !seg->paddingNotSeg ) { + bool stop = false; + handler(segIndex, ( fileSize == 0) ? 0 : fileOffset, fileSize, vmOffset, vmSize, seg->permissions, stop); + ++segIndex; + if ( stop ) + break; + } + vmOffset += vmSize; + fileOffset += fileSize; + } +} + +void Image::forEachCacheSegment(void (^handler)(uint32_t segIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop)) const +{ + assert(!isDiskImage()); + const uint64_t* rawSegs = group().segmentPool(_binaryData->segmentsArrayStartIndex); + const binary_format::DyldCacheSegment* cacheSegs = (binary_format::DyldCacheSegment*)rawSegs; + bool stop = false; + for (uint32_t i=0; i < _binaryData->segmentsArrayCount; ++i) { + uint64_t vmOffset = cacheSegs[i].cacheOffset - cacheSegs[0].cacheOffset; + uint64_t vmSize = cacheSegs[i].size; + uint8_t permissions = cacheSegs[i].permissions; + handler(i, vmOffset, vmSize, permissions, stop); + if ( stop ) + break; + } +} + +bool Image::segmentHasFixups(uint32_t segIndex) const +{ + return (segmentFixups(segIndex) != nullptr); +} + +bool Image::containsAddress(const void* addr, const void* imageLoadAddress, uint8_t* permissions) const +{ + if ( addr < imageLoadAddress ) + return false; + + __block bool found = false; + uint64_t offsetInImage = (char*)addr - (char*)imageLoadAddress; + if ( _binaryData->isDiskImage ) { + forEachDiskSegment(^(uint32_t segIterIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t segPerms, bool& stop) { + if ( (offsetInImage >= vmOffset) && (offsetInImage < vmOffset+vmSize) ) { + if ( permissions != nullptr ) + *permissions = segPerms; + found = true; + stop = true; + } + }); + } + else { + forEachCacheSegment(^(uint32_t segIterIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t segPerms, bool& stop) { + if ( (offsetInImage >= vmOffset) && (offsetInImage < vmOffset+vmSize) ) { + if ( permissions != nullptr ) + *permissions = segPerms; + found = true; + stop = true; + } + }); + } + return found; +} + +void Image::forEachInitializer(const void* imageLoadAddress, void (^handler)(const void* initializer)) const +{ + const uint32_t initCount = _binaryData->initOffsetsArrayCount; + const uint32_t startIndex = _binaryData->initOffsetsArrayStartIndex; + const uint32_t* initOffsets = group().initializerOffsetsPool(); + assert(startIndex + initCount <= group().initializerOffsetsCount()); + for (uint32_t i=0; i < initCount; ++i) { + uint32_t anOffset = initOffsets[startIndex+i]; + const void* func = (char*)imageLoadAddress + anOffset; + handler(func); + } +} + +void Image::forEachInitBefore(void (^handler)(binary_format::ImageRef imageToInit)) const +{ + const uint32_t initCount = _binaryData->initBeforeArrayCount; + const uint32_t startIndex = _binaryData->initBeforeArrayStartIndex; + const uint32_t endIndex = group().intializerListPoolCount(); + const binary_format::ImageRef* initRefs = group().intializerListPool(); + assert(startIndex + initCount <= endIndex); + for (uint32_t i=0; i < initCount; ++i) { + binary_format::ImageRef ref = initRefs[startIndex+i]; + handler(ref); + } +} + +void Image::forEachDOF(const void* imageLoadAddress, void (^handler)(const void* section)) const +{ + const uint32_t dofCount = _binaryData->dofOffsetsArrayCount; + const uint32_t startIndex = _binaryData->dofOffsetsArrayStartIndex; + const uint32_t* dofOffsets = group().dofOffsetsPool(); + assert(startIndex + dofCount <= group().dofOffsetsCount()); + for (uint32_t i=0; i < dofCount; ++i) { + uint32_t anOffset = dofOffsets[startIndex+i]; + const void* section = (char*)imageLoadAddress + anOffset; + handler(section); + } +} + +Image Image::resolveImageRef(const ImageGroupList& groups, binary_format::ImageRef ref, bool applyOverrides) +{ + // first look if ref image is overridden in closure + __block binary_format::ImageRef targetRef = ref; + if ( applyOverrides ) { + binary_format::ImageRef refToMatch = ref; + refToMatch.clearKind(); + for (int i=0; i < groups.count(); ++i) { + ImageGroup aGroup(groups[i]); + if ( aGroup.groupNum() >= 2 ) { + aGroup.forEachImageRefOverride(^(binary_format::ImageRef standardDylibRef, binary_format::ImageRef overrideDylibRef, bool &stop) { + if ( refToMatch == standardDylibRef ) { + targetRef = overrideDylibRef; + stop = true; + } + }); + } + } + } + // create Image object from targetRef + for (int i=0; i < groups.count(); ++i) { + ImageGroup aGroup(groups[i]); + if ( aGroup.groupNum() == targetRef.groupNum() ) { + return aGroup.image(targetRef.indexInGroup()); + } + } + //assert(0 && "invalid ImageRef"); + return Image(nullptr); +} + +void Image::forEachInitBefore(const ImageGroupList& groups, void (^handler)(Image imageToInit)) const +{ + forEachInitBefore(^(binary_format::ImageRef ref) { + handler(resolveImageRef(groups, ref)); + }); +} + +bool Image::validateUsingModTimeAndInode() const +{ + return !group().binaryData()->imageFileInfoIsCdHash; +} + +bool Image::validateUsingCdHash() const +{ + // don't have cdHash info if union has modtime info in it + if ( !group().binaryData()->imageFileInfoIsCdHash ) + return false; + + // don't have codesign blob in dyld cache + if ( !_binaryData->isDiskImage ) + return false; + + // return true if image is code signed and cdHash16 is non-zero + const binary_format::DiskImage* diskImage = asDiskImage(); + if ( diskImage->codeSignFileOffset == 0 ) + return false; + + uint8_t zeros[16]; + bzero(zeros, 16); + return (memcmp(cdHash16(), zeros, 16) != 0); +} + +const uint8_t* Image::cdHash16() const +{ + return _binaryData->fileInfo.cdHash16.bytes; +} + +uint64_t Image::fileModTime() const +{ + return _binaryData->fileInfo.statInfo.mtime; +} + +uint64_t Image::fileINode() const +{ + return _binaryData->fileInfo.statInfo.inode; +} + + +bool Image::isDiskImage() const +{ + return _binaryData->isDiskImage; +} + +const binary_format::DiskImage* Image::asDiskImage() const +{ + assert(_binaryData->isDiskImage); + return (binary_format::DiskImage*)_binaryData; +} + +const binary_format::CachedImage* Image::asCachedImage() const +{ + assert(!_binaryData->isDiskImage); + return (binary_format::CachedImage*)_binaryData; +} + +uint32_t Image::pageSize() const +{ + return (_binaryData->has16KBpages ? 0x4000 : 0x1000); +} + +uint32_t Image::cacheOffset() const +{ + assert(!_binaryData->isDiskImage); + const uint64_t* rawSegs = group().segmentPool(_binaryData->segmentsArrayStartIndex); + const binary_format::DyldCacheSegment* cacheSegs = (binary_format::DyldCacheSegment*)rawSegs; + return cacheSegs[0].cacheOffset; +} + +uint32_t Image::patchStartIndex() const +{ + return asCachedImage()->patchStartIndex; +} + +uint32_t Image::patchCount() const +{ + return asCachedImage()->patchCount; +} + +uint64_t Image::sliceOffsetInFile() const +{ + return asDiskImage()->sliceOffsetIn4K * 4096; +} + +bool Image::hasCodeSignature(uint32_t& fileOffset, uint32_t& size) const +{ + const binary_format::DiskImage* diskImage = asDiskImage(); + if ( diskImage->codeSignFileOffset != 0 ) { + fileOffset = diskImage->codeSignFileOffset; + size = diskImage->codeSignFileSize; + return true; + } + return false; +} + +bool Image::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const +{ + const binary_format::DiskImage* diskImage = asDiskImage(); + if ( diskImage->fairPlayTextPageCount != 0 ) { + textOffset = diskImage->fairPlayTextStartPage * pageSize(); + size = diskImage->fairPlayTextPageCount * pageSize(); + return true; + } + return false; +} + +uint64_t Image::vmSizeToMap() const +{ + return asDiskImage()->totalVmPages * pageSize(); +} + +void Image::forEachFixup(const uint8_t* pageFixups, const void* segContent, uint32_t& offset, uint32_t& ordinal, + void (^handler)(uint32_t pageOffset, FixupKind kind, uint32_t ordinal, bool& stop)) +{ + bool stop = false; + for (const uint8_t* p = pageFixups; (*p != 0) && !stop;) { + binary_format::FixUpOpcode fullOp = (binary_format::FixUpOpcode)(*p); + binary_format::FixUpOpcode majorOp = (binary_format::FixUpOpcode)(*p & 0xF0); + uint8_t low4 = (*p & 0x0F); + switch ( majorOp ) { + case binary_format::FixUpOpcode::done: + return; + case binary_format::FixUpOpcode::rebase32: // apply + switch ( fullOp ) { + case binary_format::FixUpOpcode::bind64: + handler(offset, FixupKind::bind64, ordinal, stop); + offset += 8; + ++p; + break; + case binary_format::FixUpOpcode::bind32: + handler(offset, FixupKind::bind32, ordinal, stop); + offset += 4; + ++p; + break; + case binary_format::FixUpOpcode::rebase64: + handler(offset, FixupKind::rebase64, 0, stop); + offset += 8; + ++p; + break; + case binary_format::FixUpOpcode::rebase32: + handler(offset, FixupKind::rebase32, 0, stop); + offset += 4; + ++p; + break; + case binary_format::FixUpOpcode::rebaseText32: + handler(offset, FixupKind::rebaseText32, 0, stop); + offset += 4; + ++p; + break; + case binary_format::FixUpOpcode::bindText32: + handler(offset, FixupKind::bindText32, ordinal, stop); + offset += 4; + ++p; + break; + case binary_format::FixUpOpcode::bindTextRel32: + handler(offset, FixupKind::bindTextRel32, ordinal, stop); + offset += 4; + ++p; + break; + case binary_format::FixUpOpcode::bindImportJmp32: + handler(offset, FixupKind::bindImportJmp32, ordinal, stop); + offset += 5; + ++p; + break; + //case binary_format::FixUpOpcode::fixupChain64: + // assert(0 && "rebase/bind chain support not implemented yet"); + // break; + default: + assert(0 && "bad opcode"); + break; + } + break; + case binary_format::FixUpOpcode::incPageOffset: + if ( low4 == 0 ) { + ++p; + offset += read_uleb128(p, p+8)*4; + } + else { + offset += (low4*4); + ++p; + } + break; + case binary_format::FixUpOpcode::setPageOffset: + if ( low4 == 0 ) { + ++p; + offset = (uint32_t)read_uleb128(p, p+8); + } + else { + offset = low4; + ++p; + } + break; + case binary_format::FixUpOpcode::incOrdinal: + if ( low4 == 0 ) { + ++p; + ordinal += read_uleb128(p, p+8); + } + else { + ordinal += low4; + ++p; + } + break; + case binary_format::FixUpOpcode::setOrdinal: + if ( low4 == 0 ) { + ++p; + ordinal = (uint32_t)read_uleb128(p, p+8); + } + else { + ordinal = low4; + ++p; + } + break; + case binary_format::FixUpOpcode::repeat: { + ++p; + uint32_t count = (uint32_t)read_uleb128(p, p+8); + uint8_t pattern[32]; + for (int j=0; j < low4; ++j) { + pattern[j] = *p++; + } + pattern[low4] = (uint8_t)binary_format::FixUpOpcode::done; + for (int j=0; j < count; ++j) { + forEachFixup(&pattern[0], segContent, offset, ordinal, handler); + if ( stop ) + break; + } + } + break; + default: + assert(0 && "bad opcode"); + break; + } + } +} + +const binary_format::SegmentFixupsByPage* Image::segmentFixups(uint32_t segIndex) const +{ + const binary_format::DiskImage* diskImage = asDiskImage(); + //const BinaryImageGroupData* g = group().binaryData(); + uint32_t segCountWithFixups = diskImage->fixupsPoolSegCount; + //fprintf(stderr,"segmentFixups(binImage=%p, segIndex=%d), group=%p, segCountWithFixup=%d\n", _binaryData, segIndex, g, segCountWithFixups); + const binary_format::AllFixupsBySegment* allFixups = group().fixUps(diskImage->fixupsPoolOffset); + for (uint32_t i=0; i < segCountWithFixups; ++i) { + if ( allFixups[i].segIndex == segIndex ) { + //fprintf(stderr,"segmentFixups(binImage=%p, segIndex=%d) allFixups=%p, allFixups[%d].segIndex=%d, allFixups[%d].offset=%d\n", _binaryData, segIndex, allFixups, i, allFixups[i].segIndex, i, allFixups[i].offset); + return (binary_format::SegmentFixupsByPage*)((char*)allFixups + allFixups[i].offset); + } + } + //fprintf(stderr,"segmentFixups(binImage=%p, segIndex=%d) => nullptr\n", _binaryData, segIndex); + return nullptr; +} + +void Image::forEachFixup(uint32_t segIndex, MemoryRange segContent, void (^handler)(uint64_t segOffset, FixupKind, TargetSymbolValue, bool& stop)) const +{ + const binary_format::SegmentFixupsByPage* segFixups = segmentFixups(segIndex); + if ( segFixups == nullptr ) + return; + + assert(segFixups->pageCount*segFixups->pageSize <= segContent.size); + + const uint32_t ordinalsIndexInGroupPool = asDiskImage()->targetsArrayStartIndex; + const uint32_t maxOrdinal = asDiskImage()->targetsArrayCount; + const TargetSymbolValue* groupArray = group().targetValuesArray(); + assert(ordinalsIndexInGroupPool < group().targetValuesCount()); + const TargetSymbolValue* targetOrdinalArray = &groupArray[ordinalsIndexInGroupPool]; + + for (uint32_t pageIndex=0; pageIndex < segFixups->pageCount; ++pageIndex) { + const uint8_t* opcodes = (uint8_t*)(segFixups) + segFixups->pageInfoOffsets[pageIndex]; + uint64_t pageStartOffet = pageIndex * segFixups->pageSize; + uint32_t curOffset = 0; + uint32_t curOrdinal = 0; + forEachFixup(opcodes, segContent.address, curOffset, curOrdinal, ^(uint32_t pageOffset, FixupKind kind, uint32_t targetOrdinal, bool& stop) { + assert(targetOrdinal < maxOrdinal); + handler(pageStartOffet + pageOffset, kind, targetOrdinalArray[targetOrdinal], stop); + }); + } +} + + +} // namespace launch_cache +} // namespace dyld3 + + + diff --git a/dyld3/LaunchCacheWriter.cpp b/dyld3/LaunchCacheWriter.cpp new file mode 100644 index 0000000..e51fbdf --- /dev/null +++ b/dyld3/LaunchCacheWriter.cpp @@ -0,0 +1,1285 @@ +/* + * Copyright (c) 2017 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 "LaunchCacheFormat.h" +#include "LaunchCacheWriter.h" +#include "shared-cache/dyld_cache_format.h" +#include "shared-cache/DyldSharedCache.h" +#include "shared-cache/FileUtils.h" + +namespace std +{ + template <> + struct hash + { + std::size_t operator()(const dyld3::launch_cache::binary_format::ImageRef& value) const { + return std::hash()(value.value()); + } + }; +} + + +namespace dyld3 { +namespace launch_cache { + + +static uintptr_t align(uintptr_t value, uintptr_t align) +{ + return (value+align-1) & (-align); +} + +//////////////////////////// ImageGroupWriter //////////////////////////////////////// + +ImageGroupWriter::ImageGroupWriter(uint32_t groupNum, bool pages16KB, bool is64, bool dylibsExpectedOnDisk, bool mtimeAndInodeAreValid) + : _isDiskImage(groupNum != 0), _is64(is64), _groupNum(groupNum), _pageSize(pages16KB ? 0x4000 : 0x1000), + _dylibsExpectedOnDisk(dylibsExpectedOnDisk), _imageFileInfoIsCdHash(!mtimeAndInodeAreValid) +{ +} + + +uint32_t ImageGroupWriter::size() const +{ + binary_format::ImageGroup tempGroup; + layoutBinary(&tempGroup); + return tempGroup.stringsPoolOffset + tempGroup.stringsPoolSize; +} + +void ImageGroupWriter::layoutBinary(binary_format::ImageGroup* grp) const +{ + grp->imagesEntrySize = _isDiskImage ? sizeof(binary_format::DiskImage) : sizeof(binary_format::CachedImage); + grp->groupNum = _groupNum; + grp->dylibsExpectedOnDisk = _dylibsExpectedOnDisk; + grp->imageFileInfoIsCdHash = _imageFileInfoIsCdHash; + grp->padding = 0; + + grp->imagesPoolCount = imageCount(); + grp->imagesPoolOffset = sizeof(binary_format::ImageGroup); + uint32_t imagesPoolSize = grp->imagesEntrySize * grp->imagesPoolCount; + + grp->imageAliasCount = (uint32_t)_aliases.size(); + grp->imageAliasOffset = grp->imagesPoolOffset + imagesPoolSize; + uint32_t imageAliasSize = grp->imageAliasCount * sizeof(binary_format::AliasEntry); + + grp->segmentsPoolCount = (uint32_t)_segmentPool.size(); + grp->segmentsPoolOffset = (uint32_t)align(grp->imageAliasOffset + imageAliasSize, 8); + uint32_t segmentsPoolSize = grp->segmentsPoolCount * sizeof(uint64_t); + + grp->dependentsPoolCount = (uint32_t)_dependentsPool.size(); + grp->dependentsPoolOffset = grp->segmentsPoolOffset + segmentsPoolSize; + uint32_t dependentsPoolSize = grp->dependentsPoolCount * sizeof(binary_format::ImageRef); + + grp->intializerOffsetPoolCount = (uint32_t)_initializerOffsets.size(); + grp->intializerOffsetPoolOffset = (uint32_t)align(grp->dependentsPoolOffset + dependentsPoolSize, 4); + uint32_t intializerOffsetSize = grp->intializerOffsetPoolCount * sizeof(uint32_t); + + grp->intializerListPoolCount = (uint32_t)_initializerBeforeLists.size(); + grp->intializerListPoolOffset = grp->intializerOffsetPoolOffset + intializerOffsetSize; + uint32_t intializerListPoolSize = grp->intializerListPoolCount * sizeof(binary_format::ImageRef); + + grp->targetsPoolCount = (uint32_t)_targetsPool.size(); + grp->targetsOffset = (uint32_t)align(grp->intializerListPoolOffset + intializerListPoolSize, 8); + uint32_t targetsSize = grp->targetsPoolCount * sizeof(TargetSymbolValue); + + grp->fixupsPoolSize = (uint32_t)_fixupsPool.size(); + grp->fixupsOffset = (uint32_t)align(grp->targetsOffset + targetsSize, 4); + + grp->cachePatchTableCount = (uint32_t)_patchPool.size(); + grp->cachePatchTableOffset = (uint32_t)align(grp->fixupsOffset + grp->fixupsPoolSize, 4); + uint32_t patchTableSize = grp->cachePatchTableCount * sizeof(binary_format::PatchTable); + + grp->cachePatchOffsetsCount = (uint32_t)_patchLocationPool.size(); + grp->cachePatchOffsetsOffset = grp->cachePatchTableOffset + patchTableSize; + uint32_t patchOffsetsSize = grp->cachePatchOffsetsCount * sizeof(binary_format::PatchOffset); + + grp->symbolOverrideTableCount = (uint32_t)_dyldCacheSymbolOverridePool.size(); + grp->symbolOverrideTableOffset = grp->cachePatchOffsetsOffset + patchOffsetsSize; + uint32_t symbolOverrideSize = grp->symbolOverrideTableCount * sizeof(binary_format::DyldCacheOverride); + + grp->imageOverrideTableCount = (uint32_t)_imageOverridePool.size(); + grp->imageOverrideTableOffset = grp->symbolOverrideTableOffset + symbolOverrideSize; + uint32_t imageOverrideSize = grp->imageOverrideTableCount * sizeof(binary_format::ImageRefOverride); + + grp->dofOffsetPoolCount = (uint32_t)_dofOffsets.size(); + grp->dofOffsetPoolOffset = grp->imageOverrideTableOffset + imageOverrideSize; + uint32_t dofOffsetSize = grp->dofOffsetPoolCount * sizeof(uint32_t); + + grp->indirectGroupNumPoolCount = (uint32_t)_indirectGroupNumPool.size(); + grp->indirectGroupNumPoolOffset = grp->dofOffsetPoolOffset + dofOffsetSize; + uint32_t indirectGroupNumSize = grp->indirectGroupNumPoolCount * sizeof(uint32_t); + + grp->stringsPoolSize = (uint32_t)_stringPool.size(); + grp->stringsPoolOffset = grp->indirectGroupNumPoolOffset + indirectGroupNumSize; +} + + +void ImageGroupWriter::finalizeTo(Diagnostics& diag, const std::vector& curGroups, binary_format::ImageGroup* grp) const +{ + layoutBinary(grp); + uint8_t* buffer = (uint8_t*)grp; + if ( imageCount() > 0 ) { + uint32_t pad1Size = grp->segmentsPoolOffset - (grp->imageAliasOffset + grp->imageAliasCount * sizeof(binary_format::AliasEntry)); + uint32_t pad2Size = grp->targetsOffset - (grp->intializerListPoolOffset + grp->intializerListPoolCount * sizeof(binary_format::ImageRef)); + memcpy(&buffer[grp->imagesPoolOffset], &imageByIndex(0), grp->imagesEntrySize * grp->imagesPoolCount); + memcpy(&buffer[grp->imageAliasOffset], &_aliases[0], grp->imageAliasCount * sizeof(binary_format::AliasEntry)); + bzero( &buffer[grp->segmentsPoolOffset-pad1Size], pad1Size); + memcpy(&buffer[grp->segmentsPoolOffset], &_segmentPool[0], grp->segmentsPoolCount * sizeof(uint64_t)); + memcpy(&buffer[grp->dependentsPoolOffset], &_dependentsPool[0], grp->dependentsPoolCount * sizeof(binary_format::ImageRef)); + memcpy(&buffer[grp->intializerListPoolOffset], &_initializerBeforeLists[0], grp->intializerListPoolCount * sizeof(binary_format::ImageRef)); + memcpy(&buffer[grp->intializerOffsetPoolOffset],&_initializerOffsets[0], grp->intializerOffsetPoolCount * sizeof(uint32_t)); + bzero( &buffer[grp->targetsOffset-pad2Size], pad2Size); + memcpy(&buffer[grp->targetsOffset], &_targetsPool[0], grp->targetsPoolCount * sizeof(TargetSymbolValue)); + memcpy(&buffer[grp->fixupsOffset], _fixupsPool.start(), grp->fixupsPoolSize); + memcpy(&buffer[grp->cachePatchTableOffset], &_patchPool[0], grp->cachePatchTableCount * sizeof(binary_format::PatchTable)); + memcpy(&buffer[grp->cachePatchOffsetsOffset], &_patchLocationPool[0], grp->cachePatchOffsetsCount * sizeof(binary_format::PatchOffset)); + memcpy(&buffer[grp->symbolOverrideTableOffset], &_dyldCacheSymbolOverridePool[0], grp->symbolOverrideTableCount * sizeof(binary_format::DyldCacheOverride)); + memcpy(&buffer[grp->imageOverrideTableOffset], &_imageOverridePool[0], grp->imageOverrideTableCount * sizeof(binary_format::ImageRefOverride)); + memcpy(&buffer[grp->dofOffsetPoolOffset], &_dofOffsets[0], grp->dofOffsetPoolCount * sizeof(uint32_t)); + memcpy(&buffer[grp->indirectGroupNumPoolOffset], &_indirectGroupNumPool[0], grp->indirectGroupNumPoolCount * sizeof(uint32_t)); + memcpy(&buffer[grp->stringsPoolOffset], &_stringPool[0], grp->stringsPoolSize); + } + + // now that we have a real ImageGroup, we can analyze it to find max load counts for each image + ImageGroup imGroup(grp); + std::unordered_set allDependents; + STACK_ALLOC_DYNARRAY(const binary_format::ImageGroup*, curGroups.size()+1, newGroupList); + for (int i=0; i < curGroups.size(); ++i) + newGroupList[i] = curGroups[i]; + newGroupList[newGroupList.count()-1] = grp; + for (uint32_t i=0; i < grp->imagesPoolCount; ++i) { + Image image = imGroup.image(i); + if ( image.isInvalid() ) + continue; + allDependents.clear(); + allDependents.insert(image.binaryData()); + BinaryImageData* imageData = (BinaryImageData*)(buffer + grp->imagesPoolOffset + (i * grp->imagesEntrySize)); + if ( !image.recurseAllDependentImages(newGroupList, allDependents) ) { + //diag.warning("%s dependents on an invalid dylib", image.path()); + imageData->isInvalid = true; + } + imageData->maxLoadCount = (uint32_t)allDependents.size(); + } +} + +uint32_t ImageGroupWriter::maxLoadCount(Diagnostics& diag, const std::vector& curGroups, binary_format::ImageGroup* grp) const +{ + ImageGroup imGroup(grp); + std::unordered_set allDependents; + std::vector allGroups = curGroups; + if ( grp->groupNum == 2 ) + allGroups.push_back(grp); + DynArray groupList(allGroups); + for (uint32_t i=0; i < grp->imagesPoolCount; ++i) { + Image image = imGroup.image(i); + if ( image.isInvalid() ) + continue; + allDependents.insert(image.binaryData()); + BinaryImageData* imageData = (BinaryImageData*)((char*)grp + grp->imagesPoolOffset + (i * grp->imagesEntrySize)); + if ( !image.recurseAllDependentImages(groupList, allDependents) ) { + //diag.warning("%s dependents on an invalid dylib", image.path()); + imageData->isInvalid = true; + } + } + return (uint32_t)allDependents.size(); +} + +void ImageGroupWriter::setImageCount(uint32_t count) +{ + if ( _isDiskImage ) { + _diskImages.resize(count); + bzero(&_diskImages[0], count*sizeof(binary_format::DiskImage)); + } + else { + _images.resize(count); + bzero(&_images[0], count*sizeof(binary_format::CachedImage)); + } + + int32_t offset = 0 - (int32_t)sizeof(binary_format::ImageGroup); + for (uint32_t i=0; i < count; ++i) { + binary_format::Image& img = imageByIndex(i); + img.isDiskImage = _isDiskImage; + img.has16KBpages = (_pageSize == 0x4000); + img.groupOffset = offset; + if ( _isDiskImage ) + offset -= sizeof(binary_format::DiskImage); + else + offset -= sizeof(binary_format::CachedImage); + } +} + +uint32_t ImageGroupWriter::imageCount() const +{ + if ( _isDiskImage ) + return (uint32_t)_diskImages.size(); + else + return (uint32_t)_images.size(); +} + +binary_format::Image& ImageGroupWriter::imageByIndex(uint32_t imageIndex) +{ + assert(imageIndex < imageCount()); + if ( _isDiskImage ) + return _diskImages[imageIndex]; + else + return _images[imageIndex]; +} + +const binary_format::Image& ImageGroupWriter::imageByIndex(uint32_t imageIndex) const +{ + assert(imageIndex < imageCount()); + if ( _isDiskImage ) + return _diskImages[imageIndex]; + else + return _images[imageIndex]; +} + +bool ImageGroupWriter::isInvalid(uint32_t imageIndex) const +{ + return imageByIndex(imageIndex).isInvalid; +} + +void ImageGroupWriter::setImageInvalid(uint32_t imageIndex) +{ + imageByIndex(imageIndex).isInvalid = true; +} + +uint32_t ImageGroupWriter::addIndirectGroupNum(uint32_t groupNum) +{ + auto pos = _indirectGroupNumPoolExisting.find(groupNum); + if ( pos != _indirectGroupNumPoolExisting.end() ) + return pos->second; + uint32_t startOffset = (uint32_t)_indirectGroupNumPool.size(); + _indirectGroupNumPool.push_back(groupNum); + _indirectGroupNumPoolExisting[startOffset] = groupNum; + return startOffset; +} + +uint32_t ImageGroupWriter::addString(const char* str) +{ + auto pos = _stringPoolExisting.find(str); + if ( pos != _stringPoolExisting.end() ) + return pos->second; + uint32_t startOffset = (uint32_t)_stringPool.size(); + size_t size = strlen(str) + 1; + _stringPool.insert(_stringPool.end(), str, &str[size]); + _stringPoolExisting[str] = startOffset; + return startOffset; +} + +void ImageGroupWriter::alignStringPool() +{ + while ( (_stringPool.size() % 4) != 0 ) + _stringPool.push_back('\0'); +} + +void ImageGroupWriter::setImagePath(uint32_t imageIndex, const char* path) +{ + binary_format::Image& image = imageByIndex(imageIndex); + image.pathPoolOffset = addString(path); + image.pathHash = ImageGroup::hashFunction(path); +} + +void ImageGroupWriter::addImageAliasPath(uint32_t imageIndex, const char* anAlias) +{ + binary_format::AliasEntry entry; + entry.aliasHash = ImageGroup::hashFunction(anAlias); + entry.imageIndexInGroup = imageIndex; + entry.aliasOffsetInStringPool = addString(anAlias); + _aliases.push_back(entry); +} + +void ImageGroupWriter::ImageGroupWriter::setImageUUID(uint32_t imageIndex, const uuid_t uuid) +{ + memcpy(imageByIndex(imageIndex).uuid, uuid, sizeof(uuid_t)); +} + +void ImageGroupWriter::setImageHasObjC(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).hasObjC = value; +} + +void ImageGroupWriter::setImageIsBundle(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).isBundle = value; +} + +void ImageGroupWriter::setImageHasWeakDefs(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).hasWeakDefs = value; +} + +void ImageGroupWriter::setImageMayHavePlusLoads(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).mayHavePlusLoads = value; +} + +void ImageGroupWriter::setImageNeverUnload(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).neverUnload = value; +} + +void ImageGroupWriter::setImageMustBeThisDir(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).cwdSameAsThis = value; +} + +void ImageGroupWriter::setImageIsPlatformBinary(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).isPlatformBinary = value; +} + +void ImageGroupWriter::setImageOverridableDylib(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).overridableDylib = value; +} + +void ImageGroupWriter::setImageFileMtimeAndInode(uint32_t imageIndex, uint64_t mTime, uint64_t inode) +{ + imageByIndex(imageIndex).fileInfo.statInfo.mtime = mTime; + imageByIndex(imageIndex).fileInfo.statInfo.inode = inode; + assert(!_imageFileInfoIsCdHash); +} + +void ImageGroupWriter::setImageCdHash(uint32_t imageIndex, uint8_t cdHash[20]) +{ + memcpy(imageByIndex(imageIndex).fileInfo.cdHash16.bytes, cdHash, 16); + assert(_imageFileInfoIsCdHash); +} + +void ImageGroupWriter::setImageIsEncrypted(uint32_t imageIndex, bool value) +{ + imageByIndex(imageIndex).isEncrypted = value; +} + +void ImageGroupWriter::setImageMaxLoadCount(uint32_t imageIndex, uint32_t count) +{ + imageByIndex(imageIndex).maxLoadCount = count; +} + +void ImageGroupWriter::setImageFairPlayRange(uint32_t imageIndex, uint32_t offset, uint32_t size) +{ + assert(imageIndex < imageCount()); + assert(_isDiskImage); + binary_format::DiskImage& image = _diskImages[imageIndex]; + if ( image.has16KBpages ) { + assert((offset & 0x3FFF) == 0); + assert((size & 0x3FFF) == 0); + } + else { + assert((offset & 0xFFF) == 0); + assert((size & 0xFFF) == 0); + } + assert(offset < (_pageSize*16)); + image.fairPlayTextStartPage = offset / _pageSize; + image.fairPlayTextPageCount = size / _pageSize; +} + +void ImageGroupWriter::setImageInitializerOffsets(uint32_t imageIndex, const std::vector& offsets) +{ + binary_format::Image& image = imageByIndex(imageIndex); + image.initOffsetsArrayStartIndex = _initializerOffsets.size(); + image.initOffsetsArrayCount = offsets.size(); + _initializerOffsets.insert(_initializerOffsets.end(), offsets.begin(), offsets.end()); +} + +void ImageGroupWriter::setImageDOFOffsets(uint32_t imageIndex, const std::vector& offsets) +{ + binary_format::Image& image = imageByIndex(imageIndex); + image.dofOffsetsArrayStartIndex = _dofOffsets.size(); + image.dofOffsetsArrayCount = offsets.size(); + _dofOffsets.insert(_dofOffsets.end(), offsets.begin(), offsets.end()); +} + +uint32_t ImageGroupWriter::addUniqueInitList(const std::vector& initBefore) +{ + // see if this initBefore list already exists in pool + if ( _initializerBeforeLists.size() > initBefore.size() ) { + size_t cmpLen = initBefore.size()*sizeof(binary_format::ImageRef); + size_t end = _initializerBeforeLists.size() - initBefore.size(); + for (uint32_t i=0; i < end; ++i) { + if ( memcmp(&initBefore[0], &_initializerBeforeLists[i], cmpLen) == 0 ) { + return i; + } + } + } + uint32_t result = (uint32_t)_initializerBeforeLists.size(); + _initializerBeforeLists.insert(_initializerBeforeLists.end(), initBefore.begin(), initBefore.end()); + return result; +} + +void ImageGroupWriter::setImageInitBefore(uint32_t imageIndex, const std::vector& initBefore) +{ + binary_format::Image& image = imageByIndex(imageIndex); + image.initBeforeArrayStartIndex = addUniqueInitList(initBefore); + image.initBeforeArrayCount = initBefore.size(); +} + +void ImageGroupWriter::setImageSliceOffset(uint32_t imageIndex, uint64_t fileOffset) +{ + assert(imageIndex < imageCount()); + assert(_isDiskImage); + binary_format::DiskImage& image = _diskImages[imageIndex]; + image.sliceOffsetIn4K = (uint32_t)(fileOffset / 4096); +} + +void ImageGroupWriter::setImageCodeSignatureLocation(uint32_t imageIndex, uint32_t fileOffset, uint32_t size) +{ + assert(imageIndex < imageCount()); + assert(_isDiskImage); + binary_format::DiskImage& image = _diskImages[imageIndex]; + image.codeSignFileOffset = fileOffset; + image.codeSignFileSize = size; +} + +void ImageGroupWriter::setImageDependentsCount(uint32_t imageIndex, uint32_t count) +{ + binary_format::Image& image = imageByIndex(imageIndex); + image.dependentsArrayStartIndex = _dependentsPool.size(); + image.dependentsArrayCount = count; + _dependentsPool.resize(_dependentsPool.size() + count); +} + +void ImageGroupWriter::setImageDependent(uint32_t imageIndex, uint32_t depIndex, binary_format::ImageRef dependent) +{ + binary_format::Image& image = imageByIndex(imageIndex); + assert(depIndex < image.dependentsArrayCount); + _dependentsPool[image.dependentsArrayStartIndex + depIndex] = dependent; +} + +uint32_t ImageGroupWriter::imageDependentsCount(uint32_t imageIndex) const +{ + return imageByIndex(imageIndex).dependentsArrayCount; +} + +binary_format::ImageRef ImageGroupWriter::imageDependent(uint32_t imageIndex, uint32_t depIndex) const +{ + const binary_format::Image& image = imageByIndex(imageIndex); + assert(depIndex < image.dependentsArrayCount); + return _dependentsPool[image.dependentsArrayStartIndex + depIndex]; +} + +void ImageGroupWriter::setImageSegments(uint32_t imageIndex, MachOParser& imageParser, uint64_t cacheUnslideBaseAddress) +{ + if ( _isDiskImage ) { + __block uint32_t totalPageCount = 0; + __block uint32_t lastFileOffsetEnd = 0; + __block uint64_t lastVmAddrEnd = 0; + __block std::vector diskSegments; + diskSegments.reserve(8); + imageParser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( (fileOffset != 0) && (fileOffset != lastFileOffsetEnd) ) { + binary_format::DiskSegment filePadding; + filePadding.filePageCount = (fileOffset - lastFileOffsetEnd)/_pageSize; + filePadding.vmPageCount = 0; + filePadding.permissions = 0; + filePadding.paddingNotSeg = 1; + diskSegments.push_back(filePadding); + } + if ( (lastVmAddrEnd != 0) && (vmAddr != lastVmAddrEnd) ) { + binary_format::DiskSegment vmPadding; + vmPadding.filePageCount = 0; + vmPadding.vmPageCount = (vmAddr - lastVmAddrEnd)/_pageSize; + vmPadding.permissions = 0; + vmPadding.paddingNotSeg = 1; + diskSegments.push_back(vmPadding); + totalPageCount += vmPadding.vmPageCount; + } + { + binary_format::DiskSegment segInfo; + segInfo.filePageCount = (fileSize+_pageSize-1)/_pageSize; + segInfo.vmPageCount = (vmSize+_pageSize-1)/_pageSize; + segInfo.permissions = protections & 7; + segInfo.paddingNotSeg = 0; + diskSegments.push_back(segInfo); + totalPageCount += segInfo.vmPageCount; + if ( fileSize != 0 ) + lastFileOffsetEnd = fileOffset + fileSize; + if ( vmSize != 0 ) + lastVmAddrEnd = vmAddr + vmSize; + } + }); + binary_format::Image& image = imageByIndex(imageIndex); + image.segmentsArrayStartIndex = _segmentPool.size(); + image.segmentsArrayCount = diskSegments.size(); + _segmentPool.insert(_segmentPool.end(), (uint64_t*)&diskSegments[0], (uint64_t*)&diskSegments[image.segmentsArrayCount]); + _diskImages[imageIndex].totalVmPages = totalPageCount; + } + else { + binary_format::Image& image = imageByIndex(imageIndex); + image.segmentsArrayStartIndex = _segmentPool.size(); + image.segmentsArrayCount = imageParser.segmentCount(); + _segmentPool.resize(_segmentPool.size() + image.segmentsArrayCount); + __block uint32_t segIndex = 0; + imageParser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + binary_format::DyldCacheSegment seg = { (uint32_t)(vmAddr-cacheUnslideBaseAddress), (uint32_t)vmSize, protections }; + _segmentPool[image.segmentsArrayStartIndex + segIndex] = *((uint64_t*)&seg); + ++segIndex; + }); + } +} + +void ImageGroupWriter::setImagePatchLocations(uint32_t imageIndex, uint32_t funcVmOffset, const std::unordered_set& patchLocations) +{ + assert(imageIndex < imageCount()); + binary_format::CachedImage& image = _images[imageIndex]; + if ( image.patchStartIndex == 0 ) { + image.patchStartIndex = (uint32_t)_patchPool.size(); + image.patchCount = 0; + } + else { + assert(image.patchStartIndex + image.patchCount == _patchPool.size()); + } + + binary_format::PatchTable entry = { funcVmOffset, (uint32_t)_patchLocationPool.size() }; + for (uint32_t loc : patchLocations) { + _patchLocationPool.push_back(*((binary_format::PatchOffset*)&loc)); + } + _patchLocationPool.back().last = true; + _patchPool.push_back(entry); + _images[imageIndex].patchCount++; +} + +void ImageGroupWriter::setGroupCacheOverrides(const std::vector& cacheOverrides) +{ + _dyldCacheSymbolOverridePool = cacheOverrides; +} + +void ImageGroupWriter::addImageIsOverride(binary_format::ImageRef standardDylibRef, binary_format::ImageRef overrideDylibRef) +{ + _imageOverridePool.push_back({standardDylibRef, overrideDylibRef}); +} + + +class SegmentFixUpBuilder +{ +public: + SegmentFixUpBuilder(uint32_t segIndex, uint32_t dataSegPageCount, uint32_t pageSize, bool is64, + const std::vector& fixups, + std::vector& targetsForImage, bool log); + + bool hasFixups() { return _hasFixups; } + uint32_t segIndex() { return _segIndex; } + void appendSegmentFixUpMap(ContentBuffer&); + +private: + struct TmpOpcode { + binary_format::FixUpOpcode op; + uint8_t repeatOpcodeCount; + uint16_t count; + + bool operator!=(const TmpOpcode& rhs) const { + return ((op != rhs.op) || (count != rhs.count) || (repeatOpcodeCount != rhs.repeatOpcodeCount)); + } + }; + + + ContentBuffer makeFixupOpcodesForPage(uint32_t pageStartSegmentOffset, const ImageGroupWriter::FixUp* start, + const ImageGroupWriter::FixUp* end); + uint32_t getOrdinalForTarget(TargetSymbolValue); + void expandOpcodes(const std::vector& opcodes, uint8_t page[0x4000], uint32_t& offset, uint32_t& ordinal); + void expandOpcodes(const std::vector& opcodes, uint8_t page[0x4000]); + bool samePageContent(const uint8_t page1[], const uint8_t page2[]); + void printOpcodes(const char* prefix, const std::vector opcodes); + void printOpcodes(const char* prefix, bool printOffset, const TmpOpcode opcodes[], size_t opcodesLen, uint32_t& offset); + uint32_t opcodeEncodingSize(const std::vector& opcodes); + + const bool _is64; + const bool _log; + bool _hasFixups; + const uint32_t _segIndex; + const uint32_t _dataSegPageCount; + const uint32_t _pageSize; + std::vector& _targets; + std::vector _opcodesByPage; +}; + + + + +SegmentFixUpBuilder::SegmentFixUpBuilder(uint32_t segIndex, uint32_t segPageCount, uint32_t pageSize, bool is64, + const std::vector& fixups, + std::vector& targetsForImage, bool log) + : _is64(is64), _log(log), _hasFixups(false), _segIndex(segIndex), _dataSegPageCount(segPageCount), _pageSize(pageSize), _targets(targetsForImage) +{ + //fprintf(stderr, "SegmentFixUpBuilder(segIndex=%d, segPageCount=%d)\n", segIndex, segPageCount); + _targets.push_back(TargetSymbolValue::makeInvalid()); // ordinal zero reserved to mean "add slide" + _opcodesByPage.resize(segPageCount); + size_t startFixupIndex = 0; + for (uint32_t pageIndex=0; pageIndex < segPageCount; ++pageIndex) { + uint32_t pageStartOffset = pageIndex*_pageSize; + uint32_t pageEndOffset = pageStartOffset+_pageSize; + // find first index in this page + while ( (startFixupIndex < fixups.size()) && ((fixups[startFixupIndex].segIndex != segIndex) || (fixups[startFixupIndex].segOffset < pageStartOffset)) ) + ++startFixupIndex; + // find first index beyond this page + size_t endFixupIndex = startFixupIndex; + while ( (endFixupIndex < fixups.size()) && (fixups[endFixupIndex].segIndex == segIndex) && (fixups[endFixupIndex].segOffset < pageEndOffset) ) + ++endFixupIndex; + // create opcodes for fixups on this pageb + _opcodesByPage[pageIndex] = makeFixupOpcodesForPage(pageStartOffset, &fixups[startFixupIndex], &fixups[endFixupIndex]); + startFixupIndex = endFixupIndex; + } +} + + +uint32_t SegmentFixUpBuilder::getOrdinalForTarget(TargetSymbolValue target) +{ + uint32_t ordinal = 0; + for (const TargetSymbolValue& entry : _targets) { + if ( entry == target ) + return ordinal; + ++ordinal; + } + _targets.push_back(target); + return ordinal; +} + +void SegmentFixUpBuilder::appendSegmentFixUpMap(ContentBuffer& buffer) +{ + std::vector offsets; + uint32_t curOffset = sizeof(binary_format::SegmentFixupsByPage)-4 + _dataSegPageCount*4; + for (auto& opcodes : _opcodesByPage) { + if ( opcodes.size() == 0 ) + offsets.push_back(0); + else + offsets.push_back(curOffset); + curOffset += opcodes.size(); + } + uint32_t totalSize = curOffset; + + // write header + buffer.append_uint32(totalSize); // SegmentFixupsByPage.size + buffer.append_uint32(_pageSize); // SegmentFixupsByPage.pageSize + buffer.append_uint32(_dataSegPageCount); // SegmentFixupsByPage.pageCount + for (uint32_t i=0; i < _dataSegPageCount; ++i) { + buffer.append_uint32(offsets[i]); // SegmentFixupsByPage.pageInfoOffsets[i] + } + // write each page's opcode stream + for (uint32_t i=0; i < offsets.size(); ++i) { + buffer.append_buffer(_opcodesByPage[i]); + } +} + +void SegmentFixUpBuilder::expandOpcodes(const std::vector& opcodes, uint8_t page[]) +{ + uint32_t offset = 0; + uint32_t ordinal = 0; + bzero(page, _pageSize); + expandOpcodes(opcodes, page, offset, ordinal); +} + +void SegmentFixUpBuilder::expandOpcodes(const std::vector& opcodes, uint8_t page[], uint32_t& offset, uint32_t& ordinal) +{ + for (int i=0; i < opcodes.size(); ++i) { + assert(offset < _pageSize); + TmpOpcode tmp = opcodes[i]; + switch ( tmp.op ) { + case binary_format::FixUpOpcode::bind64: + *(uint64_t*)(&page[offset]) = ordinal; + offset += 8; + break; + case binary_format::FixUpOpcode::bind32: + *(uint32_t*)(&page[offset]) = ordinal; + offset += 4; + break; + case binary_format::FixUpOpcode::rebase64: + *(uint64_t*)(&page[offset]) = 0x1122334455667788; + offset += 8; + break; + case binary_format::FixUpOpcode::rebase32: + *(uint32_t*)(&page[offset]) = 0x23452345; + offset += 4; + break; + case binary_format::FixUpOpcode::rebaseText32: + *(uint32_t*)(&page[offset]) = 0x56785678; + offset += 4; + break; + case binary_format::FixUpOpcode::bindText32: + *(uint32_t*)(&page[offset]) = 0x98769876; + offset += 4; + break; + case binary_format::FixUpOpcode::bindTextRel32: + *(uint32_t*)(&page[offset]) = 0x34563456; + offset += 4; + break; + case binary_format::FixUpOpcode::bindImportJmp32: + *(uint32_t*)(&page[offset]) = 0x44556677; + offset += 4; + break; + case binary_format::FixUpOpcode::done: + break; + case binary_format::FixUpOpcode::setPageOffset: + offset = tmp.count; + break; + case binary_format::FixUpOpcode::incPageOffset: + offset += (tmp.count*4); + break; + case binary_format::FixUpOpcode::setOrdinal: + ordinal = tmp.count; + break; + case binary_format::FixUpOpcode::incOrdinal: + ++ordinal; + break; + case binary_format::FixUpOpcode::repeat: { + std::vector pattern; + for (int j=0; j < tmp.repeatOpcodeCount; ++j) { + pattern.push_back(opcodes[i+j+1]); + } + for (int j=0; j < tmp.count; ++j) { + expandOpcodes(pattern, page, offset, ordinal); + } + i += tmp.repeatOpcodeCount; + } + break; + } + } +} + + + +uint32_t SegmentFixUpBuilder::opcodeEncodingSize(const std::vector& opcodes) +{ + uint32_t size = 0; + for (int i=0; i < opcodes.size(); ++i) { + switch ( opcodes[i].op ) { + case binary_format::FixUpOpcode::bind64: + case binary_format::FixUpOpcode::bind32: + case binary_format::FixUpOpcode::rebase64: + case binary_format::FixUpOpcode::rebase32: + case binary_format::FixUpOpcode::rebaseText32: + case binary_format::FixUpOpcode::bindText32: + case binary_format::FixUpOpcode::bindTextRel32: + case binary_format::FixUpOpcode::bindImportJmp32: + case binary_format::FixUpOpcode::done: + ++size; + break; + case binary_format::FixUpOpcode::setPageOffset: + case binary_format::FixUpOpcode::incPageOffset: + case binary_format::FixUpOpcode::setOrdinal: + case binary_format::FixUpOpcode::incOrdinal: + ++size; + if ( opcodes[i].count >= 16 ) + size += ContentBuffer::uleb128_size(opcodes[i].count); + break; + case binary_format::FixUpOpcode::repeat: { + ++size; + size += ContentBuffer::uleb128_size(opcodes[i].count); + std::vector pattern; + for (int j=0; j < opcodes[i].repeatOpcodeCount; ++j) { + pattern.push_back(opcodes[++i]); + } + size += opcodeEncodingSize(pattern); + } + break; + } + } + return size; +} + + +bool SegmentFixUpBuilder::samePageContent(const uint8_t page1[], const uint8_t page2[]) +{ + bool result = true; + if (memcmp(page1, page2, _pageSize) != 0) { + if ( _is64 ) { + const uint64_t* p1 = (uint64_t* )page1; + const uint64_t* p2 = (uint64_t* )page2; + for (int i=0; i < _pageSize/8; ++i) { + if ( p1[i] != p2[i] ) { + fprintf(stderr, "page1[0x%03X] = 0x%016llX, page2[0x%03X] = 0x%016llX\n", i*8, p1[i], i*8, p2[i]); + result = false; + } + } + } + else { + const uint32_t* p1 = (uint32_t* )page1; + const uint32_t* p2 = (uint32_t* )page2; + for (int i=0; i < _pageSize/4; ++i) { + if ( p1[i] != p2[i] ) { + fprintf(stderr, "page1[0x%03X] = 0x%016X, page2[0x%03X] = 0x%016X\n", i*4, p1[i], i*4, p2[i]); + result = false; + } + } + } + } + return result; +} + +void SegmentFixUpBuilder::printOpcodes(const char* prefix, const std::vector opcodes) +{ + uint32_t offset = 0; + printOpcodes(prefix, true, &opcodes[0], opcodes.size(), offset); +} + +void SegmentFixUpBuilder::printOpcodes(const char* prefix, bool printOffset, const TmpOpcode opcodes[], size_t opcodesLen, uint32_t& offset) +{ + for (int i=0; i < opcodesLen; ++i) { + TmpOpcode tmp = opcodes[i]; + if ( printOffset ) + fprintf(stderr, "%s offset=0x%04X: ", prefix, offset); + else + fprintf(stderr, "%s ", prefix); + switch ( tmp.op ) { + case binary_format::FixUpOpcode::bind64: + fprintf(stderr, "bind64\n"); + offset += 8; + break; + case binary_format::FixUpOpcode::bind32: + fprintf(stderr, "bind32\n"); + offset += 4; + break; + case binary_format::FixUpOpcode::rebase64: + fprintf(stderr, "rebase64\n"); + offset += 8; + break; + case binary_format::FixUpOpcode::rebase32: + fprintf(stderr, "rebase32\n"); + offset += 4; + break; + case binary_format::FixUpOpcode::rebaseText32: + fprintf(stderr, "rebaseText32\n"); + offset += 4; + break; + case binary_format::FixUpOpcode::bindText32: + fprintf(stderr, "bindText32\n"); + offset += 4; + break; + case binary_format::FixUpOpcode::bindTextRel32: + fprintf(stderr, "bindTextRel32\n"); + offset += 4; + break; + case binary_format::FixUpOpcode::bindImportJmp32: + fprintf(stderr, "bindJmpRel32\n"); + offset += 4; + break; + case binary_format::FixUpOpcode::done: + fprintf(stderr, "done\n"); + break; + case binary_format::FixUpOpcode::setPageOffset: + fprintf(stderr, "setPageOffset(%d)\n", tmp.count); + offset = tmp.count; + break; + case binary_format::FixUpOpcode::incPageOffset: + fprintf(stderr, "incPageOffset(%d)\n", tmp.count); + offset += (tmp.count*4); + break; + case binary_format::FixUpOpcode::setOrdinal: + fprintf(stderr, "setOrdinal(%d)\n", tmp.count); + break; + case binary_format::FixUpOpcode::incOrdinal: + fprintf(stderr, "incOrdinal(%d)\n", tmp.count); + break; + case binary_format::FixUpOpcode::repeat: { + char morePrefix[128]; + strcpy(morePrefix, prefix); + strcat(morePrefix, " "); + uint32_t prevOffset = offset; + fprintf(stderr, "repeat(%d times, next %d opcodes)\n", tmp.count, tmp.repeatOpcodeCount); + printOpcodes(morePrefix, false, &opcodes[i+1], tmp.repeatOpcodeCount, offset); + i += tmp.repeatOpcodeCount; + uint32_t repeatDelta = (offset-prevOffset)*(tmp.count-1); + offset += repeatDelta; + } + break; + } + } +} + +ContentBuffer SegmentFixUpBuilder::makeFixupOpcodesForPage(uint32_t pageStartSegmentOffset, const ImageGroupWriter::FixUp* start, const ImageGroupWriter::FixUp* end) +{ + //fprintf(stderr, " makeFixupOpcodesForPage(segOffset=0x%06X, startFixup=%p, endFixup=%p)\n", pageStartSegmentOffset, start, end); + std::vector tmpOpcodes; + const uint32_t pointerSize = (_is64 ? 8 : 4); + uint32_t offset = pageStartSegmentOffset; + uint32_t ordinal = 0; + const ImageGroupWriter::FixUp* lastFixup = nullptr; + for (const ImageGroupWriter::FixUp* f=start; f < end; ++f) { + // ignore double bind at same address (ld64 bug) + if ( lastFixup && (lastFixup->segOffset == f->segOffset) ) + continue; + // add opcode to adjust current offset if needed + if ( f->segOffset != offset ) { + if ( ((f->segOffset % 4) != 0) || ((offset % 4) != 0) ) { + // mis aligned pointers use bigger set opcode + tmpOpcodes.push_back({binary_format::FixUpOpcode::setPageOffset, 0, (uint16_t)(f->segOffset-pageStartSegmentOffset)}); + } + else { + uint32_t delta4 = (uint32_t)(f->segOffset - offset)/4; + assert(delta4*4 < _pageSize); + tmpOpcodes.push_back({binary_format::FixUpOpcode::incPageOffset, 0, (uint16_t)delta4}); + } + offset = (uint32_t)f->segOffset; + } + uint32_t nextOrd = 0; + switch ( f->type ) { + case ImageGroupWriter::FixupType::rebase: + tmpOpcodes.push_back({_is64 ? binary_format::FixUpOpcode::rebase64 : binary_format::FixUpOpcode::rebase32, 0, 0}); + offset += pointerSize; + _hasFixups = true; + break; + case ImageGroupWriter::FixupType::pointerLazyBind: + case ImageGroupWriter::FixupType::pointerBind: + //assert(f->target.imageIndex == binary_format::OrdinalEntry::kImageIndexDyldSharedCache); + nextOrd = getOrdinalForTarget(f->target); + if ( nextOrd != ordinal ) { + if ( (nextOrd > ordinal) && (nextOrd < (ordinal+31)) ) { + tmpOpcodes.push_back({binary_format::FixUpOpcode::incOrdinal, 0, (uint16_t)(nextOrd-ordinal)}); + } + else { + tmpOpcodes.push_back({binary_format::FixUpOpcode::setOrdinal, 0, (uint16_t)nextOrd}); + } + ordinal = nextOrd; + } + tmpOpcodes.push_back({_is64 ? binary_format::FixUpOpcode::bind64 : binary_format::FixUpOpcode::bind32, 0, 0}); + offset += pointerSize; + _hasFixups = true; + break; + case ImageGroupWriter::FixupType::rebaseText: + assert(!_is64); + tmpOpcodes.push_back({binary_format::FixUpOpcode::rebaseText32, 0, 0}); + offset += pointerSize; + _hasFixups = true; + break; + case ImageGroupWriter::FixupType::bindText: + assert(!_is64); + nextOrd = getOrdinalForTarget(f->target); + if ( nextOrd != ordinal ) { + if ( (nextOrd > ordinal) && (nextOrd < (ordinal+31)) ) { + tmpOpcodes.push_back({binary_format::FixUpOpcode::incOrdinal, 0, (uint16_t)(nextOrd-ordinal)}); + } + else { + tmpOpcodes.push_back({binary_format::FixUpOpcode::setOrdinal, 0, (uint16_t)nextOrd}); + } + ordinal = nextOrd; + } + tmpOpcodes.push_back({binary_format::FixUpOpcode::bindText32, 0, 0}); + offset += pointerSize; + _hasFixups = true; + break; + case ImageGroupWriter::FixupType::bindTextRel: + assert(!_is64); + nextOrd = getOrdinalForTarget(f->target); + if ( nextOrd != ordinal ) { + if ( (nextOrd > ordinal) && (nextOrd < (ordinal+31)) ) { + tmpOpcodes.push_back({binary_format::FixUpOpcode::incOrdinal, 0, (uint16_t)(nextOrd-ordinal)}); + } + else { + tmpOpcodes.push_back({binary_format::FixUpOpcode::setOrdinal, 0, (uint16_t)nextOrd}); + } + ordinal = nextOrd; + } + tmpOpcodes.push_back({binary_format::FixUpOpcode::bindTextRel32, 0, 0}); + offset += pointerSize; + _hasFixups = true; + break; + case ImageGroupWriter::FixupType::bindImportJmpRel: + assert(!_is64); + nextOrd = getOrdinalForTarget(f->target); + if ( nextOrd != ordinal ) { + if ( (nextOrd > ordinal) && (nextOrd < (ordinal+31)) ) { + tmpOpcodes.push_back({binary_format::FixUpOpcode::incOrdinal, 0, (uint16_t)(nextOrd-ordinal)}); + } + else { + tmpOpcodes.push_back({binary_format::FixUpOpcode::setOrdinal, 0, (uint16_t)nextOrd}); + } + ordinal = nextOrd; + } + tmpOpcodes.push_back({binary_format::FixUpOpcode::bindImportJmp32, 0, 0}); + offset += pointerSize; + _hasFixups = true; + break; + case ImageGroupWriter::FixupType::ignore: + assert(0 && "ignore fixup types should have been removed"); + break; + } + lastFixup = f; + } + + uint8_t firstExpansion[0x4010]; // larger than 16KB to handle unaligned pointers + expandOpcodes(tmpOpcodes, firstExpansion); + + if (_log) printOpcodes("start", tmpOpcodes); + + + for (int stride=1; stride < 6; ++stride) { + for (int i=0; i < tmpOpcodes.size(); ++i) { + int j; + for (j=i+stride; j < tmpOpcodes.size(); j += stride) { + bool strideMatch = true; + for (int k=0; k < stride; ++k) { + if ( (j+k >= tmpOpcodes.size()) || (tmpOpcodes[j+k] != tmpOpcodes[i+k]) ) { + strideMatch = false; + break; + } + if ( (tmpOpcodes[j+k].op == binary_format::FixUpOpcode::repeat) && (tmpOpcodes[j+k].repeatOpcodeCount+k >= stride) ) { + strideMatch = false; + break; + } + } + if ( !strideMatch ) + break; + } + // see if same opcode repeated three or more times + int repeats = (j-i)/stride; + if ( repeats > 3 ) { + // replace run with repeat opcode + tmpOpcodes[i].op = binary_format::FixUpOpcode::repeat; + tmpOpcodes[i].repeatOpcodeCount = stride; + tmpOpcodes[i].count = repeats; + tmpOpcodes.erase(tmpOpcodes.begin()+i+1, tmpOpcodes.begin()+j-stride); + i += stride; + } + else { + // don't look for matches inside a repeat loop + if ( tmpOpcodes[i].op == binary_format::FixUpOpcode::repeat ) + i += tmpOpcodes[i].repeatOpcodeCount; + } + } + if (_log) { + char tmp[32]; + sprintf(tmp, "stride %d", stride); + printOpcodes(tmp, tmpOpcodes); + } + uint8_t secondExpansion[0x4010]; + expandOpcodes(tmpOpcodes, secondExpansion); + if ( !samePageContent(firstExpansion, secondExpansion) ) + printOpcodes("opt", tmpOpcodes); + } + + // convert temp opcodes to real opcodes + bool wroteDone = false; + ContentBuffer opcodes; + for (const TmpOpcode& tmp : tmpOpcodes) { + switch ( tmp.op ) { + case binary_format::FixUpOpcode::bind64: + case binary_format::FixUpOpcode::bind32: + case binary_format::FixUpOpcode::rebase64: + case binary_format::FixUpOpcode::rebase32: + case binary_format::FixUpOpcode::rebaseText32: + case binary_format::FixUpOpcode::bindText32: + case binary_format::FixUpOpcode::bindTextRel32: + case binary_format::FixUpOpcode::bindImportJmp32: + opcodes.append_byte((uint8_t)tmp.op); + break; + case binary_format::FixUpOpcode::done: + opcodes.append_byte((uint8_t)tmp.op); + wroteDone = true; + break; + case binary_format::FixUpOpcode::setPageOffset: + case binary_format::FixUpOpcode::incPageOffset: + case binary_format::FixUpOpcode::setOrdinal: + case binary_format::FixUpOpcode::incOrdinal: + if ( (tmp.count > 0) && (tmp.count < 16) ) { + opcodes.append_byte((uint8_t)tmp.op | tmp.count); + } + else { + opcodes.append_byte((uint8_t)tmp.op); + opcodes.append_uleb128(tmp.count); + } + break; + case binary_format::FixUpOpcode::repeat: { + const TmpOpcode* nextOpcodes = &tmp; + ++nextOpcodes; + std::vector pattern; + for (int i=0; i < tmp.repeatOpcodeCount; ++i) { + pattern.push_back(nextOpcodes[i]); + } + uint32_t repeatBytes = opcodeEncodingSize(pattern); + assert(repeatBytes < 15); + opcodes.append_byte((uint8_t)tmp.op | repeatBytes); + opcodes.append_uleb128(tmp.count); + } + break; + } + } + + if ( (opcodes.size() == 0) || !wroteDone ) + opcodes.append_byte((uint8_t)binary_format::FixUpOpcode::done); + + // make opcodes streams 4-byte aligned + opcodes.pad_to_size(4); + + //fprintf(stderr, " makeFixupOpcodesForPage(pageStartSegmentOffset=0x%0X) result=%lu bytes\n", pageStartSegmentOffset, opcodes.size()); + + return opcodes; +} + + + + +void ImageGroupWriter::setImageFixups(Diagnostics& diag, uint32_t imageIndex, std::vector& fixups, bool hasTextRelocs) +{ + // only applicable for ImageGroup in a closure (not group of images in dyld cache) + assert(_isDiskImage); + + // sort all rebases and binds by address + std::sort(fixups.begin(), fixups.end(), [](FixUp& lhs, FixUp& rhs) -> bool { + if ( &lhs == &rhs ) + return false; + // sort by segIndex + if ( lhs.segIndex < rhs.segIndex ) + return true; + if ( lhs.segIndex > rhs.segIndex ) + return false; + // then sort by segOffset + if ( lhs.segOffset < rhs.segOffset ) + return true; + if ( lhs.segOffset > rhs.segOffset ) + return false; + // two fixups at same location + + // if the same (linker bug), ignore one + if ( lhs.type == rhs.type ) { + rhs.type = FixupType::ignore; + } + // if one is rebase for lazy pointer, ignore rebase because dyld3 does not lazy bind + else if ( (lhs.type == FixupType::pointerLazyBind) && (rhs.type == FixupType::rebase) ) { + // lazy pointers have rebase and (lazy) bind at same location. since dyld3 does not do lazy binding, we mark the rebase to be ignored later + rhs.type = FixupType::ignore; + } + else if ( (rhs.type == FixupType::pointerLazyBind) && (lhs.type == FixupType::rebase) ) { + // lazy pointers have rebase and (lazy) bind at same location. since dyld3 does not do lazy binding, we mark the rebase to be ignored later + lhs.type = FixupType::ignore; + } + return (lhs.type < rhs.type); + }); + + // remove ignoreable fixups + fixups.erase(std::remove_if(fixups.begin(), fixups.end(), + [&](const FixUp& a) { + return (a.type == FixupType::ignore); + }), fixups.end()); + + // look for overlapping fixups + const uint32_t pointerSize = (_is64 ? 8 : 4); + const FixUp* lastFixup = nullptr; + for (const FixUp& fixup : fixups) { + if ( lastFixup != nullptr ) { + if ( lastFixup->segIndex == fixup.segIndex ) { + uint64_t increment = fixup.segOffset - lastFixup->segOffset; + if ( increment < pointerSize ) { + if ( (increment == 0) && ((lastFixup->type == FixupType::ignore) || (fixup.type == FixupType::ignore)) ) { + // allow rebase to local lazy helper and lazy bind to same location + } + else { + diag.error("segment %d has overlapping fixups at offset 0x%0llX and 0x%0llX", fixup.segIndex, lastFixup->segOffset, fixup.segOffset); + setImageInvalid(imageIndex); + return; + } + } + } + } + lastFixup = &fixup; + } + + if ( hasTextRelocs ) + _diskImages[imageIndex].hasTextRelocs = true; + + // there is one ordinal table per image, shared by all segments with fixups in that image + std::vector targetsForImage; + + const bool opcodeLogging = false; + // calculate SegmentFixupsByPage for each segment + std::vector builders; + for (uint32_t segIndex=0, onDiskSegIndex=0; segIndex < _diskImages[imageIndex].segmentsArrayCount; ++segIndex) { + const binary_format::DiskSegment* diskSeg = (const binary_format::DiskSegment*)&(_segmentPool[_diskImages[imageIndex].segmentsArrayStartIndex+segIndex]); + SegmentFixUpBuilder* builder = nullptr; + if ( diskSeg->paddingNotSeg ) + continue; + if ( diskSeg->filePageCount == 0 ) { + ++onDiskSegIndex; + continue; + } + if ( diskSeg->permissions & VM_PROT_WRITE ) { + builder = new SegmentFixUpBuilder(onDiskSegIndex, diskSeg->filePageCount, _pageSize, _is64, fixups, targetsForImage, opcodeLogging); + } + else if ( hasTextRelocs && (diskSeg->permissions == (VM_PROT_READ|VM_PROT_EXECUTE)) ) { + builder = new SegmentFixUpBuilder(onDiskSegIndex, diskSeg->filePageCount, _pageSize, _is64, fixups, targetsForImage, opcodeLogging); + } + if ( builder != nullptr ) { + if ( builder->hasFixups() ) + builders.push_back(builder); + else + delete builder; + } + ++onDiskSegIndex; + } + + // build AllFixupsBySegment for image + _fixupsPool.pad_to_size(4); + uint32_t startOfFixupsOffset = (uint32_t)_fixupsPool.size(); + size_t headerSize = builders.size() * sizeof(binary_format::AllFixupsBySegment); + size_t offsetOfSegmentHeaderInBuffer = _fixupsPool.size(); + for (int i=0; i < headerSize; ++i) { + _fixupsPool.append_byte(0); + } + uint32_t entryIndex = 0; + for (SegmentFixUpBuilder* builder : builders) { + binary_format::AllFixupsBySegment* entries = (binary_format::AllFixupsBySegment*)(_fixupsPool.start()+offsetOfSegmentHeaderInBuffer); + entries[entryIndex].segIndex = builder->segIndex(); + entries[entryIndex].offset = (uint32_t)_fixupsPool.size() - startOfFixupsOffset; + builder->appendSegmentFixUpMap(_fixupsPool); + delete builder; + ++entryIndex; + } + _diskImages[imageIndex].fixupsPoolOffset = (uint32_t)offsetOfSegmentHeaderInBuffer; + _diskImages[imageIndex].fixupsPoolSegCount = entryIndex; + + // append targetsForImage into group + size_t start = _targetsPool.size(); + size_t count = targetsForImage.size(); + _diskImages[imageIndex].targetsArrayStartIndex = (uint32_t)start; + _diskImages[imageIndex].targetsArrayCount = (uint32_t)count; + assert(_diskImages[imageIndex].targetsArrayStartIndex == start); + assert(_diskImages[imageIndex].targetsArrayCount == count); + _targetsPool.insert(_targetsPool.end(), targetsForImage.begin(), targetsForImage.end()); +} + + +} +} + + diff --git a/dyld3/LaunchCacheWriter.h b/dyld3/LaunchCacheWriter.h new file mode 100644 index 0000000..a00ea93 --- /dev/null +++ b/dyld3/LaunchCacheWriter.h @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2017 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 LaunchCacheWriter_h +#define LaunchCacheWriter_h + + +#include + +#include +#include +#include +#include + +#include "LaunchCacheFormat.h" +#include "LaunchCache.h" +#include "MachOParser.h" +#include "shared-cache/DyldSharedCache.h" + + +namespace dyld3 { +namespace launch_cache { + + + +class ContentBuffer { +private: + std::vector _data; +public: + std::vector& bytes() { return _data; } + unsigned long size() const { return _data.size(); } + void reserve(unsigned long l) { _data.reserve(l); } + const uint8_t* start() const { return &_data[0]; } + const uint8_t* end() const { return &_data[_data.size()]; } + + void append_uleb128(uint64_t value) { + uint8_t byte; + do { + byte = value & 0x7F; + value &= ~0x7F; + if ( value != 0 ) + byte |= 0x80; + _data.push_back(byte); + value = value >> 7; + } while( byte >= 0x80 ); + } + + void append_byte(uint8_t byte) { + _data.push_back(byte); + } + + void append_uint32(uint32_t value) { + for (int i=0; i < 4; ++i) { + _data.push_back(value & 0xFF); + value = (value >> 8); + } + } + + void append_uint64(uint64_t value) { + for (int i=0; i < 8; ++i) { + _data.push_back(value & 0xFF); + value = (value >> 8); + } + } + + void append_buffer(const ContentBuffer& value) { + _data.insert(_data.end(), value.start(), value.end()); + } + + static unsigned int uleb128_size(uint64_t value) { + uint32_t result = 0; + do { + value = value >> 7; + ++result; + } while ( value != 0 ); + return result; + } + + void pad_to_size(unsigned int alignment) { + while ( (_data.size() % alignment) != 0 ) + _data.push_back(0); + } +}; + +class ImageGroupWriter +{ +public: + ImageGroupWriter(uint32_t groupNum, bool pages16KB, bool is64, bool dylibsExpectedOnDisk, bool mtimeAndInodeAreValid); + + enum class FixupType { rebase, pointerBind, pointerLazyBind, bindText, bindTextRel, rebaseText, bindImportJmpRel, ignore }; + struct FixUp { + uint32_t segIndex; + uint64_t segOffset; + FixupType type; + TargetSymbolValue target; + }; + + uint32_t size() const; + void finalizeTo(Diagnostics& diag, const std::vector&, binary_format::ImageGroup* buffer) const; + uint32_t maxLoadCount(Diagnostics& diag, const std::vector&, binary_format::ImageGroup* buffer) const; + + bool isInvalid(uint32_t imageIndex) const; + + void setImageCount(uint32_t); + void setImageInvalid(uint32_t imageIndex); + void setImagePath(uint32_t imageIndex, const char* path); + void setImageUUID(uint32_t imageIndex, const uuid_t uuid); + void setImageHasObjC(uint32_t imageIndex, bool value); + void setImageIsBundle(uint32_t imageIndex, bool value); + void setImageHasWeakDefs(uint32_t imageIndex, bool value); + void setImageMayHavePlusLoads(uint32_t imageIndex, bool value); + void setImageNeverUnload(uint32_t imageIndex, bool); + void setImageMustBeThisDir(uint32_t imageIndex, bool value); + void setImageIsPlatformBinary(uint32_t imageIndex, bool value); + void setImageOverridableDylib(uint32_t imageIndex, bool value); + void setImageIsEncrypted(uint32_t imageIndex, bool value); + void setImageMaxLoadCount(uint32_t imageIndex, uint32_t count); + void setImageFairPlayRange(uint32_t imageIndex, uint32_t offset, uint32_t size); + void setImageInitializerOffsets(uint32_t imageIndex, const std::vector& offsets); + void setImageDOFOffsets(uint32_t imageIndex, const std::vector& offsets); + void setImageInitBefore(uint32_t imageIndex, const std::vector&); + void setImageSliceOffset(uint32_t imageIndex, uint64_t fileOffset); + void setImageFileMtimeAndInode(uint32_t imageIndex, uint64_t mTime, uint64_t inode); + void setImageCdHash(uint32_t imageIndex, uint8_t cdHash[20]); + void setImageCodeSignatureLocation(uint32_t imageIndex, uint32_t fileOffset, uint32_t size); + void setImageDependentsCount(uint32_t imageIndex, uint32_t count); + void setImageDependent(uint32_t imageIndex, uint32_t depIndex, binary_format::ImageRef dependent); + void setImageSegments(uint32_t imageIndex, MachOParser& imageParser, uint64_t cacheUnslideBaseAddress); + void setImageFixups(Diagnostics& diag, uint32_t imageIndex, std::vector& fixups, bool hasTextRelocs); + void addImageAliasPath(uint32_t imageIndex, const char* anAlias); + void setImagePatchLocations(uint32_t imageIndex, uint32_t funcOffset, const std::unordered_set& patchLocations); + void setGroupCacheOverrides(const std::vector& cacheOverrides); + void addImageIsOverride(binary_format::ImageRef replacer, binary_format::ImageRef replacee); + + uint32_t addIndirectGroupNum(uint32_t groupNum); + + uint32_t addString(const char* str); + void alignStringPool(); + + uint32_t imageDependentsCount(uint32_t imageIndex) const; + binary_format::ImageRef imageDependent(uint32_t imageIndex, uint32_t depIndex) const; + +private: + struct InitializerInfo { + std::vector offsetsInImage; + std::vector initBeforeImages; + }; + + uint32_t imageCount() const; + binary_format::Image& imageByIndex(uint32_t); + const binary_format::Image& imageByIndex(uint32_t) const; + std::vector makeFixupOpcodes(const FixUp* start, const FixUp* end, uint32_t pageStartSegmentOffset, std::map&); + void makeDataFixupMapAndOrdinalTable(std::vector& fixupMap, std::vector& ordinalTable); + void computeInitializerOrdering(uint32_t imageIndex); + uint32_t addUniqueInitList(const std::vector& initBefore); + void layoutBinary(binary_format::ImageGroup* grp) const; + + const bool _isDiskImage; + const bool _is64; + const uint16_t _groupNum; + const uint32_t _pageSize; + bool _dylibsExpectedOnDisk; + bool _imageFileInfoIsCdHash; + std::vector _images; + std::vector _diskImages; + std::vector _aliases; + std::vector _segmentPool; + std::vector _dependentsPool; + std::vector _initializerOffsets; + std::vector _initializerBeforeLists; + std::vector _dofOffsets; + std::vector _targetsPool; + ContentBuffer _fixupsPool; + std::vector _patchPool; + std::vector _patchLocationPool; + std::vector_dyldCacheSymbolOverridePool; + std::vector _imageOverridePool; + std::vector _indirectGroupNumPool; + std::unordered_map _indirectGroupNumPoolExisting; + std::vector _stringPool; + std::unordered_map _stringPoolExisting; +}; + + + +} // namespace launch_cache +} // namespace dyld3 + + +#endif // LaunchCacheWriter_h + + diff --git a/dyld3/Loading.cpp b/dyld3/Loading.cpp new file mode 100644 index 0000000..50785b5 --- /dev/null +++ b/dyld3/Loading.cpp @@ -0,0 +1,804 @@ +/* + * Copyright (c) 2017 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 "LaunchCache.h" +#include "LaunchCacheFormat.h" +#include "Logging.h" +#include "Loading.h" +#include "MachOParser.h" +#include "dyld.h" +#include "dyld_cache_format.h" + +extern "C" { + #include "closuredProtocol.h" +} + +namespace dyld { + void log(const char* m, ...); +} + +namespace dyld3 { +namespace loader { + +#if DYLD_IN_PROCESS + +static bool sandboxBlocked(const char* path, const char* kind) +{ +#if BUILDING_LIBDYLD || !TARGET_IPHONE_SIMULATOR + sandbox_filter_type filter = (sandbox_filter_type)(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT); + return ( sandbox_check(getpid(), kind, filter, path) > 0 ); +#else + // sandbox calls not yet supported in dyld_sim + return false; +#endif +} + +static bool sandboxBlockedMmap(const char* path) +{ + return sandboxBlocked(path, "file-map-executable"); +} + +static bool sandboxBlockedOpen(const char* path) +{ + return sandboxBlocked(path, "file-read-data"); +} + +static bool sandboxBlockedStat(const char* path) +{ + return sandboxBlocked(path, "file-read-metadata"); +} + +#if TARGET_OS_WATCH +static uint64_t pageAlign(uint64_t value) +{ + return (value + 4095) & (-4096); +} +#endif + +static void updateSliceOffset(uint64_t& sliceOffset, uint64_t codeSignEndOffset, size_t fileLen) +{ +#if TARGET_OS_WATCH + if ( sliceOffset != 0 ) { + if ( pageAlign(codeSignEndOffset) == pageAlign(fileLen) ) { + // cache builder saw fat file, but file is now thin + sliceOffset = 0; + return; + } + } +#endif +} + +static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagnostics& diag, LogFunc log_loads, LogFunc log_segments) +{ + uint64_t sliceOffset = image.sliceOffsetInFile(); + const uint64_t totalVMSize = image.vmSizeToMap(); + const uint32_t codeSignFileOffset = image.asDiskImage()->codeSignFileOffset; + const uint32_t codeSignFileSize = image.asDiskImage()->codeSignFileSize; + + // open file + int fd = ::open(image.path(), O_RDONLY, 0); + if ( fd == -1 ) { + int openErr = errno; + if ( (openErr == EPERM) && sandboxBlockedOpen(image.path()) ) + diag.error("file system sandbox blocked open(\"%s\", O_RDONLY)", image.path()); + else + diag.error("open(\"%s\", O_RDONLY) failed with errno=%d", image.path(), openErr); + return nullptr; + } + + // get file info + struct stat statBuf; +#if TARGET_IPHONE_SIMULATOR + if ( stat(image.path(), &statBuf) != 0 ) { +#else + if ( fstat(fd, &statBuf) != 0 ) { +#endif + int statErr = errno; + if ( (statErr == EPERM) && sandboxBlockedStat(image.path()) ) + diag.error("file system sandbox blocked stat(\"%s\")", image.path()); + else + diag.error("stat(\"%s\") failed with errno=%d", image.path(), statErr); + close(fd); + return nullptr; + } + + // verify file has not changed since closure was built + if ( image.validateUsingModTimeAndInode() ) { + if ( (statBuf.st_mtime != image.fileModTime()) || (statBuf.st_ino != image.fileINode()) ) { + diag.error("file mtime/inode changed since closure was built for '%s'", image.path()); + close(fd); + return nullptr; + } + } + + // handle OS dylibs being thinned after closure was built + if ( image.group().groupNum() == 1 ) + updateSliceOffset(sliceOffset, codeSignFileOffset+codeSignFileSize, (size_t)statBuf.st_size); + + // register code signature + uint64_t coveredCodeLength = UINT64_MAX; + if ( codeSignFileOffset != 0 ) { + fsignatures_t siginfo; + siginfo.fs_file_start = sliceOffset; // start of mach-o slice in fat file + siginfo.fs_blob_start = (void*)(long)(codeSignFileOffset); // start of CD in mach-o file + siginfo.fs_blob_size = codeSignFileSize; // size of CD + int result = fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo); + if ( result == -1 ) { + int errnoCopy = errno; + if ( (errnoCopy == EPERM) || (errnoCopy == EBADEXEC) ) { + diag.error("code signature invalid (errno=%d) sliceOffset=0x%08llX, codeBlobOffset=0x%08X, codeBlobSize=0x%08X for '%s'", + errnoCopy, sliceOffset, codeSignFileOffset, codeSignFileSize, image.path()); + } + else { + diag.error("fcntl(fd, F_ADDFILESIGS_RETURN) failed with errno=%d, sliceOffset=0x%08llX, codeBlobOffset=0x%08X, codeBlobSize=0x%08X for '%s'", + errnoCopy, sliceOffset, codeSignFileOffset, codeSignFileSize, image.path()); + } + close(fd); + return nullptr; + } + coveredCodeLength = siginfo.fs_file_start; + if ( coveredCodeLength < image.asDiskImage()->codeSignFileOffset ) { + diag.error("code signature does not cover entire file up to signature"); + close(fd); + return nullptr; + } + + // always call F_CHECK_LV to preflight + fchecklv checkInfo; + char messageBuffer[512]; + messageBuffer[0] = '\0'; + checkInfo.lv_file_start = sliceOffset; + checkInfo.lv_error_message_size = sizeof(messageBuffer); + checkInfo.lv_error_message = messageBuffer; + int res = fcntl(fd, F_CHECK_LV, &checkInfo); + if ( res == -1 ) { + diag.error("code signature in (%s) not valid for use in process: %s", image.path(), messageBuffer); + close(fd); + return nullptr; + } + } + + // reserve address range + vm_address_t loadAddress = 0; + kern_return_t r = vm_allocate(mach_task_self(), &loadAddress, (vm_size_t)totalVMSize, VM_FLAGS_ANYWHERE); + if ( r != KERN_SUCCESS ) { + diag.error("vm_allocate(size=0x%0llX) failed with result=%d", totalVMSize, r); + close(fd); + return nullptr; + } + + if ( sliceOffset != 0 ) + log_segments("dyld: Mapping %s (slice offset=%llu)\n", image.path(), sliceOffset); + else + log_segments("dyld: Mapping %s\n", image.path()); + + // map each segment + __block bool mmapFailure = false; + __block const uint8_t* codeSignatureStartAddress = nullptr; + __block const uint8_t* linkeditEndAddress = nullptr; + __block bool mappedFirstSegment = false; + image.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop) { + // Mapping zero filled segments fails with mmap of size 0 + if ( fileSize == 0 ) + return; + void* segAddress = mmap((void*)(loadAddress+vmOffset), fileSize, permissions, MAP_FIXED | MAP_PRIVATE, fd, sliceOffset+fileOffset); + int mmapErr = errno; + if ( segAddress == MAP_FAILED ) { + if ( mmapErr == EPERM ) { + if ( sandboxBlockedMmap(image.path()) ) + diag.error("file system sandbox blocked mmap() of '%s'", image.path()); + else + diag.error("code signing blocked mmap() of '%s'", image.path()); + } + else { + diag.error("mmap(addr=0x%0llX, size=0x%08X) failed with errno=%d for %s", loadAddress+vmOffset, fileSize, mmapErr, image.path()); + } + mmapFailure = true; + stop = true; + } + else if ( codeSignFileOffset > fileOffset ) { + codeSignatureStartAddress = (uint8_t*)segAddress + (codeSignFileOffset-fileOffset); + linkeditEndAddress = (uint8_t*)segAddress + vmSize; + } + // sanity check first segment is mach-o header + if ( (segAddress != MAP_FAILED) && !mappedFirstSegment ) { + mappedFirstSegment = true; + if ( !MachOParser::isMachO(diag, segAddress, fileSize) ) { + mmapFailure = true; + stop = true; + } + } + if ( !mmapFailure ) { + MachOParser parser((mach_header*)loadAddress); + log_segments("%14s (%c%c%c) 0x%012lX->0x%012lX \n", parser.segmentName(segIndex), + (permissions & PROT_READ) ? 'r' : '.', (permissions & PROT_WRITE) ? 'w' : '.', (permissions & PROT_EXEC) ? 'x' : '.' , + (long)segAddress, (long)segAddress+vmSize-1); + } + }); + if ( mmapFailure ) { + vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize); + close(fd); + return nullptr; + } + + // close file + close(fd); + + #if BUILDING_LIBDYLD + // verify file has not changed since closure was built by checking code signature has not changed + if ( image.validateUsingCdHash() ) { + if ( codeSignatureStartAddress == nullptr ) { + diag.error("code signature missing"); + } + else if ( codeSignatureStartAddress+codeSignFileSize > linkeditEndAddress ) { + diag.error("code signature extends beyond end of __LINKEDIT"); + } + else { + uint8_t cdHash[20]; + if ( MachOParser::cdHashOfCodeSignature(codeSignatureStartAddress, codeSignFileSize, cdHash) ) { + if ( memcmp(image.cdHash16(), cdHash, 16) != 0 ) + diag.error("code signature changed since closure was built"); + } + else{ + diag.error("code signature format invalid"); + } + } + if ( diag.hasError() ) { + vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize); + return nullptr; + } + } +#endif + +#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR + // tell kernel about fairplay encrypted regions + uint32_t fpTextOffset; + uint32_t fpSize; + if ( image.isFairPlayEncrypted(fpTextOffset, fpSize) ) { + const mach_header* mh = (mach_header*)loadAddress; + int result = mremap_encrypted(((uint8_t*)mh) + fpTextOffset, fpSize, 1, mh->cputype, mh->cpusubtype); + diag.error("could not register fairplay decryption, mremap_encrypted() => %d", result); + vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize); + return nullptr; + } +#endif + + log_loads("dyld: load %s\n", image.path()); + + return (mach_header*)loadAddress; +} + + +void unmapImage(const launch_cache::binary_format::Image* binImage, const mach_header* loadAddress) +{ + assert(loadAddress != nullptr); + launch_cache::Image image(binImage); + vm_deallocate(mach_task_self(), (vm_address_t)loadAddress, (vm_size_t)(image.vmSizeToMap())); +} + + +static void applyFixupsToImage(Diagnostics& diag, const mach_header* imageMH, const launch_cache::binary_format::Image* imageData, + launch_cache::TargetSymbolValue::LoadedImages& imageResolver, LogFunc log_fixups) +{ + launch_cache::Image image(imageData); + MachOParser imageParser(imageMH); + // Note, these are cached here to avoid recalculating them on every loop iteration + const launch_cache::ImageGroup& imageGroup = image.group(); + const char* leafName = image.leafName(); + intptr_t slide = imageParser.getSlide(); + image.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t protections, bool& segStop) { + if ( !image.segmentHasFixups(segIndex) ) + return; + const launch_cache::MemoryRange segContent = { (char*)imageMH + vmOffset, vmSize }; + #if __i386__ + bool textRelocs = ((protections & VM_PROT_WRITE) == 0); + if ( textRelocs ) { + kern_return_t r = vm_protect(mach_task_self(), (vm_address_t)segContent.address, (vm_size_t)segContent.size, false, VM_PROT_WRITE | VM_PROT_READ); + if ( r != KERN_SUCCESS ) { + diag.error("vm_protect() failed trying to make text segment writable, result=%d", r); + return; + } + } + #else + if ( (protections & VM_PROT_WRITE) == 0 ) { + diag.error("fixups found in non-writable segment of %s", image.path()); + return; + } + #endif + image.forEachFixup(segIndex, segContent, ^(uint64_t segOffset, launch_cache::Image::FixupKind kind, launch_cache::TargetSymbolValue targetValue, bool& stop) { + if ( segOffset > segContent.size ) { + diag.error("fixup is past end of segment. segOffset=0x%0llX, segSize=0x%0llX, segIndex=%d", segOffset, segContent.size, segIndex); + stop = true; + return; + } + uintptr_t* fixUpLoc = (uintptr_t*)((char*)(segContent.address) + segOffset); + uintptr_t value; + #if __i386__ + uint32_t rel32; + uint8_t* jumpSlot; + #endif + //dyld::log("fixup loc=%p\n", fixUpLoc); + switch ( kind ) { + #if __LP64__ + case launch_cache::Image::FixupKind::rebase64: + #else + case launch_cache::Image::FixupKind::rebase32: + #endif + *fixUpLoc += slide; + log_fixups("dyld: fixup: %s:%p += %p\n", leafName, fixUpLoc, (void*)slide); + break; + #if __LP64__ + case launch_cache::Image::FixupKind::bind64: + #else + case launch_cache::Image::FixupKind::bind32: + #endif + value = targetValue.resolveTarget(diag, imageGroup, imageResolver); + log_fixups("dyld: fixup: %s:%p = %p\n", leafName, fixUpLoc, (void*)value); + *fixUpLoc = value; + break; + #if __i386__ + case launch_cache::Image::FixupKind::rebaseText32: + log_fixups("dyld: text fixup: %s:%p += %p\n", leafName, fixUpLoc, (void*)slide); + *fixUpLoc += slide; + break; + case launch_cache::Image::FixupKind::bindText32: + value = targetValue.resolveTarget(diag, imageGroup, imageResolver); + log_fixups("dyld: text fixup: %s:%p = %p\n", leafName, fixUpLoc, (void*)value); + *fixUpLoc = value; + break; + case launch_cache::Image::FixupKind::bindTextRel32: + // CALL instruction uses pc-rel value + value = targetValue.resolveTarget(diag, imageGroup, imageResolver); + log_fixups("dyld: CALL fixup: %s:%p = %p (pc+0x%08X)\n", leafName, fixUpLoc, (void*)value, (value - (uintptr_t)(fixUpLoc))); + *fixUpLoc = (value - (uintptr_t)(fixUpLoc)); + break; + case launch_cache::Image::FixupKind::bindImportJmp32: + // JMP instruction in __IMPORT segment uses pc-rel value + jumpSlot = (uint8_t*)fixUpLoc; + value = targetValue.resolveTarget(diag, imageGroup, imageResolver); + rel32 = (value - ((uintptr_t)(fixUpLoc)+5)); + log_fixups("dyld: JMP fixup: %s:%p = %p (pc+0x%08X)\n", leafName, fixUpLoc, (void*)value, rel32); + jumpSlot[0] = 0xE9; // JMP rel32 + jumpSlot[1] = rel32 & 0xFF; + jumpSlot[2] = (rel32 >> 8) & 0xFF; + jumpSlot[3] = (rel32 >> 16) & 0xFF; + jumpSlot[4] = (rel32 >> 24) & 0xFF; + break; + #endif + default: + diag.error("unknown fixup kind %d", kind); + } + if ( diag.hasError() ) + stop = true; + }); + #if __i386__ + if ( textRelocs ) { + kern_return_t r = vm_protect(mach_task_self(), (vm_address_t)segContent.address, (vm_size_t)segContent.size, false, protections); + if ( r != KERN_SUCCESS ) { + diag.error("vm_protect() failed trying to make text segment non-writable, result=%d", r); + return; + } + } + #endif + }); +} + + + +class VIS_HIDDEN CurrentLoadImages : public launch_cache::TargetSymbolValue::LoadedImages +{ +public: + CurrentLoadImages(launch_cache::DynArray& images, const uint8_t* cacheAddr) + : _dyldCacheLoadAddress(cacheAddr), _images(images) { } + + virtual const uint8_t* dyldCacheLoadAddressForImage(); + virtual const mach_header* loadAddressFromGroupAndIndex(uint32_t groupNum, uint32_t indexInGroup); + virtual void forEachImage(void (^handler)(uint32_t anIndex, const launch_cache::binary_format::Image*, const mach_header*, bool& stop)); + virtual void setAsNeverUnload(uint32_t anIndex) { _images[anIndex].neverUnload = true; } +private: + const uint8_t* _dyldCacheLoadAddress; + launch_cache::DynArray& _images; +}; + +const uint8_t* CurrentLoadImages::dyldCacheLoadAddressForImage() +{ + return _dyldCacheLoadAddress; +} + +const mach_header* CurrentLoadImages::loadAddressFromGroupAndIndex(uint32_t groupNum, uint32_t indexInGroup) +{ + __block const mach_header* result = nullptr; + forEachImage(^(uint32_t anIndex, const launch_cache::binary_format::Image* imageData, const mach_header* mh, bool& stop) { + launch_cache::Image image(imageData); + launch_cache::ImageGroup imageGroup = image.group(); + if ( imageGroup.groupNum() != groupNum ) + return; + if ( imageGroup.indexInGroup(imageData) == indexInGroup ) { + result = mh; + stop = true; + } + }); + return result; +} + +void CurrentLoadImages::forEachImage(void (^handler)(uint32_t anIndex, const launch_cache::binary_format::Image*, const mach_header*, bool& stop)) +{ + bool stop = false; + for (int i=0; i < _images.count(); ++i) { + ImageInfo& info = _images[i]; + handler(i, info.imageData, info.loadAddress, stop); + if ( stop ) + break; + } +} + +struct DOFInfo { + const void* dof; + const mach_header* imageHeader; + const char* imageShortName; +}; + +static void registerDOFs(const DOFInfo* dofs, uint32_t dofSectionCount, LogFunc log_dofs) +{ + if ( dofSectionCount != 0 ) { + int fd = open("/dev/" DTRACEMNR_HELPER, O_RDWR); + if ( fd < 0 ) { + log_dofs("can't open /dev/" DTRACEMNR_HELPER " to register dtrace DOF sections\n"); + } + else { + // allocate a buffer on the stack for the variable length dof_ioctl_data_t type + uint8_t buffer[sizeof(dof_ioctl_data_t) + dofSectionCount*sizeof(dof_helper_t)]; + dof_ioctl_data_t* ioctlData = (dof_ioctl_data_t*)buffer; + + // fill in buffer with one dof_helper_t per DOF section + ioctlData->dofiod_count = dofSectionCount; + for (unsigned int i=0; i < dofSectionCount; ++i) { + strlcpy(ioctlData->dofiod_helpers[i].dofhp_mod, dofs[i].imageShortName, DTRACE_MODNAMELEN); + ioctlData->dofiod_helpers[i].dofhp_dof = (uintptr_t)(dofs[i].dof); + ioctlData->dofiod_helpers[i].dofhp_addr = (uintptr_t)(dofs[i].dof); + } + + // tell kernel about all DOF sections en mas + // pass pointer to ioctlData because ioctl() only copies a fixed size amount of data into kernel + user_addr_t val = (user_addr_t)(unsigned long)ioctlData; + if ( ioctl(fd, DTRACEHIOC_ADDDOF, &val) != -1 ) { + // kernel returns a unique identifier for each section in the dofiod_helpers[].dofhp_dof field. + // Note, the closure marked the image as being never unload, so we don't need to keep the ID around + // or support unregistering it later. + for (unsigned int i=0; i < dofSectionCount; ++i) { + log_dofs("dyld: registering DOF section %p in %s with dtrace, ID=0x%08X\n", + dofs[i].dof, dofs[i].imageShortName, (int)(ioctlData->dofiod_helpers[i].dofhp_dof)); + } + } + else { + //dyld::log( "dyld: ioctl to register dtrace DOF section failed\n"); + } + close(fd); + } + } +} + + +void mapAndFixupImages(Diagnostics& diag, launch_cache::DynArray& images, const uint8_t* cacheLoadAddress, + LogFunc log_loads, LogFunc log_segments, LogFunc log_fixups, LogFunc log_dofs) +{ + // scan array and map images not already loaded + for (int i=0; i < images.count(); ++i) { + ImageInfo& info = images[i]; + const dyld3::launch_cache::Image image(info.imageData); + if ( info.loadAddress != nullptr ) { + // log main executable's segments + if ( (info.groupNum == 2) && (info.loadAddress->filetype == MH_EXECUTE) && !info.previouslyFixedUp ) { + if ( log_segments("dyld: mapped by kernel %s\n", image.path()) ) { + MachOParser parser(info.loadAddress); + image.forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop) { + uint64_t start = (long)info.loadAddress + vmOffset; + uint64_t end = start+vmSize-1; + if ( (segIndex == 0) && (permissions == 0) ) { + start = 0; + } + log_segments("%14s (%c%c%c) 0x%012llX->0x%012llX \n", parser.segmentName(segIndex), + (permissions & PROT_READ) ? 'r' : '.', (permissions & PROT_WRITE) ? 'w' : '.', (permissions & PROT_EXEC) ? 'x' : '.' , + start, end); + }); + } + } + // skip over ones already loaded + continue; + } + if ( image.isDiskImage() ) { + //dyld::log("need to load image[%d] %s\n", i, image.path()); + info.loadAddress = mapImage(image, diag, log_loads, log_segments); + if ( diag.hasError() ) { + break; // out of for loop + } + info.justMapped = true; + } + else { + bool expectedOnDisk = image.group().dylibsExpectedOnDisk(); + bool overridableDylib = image.overridableDylib(); + if ( expectedOnDisk || overridableDylib ) { + struct stat statBuf; + if ( ::stat(image.path(), &statBuf) == 0 ) { + if ( expectedOnDisk ) { + // macOS case: verify dylib file info matches what it was when cache was built + if ( image.fileModTime() != statBuf.st_mtime ) { + diag.error("cached dylib mod-time has changed, dylib cache has: 0x%08llX, file has: 0x%08lX, for: %s", image.fileModTime(), (long)statBuf.st_mtime, image.path()); + break; // out of for loop + } + if ( image.fileINode() != statBuf.st_ino ) { + diag.error("cached dylib inode has changed, dylib cache has: 0x%08llX, file has: 0x%08llX, for: %s", image.fileINode(), statBuf.st_ino, image.path()); + break; // out of for loop + } + } + else { + // iOS internal: dylib override installed + diag.error("cached dylib overridden: %s", image.path()); + break; // out of for loop + } + } + else { + if ( expectedOnDisk ) { + // macOS case: dylib that existed when cache built no longer exists + diag.error("missing cached dylib: %s", image.path()); + break; // out of for loop + } + } + } + info.loadAddress = (mach_header*)(cacheLoadAddress + image.cacheOffset()); + info.justUsedFromDyldCache = true; + if ( log_segments("dyld: Using from dyld cache %s\n", image.path()) ) { + MachOParser parser(info.loadAddress); + image.forEachCacheSegment(^(uint32_t segIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool &stop) { + log_segments("%14s (%c%c%c) 0x%012lX->0x%012lX \n", parser.segmentName(segIndex), + (permissions & PROT_READ) ? 'r' : '.', (permissions & PROT_WRITE) ? 'w' : '.', (permissions & PROT_EXEC) ? 'x' : '.' , + (long)cacheLoadAddress+vmOffset, (long)cacheLoadAddress+vmOffset+vmSize-1); + }); + } + } + } + if ( diag.hasError() ) { + // back out and unmapped images all loaded so far + for (uint32_t j=0; j < images.count(); ++j) { + ImageInfo& anInfo = images[j]; + if ( anInfo.justMapped ) + unmapImage(anInfo.imageData, anInfo.loadAddress); + anInfo.loadAddress = nullptr; + } + return; + } + + // apply fixups + CurrentLoadImages fixupHelper(images, cacheLoadAddress); + for (int i=0; i < images.count(); ++i) { + ImageInfo& info = images[i]; + // images in shared cache do not need fixups applied + launch_cache::Image image(info.imageData); + if ( !image.isDiskImage() ) + continue; + // previously loaded images were previously fixed up + if ( info.previouslyFixedUp ) + continue; + //dyld::log("apply fixups to mh=%p, path=%s\n", info.loadAddress, Image(info.imageData).path()); + dyld3::loader::applyFixupsToImage(diag, info.loadAddress, info.imageData, fixupHelper, log_fixups); + if ( diag.hasError() ) + break; + } + + // Record dtrace DOFs + // if ( /* FIXME! register dofs */ ) + { + __block uint32_t dofCount = 0; + for (int i=0; i < images.count(); ++i) { + ImageInfo& info = images[i]; + launch_cache::Image image(info.imageData); + // previously loaded images were previously fixed up + if ( info.previouslyFixedUp ) + continue; + image.forEachDOF(nullptr, ^(const void* section) { + // DOFs cause the image to be never-unload + assert(image.neverUnload()); + ++dofCount; + }); + } + + // struct RegisteredDOF { const mach_header* mh; int registrationID; }; + DOFInfo dofImages[dofCount]; + __block DOFInfo* dofImagesBase = dofImages; + dofCount = 0; + for (int i=0; i < images.count(); ++i) { + ImageInfo& info = images[i]; + launch_cache::Image image(info.imageData); + // previously loaded images were previously fixed up + if ( info.previouslyFixedUp ) + continue; + image.forEachDOF(info.loadAddress, ^(const void* section) { + DOFInfo dofInfo; + dofInfo.dof = section; + dofInfo.imageHeader = info.loadAddress; + dofInfo.imageShortName = image.leafName(); + dofImagesBase[dofCount++] = dofInfo; + }); + } + registerDOFs(dofImages, dofCount, log_dofs); + } +} + +#if BUILDING_DYLD +void forEachLineInFile(const char* path, void (^lineHandler)(const char* line, bool& stop)) +{ + int fd = dyld::my_open(path, O_RDONLY, 0); + if ( fd != -1 ) { + struct stat statBuf; + if ( fstat(fd, &statBuf) == 0 ) { + const char* lines = (const char*)mmap(nullptr, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if ( lines != MAP_FAILED ) { + bool stop = false; + const char* const eof = &lines[statBuf.st_size]; + for (const char* s = lines; s < eof; ++s) { + char lineBuffer[MAXPATHLEN]; + char* t = lineBuffer; + char* tEnd = &lineBuffer[MAXPATHLEN]; + while ( (s < eof) && (t != tEnd) ) { + if ( *s == '\n' ) + break; + *t++ = *s++; + } + *t = '\0'; + lineHandler(lineBuffer, stop); + if ( stop ) + break; + } + munmap((void*)lines, (size_t)statBuf.st_size); + } + } + close(fd); + } +} + + +bool internalInstall() +{ +#if TARGET_IPHONE_SIMULATOR + return false; +#elif __IPHONE_OS_VERSION_MIN_REQUIRED + uint32_t devFlags = *((uint32_t*)_COMM_PAGE_DEV_FIRM); + return ( (devFlags & 1) == 1 ); +#else + return ( csr_check(CSR_ALLOW_APPLE_INTERNAL) == 0 ); +#endif +} + +/* Checks to see if there are any args that impact dyld. These args + * can be set sevaral ways. These will only be honored on development + * and Apple Internal builds. + * + * First the existence of a file is checked for: + * /S/L/C/com.apple.dyld/dyld-bootargs + * If it exists it will be mapped and scanned line by line. If the executable + * exists in the file then the arguments on its line will be applied. "*" may + * be used a wildcard to represent all apps. First matching line will be used, + * the wild card must be one the last line. Additionally, lines must end with + * a "\n" + * + * + * SAMPLE FILE: + + /bin/ls:force_dyld2=1 + /usr/bin/sw_vers:force_dyld2=1 +*:force_dyld3=1 +EOL + + If no file exists then the kernel boot-args will be scanned. + */ +bool bootArgsContains(const char* arg) +{ + //FIXME: Use strnstr(). Unfortunately we are missing an imp libc. +#if TARGET_IPHONE_SIMULATOR + return false; +#else + // don't check for boot-args on customer installs + if ( !internalInstall() ) + return false; + + char pathBuffer[MAXPATHLEN+1]; +#if __IPHONE_OS_VERSION_MIN_REQUIRED + strlcpy(pathBuffer, IPHONE_DYLD_SHARED_CACHE_DIR, sizeof(IPHONE_DYLD_SHARED_CACHE_DIR)); +#else + strlcpy(pathBuffer, MACOSX_DYLD_SHARED_CACHE_DIR, sizeof(MACOSX_DYLD_SHARED_CACHE_DIR)); +#endif + strlcat(pathBuffer, "dyld-bootargs", MAXPATHLEN+1); + __block bool result = false; + forEachLineInFile(pathBuffer, ^(const char* line, bool& stop) { + const char* delim = strchr(line, ':'); + if ( delim == nullptr ) + return; + char binary[MAXPATHLEN]; + char options[MAXPATHLEN]; + strlcpy(binary, line, MAXPATHLEN); + binary[delim-line] = '\0'; + strlcpy(options, delim+1, MAXPATHLEN); + if ( (strcmp(dyld::getExecutablePath(), binary) == 0) || (strcmp("*", binary) == 0) ) { + result = (strstr(options, arg) != nullptr); + return; + } + }); + + // get length of full boot-args string + size_t len; + if ( sysctlbyname("kern.bootargs", NULL, &len, NULL, 0) != 0 ) + return false; + + // get copy of boot-args string + char bootArgsBuffer[len]; + if ( sysctlbyname("kern.bootargs", bootArgsBuffer, &len, NULL, 0) != 0 ) + return false; + + // return true if 'arg' is a sub-string of boot-args + return (strstr(bootArgsBuffer, arg) != nullptr); +#endif +} +#endif + +#if BUILDING_LIBDYLD +// hack because libdyld.dylib should not link with libc++.dylib +extern "C" void __cxa_pure_virtual() __attribute__((visibility("hidden"))); +void __cxa_pure_virtual() +{ + abort(); +} +#endif + + +#endif // DYLD_IN_PROCESS + +} // namespace loader +} // namespace dyld3 + + + + + diff --git a/dyld3/Loading.h b/dyld3/Loading.h new file mode 100644 index 0000000..177543b --- /dev/null +++ b/dyld3/Loading.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 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_LOADING_H__ +#define __DYLD_LOADING_H__ + +#include +#include +#include +#include <_simple.h> +#include "LaunchCache.h" +#include "LaunchCacheFormat.h" +#include "MachOParser.h" +#include "ClosureBuffer.h" + + + +namespace dyld3 { + +ClosureBuffer closured_CreateImageGroup(const ClosureBuffer& input); + +namespace loader { + +struct ImageInfo +{ + const launch_cache::binary_format::Image* imageData; + const mach_header* loadAddress; + uint32_t groupNum; + uint32_t indexInGroup; + bool previouslyFixedUp; + bool justMapped; + bool justUsedFromDyldCache; + bool neverUnload; +}; + + +#if DYLD_IN_PROCESS + +typedef bool (*LogFunc)(const char*, ...) __attribute__((format(printf, 1, 2))); + +void mapAndFixupImages(Diagnostics& diag, launch_cache::DynArray& images, const uint8_t* cacheLoadAddress, + LogFunc log_loads, LogFunc log_segments, LogFunc log_fixups, LogFunc log_dofs) VIS_HIDDEN; + + +void unmapImage(const launch_cache::binary_format::Image* image, const mach_header* loadAddress) VIS_HIDDEN; + +#if BUILDING_DYLD +bool bootArgsContains(const char* arg) VIS_HIDDEN; +bool internalInstall(); +void forEachLineInFile(const char* path, void (^lineHandler)(const char* line, bool& stop)); +#endif +#endif + +} // namespace loader +} // namespace dyld3 + + +#endif // __DYLD_LOADING_H__ + + diff --git a/dyld3/Logging.cpp b/dyld3/Logging.cpp new file mode 100644 index 0000000..593f3c7 --- /dev/null +++ b/dyld3/Logging.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2017 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 <_simple.h> + +#include "Logging.h" + + +namespace dyld3 { + + +static bool sVerboseLoading = false; +static bool sVerboseInitializers = false; +static bool sVerboseSegments = false; +static bool sVerboseAPIs = false; +static bool sVerboseNotifications = false; +static bool sVerboseFixups = false; +static bool sVerboseDOFs = false; + +static void vlog_default(const char* format, va_list list) +{ + _simple_vdprintf(2, format, list); +} + +static void (*sLogFunction)(const char* format, va_list list) = &vlog_default; +static void (*sHaltFunction)(const char* message) __attribute__((noreturn)) = nullptr; + +void halt(const char* message) +{ + (*sHaltFunction)(message); +} + +static void vlog(const char* format, va_list list) +{ + (*sLogFunction)(format, list); +} + +bool log_loads(const char* format, ...) +{ + if ( !sVerboseLoading ) + return false; + va_list list; + va_start(list, format); + vlog(format, list); + va_end(list); + return true; +} + +bool log_segments(const char* format, ...) +{ + if ( !sVerboseSegments ) + return false; + va_list list; + va_start(list, format); + vlog(format, list); + va_end(list); + return true; +} + +bool log_fixups(const char* format, ...) +{ + if ( !sVerboseFixups ) + return false; + va_list list; + va_start(list, format); + vlog(format, list); + va_end(list); + return true; +} + +bool log_initializers(const char* format, ...) +{ + if ( !sVerboseInitializers ) + return false; + va_list list; + va_start(list, format); + vlog(format, list); + va_end(list); + return true; +} + +bool log_apis(const char* format, ...) +{ + if ( !sVerboseAPIs ) + return false; + va_list list; + va_start(list, format); + vlog(format, list); + va_end(list); + return true; +} + +bool log_notifications(const char* format, ...) +{ + if ( !sVerboseNotifications ) + return false; + va_list list; + va_start(list, format); + vlog(format, list); + va_end(list); + return true; +} + +bool log_dofs(const char* format, ...) +{ + if ( !sVerboseDOFs ) + return false; + va_list list; + va_start(list, format); + vlog(format, list); + va_end(list); + return true; +} + + + +void setLoggingFromEnvs(const char* envp[]) +{ + for(const char** p = envp; *p != NULL; p++) { + const char* keyEqualsValue = *p; + const char* equals = strchr(keyEqualsValue, '='); + if ( equals != NULL ) { + //const char* value = &equals[1]; + const size_t keyLen = equals-keyEqualsValue; + char key[keyLen+1]; + strncpy(key, keyEqualsValue, keyLen); + key[keyLen] = '\0'; + if ( strcmp(key, "DYLD_PRINT_LIBRARIES") == 0 ) { + sVerboseLoading = true; + } + else if ( strcmp(key, "DYLD_PRINT_SEGMENTS") == 0 ) { + sVerboseSegments = true; + } + else if ( strcmp(key, "DYLD_PRINT_INITIALIZERS") == 0 ) { + sVerboseInitializers = true; + } + else if ( strcmp(key, "DYLD_PRINT_APIS") == 0 ) { + sVerboseAPIs = true; + } + else if ( strcmp(key, "DYLD_PRINT_NOTIFICATIONS") == 0 ) { + sVerboseNotifications = true; + } + else if ( strcmp(key, "DYLD_PRINT_BINDINGS") == 0 ) { + sVerboseFixups = true; + } + else if ( strcmp(key, "DYLD_PRINT_DOFS") == 0 ) { + sVerboseDOFs = true; + } + } + } +} + +void setLoggingFunction(void (*func)(const char* format, va_list list)) +{ + sLogFunction = func; +} + +void setHaltFunction(void (*func)(const char* message) __attribute__((noreturn))) +{ + sHaltFunction = func; +} + + +} // namespace dyld3 + diff --git a/dyld3/Logging.h b/dyld3/Logging.h new file mode 100644 index 0000000..f6b82fb --- /dev/null +++ b/dyld3/Logging.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017 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_LOGGING_H__ +#define __DYLD_LOGGING_H__ + +#include +#include +#include + +#define VIS_HIDDEN __attribute__((visibility("hidden"))) + + +namespace dyld3 { + + +bool log_loads(const char* format, ...) __attribute__((format(printf, 1, 2))) VIS_HIDDEN; +bool log_apis(const char* format, ...) __attribute__((format(printf, 1, 2))) VIS_HIDDEN; +bool log_segments(const char* format, ...) __attribute__((format(printf, 1, 2))) VIS_HIDDEN; +bool log_initializers(const char* format, ...) __attribute__((format(printf, 1, 2))) VIS_HIDDEN; +bool log_fixups(const char* format, ...) __attribute__((format(printf, 1, 2))) VIS_HIDDEN; +bool log_notifications(const char* format, ...) __attribute__((format(printf, 1, 2))) VIS_HIDDEN; +bool log_dofs(const char* format, ...) __attribute__((format(printf, 1, 2))) VIS_HIDDEN; + +void halt(const char* message) __attribute((noreturn)) VIS_HIDDEN ; + + +// only called during libdyld set up +void setLoggingFromEnvs(const char* envp[]) VIS_HIDDEN ; +void setLoggingFunction(void (*func)(const char* format, va_list list)) VIS_HIDDEN; +void setHaltFunction(void (*func)(const char* message) __attribute__((noreturn))) VIS_HIDDEN; + + + + +} // namespace dyld3 + +#endif // __DYLD_LOGGING_H__ + + diff --git a/dyld3/MachOParser.cpp b/dyld3/MachOParser.cpp new file mode 100644 index 0000000..27bf3ba --- /dev/null +++ b/dyld3/MachOParser.cpp @@ -0,0 +1,3509 @@ +/* + * Copyright (c) 2017 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 + +#if !DYLD_IN_PROCESS +#include +#endif + +#include "MachOParser.h" +#include "Logging.h" +#include "CodeSigningTypes.h" +#include "DyldSharedCache.h" +#include "Trie.hpp" + +#if DYLD_IN_PROCESS + #include "APIs.h" +#else + #include "StringUtils.h" +#endif + + + +#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE + #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 +#endif + +#ifndef CPU_SUBTYPE_ARM64_E + #define CPU_SUBTYPE_ARM64_E 2 +#endif + +#ifndef LC_BUILD_VERSION + #define LC_BUILD_VERSION 0x32 /* build for platform min OS version */ + + /* + * The build_version_command contains the min OS version on which this + * binary was built to run for its platform. The list of known platforms and + * tool values following it. + */ + struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ + }; + + struct build_tool_version { + uint32_t tool; /* enum for the tool */ + uint32_t version; /* version number of the tool */ + }; + + /* Known values for the platform field above. */ + #define PLATFORM_MACOS 1 + #define PLATFORM_IOS 2 + #define PLATFORM_TVOS 3 + #define PLATFORM_WATCHOS 4 + #define PLATFORM_BRIDGEOS 5 + + /* Known values for the tool field above. */ + #define TOOL_CLANG 1 + #define TOOL_SWIFT 2 + #define TOOL_LD 3 +#endif + + +namespace dyld3 { + + +bool FatUtil::isFatFile(const void* fileStart) +{ + const fat_header* fileStartAsFat = (fat_header*)fileStart; + return ( fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ); +} + +/// Returns true if (addLHS + addRHS) > b, or if the add overflowed +template +static bool greaterThanAddOrOverflow(uint32_t addLHS, uint32_t addRHS, T b) { + return (addLHS > b) || (addRHS > (b-addLHS)); +} + +/// Returns true if (addLHS + addRHS) > b, or if the add overflowed +template +static bool greaterThanAddOrOverflow(uint64_t addLHS, uint64_t addRHS, T b) { + return (addLHS > b) || (addRHS > (b-addLHS)); +} + +void FatUtil::forEachSlice(Diagnostics& diag, const void* fileContent, size_t fileLen, void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSize, bool& stop)) +{ + const fat_header* fh = (fat_header*)fileContent; + if ( fh->magic != OSSwapBigToHostInt32(FAT_MAGIC) ) { + diag.error("not a fat file"); + return; + } + + if ( OSSwapBigToHostInt32(fh->nfat_arch) > ((4096 - sizeof(fat_header)) / sizeof(fat_arch)) ) { + diag.error("fat header too large: %u entries", OSSwapBigToHostInt32(fh->nfat_arch)); + } + const fat_arch* const archs = (fat_arch*)(((char*)fh)+sizeof(fat_header)); + bool stop = false; + for (uint32_t i=0; i < OSSwapBigToHostInt32(fh->nfat_arch); ++i) { + uint32_t cpuType = OSSwapBigToHostInt32(archs[i].cputype); + uint32_t cpuSubType = OSSwapBigToHostInt32(archs[i].cpusubtype); + uint32_t offset = OSSwapBigToHostInt32(archs[i].offset); + uint32_t len = OSSwapBigToHostInt32(archs[i].size); + if (greaterThanAddOrOverflow(offset, len, fileLen)) { + diag.error("slice %d extends beyond end of file", i); + return; + } + callback(cpuType, cpuSubType, (uint8_t*)fileContent+offset, len, stop); + if ( stop ) + break; + } +} + +#if !DYLD_IN_PROCESS +bool FatUtil::isFatFileWithSlice(Diagnostics& diag, const void* fileContent, size_t fileLen, const std::string& archName, size_t& sliceOffset, size_t& sliceLen, bool& missingSlice) +{ + missingSlice = false; + if ( !isFatFile(fileContent) ) + return false; + + __block bool found = false; + forEachSlice(diag, fileContent, fileLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSize, bool& stop) { + std::string sliceArchName = MachOParser::archName(sliceCpuType, sliceCpuSubType); + if ( sliceArchName == archName ) { + sliceOffset = (char*)sliceStart - (char*)fileContent; + sliceLen = sliceSize; + found = true; + stop = true; + } + }); + if ( diag.hasError() ) + return false; + + if ( !found ) + missingSlice = true; + + // when looking for x86_64h fallback to x86_64 + if ( !found && (archName == "x86_64h") ) + return isFatFileWithSlice(diag, fileContent, fileLen, "x86_64", sliceOffset, sliceLen, missingSlice); + + return found; +} + +#endif + +MachOParser::MachOParser(const mach_header* mh, bool dyldCacheIsRaw) +{ +#if DYLD_IN_PROCESS + // assume all in-process mach_headers are real loaded images + _data = (long)mh; +#else + if (mh == nullptr) + return; + _data = (long)mh; + if ( (mh->flags & 0x80000000) == 0 ) { + // asssume out-of-process mach_header not in a dyld cache are raw mapped files + _data |= 1; + } + else { + // out-of-process mach_header in a dyld cache are not raw, but cache may be raw + if ( dyldCacheIsRaw ) + _data |= 2; + } +#endif +} + +const mach_header* MachOParser::header() const +{ + return (mach_header*)(_data & -4); +} + +// "raw" means the whole mach-o file was mapped as one contiguous region +// not-raw means the the mach-o file was mapped like dyld does - with zero fill expansion +bool MachOParser::isRaw() const +{ + return (_data & 1); +} + +// A raw dyld cache is when the whole dyld cache file is mapped in one contiguous region +// not-raw manes the dyld cache was mapped as it is at runtime with padding between regions +bool MachOParser::inRawCache() const +{ + return (_data & 2); +} + +uint32_t MachOParser::fileType() const +{ + return header()->filetype; +} + +bool MachOParser::inDyldCache() const +{ + return (header()->flags & 0x80000000); +} + +bool MachOParser::hasThreadLocalVariables() const +{ + return (header()->flags & MH_HAS_TLV_DESCRIPTORS); +} + +Platform MachOParser::platform() const +{ + Platform platform; + uint32_t minOS; + uint32_t sdk; + if ( getPlatformAndVersion(&platform, &minOS, &sdk) ) + return platform; + + // old binary with no explict load command to mark platform, look at arch + switch ( header()->cputype ) { + case CPU_TYPE_X86_64: + case CPU_TYPE_I386: + return Platform::macOS; + case CPU_TYPE_ARM64: + case CPU_TYPE_ARM: + return Platform::iOS; + } + return Platform::macOS; +} + + +#if !DYLD_IN_PROCESS + +const MachOParser::ArchInfo MachOParser::_s_archInfos[] = { + { "x86_64", CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL }, + { "x86_64h", CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H }, + { "i386", CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL }, + { "arm64", CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL }, + { "arm64e", CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_E }, + { "armv7k", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K }, + { "armv7s", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S }, + { "armv7", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7 } +}; + +bool MachOParser::isValidMachO(Diagnostics& diag, const std::string& archName, Platform platform, const void* fileContent, size_t fileLength, const std::string& pathOpened, bool ignoreMainExecutables) +{ + // must start with mach-o magic value + const mach_header* mh = (const mach_header*)fileContent; + if ( (mh->magic != MH_MAGIC) && (mh->magic != MH_MAGIC_64) ) { + diag.warning("could not use '%s' because it is not a mach-o file", pathOpened.c_str()); + return false; + } + + // must match requested architecture if specified + if (!archName.empty() && !isArch(mh, archName)) { + // except when looking for x86_64h, fallback to x86_64 + if ( (archName != "x86_64h") || !isArch(mh, "x86_64") ) { + diag.warning("could not use '%s' because it does not contain required architecture %s", pathOpened.c_str(), archName.c_str()); + return false; + } + } + + // must be a filetype dyld can load + switch ( mh->filetype ) { + case MH_EXECUTE: + if ( ignoreMainExecutables ) + return false; + break; + case MH_DYLIB: + case MH_BUNDLE: + break; + default: + diag.warning("could not use '%s' because it is not a dylib, bundle, or executable", pathOpened.c_str()); + return false; + } + + // must be from a file - not in the dyld shared cache + if ( mh->flags & 0x80000000 ) { + diag.warning("could not use '%s' because the high bit of mach_header flags is reserved for images in dyld cache", pathOpened.c_str()); + return false; + } + + // validate load commands structure + MachOParser parser(mh); + if ( !parser.validLoadCommands(diag, fileLength) ) + return false; + + // must match requested platform + if ( parser.platform() != platform ) { + diag.warning("could not use '%s' because it was built for a different platform", pathOpened.c_str()); + return false; + } + + // cannot be a static executable + if ( (mh->filetype == MH_EXECUTE) && !parser.isDynamicExecutable() ) { + diag.warning("could not use '%s' because it is a static executable", pathOpened.c_str()); + return false; + } + + // validate dylib loads + if ( !parser.validEmbeddedPaths(diag) ) + return false; + + // validate segments + if ( !parser.validSegments(diag, fileLength) ) + return false; + + // validate LINKEDIT layout + if ( !parser.validLinkeditLayout(diag) ) + return false; + + return true; +} + + +bool MachOParser::validLoadCommands(Diagnostics& diag, size_t fileLen) +{ + // check load command don't exceed file length + if ( header()->sizeofcmds + sizeof(mach_header_64) > fileLen ) { + diag.warning("load commands exceed length of file"); + return false; + } + // walk all load commands and sanity check them + Diagnostics walkDiag; + LinkEditInfo lePointers; + getLinkEditLoadCommands(walkDiag, lePointers); + if ( walkDiag.hasError() ) { + diag.warning("%s", walkDiag.errorMessage().c_str()); + return false; + } + + // check load commands fit in TEXT segment + __block bool overflowText = false; + forEachSegment(^(const char* segName, uint32_t segFileOffset, uint32_t segFileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( strcmp(segName, "__TEXT") == 0 ) { + if ( header()->sizeofcmds + sizeof(mach_header_64) > segFileSize ) { + diag.warning("load commands exceed length of __TEXT segment"); + overflowText = true; + } + stop = true; + } + }); + if ( overflowText ) + return false; + + return true; +} + +bool MachOParser::validEmbeddedPaths(Diagnostics& diag) +{ + __block int index = 1; + __block bool allGood = true; + __block bool foundInstallName = false; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + const dylib_command* dylibCmd; + const rpath_command* rpathCmd; + switch ( cmd->cmd ) { + case LC_ID_DYLIB: + foundInstallName = true; + // fall through + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + dylibCmd = (dylib_command*)cmd; + if ( dylibCmd->dylib.name.offset > cmd->cmdsize ) { + diag.warning("load command #%d name offset (%u) outside its size (%u)", index, dylibCmd->dylib.name.offset, cmd->cmdsize); + stop = true; + allGood = false; + } + else { + bool foundEnd = false; + const char* start = (char*)dylibCmd + dylibCmd->dylib.name.offset; + const char* end = (char*)dylibCmd + cmd->cmdsize; + for (const char* s=start; s < end; ++s) { + if ( *s == '\0' ) { + foundEnd = true; + break; + } + } + if ( !foundEnd ) { + diag.warning("load command #%d string extends beyond end of load command", index); + stop = true; + allGood = false; + } + } + break; + case LC_RPATH: + rpathCmd = (rpath_command*)cmd; + if ( rpathCmd->path.offset > cmd->cmdsize ) { + diag.warning("load command #%d path offset (%u) outside its size (%u)", index, rpathCmd->path.offset, cmd->cmdsize); + stop = true; + allGood = false; + } + else { + bool foundEnd = false; + const char* start = (char*)rpathCmd + rpathCmd->path.offset; + const char* end = (char*)rpathCmd + cmd->cmdsize; + for (const char* s=start; s < end; ++s) { + if ( *s == '\0' ) { + foundEnd = true; + break; + } + } + if ( !foundEnd ) { + diag.warning("load command #%d string extends beyond end of load command", index); + stop = true; + allGood = false; + } + } + break; + } + ++index; + }); + + if ( header()->filetype == MH_DYLIB ) { + if ( !foundInstallName ) { + diag.warning("MH_DYLIB is missing LC_ID_DYLIB"); + allGood = false; + } + } + else { + if ( foundInstallName ) { + diag.warning("LC_ID_DYLIB found in non-MH_DYLIB"); + allGood = false; + } + } + + return allGood; +} + +bool MachOParser::validSegments(Diagnostics& diag, size_t fileLen) +{ + // check segment load command size + __block bool badSegmentLoadCommand = false; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* seg = (segment_command_64*)cmd; + int32_t sectionsSpace = cmd->cmdsize - sizeof(segment_command_64); + if ( sectionsSpace < 0 ) { + diag.warning("load command size too small for LC_SEGMENT_64"); + badSegmentLoadCommand = true; + stop = true; + } + else if ( (sectionsSpace % sizeof(section_64)) != 0 ) { + diag.warning("segment load command size 0x%X will not fit whole number of sections", cmd->cmdsize); + badSegmentLoadCommand = true; + stop = true; + } + else if ( sectionsSpace != (seg->nsects * sizeof(section_64)) ) { + diag.warning("load command size 0x%X does not match nsects %d", cmd->cmdsize, seg->nsects); + badSegmentLoadCommand = true; + stop = true; + } else if (greaterThanAddOrOverflow(seg->fileoff, seg->filesize, fileLen)) { + diag.warning("segment load command content extends beyond end of file"); + badSegmentLoadCommand = true; + stop = true; + } else if ( (seg->filesize > seg->vmsize) && ((seg->vmsize != 0) || ((seg->flags & SG_NORELOC) == 0)) ) { + // dyld should support non-allocatable __LLVM segment + diag.warning("segment filesize exceeds vmsize"); + badSegmentLoadCommand = true; + stop = true; + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* seg = (segment_command*)cmd; + int32_t sectionsSpace = cmd->cmdsize - sizeof(segment_command); + if ( sectionsSpace < 0 ) { + diag.warning("load command size too small for LC_SEGMENT"); + badSegmentLoadCommand = true; + stop = true; + } + else if ( (sectionsSpace % sizeof(section)) != 0 ) { + diag.warning("segment load command size 0x%X will not fit whole number of sections", cmd->cmdsize); + badSegmentLoadCommand = true; + stop = true; + } + else if ( sectionsSpace != (seg->nsects * sizeof(section)) ) { + diag.warning("load command size 0x%X does not match nsects %d", cmd->cmdsize, seg->nsects); + badSegmentLoadCommand = true; + stop = true; + } else if ( (seg->filesize > seg->vmsize) && ((seg->vmsize != 0) || ((seg->flags & SG_NORELOC) == 0)) ) { + // dyld should support non-allocatable __LLVM segment + diag.warning("segment filesize exceeds vmsize"); + badSegmentLoadCommand = true; + stop = true; + } + } + }); + if ( badSegmentLoadCommand ) + return false; + + // check mapping permissions of segments + __block bool badPermissions = false; + __block bool badSize = false; + __block bool hasTEXT = false; + __block bool hasLINKEDIT = false; + forEachSegment(^(const char* segName, uint32_t segFileOffset, uint32_t segFileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( strcmp(segName, "__TEXT") == 0 ) { + if ( protections != (VM_PROT_READ|VM_PROT_EXECUTE) ) { + diag.warning("__TEXT segment permissions is not 'r-x'"); + badPermissions = true; + stop = true; + } + hasTEXT = true; + } + else if ( strcmp(segName, "__LINKEDIT") == 0 ) { + if ( protections != VM_PROT_READ ) { + diag.warning("__LINKEDIT segment permissions is not 'r--'"); + badPermissions = true; + stop = true; + } + hasLINKEDIT = true; + } + else if ( (protections & 0xFFFFFFF8) != 0 ) { + diag.warning("%s segment permissions has invalid bits set", segName); + badPermissions = true; + stop = true; + } + if (greaterThanAddOrOverflow(segFileOffset, segFileSize, fileLen)) { + diag.warning("%s segment content extends beyond end of file", segName); + badSize = true; + stop = true; + } + if ( is64() ) { + if ( vmAddr+vmSize < vmAddr ) { + diag.warning("%s segment vm range wraps", segName); + badSize = true; + stop = true; + } + } + else { + if ( (uint32_t)(vmAddr+vmSize) < (uint32_t)(vmAddr) ) { + diag.warning("%s segment vm range wraps", segName); + badSize = true; + stop = true; + } + } + }); + if ( badPermissions || badSize ) + return false; + if ( !hasTEXT ) { + diag.warning("missing __TEXT segment"); + return false; + } + if ( !hasLINKEDIT ) { + diag.warning("missing __LINKEDIT segment"); + return false; + } + + // check for overlapping segments + __block bool badSegments = false; + forEachSegment(^(const char* seg1Name, uint32_t seg1FileOffset, uint32_t seg1FileSize, uint64_t seg1vmAddr, uint64_t seg1vmSize, uint8_t seg1Protections, uint32_t seg1Index, uint64_t seg1SizeOfSections, uint8_t seg1Align, bool& stop1) { + uint64_t seg1vmEnd = seg1vmAddr + seg1vmSize; + uint32_t seg1FileEnd = seg1FileOffset + seg1FileSize; + forEachSegment(^(const char* seg2Name, uint32_t seg2FileOffset, uint32_t seg2FileSize, uint64_t seg2vmAddr, uint64_t seg2vmSize, uint8_t seg2Protections, uint32_t seg2Index, uint64_t seg2SizeOfSections, uint8_t seg2Align, bool& stop2) { + if ( seg1Index == seg2Index ) + return; + uint64_t seg2vmEnd = seg2vmAddr + seg2vmSize; + uint32_t seg2FileEnd = seg2FileOffset + seg2FileSize; + if ( ((seg2vmAddr <= seg1vmAddr) && (seg2vmEnd > seg1vmAddr) && (seg1vmEnd > seg1vmAddr)) || ((seg2vmAddr >= seg1vmAddr) && (seg2vmAddr < seg1vmEnd) && (seg2vmEnd > seg2vmAddr)) ) { + diag.warning("segment %s vm range overlaps segment %s", seg1Name, seg2Name); + badSegments = true; + stop1 = true; + stop2 = true; + } + if ( ((seg2FileOffset <= seg1FileOffset) && (seg2FileEnd > seg1FileOffset) && (seg1FileEnd > seg1FileOffset)) || ((seg2FileOffset >= seg1FileOffset) && (seg2FileOffset < seg1FileEnd) && (seg2FileEnd > seg2FileOffset)) ) { + diag.warning("segment %s file content overlaps segment %s", seg1Name, seg2Name); + badSegments = true; + stop1 = true; + stop2 = true; + } + // check for out of order segments + if ( (seg1Index < seg2Index) && !stop1 ) { + if ( (seg1vmAddr > seg2vmAddr) || ((seg1FileOffset > seg2FileOffset) && (seg1FileOffset != 0) && (seg2FileOffset != 0)) ){ + diag.warning("segment load commands out of order with respect to layout for %s and %s", seg1Name, seg2Name); + badSegments = true; + stop1 = true; + stop2 = true; + } + } + }); + }); + if ( badSegments ) + return false; + + // check sections are within segment + __block bool badSections = false; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* seg = (segment_command_64*)cmd; + const section_64* const sectionsStart = (section_64*)((char*)seg + sizeof(struct segment_command_64)); + const section_64* const sectionsEnd = §ionsStart[seg->nsects]; + for (const section_64* sect=sectionsStart; (sect < sectionsEnd); ++sect) { + if ( (int64_t)(sect->size) < 0 ) { + diag.warning("section %s size too large 0x%llX", sect->sectname, sect->size); + badSections = true; + } + else if ( sect->addr < seg->vmaddr ) { + diag.warning("section %s start address 0x%llX is before containing segment's address 0x%0llX", sect->sectname, sect->addr, seg->vmaddr); + badSections = true; + } + else if ( sect->addr+sect->size > seg->vmaddr+seg->vmsize ) { + diag.warning("section %s end address 0x%llX is beyond containing segment's end address 0x%0llX", sect->sectname, sect->addr+sect->size, seg->vmaddr+seg->vmsize); + badSections = true; + } + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* seg = (segment_command*)cmd; + const section* const sectionsStart = (section*)((char*)seg + sizeof(struct segment_command)); + const section* const sectionsEnd = §ionsStart[seg->nsects]; + for (const section* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) { + if ( (int64_t)(sect->size) < 0 ) { + diag.warning("section %s size too large 0x%X", sect->sectname, sect->size); + badSections = true; + } + else if ( sect->addr < seg->vmaddr ) { + diag.warning("section %s start address 0x%X is before containing segment's address 0x%0X", sect->sectname, sect->addr, seg->vmaddr); + badSections = true; + } + else if ( sect->addr+sect->size > seg->vmaddr+seg->vmsize ) { + diag.warning("section %s end address 0x%X is beyond containing segment's end address 0x%0X", sect->sectname, sect->addr+sect->size, seg->vmaddr+seg->vmsize); + badSections = true; + } + } + } + }); + + return !badSections; +} + +struct LinkEditContent +{ + const char* name; + uint32_t stdOrder; + uint32_t fileOffsetStart; + uint32_t size; +}; + + + +bool MachOParser::validLinkeditLayout(Diagnostics& diag) +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return false; + const bool is64Bit = is64(); + const uint32_t pointerSize = (is64Bit ? 8 : 4); + + // build vector of all blobs in LINKEDIT + std::vector blobs; + if ( leInfo.dyldInfo != nullptr ) { + if ( leInfo.dyldInfo->rebase_size != 0 ) + blobs.push_back({"rebase opcodes", 1, leInfo.dyldInfo->rebase_off, leInfo.dyldInfo->rebase_size}); + if ( leInfo.dyldInfo->bind_size != 0 ) + blobs.push_back({"bind opcodes", 2, leInfo.dyldInfo->bind_off, leInfo.dyldInfo->bind_size}); + if ( leInfo.dyldInfo->weak_bind_size != 0 ) + blobs.push_back({"weak bind opcodes", 3, leInfo.dyldInfo->weak_bind_off, leInfo.dyldInfo->weak_bind_size}); + if ( leInfo.dyldInfo->lazy_bind_size != 0 ) + blobs.push_back({"lazy bind opcodes", 4, leInfo.dyldInfo->lazy_bind_off, leInfo.dyldInfo->lazy_bind_size}); + if ( leInfo.dyldInfo->export_size!= 0 ) + blobs.push_back({"exports trie", 5, leInfo.dyldInfo->export_off, leInfo.dyldInfo->export_size}); + } + if ( leInfo.dynSymTab != nullptr ) { + if ( leInfo.dynSymTab->nlocrel != 0 ) + blobs.push_back({"local relocations", 6, leInfo.dynSymTab->locreloff, static_cast(leInfo.dynSymTab->nlocrel*sizeof(relocation_info))}); + if ( leInfo.dynSymTab->nextrel != 0 ) + blobs.push_back({"external relocations", 11, leInfo.dynSymTab->extreloff, static_cast(leInfo.dynSymTab->nextrel*sizeof(relocation_info))}); + if ( leInfo.dynSymTab->nindirectsyms != 0 ) + blobs.push_back({"indirect symbol table", 12, leInfo.dynSymTab->indirectsymoff, leInfo.dynSymTab->nindirectsyms*4}); + } + if ( leInfo.splitSegInfo != nullptr ) { + if ( leInfo.splitSegInfo->datasize != 0 ) + blobs.push_back({"shared cache info", 6, leInfo.splitSegInfo->dataoff, leInfo.splitSegInfo->datasize}); + } + if ( leInfo.functionStarts != nullptr ) { + if ( leInfo.functionStarts->datasize != 0 ) + blobs.push_back({"function starts", 7, leInfo.functionStarts->dataoff, leInfo.functionStarts->datasize}); + } + if ( leInfo.dataInCode != nullptr ) { + if ( leInfo.dataInCode->datasize != 0 ) + blobs.push_back({"data in code", 8, leInfo.dataInCode->dataoff, leInfo.dataInCode->datasize}); + } + if ( leInfo.symTab != nullptr ) { + if ( leInfo.symTab->nsyms != 0 ) + blobs.push_back({"symbol table", 10, leInfo.symTab->symoff, static_cast(leInfo.symTab->nsyms*(is64Bit ? sizeof(nlist_64) : sizeof(struct nlist)))}); + if ( leInfo.symTab->strsize != 0 ) + blobs.push_back({"symbol table strings", 20, leInfo.symTab->stroff, leInfo.symTab->strsize}); + } + if ( leInfo.codeSig != nullptr ) { + if ( leInfo.codeSig->datasize != 0 ) + blobs.push_back({"code signature", 21, leInfo.codeSig->dataoff, leInfo.codeSig->datasize}); + } + + // check for bad combinations + if ( (leInfo.dyldInfo != nullptr) && (leInfo.dyldInfo->cmd == LC_DYLD_INFO_ONLY) && (leInfo.dynSymTab != nullptr) ) { + if ( leInfo.dynSymTab->nlocrel != 0 ) { + diag.error("malformed mach-o contains LC_DYLD_INFO_ONLY and local relocations"); + return false; + } + if ( leInfo.dynSymTab->nextrel != 0 ) { + diag.error("malformed mach-o contains LC_DYLD_INFO_ONLY and external relocations"); + return false; + } + } + if ( (leInfo.dyldInfo == nullptr) && (leInfo.dynSymTab == nullptr) ) { + diag.error("malformed mach-o misssing LC_DYLD_INFO and LC_DYSYMTAB"); + return false; + } + if ( blobs.empty() ) { + diag.error("malformed mach-o misssing LINKEDIT"); + return false; + } + + // sort vector by file offset and error on overlaps + std::sort(blobs.begin(), blobs.end(), [&](const LinkEditContent& a, const LinkEditContent& b) { + return a.fileOffsetStart < b.fileOffsetStart; + }); + uint32_t prevEnd = (uint32_t)(leInfo.layout.segments[leInfo.layout.linkeditSegIndex].fileOffset); + const char* prevName = "start of LINKEDIT"; + for (const LinkEditContent& blob : blobs) { + if ( blob.fileOffsetStart < prevEnd ) { + diag.error("LINKEDIT overlap of %s and %s", prevName, blob.name); + return false; + } + prevEnd = blob.fileOffsetStart + blob.size; + prevName = blob.name; + } + const LinkEditContent& lastBlob = blobs.back(); + uint32_t linkeditFileEnd = (uint32_t)(leInfo.layout.segments[leInfo.layout.linkeditSegIndex].fileOffset + leInfo.layout.segments[leInfo.layout.linkeditSegIndex].fileSize); + if (greaterThanAddOrOverflow(lastBlob.fileOffsetStart, lastBlob.size, linkeditFileEnd)) { + diag.error("LINKEDIT content '%s' extends beyond end of segment", lastBlob.name); + return false; + } + + // sort vector by order and warn on non standard order or mis-alignment + std::sort(blobs.begin(), blobs.end(), [&](const LinkEditContent& a, const LinkEditContent& b) { + return a.stdOrder < b.stdOrder; + }); + prevEnd = (uint32_t)(leInfo.layout.segments[leInfo.layout.linkeditSegIndex].fileOffset); + prevName = "start of LINKEDIT"; + for (const LinkEditContent& blob : blobs) { + if ( ((blob.fileOffsetStart & (pointerSize-1)) != 0) && (blob.stdOrder != 20) ) // ok for "symbol table strings" to be mis-aligned + diag.warning("mis-aligned LINKEDIT content '%s'", blob.name); + if ( blob.fileOffsetStart < prevEnd ) { + diag.warning("LINKEDIT out of order %s", blob.name); + } + prevEnd = blob.fileOffsetStart; + prevName = blob.name; + } + + // Check for invalid symbol table sizes + if ( leInfo.symTab != nullptr ) { + if ( leInfo.symTab->nsyms > 0x10000000 ) { + diag.error("malformed mach-o image: symbol table too large"); + return false; + } + if ( leInfo.dynSymTab != nullptr ) { + // validate indirect symbol table + if ( leInfo.dynSymTab->nindirectsyms != 0 ) { + if ( leInfo.dynSymTab->nindirectsyms > 0x10000000 ) { + diag.error("malformed mach-o image: indirect symbol table too large"); + return false; + } + } + if ( (leInfo.dynSymTab->nlocalsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->ilocalsym > leInfo.symTab->nsyms) ) { + diag.error("malformed mach-o image: indirect symbol table local symbol count exceeds total symbols"); + return false; + } + if ( leInfo.dynSymTab->ilocalsym + leInfo.dynSymTab->nlocalsym < leInfo.dynSymTab->ilocalsym ) { + diag.error("malformed mach-o image: indirect symbol table local symbol count wraps"); + return false; + } + if ( (leInfo.dynSymTab->nextdefsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->iextdefsym > leInfo.symTab->nsyms) ) { + diag.error("malformed mach-o image: indirect symbol table extern symbol count exceeds total symbols"); + return false; + } + if ( leInfo.dynSymTab->iextdefsym + leInfo.dynSymTab->nextdefsym < leInfo.dynSymTab->iextdefsym ) { + diag.error("malformed mach-o image: indirect symbol table extern symbol count wraps"); + return false; + } + if ( (leInfo.dynSymTab->nundefsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->iundefsym > leInfo.symTab->nsyms) ) { + diag.error("malformed mach-o image: indirect symbol table undefined symbol count exceeds total symbols"); + return false; + } + if ( leInfo.dynSymTab->iundefsym + leInfo.dynSymTab->nundefsym < leInfo.dynSymTab->iundefsym ) { + diag.error("malformed mach-o image: indirect symbol table undefined symbol count wraps"); + return false; + } + } + } + + return true; +} + +bool MachOParser::isArch(const mach_header* mh, const std::string& archName) +{ + for (const ArchInfo& info : _s_archInfos) { + if ( archName == info.name ) { + return ( (mh->cputype == info.cputype) && ((mh->cpusubtype & ~CPU_SUBTYPE_MASK) == info.cpusubtype) ); + } + } + return false; +} + + +std::string MachOParser::archName(uint32_t cputype, uint32_t cpusubtype) +{ + for (const ArchInfo& info : _s_archInfos) { + if ( (cputype == info.cputype) && ((cpusubtype & ~CPU_SUBTYPE_MASK) == info.cpusubtype) ) { + return info.name; + } + } + return "unknown"; +} + +uint32_t MachOParser::cpuTypeFromArchName(const std::string& archName) +{ + for (const ArchInfo& info : _s_archInfos) { + if ( archName == info.name ) { + return info.cputype; + } + } + return 0; +} + +uint32_t MachOParser::cpuSubtypeFromArchName(const std::string& archName) +{ + for (const ArchInfo& info : _s_archInfos) { + if ( archName == info.name ) { + return info.cpusubtype; + } + } + return 0; +} + +std::string MachOParser::archName() const +{ + return archName(header()->cputype, header()->cpusubtype); +} + +std::string MachOParser::platformName(Platform platform) +{ + switch ( platform ) { + case Platform::unknown: + return "unknown"; + case Platform::macOS: + return "macOS"; + case Platform::iOS: + return "iOS"; + case Platform::tvOS: + return "tvOS"; + case Platform::watchOS: + return "watchOS"; + case Platform::bridgeOS: + return "bridgeOS"; + } + return "unknown platform"; +} + +std::string MachOParser::versionString(uint32_t packedVersion) +{ + char buff[64]; + sprintf(buff, "%d.%d.%d", (packedVersion >> 16), ((packedVersion >> 8) & 0xFF), (packedVersion & 0xFF)); + return buff; +} + +#else + +bool MachOParser::isMachO(Diagnostics& diag, const void* fileContent, size_t mappedLength) +{ + // sanity check length + if ( mappedLength < 4096 ) { + diag.error("file too short"); + return false; + } + + // must start with mach-o magic value + const mach_header* mh = (const mach_header*)fileContent; +#if __LP64__ + const uint32_t requiredMagic = MH_MAGIC_64; +#else + const uint32_t requiredMagic = MH_MAGIC; +#endif + if ( mh->magic != requiredMagic ) { + diag.error("not a mach-o file"); + return false; + } + +#if __x86_64__ + const uint32_t requiredCPU = CPU_TYPE_X86_64; +#elif __i386__ + const uint32_t requiredCPU = CPU_TYPE_I386; +#elif __arm__ + const uint32_t requiredCPU = CPU_TYPE_ARM; +#elif __arm64__ + const uint32_t requiredCPU = CPU_TYPE_ARM64; +#else + #error unsupported architecture +#endif + if ( mh->cputype != requiredCPU ) { + diag.error("wrong cpu type"); + return false; + } + + return true; +} + +bool MachOParser::wellFormedMachHeaderAndLoadCommands(const mach_header* mh) +{ + const load_command* startCmds = nullptr; + 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; + } + cmd = nextCmd; + } + return true; +} + +#endif + +Platform MachOParser::currentPlatform() +{ +#if TARGET_OS_BRIDGE + return Platform::bridgeOS; +#elif TARGET_OS_WATCH + return Platform::watchOS; +#elif TARGET_OS_TV + return Platform::tvOS; +#elif TARGET_OS_IOS + return Platform::iOS; +#elif TARGET_OS_MAC + return Platform::macOS; +#else + #error unknown platform +#endif +} + + +bool MachOParser::valid(Diagnostics& diag) +{ +#if DYLD_IN_PROCESS + // only images loaded by dyld to be parsed + const mach_header* inImage = dyld3::dyld_image_header_containing_address(header()); + if ( inImage != header() ) { + diag.error("only dyld loaded images can be parsed by MachOParser"); + return false; + } +#else + +#endif + return true; +} + + +void MachOParser::forEachLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& stop)) const +{ + bool stop = false; + const load_command* startCmds = nullptr; + if ( header()->magic == MH_MAGIC_64 ) + startCmds = (load_command*)((char *)header() + sizeof(mach_header_64)); + else if ( header()->magic == MH_MAGIC ) + startCmds = (load_command*)((char *)header() + sizeof(mach_header)); + else { + diag.error("file does not start with MH_MAGIC[_64]"); + return; // not a mach-o file, or wrong endianness + } + const load_command* const cmdsEnd = (load_command*)((char*)startCmds + header()->sizeofcmds); + const load_command* cmd = startCmds; + for(uint32_t i = 0; i < header()->ncmds; ++i) { + const load_command* nextCmd = (load_command*)((char *)cmd + cmd->cmdsize); + if ( cmd->cmdsize < 8 ) { + diag.error("malformed load command #%d, size too small %d", i, cmd->cmdsize); + return; + } + if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) { + diag.error("malformed load command #%d, size too large 0x%X", i, cmd->cmdsize); + return; + } + callback(cmd, stop); + if ( stop ) + return; + cmd = nextCmd; + } +} + +UUID MachOParser::uuid() const +{ + uuid_t uuid; + getUuid(uuid); + return uuid; +} + +bool MachOParser::getUuid(uuid_t uuid) const +{ + Diagnostics diag; + __block bool found = false; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_UUID ) { + const uuid_command* uc = (const uuid_command*)cmd; + memcpy(uuid, uc->uuid, sizeof(uuid_t)); + found = true; + stop = true; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + if ( !found ) + bzero(uuid, sizeof(uuid_t)); + return found; +} + +uint64_t MachOParser::preferredLoadAddress() const +{ + __block uint64_t result = 0; + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( strcmp(segName, "__TEXT") == 0 ) { + result = vmAddr; + stop = true; + } + }); + return result; +} + +bool MachOParser::getPlatformAndVersion(Platform* platform, uint32_t* minOS, uint32_t* sdk) const +{ + Diagnostics diag; + __block bool found = false; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + const version_min_command* versCmd; + switch ( cmd->cmd ) { + case LC_VERSION_MIN_IPHONEOS: + versCmd = (version_min_command*)cmd; + *platform = Platform::iOS; + *minOS = versCmd->version; + *sdk = versCmd->sdk; + found = true; + stop = true; + break; + case LC_VERSION_MIN_MACOSX: + versCmd = (version_min_command*)cmd; + *platform = Platform::macOS; + *minOS = versCmd->version; + *sdk = versCmd->sdk; + found = true; + stop = true; + break; + case LC_VERSION_MIN_TVOS: + versCmd = (version_min_command*)cmd; + *platform = Platform::tvOS; + *minOS = versCmd->version; + *sdk = versCmd->sdk; + found = true; + stop = true; + break; + case LC_VERSION_MIN_WATCHOS: + versCmd = (version_min_command*)cmd; + *platform = Platform::watchOS; + *minOS = versCmd->version; + *sdk = versCmd->sdk; + found = true; + stop = true; + break; + case LC_BUILD_VERSION: { + const build_version_command* buildCmd = (build_version_command *)cmd; + *minOS = buildCmd->minos; + *sdk = buildCmd->sdk; + + switch(buildCmd->platform) { + /* Known values for the platform field above. */ + case PLATFORM_MACOS: + *platform = Platform::macOS; + break; + case PLATFORM_IOS: + *platform = Platform::iOS; + break; + case PLATFORM_TVOS: + *platform = Platform::tvOS; + break; + case PLATFORM_WATCHOS: + *platform = Platform::watchOS; + break; + case PLATFORM_BRIDGEOS: + *platform = Platform::bridgeOS; + break; + } + found = true; + stop = true; + } break; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + return found; +} + + +bool MachOParser::isSimulatorBinary() const +{ + Platform platform; + uint32_t minOS; + uint32_t sdk; + switch ( header()->cputype ) { + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: + if ( getPlatformAndVersion(&platform, &minOS, &sdk) ) { + return (platform != Platform::macOS); + } + break; + } + return false; +} + + +bool MachOParser::getDylibInstallName(const char** installName, uint32_t* compatVersion, uint32_t* currentVersion) const +{ + Diagnostics diag; + __block bool found = false; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_ID_DYLIB ) { + const dylib_command* dylibCmd = (dylib_command*)cmd; + *compatVersion = dylibCmd->dylib.compatibility_version; + *currentVersion = dylibCmd->dylib.current_version; + *installName = (char*)dylibCmd + dylibCmd->dylib.name.offset; + found = true; + stop = true; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + return found; +} + +const char* MachOParser::installName() const +{ + assert(header()->filetype == MH_DYLIB); + const char* result; + uint32_t ignoreVersion; + assert(getDylibInstallName(&result, &ignoreVersion, &ignoreVersion)); + return result; +} + + +uint32_t MachOParser::dependentDylibCount() const +{ + __block uint32_t count = 0; + forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + ++count; + }); + return count; +} + +const char* MachOParser::dependentDylibLoadPath(uint32_t depIndex) const +{ + __block const char* foundLoadPath = nullptr; + __block uint32_t curDepIndex = 0; + forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if ( curDepIndex == depIndex ) { + foundLoadPath = loadPath; + stop = true; + } + ++curDepIndex; + }); + return foundLoadPath; +} + + +void MachOParser::forEachDependentDylib(void (^callback)(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop)) const +{ + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + switch ( cmd->cmd ) { + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: { + const dylib_command* dylibCmd = (dylib_command*)cmd; + assert(dylibCmd->dylib.name.offset < cmd->cmdsize); + const char* loadPath = (char*)dylibCmd + dylibCmd->dylib.name.offset; + callback(loadPath, (cmd->cmd == LC_LOAD_WEAK_DYLIB), (cmd->cmd == LC_REEXPORT_DYLIB), (cmd->cmd == LC_LOAD_UPWARD_DYLIB), + dylibCmd->dylib.compatibility_version, dylibCmd->dylib.current_version, stop); + } + break; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + +void MachOParser::forEachRPath(void (^callback)(const char* rPath, bool& stop)) const +{ + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_RPATH ) { + const char* rpath = (char*)cmd + ((struct rpath_command*)cmd)->path.offset; + callback(rpath, stop); + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + +/* + struct LayoutInfo { +#if DYLD_IN_PROCESS + uintptr_t slide; + uintptr_t textUnslidVMAddr; + uintptr_t linkeditUnslidVMAddr; + uint32_t linkeditFileOffset; +#else + uint32_t segmentCount; + uint32_t linkeditSegIndex; + struct { + uint64_t mappingOffset; + uint64_t fileOffset; + uint64_t segUnslidAddress; + uint64_t segSize; + } segments[16]; +#endif + }; +*/ + +#if !DYLD_IN_PROCESS +const uint8_t* MachOParser::getContentForVMAddr(const LayoutInfo& info, uint64_t addr) const +{ + for (uint32_t i=0; i < info.segmentCount; ++i) { + if ( (addr >= info.segments[i].segUnslidAddress) && (addr < (info.segments[i].segUnslidAddress+info.segments[i].segSize)) ) + return (uint8_t*)header() + info.segments[i].mappingOffset + (addr - info.segments[i].segUnslidAddress); + } + // value is outside this image. could be pointer into another image + if ( inDyldCache() ) { + return (uint8_t*)header() + info.segments[0].mappingOffset + (addr - info.segments[0].segUnslidAddress); + } + assert(0 && "address not found in segment"); + return nullptr; +} +#endif + +const uint8_t* MachOParser::getLinkEditContent(const LayoutInfo& info, uint32_t fileOffset) const +{ +#if DYLD_IN_PROCESS + uint32_t offsetInLinkedit = fileOffset - info.linkeditFileOffset; + uintptr_t linkeditStartAddr = info.linkeditUnslidVMAddr + info.slide; + return (uint8_t*)(linkeditStartAddr + offsetInLinkedit); +#else + uint32_t offsetInLinkedit = fileOffset - (uint32_t)(info.segments[info.linkeditSegIndex].fileOffset); + const uint8_t* linkeditStart = (uint8_t*)header() + info.segments[info.linkeditSegIndex].mappingOffset; + return linkeditStart + offsetInLinkedit; +#endif +} + + +void MachOParser::getLayoutInfo(LayoutInfo& result) const +{ +#if DYLD_IN_PROCESS + // image loaded by dyld, just record the addr and file offset of TEXT and LINKEDIT segments + result.slide = getSlide(); + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( strcmp(segName, "__TEXT") == 0 ) { + result.textUnslidVMAddr = (uintptr_t)vmAddr; + } + else if ( strcmp(segName, "__LINKEDIT") == 0 ) { + result.linkeditUnslidVMAddr = (uintptr_t)vmAddr; + result.linkeditFileOffset = fileOffset; + } + }); +#else + bool inCache = inDyldCache(); + bool intel32 = (header()->cputype == CPU_TYPE_I386); + result.segmentCount = 0; + result.linkeditSegIndex = 0xFFFFFFFF; + __block uint64_t textSegAddr = 0; + __block uint64_t textSegFileOffset = 0; + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + auto& segInfo = result.segments[result.segmentCount]; + if ( strcmp(segName, "__TEXT") == 0 ) { + textSegAddr = vmAddr; + textSegFileOffset = fileOffset; + } + __block bool textRelocsAllowed = false; + if ( intel32 ) { + forEachSection(^(const char* curSegName, uint32_t segIndex, uint64_t segVMAddr, const char* sectionName, uint32_t sectFlags, + uint64_t sectAddr, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& sectStop) { + if ( strcmp(curSegName, segName) == 0 ) { + if ( sectFlags & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC) ) { + textRelocsAllowed = true; + sectStop = true; + } + } + }); + } + if ( inCache ) { + if ( inRawCache() ) { + // whole cache file mapped somewhere (padding not expanded) + // vmaddrs are useless. only file offset make sense + segInfo.mappingOffset = fileOffset - textSegFileOffset; + } + else { + // cache file was loaded by dyld into shared region + // vmaddrs of segments are correct except for ASLR slide + segInfo.mappingOffset = vmAddr - textSegAddr; + } + } + else { + // individual mach-o file mapped in one region, so mappingOffset == fileOffset + segInfo.mappingOffset = fileOffset; + } + segInfo.fileOffset = fileOffset; + segInfo.fileSize = fileSize; + segInfo.segUnslidAddress = vmAddr; + segInfo.segSize = vmSize; + segInfo.writable = ((protections & VM_PROT_WRITE) == VM_PROT_WRITE); + segInfo.executable = ((protections & VM_PROT_EXECUTE) == VM_PROT_EXECUTE); + segInfo.textRelocsAllowed = textRelocsAllowed; + if ( strcmp(segName, "__LINKEDIT") == 0 ) { + result.linkeditSegIndex = result.segmentCount; + } + ++result.segmentCount; + if ( result.segmentCount > 127 ) + stop = true; + }); +#endif +} + + +void MachOParser::forEachSection(void (^callback)(const char* segName, const char* sectionName, uint32_t flags, + const void* content, size_t size, bool illegalSectionSize, bool& stop)) const +{ + forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, + const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop) { + callback(segName, sectionName, flags, content, (size_t)size, illegalSectionSize, stop); + }); +} + +void MachOParser::forEachSection(void (^callback)(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, + const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, + bool illegalSectionSize, bool& stop)) const +{ + Diagnostics diag; + //fprintf(stderr, "forEachSection() mh=%p\n", header()); + LayoutInfo layout; + getLayoutInfo(layout); + forEachSection(^(const char* segName, uint32_t segIndex, uint64_t segVMAddr, const char* sectionName, uint32_t sectFlags, + uint64_t sectAddr, uint64_t sectSize, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop) { + #if DYLD_IN_PROCESS + const uint8_t* segContentStart = (uint8_t*)(segVMAddr + layout.slide); + #else + const uint8_t* segContentStart = (uint8_t*)header() + layout.segments[segIndex].mappingOffset; + #endif + const void* contentAddr = segContentStart + (sectAddr - segVMAddr); + callback(segName, sectionName, sectFlags, sectAddr, contentAddr, sectSize, alignP2, reserved1, reserved2, illegalSectionSize, stop); + }); + +} + +// this iterator just walks the segment/section array. It does interpret addresses +void MachOParser::forEachSection(void (^callback)(const char* segName, uint32_t segIndex, uint64_t segVMAddr, const char* sectionName, uint32_t sectFlags, + uint64_t sectAddr, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop)) const +{ + Diagnostics diag; + //fprintf(stderr, "forEachSection() mh=%p\n", header()); + __block uint32_t segIndex = 0; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* seg = (segment_command_64*)cmd; + const section_64* const sectionsStart = (section_64*)((char*)seg + sizeof(struct segment_command_64)); + const section_64* const sectionsEnd = §ionsStart[seg->nsects]; + for (const section_64* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) { + const char* sectName = sect->sectname; + char sectNameCopy[20]; + if ( sectName[15] != '\0' ) { + strlcpy(sectNameCopy, sectName, 17); + sectName = sectNameCopy; + } + bool illegalSectionSize = (sect->addr < seg->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, seg->vmaddr + seg->filesize); + callback(seg->segname, segIndex, seg->vmaddr, sectName, sect->flags, sect->addr, sect->size, sect->align, sect->reserved1, sect->reserved2, illegalSectionSize, stop); + } + ++segIndex; + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* seg = (segment_command*)cmd; + const section* const sectionsStart = (section*)((char*)seg + sizeof(struct segment_command)); + const section* const sectionsEnd = §ionsStart[seg->nsects]; + for (const section* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) { + const char* sectName = sect->sectname; + char sectNameCopy[20]; + if ( sectName[15] != '\0' ) { + strlcpy(sectNameCopy, sectName, 17); + sectName = sectNameCopy; + } + bool illegalSectionSize = (sect->addr < seg->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, seg->vmaddr + seg->filesize); + callback(seg->segname, segIndex, seg->vmaddr, sectName, sect->flags, sect->addr, sect->size, sect->align, sect->reserved1, sect->reserved2, illegalSectionSize, stop); + } + ++segIndex; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + +void MachOParser::forEachGlobalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + const bool is64Bit = is64(); + if ( leInfo.symTab != nullptr ) { + uint32_t globalsStartIndex = 0; + uint32_t globalsCount = leInfo.symTab->nsyms; + if ( leInfo.dynSymTab != nullptr ) { + globalsStartIndex = leInfo.dynSymTab->iextdefsym; + globalsCount = leInfo.dynSymTab->nextdefsym; + } + uint32_t maxStringOffset = leInfo.symTab->strsize; + const char* stringPool = (char*)getLinkEditContent(leInfo.layout, leInfo.symTab->stroff); + const struct nlist* symbols = (struct nlist*) (getLinkEditContent(leInfo.layout, leInfo.symTab->symoff)); + const struct nlist_64* symbols64 = (struct nlist_64*)(getLinkEditContent(leInfo.layout, leInfo.symTab->symoff)); + bool stop = false; + for (uint32_t i=0; (i < globalsCount) && !stop; ++i) { + if ( is64Bit ) { + const struct nlist_64& sym = symbols64[globalsStartIndex+i]; + if ( sym.n_un.n_strx > maxStringOffset ) + continue; + if ( (sym.n_type & N_EXT) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) ) + callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop); + } + else { + const struct nlist& sym = symbols[globalsStartIndex+i]; + if ( sym.n_un.n_strx > maxStringOffset ) + continue; + if ( (sym.n_type & N_EXT) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) ) + callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop); + } + } + } +} + +void MachOParser::forEachLocalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + const bool is64Bit = is64(); + if ( leInfo.symTab != nullptr ) { + uint32_t localsStartIndex = 0; + uint32_t localsCount = leInfo.symTab->nsyms; + if ( leInfo.dynSymTab != nullptr ) { + localsStartIndex = leInfo.dynSymTab->ilocalsym; + localsCount = leInfo.dynSymTab->nlocalsym; + } + uint32_t maxStringOffset = leInfo.symTab->strsize; + const char* stringPool = (char*)getLinkEditContent(leInfo.layout, leInfo.symTab->stroff); + const struct nlist* symbols = (struct nlist*) (getLinkEditContent(leInfo.layout, leInfo.symTab->symoff)); + const struct nlist_64* symbols64 = (struct nlist_64*)(getLinkEditContent(leInfo.layout, leInfo.symTab->symoff)); + bool stop = false; + for (uint32_t i=0; (i < localsCount) && !stop; ++i) { + if ( is64Bit ) { + const struct nlist_64& sym = symbols64[localsStartIndex+i]; + if ( sym.n_un.n_strx > maxStringOffset ) + continue; + if ( ((sym.n_type & N_EXT) == 0) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) ) + callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop); + } + else { + const struct nlist& sym = symbols[localsStartIndex+i]; + if ( sym.n_un.n_strx > maxStringOffset ) + continue; + if ( ((sym.n_type & N_EXT) == 0) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) ) + callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop); + } + } + } +} + + +bool MachOParser::findExportedSymbol(Diagnostics& diag, const char* symbolName, void* extra, FoundSymbol& foundInfo, DependentFinder findDependent) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return false; + if ( leInfo.dyldInfo != nullptr ) { + const uint8_t* trieStart = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->export_off); + const uint8_t* trieEnd = trieStart + leInfo.dyldInfo->export_size; + const uint8_t* node = trieWalk(diag, trieStart, trieEnd, symbolName); + if ( node == nullptr ) { + // symbol not exported from this image. Seach any re-exported dylibs + __block unsigned depIndex = 0; + __block bool foundInReExportedDylib = false; + forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if ( isReExport && findDependent ) { + const mach_header* depMH; + void* depExtra; + if ( findDependent(depIndex, loadPath, extra, &depMH, &depExtra) ) { + bool depInRawCache = inRawCache() && (depMH->flags & 0x80000000); + MachOParser dep(depMH, depInRawCache); + if ( dep.findExportedSymbol(diag, symbolName, depExtra, foundInfo, findDependent) ) { + stop = true; + foundInReExportedDylib = true; + } + } + else { + fprintf(stderr, "could not find re-exported dylib %s\n", loadPath); + } + } + ++depIndex; + }); + return foundInReExportedDylib; + } + const uint8_t* p = node; + const uint64_t flags = read_uleb128(diag, p, trieEnd); + if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { + if ( !findDependent ) + return false; + // re-export from another dylib, lookup there + const uint64_t ordinal = read_uleb128(diag, p, trieEnd); + const char* importedName = (char*)p; + if ( importedName[0] == '\0' ) + importedName = symbolName; + assert(ordinal >= 1); + if (ordinal > dependentDylibCount()) { + diag.error("ordinal %lld out of range for %s", ordinal, symbolName); + return false; + } + uint32_t depIndex = (uint32_t)(ordinal-1); + const mach_header* depMH; + void* depExtra; + if ( findDependent(depIndex, dependentDylibLoadPath(depIndex), extra, &depMH, &depExtra) ) { + bool depInRawCache = inRawCache() && (depMH->flags & 0x80000000); + MachOParser depParser(depMH, depInRawCache); + return depParser.findExportedSymbol(diag, importedName, depExtra, foundInfo, findDependent); + } + else { + diag.error("dependent dylib %lld not found for re-exported symbol %s", ordinal, symbolName); + return false; + } + } + foundInfo.kind = FoundSymbol::Kind::headerOffset; + foundInfo.isThreadLocal = false; + foundInfo.foundInDylib = header(); + foundInfo.foundExtra = extra; + foundInfo.value = read_uleb128(diag, p, trieEnd); + foundInfo.resolverFuncOffset = 0; + foundInfo.foundSymbolName = symbolName; + if ( diag.hasError() ) + return false; + switch ( flags & EXPORT_SYMBOL_FLAGS_KIND_MASK ) { + case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: + if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) { + foundInfo.kind = FoundSymbol::Kind::headerOffset; + foundInfo.resolverFuncOffset = (uint32_t)read_uleb128(diag, p, trieEnd); + } + else { + foundInfo.kind = FoundSymbol::Kind::headerOffset; + } + break; + case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: + foundInfo.isThreadLocal = true; + break; + case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: + foundInfo.kind = FoundSymbol::Kind::absolute; + break; + default: + diag.error("unsupported exported symbol kind. flags=%llu at node offset=0x%0lX", flags, (long)(node-trieStart)); + return false; + } + return true; + } + else { + // this is an old binary (before macOS 10.6), scan the symbol table + foundInfo.foundInDylib = nullptr; + uint64_t baseAddress = preferredLoadAddress(); + forEachGlobalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop) { + if ( strcmp(aSymbolName, symbolName) == 0 ) { + foundInfo.kind = FoundSymbol::Kind::headerOffset; + foundInfo.isThreadLocal = false; + foundInfo.foundInDylib = header(); + foundInfo.foundExtra = extra; + foundInfo.value = n_value - baseAddress; + foundInfo.resolverFuncOffset = 0; + foundInfo.foundSymbolName = symbolName; + stop = true; + } + }); + return (foundInfo.foundInDylib != nullptr); + } +} + + +void MachOParser::getLinkEditLoadCommands(Diagnostics& diag, LinkEditInfo& result) const +{ + result.dyldInfo = nullptr; + result.symTab = nullptr; + result.dynSymTab = nullptr; + result.splitSegInfo = nullptr; + result.functionStarts = nullptr; + result.dataInCode = nullptr; + result.codeSig = nullptr; + __block bool hasUUID = false; + __block bool hasVersion = false; + __block bool hasEncrypt = false; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + switch ( cmd->cmd ) { + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + if ( cmd->cmdsize != sizeof(dyld_info_command) ) + diag.error("LC_DYLD_INFO load command size wrong"); + else if ( result.dyldInfo != nullptr ) + diag.error("multiple LC_DYLD_INFO load commands"); + result.dyldInfo = (dyld_info_command*)cmd; + break; + case LC_SYMTAB: + if ( cmd->cmdsize != sizeof(symtab_command) ) + diag.error("LC_SYMTAB load command size wrong"); + else if ( result.symTab != nullptr ) + diag.error("multiple LC_SYMTAB load commands"); + result.symTab = (symtab_command*)cmd; + break; + case LC_DYSYMTAB: + if ( cmd->cmdsize != sizeof(dysymtab_command) ) + diag.error("LC_DYSYMTAB load command size wrong"); + else if ( result.dynSymTab != nullptr ) + diag.error("multiple LC_DYSYMTAB load commands"); + result.dynSymTab = (dysymtab_command*)cmd; + break; + case LC_SEGMENT_SPLIT_INFO: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + diag.error("LC_SEGMENT_SPLIT_INFO load command size wrong"); + else if ( result.splitSegInfo != nullptr ) + diag.error("multiple LC_SEGMENT_SPLIT_INFO load commands"); + result.splitSegInfo = (linkedit_data_command*)cmd; + break; + case LC_FUNCTION_STARTS: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + diag.error("LC_FUNCTION_STARTS load command size wrong"); + else if ( result.functionStarts != nullptr ) + diag.error("multiple LC_FUNCTION_STARTS load commands"); + result.functionStarts = (linkedit_data_command*)cmd; + break; + case LC_DATA_IN_CODE: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + diag.error("LC_DATA_IN_CODE load command size wrong"); + else if ( result.dataInCode != nullptr ) + diag.error("multiple LC_DATA_IN_CODE load commands"); + result.dataInCode = (linkedit_data_command*)cmd; + break; + case LC_CODE_SIGNATURE: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + diag.error("LC_CODE_SIGNATURE load command size wrong"); + else if ( result.codeSig != nullptr ) + diag.error("multiple LC_CODE_SIGNATURE load commands"); + result.codeSig = (linkedit_data_command*)cmd; + break; + case LC_UUID: + if ( cmd->cmdsize != sizeof(uuid_command) ) + diag.error("LC_UUID load command size wrong"); + else if ( hasUUID ) + diag.error("multiple LC_UUID load commands"); + hasUUID = true; + break; + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: + if ( cmd->cmdsize != sizeof(version_min_command) ) + diag.error("LC_VERSION_* load command size wrong"); + else if ( hasVersion ) + diag.error("multiple LC_VERSION_MIN_* load commands"); + hasVersion = true; + break; + case LC_BUILD_VERSION: + if ( cmd->cmdsize != (sizeof(build_version_command) + ((build_version_command*)cmd)->ntools * sizeof(build_tool_version)) ) + diag.error("LC_BUILD_VERSION load command size wrong"); + else if ( hasVersion ) + diag.error("multiple LC_BUILD_VERSION load commands"); + hasVersion = true; + break; + case LC_ENCRYPTION_INFO: + if ( cmd->cmdsize != sizeof(encryption_info_command) ) + diag.error("LC_ENCRYPTION_INFO load command size wrong"); + else if ( hasEncrypt ) + diag.error("multiple LC_ENCRYPTION_INFO load commands"); + else if ( is64() ) + diag.error("LC_ENCRYPTION_INFO found in 64-bit mach-o"); + hasEncrypt = true; + break; + case LC_ENCRYPTION_INFO_64: + if ( cmd->cmdsize != sizeof(encryption_info_command_64) ) + diag.error("LC_ENCRYPTION_INFO_64 load command size wrong"); + else if ( hasEncrypt ) + diag.error("multiple LC_ENCRYPTION_INFO_64 load commands"); + else if ( !is64() ) + diag.error("LC_ENCRYPTION_INFO_64 found in 32-bit mach-o"); + hasEncrypt = true; + break; + } + }); + if ( diag.noError() && (result.dynSymTab != nullptr) && (result.symTab == nullptr) ) + diag.error("LC_DYSYMTAB but no LC_SYMTAB load command"); + +} + +void MachOParser::getLinkEditPointers(Diagnostics& diag, LinkEditInfo& result) const +{ + getLinkEditLoadCommands(diag, result); + if ( diag.noError() ) + getLayoutInfo(result.layout); +} + +void MachOParser::forEachSegment(void (^callback)(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop)) const +{ + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* seg = (segment_command_64*)cmd; + callback(seg->segname, (uint32_t)seg->fileoff, (uint32_t)seg->filesize, seg->vmaddr, seg->vmsize, seg->initprot, stop); + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* seg = (segment_command*)cmd; + callback(seg->segname, seg->fileoff, seg->filesize, seg->vmaddr, seg->vmsize, seg->initprot, stop); + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + +const uint8_t* MachOParser::trieWalk(Diagnostics& diag, const uint8_t* start, const uint8_t* end, const char* symbol) +{ + uint32_t visitedNodeOffsets[128]; + int visitedNodeOffsetCount = 0; + visitedNodeOffsets[visitedNodeOffsetCount++] = 0; + const uint8_t* p = start; + while ( p < end ) { + uint64_t terminalSize = *p++; + if ( terminalSize > 127 ) { + // except for re-export-with-rename, all terminal sizes fit in one byte + --p; + terminalSize = read_uleb128(diag, p, end); + if ( diag.hasError() ) + return nullptr; + } + if ( (*symbol == '\0') && (terminalSize != 0) ) { + return p; + } + const uint8_t* children = p + terminalSize; + if ( children > end ) { + diag.error("malformed trie node, terminalSize=0x%llX extends past end of trie\n", terminalSize); + return nullptr; + } + uint8_t childrenRemaining = *children++; + p = children; + uint64_t nodeOffset = 0; + for (; childrenRemaining > 0; --childrenRemaining) { + const char* ss = symbol; + 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 ) { + diag.error("malformed trie node, child node extends past end of trie\n"); + return nullptr; + } + } + else { + // the symbol so far matches this edge (child) + // so advance to the child's node + ++p; + nodeOffset = read_uleb128(diag, p, end); + if ( diag.hasError() ) + return nullptr; + if ( (nodeOffset == 0) || ( &start[nodeOffset] > end) ) { + diag.error("malformed trie child, nodeOffset=0x%llX out of range\n", nodeOffset); + return nullptr; + } + symbol = ss; + break; + } + } + if ( nodeOffset != 0 ) { + if ( nodeOffset > (end-start) ) { + diag.error("malformed trie child, nodeOffset=0x%llX out of range\n", nodeOffset); + return nullptr; + } + for (int i=0; i < visitedNodeOffsetCount; ++i) { + if ( visitedNodeOffsets[i] == nodeOffset ) { + diag.error("malformed trie child, cycle to nodeOffset=0x%llX\n", nodeOffset); + return nullptr; + } + } + visitedNodeOffsets[visitedNodeOffsetCount++] = (uint32_t)nodeOffset; + if ( visitedNodeOffsetCount >= 128 ) { + diag.error("malformed trie too deep\n"); + return nullptr; + } + p = &start[nodeOffset]; + } + else + p = end; + } + return nullptr; +} + + +uint64_t MachOParser::read_uleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end) +{ + uint64_t result = 0; + int bit = 0; + do { + if ( p == end ) { + diag.error("malformed uleb128"); + break; + } + uint64_t slice = *p & 0x7f; + + if ( bit > 63 ) { + diag.error("uleb128 too big for uint64"); + break; + } + else { + result |= (slice << bit); + bit += 7; + } + } + while (*p++ & 0x80); + return result; +} + + +int64_t MachOParser::read_sleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end) +{ + int64_t result = 0; + int bit = 0; + uint8_t byte = 0; + do { + if ( p == end ) { + diag.error("malformed sleb128"); + break; + } + 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; +} + +bool MachOParser::is64() const +{ +#if DYLD_IN_PROCESS + return (sizeof(void*) == 8); +#else + return (header()->magic == MH_MAGIC_64); +#endif +} + + + + +bool MachOParser::findClosestSymbol(uint64_t targetUnslidAddress, const char** symbolName, uint64_t* symbolUnslidAddr) const +{ + Diagnostics diag; + __block uint64_t closestNValueSoFar = 0; + __block const char* closestNameSoFar = nullptr; + forEachGlobalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop) { + if ( n_value <= targetUnslidAddress ) { + if ( (closestNameSoFar == nullptr) || (closestNValueSoFar < n_value) ) { + closestNValueSoFar = n_value; + closestNameSoFar = aSymbolName; + } + } + }); + forEachLocalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop) { + if ( n_value <= targetUnslidAddress ) { + if ( (closestNameSoFar == nullptr) || (closestNValueSoFar < n_value) ) { + closestNValueSoFar = n_value; + closestNameSoFar = aSymbolName; + } + } + }); + if ( closestNameSoFar == nullptr ) { + return false; + } + + *symbolName = closestNameSoFar; + *symbolUnslidAddr = closestNValueSoFar; + return true; +} + + +#if DYLD_IN_PROCESS + +bool MachOParser::findClosestSymbol(const void* addr, const char** symbolName, const void** symbolAddress) const +{ + uint64_t slide = getSlide(); + uint64_t symbolUnslidAddr; + if ( findClosestSymbol((uint64_t)addr - slide, symbolName, &symbolUnslidAddr) ) { + *symbolAddress = (const void*)(long)(symbolUnslidAddr + slide); + return true; + } + return false; +} + +intptr_t MachOParser::getSlide() const +{ + Diagnostics diag; + __block intptr_t slide = 0; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { +#if __LP64__ + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* seg = (segment_command_64*)cmd; + if ( strcmp(seg->segname, "__TEXT") == 0 ) { + slide = ((uint64_t)header()) - seg->vmaddr; + stop = true; + } + } +#else + if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* seg = (segment_command*)cmd; + if ( strcmp(seg->segname, "__TEXT") == 0 ) { + slide = ((uint32_t)header()) - seg->vmaddr; + stop = true; + } + } +#endif + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + return slide; +} + +// this is only used by dlsym() at runtime. All other binding is done when the closure is built. +bool MachOParser::hasExportedSymbol(const char* symbolName, DependentFinder finder, void** result) const +{ + typedef void* (*ResolverFunc)(void); + ResolverFunc resolver; + Diagnostics diag; + FoundSymbol foundInfo; + if ( findExportedSymbol(diag, symbolName, (void*)header(), foundInfo, finder) ) { + switch ( foundInfo.kind ) { + case FoundSymbol::Kind::headerOffset: + *result = (uint8_t*)foundInfo.foundInDylib + foundInfo.value; + break; + case FoundSymbol::Kind::absolute: + *result = (void*)(long)foundInfo.value; + break; + case FoundSymbol::Kind::resolverOffset: + // foundInfo.value contains "stub". + // in dlsym() we want to call resolver function to get final function address + resolver = (ResolverFunc)((uint8_t*)foundInfo.foundInDylib + foundInfo.resolverFuncOffset); + *result = (*resolver)(); + break; + } + return true; + } + return false; +} + +const char* MachOParser::segmentName(uint32_t targetSegIndex) const +{ + __block const char* result = nullptr; + __block uint32_t segIndex = 0; + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( segIndex == targetSegIndex ) { + result = segName; + stop = true; + } + ++segIndex; + }); + return result; +} + +#else + + +bool MachOParser::uses16KPages() const +{ + return (header()->cputype == CPU_TYPE_ARM64); +} + + +bool MachOParser::isEncrypted() const +{ + __block bool result = false; + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* segCmd = (segment_command_64*)cmd; + if ( segCmd->flags & SG_PROTECTED_VERSION_1 ) { + result = true; + stop = true; + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + if ( segCmd->flags & SG_PROTECTED_VERSION_1 ) { + result = true; + stop = true; + } + } + else if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) { + const encryption_info_command* encCmd = (encryption_info_command*)cmd; + if ( encCmd->cryptid != 0 ) { + result = true; + stop = true; + } + } + }); + return result; +} + +bool MachOParser::hasWeakDefs() const +{ + return (header()->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK)); +} + +bool MachOParser::hasObjC() const +{ + __block bool result = false; + forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop) { + if ( (strncmp(sectionName, "__objc_imageinfo", 16) == 0) && (strncmp(segmentName, "__DATA", 6) == 0) ) { + result = true; + stop = true; + } + if ( (header()->cputype == CPU_TYPE_I386) && (strcmp(sectionName, "__image_info") == 0) && (strcmp(segmentName, "__OBJC") == 0) ) { + result = true; + stop = true; + } + }); + return result; +} + +bool MachOParser::hasPlusLoadMethod(Diagnostics& diag) const +{ +#if 1 + __block bool result = false; + forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop) { + if ( ( (flags & SECTION_TYPE) == S_CSTRING_LITERALS ) ) { + if (illegalSectionSize) { + diag.error("cstring section %s/%s extends beyond the end of the segment", segmentName, sectionName); + return; + } + const char* s = (char*)content; + const char* end = s + size; + while ( s < end ) { + if ( strcmp(s, "load") == 0 ) { + result = true; + stop = true; + return; + } + while (*s != '\0' ) + ++s; + ++s; + } + } + }); + return result; +#else + LayoutInfo layout; + getLayoutInfo(layout); + + __block bool hasSwift = false; + __block const void* classList = nullptr; + __block size_t classListSize = 0; + __block const void* objcData = nullptr; + __block size_t objcDataSize = 0; + __block const void* objcConstData = nullptr; + __block size_t objcConstDataSize = 0; + forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool& stop) { + if ( (strcmp(sectionName, "__objc_classlist") == 0) && (strncmp(segmentName, "__DATA", 6) == 0) ) { + classList = content; + classListSize = size; + } + if ( (strcmp(sectionName, "__objc_imageinfo") == 0) && (strncmp(segmentName, "__DATA", 6) == 0) ) { + const uint32_t* info = (uint32_t*)content; + uint8_t swiftVersion = (info[1] >> 8) & 0xFF; + if ( swiftVersion != 0 ) + hasSwift = true; + } + }); + if ( classList == nullptr ) + return false; + // FIXME: might be objc and swift intermixed + if ( hasSwift ) + return true; + const bool p64 = is64(); + const uint32_t pointerSize = (p64 ? 8 : 4); + const uint64_t* classArray64 = (uint64_t*)classList; + const uint32_t* classArray32 = (uint32_t*)classList; + const uint32_t classListCount = (uint32_t)(classListSize/pointerSize); + for (uint32_t i=0; i < classListCount; ++i) { + if ( p64 ) { + uint64_t classObjAddr = classArray64[i]; + const uint64_t* classObjContent = (uint64_t*)getContentForVMAddr(layout, classObjAddr); + uint64_t classROAddr = classObjContent[4]; + uint64_t metaClassObjAddr = classObjContent[0]; + const uint64_t* metaClassObjContent = (uint64_t*)getContentForVMAddr(layout, metaClassObjAddr); + uint64_t metaClassROObjAddr = metaClassObjContent[4]; + const uint64_t* metaClassROObjContent = (uint64_t*)getContentForVMAddr(layout, metaClassROObjAddr); + uint64_t metaClassMethodListAddr = metaClassROObjContent[4]; + if ( metaClassMethodListAddr != 0 ) { + const uint64_t* metaClassMethodListContent = (uint64_t*)getContentForVMAddr(layout, metaClassMethodListAddr); + const uint32_t methodListCount = ((uint32_t*)metaClassMethodListContent)[1]; + for (uint32_t m=0; m < methodListCount; ++m) { + uint64_t methodNameAddr = metaClassMethodListContent[m*3+1]; + const char* methodNameContent = (char*)getContentForVMAddr(layout, methodNameAddr); + if ( strcmp(methodNameContent, "load") == 0 ) { + return true; + } + } + } + } + else { + + } + } + + return false; +#endif +} + +bool MachOParser::getCDHash(uint8_t cdHash[20]) +{ + Diagnostics diag; + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() || (leInfo.codeSig == nullptr) ) + return false; + + return cdHashOfCodeSignature(getLinkEditContent(leInfo.layout, leInfo.codeSig->dataoff), leInfo.codeSig->datasize, cdHash); + } + +bool MachOParser::usesLibraryValidation() const +{ + Diagnostics diag; + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() || (leInfo.codeSig == nullptr) ) + return false; + + const CS_CodeDirectory* cd = (const CS_CodeDirectory*)findCodeDirectoryBlob(getLinkEditContent(leInfo.layout, leInfo.codeSig->dataoff), leInfo.codeSig->datasize); + if ( cd == nullptr ) + return false; + + // check for CS_REQUIRE_LV in CS_CodeDirectory.flags + return (htonl(cd->flags) & CS_REQUIRE_LV); + } + + +bool MachOParser::isRestricted() const +{ + __block bool result = false; + forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop) { + if ( (strcmp(segName, "__RESTRICT") == 0) && (strcmp(sectionName, "__restrict") == 0) ) { + result = true; + stop = true; + } + + }); + return result; +} + +bool MachOParser::hasCodeSignature(uint32_t& fileOffset, uint32_t& size) +{ + fileOffset = 0; + size = 0; + + // ignore code signatures in macOS binaries built with pre-10.9 tools + Platform platform; + uint32_t minOS; + uint32_t sdk; + if ( getPlatformAndVersion(&platform, &minOS, &sdk) ) { + // if have LC_VERSION_MIN_MACOSX and it says SDK < 10.9, so ignore code signature + if ( (platform == Platform::macOS) && (sdk < 0x000A0900) ) + return false; + } + else { + switch ( header()->cputype ) { + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: + // old binary with no LC_VERSION_*, assume intel binaries are old macOS binaries (ignore code signature) + return false; + } + } + + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_CODE_SIGNATURE ) { + const linkedit_data_command* sigCmd = (linkedit_data_command*)cmd; + fileOffset = sigCmd->dataoff; + size = sigCmd->datasize; + stop = true; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + return (fileOffset != 0); +} + +bool MachOParser::getEntry(uint32_t& offset, bool& usesCRT) +{ + Diagnostics diag; + offset = 0; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_MAIN ) { + entry_point_command* mainCmd = (entry_point_command*)cmd; + usesCRT = false; + offset = (uint32_t)mainCmd->entryoff; + stop = true; + } + else if ( cmd->cmd == LC_UNIXTHREAD ) { + stop = true; + usesCRT = true; + const uint32_t* regs32 = (uint32_t*)(((char*)cmd) + 16); + const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16); + uint64_t startAddress = 0; + switch ( header()->cputype ) { + case CPU_TYPE_I386: + startAddress = regs32[10]; // i386_thread_state_t.eip + break; + case CPU_TYPE_X86_64: + startAddress = regs64[16]; // x86_thread_state64_t.rip + break; + case CPU_TYPE_ARM: + startAddress = regs32[15]; // arm_thread_state_t.__pc + break; + case CPU_TYPE_ARM64: + startAddress = regs64[32]; // arm_thread_state64_t.__pc + break; + } + offset = (uint32_t)(startAddress - preferredLoadAddress()); + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + // FIXME: validate offset is into executable segment + return (offset != 0); +} + +bool MachOParser::canBePlacedInDyldCache(const std::string& path) const { + std::set reasons; + return canBePlacedInDyldCache(path, reasons); +} + +bool MachOParser::canBePlacedInDyldCache(const std::string& path, std::set& reasons) const +{ + bool retval = true; + // only dylibs can go in cache + if ( fileType() != MH_DYLIB ) { + reasons.insert("Not MH_DYLIB"); + return false; // cannot continue, installName() will assert() if not a dylib + } + + // only dylibs built for /usr/lib or /System/Library can go in cache + const char* dylibName = installName(); + if ( (strncmp(dylibName, "/usr/lib/", 9) != 0) && (strncmp(dylibName, "/System/Library/", 16) != 0) ) { + retval = false; + reasons.insert("Not in '/usr/lib/' or '/System/Library/'"); + } + + // flat namespace files cannot go in cache + if ( (header()->flags & MH_TWOLEVEL) == 0 ) { + retval = false; + reasons.insert("Not built with two level namespaces"); + } + + // don't put debug variants into dyld cache + if ( endsWith(path, "_profile.dylib") || endsWith(path, "_debug.dylib") || endsWith(path, "_profile") || endsWith(path, "_debug") || endsWith(path, "/CoreADI") ) { + retval = false; + reasons.insert("Variant image"); + } + + // dylib must have extra info for moving DATA and TEXT segments apart + __block bool hasExtraInfo = false; + __block bool hasDyldInfo = false; + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_SPLIT_INFO ) + hasExtraInfo = true; + if ( cmd->cmd == LC_DYLD_INFO_ONLY ) + hasDyldInfo = true; + }); + if ( !hasExtraInfo ) { + retval = false; + reasons.insert("Missing split seg info"); + } + if ( !hasDyldInfo ) { + retval = false; + reasons.insert("Old binary, missing dyld info"); + } + + // dylib can only depend on other dylibs in the shared cache + __block bool allDepPathsAreGood = true; + forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if ( (strncmp(loadPath, "/usr/lib/", 9) != 0) && (strncmp(loadPath, "/System/Library/", 16) != 0) ) { + allDepPathsAreGood = false; + stop = true; + } + }); + if ( !allDepPathsAreGood ) { + retval = false; + reasons.insert("Depends on cache inelegible dylibs"); + } + + // dylibs with interposing info cannot be in cache + __block bool hasInterposing = false; + forEachInterposingTuple(diag, ^(uint32_t segIndex, uint64_t replacementSegOffset, uint64_t replaceeSegOffset, uint64_t replacementContent, bool& stop) { + hasInterposing = true; + }); + if ( hasInterposing ) { + retval = false; + reasons.insert("Has interposing tuples"); + } + + return retval; +} + +bool MachOParser::isDynamicExecutable() const +{ + if ( fileType() != MH_EXECUTE ) + return false; + + // static executables do not have dyld load command + __block bool hasDyldLoad = false; + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_LOAD_DYLINKER ) { + hasDyldLoad = true; + stop = true; + } + }); + return hasDyldLoad; +} + + +bool MachOParser::isSlideable() const +{ + if ( header()->filetype == MH_DYLIB ) + return true; + if ( header()->filetype == MH_BUNDLE ) + return true; + if ( (header()->filetype == MH_EXECUTE) && (header()->flags & MH_PIE) ) + return true; + + return false; +} + + + +bool MachOParser::hasInitializer(Diagnostics& diag) const +{ + __block bool result = false; + forEachInitializer(diag, ^(uint32_t offset) { + result = true; + }); + return result; +} + +void MachOParser::forEachInitializer(Diagnostics& diag, void (^callback)(uint32_t offset)) const +{ + __block uint64_t textSegAddrStart = 0; + __block uint64_t textSegAddrEnd = 0; + + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( strcmp(segName, "__TEXT") == 0 ) { + textSegAddrStart = vmAddr; + textSegAddrEnd = vmAddr + vmSize; + stop = true; + } + }); + if ( textSegAddrStart == textSegAddrEnd ) { + diag.error("no __TEXT segment"); + return; + } + + // if dylib linked with -init linker option, that initializer is first + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_ROUTINES ) { + const routines_command* routines = (routines_command*)cmd; + uint64_t dashInit = routines->init_address; + if ( (textSegAddrStart < dashInit) && (dashInit < textSegAddrEnd) ) + callback((uint32_t)(dashInit - textSegAddrStart)); + else + diag.error("-init does not point within __TEXT segment"); + } + else if ( cmd->cmd == LC_ROUTINES_64 ) { + const routines_command_64* routines = (routines_command_64*)cmd; + uint64_t dashInit = routines->init_address; + if ( (textSegAddrStart < dashInit) && (dashInit < textSegAddrEnd) ) + callback((uint32_t)(dashInit - textSegAddrStart)); + else + diag.error("-init does not point within __TEXT segment"); + } + }); + + // next any function pointers in mod-init section + bool p64 = is64(); + unsigned pointerSize = p64 ? 8 : 4; + forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop) { + if ( (flags & SECTION_TYPE) == S_MOD_INIT_FUNC_POINTERS ) { + if ( (size % pointerSize) != 0 ) { + diag.error("initializer section %s/%s has bad size", segmentName, sectionName); + stop = true; + return; + } + if ( illegalSectionSize ) { + diag.error("initializer section %s/%s extends beyond the end of the segment", segmentName, sectionName); + stop = true; + return; + } + if ( ((long)content % pointerSize) != 0 ) { + diag.error("initializer section %s/%s is not pointer aligned", segmentName, sectionName); + stop = true; + return; + } + if ( p64 ) { + const uint64_t* initsStart = (uint64_t*)content; + const uint64_t* initsEnd = (uint64_t*)((uint8_t*)content + size); + for (const uint64_t* p=initsStart; p < initsEnd; ++p) { + uint64_t anInit = *p; + if ( (anInit <= textSegAddrStart) || (anInit > textSegAddrEnd) ) { + diag.error("initializer 0x%0llX does not point within __TEXT segment", anInit); + stop = true; + break; + } + callback((uint32_t)(anInit - textSegAddrStart)); + } + } + else { + const uint32_t* initsStart = (uint32_t*)content; + const uint32_t* initsEnd = (uint32_t*)((uint8_t*)content + size); + for (const uint32_t* p=initsStart; p < initsEnd; ++p) { + uint32_t anInit = *p; + if ( (anInit <= textSegAddrStart) || (anInit > textSegAddrEnd) ) { + diag.error("initializer 0x%0X does not point within __TEXT segment", anInit); + stop = true; + break; + } + callback(anInit - (uint32_t)textSegAddrStart); + } + } + } + }); +} + +void MachOParser::forEachDOFSection(Diagnostics& diag, void (^callback)(uint32_t offset)) const +{ + forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop) { + if ( ( (flags & SECTION_TYPE) == S_DTRACE_DOF ) && !illegalSectionSize ) { + callback((uint32_t)((uintptr_t)content - (uintptr_t)header())); + } + }); +} + + +uint32_t MachOParser::segmentCount() const +{ + __block uint32_t count = 0; + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + ++count; + }); + return count; +} + +void MachOParser::forEachSegment(void (^callback)(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop)) const +{ + Diagnostics diag; + __block uint32_t segIndex = 0; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* segCmd = (segment_command_64*)cmd; + uint64_t sizeOfSections = segCmd->vmsize; + uint8_t p2align = 0; + const section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64)); + const section_64* const sectionsEnd = §ionsStart[segCmd->nsects]; + for (const section_64* sect=sectionsStart; sect < sectionsEnd; ++sect) { + sizeOfSections = sect->addr + sect->size - segCmd->vmaddr; + if ( sect->align > p2align ) + p2align = sect->align; + } + callback(segCmd->segname, (uint32_t)segCmd->fileoff, (uint32_t)segCmd->filesize, segCmd->vmaddr, segCmd->vmsize, segCmd->initprot, segIndex, sizeOfSections, p2align, stop); + ++segIndex; + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + uint64_t sizeOfSections = segCmd->vmsize; + uint8_t p2align = 0; + const section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command)); + const section* const sectionsEnd = §ionsStart[segCmd->nsects]; + for (const section* sect=sectionsStart; sect < sectionsEnd; ++sect) { + sizeOfSections = sect->addr + sect->size - segCmd->vmaddr; + if ( sect->align > p2align ) + p2align = sect->align; + } + callback(segCmd->segname, (uint32_t)segCmd->fileoff, (uint32_t)segCmd->filesize, segCmd->vmaddr, segCmd->vmsize, segCmd->initprot, segIndex, sizeOfSections, p2align, stop); + ++segIndex; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + +void MachOParser::forEachExportedSymbol(Diagnostics diag, void (^handler)(const char* symbolName, uint64_t imageOffset, bool isReExport, bool& stop)) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + if ( leInfo.dyldInfo != nullptr ) { + const uint8_t* trieStart = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->export_off); + const uint8_t* trieEnd = trieStart + leInfo.dyldInfo->export_size; + std::vector exports; + if ( !ExportInfoTrie::parseTrie(trieStart, trieEnd, exports) ) { + diag.error("malformed exports trie"); + return; + } + bool stop = false; + for (const ExportInfoTrie::Entry& exp : exports) { + bool isReExport = (exp.info.flags & EXPORT_SYMBOL_FLAGS_REEXPORT); + handler(exp.name.c_str(), exp.info.address, isReExport, stop); + if ( stop ) + break; + } + } +} + +bool MachOParser::invalidRebaseState(Diagnostics& diag, const char* opcodeName, const MachOParser::LinkEditInfo& leInfo, + bool segIndexSet, uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type) const +{ + if ( !segIndexSet ) { + diag.error("%s missing preceding REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", opcodeName); + return true; + } + if ( segmentIndex >= leInfo.layout.segmentCount ) { + diag.error("%s segment index %d too large", opcodeName, segmentIndex); + return true; + } + if ( segmentOffset > (leInfo.layout.segments[segmentIndex].segSize-pointerSize) ) { + diag.error("%s current segment offset 0x%08llX beyond segment size (0x%08llX)", opcodeName, segmentOffset, leInfo.layout.segments[segmentIndex].segSize); + return true; + } + switch ( type ) { + case REBASE_TYPE_POINTER: + if ( !leInfo.layout.segments[segmentIndex].writable ) { + diag.error("%s pointer rebase is in non-writable segment", opcodeName); + return true; + } + if ( leInfo.layout.segments[segmentIndex].executable ) { + diag.error("%s pointer rebase is in executable segment", opcodeName); + return true; + } + break; + case REBASE_TYPE_TEXT_ABSOLUTE32: + case REBASE_TYPE_TEXT_PCREL32: + if ( !leInfo.layout.segments[segmentIndex].textRelocsAllowed ) { + diag.error("%s text rebase is in segment that does not support text relocations", opcodeName); + return true; + } + if ( leInfo.layout.segments[segmentIndex].writable ) { + diag.error("%s text rebase is in writable segment", opcodeName); + return true; + } + if ( !leInfo.layout.segments[segmentIndex].executable ) { + diag.error("%s pointer rebase is in non-executable segment", opcodeName); + return true; + } + break; + default: + diag.error("%s unknown rebase type %d", opcodeName, type); + return true; + } + return false; +} + +void MachOParser::forEachRebase(Diagnostics& diag, void (^handler)(uint32_t segIndex, uint64_t segOffset, uint8_t type, bool& stop)) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + if ( leInfo.dyldInfo != nullptr ) { + // work around linker bug that laid down rebase opcodes for lazy pointer section when -bind_at_load used + __block int lpSegIndex = 0; + __block uint64_t lpSegOffsetStart = 0; + __block uint64_t lpSegOffsetEnd = 0; + bool hasWeakBinds = (leInfo.dyldInfo->weak_bind_size != 0); + if ( leInfo.dyldInfo->lazy_bind_size == 0 ) { + __block uint64_t lpAddr = 0; + __block uint64_t lpSize = 0; + forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& sectStop) { + if ( (flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS ) { + lpAddr = addr; + lpSize = size; + sectStop = true; + } + }); + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& segStop) { + if ( (vmAddr <= lpAddr) && (vmAddr+vmSize >= lpAddr+lpSize) ) { + lpSegOffsetStart = lpAddr - vmAddr; + lpSegOffsetEnd = lpSegOffsetStart + lpSize; + segStop = true; + return; + } + ++lpSegIndex; + }); + } + // don't remove rebase if there is a weak-bind at pointer location + bool (^weakBindAt)(uint64_t segOffset) = ^(uint64_t segOffset) { + if ( !hasWeakBinds ) + return false; + __block bool result = false; + Diagnostics weakDiag; + forEachWeakDef(weakDiag, ^(bool strongDef, uint32_t dataSegIndex, uint64_t dataSegOffset, uint64_t addend, const char* symbolName, bool& weakStop) { + if ( segOffset == dataSegOffset ) { + result = true; + weakStop = true; + } + }); + return result; + }; + + + const uint8_t* p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->rebase_off); + const uint8_t* end = p + leInfo.dyldInfo->rebase_size; + const uint32_t pointerSize = (is64() ? 8 : 4); + uint8_t type = 0; + int segIndex = 0; + uint64_t segOffset = 0; + uint64_t count; + uint64_t skip; + bool segIndexSet = false; + bool stop = false; + while ( !stop && diag.noError() && (p < end) ) { + uint8_t immediate = *p & REBASE_IMMEDIATE_MASK; + uint8_t opcode = *p & REBASE_OPCODE_MASK; + ++p; + switch (opcode) { + case REBASE_OPCODE_DONE: + stop = true; + break; + case REBASE_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segIndex = immediate; + segOffset = read_uleb128(diag, p, end); + segIndexSet = true; + break; + case REBASE_OPCODE_ADD_ADDR_ULEB: + segOffset += read_uleb128(diag, p, end); + break; + case REBASE_OPCODE_ADD_ADDR_IMM_SCALED: + segOffset += immediate*pointerSize; + break; + case REBASE_OPCODE_DO_REBASE_IMM_TIMES: + for (int i=0; i < immediate; ++i) { + if ( invalidRebaseState(diag, "REBASE_OPCODE_DO_REBASE_IMM_TIMES", leInfo, segIndexSet, pointerSize, segIndex, segOffset, type) ) + return; + if ( (segIndex != lpSegIndex) || (segOffset > lpSegOffsetEnd) || (segOffset < lpSegOffsetStart) || weakBindAt(segOffset) ) + handler(segIndex, segOffset, type, stop); + segOffset += pointerSize; + } + break; + case REBASE_OPCODE_DO_REBASE_ULEB_TIMES: + count = read_uleb128(diag, p, end); + for (uint32_t i=0; i < count; ++i) { + if ( invalidRebaseState(diag, "REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", leInfo, segIndexSet, pointerSize, segIndex, segOffset, type) ) + return; + if ( (segIndex != lpSegIndex) || (segOffset > lpSegOffsetEnd) || (segOffset < lpSegOffsetStart) || weakBindAt(segOffset) ) + handler(segIndex, segOffset, type, stop); + segOffset += pointerSize; + } + break; + case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: + if ( invalidRebaseState(diag, "REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", leInfo, segIndexSet, pointerSize, segIndex, segOffset, type) ) + return; + handler(segIndex, segOffset, type, stop); + segOffset += read_uleb128(diag, p, end) + pointerSize; + break; + case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(diag, p, end); + if ( diag.hasError() ) + break; + skip = read_uleb128(diag, p, end); + for (uint32_t i=0; i < count; ++i) { + if ( invalidRebaseState(diag, "REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB", leInfo, segIndexSet, pointerSize, segIndex, segOffset, type) ) + return; + handler(segIndex, segOffset, type, stop); + segOffset += skip + pointerSize; + } + break; + default: + diag.error("unknown rebase opcode 0x%02X", opcode); + } + } + } + else { + // old binary + const relocation_info* const relocsStart = (relocation_info*)getLinkEditContent(leInfo.layout, leInfo.dynSymTab->locreloff); + const relocation_info* const relocsEnd = &relocsStart[leInfo.dynSymTab->nlocrel]; + bool stop = false; + const uint8_t relocSize = (is64() ? 3 : 2); + for (const relocation_info* reloc=relocsStart; (reloc < relocsEnd) && !stop; ++reloc) { + if ( reloc->r_length != relocSize ) { + diag.error("local relocation has wrong r_length"); + break; + } + if ( reloc->r_type != 0 ) { // 0 == X86_64_RELOC_UNSIGNED == GENERIC_RELOC_VANILLA == ARM64_RELOC_UNSIGNED + diag.error("local relocation has wrong r_type"); + break; + } + doLocalReloc(diag, reloc->r_address, stop, handler); + } + // then process indirect symbols + forEachIndirectPointer(diag, ^(uint32_t segIndex, uint64_t segOffset, bool bind, int bindLibOrdinal, + const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& indStop) { + if ( !bind && !bindLazy ) + handler(segIndex, segOffset, REBASE_TYPE_POINTER, indStop); + }); + } +} + +bool MachOParser::doLocalReloc(Diagnostics& diag, uint32_t r_address, bool& stop, void (^handler)(uint32_t segIndex, uint64_t segOffset, uint8_t type, bool& stop)) const +{ + bool firstWritable = (header()->cputype == CPU_TYPE_X86_64); + __block uint64_t relocBaseAddress = 0; + __block bool baseFound = false; + __block uint32_t segIndex = 0; + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool &stopSeg) { + if ( !baseFound ) { + if ( !firstWritable || (protections & VM_PROT_WRITE) ) { + baseFound = true; + relocBaseAddress = vmAddr; + } + } + if ( baseFound && (vmAddr < relocBaseAddress+r_address) && (relocBaseAddress+r_address < vmAddr+vmSize) ) { + uint8_t type = REBASE_TYPE_POINTER; + uint64_t segOffset = relocBaseAddress + r_address - vmAddr; + handler(segIndex, segOffset, type, stop); + stopSeg = true; + } + ++segIndex; + }); + + return false; +} + +int MachOParser::libOrdinalFromDesc(uint16_t n_desc) const +{ + // -flat_namespace is always flat lookup + if ( (header()->flags & MH_TWOLEVEL) == 0 ) + return BIND_SPECIAL_DYLIB_FLAT_LOOKUP; + + // extract byte from undefined symbol entry + int libIndex = GET_LIBRARY_ORDINAL(n_desc); + switch ( libIndex ) { + case SELF_LIBRARY_ORDINAL: + return BIND_SPECIAL_DYLIB_SELF; + + case DYNAMIC_LOOKUP_ORDINAL: + return BIND_SPECIAL_DYLIB_FLAT_LOOKUP; + + case EXECUTABLE_ORDINAL: + return BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE; + } + + return libIndex; +} + +bool MachOParser::doExternalReloc(Diagnostics& diag, uint32_t r_address, uint32_t r_symbolnum, LinkEditInfo& leInfo, bool& stop, + void (^handler)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, int libOrdinal, + uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop)) const +{ + const bool firstWritable = (header()->cputype == CPU_TYPE_X86_64); + const bool is64Bit = is64(); + __block uint64_t relocBaseAddress = 0; + __block bool baseFound = false; + __block uint32_t segIndex = 0; + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool &stopSeg) { + if ( !baseFound ) { + if ( !firstWritable || (protections & VM_PROT_WRITE) ) { + baseFound = true; + relocBaseAddress = vmAddr; + } + } + if ( baseFound && (vmAddr < relocBaseAddress+r_address) && (relocBaseAddress+r_address < vmAddr+vmSize) ) { + uint8_t type = BIND_TYPE_POINTER; + uint64_t segOffset = relocBaseAddress + r_address - vmAddr; + const void* symbolTable = getLinkEditContent(leInfo.layout, leInfo.symTab->symoff); + const struct nlist_64* symbols64 = (nlist_64*)symbolTable; + const struct nlist* symbols32 = (struct nlist*)symbolTable; + const char* stringPool = (char*)getLinkEditContent(leInfo.layout, leInfo.symTab->stroff); + uint32_t symCount = leInfo.symTab->nsyms; + uint32_t poolSize = leInfo.symTab->strsize; + if ( r_symbolnum < symCount ) { + uint16_t n_desc = is64Bit ? symbols64[r_symbolnum].n_desc : symbols32[r_symbolnum].n_desc; + uint32_t libOrdinal = libOrdinalFromDesc(n_desc); + uint32_t strOffset = is64Bit ? symbols64[r_symbolnum].n_un.n_strx : symbols32[r_symbolnum].n_un.n_strx; + if ( strOffset < poolSize ) { + const char* symbolName = stringPool + strOffset; + bool weakImport = (n_desc & N_WEAK_REF); + bool lazy = false; + uint64_t addend = is64Bit ? (*((uint64_t*)((char*)header()+fileOffset+segOffset))) : (*((uint32_t*)((char*)header()+fileOffset+segOffset))); + handler(segIndex, segOffset, type, libOrdinal, addend, symbolName, weakImport, lazy, stop); + stopSeg = true; + } + } + } + ++segIndex; + }); + + return false; +} + +bool MachOParser::invalidBindState(Diagnostics& diag, const char* opcodeName, const LinkEditInfo& leInfo, bool segIndexSet, bool libraryOrdinalSet, + uint32_t dylibCount, int libOrdinal, uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, const char* symbolName) const +{ + if ( !segIndexSet ) { + diag.error("%s missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", opcodeName); + return true; + } + if ( segmentIndex >= leInfo.layout.segmentCount ) { + diag.error("%s segment index %d too large", opcodeName, segmentIndex); + return true; + } + if ( segmentOffset > (leInfo.layout.segments[segmentIndex].segSize-pointerSize) ) { + diag.error("%s current segment offset 0x%08llX beyond segment size (0x%08llX)", opcodeName, segmentOffset, leInfo.layout.segments[segmentIndex].segSize); + return true; + } + if ( symbolName == NULL ) { + diag.error("%s missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM", opcodeName); + return true; + } + if ( !libraryOrdinalSet ) { + diag.error("%s missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL", opcodeName); + return true; + } + if ( libOrdinal > (int)dylibCount ) { + diag.error("%s has library ordinal too large (%d) max (%d)", opcodeName, libOrdinal, dylibCount); + return true; + } + if ( libOrdinal < -2 ) { + diag.error("%s has unknown library special ordinal (%d)", opcodeName, libOrdinal); + return true; + } + switch ( type ) { + case BIND_TYPE_POINTER: + if ( !leInfo.layout.segments[segmentIndex].writable ) { + diag.error("%s pointer bind is in non-writable segment", opcodeName); + return true; + } + if ( leInfo.layout.segments[segmentIndex].executable ) { + diag.error("%s pointer bind is in executable segment", opcodeName); + return true; + } + break; + case BIND_TYPE_TEXT_ABSOLUTE32: + case BIND_TYPE_TEXT_PCREL32: + if ( !leInfo.layout.segments[segmentIndex].textRelocsAllowed ) { + diag.error("%s text bind is in segment that does not support text relocations", opcodeName); + return true; + } + if ( leInfo.layout.segments[segmentIndex].writable ) { + diag.error("%s text bind is in writable segment", opcodeName); + return true; + } + if ( !leInfo.layout.segments[segmentIndex].executable ) { + diag.error("%s pointer bind is in non-executable segment", opcodeName); + return true; + } + break; + default: + diag.error("%s unknown bind type %d", opcodeName, type); + return true; + } + return false; +} + +void MachOParser::forEachBind(Diagnostics& diag, void (^handler)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, + int libOrdinal, uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop)) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + const uint32_t dylibCount = dependentDylibCount(); + + if ( leInfo.dyldInfo != nullptr ) { + // process bind opcodes + const uint8_t* p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->bind_off); + const uint8_t* end = p + leInfo.dyldInfo->bind_size; + const uint32_t pointerSize = (is64() ? 8 : 4); + uint8_t type = 0; + uint64_t segmentOffset = 0; + uint8_t segmentIndex = 0; + const char* symbolName = NULL; + int libraryOrdinal = 0; + bool segIndexSet = false; + bool libraryOrdinalSet = false; + + int64_t addend = 0; + uint64_t count; + uint64_t skip; + bool weakImport = false; + bool done = false; + bool stop = false; + while ( !done && !stop && diag.noError() && (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; + libraryOrdinalSet = true; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + libraryOrdinal = (int)read_uleb128(diag, p, end); + libraryOrdinalSet = true; + 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; + } + libraryOrdinalSet = true; + 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(diag, p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segmentIndex = immediate; + segmentOffset = read_uleb128(diag, p, end); + segIndexSet = true; + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + segmentOffset += read_uleb128(diag, p, end); + break; + case BIND_OPCODE_DO_BIND: + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, false, stop); + segmentOffset += pointerSize; + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, false, stop); + segmentOffset += read_uleb128(diag, p, end) + pointerSize; + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, false, stop); + segmentOffset += immediate*pointerSize + pointerSize; + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(diag, p, end); + skip = read_uleb128(diag, p, end); + for (uint32_t i=0; i < count; ++i) { + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, false, stop); + segmentOffset += skip + pointerSize; + } + break; + default: + diag.error("bad bind opcode 0x%02X", *p); + } + } + if ( diag.hasError() || stop ) + return; + // process lazy bind opcodes + if ( leInfo.dyldInfo->lazy_bind_size != 0 ) { + p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->lazy_bind_off); + end = p + leInfo.dyldInfo->lazy_bind_size; + type = BIND_TYPE_POINTER; + segmentOffset = 0; + segmentIndex = 0; + symbolName = NULL; + libraryOrdinal = 0; + segIndexSet = false; + libraryOrdinalSet= false; + addend = 0; + weakImport = false; + stop = false; + while ( !stop && diag.noError() && (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; + libraryOrdinalSet = true; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + libraryOrdinal = (int)read_uleb128(diag, p, end); + libraryOrdinalSet = true; + 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; + } + libraryOrdinalSet = true; + 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(diag, p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segmentIndex = immediate; + segmentOffset = read_uleb128(diag, p, end); + segIndexSet = true; + break; + case BIND_OPCODE_DO_BIND: + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, true, stop); + segmentOffset += pointerSize; + 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: + diag.error("bad lazy bind opcode 0x%02X", opcode); + break; + } + } + } + } + else { + // old binary, first process relocation + const relocation_info* const relocsStart = (relocation_info*)getLinkEditContent(leInfo.layout, leInfo.dynSymTab->extreloff); + const relocation_info* const relocsEnd = &relocsStart[leInfo.dynSymTab->nextrel]; + bool stop = false; + const uint8_t relocSize = (is64() ? 3 : 2); + for (const relocation_info* reloc=relocsStart; (reloc < relocsEnd) && !stop; ++reloc) { + if ( reloc->r_length != relocSize ) { + diag.error("external relocation has wrong r_length"); + break; + } + if ( reloc->r_type != 0 ) { // 0 == X86_64_RELOC_UNSIGNED == GENERIC_RELOC_VANILLA == ARM64_RELOC_UNSIGNED + diag.error("external relocation has wrong r_type"); + break; + } + doExternalReloc(diag, reloc->r_address, reloc->r_symbolnum, leInfo, stop, handler); + } + // then process indirect symbols + forEachIndirectPointer(diag, ^(uint32_t segIndex, uint64_t segOffset, bool bind, int bindLibOrdinal, + const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& indStop) { + if ( bind ) + handler(segIndex, segOffset, (selfModifyingStub ? BIND_TYPE_IMPORT_JMP_REL32 : BIND_TYPE_POINTER), bindLibOrdinal, 0, bindSymbolName, bindWeakImport, bindLazy, indStop); + }); + } +} + + +void MachOParser::forEachWeakDef(Diagnostics& diag, void (^handler)(bool strongDef, uint32_t dataSegIndex, uint64_t dataSegOffset, + uint64_t addend, const char* symbolName, bool& stop)) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + const uint32_t dylibCount = dependentDylibCount(); + if ( leInfo.dyldInfo != nullptr ) { + // process weak bind opcodes + const uint8_t* p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->weak_bind_off); + const uint8_t* end = p + leInfo.dyldInfo->weak_bind_size; + const uint32_t pointerSize = (is64() ? 8 : 4); + uint8_t type = 0; + uint64_t segmentOffset = 0; + uint8_t segmentIndex = 0; + const char* symbolName = NULL; + int64_t addend = 0; + uint64_t count; + uint64_t skip; + bool segIndexSet = false; + bool done = false; + bool stop = false; + while ( !done && !stop && diag.noError() && (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: + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + diag.error("unexpected dylib ordinal in weak binding info"); + return; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + symbolName = (char*)p; + while (*p != '\0') + ++p; + ++p; + if ( (immediate & BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION) != 0 ) + handler(true, 0, 0, 0, symbolName, stop); + break; + case BIND_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + addend = read_sleb128(diag, p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segmentIndex = immediate; + segmentOffset = read_uleb128(diag, p, end); + segIndexSet = true; + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + segmentOffset += read_uleb128(diag, p, end); + break; + case BIND_OPCODE_DO_BIND: + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, true, dylibCount, -2, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(false, segmentIndex, segmentOffset, addend, symbolName, stop); + segmentOffset += pointerSize; + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, true, dylibCount, -2, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(false, segmentIndex, segmentOffset, addend, symbolName, stop); + segmentOffset += read_uleb128(diag, p, end) + pointerSize; + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, true, dylibCount, -2, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(false, segmentIndex, segmentOffset, addend, symbolName, stop); + segmentOffset += immediate*pointerSize + pointerSize; + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(diag, p, end); + skip = read_uleb128(diag, p, end); + for (uint32_t i=0; i < count; ++i) { + if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, true, dylibCount, -2, pointerSize, segmentIndex, segmentOffset, type, symbolName) ) + return; + handler(false, segmentIndex, segmentOffset, addend, symbolName, stop); + segmentOffset += skip + pointerSize; + } + break; + default: + diag.error("bad weak bind opcode 0x%02X", *p); + } + } + if ( diag.hasError() || stop ) + return; + } + else { + // old binary + //assert(0 && "weak defs not supported for old binaries yet"); + } +} + + + +void MachOParser::forEachIndirectPointer(Diagnostics& diag, void (^handler)(uint32_t dataSegIndex, uint64_t dataSegOffset, bool bind, int bindLibOrdinal, + const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& stop)) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + // find lazy and non-lazy pointer sections + const bool is64Bit = is64(); + const uint32_t* const indirectSymbolTable = (uint32_t*)getLinkEditContent(leInfo.layout, leInfo.dynSymTab->indirectsymoff); + const uint32_t indirectSymbolTableCount = leInfo.dynSymTab->nindirectsyms; + const uint32_t pointerSize = is64Bit ? 8 : 4; + const void* symbolTable = getLinkEditContent(leInfo.layout, leInfo.symTab->symoff); + const struct nlist_64* symbols64 = (nlist_64*)symbolTable; + const struct nlist* symbols32 = (struct nlist*)symbolTable; + const char* stringPool = (char*)getLinkEditContent(leInfo.layout, leInfo.symTab->stroff); + uint32_t symCount = leInfo.symTab->nsyms; + uint32_t poolSize = leInfo.symTab->strsize; + __block bool stop = false; + forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, + uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& sectionStop) { + uint8_t sectionType = (flags & SECTION_TYPE); + if ( (sectionType != S_LAZY_SYMBOL_POINTERS) && (sectionType != S_NON_LAZY_SYMBOL_POINTERS) && (sectionType != S_SYMBOL_STUBS) ) + return; + bool selfModifyingStub = (sectionType == S_SYMBOL_STUBS) && (flags & S_ATTR_SELF_MODIFYING_CODE) && (reserved2 == 5) && (header()->cputype == CPU_TYPE_I386); + if ( (flags & S_ATTR_SELF_MODIFYING_CODE) && !selfModifyingStub ) { + diag.error("S_ATTR_SELF_MODIFYING_CODE section type only valid in old i386 binaries"); + sectionStop = true; + return; + } + uint32_t elementSize = selfModifyingStub ? reserved2 : pointerSize; + uint32_t elementCount = (uint32_t)(size/elementSize); + if (greaterThanAddOrOverflow(reserved1, elementCount, indirectSymbolTableCount)) { + diag.error("section %s overflows indirect symbol table", sectionName); + sectionStop = true; + return; + } + __block uint32_t index = 0; + __block uint32_t segIndex = 0; + __block uint64_t sectionSegOffset; + forEachSegment(^(const char* segmentName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool &segStop) { + if ( (vmAddr <= addr) && (addr < vmAddr+vmSize) ) { + sectionSegOffset = addr - vmAddr; + segIndex = index; + segStop = true; + } + ++index; + }); + + for (int i=0; (i < elementCount) && !stop; ++i) { + uint32_t symNum = indirectSymbolTable[reserved1 + i]; + if ( symNum == INDIRECT_SYMBOL_ABS ) + continue; + uint64_t segOffset = sectionSegOffset+i*elementSize; + if ( symNum == INDIRECT_SYMBOL_LOCAL ) { + handler(segIndex, segOffset, false, 0, "", false, false, false, stop); + continue; + } + if ( symNum > symCount ) { + diag.error("indirect symbol[%d] = %d which is invalid symbol index", reserved1 + i, symNum); + sectionStop = true; + return; + } + uint16_t n_desc = is64Bit ? symbols64[symNum].n_desc : symbols32[symNum].n_desc; + uint32_t libOrdinal = libOrdinalFromDesc(n_desc); + uint32_t strOffset = is64Bit ? symbols64[symNum].n_un.n_strx : symbols32[symNum].n_un.n_strx; + if ( strOffset > poolSize ) { + diag.error("symbol[%d] string offset out of range", reserved1 + i); + sectionStop = true; + return; + } + const char* symbolName = stringPool + strOffset; + bool weakImport = (n_desc & N_WEAK_REF); + bool lazy = (sectionType == S_LAZY_SYMBOL_POINTERS); + handler(segIndex, segOffset, true, libOrdinal, symbolName, weakImport, lazy, selfModifyingStub, stop); + } + sectionStop = stop; + }); +} + +void MachOParser::forEachInterposingTuple(Diagnostics& diag, void (^handler)(uint32_t segIndex, uint64_t replacementSegOffset, uint64_t replaceeSegOffset, uint64_t replacementContent, bool& stop)) const +{ + const bool is64Bit = is64(); + const unsigned entrySize = is64Bit ? 16 : 8; + const unsigned pointerSize = is64Bit ? 8 : 4; + forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& secStop) { + if ( ((flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sectionName, "__interpose") == 0) && (strcmp(segmentName, "__DATA") == 0)) ) { + if ( (size % entrySize) != 0 ) { + diag.error("interposing section %s/%s has bad size", segmentName, sectionName); + secStop = true; + return; + } + if ( illegalSectionSize ) { + diag.error("interposing section %s/%s extends beyond the end of the segment", segmentName, sectionName); + secStop = true; + return; + } + if ( ((long)content % pointerSize) != 0 ) { + diag.error("interposing section %s/%s is not pointer aligned", segmentName, sectionName); + secStop = true; + return; + } + __block uint32_t sectionSegIndex = 0; + __block uint64_t sectionSegOffset = 0; + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& segStop) { + if ( (vmAddr <= addr) && (addr < vmAddr+vmSize) ) { + sectionSegIndex = segIndex; + sectionSegOffset = addr - vmAddr; + segStop = true; + } + }); + if ( sectionSegIndex == 0 ) { + diag.error("interposing section %s/%s is not in a segment", segmentName, sectionName); + secStop = true; + return; + } + uint32_t offset = 0; + bool tupleStop = false; + for (int i=0; i < (size/entrySize); ++i) { + uint64_t replacementContent = is64Bit ? (*(uint64_t*)((char*)content + offset)) : (*(uint32_t*)((char*)content + offset)); + handler(sectionSegIndex, sectionSegOffset+offset, sectionSegOffset+offset+pointerSize, replacementContent, tupleStop); + offset += entrySize; + if ( tupleStop ) + break; + } + } + }); +} + + +const void* MachOParser::content(uint64_t vmOffset) +{ + __block const void* result = nullptr; + __block uint32_t firstSegFileOffset = 0; + __block uint64_t firstSegVmAddr = 0; + if ( isRaw() ) { + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool &stop) { + if ( firstSegFileOffset == 0) { + if ( fileSize == 0 ) + return; // skip __PAGEZERO + firstSegFileOffset = fileOffset; + firstSegVmAddr = vmAddr; + } + uint64_t segVmOffset = vmAddr - firstSegVmAddr; + if ( (vmOffset >= segVmOffset) && (vmOffset < segVmOffset+vmSize) ) { + result = (char*)(header()) + (fileOffset - firstSegFileOffset) + (vmOffset - segVmOffset); + stop = true; + } + }); + } + else if ( inRawCache() ) { + forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool &stop) { + if ( firstSegFileOffset == 0 ) { + firstSegFileOffset = fileOffset; + firstSegVmAddr = vmAddr; + } + uint64_t segVmOffset = vmAddr - firstSegVmAddr; + if ( (vmOffset >= segVmOffset) && (vmOffset < segVmOffset+vmSize) ) { + result = (char*)(header()) + (fileOffset - firstSegFileOffset) + (vmOffset - segVmOffset); + stop = true; + } + }); + } + else { + // non-raw cache is easy + result = (char*)(header()) + vmOffset; + } + return result; +} + +#endif // !DYLD_IN_PROCESS + +bool MachOParser::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) +{ + textOffset = 0; + size = 0; + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) { + const encryption_info_command* encCmd = (encryption_info_command*)cmd; + if ( encCmd->cryptid == 1 ) { + // Note: cryptid is 0 in just-built apps. The iTunes App Store sets cryptid to 1 + textOffset = encCmd->cryptoff; + size = encCmd->cryptsize; + } + stop = true; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + return (textOffset != 0); +} + +bool MachOParser::cdHashOfCodeSignature(const void* codeSigStart, size_t codeSignLen, uint8_t cdHash[20]) +{ + const CS_CodeDirectory* cd = (const CS_CodeDirectory*)findCodeDirectoryBlob(codeSigStart, codeSignLen); + if ( cd == nullptr ) + return false; + + uint32_t cdLength = htonl(cd->length); + if ( cd->hashType == CS_HASHTYPE_SHA256 ) { + uint8_t digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(cd, cdLength, digest); + // cd-hash of sigs that use SHA256 is the first 20 bytes of the SHA256 of the code digest + memcpy(cdHash, digest, 20); + return true; + } + else if ( cd->hashType == CS_HASHTYPE_SHA1 ) { + // compute hash directly into return buffer + CC_SHA1(cd, cdLength, cdHash); + return true; + } + + return false; +} + +const void* MachOParser::findCodeDirectoryBlob(const void* codeSigStart, size_t codeSignLen) +{ + // verify min length of overall code signature + if ( codeSignLen < sizeof(CS_SuperBlob) ) + return nullptr; + + // verify magic at start + const CS_SuperBlob* codeSuperBlob = (CS_SuperBlob*)codeSigStart; + if ( codeSuperBlob->magic != htonl(CSMAGIC_EMBEDDED_SIGNATURE) ) + return nullptr; + + // verify count of sub-blobs not too large + uint32_t subBlobCount = htonl(codeSuperBlob->count); + if ( (codeSignLen-sizeof(CS_SuperBlob))/sizeof(CS_BlobIndex) < subBlobCount ) + return nullptr; + + // walk each sub blob, looking at ones with type CSSLOT_CODEDIRECTORY + for (uint32_t i=0; i < subBlobCount; ++i) { + if ( codeSuperBlob->index[i].type != htonl(CSSLOT_CODEDIRECTORY) ) + continue; + uint32_t cdOffset = htonl(codeSuperBlob->index[i].offset); + // verify offset is not out of range + if ( cdOffset > (codeSignLen - sizeof(CS_CodeDirectory)) ) + return nullptr; + const CS_CodeDirectory* cd = (CS_CodeDirectory*)((uint8_t*)codeSuperBlob + cdOffset); + uint32_t cdLength = htonl(cd->length); + // verify code directory length not out of range + if ( cdLength > (codeSignLen - cdOffset) ) + return nullptr; + if ( cd->magic == htonl(CSMAGIC_CODEDIRECTORY) ) + return cd; + } + return nullptr; +} + + + + +} // namespace dyld3 + diff --git a/dyld3/MachOParser.h b/dyld3/MachOParser.h new file mode 100644 index 0000000..73b5ead --- /dev/null +++ b/dyld3/MachOParser.h @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2017 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 MachOParser_h +#define MachOParser_h + +#include +#include +#include + +#include +#include +#include + +#include "Diagnostics.h" + + +#define BIND_TYPE_IMPORT_JMP_REL32 4 + +namespace dyld3 { + +// Note, this should make PLATFORM_* values in +enum class Platform { + unknown = 0, + macOS = 1, + iOS = 2, + tvOS = 3, + watchOS = 4, + bridgeOS = 5 +}; + +struct VIS_HIDDEN UUID { + UUID() {} + UUID(const UUID& other) { uuid_copy(&_bytes[0], &other._bytes[0]); } + UUID(const uuid_t other_uuid) { uuid_copy(&_bytes[0], other_uuid); } + bool operator<(const UUID& other) const { return uuid_compare(&_bytes[0], &other._bytes[0]) < 0; } + bool operator==(const UUID& other) const { return uuid_compare(&_bytes[0], &other._bytes[0]) == 0; } + bool operator!=(const UUID& other) const { return !(*this == other); } + + size_t hash() const + { + size_t retval = 0; + for (auto i = 0; i < 16 / sizeof(size_t); ++i) { + retval ^= ((size_t*)(&_bytes[0]))[i]; + } + return retval; + } + const unsigned char* get() const { return &_bytes[0]; }; +private: + std::array _bytes; +}; + +class VIS_HIDDEN MachOParser +{ +public: +#if !DYLD_IN_PROCESS + static bool isValidMachO(Diagnostics& diag, const std::string& archName, Platform platform, const void* fileContent, size_t fileLength, const std::string& pathOpened, bool ignoreMainExecutables); + static bool isArch(const mach_header* mh, const std::string& archName); + static std::string archName(uint32_t cputype, uint32_t cpusubtype); + static std::string platformName(Platform platform); + static std::string versionString(uint32_t packedVersion); + static uint32_t cpuTypeFromArchName(const std::string& archName); + static uint32_t cpuSubtypeFromArchName(const std::string& archName); +#else + static bool isMachO(Diagnostics& diag, const void* fileContent, size_t fileLength); + static bool wellFormedMachHeaderAndLoadCommands(const mach_header* mh); +#endif + MachOParser(const mach_header* mh, bool dyldCacheIsRaw=false); + bool valid(Diagnostics& diag); + + const mach_header* header() const; + uint32_t fileType() const; + std::string archName() const; + bool is64() const; + bool inDyldCache() const; + bool hasThreadLocalVariables() const; + Platform platform() const; + uint64_t preferredLoadAddress() const; + UUID uuid() const; + bool getUuid(uuid_t uuid) const; + bool getPlatformAndVersion(Platform* platform, uint32_t* minOS, uint32_t* sdk) const; + bool isSimulatorBinary() const; + bool getDylibInstallName(const char** installName, uint32_t* compatVersion, uint32_t* currentVersion) const; + const char* installName() const; + uint32_t dependentDylibCount() const; + const char* dependentDylibLoadPath(uint32_t depIndex) const; + void forEachDependentDylib(void (^callback)(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop)) const; + void forEachSection(void (^callback)(const char* segName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop)) const; + void forEachSegment(void (^callback)(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop)) const; + void forEachGlobalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const; + void forEachLocalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const; + void forEachRPath(void (^callback)(const char* rPath, bool& stop)) const; + void forEachSection(void (^callback)(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, + uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop)) const; + + struct FoundSymbol { + enum class Kind { headerOffset, absolute, resolverOffset }; + Kind kind; + bool isThreadLocal; + const mach_header* foundInDylib; + void* foundExtra; + uint64_t value; + uint32_t resolverFuncOffset; + const char* foundSymbolName; + }; + + typedef bool (^DependentFinder)(uint32_t depIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra); + + bool findExportedSymbol(Diagnostics& diag, const char* symbolName, void* extra, FoundSymbol& foundInfo, DependentFinder finder) const; + bool findClosestSymbol(uint64_t unSlidAddr, const char** symbolName, uint64_t* symbolUnslidAddr) const; + bool isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size); + +#if DYLD_IN_PROCESS + intptr_t getSlide() const; + bool hasExportedSymbol(const char* symbolName, DependentFinder finder, void** result) const; + bool findClosestSymbol(const void* addr, const char** symbolName, const void** symbolAddress) const; + const char* segmentName(uint32_t segIndex) const; +#else + + bool uses16KPages() const; + bool hasObjC() const; + bool hasWeakDefs() const; + bool isEncrypted() const; + bool hasPlusLoadMethod(Diagnostics& diag) const; + bool hasInitializer(Diagnostics& diag) const; + bool getCDHash(uint8_t cdHash[20]); + bool hasCodeSignature(uint32_t& fileOffset, uint32_t& size); + bool usesLibraryValidation() const; + bool isRestricted() const; + bool getEntry(uint32_t& offset, bool& usesCRT); + bool canBePlacedInDyldCache(const std::string& path) const; + bool canBePlacedInDyldCache(const std::string& path, std::set& reasons) const; + bool isDynamicExecutable() const; + bool isSlideable() const; + void forEachInitializer(Diagnostics& diag, void (^callback)(uint32_t offset)) const; + void forEachDOFSection(Diagnostics& diag, void (^callback)(uint32_t offset)) const; + uint32_t segmentCount() const; + void forEachExportedSymbol(Diagnostics diag, void (^callback)(const char* symbolName, uint64_t imageOffset, bool isReExport, bool& stop)) const; + void forEachSegment(void (^callback)(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop)) const; + void forEachRebase(Diagnostics& diag, void (^callback)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, bool& stop)) const; + void forEachBind(Diagnostics& diag, void (^callback)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, int libOrdinal, + uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop)) const; + void forEachWeakDef(Diagnostics& diag, void (^callback)(bool strongDef, uint32_t dataSegIndex, uint64_t dataSegOffset, + uint64_t addend, const char* symbolName, bool& stop)) const; + void forEachIndirectPointer(Diagnostics& diag, void (^handler)(uint32_t dataSegIndex, uint64_t dataSegOffset, bool bind, int bindLibOrdinal, + const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& stop)) const; + void forEachInterposingTuple(Diagnostics& diag, void (^handler)(uint32_t segIndex, uint64_t replacementSegOffset, uint64_t replaceeSegOffset, uint64_t replacementContent, bool& stop)) const; + const void* content(uint64_t vmOffset); +#endif + + static const uint8_t* trieWalk(Diagnostics& diag, const uint8_t* start, const uint8_t* end, const char* symbol); + static uint64_t read_uleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end); + static int64_t read_sleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end); + static bool cdHashOfCodeSignature(const void* codeSigStart, size_t codeSignLen, uint8_t cdHash[20]); + static Platform currentPlatform(); + +private: + struct LayoutInfo { +#if DYLD_IN_PROCESS + uintptr_t slide; + uintptr_t textUnslidVMAddr; + uintptr_t linkeditUnslidVMAddr; + uint32_t linkeditFileOffset; +#else + uint32_t segmentCount; + uint32_t linkeditSegIndex; + struct { + uint64_t mappingOffset; + uint64_t fileOffset; + uint64_t fileSize; + uint64_t segUnslidAddress; + uint64_t writable : 1, + executable : 1, + textRelocsAllowed : 1, // segment supports text relocs (i386 only) + segSize : 61; + } segments[128]; +#endif + }; + + struct LinkEditInfo + { + const dyld_info_command* dyldInfo; + const symtab_command* symTab; + const dysymtab_command* dynSymTab; + const linkedit_data_command* splitSegInfo; + const linkedit_data_command* functionStarts; + const linkedit_data_command* dataInCode; + const linkedit_data_command* codeSig; + LayoutInfo layout; + }; + + void getLinkEditPointers(Diagnostics& diag, LinkEditInfo&) const; + void getLinkEditLoadCommands(Diagnostics& diag, LinkEditInfo& result) const; + void getLayoutInfo(LayoutInfo&) const; + const uint8_t* getLinkEditContent(const LayoutInfo& info, uint32_t fileOffset) const; + +#if !DYLD_IN_PROCESS + struct ArchInfo + { + const char* name; + uint32_t cputype; + uint32_t cpusubtype; + }; + static const ArchInfo _s_archInfos[]; + + const uint8_t* getContentForVMAddr(const LayoutInfo& info, uint64_t vmAddr) const; + bool doLocalReloc(Diagnostics& diag, uint32_t r_address, bool& stop, void (^callback)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, bool& stop)) const; + uint8_t relocPointerType() const; + int libOrdinalFromDesc(uint16_t n_desc) const; + bool doExternalReloc(Diagnostics& diag, uint32_t r_address, uint32_t r_symbolnum, LinkEditInfo& leInfo, bool& stop, + void (^callback)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, int libOrdinal, + uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop)) const; + bool validLoadCommands(Diagnostics& diag, size_t fileLen); + bool validEmbeddedPaths(Diagnostics& diag); + bool validSegments(Diagnostics& diag, size_t fileLen); + bool validLinkeditLayout(Diagnostics& diag); + bool invalidBindState(Diagnostics& diag, const char* opcodeName, const MachOParser::LinkEditInfo& leInfo, bool segIndexSet, bool libraryOrdinalSet, + uint32_t dylibCount, int libOrdinal, uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, const char* symbolName) const; + bool invalidRebaseState(Diagnostics& diag, const char* opcodeName, const MachOParser::LinkEditInfo& leInfo, bool segIndexSet, + uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type) const; +#endif + static const void* findCodeDirectoryBlob(const void* codeSigStart, size_t codeSignLen); + void forEachSection(void (^callback)(const char* segName, uint32_t segIndex, uint64_t segVMAddr, const char* sectionName, uint32_t sectFlags, + uint64_t sectAddr, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop)) const; + + void forEachLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& stop)) const; + bool isRaw() const; + bool inRawCache() const; + + long _data; // if low bit true, then this is raw file (not loaded image) +}; + + + +class VIS_HIDDEN FatUtil +{ +public: + static bool isFatFile(const void* fileStart); + static void forEachSlice(Diagnostics& diag, const void* fileContent, size_t fileLen, void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSize, bool& stop)); +#if !DYLD_IN_PROCESS + static bool isFatFileWithSlice(Diagnostics& diag, const void* fileContent, size_t fileLen, const std::string& archName, size_t& sliceOffset, size_t& sliceLen, bool& missingSlice); +#endif +}; + + +} // namespace dyld3 + +namespace std { +template <> +struct hash { + size_t operator()(const dyld3::UUID& x) const + { + return x.hash(); + } +}; +} + +#endif // MachOParser_h diff --git a/dyld3/PathOverrides.cpp b/dyld3/PathOverrides.cpp new file mode 100644 index 0000000..2516175 --- /dev/null +++ b/dyld3/PathOverrides.cpp @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2017 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 "PathOverrides.h" + + + +namespace dyld3 { + +#if BUILDING_LIBDYLD +PathOverrides gPathOverrides; +#endif + + +// based on ANSI-C strstr() +static const char* strrstr(const char* str, const char* sub) +{ + const size_t sublen = strlen(sub); + for(const char* p = &str[strlen(str)]; p != str; --p) { + if ( strncmp(p, sub, sublen) == 0 ) + return p; + } + return NULL; +} + + +#if DYLD_IN_PROCESS +void PathOverrides::setEnvVars(const char* envp[]) +{ + for (const char** p = envp; *p != NULL; p++) { + addEnvVar(*p); + } +} + +#else +PathOverrides::PathOverrides(const std::vector& env) +{ + for (const std::string& envVar : env) { + addEnvVar(envVar.c_str()); + } +} +#endif + +#if !BUILDING_LIBDYLD +// libdyld is never unloaded +PathOverrides::~PathOverrides() +{ + freeArray(_dylibPathOverrides); + freeArray(_frameworkPathOverrides); + freeArray(_frameworkPathFallbacks); + freeArray(_dylibPathFallbacks); +} +#endif + + +void PathOverrides::handleEnvVar(const char* key, const char* value, void (^handler)(const char* envVar)) const +{ + if ( value == nullptr ) + return; + size_t allocSize = strlen(key) + strlen(value) + 2; + char buffer[allocSize]; + strlcpy(buffer, key, allocSize); + strlcat(buffer, "=", allocSize); + strlcat(buffer, value, allocSize); + handler(buffer); +} + +void PathOverrides::handleListEnvVar(const char* key, const char** list, void (^handler)(const char* envVar)) const +{ + if ( list == nullptr ) + return; + size_t allocSize = strlen(key) + 2; + for (const char** lp=list; *lp != nullptr; ++lp) + allocSize += strlen(*lp)+1; + char buffer[allocSize]; + strlcpy(buffer, key, allocSize); + strlcat(buffer, "=", allocSize); + bool needColon = false; + for (const char** lp=list; *lp != nullptr; ++lp) { + if ( needColon ) + strlcat(buffer, ":", allocSize); + strlcat(buffer, *lp, allocSize); + needColon = true; + } + handler(buffer); +} + +void PathOverrides::forEachEnvVar(void (^handler)(const char* envVar)) const +{ + handleListEnvVar("DYLD_LIBRARY_PATH", _dylibPathOverrides, handler); + handleListEnvVar("DYLD_FRAMEWORK_PATH", _frameworkPathOverrides, handler); + handleListEnvVar("DYLD_FALLBACK_FRAMEWORK_PATH", _frameworkPathFallbacks, handler); + handleListEnvVar("DYLD_FALLBACK_LIBRARY_PATH", _dylibPathFallbacks, handler); + handleListEnvVar("DYLD_INSERT_LIBRARIES", _insertedDylibs, handler); + handleEnvVar( "DYLD_IMAGE_SUFFIX", _imageSuffix, handler); + handleEnvVar( "DYLD_ROOT_PATH", _rootPath, handler); +} + +uint32_t PathOverrides::envVarCount() const +{ + uint32_t count = 0; + if ( _dylibPathOverrides != nullptr ) + ++count; + if ( _frameworkPathOverrides != nullptr ) + ++count; + if ( _frameworkPathFallbacks != nullptr ) + ++count; + if ( _dylibPathFallbacks != nullptr ) + ++count; + if ( _insertedDylibs != nullptr ) + ++count; + if ( _imageSuffix != nullptr ) + ++count; + if ( _rootPath != nullptr ) + ++count; + return count; +} + +void PathOverrides::forEachInsertedDylib(void (^handler)(const char* dylibPath)) const +{ + if ( _insertedDylibs == nullptr ) + return; + for (const char** lp=_insertedDylibs; *lp != nullptr; ++lp) + handler(*lp); +} + +void PathOverrides::addEnvVar(const char* keyEqualsValue) +{ + const char* equals = strchr(keyEqualsValue, '='); + if ( equals != NULL ) { + const char* value = &equals[1]; + const size_t keyLen = equals-keyEqualsValue; + char key[keyLen+1]; + strncpy(key, keyEqualsValue, keyLen); + key[keyLen] = '\0'; + if ( strcmp(key, "DYLD_LIBRARY_PATH") == 0 ) { + _dylibPathOverrides = parseColonListIntoArray(value); + } + else if ( strcmp(key, "DYLD_FRAMEWORK_PATH") == 0 ) { + _frameworkPathOverrides = parseColonListIntoArray(value); + } + else if ( strcmp(key, "DYLD_FALLBACK_FRAMEWORK_PATH") == 0 ) { + _frameworkPathFallbacks = parseColonListIntoArray(value); + } + else if ( strcmp(key, "DYLD_FALLBACK_LIBRARY_PATH") == 0 ) { + _dylibPathFallbacks = parseColonListIntoArray(value); + } + else if ( strcmp(key, "DYLD_INSERT_LIBRARIES") == 0 ) { + _insertedDylibs = parseColonListIntoArray(value); + } + else if ( strcmp(key, "DYLD_IMAGE_SUFFIX") == 0 ) { + _imageSuffix = value; + } + else if ( strcmp(key, "DYLD_ROOT_PATH") == 0 ) { + _rootPath = value; + } + } +} + +void PathOverrides::forEachInColonList(const char* list, void (^handler)(const char* path)) +{ + char buffer[strlen(list)+1]; + const char* t = list; + for (const char* s=list; *s != '\0'; ++s) { + if (*s != ':') + continue; + size_t len = s - t; + memcpy(buffer, t, len); + buffer[len] = '\0'; + handler(buffer); + t = s+1; + } + handler(t); +} + +const char** PathOverrides::parseColonListIntoArray(const char* list) +{ + __block int count = 1; + forEachInColonList(list, ^(const char* path) { + ++count; + }); + const char** array = (const char**)malloc(count*sizeof(char*)); + __block const char** p = array; + forEachInColonList(list, ^(const char* path) { + *p++ = strdup(path); + }); + *p = nullptr; + return array; +} + +void PathOverrides::freeArray(const char** array) +{ + if ( array == nullptr ) + return; + + for (const char** p=array; *p != nullptr; ++p) { + free((void*)*p); + } + free(array); +} + +void PathOverrides::forEachDylibFallback(Platform platform, void (^handler)(const char* fallbackDir, bool& stop)) const +{ + bool stop = false; + if ( _dylibPathFallbacks != nullptr ) { + for (const char** fp=_dylibPathFallbacks; *fp != nullptr; ++fp) { + handler(*fp, stop); + if ( stop ) + return; + } + } + else { + switch ( platform ) { + case Platform::macOS: + // "$HOME/lib" + handler("/usr/local/lib", stop); // FIXME: not for restricted processes + if ( !stop ) + handler("/usr/lib", stop); + break; + case Platform::iOS: + case Platform::watchOS: + case Platform::tvOS: + case Platform::bridgeOS: + case Platform::unknown: + handler("/usr/local/lib", stop); + if ( !stop ) + handler("/usr/lib", stop); + break; + } + } +} + +void PathOverrides::forEachFrameworkFallback(Platform platform, void (^handler)(const char* fallbackDir, bool& stop)) const +{ + bool stop = false; + if ( _frameworkPathFallbacks != nullptr ) { + for (const char** fp=_frameworkPathFallbacks; *fp != nullptr; ++fp) { + handler(*fp, stop); + if ( stop ) + return; + } + } + else { + switch ( platform ) { + case Platform::macOS: + // "$HOME/Library/Frameworks" + handler("/Library/Frameworks", stop); // FIXME: not for restricted processes + // "/Network/Library/Frameworks" + if ( !stop ) + handler("/System/Library/Frameworks", stop); + break; + case Platform::iOS: + case Platform::watchOS: + case Platform::tvOS: + case Platform::bridgeOS: + case Platform::unknown: + handler("/System/Library/Frameworks", stop); + break; + } + } +} + +void PathOverrides::forEachPathVariant(const char* initialPath, +#if !DYLD_IN_PROCESS + Platform platform, +#endif + void (^handler)(const char* possiblePath, bool& stop)) const +{ +#if DYLD_IN_PROCESS + Platform platform = MachOParser::currentPlatform(); +#endif + __block bool stop = false; + + // check for overrides + const char* frameworkPartialPath = getFrameworkPartialPath(initialPath); + if ( frameworkPartialPath != nullptr ) { + const size_t frameworkPartialPathLen = strlen(frameworkPartialPath); + // look at each DYLD_FRAMEWORK_PATH directory + if ( _frameworkPathOverrides != nullptr ) { + for (const char** fp=_frameworkPathOverrides; *fp != nullptr; ++fp) { + char npath[strlen(*fp)+frameworkPartialPathLen+8]; + strcpy(npath, *fp); + strcat(npath, "/"); + strcat(npath, frameworkPartialPath); + handler(npath, stop); + if ( stop ) + return; + } + } + } + else { + const char* libraryLeafName = getLibraryLeafName(initialPath); + const size_t libraryLeafNameLen = strlen(libraryLeafName); + // look at each DYLD_LIBRARY_PATH directory + if ( _dylibPathOverrides != nullptr ) { + for (const char** lp=_dylibPathOverrides; *lp != nullptr; ++lp) { + char libpath[strlen(*lp)+libraryLeafNameLen+8]; + strcpy(libpath, *lp); + strcat(libpath, "/"); + strcat(libpath, libraryLeafName); + handler(libpath, stop); + if ( stop ) + return; + } + } + } + + // try original path + handler(initialPath, stop); + if ( stop ) + return; + + // check fallback paths + if ( frameworkPartialPath != nullptr ) { + const size_t frameworkPartialPathLen = strlen(frameworkPartialPath); + // look at each DYLD_FALLBACK_FRAMEWORK_PATH directory + forEachFrameworkFallback(platform, ^(const char* dir, bool& innerStop) { + char npath[strlen(dir)+frameworkPartialPathLen+8]; + strcpy(npath, dir); + strcat(npath, "/"); + strcat(npath, frameworkPartialPath); + handler(npath, innerStop); + if ( innerStop ) + stop = innerStop; + }); + + } + else { + const char* libraryLeafName = getLibraryLeafName(initialPath); + const size_t libraryLeafNameLen = strlen(libraryLeafName); + // look at each DYLD_FALLBACK_LIBRARY_PATH directory + forEachDylibFallback(platform, ^(const char* dir, bool& innerStop) { + char libpath[strlen(dir)+libraryLeafNameLen+8]; + strcpy(libpath, dir); + strcat(libpath, "/"); + strcat(libpath, libraryLeafName); + handler(libpath, innerStop); + if ( innerStop ) + stop = innerStop; + }); + } +} + + +// +// Find framework path +// +// /path/foo.framework/foo => foo.framework/foo +// /path/foo.framework/Versions/A/foo => foo.framework/Versions/A/foo +// /path/foo.framework/Frameworks/bar.framework/bar => bar.framework/bar +// /path/foo.framework/Libraries/bar.dylb => NULL +// /path/foo.framework/bar => NULL +// +// Returns nullptr if not a framework path +// +const char* PathOverrides::getFrameworkPartialPath(const char* path) const +{ + const char* dirDot = strrstr(path, ".framework/"); + if ( dirDot != nullptr ) { + const char* dirStart = dirDot; + for ( ; dirStart >= path; --dirStart) { + if ( (*dirStart == '/') || (dirStart == path) ) { + const char* frameworkStart = &dirStart[1]; + if ( dirStart == path ) + --frameworkStart; + size_t len = dirDot - frameworkStart; + char framework[len+1]; + strncpy(framework, frameworkStart, len); + framework[len] = '\0'; + const char* leaf = strrchr(path, '/'); + if ( leaf != nullptr ) { + if ( strcmp(framework, &leaf[1]) == 0 ) { + return frameworkStart; + } + if ( _imageSuffix != nullptr ) { + // some debug frameworks have install names that end in _debug + if ( strncmp(framework, &leaf[1], len) == 0 ) { + if ( strcmp( _imageSuffix, &leaf[len+1]) == 0 ) + return frameworkStart; + } + } + } + } + } + } + return nullptr; +} + + +const char* PathOverrides::getLibraryLeafName(const char* path) +{ + const char* start = strrchr(path, '/'); + if ( start != nullptr ) + return &start[1]; + else + return path; +} + +} // namespace dyld3 + + + + + diff --git a/dyld3/PathOverrides.h b/dyld3/PathOverrides.h new file mode 100644 index 0000000..4ae7407 --- /dev/null +++ b/dyld3/PathOverrides.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2017 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_PATH_OVERRIDES_H__ +#define __DYLD_PATH_OVERRIDES_H__ + +#include + +#if !DYLD_IN_PROCESS +#include +#include +#endif + +#include "Logging.h" +#include "MachOParser.h" + + +namespace dyld3 { + +class VIS_HIDDEN PathOverrides +{ +public: +#if !BUILDING_LIBDYLD + // libdyld is never unloaded + ~PathOverrides(); +#endif + +#if DYLD_IN_PROCESS + void setEnvVars(const char* envp[]); + void forEachPathVariant(const char* initialPath, void (^handler)(const char* possiblePath, bool& stop)) const; +#else + PathOverrides(const std::vector& env); + void forEachPathVariant(const char* initialPath, Platform platform, void (^handler)(const char* possiblePath, bool& stop)) const; +#endif + + uint32_t envVarCount() const; + void forEachEnvVar(void (^handler)(const char* envVar)) const; + void forEachInsertedDylib(void (^handler)(const char* dylibPath)) const; + +private: + void forEachInColonList(const char* list, void (^callback)(const char* path)); + const char** parseColonListIntoArray(const char* list); + void freeArray(const char** array); + void addEnvVar(const char* keyEqualsValue); + const char* getFrameworkPartialPath(const char* path) const; + static const char* getLibraryLeafName(const char* path); + void handleListEnvVar(const char* key, const char** list, void (^handler)(const char* envVar)) const; + void handleEnvVar(const char* key, const char* value, void (^handler)(const char* envVar)) const; + void forEachDylibFallback(Platform platform, void (^handler)(const char* fallbackDir, bool& stop)) const; + void forEachFrameworkFallback(Platform platform, void (^handler)(const char* fallbackDir, bool& stop)) const; + + const char** _dylibPathOverrides = nullptr; + const char** _frameworkPathOverrides = nullptr; + const char** _dylibPathFallbacks = nullptr; + const char** _frameworkPathFallbacks = nullptr; + const char** _insertedDylibs = nullptr; + const char* _imageSuffix = nullptr; + const char* _rootPath = nullptr; // simulator only +}; + +#if BUILDING_LIBDYLD +extern PathOverrides gPathOverrides; +#endif + + +} // namespace dyld3 + +#endif // __DYLD_PATH_OVERRIDES_H__ + + diff --git a/dyld3/SharedCacheRuntime.cpp b/dyld3/SharedCacheRuntime.cpp new file mode 100644 index 0000000..d1c821b --- /dev/null +++ b/dyld3/SharedCacheRuntime.cpp @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2017 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 "dyld_cache_format.h" +#include "SharedCacheRuntime.h" +#include "LaunchCache.h" +#include "LaunchCacheFormat.h" +#include "Loading.h" + +#define ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE 1024 + +// should be in mach/shared_region.h +extern "C" int __shared_region_check_np(uint64_t* startaddress); +extern "C" int __shared_region_map_and_slide_np(int fd, uint32_t count, const shared_file_mapping_np mappings[], long slide, const dyld_cache_slide_info2* slideInfo, size_t slideInfoSize); + + +namespace dyld { + extern int my_stat(const char* path, struct stat* buf); + extern int my_open(const char* path, int flag, int other); + extern void log(const char*, ...); +} + + +namespace dyld3 { + + +struct CacheInfo +{ + int fd; + shared_file_mapping_np mappings[3]; + uint64_t slideInfoAddressUnslid; + size_t slideInfoSize; + uint64_t cachedDylibsGroupUnslid; + uint64_t sharedRegionStart; + uint64_t sharedRegionSize; + uint64_t maxSlide; +}; + + + + +#if __i386__ + #define ARCH_NAME "i386" + #define ARCH_CACHE_MAGIC "dyld_v1 i386" +#elif __x86_64__ + #define ARCH_NAME "x86_64" + #define ARCH_CACHE_MAGIC "dyld_v1 x86_64" + #define ARCH_NAME_H "x86_64h" + #define ARCH_CACHE_MAGIC_H "dyld_v1 x86_64h" +#elif __ARM_ARCH_7K__ + #define ARCH_NAME "armv7k" + #define ARCH_CACHE_MAGIC "dyld_v1 armv7k" +#elif __ARM_ARCH_7A__ + #define ARCH_NAME "armv7" + #define ARCH_CACHE_MAGIC "dyld_v1 armv7" +#elif __ARM_ARCH_7S__ + #define ARCH_NAME "armv7s" + #define ARCH_CACHE_MAGIC "dyld_v1 armv7s" +#elif __arm64e__ + #define ARCH_NAME "arm64e" + #define ARCH_CACHE_MAGIC "dyld_v1 arm64e" +#elif __arm64__ + #define ARCH_NAME "arm64" + #define ARCH_CACHE_MAGIC "dyld_v1 arm64" +#endif + + + +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 getCachePath(const SharedCacheOptions& options, size_t pathBufferSize, char pathBuffer[]) +{ + // set cache dir + if ( options.cacheDirOverride != nullptr ) { + strlcpy(pathBuffer, options.cacheDirOverride, pathBufferSize); + } + else { +#if __IPHONE_OS_VERSION_MIN_REQUIRED + strlcpy(pathBuffer, IPHONE_DYLD_SHARED_CACHE_DIR, sizeof(IPHONE_DYLD_SHARED_CACHE_DIR)); +#else + strlcpy(pathBuffer, MACOSX_DYLD_SHARED_CACHE_DIR, sizeof(MACOSX_DYLD_SHARED_CACHE_DIR)); +#endif + } + + // append file component of cache file + if ( pathBuffer[strlen(pathBuffer)-1] != '/' ) + strlcat(pathBuffer, "/", pathBufferSize); +#if __x86_64__ && !__IPHONE_OS_VERSION_MIN_REQUIRED + if ( options.useHaswell ) { + size_t len = strlen(pathBuffer); + struct stat haswellStatBuf; + strlcat(pathBuffer, DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME_H, pathBufferSize); + if ( dyld::my_stat(pathBuffer, &haswellStatBuf) == 0 ) + return; + // no haswell cache file, use regular x86_64 cache + pathBuffer[len] = '\0'; + } +#endif + strlcat(pathBuffer, DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, pathBufferSize); + +#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR + // use .development cache if it exists + struct stat enableStatBuf; + struct stat devCacheStatBuf; + struct stat optCacheStatBuf; + bool enableFileExists = (dyld::my_stat(IPHONE_DYLD_SHARED_CACHE_DIR "enable-dylibs-to-override-cache", &enableStatBuf) == 0); + bool devCacheExists = (dyld::my_stat(IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME DYLD_SHARED_CACHE_DEVELOPMENT_EXT, &devCacheStatBuf) == 0); + bool optCacheExists = (dyld::my_stat(IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &optCacheStatBuf) == 0); + if ( (enableFileExists && (enableStatBuf.st_size < ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE) && devCacheExists) || !optCacheExists ) + strlcat(pathBuffer, DYLD_SHARED_CACHE_DEVELOPMENT_EXT, pathBufferSize); +#endif + +} + + +int openSharedCacheFile(const SharedCacheOptions& options, SharedCacheLoadInfo* results) +{ + getCachePath(options, sizeof(results->path), results->path); + return dyld::my_open(results->path, O_RDONLY, 0); +} + +static bool validMagic(const SharedCacheOptions& options, const DyldSharedCache* cache) +{ + if ( strcmp(cache->header.magic, ARCH_CACHE_MAGIC) == 0 ) + return true; + +#if __x86_64__ + if ( options.useHaswell ) { + if ( strcmp(cache->header.magic, ARCH_CACHE_MAGIC_H) == 0 ) + return true; + } +#endif + return false; +} + + +static bool validPlatform(const SharedCacheOptions& options, const DyldSharedCache* cache) +{ + // grandfather in old cache that does not have platform in header + if ( cache->header.mappingOffset < 0xE0 ) + return true; + + if ( cache->header.platform != (uint32_t)MachOParser::currentPlatform() ) + return false; + +#if TARGET_IPHONE_SIMULATOR + if ( cache->header.simulator == 0 ) + return false; +#else + if ( cache->header.simulator != 0 ) + return false; +#endif + + return true; +} + + +static void verboseSharedCacheMappings(const shared_file_mapping_np mappings[3]) +{ + for (int i=0; i < 3; ++i) { + dyld::log(" 0x%08llX->0x%08llX init=%x, max=%x %s%s%s\n", + mappings[i].sfm_address, mappings[i].sfm_address+mappings[i].sfm_size-1, + mappings[i].sfm_init_prot, mappings[i].sfm_init_prot, + ((mappings[i].sfm_init_prot & VM_PROT_READ) ? "read " : ""), + ((mappings[i].sfm_init_prot & VM_PROT_WRITE) ? "write " : ""), + ((mappings[i].sfm_init_prot & VM_PROT_EXECUTE) ? "execute " : "")); + } +} + +static bool preflightCacheFile(const SharedCacheOptions& options, SharedCacheLoadInfo* results, CacheInfo* info) +{ + // find and open shared cache file + int fd = openSharedCacheFile(options, results); + if ( fd == -1 ) { + results->errorMessage = "shared cache file cannot be opened"; + return false; + } + struct stat cacheStatBuf; + if ( dyld::my_stat(results->path, &cacheStatBuf) != 0 ) { + results->errorMessage = "shared cache file cannot be stat()ed"; + ::close(fd); + return false; + } + size_t cacheFileLength = (size_t)(cacheStatBuf.st_size); + + // sanity check header and mappings + uint8_t firstPage[0x4000]; + if ( ::pread(fd, firstPage, sizeof(firstPage), 0) != sizeof(firstPage) ) { + results->errorMessage = "shared cache header could not be read"; + ::close(fd); + return false; + } + const DyldSharedCache* cache = (DyldSharedCache*)firstPage; + if ( !validMagic(options, cache) ) { + results->errorMessage = "shared cache file has wrong magic"; + ::close(fd); + return false; + } + if ( !validPlatform(options, cache) ) { + results->errorMessage = "shared cache file is for a different platform"; + ::close(fd); + return false; + } + const dyld_cache_mapping_info* const fileMappings = (dyld_cache_mapping_info*)&firstPage[cache->header.mappingOffset]; + if ( (cache->header.mappingCount != 3) + || (cache->header.mappingOffset > 0x120) + || (fileMappings[0].fileOffset != 0) + || ((fileMappings[0].address + fileMappings[0].size) > fileMappings[1].address) + || ((fileMappings[1].address + fileMappings[1].size) > fileMappings[2].address) + || ((fileMappings[0].fileOffset + fileMappings[0].size) != fileMappings[1].fileOffset) + || ((fileMappings[1].fileOffset + fileMappings[1].size) != fileMappings[2].fileOffset) + || ((cache->header.codeSignatureOffset + cache->header.codeSignatureSize) != cacheFileLength) + || (fileMappings[0].maxProt != (VM_PROT_READ|VM_PROT_EXECUTE)) + || (fileMappings[1].maxProt != (VM_PROT_READ|VM_PROT_WRITE)) + || (fileMappings[2].maxProt != VM_PROT_READ) ) { + results->errorMessage = "shared cache file mappings are invalid"; + ::close(fd); + return false; + } + + if ( cache->header.mappingOffset >= 0xF8 ) { + if ( (fileMappings[0].address != cache->header.sharedRegionStart) || ((fileMappings[2].address + fileMappings[2].size) > (cache->header.sharedRegionStart+cache->header.sharedRegionSize)) ) { + results->errorMessage = "shared cache file mapping addressses invalid"; + ::close(fd); + return false; + } + } + else { + if ( (fileMappings[0].address != SHARED_REGION_BASE) || ((fileMappings[2].address + fileMappings[2].size) > (SHARED_REGION_BASE+SHARED_REGION_SIZE)) ) { + results->errorMessage = "shared cache file mapping addressses invalid"; + ::close(fd); + return false; + } + } + + // register code signature of cache file + fsignatures_t siginfo; + siginfo.fs_file_start = 0; // cache always starts at beginning of file + siginfo.fs_blob_start = (void*)cache->header.codeSignatureOffset; + siginfo.fs_blob_size = (size_t)(cache->header.codeSignatureSize); + int result = fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo); + if ( result == -1 ) { + results->errorMessage = "code signature registration for shared cache failed"; + ::close(fd); + return false; + } + + // validate code signature covers entire shared cache + uint64_t codeSignedLength = siginfo.fs_file_start; + if ( codeSignedLength < cache->header.codeSignatureOffset ) { + results->errorMessage = "code signature does not cover entire shared cache file"; + ::close(fd); + return false; + } + void* mappedData = ::mmap(NULL, sizeof(firstPage), PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0); + if ( mappedData == MAP_FAILED ) { + results->errorMessage = "first page of shared cache not mmap()able"; + ::close(fd); + return false; + } + if ( memcmp(mappedData, firstPage, sizeof(firstPage)) != 0 ) { + results->errorMessage = "first page of shared cache not mmap()able"; + ::close(fd); + return false; + } + ::munmap(mappedData, sizeof(firstPage)); + + // fill out results + info->fd = fd; + for (int i=0; i < 3; ++i) { + info->mappings[i].sfm_address = fileMappings[i].address; + info->mappings[i].sfm_size = fileMappings[i].size; + info->mappings[i].sfm_file_offset = fileMappings[i].fileOffset; + info->mappings[i].sfm_max_prot = fileMappings[i].maxProt; + info->mappings[i].sfm_init_prot = fileMappings[i].initProt; + } + info->mappings[1].sfm_max_prot |= VM_PROT_SLIDE; + info->mappings[1].sfm_init_prot |= VM_PROT_SLIDE; + info->slideInfoAddressUnslid = fileMappings[2].address + cache->header.slideInfoOffset - fileMappings[2].fileOffset; + info->slideInfoSize = (long)cache->header.slideInfoSize; + if ( cache->header.mappingOffset > 0xD0 ) + info->cachedDylibsGroupUnslid = cache->header.dylibsImageGroupAddr; + else + info->cachedDylibsGroupUnslid = 0; + if ( cache->header.mappingOffset >= 0xf8 ) { + info->sharedRegionStart = cache->header.sharedRegionStart; + info->sharedRegionSize = cache->header.sharedRegionSize; + info->maxSlide = cache->header.maxSlide; + } + else { + info->sharedRegionStart = SHARED_REGION_BASE; + info->sharedRegionSize = SHARED_REGION_SIZE; + info->maxSlide = SHARED_REGION_SIZE - (fileMappings[2].address + fileMappings[2].size - fileMappings[0].address); + } + return true; +} + +#if !TARGET_IPHONE_SIMULATOR +static bool reuseExistingCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results) +{ + uint64_t cacheBaseAddress; +#if __i386__ + if ( syscall(294, &cacheBaseAddress) == 0 ) { +#else + if ( __shared_region_check_np(&cacheBaseAddress) == 0 ) { +#endif + const DyldSharedCache* existingCache = (DyldSharedCache*)cacheBaseAddress; + if ( validMagic(options, existingCache) ) { + const dyld_cache_mapping_info* const fileMappings = (dyld_cache_mapping_info*)(cacheBaseAddress + existingCache->header.mappingOffset); + results->loadAddress = existingCache; + results->slide = (long)(cacheBaseAddress - fileMappings[0].address); + if ( (existingCache->header.mappingOffset > 0xD0) && (existingCache->header.dylibsImageGroupAddr != 0) ) + results->cachedDylibsGroup = (const launch_cache::binary_format::ImageGroup*)(existingCache->header.dylibsImageGroupAddr + results->slide); + else + results->cachedDylibsGroup = nullptr; + // we don't know the path this cache was previously loaded from, assume default + getCachePath(options, sizeof(results->path), results->path); + if ( options.verbose ) { + const shared_file_mapping_np* const mappings = (shared_file_mapping_np*)(cacheBaseAddress + existingCache->header.mappingOffset); + dyld::log("re-using existing shared cache (%s):\n", results->path); + shared_file_mapping_np slidMappings[3]; + for (int i=0; i < 3; ++i) { + slidMappings[i] = mappings[i]; + slidMappings[i].sfm_address += results->slide; + } + verboseSharedCacheMappings(slidMappings); + } + } + else { + results->errorMessage = "existing shared cache in memory is not compatible"; + } + return true; + } + return false; +} + +static long pickCacheASLR(CacheInfo& info) +{ + // choose new random slide +#if __IPHONE_OS_VERSION_MIN_REQUIRED + // change shared cache slide for 32-bit arm to always be 16k aligned + long slide = ((arc4random() % info.maxSlide) & (-16384)); +#else + long slide = ((arc4random() % info.maxSlide) & (-4096)); +#endif + + // respect -disable_aslr boot-arg + if ( dyld3::loader::bootArgsContains("-disable_aslr") ) + slide = 0; + + // update mappings + for (uint32_t i=0; i < 3; ++i) { + info.mappings[i].sfm_address += slide; + } + + return slide; +} + +static bool mapCacheSystemWide(const SharedCacheOptions& options, SharedCacheLoadInfo* results) +{ + CacheInfo info; + if ( !preflightCacheFile(options, results, &info) ) + return false; + + const dyld_cache_slide_info2* slideInfo = nullptr; + if ( info.slideInfoSize != 0 ) { + results->slide = pickCacheASLR(info); + slideInfo = (dyld_cache_slide_info2*)(info.slideInfoAddressUnslid + results->slide); + } + if ( info.cachedDylibsGroupUnslid != 0 ) + results->cachedDylibsGroup = (const launch_cache::binary_format::ImageGroup*)(info.cachedDylibsGroupUnslid + results->slide); + else + results->cachedDylibsGroup = nullptr; + + int result = __shared_region_map_and_slide_np(info.fd, 3, info.mappings, results->slide, slideInfo, info.slideInfoSize); + ::close(info.fd); + if ( result == 0 ) { + results->loadAddress = (const DyldSharedCache*)(info.mappings[0].sfm_address); + } + else { + // could be another process beat us to it + if ( reuseExistingCache(options, results) ) + return true; + // if cache does not exist, then really is an error + results->errorMessage = "syscall to map cache into shared region failed"; + return false; + } + + if ( options.verbose ) { + dyld::log("mapped dyld cache file system wide: %s\n", results->path); + verboseSharedCacheMappings(info.mappings); + } + return true; +} +#endif + +static bool mapCachePrivate(const SharedCacheOptions& options, SharedCacheLoadInfo* results) +{ + // open and validate cache file + CacheInfo info; + if ( !preflightCacheFile(options, results, &info) ) + return false; + + // compute ALSR slide + results->slide = 0; + const dyld_cache_slide_info2* slideInfo = nullptr; +#if !TARGET_IPHONE_SIMULATOR // simulator caches do not support sliding + if ( info.slideInfoSize != 0 ) { + results->slide = pickCacheASLR(info); + slideInfo = (dyld_cache_slide_info2*)(info.slideInfoAddressUnslid + results->slide); + } +#endif + results->loadAddress = (const DyldSharedCache*)(info.mappings[0].sfm_address); + if ( info.cachedDylibsGroupUnslid != 0 ) + results->cachedDylibsGroup = (const launch_cache::binary_format::ImageGroup*)(info.cachedDylibsGroupUnslid + results->slide); + else + results->cachedDylibsGroup = nullptr; + + // remove the shared region sub-map + vm_deallocate(mach_task_self(), (vm_address_t)info.sharedRegionStart, (vm_size_t)info.sharedRegionSize); + + // map cache just for this process with mmap() + for (int i=0; i < 3; ++i) { + void* mmapAddress = (void*)(uintptr_t)(info.mappings[i].sfm_address); + size_t size = (size_t)(info.mappings[i].sfm_size); + //dyld::log("dyld: mapping address %p with size 0x%08lX\n", mmapAddress, size); + int protection = 0; + if ( info.mappings[i].sfm_init_prot & VM_PROT_EXECUTE ) + protection |= PROT_EXEC; + if ( info.mappings[i].sfm_init_prot & VM_PROT_READ ) + protection |= PROT_READ; + if ( info.mappings[i].sfm_init_prot & VM_PROT_WRITE ) + protection |= PROT_WRITE; + off_t offset = info.mappings[i].sfm_file_offset; + if ( ::mmap(mmapAddress, size, protection, MAP_FIXED | MAP_PRIVATE, info.fd, offset) != mmapAddress ) { + // failed to map some chunk of this shared cache file + // clear shared region + vm_deallocate(mach_task_self(), (vm_address_t)info.sharedRegionStart, (vm_size_t)info.sharedRegionSize); + // return failure + results->loadAddress = nullptr; + results->cachedDylibsGroup = nullptr; + results->errorMessage = "could not mmap() part of dyld cache"; + return false; + } + } + + // update all __DATA pages with slide info + const dyld_cache_slide_info* slideInfoHeader = (dyld_cache_slide_info*)slideInfo; + if ( slideInfoHeader != nullptr ) { + if ( slideInfoHeader->version != 2 ) { + results->errorMessage = "invalide slide info in cache file"; + return false; + } + 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 = (uintptr_t)info.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 pInfo = page_extras[chainIndex]; + uint16_t pageStartOffset = (pInfo & 0x3FFF)*4; + //dyld::log(" chain[%d] pageOffset=0x%03X\n", chainIndex, pageStartOffset); + rebaseChain(page, pageStartOffset, results->slide, slideInfo); + done = (pInfo & DYLD_CACHE_SLIDE_PAGE_ATTR_END); + ++chainIndex; + } + } + else { + uint32_t pageOffset = pageEntry * 4; + //dyld::log(" start pageOffset=0x%03X\n", pageOffset); + rebaseChain(page, pageOffset, results->slide, slideInfo); + } + } + } + + if ( options.verbose ) { + dyld::log("mapped dyld cache file private to process (%s):\n", results->path); + verboseSharedCacheMappings(info.mappings); + } + return true; +} + + + +bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results) +{ + results->loadAddress = 0; + results->slide = 0; + results->cachedDylibsGroup = nullptr; + results->errorMessage = nullptr; + +#if TARGET_IPHONE_SIMULATOR + // simulator only supports mmap()ing cache privately into process + return mapCachePrivate(options, results); +#else + if ( options.forcePrivate ) { + // mmap cache into this process only + return mapCachePrivate(options, results); + } + else { + // fast path: when cache is already mapped into shared region + if ( reuseExistingCache(options, results) ) + return (results->errorMessage != nullptr); + + // slow path: this is first process to load cache + return mapCacheSystemWide(options, results); + } +#endif +} + + +bool findInSharedCacheImage(const SharedCacheLoadInfo& loadInfo, const char* dylibPathToFind, SharedCacheFindDylibResults* results) +{ + if ( loadInfo.loadAddress == nullptr ) + return false; + + // HACK: temp support for old caches + if ( (loadInfo.cachedDylibsGroup == nullptr) || (loadInfo.loadAddress->header.formatVersion != launch_cache::binary_format::kFormatVersion) ) { + const dyld_cache_image_info* const start = (dyld_cache_image_info*)((uint8_t*)loadInfo.loadAddress + loadInfo.loadAddress->header.imagesOffset); + const dyld_cache_image_info* const end = &start[loadInfo.loadAddress->header.imagesCount]; + for (const dyld_cache_image_info* p = start; p != end; ++p) { + const char* aPath = (char*)loadInfo.loadAddress + p->pathFileOffset; + if ( strcmp(aPath, dylibPathToFind) == 0 ) { + results->mhInCache = (const mach_header*)(p->address+loadInfo.slide); + results->pathInCache = aPath; + results->slideInCache = loadInfo.slide; + results->imageData = nullptr; + return true; + } + } + return false; + } + // HACK: end + + launch_cache::ImageGroup dylibsGroup(loadInfo.cachedDylibsGroup); + uint32_t foundIndex; + const launch_cache::binary_format::Image* imageData = dylibsGroup.findImageByPath(dylibPathToFind, foundIndex); +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // handle symlink to cached dylib + if ( imageData == nullptr ) { + char resolvedPath[PATH_MAX]; + if ( realpath(dylibPathToFind, resolvedPath) != nullptr ) + imageData = dylibsGroup.findImageByPath(resolvedPath, foundIndex); + } +#endif + if ( imageData == nullptr ) + return false; + + launch_cache::Image image(imageData); + results->mhInCache = (const mach_header*)((uintptr_t)loadInfo.loadAddress + image.cacheOffset()); + results->pathInCache = image.path(); + results->slideInCache = loadInfo.slide; + results->imageData = imageData; + return true; +} + + +bool pathIsInSharedCacheImage(const SharedCacheLoadInfo& loadInfo, const char* dylibPathToFind) +{ + if ( (loadInfo.loadAddress == nullptr) || (loadInfo.cachedDylibsGroup == nullptr) || (loadInfo.loadAddress->header.formatVersion != launch_cache::binary_format::kFormatVersion) ) + return false; + + launch_cache::ImageGroup dylibsGroup(loadInfo.cachedDylibsGroup); + uint32_t foundIndex; + const launch_cache::binary_format::Image* imageData = dylibsGroup.findImageByPath(dylibPathToFind, foundIndex); + return (imageData != nullptr); +} + + +} // namespace dyld3 + diff --git a/dyld3/SharedCacheRuntime.h b/dyld3/SharedCacheRuntime.h new file mode 100644 index 0000000..dbd4595 --- /dev/null +++ b/dyld3/SharedCacheRuntime.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017 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_SHARED_CACHE_RUNTIME_H__ +#define __DYLD_SHARED_CACHE_RUNTIME_H__ + +#include +#include + +#include "DyldSharedCache.h" + +namespace dyld3 { + +struct SharedCacheOptions { + const char* cacheDirOverride; + bool forcePrivate; + bool useHaswell; + bool verbose; +}; + +struct SharedCacheLoadInfo { + const DyldSharedCache* loadAddress; + long slide; + const launch_cache::binary_format::ImageGroup* cachedDylibsGroup; + const char* errorMessage; + char path[256]; +}; + +bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results); + +struct SharedCacheFindDylibResults { + const mach_header* mhInCache; + const char* pathInCache; + long slideInCache; + const launch_cache::binary_format::Image* imageData; +}; + + +bool findInSharedCacheImage(const SharedCacheLoadInfo& loadInfo, const char* dylibPathToFind, SharedCacheFindDylibResults* results); + +bool pathIsInSharedCacheImage(const SharedCacheLoadInfo& loadInfo, const char* dylibPathToFind); + + +} // namespace dyld3 + +#endif // __DYLD_SHARED_CACHE_RUNTIME_H__ + + diff --git a/dyld3/Tracing.cpp b/dyld3/Tracing.cpp new file mode 100644 index 0000000..ff15311 --- /dev/null +++ b/dyld3/Tracing.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017 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 "Tracing.h" + +namespace { +VIS_HIDDEN +static uint64_t elapsed(const time_value_t start, const time_value_t end) { + uint64_t duration; + duration = 1000000*(end.seconds - start.seconds); + duration += (end.microseconds - start.microseconds); + return duration; +} +} + +namespace dyld3 { + +VIS_HIDDEN +void kdebug_trace_dyld_image(const uint32_t code, + const uuid_t* uuid_bytes, + const fsobj_id_t fsobjid, + const fsid_t fsid, + const mach_header* load_addr) +{ +#if __LP64__ + uint64_t *uuid = (uint64_t *)uuid_bytes[0]; + kdebug_trace(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, code), uuid[0], + uuid[1], (uint64_t)load_addr, + (uint64_t)fsid.val[0] | ((uint64_t)fsid.val[1] << 32)); + kdebug_trace(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, code + 1), + (uint64_t)fsobjid.fid_objno | + ((uint64_t)fsobjid.fid_generation << 32), + 0, 0, 0); +#else /* __LP64__ */ + uint32_t *uuid = (uint32_t *)uuid_bytes[0]; + kdebug_trace(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, code + 2), uuid[0], + uuid[1], uuid[2], uuid[3]); + kdebug_trace(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, code + 3), + (uint32_t)load_addr, fsid.val[0], fsid.val[1], + fsobjid.fid_objno); + kdebug_trace(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, code + 4), + fsobjid.fid_generation, 0, 0, 0); +#endif /* __LP64__ */ +} + +VIS_HIDDEN +void kdebug_trace_dyld_signpost(const uint32_t code, uint64_t data1, uint64_t data2) { + if (kdebug_is_enabled(KDBG_CODE(DBG_DYLD, DBG_DYLD_SIGNPOST, code))) { + task_thread_times_info info; + mach_msg_type_number_t infoSize = sizeof(task_thread_times_info); + (void)task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, (task_info_t)&info, &infoSize); + uint64_t user_duration = elapsed({0,0}, info.user_time); + uint64_t system_duration = elapsed({0,0}, info.system_time); + kdebug_trace(KDBG_CODE(DBG_DYLD, DBG_DYLD_SIGNPOST, code), user_duration, system_duration, data1, data2); + } +} + +static std::atomic trace_pair_id(0); + +VIS_HIDDEN +void kdebug_trace_dyld_duration(const uint32_t code, uint64_t data1, uint64_t data2, void (^block)()) { + //FIXME: We should assert here, but it is verified on our current platforms + //Re-enabled when we move to C++17 and can use constexpr is_lock_always_free() + //assert(std::atomic{}.is_lock_free()); + if (kdebug_is_enabled(KDBG_CODE(DBG_DYLD, DBG_DYLD_TIMING, code))) { + uint64_t current_trace_id = trace_pair_id++; + kdebug_trace(KDBG_CODE(DBG_DYLD, DBG_DYLD_TIMING, code) | DBG_FUNC_START, current_trace_id, 0, data1, data2); + block(); + kdebug_trace(KDBG_CODE(DBG_DYLD, DBG_DYLD_TIMING, code) | DBG_FUNC_END, current_trace_id, 0, data1, data2); + } else { + block(); + } +} + +void kdebug_trace_print(const uint32_t code, const char *string) { + if (kdebug_is_enabled(KDBG_CODE(DBG_DYLD, DBG_DYLD_PRINT, code))) { + kdebug_trace_string(KDBG_CODE(DBG_DYLD, DBG_DYLD_PRINT, code), 0, string); + } +} +}; diff --git a/dyld3/Tracing.h b/dyld3/Tracing.h new file mode 100644 index 0000000..b127aa9 --- /dev/null +++ b/dyld3/Tracing.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2017 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 Tracing_h +#define Tracing_h + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef DBG_DYLD_SIGNPOST + #define DBG_DYLD_SIGNPOST (6) +#endif + +#ifndef DBG_DYLD_TIMING + #define DBG_DYLD_TIMING (7) +#endif + +#ifndef DBG_DYLD_PRINT + #define DBG_DYLD_PRINT (8) +#endif + +#ifndef DBG_DYLD_SIGNPOST_START_DYLD + #define DBG_DYLD_SIGNPOST_START_DYLD (0) +#endif + +#ifndef DBG_DYLD_SIGNPOST_START_MAIN + #define DBG_DYLD_SIGNPOST_START_MAIN (1) +#endif + +#ifndef DBG_DYLD_SIGNPOST_START_MAIN_DYLD2 + #define DBG_DYLD_SIGNPOST_START_MAIN_DYLD2 (2) +#endif + +#ifndef DBG_DYLD_TIMING_STATIC_INITIALIZER + #define DBG_DYLD_TIMING_STATIC_INITIALIZER (0) +#endif + +#ifndef DBG_DYLD_PRINT_GENERIC + #define DBG_DYLD_PRINT_GENERIC (0) +#endif + + +#define VIS_HIDDEN __attribute__((visibility("hidden"))) + +namespace dyld3 { + +VIS_HIDDEN +void kdebug_trace_dyld_image(const uint32_t code, + const uuid_t* uuid_bytes, + const fsobj_id_t fsobjid, + const fsid_t fsid, + const mach_header* load_addr); + +VIS_HIDDEN +void kdebug_trace_dyld_signpost(const uint32_t code, uint64_t data1, uint64_t data2); + +VIS_HIDDEN +void kdebug_trace_dyld_duration(const uint32_t code, uint64_t data1, uint64_t data2, void (^block)()); + +VIS_HIDDEN +void kdebug_trace_print(const uint32_t code, const char *string); +} + +#endif /* Tracing_h */ diff --git a/dyld3/closured/closured.cpp b/dyld3/closured/closured.cpp new file mode 100644 index 0000000..1e239e8 --- /dev/null +++ b/dyld3/closured/closured.cpp @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2017 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 // for bootstrap_check_in() +#include +#include +#include + +#include +#include +#include + +#include "dyld_priv.h" +#include "ImageProxy.h" +#include "DyldSharedCache.h" +#include "FileUtils.h" + +extern "C" { + #include "closuredProtocolServer.h" +} + + +static os_log_t sLog = os_log_create("com.apple.dyld.closured", "closured"); + +static char sCrashMessageBuffer[1024]; + + +kern_return_t +do_CreateClosure( + mach_port_t port, + task_t requestor, + vm_address_t buffer, + mach_msg_type_number_t bufferCnt, + vm_address_t* returnData, + mach_msg_type_number_t* returnDataCnt) +{ + dyld3::ClosureBuffer clsBuff((void*)buffer, bufferCnt); + const char* imagePath = clsBuff.targetPath(); + os_log(sLog, "request to build closure for %s\n", imagePath); + + // set crash log message in case there is an assert during processing + strlcpy(sCrashMessageBuffer, "building closure for: ", sizeof(sCrashMessageBuffer)); + strlcat(sCrashMessageBuffer, imagePath, sizeof(sCrashMessageBuffer)); + CRSetCrashLogMessage(sCrashMessageBuffer); + + Diagnostics diag; + const dyld3::launch_cache::binary_format::Closure* cls = dyld3::ImageProxyGroup::makeClosure(diag, clsBuff, requestor); + + os_log_info(sLog, "finished closure build, closure=%p\n", cls); + for (const std::string& message: diag.warnings()) + os_log(sLog, "Image generated warning: %s\n", message.c_str()); + + if ( diag.noError() ) { + // on success return the closure binary in the "returnData" buffer + dyld3::ClosureBuffer result(cls); + *returnData = result.vmBuffer(); + *returnDataCnt = result.vmBufferSize(); + } + else { + // on failure return the error message in the "returnData" buffer + os_log_error(sLog, "failed to create ImageGroup: %s\n", diag.errorMessage().c_str()); + dyld3::ClosureBuffer err(diag.errorMessage().c_str()); + *returnData = err.vmBuffer(); + *returnDataCnt = err.vmBufferSize(); + } + + CRSetCrashLogMessage(nullptr); + return KERN_SUCCESS; +} + +kern_return_t +do_CreateImageGroup( + mach_port_t port, + task_t requestor, + vm_address_t buffer, + mach_msg_type_number_t bufferCnt, + vm_address_t* returnData, + mach_msg_type_number_t* returnDataCnt) +{ + dyld3::ClosureBuffer clsBuff((void*)buffer, bufferCnt); + const char* imagePath = clsBuff.targetPath(); + int requestorPid; + char requestorName[MAXPATHLEN]; + if ( pid_for_task(requestor, &requestorPid) == 0 ) { + int nameLen = proc_name(requestorPid, requestorName, sizeof(requestorName)); + if ( nameLen <= 0 ) + strcpy(requestorName, "???"); + os_log(sLog, "request from %d (%s) to build dlopen ImageGroup for %s\n", requestorPid, requestorName, imagePath); + } + + // set crash log message in case there is an assert during processing + strlcpy(sCrashMessageBuffer, "building ImageGroup for dlopen(", sizeof(sCrashMessageBuffer)); + strlcat(sCrashMessageBuffer, imagePath, sizeof(sCrashMessageBuffer)); + strlcat(sCrashMessageBuffer, ") requested by ", sizeof(sCrashMessageBuffer)); + strlcat(sCrashMessageBuffer, requestorName, sizeof(sCrashMessageBuffer)); + CRSetCrashLogMessage(sCrashMessageBuffer); + + uuid_string_t uuidStr; + dyld3::ClosureBuffer::CacheIdent cacheIdent = clsBuff.cacheIndent(); + uuid_unparse(cacheIdent.cacheUUID, uuidStr); + os_log_info(sLog, "findDyldCache(): cache addr=0x%llX, size=0x%0llX, uuid = %s\n", cacheIdent.cacheAddress, cacheIdent.cacheMappedSize, uuidStr); + + Diagnostics diag; + const dyld3::launch_cache::binary_format::ImageGroup* imageGroup = dyld3::ImageProxyGroup::makeDlopenGroup(diag, clsBuff, requestor, {""}); + + os_log(sLog, "finished ImageGroup build, imageGroup=%p\n", imageGroup); + for (const std::string& message: diag.warnings()) { + os_log(sLog, "Image generated warning: %s\n", message.c_str()); + } + + // delete incoming out-of-line data + vm_deallocate(mach_task_self(), buffer, bufferCnt); + + if ( diag.noError() ) { + // on success return the ImageGroup binary in the "returnData" buffer + dyld3::ClosureBuffer result(imageGroup); + os_log_info(sLog, "returning closure buffer: 0x%lX, size=0x%X\n", (long)result.vmBuffer(), result.vmBufferSize()); + *returnData = result.vmBuffer(); + *returnDataCnt = result.vmBufferSize(); + free((void*)imageGroup); + } + else { + // on failure return the error message in the "returnData" buffer + os_log_error(sLog, "failed to create ImageGroup: %s\n", diag.errorMessage().c_str()); + dyld3::ClosureBuffer err(diag.errorMessage().c_str()); + *returnData = err.vmBuffer(); + *returnDataCnt = err.vmBufferSize(); + } + + CRSetCrashLogMessage(nullptr); + return KERN_SUCCESS; +} + + + + +// /usr/libexec/closured -create_closure /Applications/TextEdit.app -pipefd 4 -env DYLD_FOO=1 -cache_uuid C153F90A-69F2-323E-AC9F-2BBAE48ABAF7 +int runAsTool(int argc, const char* argv[]) +{ + const char* progPath = nullptr; + int pipeNum = -1; + bool verbose = false; + std::vector dyldEnvVars; + + dyld3::ClosureBuffer::CacheIdent cacheIdent; + bzero(&cacheIdent, sizeof(cacheIdent)); + + // set crash log message in case there is an assert during processing + sCrashMessageBuffer[0] = '\0'; + for (int i=0; i < argc; ++i) { + strlcat(sCrashMessageBuffer, argv[i], sizeof(sCrashMessageBuffer)); + strlcat(sCrashMessageBuffer, " ", sizeof(sCrashMessageBuffer)); + } + CRSetCrashLogMessage(sCrashMessageBuffer); + + for (int i=1; i < argc; ++i) { + const char* arg = argv[i]; + if ( strcmp(arg, "-create_closure") == 0 ) { + progPath = argv[++i]; + if ( progPath == nullptr ) { + fprintf(stderr, "-create_closure option requires a path to follow\n"); + return 1; + } + } + else if ( strcmp(arg, "-cache_uuid") == 0 ) { + const char* uuidStr = argv[++i]; + if ( uuidStr == nullptr ) { + fprintf(stderr, "-cache_uuid option requires a path to follow\n"); + return 1; + } + uuid_parse(uuidStr, cacheIdent.cacheUUID); + } + else if ( strcmp(arg, "-cache_address") == 0 ) { + const char* cacheAddr = argv[++i]; + if ( cacheAddr == nullptr ) { + fprintf(stderr, "-cache_address option requires a path to follow\n"); + return 1; + } + char *end; + cacheIdent.cacheAddress = strtol(cacheAddr, &end, 0); + } + else if ( strcmp(arg, "-cache_size") == 0 ) { + const char* cacheSize = argv[++i]; + if ( cacheSize == nullptr ) { + fprintf(stderr, "-cache_size option requires a path to follow\n"); + return 1; + } + char *end; + cacheIdent.cacheMappedSize = strtol(cacheSize, &end, 0); + } + else if ( strcmp(arg, "-pipefd") == 0 ) { + const char* numStr = argv[++i]; + if ( numStr == nullptr ) { + fprintf(stderr, "-pipefd option requires an file descriptor number to follow\n"); + return 1; + } + char *end; + pipeNum = (int)strtol(numStr, &end, 0); + if ( (pipeNum == 0) && (errno != 0) ) { + fprintf(stderr, "bad value (%s) for -pipefd option %d\n", numStr, pipeNum); + return 1; + } + } + else if ( strcmp(arg, "-env") == 0 ) { + const char* var = argv[++i]; + if ( var == nullptr ) { + fprintf(stderr, "-env option requires a following VAR=XXX\n"); + return 1; + } + dyldEnvVars.push_back(var); + } + else { + fprintf(stderr, "unknown option: %s\n", arg); + return 1; + } + } + if ( progPath == nullptr ) { + fprintf(stderr, "missing required -create_closure option\n"); + return 1; + } + if ( pipeNum == -1 ) { + fprintf(stderr, "missing required -pipefd option\n"); + return 1; + } + + if ( verbose ) { + fprintf(stderr, "closured: runAsTool()\n"); + for (int i=1; i < argc; ++i) + fprintf(stderr, " argv[%d] = %s\n", i, argv[i]); + } + + os_log(sLog, "fork/exec request to build closure for %s\n", progPath); + SocketBasedClousureHeader header; + + // find dyld cache for requested arch + size_t currentCacheSize; + const DyldSharedCache* currentCache = (const DyldSharedCache*)_dyld_get_shared_cache_range(¤tCacheSize); + if ( currentCache == nullptr ) { + os_log_error(sLog, "closured is running without a dyld cache\n"); + return 1; + } + uuid_t currentCacheUUID; + currentCache->getUUID(currentCacheUUID); + if ( memcmp(currentCacheUUID, cacheIdent.cacheUUID, 16) != 0 ) { + const char* errorString = "closured is running with a different dyld cache than client"; + os_log_error(sLog, "%s\n", errorString); + header.success = 0; + header.length = (int)strlen(errorString) + 1; + write(pipeNum, &header, sizeof(SocketBasedClousureHeader)); + write(pipeNum, errorString, header.length); + close(pipeNum); + return 0; + } + dyld3::DyldCacheParser cacheParser(currentCache, false); + + Diagnostics diag; + os_log_info(sLog, "starting closure build\n"); + const dyld3::launch_cache::BinaryClosureData* cls = dyld3::ImageProxyGroup::makeClosure(diag, cacheParser, progPath, false, {""}, dyldEnvVars); + os_log_info(sLog, "finished closure build, cls=%p\n", cls); + if ( diag.noError() ) { + // on success write the closure binary after the header to the socket + dyld3::launch_cache::Closure closure(cls); + os_log(sLog, "returning closure, size=%lu\n", closure.size()); + header.success = 1; + header.length = (uint32_t)closure.size(); + write(pipeNum, &header, sizeof(SocketBasedClousureHeader)); + write(pipeNum, cls, closure.size()); + } + else { + // on failure write the error message after the header to the socket + const std::string& message = diag.errorMessage(); + os_log_error(sLog, "closure could not be created: %s\n", message.c_str()); + header.success = 0; + header.length = (uint32_t)message.size() + 1; + write(pipeNum, &header, sizeof(SocketBasedClousureHeader)); + write(pipeNum, message.c_str(), header.length); + } + + close(pipeNum); + + return 0; +} + + +union MaxMsgSize { + union __RequestUnion__do_closured_subsystem req; + union __ReplyUnion__do_closured_subsystem rep; +}; + +int main(int argc, const char* argv[]) +{ +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // establish sandbox around process + char* errMsg = nullptr; + if ( sandbox_init_with_parameters("com.apple.dyld.closured", SANDBOX_NAMED, nullptr, &errMsg) != 0 ) { + os_log_error(sLog, "Failed to enter sandbox: %{public}s", errMsg); + exit(EXIT_FAILURE); + } +#endif + + if ( argc != 1 ) + return runAsTool(argc, argv);\ + + mach_port_t serverPort = MACH_PORT_NULL; + kern_return_t kr = bootstrap_check_in(bootstrap_port, CLOSURED_SERVICE_NAME, &serverPort); + if (kr != KERN_SUCCESS) + exit(-1); + + dispatch_source_t machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, serverPort, 0, dispatch_get_main_queue()); + if (machSource == nullptr) + exit(-1); + + dispatch_source_set_event_handler(machSource, ^{ + dispatch_mig_server(machSource, sizeof(union MaxMsgSize), closured_server); + }); + dispatch_source_set_cancel_handler(machSource, ^{ + mach_port_t port = (mach_port_t)dispatch_source_get_handle(machSource); + kern_return_t kr = mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_RECEIVE, -1); + if (kr != KERN_SUCCESS) + exit(-1); + }); + dispatch_resume(machSource); + dispatch_main(); + + return 0; +} + diff --git a/dyld3/closured/closuredProtocol.defs b/dyld3/closured/closuredProtocol.defs new file mode 100644 index 0000000..565c8bc --- /dev/null +++ b/dyld3/closured/closuredProtocol.defs @@ -0,0 +1,29 @@ + + +#include +#include + +import "closuredtypes.h"; + +subsystem closured 6000; + +userprefix closured_; // Routine prefixes for user access +serverprefix do_; // Routine prefixes for internal server access + +type OutOfLineBuffer_t = ^array[] of MACH_MSG_TYPE_BYTE ctype: vm_address_t; + +// used at launch +routine CreateClosure ( + port : mach_port_t; + in requestor : task_t; + in buffer : OutOfLineBuffer_t; + out returnData : OutOfLineBuffer_t, dealloc +); + +// used in dlopen()cl +routine CreateImageGroup ( + port : mach_port_t; + in requestor : task_t; + in buffer : OutOfLineBuffer_t; + out returnData : OutOfLineBuffer_t, dealloc +); diff --git a/dyld3/closured/closured_entitlements.plist b/dyld3/closured/closured_entitlements.plist new file mode 100644 index 0000000..5a352d6 --- /dev/null +++ b/dyld3/closured/closured_entitlements.plist @@ -0,0 +1,12 @@ + + + + + seatbelt-profiles + + closured + + platform-application + + + diff --git a/dyld3/closured/closuredtypes.h b/dyld3/closured/closuredtypes.h new file mode 100644 index 0000000..6acc727 --- /dev/null +++ b/dyld3/closured/closuredtypes.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 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 + +#undef __MigTypeCheck +#undef USING_VOUCHERS + + +typedef void* ClosureBufferPtr; +typedef void* ClosureBufferConstPtr; + +struct SocketBasedClousureHeader +{ + uint32_t success; // 1 => rest of buffer is closure, 0 => rest of buffer is error string + uint32_t length; +}; + +#define CLOSURED_SERVICE_NAME "com.apple.dyld.closured" + +#define mig_external __private_extern__ + diff --git a/dyld3/closured/com.apple.dyld.closured.plist b/dyld3/closured/com.apple.dyld.closured.plist new file mode 100644 index 0000000..e19d134 --- /dev/null +++ b/dyld3/closured/com.apple.dyld.closured.plist @@ -0,0 +1,25 @@ + + + + + ProcessType + Adaptive + EnableTransactions + + EnablePressuredExit + + Label + com.apple.dyld.closured + MachServices + + com.apple.dyld.closured + + + TimeOut + 60 + ProgramArguments + + /usr/libexec/closured + + + diff --git a/dyld3/closured/com.apple.dyld.closured.sb b/dyld3/closured/com.apple.dyld.closured.sb new file mode 100644 index 0000000..bd89f9c --- /dev/null +++ b/dyld3/closured/com.apple.dyld.closured.sb @@ -0,0 +1,22 @@ +;;; Copyright (c) 2017 Apple Inc. All Rights reserved. +;;; +;;; WARNING: The sandbox rules in this file currently constitute +;;; Apple System Private Interface and are subject to change at any time and +;;; without notice. +;;; +(version 1) + +(deny default) +(deny file-map-executable iokit-get-properties process-info* nvram*) +(deny dynamic-code-generation) + +(import "system.sb") + +;; For reading dylibs +(allow file-read*) + +;; For resolving symlinks, realpath(3), and equivalents. +(allow file-read-metadata) + +;; for logging name of client +(allow process-info-pidinfo) diff --git a/dyld3/dyld-potential-framework-overrides b/dyld3/dyld-potential-framework-overrides new file mode 100644 index 0000000..998a298 --- /dev/null +++ b/dyld3/dyld-potential-framework-overrides @@ -0,0 +1,7 @@ +/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation +/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation +/System/Library/Frameworks/MediaToolbox.framework/Versions/A/MediaToolbox +/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools +/System/Library/Frameworks/WebKit.framework/Versions/A/WebKit +/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/JavaScriptCore + diff --git a/dyld3/libclosured-stub.cpp b/dyld3/libclosured-stub.cpp new file mode 100644 index 0000000..90820e4 --- /dev/null +++ b/dyld3/libclosured-stub.cpp @@ -0,0 +1,12 @@ + +namespace dyld3 { + +struct ClosureBuffer { int x; }; + +ClosureBuffer closured_CreateImageGroup(const ClosureBuffer& input) +{ + return ClosureBuffer(); +} + + +} // namespace dyld3 diff --git a/dyld3/libdyldEntryVector.cpp b/dyld3/libdyldEntryVector.cpp new file mode 100644 index 0000000..9735df2 --- /dev/null +++ b/dyld3/libdyldEntryVector.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2017 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 "dyld_priv.h" +#include "libdyldEntryVector.h" +#include "AllImages.h" +#include "Logging.h" +#include "PathOverrides.h" +#include "LaunchCacheFormat.h" + +extern "C" void start(); + + +VIS_HIDDEN const char** appleParams; + +extern bool gUseDyld3; + +namespace dyld3 { + + +AllImages::ProgramVars sVars; +static void (*sChildForkFunction)(); + +static const char* leafName(const char* argv0) +{ + if ( argv0 == nullptr ) + return ""; + + if ( const char* lastSlash = strrchr(argv0, '/') ) + return lastSlash+1; + else + return argv0; +} + +static void entry_setVars(const mach_header* mainMH, int argc, const char* argv[], const char* envp[], const char* apple[]) +{ + NXArgc = argc; + NXArgv = argv; + environ = (char**)envp; + appleParams = apple; + __progname = leafName(argv[0]); + + sVars.mh = mainMH; + sVars.NXArgcPtr = &NXArgc; + sVars.NXArgvPtr = &NXArgv; + sVars.environPtr = (const char***)&environ; + sVars.__prognamePtr = &__progname; + gAllImages.setProgramVars(&sVars); + + gUseDyld3 = true; + + setLoggingFromEnvs(envp); +} + +static void entry_setHaltFunction(void (*func)(const char* message) __attribute__((noreturn)) ) +{ + setHaltFunction(func); +} + +static void entry_setLogFunction(void (*logFunction)(const char* format, va_list list)) +{ + setLoggingFunction(logFunction); +} + +static void entry_setOldAllImageInfo(dyld_all_image_infos* old) +{ + gAllImages.setOldAllImageInfo(old); +} + +static void entry_setInitialImageList(const launch_cache::binary_format::Closure* closure, + const void* dyldCacheLoadAddress, const char* dyldCachePath, + const dyld3::launch_cache::DynArray& initialImages, + const mach_header* libSystemMH, const launch_cache::binary_format::Image* libSystemImage) +{ + gAllImages.init(closure, dyldCacheLoadAddress, dyldCachePath, initialImages); + gAllImages.applyInterposingToDyldCache(closure, initialImages); + + const char* mainPath = _simple_getenv(appleParams, "executable_path"); + if ( (mainPath != nullptr) && (mainPath[0] == '/') ) + gAllImages.setMainPath(mainPath); + + // ok to call before malloc is ready because 4 slots are reserved. + gAllImages.setInitialGroups(); + + // run initializer for libSytem.B.dylib + // this calls back into _dyld_initializer which calls gAllIimages.addImages() + gAllImages.runLibSystemInitializer(libSystemMH, libSystemImage); + + // now that malloc is available, parse DYLD_ env vars + gPathOverrides.setEnvVars((const char**)environ); +} + +static void entry_runInitialzersBottomUp(const mach_header* mainExecutableImageLoadAddress) +{ + gAllImages.runInitialzersBottomUp(mainExecutableImageLoadAddress); + gAllImages.notifyMonitorMain(); +} + +static void entry_setChildForkFunction(void (*func)() ) +{ + sChildForkFunction = func; +} + +const LibDyldEntryVector entryVectorForDyld = { + LibDyldEntryVector::kCurrentVectorVersion, + launch_cache::binary_format::kFormatVersion, + &entry_setVars, + &entry_setHaltFunction, + &entry_setOldAllImageInfo, + &entry_setInitialImageList, + &entry_runInitialzersBottomUp, + &start, + &entry_setChildForkFunction, + &entry_setLogFunction, +}; + +VIS_HIDDEN void _dyld_fork_child() +{ + (*sChildForkFunction)(); +} + + +} // namespace dyld3 + diff --git a/dyld3/libdyldEntryVector.h b/dyld3/libdyldEntryVector.h new file mode 100644 index 0000000..3c61df2 --- /dev/null +++ b/dyld3/libdyldEntryVector.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017 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_ENTRY_VECTOR_H__ +#define __DYLD_ENTRY_VECTOR_H__ + +#include + +#include "LaunchCache.h" +#include "Loading.h" + +struct dyld_all_image_infos; + +namespace dyld3 { + +struct LibDyldEntryVector +{ + enum { kCurrentVectorVersion = 4 }; + + uint32_t vectorVersion; // should be kCurrentVectorVersion + uint32_t binaryFormatVersion; // should be launch_cache::binary_format::kFormatVersion + void (*setVars)(const mach_header* mainMH, int argc, const char* argv[], const char* envp[], const char* apple[]); + void (*setHaltFunction)(void (*func)(const char* message) __attribute__((noreturn)) ); + void (*setOldAllImageInfo)(dyld_all_image_infos*); + void (*setInitialImageList)(const launch_cache::BinaryClosureData* closure, + const void* dyldCacheLoadAddress, const char* dyldCachePath, + const dyld3::launch_cache::DynArray& initialImages, + const mach_header* libSystemMH, const launch_cache::BinaryImageData* libSystemImage); + void (*runInitialzersBottomUp)(const mach_header* topImageLoadAddress); + void (*startFunc)(); + // added in version 3 + void (*setChildForkFunction)(void (*func)()); + // added in version 4 + void (*setLogFunction)(void (*logFunction)(const char* format, va_list list)); +}; + +extern const LibDyldEntryVector entryVectorForDyld; + +} // namespace dyld3 + + +#endif // __DYLD_ENTRY_VECTOR_H__ + + + + diff --git a/dyld3/shared-cache/AdjustDylibSegments.cpp b/dyld3/shared-cache/AdjustDylibSegments.cpp new file mode 100644 index 0000000..9ab3bb8 --- /dev/null +++ b/dyld3/shared-cache/AdjustDylibSegments.cpp @@ -0,0 +1,1084 @@ +/* -*- 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 "CacheBuilder.h" +#include "Diagnostics.h" +#include "DyldSharedCache.h" +#include "Trie.hpp" +#include "MachOFileAbstraction.hpp" + + +#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE + #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 +#endif + +namespace { + +template +class Adjustor { +public: + Adjustor(DyldSharedCache* cacheBuffer, macho_header

* mh, const std::vector& mappingInfo, Diagnostics& diag); + 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; + + DyldSharedCache* _cacheBuffer; + macho_header

* _mh; + Diagnostics& _diagnostics; + 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 _segSlides; + std::vector*> _segCmds; + const std::vector& _mappingInfo; +}; + +template +Adjustor

::Adjustor(DyldSharedCache* cacheBuffer, macho_header

* mh, const std::vector& mappingInfo, Diagnostics& diag) + : _cacheBuffer(cacheBuffer), _mh(mh), _diagnostics(diag), _mappingInfo(mappingInfo) +{ + assert((mh->magic() == MH_MAGIC) || (mh->magic() == MH_MAGIC_64)); + 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(_mappingInfo[segIndex].dstCacheAddress - segCmd->vmaddr()); + if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { + _linkeditAdjust = _mappingInfo[segIndex].dstCacheOffset - 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); + } + else { + _diagnostics.error("missing LC_SEGMENT_SPLIT_INFO in %s", _installName); + } +} + +template +void Adjustor

::adjustImageForNewSegmentLocations(std::vector& pointersForASLR) +{ + if ( _diagnostics.hasError() ) + return; + if ( _splitSegInfoV2 ) { + adjustReferencesUsingInfoV2(pointersForASLR); + } + else { + adjustDataPointers(pointersForASLR); + adjustCode(); + } + if ( _diagnostics.hasError() ) + return; + adjustSymbolTable(); + if ( _diagnostics.hasError() ) + return; + 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); + } + _diagnostics.error("slide not known for dylib address 0x%llX in %s", addr, _installName); + return 0; +} + +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 ) { + _diagnostics.error("LINKEDIT overflow in %s", _installName); + return; + } + + uint32_t linkeditStartOffset = (uint32_t)_mappingInfo[_linkeditSegIndex].dstCacheOffset; + 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)(_mappingInfo[segIndex].dstCacheOffset - segCmd->fileoff()); + segCmd->set_vmaddr(_mappingInfo[segIndex].dstCacheAddress); + segCmd->set_vmsize(_mappingInfo[segIndex].dstCacheSegmentSize); + segCmd->set_fileoff(_mappingInfo[segIndex].dstCacheOffset); + segCmd->set_filesize(_mappingInfo[segIndex].copySegmentSize); + 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: + _diagnostics.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 + _mappingInfo[segIndex].dstCacheOffset + 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: + _diagnostics.error("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 = 0; + uint32_t value32; + uint32_t* mappedAddr32 = 0; + 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) ) { + _diagnostics.error("DYLD_CACHE_ADJ_V2_DELTA_32 can't be adjust by 0x%016llX in %s", adjust, _installName); + return; + } + 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) ) { + _diagnostics.error("bad DYLD_CACHE_ADJ_V2_POINTER_32 value not as expected at address 0x%llX in %s", fromNewAddress, _installName); + return; + } + 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) ) { + _diagnostics.error("bad DYLD_CACHE_ADJ_V2_POINTER_64 value not as expected at address 0x%llX in %s", fromNewAddress, _installName); + return; + } + 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 ) { + _diagnostics.error("DYLD_CACHE_ADJ_V2_IMAGE_OFF_32 can't be adjust to 0x%016llX in %s", toNewAddress, _installName); + return; + } + P::E::set32(*mappedAddr32, (uint32_t)value64); + 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) ) { + _diagnostics.error("DYLD_CACHE_ADJ_V2_ARM64_ADRP can't be adjusted that far in %s", _installName); + return; + } + 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 ) { + _diagnostics.error("can't adjust off12 scale=16 instruction by %lld bytes at mapped address=%p in %s", offsetAdjust, mappedAddr, _installName); + return; + } + if ( encodedAddend*16 >= 4096 ) { + _diagnostics.error("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 ) { + _diagnostics.error("can't adjust off12 scale=2 instruction by %lld bytes at mapped address=%p in %s", offsetAdjust, mappedAddr, _installName); + return; + } + if ( encodedAddend*2 >= 4096 ) { + _diagnostics.error("off12 scale=2 instruction points outside its page at mapped address=%p in %s", mappedAddr, _installName); + return; + } + newAddend = (encodedAddend + offsetAdjust/2) % 2048; + break; + case 0x80000000: + if ( offsetAdjust & 3 ) { + _diagnostics.error("can't adjust off12 scale=4 instruction by %lld bytes at mapped address=%p in %s", offsetAdjust, mappedAddr, _installName); + return; + } + if ( encodedAddend*4 >= 4096 ) { + _diagnostics.error("off12 scale=4 instruction points outside its page at mapped address=%p in %s", mappedAddr, _installName); + return; + } + newAddend = (encodedAddend + offsetAdjust/4) % 1024; + break; + case 0xC0000000: + if ( offsetAdjust & 7 ) { + _diagnostics.error("can't adjust off12 scale=8 instruction by %lld bytes at mapped address=%p in %s", offsetAdjust, mappedAddr, _installName); + return; + } + if ( encodedAddend*8 >= 4096 ) { + _diagnostics.error("off12 scale=8 instruction points outside its page at mapped address=%p in %s", mappedAddr, _installName); + return; + } + 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 ) { + _diagnostics.error("ADD off12 uses shift at mapped address=%p in %s", mappedAddr, _installName); + return; + } + 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 + _diagnostics.error("unknown off12 instruction 0x%08X at 0x%0llX in %s", instruction, fromNewAddress, _installName); + return; + } + 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 { + _diagnostics.error("two DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT in a row but not paried in %s", _installName); + return; + } + P::E::set32(*lastMappedAddr32, instruction1); + P::E::set32(*mappedAddr32, instruction2); + kind = 0; + } + else { + _diagnostics.error("two DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT in a row but target different addresses in %s", _installName); + return; + } + } + 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 { + _diagnostics.error("two DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT in a row but not paired in %s", _installName); + return; + } + P::E::set32(*lastMappedAddr32, instruction1); + P::E::set32(*mappedAddr32, instruction2); + kind = 0; + } + else { + _diagnostics.error("two DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT in a row but target different addresses in %s", _installName); + return; + } + } + 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: + _diagnostics.error("unknown split seg kind=%d in %s", kind, _installName); + return; + } + 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 ) { + _diagnostics.error("malformed split seg info in %s", _installName); + return; + } + // 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 + _mappingInfo[0].dstCacheOffset); + sectionSlides.push_back(_segSlides[0]); + sectionNewAddress.push_back(_mappingInfo[0].dstCacheAddress); + // 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 + _mappingInfo[segmentIndex].dstCacheOffset + sect->addr() - segCmd->vmaddr()); + sectionSlides.push_back(_segSlides[segmentIndex]); + sectionNewAddress.push_back(_mappingInfo[segmentIndex].dstCacheAddress + 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 ) { + _diagnostics.error("bad kind value (%llu) in %s", kind, _installName); + return; + } + 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); + if ( _diagnostics.hasError() ) + return; + } + } + } + } + +} + +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: + _diagnostics.error("unknown rebase opcode 0x%02X in %s", opcode, _installName); + done = true; + break; + } + } +} + + +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 = _mappingInfo[0].dstCacheOffset; + 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) ) { + _diagnostics.error("malformed exports trie in %s", _installName); + return; + } + + 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 adjustDylibSegments(DyldSharedCache* cache, bool is64, mach_header* mhInCache, const std::vector& mappingInfo, std::vector& pointersForASLR, Diagnostics& diag) +{ + if ( is64 ) { + Adjustor> adjustor64(cache, (macho_header>*)mhInCache, mappingInfo, diag); + adjustor64.adjustImageForNewSegmentLocations(pointersForASLR); + } + else { + Adjustor> adjustor32(cache, (macho_header>*)mhInCache, mappingInfo, diag); + adjustor32.adjustImageForNewSegmentLocations(pointersForASLR); + } +} + + + + + diff --git a/dyld3/shared-cache/BuilderUtils.h b/dyld3/shared-cache/BuilderUtils.h new file mode 100644 index 0000000..713d0bd --- /dev/null +++ b/dyld3/shared-cache/BuilderUtils.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017 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 BuilderUtils_h +#define BuilderUtils_h + +dispatch_group_t buildGroup(); +void makeBoms(dyld3::Manifest& manifest, const std::string& masterDstRoot); +bool build(Diagnostics& diags, dyld3::Manifest& manifest, const std::string& masterDstRoot, bool dedupe, bool verbose, bool skipWrites, bool agileChooseSHA256CdHash); + +#endif /* BuilderUtils_h */ diff --git a/dyld3/shared-cache/BuilderUtils.mm b/dyld3/shared-cache/BuilderUtils.mm new file mode 100644 index 0000000..ef2a675 --- /dev/null +++ b/dyld3/shared-cache/BuilderUtils.mm @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2017 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 // std::setfill, std::setw +#include +#include +#include + +#include +#include +#include +#include + +#include "Manifest.h" +#include "Diagnostics.h" +#include "FileUtils.h" + +#include "BuilderUtils.h" + +static dispatch_queue_t write_queue = dispatch_queue_create("com.apple.dyld.cache-builder.write", DISPATCH_QUEUE_CONCURRENT); +static dispatch_group_t build_group = dispatch_group_create(); + +dispatch_group_t buildGroup() { + return build_group; +} + +void insertFileInBom(const std::string& path, BOMBom bom) +{ + std::vector components; + std::vector processed_components; + std::stringstream ss(path); + std::string item; + + while (std::getline(ss, item, '/')) { + if (!item.empty()) { + components.push_back(item); + } + } + + std::string partialPath = "."; + std::string lastComponent = components.back(); + components.pop_back(); + BOMFSObject fso = BOMFSObjectNew(BOMDirectoryType); + BOMFSObjectSetFlags(fso, B_PATHONLY); + BOMFSObjectSetPathName(fso, ".", true); + BOMFSObjectSetShortName(fso, ".", true); + (void)BOMBomInsertFSObject(bom, fso, false); + BOMFSObjectFree(fso); + + for (const auto& component : components) { + partialPath = partialPath + "/" + component; + fso = BOMFSObjectNew(BOMDirectoryType); + BOMFSObjectSetFlags(fso, B_PATHONLY); + BOMFSObjectSetPathName(fso, partialPath.c_str(), true); + BOMFSObjectSetShortName(fso, component.c_str(), true); + (void)BOMBomInsertFSObject(bom, fso, false); + BOMFSObjectFree(fso); + } + + partialPath = partialPath + "/" + lastComponent; + fso = BOMFSObjectNew(BOMFileType); + BOMFSObjectSetFlags(fso, B_PATHONLY); + BOMFSObjectSetPathName(fso, partialPath.c_str(), true); + BOMFSObjectSetShortName(fso, lastComponent.c_str(), true); + (void)BOMBomInsertFSObject(bom, fso, false); + BOMFSObjectFree(fso); +} + +void makeBoms(dyld3::Manifest& manifest, const std::string& masterDstRoot) +{ + mkpath_np((masterDstRoot + "/Boms/").c_str(), 0755); + + manifest.forEachConfiguration([&manifest, &masterDstRoot](const std::string& configName) { + auto config = manifest.configuration(configName); + std::vector prodBomPaths; + std::vector devBomPaths; + + std::string runtimePath = "/System/Library/Caches/com.apple.dyld/"; + if (manifest.platform() == dyld3::Platform::macOS) { + runtimePath = "/private/var/db/dyld/"; + } + + for (auto& arch : config.architectures) { + std::string cachePath = "dyld_shared_cache_" + arch.first; + prodBomPaths.push_back(cachePath); + if (manifest.platform() != dyld3::Platform::macOS) { + cachePath += ".development"; + } + devBomPaths.push_back(cachePath); + char buffer[MAXPATHLEN]; + sprintf(buffer, "%s/Boms/%s.prod.bom", masterDstRoot.c_str(), configName.c_str()); + BOMBom bom = BOMBomNew(buffer); + for (auto& path : prodBomPaths) { + insertFileInBom(runtimePath + path, bom); + } + BOMBomFree(bom); + + sprintf(buffer, "%s/Boms/%s.dev.bom", masterDstRoot.c_str(), configName.c_str()); + bom = BOMBomNew(buffer); + for (auto& path : devBomPaths) { + insertFileInBom(runtimePath + path, bom); + } + BOMBomFree(bom); + + sprintf(buffer, "%s/Boms/%s.full.bom", masterDstRoot.c_str(), configName.c_str()); + bom = BOMBomNew(buffer); + for (auto& path : prodBomPaths) { + insertFileInBom(runtimePath + path, bom); + } + for (auto& path : devBomPaths) { + insertFileInBom(runtimePath + path, bom); + } + BOMBomFree(bom); + } + }); +} + +bool build(Diagnostics& diags, dyld3::Manifest& manifest, const std::string& masterDstRoot, bool dedupe, bool verbose, + bool skipWrites, bool agileChooseSHA256CdHash) +{ + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_queue_t warningQueue = dispatch_queue_create("com.apple.dyld.cache-builder.warnings", DISPATCH_QUEUE_SERIAL); + std::vector> dedupedCacheSets; + if (dedupe) { + manifest.forEachConfiguration([&manifest, &dedupedCacheSets](const std::string& configName) { + auto config = manifest.configuration(configName); + bool dupeFound = false; + + for (auto& cacheSet : dedupedCacheSets) { + if (config == manifest.configuration(*cacheSet.begin())) { + cacheSet.insert(configName); + dupeFound = true; + break; + } + } + + if (!dupeFound) { + std::set temp; + temp.insert(configName); + dedupedCacheSets.push_back(temp); + } + }); + } else { + manifest.forEachConfiguration([&manifest, &dedupedCacheSets](const std::string& configName) { + std::set temp; + temp.insert(configName); + dedupedCacheSets.push_back(temp); + }); + } + + std::vector buildQueue; + + 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()); + + if (dedupe) { + for (auto& config : cacheSet) { + if (!skipWrites) { + int err = symlink(("DedupedConfigs/" + fileName).c_str(), (masterDstRoot + "/" + config).c_str()); + if (err) { + diags.warning("Could not create symlink '%s' -> 'DedupedConfigs/%s' (%d)", config.c_str(), fileName.c_str(), err); + } + } + } + } + + manifest.configuration(*cacheSet.begin()).forEachArchitecture([&masterDstRoot, &dedupe, &fileName, &setName, &manifest, &buildQueue, &cacheSet, verbose](const std::string& arch) { + std::string configPath; + std::string runtimePath = "/System/Library/Caches/com.apple.dyld/"; + if (manifest.platform() == dyld3::Platform::macOS) { + runtimePath = "/private/var/db/dyld/"; + } + if (dedupe) { + configPath = masterDstRoot + "/DedupedConfigs/" + fileName + runtimePath; + } else { + configPath = masterDstRoot + runtimePath; + } + + if (manifest.platform() == dyld3::Platform::macOS) { + buildQueue.push_back(manifest.makeQueueEntry(configPath + "dyld_shared_cache_" + arch, cacheSet, arch, false, setName + "/" + arch, verbose)); + } else { + buildQueue.push_back(manifest.makeQueueEntry(configPath + "dyld_shared_cache_" + arch + ".development", cacheSet, arch, false, setName + "/" + arch, verbose)); + buildQueue.push_back(manifest.makeQueueEntry(configPath + "dyld_shared_cache_" + arch, cacheSet, arch, true, setName + "/" + arch, verbose)); + } + }); + } + + __block bool cacheBuildFailure = false; + __block std::set warnings; + __block std::set errors; + + dispatch_sync(warningQueue, ^{ + auto manifestWarnings = diags.warnings(); + warnings.insert(manifestWarnings.begin(), manifestWarnings.end()); + }); + + dispatch_apply(buildQueue.size(), queue, ^(size_t index) { + auto queueEntry = buildQueue[index]; + pthread_setname_np(queueEntry.options.loggingPrefix.substr(0, MAXTHREADNAMESIZE - 1).c_str()); + + DyldSharedCache::CreateResults results; + while (1) { + results = DyldSharedCache::create(queueEntry.options, queueEntry.dylibsForCache, queueEntry.otherDylibsAndBundles, queueEntry.mainExecutables); + if (!results.overflowed) + break; + auto evicted = manifest.removeLargestLeafDylib(queueEntry.configNames, queueEntry.options.archName); + if (evicted.empty()) + break; + queueEntry = manifest.makeQueueEntry(queueEntry.outputPath, queueEntry.configNames, queueEntry.options.archName, queueEntry.options.optimizeStubs, queueEntry.options.loggingPrefix, queueEntry.options.verbose); + dispatch_sync(warningQueue, ^{ + warnings.insert("[WARNING] CACHE OVERFLOW: " + queueEntry.options.loggingPrefix + " evicted dylib: " + evicted); + }); + } + dispatch_sync(warningQueue, ^{ + warnings.insert(results.warnings.begin(), results.warnings.end()); + bool chooseSecondCdHash = agileChooseSHA256CdHash; + if (agileChooseSHA256CdHash && !results.agileSignature) { + // Ignore this option for caches that are not signed agile (which is the majority). + chooseSecondCdHash = false; + } + for (const auto& configName : queueEntry.configNames) { + manifest.configuration(configName).architecture(queueEntry.options.archName).results.warnings = results.warnings; + if (queueEntry.options.optimizeStubs) { + manifest.configuration(configName).architecture(queueEntry.options.archName) + .results.developmentCache.cdHash = chooseSecondCdHash ? results.cdHashSecond : results.cdHashFirst; + } else { + manifest.configuration(configName).architecture(queueEntry.options.archName) + .results.productionCache.cdHash = chooseSecondCdHash ? results.cdHashSecond : results.cdHashFirst; + } + } + }); + if (!results.errorMessage.empty()) { + fprintf(stderr, "[%s] ERROR: %s\n", queueEntry.options.loggingPrefix.c_str(), results.errorMessage.c_str()); + } else if (!skipWrites) { + dispatch_sync(write_queue, ^{ + // save new cache file to disk and write new .map file + assert(results.cacheContent != nullptr); + mkpath_np(dirPath(queueEntry.outputPath).c_str(), 0755); + if (!safeSave(results.cacheContent, results.cacheLength, queueEntry.outputPath)) { + cacheBuildFailure = true; + fprintf(stderr, "[%s] ERROR: Could not write cache to: %s\n", queueEntry.options.loggingPrefix.c_str(), queueEntry.outputPath.c_str()); + } else { + fprintf(stderr, "[%s] Wrote cache to: %s\n", queueEntry.options.loggingPrefix.c_str(), queueEntry.outputPath.c_str()); + std::string mapStr = results.cacheContent->mapFile(); + std::string outFileMap = queueEntry.outputPath + ".map"; + safeSave(mapStr.c_str(), mapStr.size(), outFileMap); + } + // free created cache buffer + vm_deallocate(mach_task_self(), (vm_address_t)results.cacheContent, results.cacheLength); + }); + } else { + fprintf(stderr, "[%s] Skipped writing cache to: %s\n", queueEntry.options.loggingPrefix.c_str(), queueEntry.outputPath.c_str()); + vm_deallocate(mach_task_self(), (vm_address_t)results.cacheContent, results.cacheLength); + } + }); + + // print any warnings + for (const std::string& warn : warnings) { + fprintf(stderr, "[WARNING] %s\n", warn.c_str()); + } + + int err = sync_volume_np(masterDstRoot.c_str(), SYNC_VOLUME_FULLSYNC | SYNC_VOLUME_WAIT); + if (err) { + fprintf(stderr, "Volume sync failed errnor=%d (%s)\n", err, strerror(err)); + } + + return !cacheBuildFailure; +} diff --git a/dyld3/shared-cache/CacheBuilder.cpp b/dyld3/shared-cache/CacheBuilder.cpp new file mode 100644 index 0000000..b4fe35a --- /dev/null +++ b/dyld3/shared-cache/CacheBuilder.cpp @@ -0,0 +1,1784 @@ +/* -*- 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 "MachOParser.h" +#include "CodeSigningTypes.h" +#include "DyldSharedCache.h" +#include "CacheBuilder.h" +#include "FileAbstraction.hpp" +#include "LaunchCacheWriter.h" +#include "Trie.hpp" +#include "Diagnostics.h" +#include "ImageProxy.h" + +#if __has_include("dyld_cache_config.h") + #include "dyld_cache_config.h" +#else + #define ARM_SHARED_REGION_START 0x1A000000ULL + #define ARM_SHARED_REGION_SIZE 0x26000000ULL + #define ARM64_SHARED_REGION_START 0x180000000ULL + #define ARM64_SHARED_REGION_SIZE 0x40000000ULL +#endif + +const CacheBuilder::ArchLayout CacheBuilder::_s_archLayout[] = { + { 0x7FFF20000000ULL, 0xEFE00000ULL, 0x40000000, 0xFFFF000000000000, "x86_64", 0, 0, 0, 12, true, true }, + { 0x7FFF20000000ULL, 0xEFE00000ULL, 0x40000000, 0xFFFF000000000000, "x86_64h", 0, 0, 0, 12, true, true }, + { SHARED_REGION_BASE_I386, SHARED_REGION_SIZE_I386, 0x00200000, 0x0, "i386", 0, 0, 0, 12, false, false }, + { ARM64_SHARED_REGION_START, ARM64_SHARED_REGION_SIZE, 0x02000000, 0x00FFFF0000000000, "arm64", 0x0000C000, 0x00100000, 0x07F00000, 14, false, true }, + { ARM64_SHARED_REGION_START, ARM64_SHARED_REGION_SIZE, 0x02000000, 0x00FFFF0000000000, "arm64e", 0x0000C000, 0x00100000, 0x07F00000, 14, false, true }, + { ARM_SHARED_REGION_START, ARM_SHARED_REGION_SIZE, 0x02000000, 0xE0000000, "armv7s", 0, 0, 0, 14, false, false }, + { ARM_SHARED_REGION_START, ARM_SHARED_REGION_SIZE, 0x00400000, 0xE0000000, "armv7k", 0, 0, 0, 14, false, false }, + { 0x40000000, 0x40000000, 0x02000000, 0x0, "sim-x86", 0, 0, 0, 14, false, false } +}; + + +// These are dylibs that may be interposed, so stubs calling into them should never be bypassed +const char* const CacheBuilder::_s_neverStubEliminate[] = { + "/usr/lib/system/libdispatch.dylib", + nullptr +}; + + +CacheBuilder::CacheBuilder(const DyldSharedCache::CreateOptions& options) + : _options(options) + , _buffer(nullptr) + , _diagnostics(options.loggingPrefix, options.verbose) + , _archLayout(nullptr) + , _aliasCount(0) + , _slideInfoFileOffset(0) + , _slideInfoBufferSizeAllocated(0) + , _allocatedBufferSize(0) + , _currentFileSize(0) + , _vmSize(0) + , _branchPoolsLinkEditStartAddr(0) +{ + + std::string targetArch = options.archName; + if ( options.forSimulator && (options.archName == "i386") ) + targetArch = "sim-x86"; + + for (const ArchLayout& layout : _s_archLayout) { + if ( layout.archName == targetArch ) { + _archLayout = &layout; + break; + } + } +} + + +std::string CacheBuilder::errorMessage() +{ + return _diagnostics.errorMessage(); +} + +const std::set CacheBuilder::warnings() +{ + return _diagnostics.warnings(); +} + +void CacheBuilder::deleteBuffer() +{ + vm_deallocate(mach_task_self(), (vm_address_t)_buffer, _allocatedBufferSize); + _buffer = nullptr; + _allocatedBufferSize = 0; +} + +std::vector +CacheBuilder::makeSortedDylibs(const std::vector& dylibs, const std::unordered_map sortOrder) +{ + std::vector sortedDylibs = dylibs; + + std::sort(sortedDylibs.begin(), sortedDylibs.end(), [&](const DyldSharedCache::MappedMachO& a, const DyldSharedCache::MappedMachO& b) { + const auto& orderA = sortOrder.find(a.runtimePath); + const auto& orderB = sortOrder.find(b.runtimePath); + bool foundA = (orderA != sortOrder.end()); + bool foundB = (orderB != sortOrder.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 a.runtimePath < b.runtimePath; + }); + + return sortedDylibs; +} + + +inline uint32_t absolutetime_to_milliseconds(uint64_t abstime) +{ + return (uint32_t)(abstime/1000/1000); +} + +struct DylibAndSize +{ + const char* installName; + uint64_t size; +}; + +bool CacheBuilder::cacheOverflow(const dyld_cache_mapping_info regions[3]) +{ + if ( _archLayout->sharedRegionsAreDiscontiguous ) { + // for macOS x86_64 cache, need to check each region for overflow + return ( (regions[0].size > 0x60000000) || (regions[1].size > 0x40000000) || (regions[2].size > 0x3FE00000) ); + } + else { + return (_vmSize > _archLayout->sharedMemorySize); + } +} + +bool CacheBuilder::build(const std::vector& dylibs, + const std::vector& otherOsDylibsInput, + const std::vector& osExecutables) +{ + // error out instead of crash if cache has no dylibs + // FIXME: plist should specify required vs optional dylibs + if ( dylibs.size() < 30 ) { + _diagnostics.error("missing required minimum set of dylibs"); + return false; + } + uint64_t t1 = mach_absolute_time(); + + + // make copy of dylib list and sort + std::vector sortedDylibs = makeSortedDylibs(dylibs, _options.dylibOrdering); + std::vector otherOsDylibs = otherOsDylibsInput; + + // assign addresses for each segment of each dylib in new cache + dyld_cache_mapping_info regions[3]; + SegmentMapping segmentMapping = assignSegmentAddresses(sortedDylibs, regions); + if ( cacheOverflow(regions) ) { + if ( !_options.evictLeafDylibsOnOverflow ) { + _diagnostics.error("cache overflow: %lluMB (max %lluMB)", _vmSize / 1024 / 1024, (_archLayout->sharedMemorySize) / 1024 / 1024); + return false; + } + // find all leaf (not referenced by anything else in cache) dylibs + + // build count of how many references there are to each dylib + __block std::map referenceCount; + for (const DyldSharedCache::MappedMachO& dylib : sortedDylibs) { + dyld3::MachOParser parser(dylib.mh); + parser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) { + referenceCount[loadPath] += 1; + }); + } + + // find all dylibs not referenced + std::vector unreferencedDylibs; + for (const DyldSharedCache::MappedMachO& dylib : sortedDylibs) { + dyld3::MachOParser parser(dylib.mh); + const char* installName = parser.installName(); + if ( referenceCount.count(installName) == 0 ) { + // conservative: sum up all segments except LINKEDIT + __block uint64_t segsSize = 0; + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool &stop) { + if ( strcmp(segName, "__LINKEDIT") != 0 ) + segsSize += vmSize; + }); + unreferencedDylibs.push_back({installName, segsSize}); + } + } + // sort leaf dylibs by size + std::sort(unreferencedDylibs.begin(), unreferencedDylibs.end(), [&](const DylibAndSize& a, const DylibAndSize& b) { + return ( a.size > b.size ); + }); + + // build set of dylibs that if removed will allow cache to build + uint64_t reductionTarget = _vmSize - _archLayout->sharedMemorySize; + std::set toRemove; + for (DylibAndSize& dylib : unreferencedDylibs) { + if ( _options.verbose ) + _diagnostics.warning("to prevent cache overflow, not caching %s", dylib.installName); + toRemove.insert(dylib.installName); + if ( dylib.size > reductionTarget ) + break; + reductionTarget -= dylib.size; + } + // transfer overflow dylibs from cached vector to other vector + for (const std::string& installName : toRemove) { + for (std::vector::iterator it=sortedDylibs.begin(); it != sortedDylibs.end(); ++it) { + dyld3::MachOParser parser(it->mh); + if ( installName == parser.installName() ) { + otherOsDylibs.push_back(*it); + sortedDylibs.erase(it); + break; + } + } + } + // re-layout cache + segmentMapping = assignSegmentAddresses(sortedDylibs, regions); + if ( cacheOverflow(regions) ) { + _diagnostics.error("cache overflow, tried evicting %ld leaf daylibs, but still too big: %lluMB (max %lluMB)", + toRemove.size(), _vmSize / 1024 / 1024, (_archLayout->sharedMemorySize) / 1024 / 1024); + return false; + } + } + + // allocate buffer for new cache + _allocatedBufferSize = std::max(_currentFileSize, (uint64_t)0x100000)*1.1; // add 10% to allocation to support large closures + if ( vm_allocate(mach_task_self(), (vm_address_t*)&_buffer, _allocatedBufferSize, VM_FLAGS_ANYWHERE) != 0 ) { + _diagnostics.error("could not allocate buffer"); + return false; + } + _currentFileSize = _allocatedBufferSize; + + // write unoptimized cache + writeCacheHeader(regions, sortedDylibs, segmentMapping); + copyRawSegments(sortedDylibs, segmentMapping); + adjustAllImagesForNewSegmentLocations(sortedDylibs, segmentMapping); + if ( _diagnostics.hasError() ) + return false; + + bindAllImagesInCacheFile(regions); + if ( _diagnostics.hasError() ) + return false; + + // optimize ObjC + if ( _options.optimizeObjC ) + optimizeObjC(_buffer, _archLayout->is64, _options.optimizeStubs, _pointersForASLR, _diagnostics); + if ( _diagnostics.hasError() ) + return false; + + // optimize away stubs + std::vector branchPoolOffsets; + uint64_t cacheStartAddress = _archLayout->sharedMemoryStart; + if ( _options.optimizeStubs ) { + std::vector branchPoolStartAddrs; + const uint64_t* p = (uint64_t*)((uint8_t*)_buffer + _buffer->header.branchPoolsOffset); + for (int i=0; i < _buffer->header.branchPoolsCount; ++i) { + uint64_t poolAddr = p[i]; + branchPoolStartAddrs.push_back(poolAddr); + branchPoolOffsets.push_back(poolAddr - cacheStartAddress); + } + bypassStubs(_buffer, branchPoolStartAddrs, _s_neverStubEliminate, _diagnostics); + } + uint64_t t2 = mach_absolute_time(); + + // FIPS seal corecrypto, This must be done after stub elimination (so that + // __TEXT,__text is not changed after sealing), but before LINKEDIT + // optimization (so that we still have access to local symbols) + fipsSign(); + + // merge and compact LINKEDIT segments + dyld_cache_local_symbols_info* localsInfo = nullptr; + if ( dylibs.size() == 0 ) + _currentFileSize = 0x1000; + else + _currentFileSize = optimizeLinkedit(_buffer, _archLayout->is64, _options.excludeLocalSymbols, _options.optimizeStubs, branchPoolOffsets, _diagnostics, &localsInfo); + + uint64_t t3 = mach_absolute_time(); + + // add ImageGroup for all dylibs in cache + __block std::vector cachedDylibs; + std::unordered_map mapIntoSortedDylibs; + for (const DyldSharedCache::MappedMachO& entry : sortedDylibs) { + mapIntoSortedDylibs[entry.runtimePath] = &entry; + } + _buffer->forEachImage(^(const mach_header* mh, const char* installName) { + auto pos = mapIntoSortedDylibs.find(installName); + if ( pos != mapIntoSortedDylibs.end() ) { + DyldSharedCache::MappedMachO newEntry = *(pos->second); + newEntry.mh = mh; + cachedDylibs.push_back(newEntry); + } + else { + bool found = false; + for (const std::string& prefix : _options.pathPrefixes) { + std::string fullPath = prefix + installName; + char resolvedPath[PATH_MAX]; + if ( realpath(fullPath.c_str(), resolvedPath) != nullptr ) { + std::string resolvedUnPrefixed = &resolvedPath[prefix.size()]; + pos = mapIntoSortedDylibs.find(resolvedUnPrefixed); + if ( pos != mapIntoSortedDylibs.end() ) { + DyldSharedCache::MappedMachO newEntry = *(pos->second); + newEntry.mh = mh; + cachedDylibs.push_back(newEntry); + found = true; + } + } + } + if ( !found ) + fprintf(stderr, "missing mapping for %s\n", installName); + } + }); + dyld3::DyldCacheParser dyldCacheParser(_buffer, true); + dyld3::ImageProxyGroup* dylibGroup = dyld3::ImageProxyGroup::makeDyldCacheDylibsGroup(_diagnostics, dyldCacheParser, cachedDylibs, + _options.pathPrefixes, _patchTable, + _options.optimizeStubs, !_options.dylibsRemovedDuringMastering); + if ( _diagnostics.hasError() ) + return false; + addCachedDylibsImageGroup(dylibGroup); + if ( _diagnostics.hasError() ) + return false; + + uint64_t t4 = mach_absolute_time(); + + // add ImageGroup for other OS dylibs and bundles + dyld3::ImageProxyGroup* otherGroup = dyld3::ImageProxyGroup::makeOtherOsGroup(_diagnostics, dyldCacheParser, dylibGroup, otherOsDylibs, + _options.inodesAreSameAsRuntime, _options.pathPrefixes); + if ( _diagnostics.hasError() ) + return false; + addCachedOtherDylibsImageGroup(otherGroup); + if ( _diagnostics.hasError() ) + return false; + + uint64_t t5 = mach_absolute_time(); + + // compute and add launch closures + std::map closures; + for (const DyldSharedCache::MappedMachO& mainProg : osExecutables) { + Diagnostics clsDiag; + const dyld3::launch_cache::binary_format::Closure* cls = dyld3::ImageProxyGroup::makeClosure(clsDiag, dyldCacheParser, dylibGroup, otherGroup, mainProg, + _options.inodesAreSameAsRuntime, _options.pathPrefixes); + if ( clsDiag.hasError() ) { + // if closure cannot be built, silently skip it, unless in verbose mode + if ( _options.verbose ) { + _diagnostics.warning("building closure for '%s': %s", mainProg.runtimePath.c_str(), clsDiag.errorMessage().c_str()); + for (const std::string& warn : clsDiag.warnings() ) + _diagnostics.warning("%s", warn.c_str()); + } + } + else { + closures[mainProg.runtimePath] = cls; + } + } + addClosures(closures); + if ( _diagnostics.hasError() ) + return false; + + uint64_t t6 = mach_absolute_time(); + + // fill in slide info at start of region[2] + // do this last because it modifies pointers in DATA segments + if ( _options.cacheSupportsASLR ) { + if ( _archLayout->is64 ) + writeSlideInfoV2>(); + else + writeSlideInfoV2>(); + } + + uint64_t t7 = mach_absolute_time(); + + // update last region size + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)_buffer + _buffer->header.mappingOffset); + _currentFileSize = align(_currentFileSize, _archLayout->sharedRegionAlignP2); + mappings[2].size = _currentFileSize - mappings[2].fileOffset; + + // record cache bounds + _buffer->header.sharedRegionStart = _archLayout->sharedMemoryStart; + _buffer->header.sharedRegionSize = _archLayout->sharedMemorySize; + if ( _archLayout->sharedRegionsAreDiscontiguous ) { + // special case x86_64 which has three non-contiguous chunks each in their own 1GB regions + uint64_t maxSlide0 = 0x60000000 - mappings[0].size; // TEXT region has 1.5GB region + uint64_t maxSlide1 = 0x40000000 - mappings[1].size; + uint64_t maxSlide2 = 0x3FE00000 - mappings[2].size; + _buffer->header.maxSlide = std::min(std::min(maxSlide0, maxSlide1), maxSlide2); + } + else { + _buffer->header.maxSlide = (_archLayout->sharedMemoryStart + _archLayout->sharedMemorySize) - (mappings[2].address + mappings[2].size); + } + + // append "unmapped" local symbols region + if ( _options.excludeLocalSymbols ) { + size_t localsInfoSize = align(localsInfo->stringsOffset + localsInfo->stringsSize, _archLayout->sharedRegionAlignP2); + if ( _currentFileSize + localsInfoSize > _allocatedBufferSize ) { + _diagnostics.warning("local symbols omitted because cache buffer overflow"); + } + else { + memcpy((char*)_buffer+_currentFileSize, localsInfo, localsInfoSize); + _buffer->header.localSymbolsOffset = _currentFileSize; + _buffer->header.localSymbolsSize = localsInfoSize; + _currentFileSize += localsInfoSize; + } + free((void*)localsInfo); + } + + recomputeCacheUUID(); + + // Calculate the VMSize of the resulting cache + __block uint64_t endAddr = 0; + _buffer->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + if (vmAddr+size > endAddr) + endAddr = vmAddr+size; + }); + _vmSize = endAddr - cacheStartAddress; + + // last sanity check on size + if ( _vmSize > _archLayout->sharedMemorySize ) { + _diagnostics.error("cache overflow after optimizations. %lluMB (max %lluMB)", _vmSize / 1024 / 1024, (_archLayout->sharedMemorySize) / 1024 / 1024); + return true; + } + + // codesignature is part of file, but is not mapped + codeSign(); + if ( _diagnostics.hasError() ) + return false; + + uint64_t t8 = mach_absolute_time(); + + if ( _options.verbose ) { + fprintf(stderr, "time to copy and bind cached dylibs: %ums\n", absolutetime_to_milliseconds(t2-t1)); + fprintf(stderr, "time to optimize LINKEDITs: %ums\n", absolutetime_to_milliseconds(t3-t2)); + fprintf(stderr, "time to build ImageGroup of %lu cached dylibs: %ums\n", sortedDylibs.size(), absolutetime_to_milliseconds(t4-t3)); + fprintf(stderr, "time to build ImageGroup of %lu other dylibs: %ums\n", otherOsDylibs.size(), absolutetime_to_milliseconds(t5-t4)); + fprintf(stderr, "time to build %lu closures: %ums\n", osExecutables.size(), absolutetime_to_milliseconds(t6-t5)); + fprintf(stderr, "time to compute slide info: %ums\n", absolutetime_to_milliseconds(t7-t6)); + fprintf(stderr, "time to compute UUID and codesign cache file: %ums\n", absolutetime_to_milliseconds(t8-t7)); + } + + // trim over allocated buffer + if ( _allocatedBufferSize > _currentFileSize ) { + uint8_t* startOfUnused = (uint8_t*)_buffer+_currentFileSize; + size_t unusedLen = _allocatedBufferSize-_currentFileSize; + vm_deallocate(mach_task_self(), (vm_address_t)startOfUnused, unusedLen); + _allocatedBufferSize = _currentFileSize; + } + + return false; +} + + +void CacheBuilder::writeCacheHeader(const dyld_cache_mapping_info regions[3], const std::vector& dylibs, const SegmentMapping& segmentMappings) +{ + // "dyld_v1" + spaces + archName(), with enough spaces to pad to 15 bytes + std::string magic = "dyld_v1"; + magic.append(15 - magic.length() - _options.archName.length(), ' '); + magic.append(_options.archName); + assert(magic.length() == 15); + + // fill in header + memcpy(_buffer->header.magic, magic.c_str(), 16); + _buffer->header.mappingOffset = sizeof(dyld_cache_header); + _buffer->header.mappingCount = 3; + _buffer->header.imagesOffset = (uint32_t)(_buffer->header.mappingOffset + 3*sizeof(dyld_cache_mapping_info) + sizeof(uint64_t)*_branchPoolStarts.size()); + _buffer->header.imagesCount = (uint32_t)dylibs.size() + _aliasCount; + _buffer->header.dyldBaseAddress = 0; + _buffer->header.codeSignatureOffset= 0; + _buffer->header.codeSignatureSize = 0; + _buffer->header.slideInfoOffset = _slideInfoFileOffset; + _buffer->header.slideInfoSize = _slideInfoBufferSizeAllocated; + _buffer->header.localSymbolsOffset = 0; + _buffer->header.localSymbolsSize = 0; + _buffer->header.cacheType = _options.optimizeStubs ? kDyldSharedCacheTypeProduction : kDyldSharedCacheTypeDevelopment; + _buffer->header.accelerateInfoAddr = 0; + _buffer->header.accelerateInfoSize = 0; + bzero(_buffer->header.uuid, 16); // overwritten later by recomputeCacheUUID() + _buffer->header.branchPoolsOffset = _buffer->header.mappingOffset + 3*sizeof(dyld_cache_mapping_info); + _buffer->header.branchPoolsCount = (uint32_t)_branchPoolStarts.size(); + _buffer->header.imagesTextOffset = _buffer->header.imagesOffset + sizeof(dyld_cache_image_info)*_buffer->header.imagesCount; + _buffer->header.imagesTextCount = dylibs.size(); + _buffer->header.platform = (uint8_t)_options.platform; + _buffer->header.formatVersion = dyld3::launch_cache::binary_format::kFormatVersion; + _buffer->header.dylibsExpectedOnDisk = !_options.dylibsRemovedDuringMastering; + _buffer->header.simulator = _options.forSimulator; + + // fill in mappings + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)_buffer + _buffer->header.mappingOffset); + mappings[0] = regions[0]; + mappings[1] = regions[1]; + mappings[2] = regions[2]; + + // fill in branch pool addresses + uint64_t* p = (uint64_t*)((char*)_buffer + _buffer->header.branchPoolsOffset); + for (uint64_t pool : _branchPoolStarts) { + *p++ = pool; + } + + // fill in image table + dyld_cache_image_info* images = (dyld_cache_image_info*)((char*)_buffer + _buffer->header.imagesOffset); + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + const std::vector& segs = segmentMappings.at(dylib.mh); + dyld3::MachOParser parser(dylib.mh); + const char* installName = parser.installName(); + images->address = segs[0].dstCacheAddress; + if ( _options.dylibsRemovedDuringMastering ) { + images->modTime = 0; + images->inode = pathHash(installName); + } + else { + images->modTime = dylib.modTime; + images->inode = dylib.inode; + } + uint32_t installNameOffsetInTEXT = (uint32_t)(installName - (char*)dylib.mh); + images->pathFileOffset = (uint32_t)segs[0].dstCacheOffset + installNameOffsetInTEXT; + ++images; + } + // append aliases image records and strings +/* + 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->modTime = dylib->lastModTime; + images->inode = dylib->inode; + } + else { + images->modTime = 0; + images->inode = pathHash(alias.c_str()); + } + images->pathFileOffset = offset; + //fprintf(stderr, "adding alias %s for %s\n", alias.c_str(), dylib->installName.c_str()); + ::strcpy((char*)&_buffer[offset], alias.c_str()); + offset += alias.size() + 1; + ++images; + } + } + } +*/ + // calculate start of text image array and trailing string pool + dyld_cache_image_text_info* textImages = (dyld_cache_image_text_info*)((char*)_buffer + _buffer->header.imagesTextOffset); + uint32_t stringOffset = (uint32_t)(_buffer->header.imagesTextOffset + sizeof(dyld_cache_image_text_info) * dylibs.size()); + + // write text image array and image names pool at same time + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + const std::vector& segs = segmentMappings.at(dylib.mh); + dyld3::MachOParser parser(dylib.mh); + parser.getUuid(textImages->uuid); + textImages->loadAddress = segs[0].dstCacheAddress; + textImages->textSegmentSize = (uint32_t)segs[0].dstCacheSegmentSize; + textImages->pathOffset = stringOffset; + const char* installName = parser.installName(); + ::strcpy((char*)_buffer + stringOffset, installName); + stringOffset += (uint32_t)strlen(installName)+1; + ++textImages; + } + + // make sure header did not overflow into first mapped image + const dyld_cache_image_info* firstImage = (dyld_cache_image_info*)((char*)_buffer + _buffer->header.imagesOffset); + assert(stringOffset <= (firstImage->address - mappings[0].address)); +} + + +void CacheBuilder::copyRawSegments(const std::vector& dylibs, const SegmentMapping& mapping) +{ + uint8_t* cacheBytes = (uint8_t*)_buffer; + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + auto pos = mapping.find(dylib.mh); + assert(pos != mapping.end()); + for (const SegmentMappingInfo& info : pos->second) { + //fprintf(stderr, "copy %s segment %s (0x%08X bytes) from %p to %p (logical addr 0x%llX) for %s\n", _options.archName.c_str(), info.segName, info.copySegmentSize, info.srcSegment, &cacheBytes[info.dstCacheOffset], info.dstCacheAddress, dylib.runtimePath.c_str()); + ::memcpy(&cacheBytes[info.dstCacheOffset], info.srcSegment, info.copySegmentSize); + } + } +} + +void CacheBuilder::adjustAllImagesForNewSegmentLocations(const std::vector& dylibs, const SegmentMapping& mapping) +{ + uint8_t* cacheBytes = (uint8_t*)_buffer; + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + auto pos = mapping.find(dylib.mh); + assert(pos != mapping.end()); + mach_header* mhInCache = (mach_header*)&cacheBytes[pos->second[0].dstCacheOffset]; + adjustDylibSegments(_buffer, _archLayout->is64, mhInCache, pos->second, _pointersForASLR, _diagnostics); + if ( _diagnostics.hasError() ) + break; + } +} + +struct Counts { + unsigned long lazyCount = 0; + unsigned long nonLazyCount = 0; +}; + +void CacheBuilder::bindAllImagesInCacheFile(const dyld_cache_mapping_info regions[3]) +{ + const bool log = false; + __block std::unordered_map useCounts; + + // build map of install names to mach_headers + __block std::unordered_map installNameToMH; + __block std::vector dylibMHs; + _buffer->forEachImage(^(const mach_header* mh, const char* installName) { + installNameToMH[installName] = mh; + dylibMHs.push_back(mh); + }); + + __block Diagnostics parsingDiag; + bool (^dylibFinder)(uint32_t, const char*, void* , const mach_header**, void**) = ^(uint32_t depIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) { + auto pos = installNameToMH.find(depLoadPath); + if ( pos != installNameToMH.end() ) { + *foundMH = pos->second; + *foundExtra = nullptr; + return true; + } + parsingDiag.error("dependent dylib %s not found", depLoadPath); + return false; + }; + if ( parsingDiag.hasError() ) { + _diagnostics.error("%s", parsingDiag.errorMessage().c_str()); + return; + } + + // bind every dylib in cache + for (const mach_header* mh : dylibMHs) { + dyld3::MachOParser parser(mh, true); + bool is64 = parser.is64(); + const char* depPaths[256]; + const char** depPathsArray = depPaths; + __block int depIndex = 1; + parser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + depPathsArray[depIndex++] = loadPath; + }); + uint8_t* segCacheStarts[10]; + uint64_t segCacheAddrs[10]; + uint8_t** segCacheStartsArray = segCacheStarts; + uint64_t* segCacheAddrsArray = segCacheAddrs; + __block int segIndex = 0; + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + segCacheStartsArray[segIndex] = (segIndex == 0) ? (uint8_t*)mh : (uint8_t*)_buffer + fileOffset; + segCacheAddrsArray[segIndex] = vmAddr; + ++segIndex; + }); + __block Diagnostics bindingDiag; + parser.forEachBind(bindingDiag, ^(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, int libOrdinal, uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop) { + if ( log ) { + if ( lazy ) + useCounts[symbolName].lazyCount += 1; + else + useCounts[symbolName].nonLazyCount += 1; + } + const mach_header* targetMH = nullptr; + if ( libOrdinal == BIND_SPECIAL_DYLIB_SELF ) { + targetMH = mh; + } + else if ( libOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) { + parsingDiag.error("bind ordinal BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE not supported in dylibs in dyld shared cache (found in %s)", parser.installName()); + stop = true; + return; + } + else if ( libOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) { + parsingDiag.error("bind ordinal BIND_SPECIAL_DYLIB_FLAT_LOOKUP not supported in dylibs in dyld shared cache (found in %s)", parser.installName()); + stop = true; + return; + } + else { + const char* fromPath = depPathsArray[libOrdinal]; + auto pos = installNameToMH.find(fromPath); + if (pos == installNameToMH.end()) { + if (!weakImport) { + _diagnostics.error("dependent dylib %s not found", fromPath); + } + return; + } + targetMH = pos->second; + } + dyld3::MachOParser targetParser(targetMH, true); + dyld3::MachOParser::FoundSymbol foundInfo; + uint64_t targetValue = 0; + uint8_t* fixupLoc = segCacheStartsArray[dataSegIndex] + dataSegOffset; + if ( targetParser.findExportedSymbol(parsingDiag, symbolName, nullptr, foundInfo, dylibFinder) ) { + const mach_header* foundInMH = foundInfo.foundInDylib; + dyld3::MachOParser foundInParser(foundInMH, true); + uint64_t foundInBaseAddress = foundInParser.preferredLoadAddress(); + switch ( foundInfo.kind ) { + case dyld3::MachOParser::FoundSymbol::Kind::resolverOffset: + // Bind to the target stub for resolver based functions. + // There may be a later optimization to alter the client + // stubs to directly to the target stub's lazy pointer. + case dyld3::MachOParser::FoundSymbol::Kind::headerOffset: + targetValue = foundInBaseAddress + foundInfo.value + addend; + _pointersForASLR.push_back((void*)fixupLoc); + if ( foundInMH != mh ) { + uint32_t mhVmOffset = (uint32_t)((uint8_t*)foundInMH - (uint8_t*)_buffer); + uint32_t definitionCacheVmOffset = (uint32_t)(mhVmOffset + foundInfo.value); + uint32_t referenceCacheDataVmOffset = (uint32_t)(segCacheAddrsArray[dataSegIndex] + dataSegOffset - regions[1].address); + assert(referenceCacheDataVmOffset < (1<<30)); + dyld3::launch_cache::binary_format::PatchOffset entry; + entry.last = false; + entry.hasAddend = (addend != 0); + entry.dataRegionOffset = referenceCacheDataVmOffset; + _patchTable[foundInMH][definitionCacheVmOffset].insert(*((uint32_t*)&entry)); + } + break; + case dyld3::MachOParser::FoundSymbol::Kind::absolute: + // pointers set to absolute values are not slid + targetValue = foundInfo.value + addend; + break; + } + } + else if ( weakImport ) { + // weak pointers set to zero are not slid + targetValue = 0; + } + else { + parsingDiag.error("cannot find symbol %s, needed in dylib %s", symbolName, parser.installName()); + stop = true; + } + switch ( type ) { + case BIND_TYPE_POINTER: + if ( is64 ) + *((uint64_t*)fixupLoc) = targetValue; + else + *((uint32_t*)fixupLoc) = (uint32_t)targetValue; + break; + case BIND_TYPE_TEXT_ABSOLUTE32: + case BIND_TYPE_TEXT_PCREL32: + parsingDiag.error("text relocs not supported for shared cache binding in %s", parser.installName()); + stop = true; + break; + default: + parsingDiag.error("bad bind type (%d) in %s", type, parser.installName()); + stop = true; + break; + + } + }); + if ( bindingDiag.hasError() ) { + parsingDiag.error("%s in dylib %s", bindingDiag.errorMessage().c_str(), parser.installName()); + } + if ( parsingDiag.hasError() ) + break; + // also need to add patch locations for weak-binds that point within same image, since they are not captured by binds above + parser.forEachWeakDef(bindingDiag, ^(bool strongDef, uint32_t dataSegIndex, uint64_t dataSegOffset, uint64_t addend, const char* symbolName, bool &stop) { + if ( strongDef ) + return; + uint8_t* fixupLoc = segCacheStartsArray[dataSegIndex] + dataSegOffset; + dyld3::MachOParser::FoundSymbol weakFoundInfo; + Diagnostics weakLookupDiag; + if ( parser.findExportedSymbol(weakLookupDiag, symbolName, nullptr, weakFoundInfo, nullptr) ) { + // this is an interior pointing (rebased) pointer + uint64_t targetValue; + if ( is64 ) + targetValue = *((uint64_t*)fixupLoc); + else + targetValue = *((uint32_t*)fixupLoc); + uint32_t definitionCacheVmOffset = (uint32_t)(targetValue - regions[0].address); + uint32_t referenceCacheDataVmOffset = (uint32_t)(segCacheAddrsArray[dataSegIndex] + dataSegOffset - regions[1].address); + assert(referenceCacheDataVmOffset < (1<<30)); + dyld3::launch_cache::binary_format::PatchOffset entry; + entry.last = false; + entry.hasAddend = (addend != 0); + entry.dataRegionOffset = referenceCacheDataVmOffset; + _patchTable[mh][definitionCacheVmOffset].insert(*((uint32_t*)&entry)); + } + }); + if ( bindingDiag.hasError() ) { + parsingDiag.error("%s in dylib %s", bindingDiag.errorMessage().c_str(), parser.installName()); + } + if ( parsingDiag.hasError() ) + break; + } + + if ( log ) { + unsigned lazyCount = 0; + unsigned nonLazyCount = 0; + std::unordered_set lazyTargets; + for (auto entry : useCounts) { + fprintf(stderr, "% 3ld % 3ld %s\n", entry.second.lazyCount, entry.second.nonLazyCount, entry.first.c_str()); + lazyCount += entry.second.lazyCount; + nonLazyCount += entry.second.nonLazyCount; + if ( entry.second.lazyCount != 0 ) + lazyTargets.insert(entry.first); + } + fprintf(stderr, "lazyCount = %d\n", lazyCount); + fprintf(stderr, "nonLazyCount = %d\n", nonLazyCount); + fprintf(stderr, "unique lazys = %ld\n", lazyTargets.size()); + } + + if ( parsingDiag.hasError() ) + _diagnostics.error("%s", parsingDiag.errorMessage().c_str()); +} + + +void CacheBuilder::recomputeCacheUUID(void) +{ + // Clear existing UUID, then MD5 whole cache buffer. + uint8_t* uuidLoc = _buffer->header.uuid; + bzero(uuidLoc, 16); + CC_MD5(_buffer, (unsigned)_currentFileSize, 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; +} + + +CacheBuilder::SegmentMapping CacheBuilder::assignSegmentAddresses(const std::vector& dylibs, dyld_cache_mapping_info regions[3]) +{ + // calculate size of header info and where first dylib's mach_header should start + size_t startOffset = sizeof(dyld_cache_header) + 3*sizeof(dyld_cache_mapping_info); + size_t maxPoolCount = 0; + if ( _archLayout->branchReach != 0 ) + maxPoolCount = (_archLayout->sharedMemorySize / _archLayout->branchReach); + startOffset += maxPoolCount * sizeof(uint64_t); + startOffset += sizeof(dyld_cache_image_info) * dylibs.size(); + startOffset += sizeof(dyld_cache_image_text_info) * dylibs.size(); + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + dyld3::MachOParser parser(dylib.mh); + startOffset += (strlen(parser.installName()) + 1); + } + //fprintf(stderr, "%s total header size = 0x%08lX\n", _options.archName.c_str(), startOffset); + startOffset = align(startOffset, 12); + + _branchPoolStarts.clear(); + __block uint64_t addr = _archLayout->sharedMemoryStart; + __block SegmentMapping result; + + // assign TEXT segment addresses + regions[0].address = addr; + regions[0].fileOffset = 0; + regions[0].initProt = VM_PROT_READ | VM_PROT_EXECUTE; + regions[0].maxProt = VM_PROT_READ | VM_PROT_EXECUTE; + addr += startOffset; // header + + __block uint64_t lastPoolAddress = addr; + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + dyld3::MachOParser parser(dylib.mh, true); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop) { + if ( protections != (VM_PROT_READ | VM_PROT_EXECUTE) ) + return; + // Insert branch island pools every 128MB for arm64 + if ( (_archLayout->branchPoolTextSize != 0) && ((addr + vmSize - lastPoolAddress) > _archLayout->branchReach) ) { + _branchPoolStarts.push_back(addr); + _diagnostics.verbose("adding branch pool at 0x%llX\n", addr); + lastPoolAddress = addr; + addr += _archLayout->branchPoolTextSize; + } + // Keep __TEXT segments 4K or more aligned + addr = align(addr, std::max(p2align, (uint8_t)12)); + SegmentMappingInfo info; + info.srcSegment = (uint8_t*)dylib.mh + fileOffset; + info.segName = segName; + info.dstCacheAddress = addr; + info.dstCacheOffset = (uint32_t)(addr - regions[0].address + regions[0].fileOffset); + info.dstCacheSegmentSize = (uint32_t)align(sizeOfSections, 12); + info.copySegmentSize = (uint32_t)align(sizeOfSections, 12); + info.srcSegmentIndex = segIndex; + result[dylib.mh].push_back(info); + addr += info.dstCacheSegmentSize; + }); + } + // align TEXT region end + uint64_t endTextAddress = align(addr, _archLayout->sharedRegionAlignP2); + regions[0].size = endTextAddress - regions[0].address; + + // assign __DATA* addresses + if ( _archLayout->sharedRegionsAreDiscontiguous ) + addr = _archLayout->sharedMemoryStart + 0x60000000; + else + addr = align((addr + _archLayout->sharedRegionPadding), _archLayout->sharedRegionAlignP2); + regions[1].address = addr; + regions[1].fileOffset = regions[0].fileOffset + regions[0].size; + regions[1].initProt = VM_PROT_READ | VM_PROT_WRITE; + regions[1].maxProt = VM_PROT_READ | VM_PROT_WRITE; + + // layout all __DATA_CONST segments + __block int dataConstSegmentCount = 0; + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + dyld3::MachOParser parser(dylib.mh, true); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop) { + if ( protections != (VM_PROT_READ | VM_PROT_WRITE) ) + return; + if ( strcmp(segName, "__DATA_CONST") != 0 ) + return; + ++dataConstSegmentCount; + // Pack __DATA_CONST segments + addr = align(addr, p2align); + size_t copySize = std::min((size_t)fileSize, (size_t)sizeOfSections); + SegmentMappingInfo info; + info.srcSegment = (uint8_t*)dylib.mh + fileOffset; + info.segName = segName; + info.dstCacheAddress = addr; + info.dstCacheOffset = (uint32_t)(addr - regions[1].address + regions[1].fileOffset); + info.dstCacheSegmentSize = (uint32_t)sizeOfSections; + info.copySegmentSize = (uint32_t)copySize; + info.srcSegmentIndex = segIndex; + result[dylib.mh].push_back(info); + addr += info.dstCacheSegmentSize; + }); + } + + // layout all __DATA segments (and other r/w non-dirty, non-const) segments + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + dyld3::MachOParser parser(dylib.mh, true); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop) { + if ( protections != (VM_PROT_READ | VM_PROT_WRITE) ) + return; + if ( strcmp(segName, "__DATA_CONST") == 0 ) + return; + if ( strcmp(segName, "__DATA_DIRTY") == 0 ) + return; + if ( dataConstSegmentCount > 10 ) { + // Pack __DATA segments only if we also have __DATA_CONST segments + addr = align(addr, p2align); + } + else { + // Keep __DATA segments 4K or more aligned + addr = align(addr, std::max(p2align, (uint8_t)12)); + } + size_t copySize = std::min((size_t)fileSize, (size_t)sizeOfSections); + SegmentMappingInfo info; + info.srcSegment = (uint8_t*)dylib.mh + fileOffset; + info.segName = segName; + info.dstCacheAddress = addr; + info.dstCacheOffset = (uint32_t)(addr - regions[1].address + regions[1].fileOffset); + info.dstCacheSegmentSize = (uint32_t)sizeOfSections; + info.copySegmentSize = (uint32_t)copySize; + info.srcSegmentIndex = segIndex; + result[dylib.mh].push_back(info); + addr += info.dstCacheSegmentSize; + }); + } + + // layout all __DATA_DIRTY segments, sorted + addr = align(addr, 12); + std::vector dirtyDataDylibs = makeSortedDylibs(dylibs, _options.dirtyDataSegmentOrdering); + for (const DyldSharedCache::MappedMachO& dylib : dirtyDataDylibs) { + dyld3::MachOParser parser(dylib.mh, true); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop) { + if ( protections != (VM_PROT_READ | VM_PROT_WRITE) ) + return; + if ( strcmp(segName, "__DATA_DIRTY") != 0 ) + return; + // Pack __DATA_DIRTY segments + addr = align(addr, p2align); + size_t copySize = std::min((size_t)fileSize, (size_t)sizeOfSections); + SegmentMappingInfo info; + info.srcSegment = (uint8_t*)dylib.mh + fileOffset; + info.segName = segName; + info.dstCacheAddress = addr; + info.dstCacheOffset = (uint32_t)(addr - regions[1].address + regions[1].fileOffset); + info.dstCacheSegmentSize = (uint32_t)sizeOfSections; + info.copySegmentSize = (uint32_t)copySize; + info.srcSegmentIndex = segIndex; + result[dylib.mh].push_back(info); + addr += info.dstCacheSegmentSize; + }); + } + + // align DATA region end + uint64_t endDataAddress = align(addr, _archLayout->sharedRegionAlignP2); + regions[1].size = endDataAddress - regions[1].address; + + // start read-only region + if ( _archLayout->sharedRegionsAreDiscontiguous ) + addr = _archLayout->sharedMemoryStart + 0xA0000000; + else + addr = align((addr + _archLayout->sharedRegionPadding), _archLayout->sharedRegionAlignP2); + regions[2].address = addr; + regions[2].fileOffset = regions[1].fileOffset + regions[1].size; + regions[2].maxProt = VM_PROT_READ; + regions[2].initProt = VM_PROT_READ; + + // reserve space for kernel ASLR slide info at start of r/o region + if ( _options.cacheSupportsASLR ) { + _slideInfoBufferSizeAllocated = align((regions[1].size/4096) * 4, _archLayout->sharedRegionAlignP2); // only need 2 bytes per page + _slideInfoFileOffset = regions[2].fileOffset; + addr += _slideInfoBufferSizeAllocated; + } + + // layout all read-only (but not LINKEDIT) segments + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + dyld3::MachOParser parser(dylib.mh, true); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop) { + if ( protections != VM_PROT_READ ) + return; + if ( strcmp(segName, "__LINKEDIT") == 0 ) + return; + // Keep segments segments 4K or more aligned + addr = align(addr, std::max(p2align, (uint8_t)12)); + SegmentMappingInfo info; + info.srcSegment = (uint8_t*)dylib.mh + fileOffset; + info.segName = segName; + info.dstCacheAddress = addr; + info.dstCacheOffset = (uint32_t)(addr - regions[2].address + regions[2].fileOffset); + info.dstCacheSegmentSize = (uint32_t)align(sizeOfSections, 12); + info.copySegmentSize = (uint32_t)sizeOfSections; + info.srcSegmentIndex = segIndex; + result[dylib.mh].push_back(info); + addr += info.dstCacheSegmentSize; + }); + } + // layout all LINKEDIT segments (after other read-only segments) + for (const DyldSharedCache::MappedMachO& dylib : dylibs) { + dyld3::MachOParser parser(dylib.mh, true); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop) { + if ( protections != VM_PROT_READ ) + return; + if ( strcmp(segName, "__LINKEDIT") != 0 ) + return; + // Keep segments segments 4K or more aligned + addr = align(addr, std::max(p2align, (uint8_t)12)); + SegmentMappingInfo info; + info.srcSegment = (uint8_t*)dylib.mh + fileOffset; + info.segName = segName; + info.dstCacheAddress = addr; + info.dstCacheOffset = (uint32_t)(addr - regions[2].address + regions[2].fileOffset); + info.dstCacheSegmentSize = (uint32_t)align(sizeOfSections, 12); + info.copySegmentSize = (uint32_t)align(fileSize, 12); + info.srcSegmentIndex = segIndex; + result[dylib.mh].push_back(info); + addr += info.dstCacheSegmentSize; + }); + } + // add room for branch pool linkedits + _branchPoolsLinkEditStartAddr = addr; + addr += (_branchPoolStarts.size() * _archLayout->branchPoolLinkEditSize); + + // align r/o region end + uint64_t endReadOnlyAddress = align(addr, _archLayout->sharedRegionAlignP2); + regions[2].size = endReadOnlyAddress - regions[2].address; + _currentFileSize = regions[2].fileOffset + regions[2].size; + + // FIXME: Confirm these numbers for all platform/arch combos + // assume LINKEDIT optimzation reduces LINKEDITs to %40 of original size + if ( _options.excludeLocalSymbols ) { + _vmSize = regions[2].address + (regions[2].size * 2 / 5) - regions[0].address; + } + else { + _vmSize = regions[2].address + (regions[2].size * 9 / 10) - regions[0].address; + } + + // sort SegmentMappingInfo for each image to be in the same order as original segments + for (auto& entry : result) { + std::vector& infos = entry.second; + std::sort(infos.begin(), infos.end(), [&](const SegmentMappingInfo& a, const SegmentMappingInfo& b) { + return a.srcSegmentIndex < b.srcSegmentIndex; + }); + } + + return result; +} + +uint64_t CacheBuilder::pathHash(const char* path) +{ + uint64_t sum = 0; + for (const char* s=path; *s != '\0'; ++s) + sum += sum*4 + *s; + return sum; +} + + +void CacheBuilder::findDylibAndSegment(const void* contentPtr, std::string& foundDylibName, std::string& foundSegName) +{ + foundDylibName = "???"; + foundSegName = "???"; + uint32_t cacheOffset = (uint32_t)((uint8_t*)contentPtr - (uint8_t*)_buffer); + _buffer->forEachImage(^(const mach_header* mh, const char* installName) { + dyld3::MachOParser parser(mh, true); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( (cacheOffset > fileOffset) && (cacheOffset < (fileOffset+vmSize)) ) { + foundDylibName = installName; + foundSegName = segName; + } + }); + }); + } + + +template +bool CacheBuilder::makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t offset, const dyld_cache_slide_info2* 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); + _diagnostics.error("rebase pointer does not point within cache. lastOffset=0x%04X, seg=%s, dylib=%s\n", + lastLocationOffset, segName.c_str(), dylibName.c_str()); + return false; + } + 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 CacheBuilder::addPageStarts(uint8_t* pageContent, const bool bitmap[], const dyld_cache_slide_info2* 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 ) { + _diagnostics.error("rebase overflow in page extras"); + return; + } + 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 CacheBuilder::writeSlideInfoV2() +{ + typedef typename P::uint_t pint_t; + typedef typename P::E E; + const uint32_t pageSize = 4096; + + // build one 1024/4096 bool bitmap per page (4KB/16KB) of DATA + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)_buffer + _buffer->header.mappingOffset); + uint8_t* const dataStart = (uint8_t*)_buffer + mappings[1].fileOffset; + uint8_t* const dataEnd = dataStart + mappings[1].size; + unsigned pageCount = (unsigned)(mappings[1].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) ) { + _diagnostics.error("DATA pointer for sliding, out of range\n"); + free(bitmap); + return; + } + long byteOffset = (long)((uint8_t*)p - dataStart); + if ( (byteOffset % 4) != 0 ) { + _diagnostics.error("pointer not 4-byte aligned in DATA offset 0x%08lX\n", byteOffset); + free(bitmap); + return; + } + long boolIndex = byteOffset / 4; + // work around by ignoring pointers to be slid that are NULL on disk + if ( *((pint_t*)p) == 0 ) { + std::string dylibName; + std::string segName; + findDylibAndSegment(p, dylibName, segName); + _diagnostics.warning("NULL pointer asked to be slid in %s at DATA region offset 0x%04lX of %s", segName.c_str(), byteOffset, dylibName.c_str()); + continue; + } + bitmap[boolIndex] = true; + } + + // fill in fixed info + assert(_slideInfoFileOffset != 0); + dyld_cache_slide_info2* info = (dyld_cache_slide_info2*)((uint8_t*)_buffer + _slideInfoFileOffset); + info->version = 2; + info->page_size = pageSize; + info->delta_mask = _archLayout->pointerDeltaMask; + info->value_add = (sizeof(pint_t) == 8) ? 0 : _archLayout->sharedMemoryStart; // only value_add for 32-bit archs + + // 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); + if ( _diagnostics.hasError() ) { + free(bitmap); + return; + } + pageContent += pageSize; + bitmapForPage += (sizeof(bool)*(pageSize/4)); + } + free((void*)bitmap); + + // fill in computed info + info->page_starts_offset = sizeof(dyld_cache_slide_info2); + info->page_starts_count = (unsigned)pageStarts.size(); + info->page_extras_offset = (unsigned)(sizeof(dyld_cache_slide_info2)+pageStarts.size()*sizeof(uint16_t)); + info->page_extras_count = (unsigned)pageExtras.size(); + uint16_t* pageStartsBuffer = (uint16_t*)((char*)info + info->page_starts_offset); + uint16_t* pageExtrasBuffer = (uint16_t*)((char*)info + info->page_extras_offset); + for (unsigned i=0; i < pageStarts.size(); ++i) + pageStartsBuffer[i] = pageStarts[i]; + for (unsigned i=0; i < pageExtras.size(); ++i) + pageExtrasBuffer[i] = pageExtras[i]; + // update header with final size + _buffer->header.slideInfoSize = align(info->page_extras_offset + pageExtras.size()*sizeof(uint16_t), _archLayout->sharedRegionAlignP2); + if ( _buffer->header.slideInfoSize > _slideInfoBufferSizeAllocated ) { + _diagnostics.error("kernel slide info overflow buffer"); + } + //warning("pageCount=%u, page_starts_count=%lu, page_extras_count=%lu", pageCount, pageStarts.size(), pageExtras.size()); +} + + +/* +void CacheBuilder::writeSlideInfoV1() +{ + // build one 128-byte bitmap per page (4096) of DATA + uint8_t* const dataStart = (uint8_t*)_buffer.get() + regions[1].fileOffset; + uint8_t* const dataEnd = dataStart + regions[1].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; + dyld_cache_slide_info* slideInfo = (dyld_cache_slide_info*)((uint8_t*)_buffer + _slideInfoFileOffset); + slideInfo->version = 1; + slideInfo->toc_offset = sizeof(dyld_cache_slide_info); + slideInfo->toc_count = toc_count; + slideInfo->entries_offset = (slideInfo->toc_offset+2*toc_count+127)&(-128); + slideInfo->entries_count = 0; + slideInfo->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->entries_count = entry_count; + ::free((void*)bitmap); + + _buffer.header->slideInfoSize = align(slideInfo->entries_offset + entry_count*entry_size, _archLayout->sharedRegionAlignP2); +} + +*/ + +void CacheBuilder::fipsSign() { + __block bool found = false; + _buffer->forEachImage(^(const mach_header* mh, const char* installName) { + __block void *hash_location = nullptr; + // Return if this is not corecrypto + if (strcmp(installName, "/usr/lib/system/libcorecrypto.dylib") != 0) { + return; + } + found = true; + auto parser = dyld3::MachOParser(mh, true); + parser.forEachLocalSymbol(_diagnostics, ^(const char *symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) { + if (strcmp(symbolName, "_fipspost_precalc_hmac") != 0) + return; + hash_location = (void *)(n_value - _archLayout->sharedMemoryStart + (uintptr_t)_buffer); + stop = true; + }); + + // Bail out if we did not find the symbol + if (hash_location == nullptr) { + _diagnostics.warning("Could not find _fipspost_precalc_hmac, skipping FIPS sealing"); + return; + } + + parser.forEachSection(^(const char *segName, const char *sectionName, uint32_t flags, const void *content, size_t size, bool illegalSectionSize, bool &stop) { + // FIXME: If we ever implement userspace __TEXT_EXEC this will need to be updated + if ( (strcmp(segName, "__TEXT" ) != 0) || (strcmp(sectionName, "__text") != 0) ) { + return; + } + + if (illegalSectionSize) { + _diagnostics.error("FIPS section %s/%s extends beyond the end of the segment", segName, sectionName); + return; + } + + //We have _fipspost_precalc_hmac and __TEXT,__text, seal it + unsigned char hmac_key = 0; + CCHmac(kCCHmacAlgSHA256, &hmac_key, 1, content, size, hash_location); + stop = true; + }); + }); + + if (!found) { + _diagnostics.warning("Could not find /usr/lib/system/libcorecrypto.dylib, skipping FIPS sealing"); + } +} + +void CacheBuilder::codeSign() +{ + uint8_t dscHashType; + uint8_t dscHashSize; + uint32_t dscDigestFormat; + bool agile = false; + + // select which codesigning hash + switch (_options.codeSigningDigestMode) { + case DyldSharedCache::Agile: + agile = true; + // Fall through to SHA1, because the main code directory remains SHA1 for compatibility. + case DyldSharedCache::SHA1only: + dscHashType = CS_HASHTYPE_SHA1; + dscHashSize = CS_HASH_SIZE_SHA1; + dscDigestFormat = kCCDigestSHA1; + break; + case DyldSharedCache::SHA256only: + dscHashType = CS_HASHTYPE_SHA256; + dscHashSize = CS_HASH_SIZE_SHA256; + dscDigestFormat = kCCDigestSHA256; + break; + default: + _diagnostics.error("codeSigningDigestMode has unknown, unexpected value %d, bailing out.", + _options.codeSigningDigestMode); + return; + } + + std::string cacheIdentifier = "com.apple.dyld.cache." + _options.archName; + if ( _options.dylibsRemovedDuringMastering ) { + if ( _options.optimizeStubs ) + cacheIdentifier = "com.apple.dyld.cache." + _options.archName + ".release"; + else + cacheIdentifier = "com.apple.dyld.cache." + _options.archName + ".development"; + } + // get pointers into shared cache buffer + size_t inBbufferSize = _currentFileSize; + const uint8_t* inBuffer = (uint8_t*)_buffer; + uint8_t* csBuffer = (uint8_t*)_buffer+inBbufferSize; + + // layout code signature contents + uint32_t blobCount = agile ? 4 : 3; + 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 idOffset = offsetof(CS_CodeDirectory, end_withExecSeg); + size_t hashOffset = idOffset+idSize + dscHashSize*xSlotCount; + size_t hash256Offset = idOffset+idSize + CS_HASH_SIZE_SHA256*xSlotCount; + size_t cdSize = hashOffset + (slotCount * dscHashSize); + size_t cd256Size = agile ? hash256Offset + (slotCount * CS_HASH_SIZE_SHA256) : 0; + size_t reqsSize = 12; + size_t cmsSize = sizeof(CS_Blob); + size_t cdOffset = sizeof(CS_SuperBlob) + blobCount*sizeof(CS_BlobIndex); + size_t cd256Offset = cdOffset + cdSize; + size_t reqsOffset = cd256Offset + cd256Size; // equals cdOffset + cdSize if not agile + size_t cmsOffset = reqsOffset + reqsSize; + size_t sbSize = cmsOffset + cmsSize; + size_t sigSize = align(sbSize, 14); // keep whole cache 16KB aligned + + if ( _currentFileSize+sigSize > _allocatedBufferSize ) { + _diagnostics.error("cache buffer too small to hold code signature (buffer size=%lldMB, signature size=%ldMB, free space=%lldMB)", + _allocatedBufferSize/1024/1024, sigSize/1024/1024, (_allocatedBufferSize-_currentFileSize)/1024/1024); + return; + } + + // 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(blobCount); + 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); + if ( agile ) { + sb->index[3].type = htonl(CSSLOT_ALTERNATE_CODEDIRECTORIES + 0); + sb->index[3].offset = htonl(cd256Offset); + } + + // 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; + + // 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(0x20400); // supports exec segment + 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 = 0; // not supported anymore + cd->teamOffset = 0; // no team ID + cd->spare3 = 0; // unused (must be zero) + cd->codeLimit64 = 0; // falls back to codeLimit + + // executable segment info + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)_buffer + _buffer->header.mappingOffset); + cd->execSegBase = htonll(mappings[0].fileOffset); // base of TEXT segment + cd->execSegLimit = htonll(mappings[0].size); // size of TEXT segment + cd->execSegFlags = 0; // not a main binary + + // initialize dynamic fields of Code Directory + strcpy((char*)cd + idOffset, cacheIdentifier.c_str()); + + // 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); + + CS_CodeDirectory* cd256; + uint8_t* hash256Slot; + uint8_t* reqsHash256Slot; + if ( agile ) { + // Note that the assumption here is that the size up to the hashes is the same as for + // sha1 code directory, and that they come last, after everything else. + + cd256 = (CS_CodeDirectory*)(((char*)sb)+cd256Offset); + cd256->magic = htonl(CSMAGIC_CODEDIRECTORY); + cd256->length = htonl(cd256Size); + cd256->version = htonl(0x20400); // supports exec segment + cd256->flags = htonl(kSecCodeSignatureAdhoc); + cd256->hashOffset = htonl(hash256Offset); + cd256->identOffset = htonl(idOffset); + cd256->nSpecialSlots = htonl(xSlotCount); + cd256->nCodeSlots = htonl(slotCount); + cd256->codeLimit = htonl(inBbufferSize); + cd256->hashSize = CS_HASH_SIZE_SHA256; + cd256->hashType = CS_HASHTYPE_SHA256; + cd256->platform = 0; // not platform binary + cd256->pageSize = __builtin_ctz(CS_PAGE_SIZE); // log2(CS_PAGE_SIZE); + cd256->spare2 = 0; // unused (must be zero) + cd256->scatterOffset = 0; // not supported anymore + cd256->teamOffset = 0; // no team ID + cd256->spare3 = 0; // unused (must be zero) + cd256->codeLimit64 = 0; // falls back to codeLimit + + // executable segment info + cd256->execSegBase = cd->execSegBase; + cd256->execSegLimit = cd->execSegLimit; + cd256->execSegFlags = cd->execSegFlags; + + // initialize dynamic fields of Code Directory + strcpy((char*)cd256 + idOffset, cacheIdentifier.c_str()); + + // add special slot hashes + hash256Slot = (uint8_t*)cd256 + hash256Offset; + reqsHash256Slot = &hash256Slot[-CSSLOT_REQUIREMENTS*CS_HASH_SIZE_SHA256]; + CCDigest(kCCDigestSHA256, (uint8_t*)reqs, sizeof(CS_RequirementsBlob), reqsHash256Slot); + } + else { + cd256 = NULL; + hash256Slot = NULL; + reqsHash256Slot = NULL; + } + + // 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)); + + // alter header of cache to record size and location of code signature + // do this *before* hashing each page + _buffer->header.codeSignatureOffset = inBbufferSize; + _buffer->header.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; + + if ( agile ) { + CCDigest(kCCDigestSHA256, code, CS_PAGE_SIZE, hash256Slot); + hash256Slot += CS_HASH_SIZE_SHA256; + } + code += CS_PAGE_SIZE; + } + + // hash of entire code directory (cdHash) uses same 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(_cdHashFirst, fullCdHash, 20); + if ( agile ) { + uint8_t fullCdHash256[CS_HASH_SIZE_SHA256]; + CCDigest(kCCDigestSHA256, (const uint8_t*)cd256, cd256Size, fullCdHash256); + // Note: cdHash is defined as first 20 bytes of hash, even for sha256 + memcpy(_cdHashSecond, fullCdHash256, 20); + } + else { + memset(_cdHashSecond, 0, 20); + } + + // increase file size to include newly append code signature + _currentFileSize += sigSize; +} + +const bool CacheBuilder::agileSignature() +{ + return _options.codeSigningDigestMode == DyldSharedCache::Agile; +} + +static const std::string cdHash(uint8_t hash[20]) +{ + char buff[48]; + for (int i = 0; i < 20; ++i) + sprintf(&buff[2*i], "%2.2x", hash[i]); + return buff; +} + +const std::string CacheBuilder::cdHashFirst() +{ + return cdHash(_cdHashFirst); +} + +const std::string CacheBuilder::cdHashSecond() +{ + return cdHash(_cdHashSecond); +} + +void CacheBuilder::addCachedDylibsImageGroup(dyld3::ImageProxyGroup* dylibGroup) +{ + const dyld3::launch_cache::binary_format::ImageGroup* groupBinary = dylibGroup->makeImageGroupBinary(_diagnostics, _s_neverStubEliminate); + if (!groupBinary) + return; + + dyld3::launch_cache::ImageGroup group(groupBinary); + size_t groupSize = group.size(); + + if ( _currentFileSize+groupSize > _allocatedBufferSize ) { + _diagnostics.error("cache buffer too small to hold group[0] info (buffer size=%lldMB, group size=%ldMB, free space=%lldMB)", + _allocatedBufferSize/1024/1024, groupSize/1024/1024, (_allocatedBufferSize-_currentFileSize)/1024/1024); + return; + } + + // append ImageGroup data to read-only region of cache + uint8_t* loc = (uint8_t*)_buffer + _currentFileSize; + memcpy(loc, groupBinary, groupSize); + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)_buffer + _buffer->header.mappingOffset); + _buffer->header.dylibsImageGroupAddr = mappings[2].address + (_currentFileSize - mappings[2].fileOffset); + _buffer->header.dylibsImageGroupSize = (uint32_t)groupSize; + _currentFileSize += groupSize; + free((void*)groupBinary); +} + + +void CacheBuilder::addCachedOtherDylibsImageGroup(dyld3::ImageProxyGroup* otherGroup) +{ + const dyld3::launch_cache::binary_format::ImageGroup* groupBinary = otherGroup->makeImageGroupBinary(_diagnostics); + if (!groupBinary) + return; + + dyld3::launch_cache::ImageGroup group(groupBinary); + size_t groupSize = group.size(); + + if ( _currentFileSize+groupSize > _allocatedBufferSize ) { + _diagnostics.error("cache buffer too small to hold group[1] info (buffer size=%lldMB, group size=%ldMB, free space=%lldMB)", + _allocatedBufferSize/1024/1024, groupSize/1024/1024, (_allocatedBufferSize-_currentFileSize)/1024/1024); + return; + } + + // append ImageGroup data to read-only region of cache + uint8_t* loc = (uint8_t*)_buffer + _currentFileSize; + memcpy(loc, groupBinary, groupSize); + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)_buffer + _buffer->header.mappingOffset); + _buffer->header.otherImageGroupAddr = mappings[2].address + (_currentFileSize - mappings[2].fileOffset); + _buffer->header.otherImageGroupSize = (uint32_t)groupSize; + _currentFileSize += groupSize; + free((void*)groupBinary); +} + +void CacheBuilder::addClosures(const std::map& closures) +{ + // preflight space needed + size_t closuresSpace = 0; + for (const auto& entry : closures) { + dyld3::launch_cache::Closure closure(entry.second); + closuresSpace += closure.size(); + } + size_t freeSpace = _allocatedBufferSize - _currentFileSize; + if ( closuresSpace > freeSpace ) { + _diagnostics.error("cache buffer too small to hold all closures (buffer size=%lldMB, closures size=%ldMB, free space=%ldMB)", + _allocatedBufferSize/1024/1024, closuresSpace/1024/1024, freeSpace/1024/1024); + return; + } + + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)_buffer + _buffer->header.mappingOffset); + _buffer->header.progClosuresAddr = mappings[2].address + (_currentFileSize - mappings[2].fileOffset); + uint8_t* closuresBase = (uint8_t*)_buffer + _currentFileSize; + std::vector closureEntrys; + uint32_t currentClosureOffset = 0; + for (const auto& entry : closures) { + const dyld3::launch_cache::binary_format::Closure* closBuf = entry.second; + closureEntrys.push_back(DylibIndexTrie::Entry(entry.first, DylibIndex(currentClosureOffset))); + dyld3::launch_cache::Closure closure(closBuf); + size_t size = closure.size(); + assert((size % 4) == 0); + memcpy(closuresBase+currentClosureOffset, closBuf, size); + currentClosureOffset += size; + freeSpace -= size; + free((void*)closBuf); + } + _buffer->header.progClosuresSize = currentClosureOffset; + _currentFileSize += currentClosureOffset; + freeSpace = _allocatedBufferSize - _currentFileSize; + + // build trie of indexes into closures list + DylibIndexTrie closureTrie(closureEntrys); + std::vector trieBytes; + closureTrie.emit(trieBytes); + while ( (trieBytes.size() % 8) != 0 ) + trieBytes.push_back(0); + if ( trieBytes.size() > freeSpace ) { + _diagnostics.error("cache buffer too small to hold all closures trie (buffer size=%lldMB, trie size=%ldMB, free space=%ldMB)", + _allocatedBufferSize/1024/1024, trieBytes.size()/1024/1024, freeSpace/1024/1024); + return; + } + memcpy((uint8_t*)_buffer + _currentFileSize, &trieBytes[0], trieBytes.size()); + _buffer->header.progClosuresTrieAddr = mappings[2].address + (_currentFileSize - mappings[2].fileOffset); + _buffer->header.progClosuresTrieSize = trieBytes.size(); + _currentFileSize += trieBytes.size(); +} + + diff --git a/dyld3/shared-cache/CacheBuilder.h b/dyld3/shared-cache/CacheBuilder.h new file mode 100644 index 0000000..4a6227c --- /dev/null +++ b/dyld3/shared-cache/CacheBuilder.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2017 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 CacheBuilder_h +#define CacheBuilder_h + +#include +#include +#include +#include + +#include "DyldSharedCache.h" +#include "Diagnostics.h" +#include "ImageProxy.h" + + +namespace dyld3 { + namespace launch_cache { + namespace binary_format { + struct ImageGroup; + struct Closure; + } + } +} + + +struct CacheBuilder { + + CacheBuilder(const DyldSharedCache::CreateOptions& options); + + bool build(const std::vector& dylibsToCache, + const std::vector& otherOsDylibs, + const std::vector& osExecutables); + void deleteBuffer(); + const DyldSharedCache* buffer() { return _buffer; } + size_t bufferSize() { return (size_t)_allocatedBufferSize; } + std::string errorMessage(); + const std::set warnings(); + const bool agileSignature(); + const std::string cdHashFirst(); + const std::string cdHashSecond(); + + struct SegmentMappingInfo { + const void* srcSegment; + const char* segName; + uint64_t dstCacheAddress; + uint32_t dstCacheOffset; + uint32_t dstCacheSegmentSize; + uint32_t copySegmentSize; + uint32_t srcSegmentIndex; + }; + +private: + + typedef std::unordered_map> SegmentMapping; + + struct ArchLayout + { + uint64_t sharedMemoryStart; + uint64_t sharedMemorySize; + uint64_t sharedRegionPadding; + uint64_t pointerDeltaMask; + const char* archName; + uint32_t branchPoolTextSize; + uint32_t branchPoolLinkEditSize; + uint32_t branchReach; + uint8_t sharedRegionAlignP2; + bool sharedRegionsAreDiscontiguous; + bool is64; + }; + + static const ArchLayout _s_archLayout[]; + static const char* const _s_neverStubEliminate[]; + + std::vector makeSortedDylibs(const std::vector& dylibs, const std::unordered_map sortOrder); + + SegmentMapping assignSegmentAddresses(const std::vector& dylibs, struct dyld_cache_mapping_info regions[3]); + + bool cacheOverflow(const dyld_cache_mapping_info regions[3]); + void adjustImageForNewSegmentLocations(const std::vector& segNewStartAddresses, + const std::vector& segCacheFileOffsets, + const std::vector& segCacheSizes, std::vector& pointersForASLR); + + void fipsSign(); + void codeSign(); + uint64_t pathHash(const char* path); + void writeCacheHeader(const struct dyld_cache_mapping_info regions[3], const std::vector& dylibs, const SegmentMapping&); + void copyRawSegments(const std::vector& dylibs, const SegmentMapping& mapping); + void adjustAllImagesForNewSegmentLocations(const std::vector& dylibs, const SegmentMapping& mapping); + void bindAllImagesInCacheFile(const dyld_cache_mapping_info regions[3]); + void writeSlideInfoV1(); + void recomputeCacheUUID(void); + void findDylibAndSegment(const void* contentPtr, std::string& dylibName, std::string& segName); + + void addCachedDylibsImageGroup(dyld3::ImageProxyGroup*); + void addCachedOtherDylibsImageGroup(dyld3::ImageProxyGroup*); + void addClosures(const std::map& closures); + + template void writeSlideInfoV2(); + template bool makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t newOffset, const struct dyld_cache_slide_info2* info); + template void addPageStarts(uint8_t* pageContent, const bool bitmap[], const struct dyld_cache_slide_info2* info, + std::vector& pageStarts, std::vector& pageExtras); + + + const DyldSharedCache::CreateOptions& _options; + DyldSharedCache* _buffer; + Diagnostics _diagnostics; + const ArchLayout* _archLayout; + uint32_t _aliasCount; + uint64_t _slideInfoFileOffset; + uint64_t _slideInfoBufferSizeAllocated; + uint64_t _allocatedBufferSize; + uint64_t _currentFileSize; + uint64_t _vmSize; + std::unordered_map _dataDirtySegsOrder; + std::vector _pointersForASLR; + dyld3::ImageProxyGroup::PatchTable _patchTable; + std::vector _branchPoolStarts; + uint64_t _branchPoolsLinkEditStartAddr; + uint8_t _cdHashFirst[20]; + uint8_t _cdHashSecond[20]; +}; + + +// implemented in AdjustDylibSegemnts.cpp +void adjustDylibSegments(DyldSharedCache* cache, bool is64, mach_header* mhInCache, const std::vector& mappingInfo, std::vector& pointersForASLR, Diagnostics& diag); + +// implemented in OptimizerLinkedit.cpp +uint64_t optimizeLinkedit(DyldSharedCache* cache, bool is64, bool dontMapLocalSymbols, bool addAcceleratorTables, const std::vector& branchPoolOffsets, Diagnostics& diag, dyld_cache_local_symbols_info** localsInfo); + +// implemented in OptimizerBranches.cpp +void bypassStubs(DyldSharedCache* cache, const std::vector& branchPoolStartAddrs, const char* const alwaysUsesStubsTo[], Diagnostics& diag); + +// implemented in OptimizerObjC.cpp +void optimizeObjC(DyldSharedCache* cache, bool is64, bool customerCache, std::vector& pointersForASLR, Diagnostics& diag); + + + +inline uint64_t align(uint64_t addr, uint8_t p2) +{ + uint64_t mask = (1 << p2); + return (addr + mask - 1) & (-mask); +} + + + +#endif /* CacheBuilder_h */ diff --git a/dyld3/shared-cache/DyldSharedCache.cpp b/dyld3/shared-cache/DyldSharedCache.cpp new file mode 100644 index 0000000..4fbdd23 --- /dev/null +++ b/dyld3/shared-cache/DyldSharedCache.cpp @@ -0,0 +1,328 @@ +/* -*- 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 + +#if !DYLD_IN_PROCESS +#include +#include +#include +#include +#include +#endif + +#define NO_ULEB +#include "MachOParser.h" +#include "CacheBuilder.h" +#include "DyldSharedCache.h" +#include "LaunchCache.h" +#include "Trie.hpp" +#include "StringUtils.h" + + + +#if !DYLD_IN_PROCESS +DyldSharedCache::CreateResults DyldSharedCache::create(const CreateOptions& options, + const std::vector& dylibsToCache, + const std::vector& otherOsDylibs, + const std::vector& osExecutables) +{ + CreateResults results; + CacheBuilder cache(options); + + results.overflowed = cache.build(dylibsToCache, otherOsDylibs, osExecutables); + + results.agileSignature = cache.agileSignature(); + results.cdHashFirst = cache.cdHashFirst(); + results.cdHashSecond = cache.cdHashSecond(); + results.warnings = cache.warnings(); + if ( cache.errorMessage().empty() ) { + results.cacheContent = cache.buffer(); + results.cacheLength = cache.bufferSize(); + } + else { + cache.deleteBuffer(); + results.cacheContent = nullptr; + results.cacheLength = 0; + results.errorMessage = cache.errorMessage(); + } + return results; +} + +bool DyldSharedCache::verifySelfContained(std::vector& dylibsToCache, MappedMachO (^loader)(const std::string& runtimePath), std::vector>>& rejected) +{ + + // build map of dylibs + __block std::map> badDylibs; + __block std::set knownDylibs; + for (const DyldSharedCache::MappedMachO& dylib : dylibsToCache) { + std::set reasons; + dyld3::MachOParser parser(dylib.mh); + if (parser.canBePlacedInDyldCache(dylib.runtimePath, reasons)) { + knownDylibs.insert(dylib.runtimePath); + knownDylibs.insert(parser.installName()); + } else { + badDylibs[dylib.runtimePath] = reasons; + } + } + + // check all dependencies to assure every dylib in cache only depends on other dylibs in cache + __block bool doAgain = true; + while ( doAgain ) { + __block std::vector foundMappings; + doAgain = false; + // scan dylib list making sure all dependents are in dylib list + for (const DyldSharedCache::MappedMachO& dylib : dylibsToCache) { + if ( badDylibs.count(dylib.runtimePath) != 0 ) + continue; + dyld3::MachOParser parser(dylib.mh); + parser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if ( knownDylibs.count(loadPath) == 0 ) { + doAgain = true; + MappedMachO foundMapping; + if ( badDylibs.count(loadPath) == 0 ) + foundMapping = loader(loadPath); + if ( foundMapping.length == 0 ) { + std::string reason = std::string("Could not find dependency '") + loadPath +"'"; + auto i = badDylibs.find(dylib.runtimePath); + if (i == badDylibs.end()) { + std::set reasons; + reasons.insert(reason); + badDylibs[dylib.runtimePath] = reasons; + } else { + i->second.insert(reason); + } + knownDylibs.erase(dylib.runtimePath); + dyld3::MachOParser parserBad(dylib.mh); + knownDylibs.erase(parserBad.installName()); + } + else { + dyld3::MachOParser foundParser(foundMapping.mh); + std::set reasons; + if (foundParser.canBePlacedInDyldCache(foundParser.installName(), reasons)) { + foundMappings.push_back(foundMapping); + knownDylibs.insert(foundMapping.runtimePath); + knownDylibs.insert(foundParser.installName()); + } else { + auto i = badDylibs.find(dylib.runtimePath); + if (i == badDylibs.end()) { + badDylibs[dylib.runtimePath] = reasons; + } else { + i->second.insert(reasons.begin(), reasons.end()); + } + } + } + } + }); + } + dylibsToCache.insert(dylibsToCache.end(), foundMappings.begin(), foundMappings.end()); + // remove bad dylibs + const auto badDylibsCopy = badDylibs; + dylibsToCache.erase(std::remove_if(dylibsToCache.begin(), dylibsToCache.end(), [&](const DyldSharedCache::MappedMachO& dylib) { + auto i = badDylibsCopy.find(dylib.runtimePath); + if ( i != badDylibsCopy.end()) { + rejected.push_back(std::make_pair(dylib, i->second)); + return true; + } + else { + return false; + } + }), dylibsToCache.end()); + } + + return badDylibs.empty(); +} +#endif + +void DyldSharedCache::forEachRegion(void (^handler)(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions)) const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + const dyld_cache_mapping_info* mappingsEnd = &mappings[header.mappingCount]; + for (const dyld_cache_mapping_info* m=mappings; m < mappingsEnd; ++m) { + handler((char*)this + m->fileOffset, m->address, m->size, m->initProt); + } +} + +void DyldSharedCache::forEachImage(void (^handler)(const mach_header* mh, const char* installName)) const +{ + const dyld_cache_image_info* dylibs = (dyld_cache_image_info*)((char*)this + header.imagesOffset); + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + if ( mappings[0].fileOffset != 0 ) + return; + uint64_t firstImageOffset = 0; + uint64_t firstRegionAddress = mappings[0].address; + for (uint32_t i=0; i < header.imagesCount; ++i) { + const char* dylibPath = (char*)this + dylibs[i].pathFileOffset; + uint64_t offset = dylibs[i].address - firstRegionAddress; + if ( firstImageOffset == 0 ) + firstImageOffset = offset; + // skip over aliases + if ( dylibs[i].pathFileOffset < firstImageOffset) + continue; + const mach_header* mh = (mach_header*)((char*)this + offset); + handler(mh, dylibPath); + } +} + +void DyldSharedCache::forEachImageEntry(void (^handler)(const char* path, uint64_t mTime, uint64_t inode)) const +{ + const dyld_cache_image_info* dylibs = (dyld_cache_image_info*)((char*)this + header.imagesOffset); + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + if ( mappings[0].fileOffset != 0 ) + return; + uint64_t firstImageOffset = 0; + uint64_t firstRegionAddress = mappings[0].address; + for (uint32_t i=0; i < header.imagesCount; ++i) { + const char* dylibPath = (char*)this + dylibs[i].pathFileOffset; + uint64_t offset = dylibs[i].address - firstRegionAddress; + if ( firstImageOffset == 0 ) + firstImageOffset = offset; + // skip over aliases + if ( dylibs[i].pathFileOffset < firstImageOffset) + continue; + handler(dylibPath, dylibs[i].modTime, dylibs[i].inode); + } +} + +void DyldSharedCache::forEachImageTextSegment(void (^handler)(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const uuid_t dylibUUID, const char* installName)) const +{ + // check for old cache without imagesText array + if ( header.mappingOffset < 123 ) + return; + + // walk imageText table and call callback for each entry + const dyld_cache_image_text_info* imagesText = (dyld_cache_image_text_info*)((char*)this + header.imagesTextOffset); + const dyld_cache_image_text_info* imagesTextEnd = &imagesText[header.imagesTextCount]; + for (const dyld_cache_image_text_info* p=imagesText; p < imagesTextEnd; ++p) { + handler(p->loadAddress, p->textSegmentSize, p->uuid, (char*)this + p->pathOffset); + } +} + + +std::string DyldSharedCache::archName() const +{ + const char* archSubString = ((char*)this) + 8; + while (*archSubString == ' ') + ++archSubString; + return archSubString; +} + + +uint32_t DyldSharedCache::platform() const +{ + return header.platform; +} + +#if !DYLD_IN_PROCESS +std::string DyldSharedCache::mapFile() const +{ + __block std::string result; + __block std::vector regionStartAddresses; + __block std::vector regionSizes; + __block std::vector regionFileOffsets; + + result.reserve(256*1024); + forEachRegion(^(const 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*)this); + char lineBuffer[256]; + 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 ) + sprintf(lineBuffer, "mapping %s %4lluMB 0x%0llX -> 0x%0llX\n", prot, size/(1024*1024), vmAddr, vmAddr+size); + else + sprintf(lineBuffer, "mapping %s %4lluKB 0x%0llX -> 0x%0llX\n", prot, size/1024, vmAddr, vmAddr+size); + result += lineBuffer; + }); + + // TODO: add linkedit breakdown + result += "\n\n"; + + forEachImage(^(const mach_header* mh, const char* installName) { + result += std::string(installName) + "\n"; + dyld3::MachOParser parser(mh); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + char lineBuffer[256]; + sprintf(lineBuffer, "\t%16s 0x%08llX -> 0x%08llX\n", segName, vmAddr, vmAddr+vmSize); + result += lineBuffer; + }); + result += "\n"; + }); + + return result; +} +#endif + + +uint64_t DyldSharedCache::unslidLoadAddress() const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + return mappings[0].address; +} + +void DyldSharedCache::getUUID(uuid_t uuid) const +{ + memcpy(uuid, header.uuid, sizeof(uuid_t)); +} + +uint64_t DyldSharedCache::mappedSize() const +{ + __block uint64_t startAddr = 0; + __block uint64_t endAddr = 0; + forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + if ( startAddr == 0 ) + startAddr = vmAddr; + uint64_t end = vmAddr+size; + if ( end > endAddr ) + endAddr = end; + }); + return (endAddr - startAddr); +} + + + + + + + + + + diff --git a/dyld3/shared-cache/DyldSharedCache.h b/dyld3/shared-cache/DyldSharedCache.h new file mode 100644 index 0000000..2d84065 --- /dev/null +++ b/dyld3/shared-cache/DyldSharedCache.h @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2017 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 DyldSharedCache_h +#define DyldSharedCache_h + +#include +#include +#include +#include + +#include "dyld_cache_format.h" +#include "Diagnostics.h" +#include "MachOParser.h" + + +namespace dyld3 { + namespace launch_cache { + namespace binary_format { + struct Closure; + struct ImageGroup; + struct Image; + } + } +} + + +class VIS_HIDDEN DyldSharedCache +{ +public: + + enum CodeSigningDigestMode + { + SHA256only = 0, + SHA1only = 1, + Agile = 2 + }; + + struct CreateOptions + { + std::string archName; + dyld3::Platform platform; + bool excludeLocalSymbols; + bool optimizeStubs; + bool optimizeObjC; + CodeSigningDigestMode codeSigningDigestMode; + bool agileSignatureChooseSHA256CdHash; + bool dylibsRemovedDuringMastering; + bool inodesAreSameAsRuntime; + bool cacheSupportsASLR; + bool forSimulator; + bool verbose; + bool evictLeafDylibsOnOverflow; + std::unordered_map dylibOrdering; + std::unordered_map dirtyDataSegmentOrdering; + std::vector pathPrefixes; + std::string loggingPrefix; + }; + + struct MappedMachO + { + MappedMachO() + : mh(nullptr), length(0), isSetUID(false), protectedBySIP(false), sliceFileOffset(0), modTime(0), inode(0) { } + MappedMachO(const std::string& path, const mach_header* p, size_t l, bool isu, bool sip, uint64_t o, uint64_t m, uint64_t i) + : runtimePath(path), mh(p), length(l), isSetUID(isu), protectedBySIP(sip), sliceFileOffset(o), modTime(m), inode(i) { } + + std::string runtimePath; + const mach_header* mh; + size_t length; + uint64_t isSetUID : 1, + protectedBySIP : 1, + sliceFileOffset : 62; + uint64_t modTime; // only recorded if inodesAreSameAsRuntime + uint64_t inode; // only recorded if inodesAreSameAsRuntime + }; + + struct CreateResults + { + const DyldSharedCache* cacheContent = nullptr; // caller needs to vm_deallocate() when done + size_t cacheLength = 0; + std::string errorMessage; + std::set warnings; + bool agileSignature = false; + std::string cdHashFirst; + std::string cdHashSecond; + bool overflowed = false; + }; + + + // This function verifies the set of dylibs that will go into the cache are self contained. That the depend on no dylibs + // outset the set. It will call back the loader function to try to find any mising dylibs. + static bool verifySelfContained(std::vector& dylibsToCache, MappedMachO (^loader)(const std::string& runtimePath), std::vector>>& excluded); + + + // + // This function is single threaded and creates a shared cache. The cache file is created in-memory. + // + // Inputs: + // options: various per-platform flags + // dylibsToCache: a list of dylibs to include in the cache + // otherOsDylibs: a list of other OS dylibs and bundle which should have load info added to the cache + // osExecutables: a list of main executables which should have closures created in the cache + // + // Returns: + // On success: + // cacheContent: start of the allocated cache buffer which must be vm_deallocated after the caller writes out the buffer. + // cacheLength: size of the allocated cache buffer + // cdHash: hash of the code directory of the code blob of the created cache + // warnings: all warning messsages generated during the creation of the cache + // + // On failure: + // cacheContent: nullptr + // errorMessage: the string describing why the cache could not be created + // warnings: all warning messsages generated before the failure + // + static CreateResults create(const CreateOptions& options, + const std::vector& dylibsToCache, + const std::vector& otherOsDylibs, + const std::vector& osExecutables); + + + // + // Returns a text "map" file as a big string + // + std::string mapFile() const; + + + // + // Returns the architecture name of the shared cache, e.g. "arm64" + // + std::string archName() const; + + + // + // Returns the platform the cache is for + // + uint32_t platform() const; + + + // + // Iterates over each dylib in the cache + // + void forEachImage(void (^handler)(const mach_header* mh, const char* installName)) const; + + + // + // Iterates over each dylib in the cache + // + void forEachImageEntry(void (^handler)(const char* path, uint64_t mTime, uint64_t inode)) const; + + + // + // Iterates over each dylib in the cache + // + void forEachImageTextSegment(void (^handler)(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const uuid_t dylibUUID, const char* installName)) const; + + + // + // Iterates over each of the three regions in the cache + // + void forEachRegion(void (^handler)(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions)) const; + + + // + // returns address the cache would load at if unslid + // + uint64_t unslidLoadAddress() const; + + + // + // returns UUID of cache + // + void getUUID(uuid_t uuid) const; + + + // + // returns the vm size required to map cache + // + uint64_t mappedSize() const; + + + dyld_cache_header header; +}; + + + + + + + + +#endif /* DyldSharedCache_h */ diff --git a/dyld3/shared-cache/FileAbstraction.hpp b/dyld3/shared-cache/FileAbstraction.hpp new file mode 100644 index 0000000..1d73d74 --- /dev/null +++ b/dyld3/shared-cache/FileAbstraction.hpp @@ -0,0 +1,163 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2005 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@ + */ +#ifndef __FILE_ABSTRACTION__ +#define __FILE_ABSTRACTION__ + + +#include +#include +#include + +#ifdef __OPTIMIZE__ +#define INLINE __attribute__((always_inline)) +#else +#define INLINE +#endif + +// +// This abstraction layer is for use with file formats that have 64-bit/32-bit and Big-Endian/Little-Endian variants +// +// For example: to make a utility that handles 32-bit little enidan files use: Pointer32 +// +// +// get16() read a 16-bit number from an E endian struct +// set16() write a 16-bit number to an E endian struct +// get32() read a 32-bit number from an E endian struct +// set32() write a 32-bit number to an E endian struct +// get64() read a 64-bit number from an E endian struct +// set64() write a 64-bit number to an E endian struct +// +// getBits() read a bit field from an E endian struct (bitCount=number of bits in field, firstBit=bit index of field) +// setBits() write a bit field to an E endian struct (bitCount=number of bits in field, firstBit=bit index of field) +// +// getBitsRaw() read a bit field from a struct with native endianness +// setBitsRaw() write a bit field from a struct with native endianness +// + +class BigEndian +{ +public: + static uint16_t get16(const uint16_t& from) INLINE { return OSReadBigInt16(&from, 0); } + static void set16(uint16_t& into, uint16_t value) INLINE { OSWriteBigInt16(&into, 0, value); } + + static uint32_t get32(const uint32_t& from) INLINE { return OSReadBigInt32(&from, 0); } + static void set32(uint32_t& into, uint32_t value) INLINE { OSWriteBigInt32(&into, 0, value); } + + static int32_t get32(const int32_t& from) INLINE { return OSReadBigInt32(&from, 0); } + static void set32(int32_t& into, int32_t value) INLINE { OSWriteBigInt32(&into, 0, value); } + + static uint64_t get64(const uint64_t& from) INLINE { return OSReadBigInt64(&from, 0); } + static void set64(uint64_t& into, uint64_t value) INLINE { OSWriteBigInt64(&into, 0, value); } + + static uint32_t getBits(const uint32_t& from, + uint8_t firstBit, uint8_t bitCount) INLINE { return getBitsRaw(get32(from), firstBit, bitCount); } + static void setBits(uint32_t& into, uint32_t value, + uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = get32(into); setBitsRaw(temp, value, firstBit, bitCount); set32(into, temp); } + + static uint32_t getBitsRaw(const uint32_t& from, + uint8_t firstBit, uint8_t bitCount) INLINE { return ((from >> (32-firstBit-bitCount)) & ((1<> firstBit) & ((1< +class Pointer32 +{ +public: + typedef uint32_t uint_t; + typedef _E E; + + static uint64_t getP(const uint_t& from) INLINE { return _E::get32(from); } + static void setP(uint_t& into, uint64_t value) INLINE { _E::set32(into, (uint32_t)value); } + + // Round to a P-size boundary + template + static T round_up(T value) { return (value+3) & ~(T)3; } + template + static T round_down(T value) { return value & ~(T)3; } +}; + + +template +class Pointer64 +{ +public: + typedef uint64_t uint_t; + typedef _E E; + + static uint64_t getP(const uint_t& from) INLINE { return _E::get64(from); } + static void setP(uint_t& into, uint64_t value) INLINE { _E::set64(into, value); } + + // Round to a P-size boundary + template + static T round_up(T value) { return (value+7) & ~(T)7; } + template + static T round_down(T value) { return value & ~(T)7; } +}; + + + + + +#endif // __FILE_ABSTRACTION__ + + diff --git a/dyld3/shared-cache/FileUtils.cpp b/dyld3/shared-cache/FileUtils.cpp new file mode 100644 index 0000000..19f02a4 --- /dev/null +++ b/dyld3/shared-cache/FileUtils.cpp @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2017 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 "FileUtils.h" +#include "Diagnostics.h" + +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101200 +extern "C" int rootless_check_trusted_fd(int fd) __attribute__((weak_import)); +#endif + + +void iterateDirectoryTree(const std::string& pathPrefix, const std::string& path, bool (^dirFilter)(const std::string& path), void (^fileCallback)(const std::string& path, const struct stat&), bool processFiles) +{ + std::string fullDirPath = pathPrefix + path; + DIR* dir = ::opendir(fullDirPath.c_str()); + if ( dir == nullptr ) { + //fprintf(stderr, "can't read 'dir '%s', errno=%d\n", inputPath.c_str(), errno); + return; + } + while (dirent* entry = readdir(dir)) { + struct stat statBuf; + std::string dirAndFile = path + "/" + entry->d_name; + std::string fullDirAndFile = pathPrefix + dirAndFile; + switch ( entry->d_type ) { + case DT_REG: + if ( processFiles ) { + if ( ::lstat(fullDirAndFile.c_str(), &statBuf) == -1 ) + break; + if ( ! S_ISREG(statBuf.st_mode) ) + break; + fileCallback(dirAndFile, statBuf); + } + break; + case DT_DIR: + if ( strcmp(entry->d_name, ".") == 0 ) + break; + if ( strcmp(entry->d_name, "..") == 0 ) + break; + if ( dirFilter(dirAndFile) ) + break; + iterateDirectoryTree(pathPrefix, dirAndFile, dirFilter, fileCallback); + break; + case DT_LNK: + // don't follow symlinks, dylib will be found through absolute path + break; + } + } + ::closedir(dir); +} + + +bool safeSave(const void* buffer, size_t bufferLen, const std::string& path) +{ + std::string pathTemplate = path + "-XXXXXX"; + size_t templateLen = strlen(pathTemplate.c_str())+2; + char pathTemplateSpace[templateLen]; + strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen); + int fd = mkstemp(pathTemplateSpace); + if ( fd != -1 ) { + ssize_t writtenSize = pwrite(fd, buffer, bufferLen, 0); + if ( writtenSize == bufferLen ) { + ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--" + if ( ::rename(pathTemplateSpace, path.c_str()) == 0) { + ::close(fd); + return true; // success + } + } + ::close(fd); + ::unlink(pathTemplateSpace); + } + return false; // failure +} + +const void* mapFileReadOnly(const std::string& path, size_t& mappedSize) +{ + struct stat statBuf; + if ( ::stat(path.c_str(), &statBuf) != 0 ) + return nullptr; + + int fd = ::open(path.c_str(), O_RDONLY); + if ( fd < 0 ) + return nullptr; + + const void *p = ::mmap(NULL, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + ::close(fd); + if ( p != MAP_FAILED ) { + mappedSize = (size_t)statBuf.st_size; + return p; + } + + return nullptr; +} + +static bool sipIsEnabled() +{ + static bool rootlessEnabled; + static dispatch_once_t onceToken; + // Check to make sure file system protections are on at all + dispatch_once(&onceToken, ^{ + rootlessEnabled = (csr_check(CSR_ALLOW_UNRESTRICTED_FS) != 0); + }); + return rootlessEnabled; +} + +bool isProtectedBySIP(const std::string& path) +{ + if ( !sipIsEnabled() ) + return false; + + return (rootless_check_trusted(path.c_str()) == 0); +} + +bool isProtectedBySIP(int fd) +{ + if ( !sipIsEnabled() ) + return false; + +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 + return (rootless_check_trusted_fd(fd) == 0); +#else + // fallback to using rootless_check_trusted + char realPath[MAXPATHLEN]; + if ( fcntl(fd, F_GETPATH, realPath) == 0 ) + return (rootless_check_trusted(realPath) == 0); + return false; +#endif +} + +bool fileExists(const std::string& path) +{ + struct stat statBuf; + return ( ::stat(path.c_str(), &statBuf) == 0 ); +} + +// 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. +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(); + } + + return order; +} + + +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 basePath(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 realPath(const std::string& path) +{ + char resolvedPath[PATH_MAX]; + if (realpath(dirPath(path).c_str(), &resolvedPath[0]) != nullptr) { + return std::string(resolvedPath) + "/" + basePath(path); + } else { + return ""; + } +} + +std::string realFilePath(const std::string& path) +{ + char resolvedPath[PATH_MAX]; + if ( realpath(path.c_str(), resolvedPath) != nullptr ) + return std::string(resolvedPath); + else + return ""; +} + + +std::string normalize_absolute_file_path(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; +} + + +#if BUILDING_CACHE_BUILDER + +FileCache fileCache; + +FileCache::FileCache(void) +{ + cache_queue = dispatch_queue_create("com.apple.dyld.cache.cache", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0)); +} + +void FileCache::preflightCache(Diagnostics& diags, const std::unordered_set& paths) +{ + for (auto& path : paths) { + preflightCache(diags, path); + } +} + +void FileCache::preflightCache(Diagnostics& diags, const std::string& path) +{ + dispatch_async(cache_queue, ^{ + std::string normalizedPath = normalize_absolute_file_path(path); + if (entries.count(normalizedPath) == 0) { + entries[normalizedPath] = fill(diags, normalizedPath); + } + }); +} + +std::pair FileCache::cacheLoad(Diagnostics& diags, const std::string path) +{ + __block bool found = false; + __block std::pair retval; + std::string normalizedPath = normalize_absolute_file_path(path); + dispatch_sync(cache_queue, ^{ + auto entry = entries.find(normalizedPath); + if (entry != entries.end()) { + retval = entry->second; + found = true; + } + }); + + if (!found) { + auto info = fill(diags, normalizedPath); + dispatch_sync(cache_queue, ^{ + auto entry = entries.find(normalizedPath); + if (entry != entries.end()) { + retval = entry->second; + } else { + retval = entries[normalizedPath] = info; + retval = info; + } + }); + } + + return retval; +} + +//FIXME error handling +std::pair FileCache::fill(Diagnostics& diags, const std::string& path) +{ + void* buffer_ptr = nullptr; + struct stat stat_buf; + struct statfs statfs_buf; + bool localcopy = true; + + int fd = ::open(path.c_str(), O_RDONLY, 0); + if (fd == -1) { + diags.verbose("can't open file '%s', errno=%d\n", path.c_str(), errno); + return std::make_pair((uint8_t*)(-1), stat_buf); + } + + if (fstat(fd, &stat_buf) == -1) { + diags.verbose("can't stat open file '%s', errno=%d\n", path.c_str(), errno); + ::close(fd); + return std::make_pair((uint8_t*)(-1), stat_buf); + } + + if (stat_buf.st_size < 4096) { + diags.verbose("file too small '%s'\n", path.c_str()); + ::close(fd); + return std::make_pair((uint8_t*)(-1), stat_buf); + } + + if(fstatfs(fd, &statfs_buf) == 0) { + std::string fsName = statfs_buf.f_fstypename; + if (fsName == "hfs" || fsName == "apfs") { + localcopy = false; + } + } + + if (!localcopy) { + buffer_ptr = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buffer_ptr == MAP_FAILED) { + diags.verbose("mmap() for file at %s failed, errno=%d\n", path.c_str(), errno); + ::close(fd); + return std::make_pair((uint8_t*)(-1), stat_buf); + } + } else { + buffer_ptr = malloc((size_t)stat_buf.st_size); + ssize_t readBytes = pread(fd, buffer_ptr, (size_t)stat_buf.st_size, 0); + if (readBytes == -1) { + diags.verbose("Network read for file at %s failed, errno=%d\n", path.c_str(), errno); + ::close(fd); + return std::make_pair((uint8_t*)(-1), stat_buf); + } else if (readBytes != stat_buf.st_size) { + diags.verbose("Network read udnerrun for file at %s, expected %lld bytes, got %zd bytes\n", path.c_str(), stat_buf.st_size, readBytes); + ::close(fd); + return std::make_pair((uint8_t*)(-1), stat_buf); + } + } + + ::close(fd); + + return std::make_pair((uint8_t*)buffer_ptr, stat_buf); +} + +#endif // BUILDING_CACHE_BUILDER + diff --git a/dyld3/shared-cache/FileUtils.h b/dyld3/shared-cache/FileUtils.h new file mode 100644 index 0000000..dbf6ae4 --- /dev/null +++ b/dyld3/shared-cache/FileUtils.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2017 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 FileUtils_h +#define FileUtils_h + +#include + +#include +#include +#include +#include +#include + +class Diagnostics; + +#if BUILDING_CACHE_BUILDER +struct FileCache { + FileCache(void); + std::pair cacheLoad(Diagnostics& diags, const std::string path); + void preflightCache(Diagnostics& diags, const std::string& path); + void preflightCache(Diagnostics& diags, const std::unordered_set& paths); + +private: + std::pair fill(Diagnostics& diags, const std::string& path); + + std::unordered_map> entries; + dispatch_queue_t cache_queue; +}; + +extern FileCache fileCache; +#endif + +// +// recursively walk all files in a directory tree +// symlinks are ignored +// dirFilter should return true on directories which should not be recursed into +// callback is called on each regular file found with stat() info about the file +// +void iterateDirectoryTree(const std::string& pathPrefix, const std::string& path, bool (^dirFilter)(const std::string& dirPath), + void (^callback)(const std::string& path, const struct stat& statBuf), bool processFiles=true); + + +// +// writes the buffer to a temp file, then renames the file to the final path +// returns true on success +// +bool safeSave(const void* buffer, size_t bufferLen, const std::string& path); + + +const void* mapFileReadOnly(const std::string& path, size_t& mappedSize); + +bool isProtectedBySIP(const std::string& path); +bool isProtectedBySIP(int fd); + +bool fileExists(const std::string& path); + +std::unordered_map loadOrderFile(const std::string& orderFile); + +std::string normalize_absolute_file_path(std::string path); +std::string basePath(const std::string& path); +std::string dirPath(const std::string& path); +std::string realPath(const std::string& path); +std::string realFilePath(const std::string& path); + +std::string toolDir(); + + + + +#endif // FileUtils_h diff --git a/dyld3/shared-cache/ImageProxy.cpp b/dyld3/shared-cache/ImageProxy.cpp new file mode 100644 index 0000000..fcd6db5 --- /dev/null +++ b/dyld3/shared-cache/ImageProxy.cpp @@ -0,0 +1,2366 @@ +/* + * Copyright (c) 2017 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 "ImageProxy.h" +#include "FileUtils.h" +#include "StringUtils.h" +#include "MachOParser.h" +#include "LaunchCacheFormat.h" +#include "LaunchCacheWriter.h" +#include "PathOverrides.h" +#include "libdyldEntryVector.h" + +namespace dyld3 { + +typedef launch_cache::TargetSymbolValue TargetSymbolValue; + + + +/////////////////////////// ImageProxy /////////////////////////// + +ImageProxy::ImageProxy(const mach_header* mh, const BinaryImageData* imageData, uint32_t indexInGroup, bool dyldCacheIsRaw) + : _mh(mh), _sliceFileOffset(0), _modTime(0), _inode(0), _imageBinaryData(imageData), _runtimePath(launch_cache::Image(imageData).path()), + _groupNum(0), _indexInGroup(indexInGroup), _isSetUID(false), _dyldCacheIsRaw(dyldCacheIsRaw), _platformBinary(false), _overrideOf(ImageRef::weakImportMissing()), + _directDependentsSet(false), _deepDependentsSet(false), _initBeforesArraySet(false), _initBeforesComputed(false), + _invalid(launch_cache::Image(imageData).isInvalid()), _staticallyReferenced(false), _cwdMustBeThisDir(false) +{ +} + +ImageProxy::ImageProxy(const DyldSharedCache::MappedMachO& mapping, uint32_t groupNum, uint32_t indexInGroup, bool dyldCacheIsRaw) + : _mh(mapping.mh), _sliceFileOffset(mapping.sliceFileOffset), _modTime(mapping.modTime), _inode(mapping.inode), _imageBinaryData(nullptr), _runtimePath(mapping.runtimePath), + _groupNum(groupNum), _indexInGroup(indexInGroup), _isSetUID(mapping.isSetUID), _dyldCacheIsRaw(dyldCacheIsRaw), _platformBinary(mapping.protectedBySIP), + _overrideOf(ImageRef::weakImportMissing()), _directDependentsSet(false), _deepDependentsSet(false), _initBeforesArraySet(false), _initBeforesComputed(false), + _invalid(false), _staticallyReferenced(false), _cwdMustBeThisDir(false) +{ +} + + +void ImageProxy::processRPaths(ImageProxyGroup& owningGroup) +{ + // parse LC_RPATH + __block std::unordered_set rawRpaths; + MachOParser parser(_mh, _dyldCacheIsRaw); + parser.forEachRPath(^(const char* rpath, bool& stop) { + if ( rawRpaths.count(rpath) ) { + _diag.warning("duplicate LC_RPATH (%s) in %s", rpath, _runtimePath.c_str()); + return; + } + rawRpaths.insert(rpath); + std::string thisRPath = rpath; + if ( startsWith(thisRPath, "@executable_path/") ) { + std::string mainPath = owningGroup.mainProgRuntimePath(); + if ( mainPath.empty() && parser.isDynamicExecutable() ) { + mainPath = _runtimePath; + } + if ( !mainPath.empty() ) { + std::string newPath = mainPath.substr(0, mainPath.rfind('/')+1) + thisRPath.substr(17); + std::string normalizedPath = owningGroup.normalizedPath(newPath); + if ( fileExists(normalizedPath) ) + _rpaths.push_back(normalizedPath); + else + _diag.warning("LC_RPATH to nowhere (%s) in %s", rpath, _runtimePath.c_str()); + char resolvedMainPath[PATH_MAX]; + if ( (realpath(mainPath.c_str(), resolvedMainPath) != nullptr) && (mainPath.c_str() != resolvedMainPath) ) { + std::string realMainPath = resolvedMainPath; + size_t lastSlashPos = realMainPath.rfind('/'); + std::string newRealPath = realMainPath.substr(0, lastSlashPos+1) + thisRPath.substr(17); + if ( realMainPath != mainPath ) { + for (const std::string& pre : owningGroup._buildTimePrefixes) { + std::string aPath = owningGroup.normalizedPath(pre + newRealPath); + if ( fileExists(aPath) ) { + _rpaths.push_back(owningGroup.normalizedPath(newRealPath)); + } + } + } + } + } + else { + _diag.warning("LC_RPATH uses @executable_path in %s", _runtimePath.c_str()); + } + } + else if ( thisRPath == "@executable_path" ) { + std::string mainPath = owningGroup.mainProgRuntimePath(); + if ( mainPath.empty() && parser.isDynamicExecutable() ) { + mainPath = _runtimePath; + } + if ( !mainPath.empty() ) { + std::string newPath = mainPath.substr(0, mainPath.rfind('/')+1); + std::string normalizedPath = owningGroup.normalizedPath(newPath); + _rpaths.push_back(normalizedPath); + } + else { + _diag.warning("LC_RPATH uses @executable_path in %s", _runtimePath.c_str()); + } + } + else if ( startsWith(thisRPath, "@loader_path/") ) { + size_t lastSlashPos = _runtimePath.rfind('/'); + std::string newPath = _runtimePath.substr(0, lastSlashPos+1) + thisRPath.substr(13); + bool found = false; + for (const std::string& pre : owningGroup._buildTimePrefixes) { + std::string aPath = owningGroup.normalizedPath(pre + newPath); + if ( fileExists(aPath) ) { + _rpaths.push_back(owningGroup.normalizedPath(newPath)); + found = true; + break; + } + } + char resolvedPath[PATH_MAX]; + if ( (realpath(_runtimePath.c_str(), resolvedPath) != nullptr) && (_runtimePath.c_str() != resolvedPath) ) { + std::string realRunPath = resolvedPath; + lastSlashPos = realRunPath.rfind('/'); + std::string newRealPath = realRunPath.substr(0, lastSlashPos+1) + thisRPath.substr(13); + if ( newRealPath != newPath ) { + for (const std::string& pre : owningGroup._buildTimePrefixes) { + std::string aPath = owningGroup.normalizedPath(pre + newRealPath); + if ( fileExists(aPath) ) { + _rpaths.push_back(owningGroup.normalizedPath(newRealPath)); + found = true; + break; + } + } + } + } + if ( !found ) { + // even though this path does not exist, we need to add it to must-be-missing paths + // in case it shows up at launch time + _rpaths.push_back(owningGroup.normalizedPath(newPath)); + _diag.warning("LC_RPATH to nowhere (%s) in %s", rpath, _runtimePath.c_str()); + } + } + else if ( thisRPath == "@loader_path" ) { + size_t lastSlashPos = _runtimePath.rfind('/'); + std::string newPath = _runtimePath.substr(0, lastSlashPos+1); + std::string normalizedPath = owningGroup.normalizedPath(newPath); + _rpaths.push_back(normalizedPath); + } + else if ( rpath[0] == '@' ) { + _diag.warning("LC_RPATH with unknown @ variable (%s) in %s", rpath, _runtimePath.c_str()); + } + else { + if ( rpath[0] == '/' ) + _diag.warning("LC_RPATH is absolute path (%s) in %s", rpath, _runtimePath.c_str()); + _rpaths.push_back(rpath); + } + }); + //if ( !_rpaths.empty() ) { + // fprintf(stderr, "for %s\n", _runtimePath.c_str()); + // for (const std::string& p : _rpaths) + // fprintf(stderr, " %s\n", p.c_str()); + //} +} + +void ImageProxy::addDependentsDeep(ImageProxyGroup& owningGroup, RPathChain* prev, bool staticallyReferenced) +{ + // mark binaries that are statically referenced and thus will never be unloaded + if ( staticallyReferenced ) + _staticallyReferenced = true; + + if ( _deepDependentsSet ) + return; + + // find all immediate dependents + addDependentsShallow(owningGroup, prev); + if ( _diag.hasError() ) { + _invalid = true; + return; + } + + // recurse though each dependent + RPathChain rchain = { this, prev, _rpaths }; + for (ImageProxy* proxy : _dependents) { + if ( proxy == nullptr ) + continue; // skip over weak missing dependents + if ( !proxy->_directDependentsSet ) + proxy->addDependentsDeep(owningGroup, &rchain, staticallyReferenced); + if ( proxy->invalid() ) + _invalid = true; + } + + _deepDependentsSet = true; +} + +void ImageProxy::addDependentsShallow(ImageProxyGroup& owningGroup, RPathChain* prev) +{ + if ( _directDependentsSet ) + return; + + MachOParser thisParser(mh(), _dyldCacheIsRaw); + dyld3::Platform thisPlatform = thisParser.platform(); + + processRPaths(owningGroup); + __block RPathChain rchain = { this, prev, _rpaths }; + + thisParser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) { + if ( (loadPath[0] != '/') && (loadPath[0] != '@') ) { + _diag.warning("load path is file system relative (%s) in %s", loadPath, runtimePath().c_str()); + } + Diagnostics depDiag; + ImageProxy* dep = owningGroup.findImage(depDiag, loadPath, isWeak, &rchain); + if ( (dep == nullptr) || dep->invalid() ) { + if (isWeak) { + // weak link against a broken dylib, pretend dylib is not there + dep = nullptr; + } else { + if ( depDiag.warnings().empty() ) { + if ( thisParser.header()->filetype == MH_EXECUTE ) + _diag.error("required dylib '%s' not found", loadPath); + else + _diag.error("required dylib '%s' not found, needed by '%s'", loadPath, runtimePath().c_str()); + } + else { + std::string allTries; + for (const std::string& warn : depDiag.warnings()) { + if ( allTries.empty() ) + allTries = warn; + else + allTries = allTries + ", " + warn; + } + _diag.error("required dylib '%s' not found, needed by '%s'. Did try: %s", loadPath, runtimePath().c_str(), allTries.c_str()); + } + } + } + else { + MachOParser depParser(dep->mh(), _dyldCacheIsRaw); + if ( _diag.noError() ) { + // verify found image has compatible version and matching platform + dyld3::Platform depPlatform = depParser.platform(); + if ( depPlatform != thisPlatform ) { + // simulator allows a few macOS libSystem dylibs + if ( !inLibSystem() || !dep->inLibSystem() ) { + _diag.error("found '%s' but it was built for different platform '%s' than required '%s'. Needed by '%s'", dep->runtimePath().c_str(), + MachOParser::platformName(depPlatform).c_str(), MachOParser::platformName(thisPlatform).c_str(), runtimePath().c_str()); + } + } + } + if ( _diag.noError() ) { + // verify compat version + const char* installName; + uint32_t foundCompatVers; + uint32_t foundCurrentVers; + if ( depParser.header()->filetype != MH_DYLIB ) { + _diag.error("found '%s' which is not a dylib. Needed by '%s'", dep->runtimePath().c_str(), runtimePath().c_str()); + } + else { + depParser.getDylibInstallName(&installName, &foundCompatVers, &foundCurrentVers); + if ( foundCompatVers < compatVersion ) { + _diag.error("found '%s' which has compat version (%s) which is less than required (%s). Needed by '%s'", dep->runtimePath().c_str(), + MachOParser::versionString(foundCompatVers).c_str(), MachOParser::versionString(compatVersion).c_str(), runtimePath().c_str()); + } + } + } + } + if ( _diag.hasError() ) { + stop = true; + _invalid = true; + } + _dependents.push_back(dep); + if ( isWeak ) + _dependentsKind.push_back(launch_cache::Image::LinkKind::weak); + else if ( isReExport ) + _dependentsKind.push_back(launch_cache::Image::LinkKind::reExport); + else if ( isUpward ) + _dependentsKind.push_back(launch_cache::Image::LinkKind::upward); + else + _dependentsKind.push_back(launch_cache::Image::LinkKind::regular); + }); + _directDependentsSet = true; +} + +bool ImageProxy::inLibSystem() const +{ + return startsWith(runtimePath(), "/usr/lib/system/") || startsWith(runtimePath(), "/usr/lib/libSystem."); +} + +void ImageProxy::forEachDependent(void (^handler)(ImageProxy* dep, LinkKind)) const +{ + for (int i=0; i < _dependents.size(); ++i) { + handler(_dependents[i], _dependentsKind[i]); + } +} + + +bool ImageProxy::findExportedSymbol(Diagnostics& diag, const char* symbolName, MachOParser::FoundSymbol& foundInfo) const +{ + MachOParser parser(_mh, _dyldCacheIsRaw); + return parser.findExportedSymbol(diag, symbolName, (void*)this, foundInfo, ^(uint32_t depIndex, const char* depLoadPath, void* extra, const mach_header** foundMH, void** foundExtra) { + ImageProxy* proxy = (ImageProxy*)extra; + if ( depIndex < proxy->_dependents.size() ) { + ImageProxy* depProxy = proxy->_dependents[depIndex]; + *foundMH = depProxy->_mh; + *foundExtra = (void*)depProxy; + return true; + } + return false; + }); +} + +bool ImageProxy::InitOrderInfo::beforeHas(ImageRef ref) +{ + ImageRef clearRef = ref; + clearRef.clearKind(); + return ( std::find(initBefore.begin(), initBefore.end(), clearRef) != initBefore.end() ); +} + +bool ImageProxy::InitOrderInfo::upwardHas(ImageProxy* proxy) +{ + return ( std::find(danglingUpward.begin(), danglingUpward.end(), proxy) != danglingUpward.end() ); +} + +void ImageProxy::InitOrderInfo::removeRedundantUpwards() +{ + danglingUpward.erase(std::remove_if(danglingUpward.begin(), danglingUpward.end(), + [&](ImageProxy* proxy) { + ImageRef ref(0, proxy->_groupNum, proxy->_indexInGroup); + return beforeHas(ref); + }), danglingUpward.end()); +} + + +// +// Every image has a list of "init-before" which means if that image was dlopen()ed +// here is the exact list of images to initialize in the exact order. This makes +// the runtime easy. It just walks the init-before list in order and runs each +// initializer if it has not already been run. +// +// The init-before list for each image is calculated based on the init-before list +// of each of its dependents. It simply starts with the list of its first dependent, +// then appends the list of the next, removing entries already in the list, etc. +// Lastly if the current image has an initializer, it is appended to its init-before list. +// +// To handle cycles, when recursing to get a dependent's init-before list, any image +// whose list is still being calculated (cycle), just returns its list so far. +// +// Explicit upward links are handled in two parts. First, in the algorithm described above, +// all upward links are ignored, which works fine as long as anything upward linked is +// downward linked at some point. If not, it is called a "dangling upward link". Since +// nothing depends on those, they are added to the end of the final init-before list. +// + +void ImageProxy::recursiveBuildInitBeforeInfo(ImageProxyGroup& owningGroup) +{ + if ( _initBeforesComputed ) + return; + _initBeforesComputed = true; // break cycles + + if ( _imageBinaryData != nullptr ) { + assert(_groupNum == 0); + // if this is proxy for something in dyld cache, get its list from cache + // and parse list into befores and upwards + launch_cache::Image image(_imageBinaryData); + image.forEachInitBefore(^(launch_cache::binary_format::ImageRef ref) { + if ( (LinkKind)ref.kind() == LinkKind::upward ) { + ImageProxyGroup* groupP = &owningGroup; + while (groupP->_groupNum != 0) + groupP = groupP->_nextSearchGroup; + launch_cache::ImageGroup dyldCacheGroup(groupP->_basedOn); + launch_cache::Image dyldCacheImage = dyldCacheGroup.image(ref.indexInGroup()); + Diagnostics diag; + ImageProxy* p = groupP->findAbsoluteImage(diag, dyldCacheImage.path(), false, false); + if ( diag.noError() ) + _initBeforesInfo.danglingUpward.push_back(p); + } + else { + _initBeforesInfo.initBefore.push_back(ref); + } + }); + } + else { + // calculate init-before list for this image by merging init-before's of all its dependent dylibs + unsigned depIndex = 0; + for (ImageProxy* depProxy : _dependents) { + if ( depProxy == nullptr ) { + assert(_dependentsKind[depIndex] == LinkKind::weak); + } + else { + if ( _dependentsKind[depIndex] == LinkKind::upward ) { + // if this upward link is already in the list, we ignore it. Otherwise add to front of list + if ( _initBeforesInfo.upwardHas(depProxy) ) { + // already in upward list, do nothing + } + else { + ImageRef ref(0, depProxy->_groupNum, depProxy->_indexInGroup); + if ( _initBeforesInfo.beforeHas(ref) ) { + // already in before list, do nothing + } + else { + // add to upward list + _initBeforesInfo.danglingUpward.push_back(depProxy); + } + } + } + else { + // compute init-befores of downward dependents + depProxy->recursiveBuildInitBeforeInfo(owningGroup); + // merge befores from this downward link into current befores list + for (ImageRef depInit : depProxy->_initBeforesInfo.initBefore) { + if ( !_initBeforesInfo.beforeHas(depInit) ) + _initBeforesInfo.initBefore.push_back(depInit); + } + // merge upwards from this downward link into current befores list + for (ImageProxy* upProxy : depProxy->_initBeforesInfo.danglingUpward) { + ImageRef ref(0, upProxy->_groupNum, upProxy->_indexInGroup); + if ( _initBeforesInfo.beforeHas(ref) ) { + // already in current initBefore list, so ignore this upward + } + else if ( _initBeforesInfo.upwardHas(upProxy) ) { + // already in current danglingUpward list, so ignore this upward + } + else { + // append to current danglingUpward list + _initBeforesInfo.danglingUpward.push_back(upProxy); + } + } + } + } + ++depIndex; + } + // eliminate any upward links added to befores list by some other dependent + _initBeforesInfo.removeRedundantUpwards(); + + // if this images has initializer(s) (or +load), add it to list + MachOParser parser(_mh, _dyldCacheIsRaw); + Diagnostics diag; + if ( parser.hasInitializer(diag) || parser.hasPlusLoadMethod(diag) ) { + launch_cache::binary_format::ImageRef ref(0, _groupNum, _indexInGroup); + _initBeforesInfo.initBefore.push_back(ref); + } + + //fprintf(stderr, "info for (%d, %d) %s\n", _group, _index, _runtimePath.c_str()); + //for (ImageRef ref : _initBeforesInfo.initBefore) + // fprintf(stderr, " ref = {%d, %d, %d}\n", ref.kind(), ref.group(), ref.index()); + //for (ImageProxy* p : _initBeforesInfo.danglingUpward) + // fprintf(stderr, " up = %s\n", p->runtimePath().c_str()); + } +} + +void ImageProxy::convertInitBeforeInfoToArray(ImageProxyGroup& owningGroup) +{ + if ( _initBeforesInfo.danglingUpward.empty() ) { + _initBeforesArray = _initBeforesInfo.initBefore; + } + else { + for (ImageRef ref : _initBeforesInfo.initBefore) + _initBeforesArray.push_back(ref); + bool inLibSys = inLibSystem(); + for (ImageProxy* proxy : _initBeforesInfo.danglingUpward) { + // ignore upward dependendencies between stuff within libSystem.dylib + if ( inLibSys && proxy->inLibSystem() ) + continue; + proxy->getInitBeforeList(owningGroup); + for (ImageRef depInit : proxy->_initBeforesInfo.initBefore) { + if ( std::find(_initBeforesArray.begin(), _initBeforesArray.end(), depInit) == _initBeforesArray.end() ) + _initBeforesArray.push_back(depInit); + } + ImageRef ref(0, proxy->_groupNum, proxy->_indexInGroup); + if ( std::find(_initBeforesArray.begin(), _initBeforesArray.end(), ref) == _initBeforesArray.end() ) + _initBeforesArray.push_back(ref); + } + } + //fprintf(stderr, "final init-before info for %s\n", _runtimePath.c_str()); + //for (ImageRef ref : _initBeforesArray) { + // fprintf(stderr, " ref = {%d, %d, %d}\n", ref.linkKind, ref.group, ref.index); + //} +} + +const std::vector& ImageProxy::getInitBeforeList(ImageProxyGroup& owningGroup) +{ + if ( !_initBeforesArraySet ) { + _initBeforesArraySet = true; // break cycles + recursiveBuildInitBeforeInfo(owningGroup); + convertInitBeforeInfoToArray(owningGroup); + } + return _initBeforesArray; +} + +ImageProxy::FixupInfo ImageProxy::buildFixups(Diagnostics& diag, uint64_t cacheUnslideBaseAddress, launch_cache::ImageGroupWriter& groupWriter) const +{ + __block ImageProxy::FixupInfo info; + MachOParser image(_mh, _dyldCacheIsRaw); + + // add fixup for each rebase + __block bool rebaseError = false; + image.forEachRebase(diag, ^(uint32_t segIndex, uint64_t segOffset, uint8_t type, bool& stop) { + dyld3::launch_cache::ImageGroupWriter::FixupType fixupType = launch_cache::ImageGroupWriter::FixupType::rebase; + switch ( type ) { + case REBASE_TYPE_POINTER: + fixupType = launch_cache::ImageGroupWriter::FixupType::rebase; + break; + case REBASE_TYPE_TEXT_ABSOLUTE32: + fixupType = launch_cache::ImageGroupWriter::FixupType::rebaseText; + info.hasTextRelocs = true; + break; + case REBASE_TYPE_TEXT_PCREL32: + diag.error("pcrel text rebasing not supported"); + stop = true; + rebaseError = true; + break; + default: + diag.error("unknown rebase type"); + stop = true; + rebaseError = true; + break; + } + info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeInvalid()}); + //fprintf(stderr, "rebase: segIndex=%d, segOffset=0x%0llX, type=%d\n", segIndex, segOffset, type); + }); + if ( diag.hasError() ) + return FixupInfo(); + + // add fixup for each bind + image.forEachBind(diag, ^(uint32_t segIndex, uint64_t segOffset, uint8_t type, int libOrdinal, + uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop) { + launch_cache::ImageGroupWriter::FixupType fixupType; + switch ( type ) { + case BIND_TYPE_POINTER: + if ( lazy ) + fixupType = launch_cache::ImageGroupWriter::FixupType::pointerLazyBind; + else + fixupType = launch_cache::ImageGroupWriter::FixupType::pointerBind; + break; + case BIND_TYPE_TEXT_ABSOLUTE32: + fixupType = launch_cache::ImageGroupWriter::FixupType::bindText; + info.hasTextRelocs = true; + break; + case BIND_TYPE_TEXT_PCREL32: + fixupType = launch_cache::ImageGroupWriter::FixupType::bindTextRel; + info.hasTextRelocs = true; + break; + case BIND_TYPE_IMPORT_JMP_REL32: + fixupType = launch_cache::ImageGroupWriter::FixupType::bindImportJmpRel; + break; + default: + diag.error("malformed executable, unknown bind type (%d)", type); + stop = true; + return; + } + const ImageProxy* depProxy = nullptr; + bool isWeakDylib = false; + if ( libOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) { + // -bundle_loader symbols cannot be bound ahead of time, we must look them up at load time + uint32_t imagePathPoolOffset = groupWriter.addString("@main"); + uint32_t imageSymbolPoolOffset = groupWriter.addString(symbolName); + info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeDynamicGroupValue(imagePathPoolOffset, imageSymbolPoolOffset, weakImport)}); + return; + } + else if ( libOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) { + // -dynamic_lookup symbols cannot be bound ahead of time, we must look them up at load time + uint32_t imagePathPoolOffset = groupWriter.addString("@flat"); + uint32_t imageSymbolPoolOffset = groupWriter.addString(symbolName); + info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeDynamicGroupValue(imagePathPoolOffset, imageSymbolPoolOffset, weakImport)}); + return; + } + else if ( libOrdinal == BIND_SPECIAL_DYLIB_SELF ) { + depProxy = this; + } + else if ( (libOrdinal >= 1) && (libOrdinal <= _dependents.size()) ) { + isWeakDylib = (_dependentsKind[libOrdinal-1] == LinkKind::weak); + depProxy = _dependents[libOrdinal-1]; + } + else { + diag.error("ordinal %d not supported", libOrdinal); + stop = true; + return; + } + if ( depProxy != nullptr ) { + MachOParser::FoundSymbol foundInfo; + if ( depProxy->findExportedSymbol(diag, symbolName, foundInfo) ) { + MachOParser implDylib(foundInfo.foundInDylib, _dyldCacheIsRaw); + switch ( foundInfo.kind ) { + case MachOParser::FoundSymbol::Kind::headerOffset: + case MachOParser::FoundSymbol::Kind::resolverOffset: + if ( implDylib.inDyldCache() ) { + uint32_t cacheOffset = (uint32_t)(implDylib.preferredLoadAddress() + foundInfo.value - cacheUnslideBaseAddress + addend); + info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeSharedCacheOffset(cacheOffset)}); + } + else { + ImageProxy* foundProxy = (ImageProxy*)(foundInfo.foundExtra); + bool isIndirectGroupNum = foundProxy->_groupNum >= 128; + uint32_t groupNum = isIndirectGroupNum ? groupWriter.addIndirectGroupNum(foundProxy->_groupNum) : foundProxy->_groupNum; + info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeGroupValue(groupNum, foundProxy->_indexInGroup, foundInfo.value+addend, isIndirectGroupNum)}); + } + break; + case MachOParser::FoundSymbol::Kind::absolute: + if (((((intptr_t)(foundInfo.value+addend)) << 2) >> 2) != (foundInfo.value+addend)) { + diag.error("absolute value %lld not supported", foundInfo.value+addend); + stop = true; + return; + } + info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeAbsolute(foundInfo.value+addend)}); + break; + } + } + else { + if ( !weakImport ) { + diag.error("symbol '%s' not found, expected in '%s'", symbolName, depProxy->runtimePath().c_str()); + stop = true; + } + // mark fixup needs to set fixup location to zero + info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeAbsolute(0)}); + } + } + else { + if ( isWeakDylib ) { + // dylib not found and is weak, set pointers into it to zero + info.fixups.push_back({segIndex, segOffset, fixupType, TargetSymbolValue::makeAbsolute(0)}); + } + else { + diag.error("dylib ordinal %d not found and not weak", libOrdinal); + stop = true; + } + } + }); + if ( diag.hasError() ) + return FixupInfo(); + + uint32_t weakDefPathPoolOffset = groupWriter.addString("@weak_def"); + image.forEachWeakDef(diag, ^(bool strongDef, uint32_t segIndex, uint64_t segOffset, uint64_t addend, const char* symbolName, bool& stop) { + if ( strongDef ) + return; + // find fixup for that location and change it to be a @weakdef dynamic target + bool altered = false; + for (FixUp& fixup : info.fixups) { + if ( (fixup.segOffset == segOffset) && (fixup.segIndex == segIndex) ) { + uint32_t symbolPoolOffset = groupWriter.addString(symbolName); + fixup.type = launch_cache::ImageGroupWriter::FixupType::pointerBind; + fixup.target = TargetSymbolValue::makeDynamicGroupValue(weakDefPathPoolOffset, symbolPoolOffset, false); + altered = true; + } + } + if ( !altered ) { + if ( image.isSlideable() ) { + fprintf(stderr, "weak def for %s can't find underlying rebase/bind in %s\n", symbolName, runtimePath().c_str()); + } + else { + // no-pie executable does not have rebase for weak-def fixup, so add fixup + uint32_t symbolPoolOffset = groupWriter.addString(symbolName); + info.fixups.push_back({segIndex, segOffset, launch_cache::ImageGroupWriter::FixupType::pointerBind, TargetSymbolValue::makeDynamicGroupValue(weakDefPathPoolOffset, symbolPoolOffset, false)} ); + } + } + + }); + + return info; +} + + +void ImageProxy::setOverrideOf(uint32_t groupNum, uint32_t indexInGroup) +{ + _overrideOf = ImageRef(0, groupNum, indexInGroup); +} + + +static bool alreadyInList(const std::vector& imageList, ImageProxy* image) +{ + for (ImageProxy* proxy : imageList) { + if ( proxy == image ) + return true; + } + return false; +} + +void ImageProxy::addToFlatLookup(std::vector& imageList) +{ + // add all images shallow + bool addedSomething = false; + for (ImageProxy* dep : _dependents) { + if ( dep == nullptr ) + continue; + if ( !alreadyInList(imageList, dep) ) { + imageList.push_back(dep); + addedSomething = true; + } + } + // recurse + if ( addedSomething ) { + for (ImageProxy* dep : _dependents) { + if ( dep == nullptr ) + continue; + dep->addToFlatLookup(imageList); + } + } +} + + +/////////////////////////// ImageProxyGroup /////////////////////////// + + +class StringPool +{ +public: + uint32_t add(const std::string& str); + size_t size() const { return _buffer.size(); } + const char* buffer() const { return &_buffer[0]; } + void align(); +private: + std::vector _buffer; + std::unordered_map _existingEntries; +}; + +uint32_t StringPool::add(const std::string& str) +{ + auto pos = _existingEntries.find(str); + if ( pos != _existingEntries.end() ) + return pos->second; + size_t len = str.size() + 1; + size_t offset = _buffer.size(); + _buffer.insert(_buffer.end(), &str[0], &str[len]); + _existingEntries[str] = (uint32_t)offset; + assert(offset < 0xFFFF); + return (uint32_t)offset; +} + +void StringPool::align() +{ + while ( (_buffer.size() % 4) != 0 ) + _buffer.push_back('\0'); +} + +ImageProxyGroup::ImageProxyGroup(uint32_t groupNum, const DyldCacheParser& dyldCache, const launch_cache::binary_format::ImageGroup* basedOn, + ImageProxyGroup* next, const std::string& mainProgRuntimePath, + const std::vector& knownGroups, + const std::vector& buildTimePrefixes, + const std::vector& envVars, + bool stubsEliminated, bool dylibsExpectedOnDisk, bool inodesAreSameAsRuntime) + : _pathOverrides(envVars), _patchTable(nullptr), _basedOn(basedOn), _dyldCache(dyldCache), _nextSearchGroup(next), _groupNum(groupNum), + _stubEliminated(stubsEliminated), _dylibsExpectedOnDisk(dylibsExpectedOnDisk), _inodesAreSameAsRuntime(inodesAreSameAsRuntime), + _knownGroups(knownGroups), _buildTimePrefixes(buildTimePrefixes), _mainProgRuntimePath(mainProgRuntimePath), _platform(Platform::unknown) +{ + _archName = dyldCache.cacheHeader()->archName(); + _platform = (Platform)(dyldCache.cacheHeader()->platform()); +} + + +ImageProxyGroup::~ImageProxyGroup() +{ + for (DyldSharedCache::MappedMachO& mapping : _ownedMappings ) { + vm_deallocate(mach_task_self(), (vm_address_t)mapping.mh, mapping.length); + } + for (ImageProxy* proxy : _images) { + delete proxy; + } +} + + +std::string ImageProxyGroup::normalizedPath(const std::string& path) +{ + for (const std::string& prefix : _buildTimePrefixes) { + std::string fullPath = prefix + path; + if ( fileExists(fullPath) ) { + if ( (fullPath.find("/../") != std::string::npos) || (fullPath.find("//") != std::string::npos) || (fullPath.find("/./") != std::string::npos) ) { + char resolvedPath[PATH_MAX]; + if ( realpath(fullPath.c_str(), resolvedPath) != nullptr ) { + std::string resolvedUnPrefixed = &resolvedPath[prefix.size()]; + return resolvedUnPrefixed; + } + } + break; + } + } + return path; +} + + +ImageProxy* ImageProxyGroup::findImage(Diagnostics& diag, const std::string& runtimeLoadPath, bool canBeMissing, ImageProxy::RPathChain* rChain) +{ + __block ImageProxy* result = nullptr; + _pathOverrides.forEachPathVariant(runtimeLoadPath.c_str(), _platform, ^(const char* possiblePath, bool& stop) { + if ( startsWith(possiblePath, "@rpath/") ) { + std::string trailing = &possiblePath[6]; + for (const ImageProxy::RPathChain* cur=rChain; cur != nullptr; cur = cur->prev) { + for (const std::string& rpath : cur->rpaths) { + std::string aPath = rpath + trailing; + result = findAbsoluteImage(diag, aPath, true, false); + if ( result != nullptr ) { + _pathToProxy[runtimeLoadPath] = result; + stop = true; + return; + } + } + } + // if cannot be found via current stack of rpaths, check if already found + auto pos = _pathToProxy.find(possiblePath); + if ( pos != _pathToProxy.end() ) { + result = pos->second; + stop = true; + return; + } + } + else if ( startsWith(possiblePath, "@loader_path/") ) { + std::string loaderFile = rChain->inProxy->runtimePath(); + size_t lastSlash = loaderFile.rfind('/'); + if ( lastSlash != std::string::npos ) { + std::string loaderDir = loaderFile.substr(0, lastSlash); + std::string newPath = loaderDir + &possiblePath[12]; + result = findAbsoluteImage(diag, newPath, canBeMissing, false); + if ( result != nullptr ) { + _pathToProxy[runtimeLoadPath] = result; + stop = true; + return; + } + } + } + else if ( startsWith(possiblePath, "@executable_path/") ) { + for (const ImageProxy::RPathChain* cur=rChain; cur != nullptr; cur = cur->prev) { + if ( cur->inProxy->mh()->filetype == MH_EXECUTE ) { + std::string mainProg = cur->inProxy->runtimePath(); + size_t lastSlash = mainProg.rfind('/'); + if ( lastSlash != std::string::npos ) { + std::string mainDir = mainProg.substr(0, lastSlash); + std::string newPath = mainDir + &possiblePath[16]; + result = findAbsoluteImage(diag, newPath, canBeMissing, false); + if ( result != nullptr ) { + _pathToProxy[runtimeLoadPath] = result; + stop = true; + return; + } + } + } + } + } + else { + // load command is full path to dylib + result = findAbsoluteImage(diag, possiblePath, canBeMissing, false); + if ( result != nullptr ) { + stop = true; + return; + } + } + }); + + // when building closure, check if an added dylib is an override for something in the cache + if ( (result != nullptr) && (_groupNum > 1) && !result->isProxyForCachedDylib() ) { + for (ImageProxyGroup* grp = this; grp != nullptr; grp = grp->_nextSearchGroup) { + if ( grp->_basedOn == nullptr ) + continue; + uint32_t indexInGroup; + launch_cache::ImageGroup imageGroup(grp->_basedOn); + if ( imageGroup.findImageByPath(runtimeLoadPath.c_str(), indexInGroup) ) { + result->setOverrideOf(imageGroup.groupNum(), indexInGroup); + break; + } + } + } + + return result; +} + + +bool ImageProxyGroup::builtImageStillValid(const launch_cache::Image& image) +{ + // only do checks when running on system + if ( _buildTimePrefixes.size() != 1 ) + return true; + if ( _buildTimePrefixes.front().size() != 0 ) + return true; + if ( _platform != MachOParser::currentPlatform() ) + return true; + + struct stat statBuf; + bool expectedOnDisk = image.group().dylibsExpectedOnDisk(); + bool overridableDylib = image.overridableDylib(); + bool cachedDylib = !image.isDiskImage(); + bool fileFound = ( ::stat(image.path(), &statBuf) == 0 ); + + if ( cachedDylib ) { + if ( expectedOnDisk ) { + if ( fileFound ) { + // macOS case: verify dylib file info matches what it was when cache was built + return ( (image.fileModTime() == statBuf.st_mtime) && (image.fileINode() == statBuf.st_ino) ); + } + else { + // macOS case: dylib missing + return false; + } + } + else { + if ( fileFound ) { + if ( overridableDylib ) { + // iOS case: internal install with dylib root + return false; + } + else { + // iOS case: customer install, ignore dylib on disk + return true; + } + } + else { + // iOS case: cached dylib not on disk as expected + return true; + } + } + } + else { + if ( fileFound ) { + if ( image.validateUsingModTimeAndInode() ) { + // macOS case: verify dylib file info matches what it was when cache was built + return ( (image.fileModTime() == statBuf.st_mtime) && (image.fileINode() == statBuf.st_ino) ); + } + else { + // FIXME: need to verify file cdhash + return true; + } + } + else { + // dylib not on disk as expected + return false; + } + } +} + +ImageProxy* ImageProxyGroup::findAbsoluteImage(Diagnostics& diag, const std::string& runtimeLoadPath, bool canBeMissing, bool makeErrorMessage, bool pathIsAlreadyReal) +{ + auto pos = _pathToProxy.find(runtimeLoadPath); + if ( pos != _pathToProxy.end() ) + return pos->second; + + // see if this ImageProxyGroup is a proxy for an ImageGroup from the dyld shared cache + if ( _basedOn != nullptr ) { + uint32_t foundIndex; + launch_cache::ImageGroup imageGroup(_basedOn); + if ( imageGroup.findImageByPath(runtimeLoadPath.c_str(), foundIndex) ) { + launch_cache::Image image = imageGroup.image(foundIndex); + if ( builtImageStillValid(image) ) { + ImageProxy* proxy = nullptr; + if ( _groupNum == 0 ) { + const mach_header* mh = (mach_header*)((uint8_t*)(_dyldCache.cacheHeader()) + image.cacheOffset()); + proxy = new ImageProxy(mh, image.binaryData(), foundIndex, _dyldCache.cacheIsMappedRaw()); + } + else { + DyldSharedCache::MappedMachO* mapping = addMappingIfValidMachO(diag, runtimeLoadPath); + if ( mapping != nullptr ) { + proxy = new ImageProxy(*mapping, _groupNum, foundIndex, false); + } + } + if ( proxy != nullptr ) { + _pathToProxy[runtimeLoadPath] = proxy; + _images.push_back(proxy); + if ( runtimeLoadPath != image.path() ) { + // lookup path is an alias, add real path too + _pathToProxy[image.path()] = proxy; + } + return proxy; + } + } + } + } + + if ( _nextSearchGroup != nullptr ) { + ImageProxy* result = _nextSearchGroup->findAbsoluteImage(diag, runtimeLoadPath, true, false); + if ( result != nullptr ) + return result; + } + + // see if this is a symlink to a dylib + if ( !pathIsAlreadyReal ) { + for (const std::string& prefix : _buildTimePrefixes) { + std::string fullPath = prefix + runtimeLoadPath; + if ( endsWith(prefix, "/") ) + fullPath = prefix.substr(0, prefix.size()-1) + runtimeLoadPath; + if ( fileExists(fullPath) ) { + std::string resolvedPath = realFilePath(fullPath); + if ( !resolvedPath.empty() && (resolvedPath!= fullPath) ) { + std::string resolvedRuntimePath = resolvedPath.substr(prefix.size()); + ImageProxy* proxy = findAbsoluteImage(diag, resolvedRuntimePath, true, false, true); + if ( proxy != nullptr ) + return proxy; + } + } + } + } + + if ( (_groupNum >= 2) && (_basedOn == nullptr) ) { + if ( (runtimeLoadPath[0] != '/') && (runtimeLoadPath[0] != '@') ) { + for (ImageProxy* aProxy : _images) { + if ( endsWith(aProxy->runtimePath(), runtimeLoadPath) ) { + aProxy->setCwdMustBeThisDir(); + return aProxy; + } + } + } + + DyldSharedCache::MappedMachO* mapping = addMappingIfValidMachO(diag, runtimeLoadPath); + if ( mapping != nullptr ) { + ImageProxy* proxy = new ImageProxy(*mapping, _groupNum, (uint32_t)_images.size(), false); + _pathToProxy[runtimeLoadPath] = proxy; + _images.push_back(proxy); + return proxy; + } + } + + if ( !canBeMissing && makeErrorMessage ) { + if ( diag.warnings().empty() ) { + if ( diag.hasError() ) { + std::string orgMsg = diag.errorMessage(); + diag.error("'%s' %s", runtimeLoadPath.c_str(), orgMsg.c_str()); + } + else { + diag.error("could not find '%s'", runtimeLoadPath.c_str()); + } + } + else { + std::string allTries; + for (const std::string& warn : diag.warnings()) { + if ( allTries.empty() ) + allTries = warn; + else + allTries = allTries + ", " + warn; + } + diag.clearWarnings(); + diag.error("could not use '%s'. Did try: %s", runtimeLoadPath.c_str(), allTries.c_str()); + } + } + + // record locations not found so it can be verified they are still missing at runtime + _mustBeMissingFiles.insert(runtimeLoadPath); + + return nullptr; +} + + +DyldSharedCache::MappedMachO* ImageProxyGroup::addMappingIfValidMachO(Diagnostics& diag, const std::string& runtimePath, bool ignoreMainExecutables) +{ + bool fileFound = false; + for (const std::string& prefix : _buildTimePrefixes) { + std::string fullPath = prefix + runtimePath; + struct stat statBuf; + if ( stat(fullPath.c_str(), &statBuf) != 0 ) + continue; + fileFound = true; + // map whole file and determine if it is mach-o or a fat file + int fd = ::open(fullPath.c_str(), O_RDONLY); + if ( fd < 0 ) { + diag.warning("file not open()able '%s' errno=%d", fullPath.c_str(), errno); + continue; + } + size_t len = (size_t)statBuf.st_size; + size_t offset = 0; + const void* p = ::mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); + if ( p != MAP_FAILED ) { + size_t sliceLen; + size_t sliceOffset; + bool missingSlice; + Diagnostics fatDiag; + if ( FatUtil::isFatFileWithSlice(fatDiag, p, len, _archName, sliceOffset, sliceLen, missingSlice) ) { + // unmap whole file + ::munmap((void*)p, len); + // remap just slice + p = ::mmap(NULL, sliceLen, PROT_READ, MAP_PRIVATE, fd, sliceOffset); + if ( p != MAP_FAILED ) { + offset = sliceOffset; + len = sliceLen; + } + } + else if ( fatDiag.hasError() ) { + diag.warning("%s", fatDiag.errorMessage().c_str()); + } + if ( (p != MAP_FAILED) && !missingSlice && MachOParser::isValidMachO(diag, _archName, _platform, p, len, fullPath, ignoreMainExecutables) ) { + bool issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID)); + bool sip = false; // FIXME + _ownedMappings.emplace_back(runtimePath, (mach_header*)p, len, issetuid, sip, offset, statBuf.st_mtime, statBuf.st_ino); + ::close(fd); + return &_ownedMappings.back(); + } + else if (p != MAP_FAILED) { + ::munmap((void*)p, len); + } + } + ::close(fd); + } + if ( !fileFound ) + diag.warning("file not found '%s'", runtimePath.c_str()); + + return nullptr; +} + +static bool dontExamineDir(const std::string& dirPath) +{ + return endsWith(dirPath, ".app") || endsWith(dirPath, ".xctoolchain") || endsWith(dirPath, ".sdk") || endsWith(dirPath, ".platform"); +} + +void ImageProxyGroup::addExtraMachOsInBundle(const std::string& appDir) +{ + iterateDirectoryTree("", appDir, ^(const std::string& dirPath) { return dontExamineDir(dirPath); }, ^(const std::string& path, const struct stat& statBuf) { + // ignore files that don't have 'x' bit set (all runnable mach-o files do) + const bool hasXBit = ((statBuf.st_mode & S_IXOTH) == S_IXOTH); + if ( !hasXBit ) + return; + + // ignore files too small + if ( statBuf.st_size < 0x1000 ) + return; + + // if the file is mach-o, add to list + if ( _pathToProxy.find(path) == _pathToProxy.end() ) { + Diagnostics machoDiag; + DyldSharedCache::MappedMachO* mapping = addMappingIfValidMachO(machoDiag, path, true); + if ( mapping != nullptr ) { + ImageProxy* proxy = new ImageProxy(*mapping, _groupNum, (uint32_t)_images.size(), false); + if ( proxy != nullptr ) { + _pathToProxy[path] = proxy; + _images.push_back(proxy); + } + } + } + }); +} + +// used when building dyld shared cache +ImageProxyGroup* ImageProxyGroup::makeDyldCacheDylibsGroup(Diagnostics& diag, const DyldCacheParser& dyldCache, + const std::vector& cachedDylibs, + const std::vector& buildTimePrefixes, + const PatchTable& patchTable, bool stubEliminated, bool dylibsExpectedOnDisk) +{ + std::vector emptyEnvVars; // Note: this method only used when constructing dyld cache where envs are not used + std::vector noExistingGroups; + ImageProxyGroup* groupProxy = new ImageProxyGroup(0, dyldCache, nullptr, nullptr, "", noExistingGroups, buildTimePrefixes, emptyEnvVars, stubEliminated, dylibsExpectedOnDisk); + groupProxy->_patchTable = &patchTable; + + // add every dylib in shared cache to _images + uint32_t indexInGroup = 0; + for (const DyldSharedCache::MappedMachO& mapping : cachedDylibs) { + ImageProxy* proxy = new ImageProxy(mapping, 0, indexInGroup++, true); + groupProxy->_images.push_back(proxy); + groupProxy->_pathToProxy[mapping.runtimePath] = proxy; + } + + // verify libdyld is compatible + ImageRef libdyldEntryImageRef = ImageRef::makeEmptyImageRef(); + uint32_t libdyldEntryOffset; + groupProxy->findLibdyldEntry(diag, libdyldEntryImageRef, libdyldEntryOffset); + if ( diag.hasError() ) { + delete groupProxy; + return nullptr; + } + + // wire up dependents + bool hadError = false; + for (size_t i=0; i < groupProxy->_images.size(); ++i) { + // note: addDependentsShallow() can append to _images, so can't use regular iterator + ImageProxy* proxy = groupProxy->_images[i]; + proxy->addDependentsShallow(*groupProxy); + if ( proxy->diagnostics().hasError() ) { + hadError = true; + diag.copy(proxy->diagnostics()); + break; + } + } + + if ( hadError ) { + delete groupProxy; + return nullptr; + } + + return groupProxy; +} + + +// used when building dyld shared cache +ImageProxyGroup* ImageProxyGroup::makeOtherOsGroup(Diagnostics& diag, const DyldCacheParser& dyldCache, ImageProxyGroup* cachedDylibsGroup, + const std::vector& otherDylibsAndBundles, + bool inodesAreSameAsRuntime, const std::vector& buildTimePrefixes) +{ + std::vector emptyEnvVars; // Note: this method only used when constructing dyld cache where envs are not used + const BinaryImageGroupData* cachedDylibsGroupData = dyldCache.cachedDylibsGroup(); + std::vector existingGroups = { cachedDylibsGroupData }; + ImageProxyGroup dyldCacheDylibProxyGroup(0, dyldCache, cachedDylibsGroupData, nullptr, "", existingGroups, buildTimePrefixes, emptyEnvVars); + ImageProxyGroup* groupProxy = new ImageProxyGroup(1, dyldCache, nullptr, cachedDylibsGroup, "", existingGroups, buildTimePrefixes, emptyEnvVars, + false, true, inodesAreSameAsRuntime); + + // add every dylib/bundle in "other: list to _images + uint32_t indexInGroup = 0; + for (const DyldSharedCache::MappedMachO& mapping : otherDylibsAndBundles) { + ImageProxy* proxy = new ImageProxy(mapping, 1, indexInGroup++, false); + groupProxy->_images.push_back(proxy); + groupProxy->_pathToProxy[mapping.runtimePath] = proxy; + } + + // wire up dependents + for (size_t i=0; i < groupProxy->_images.size(); ++i) { + // note: addDependentsShallow() can append to _images, so can't use regular iterator + ImageProxy* proxy = groupProxy->_images[i]; + // note: other-dylibs can only depend on dylibs in this group or group 0, so no need for deep dependents + proxy->addDependentsShallow(*groupProxy); + if ( proxy->diagnostics().hasError() ) { + diag.warning("adding dependents to %s: %s", proxy->runtimePath().c_str(), proxy->diagnostics().errorMessage().c_str()); + proxy->markInvalid(); + } + } + // propagate invalidness + __block bool somethingInvalid; + do { + somethingInvalid = false; + for (ImageProxy* proxy : groupProxy->_images) { + proxy->forEachDependent(^(ImageProxy* dep, LinkKind) { + if ( (dep != nullptr) && dep->invalid() && !proxy->invalid()) { + proxy->markInvalid(); + somethingInvalid = true; + } + }); + } + } while (somethingInvalid); + + return groupProxy; +} + +// used by closured for dlopen of unknown dylibs +const BinaryImageGroupData* ImageProxyGroup::makeDlopenGroup(Diagnostics& diag, const DyldCacheParser& dyldCache, uint32_t groupNum, + const std::vector& existingGroups, + const std::string& imagePath, const std::vector& envVars) +{ + const std::vector& noBuildTimePrefixes = {""}; + ImageProxyGroup dyldCacheDylibProxyGroup(0, dyldCache, existingGroups[0], nullptr, "", existingGroups, noBuildTimePrefixes, envVars); + ImageProxyGroup dyldCacheOtherProxyGroup(1, dyldCache, nullptr, &dyldCacheDylibProxyGroup, "", existingGroups, noBuildTimePrefixes, envVars); + ImageProxyGroup dlopenGroupProxy(groupNum, dyldCache, nullptr, &dyldCacheOtherProxyGroup, imagePath, existingGroups, noBuildTimePrefixes, envVars, false, true, true); + + DyldSharedCache::MappedMachO* topMapping = dlopenGroupProxy.addMappingIfValidMachO(diag, imagePath, true); + if ( topMapping == nullptr ) { + if ( diag.noError() ) { + const std::set& warnings = diag.warnings(); + if ( warnings.empty() ) + diag.error("no loadable mach-o in %s", imagePath.c_str()); + else + diag.error("%s", (*warnings.begin()).c_str()); + } + return nullptr; + } + + ImageProxy* topImageProxy = new ImageProxy(*topMapping, groupNum, 0, false); + if ( topImageProxy == nullptr ) { + diag.error("can't find slice matching dyld cache in %s", imagePath.c_str()); + return nullptr; + } + dlopenGroupProxy._images.push_back(topImageProxy); + dlopenGroupProxy._pathToProxy[imagePath] = topImageProxy; + + // add all dylibs needed by dylib and are not in dyld cache + topImageProxy->addDependentsDeep(dlopenGroupProxy, nullptr, false); + if ( topImageProxy->diagnostics().hasError() ) { + diag.copy(topImageProxy->diagnostics()); + return nullptr; + } + + const BinaryImageGroupData* result = dlopenGroupProxy.makeImageGroupBinary(diag); + + return result; +} + + +// used when building dyld shared cache +BinaryClosureData* ImageProxyGroup::makeClosure(Diagnostics& diag, const DyldCacheParser& dyldCache, ImageProxyGroup* cachedDylibsGroup, + ImageProxyGroup* otherOsDylibs, const DyldSharedCache::MappedMachO& mainProgMapping, + bool inodesAreSameAsRuntime, const std::vector& buildTimePrefixes) +{ + // _basedOn can not be set until ImageGroup is built + if ( cachedDylibsGroup->_basedOn == nullptr ) { + cachedDylibsGroup->_basedOn = dyldCache.cachedDylibsGroup(); + } + const BinaryImageGroupData* cachedDylibsGroupData = dyldCache.cachedDylibsGroup(); + const BinaryImageGroupData* otherDylibsGroupData = dyldCache.otherDylibsGroup(); + std::vector existingGroups = { cachedDylibsGroupData, otherDylibsGroupData }; + std::vector emptyEnvVars; // Note: this method only used when constructing dyld cache where envs are not used + ImageProxyGroup mainClosureGroupProxy(2, dyldCache, nullptr, otherOsDylibs, mainProgMapping.runtimePath, existingGroups, buildTimePrefixes, + emptyEnvVars, false, true, inodesAreSameAsRuntime); + + ImageProxy* mainProxy = new ImageProxy(mainProgMapping, 2, 0, false); + if ( mainProxy == nullptr ) { + diag.error("can't find slice matching dyld cache in %s", mainProgMapping.runtimePath.c_str()); + return nullptr; + } + mainClosureGroupProxy._images.push_back(mainProxy); + mainClosureGroupProxy._pathToProxy[mainProgMapping.runtimePath] = mainProxy; + + return mainClosureGroupProxy.makeClosureBinary(diag, mainProxy, false); +} + + +bool ImageProxyGroup::addInsertedDylibs(Diagnostics& diag) +{ + __block bool success = true; + _pathOverrides.forEachInsertedDylib(^(const char* dylibPath) { + ImageProxy* insertProxy = findAbsoluteImage(diag, dylibPath, false, true); + if ( insertProxy == nullptr ) + success = false; + }); + return success; +} + +static DyldCacheParser findDyldCache(Diagnostics& diag, const ClosureBuffer::CacheIdent& cacheIdent, task_t requestor, bool* dealloc) +{ + *dealloc = false; +#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) + size_t currentCacheSize; + const DyldSharedCache* currentCache = (const DyldSharedCache*)_dyld_get_shared_cache_range(¤tCacheSize); + if ( currentCache != nullptr ) { + uuid_t currentCacheUUID; + currentCache->getUUID(currentCacheUUID); + if ( memcmp(currentCacheUUID, cacheIdent.cacheUUID, 16) == 0 ) + return DyldCacheParser((const DyldSharedCache*)currentCache, false); + } +#endif + if ( requestor == mach_task_self() ) { + // handle dyld_closure_util case where -cache_file option maps raw cache file into this process + const DyldSharedCache* altCache = (DyldSharedCache*)cacheIdent.cacheAddress; + uuid_t altCacheUUID; + altCache->getUUID(altCacheUUID); + if ( memcmp(altCacheUUID, cacheIdent.cacheUUID, 16) == 0 ) + return DyldCacheParser(altCache, true); // only one cache can be mapped into process, so this must be raw + else + diag.error("dyld cache uuid has changed"); + } +#if BUILDING_CLOSURED + else { + // handle case where requestor to closured is running with a different dyld cache that closured + uint8_t cacheBuffer[4096]; + mach_vm_size_t actualReadSize = sizeof(cacheBuffer); + kern_return_t r; + r = mach_vm_read_overwrite(requestor, cacheIdent.cacheAddress, sizeof(cacheBuffer), (vm_address_t)&cacheBuffer, &actualReadSize); + if ( r != KERN_SUCCESS ) { + diag.error("unable to read cache header from requesting process (addr=0x%llX), kern err=%d", cacheIdent.cacheAddress, r); + return DyldCacheParser(nullptr, false); + } + const dyld_cache_header* header = (dyld_cache_header*)cacheBuffer; + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(cacheBuffer + header->mappingOffset); + vm_address_t bufferAddress = 0; + r = vm_allocate(mach_task_self(), &bufferAddress, (long)cacheIdent.cacheMappedSize, VM_FLAGS_ANYWHERE); + if ( r != KERN_SUCCESS ) { + diag.error("unable to allocate space to copy custom dyld cache (size=0x%llX), kern err=%d", cacheIdent.cacheMappedSize, r); + return DyldCacheParser(nullptr, false); + } + uint64_t slide = cacheIdent.cacheAddress - mappings[0].address; + for (int i=0; i < 3; ++i) { + mach_vm_address_t mappedAddress = bufferAddress + (mappings[i].address - mappings[0].address); + mach_vm_size_t mappedSize = mappings[i].size; + vm_prot_t curProt = VM_PROT_READ; + vm_prot_t maxProt = VM_PROT_READ; + r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, + requestor, mappings[i].address+slide, true, &curProt, &maxProt, VM_INHERIT_NONE); + if ( r != KERN_SUCCESS ) { + diag.error("unable to mach_vm_remap region %d custom dyld cache (request addr=0x%llX, size=0x%llX), kern err=%d, localBuffer=0x%lX, localMapTarget=0x%llX", + i, mappings[i].address+slide, mappedSize, r, (long)bufferAddress, mappedAddress); + return DyldCacheParser(nullptr, false); + } + if ( curProt != VM_PROT_READ ) + vm_protect(mach_task_self(), (long)mappedAddress, (long)mappedSize, false, VM_PROT_READ); + } + *dealloc = true; + return DyldCacheParser((DyldSharedCache*)bufferAddress, false); // assumes cache in other process is mapped as three regions + } +#endif + return DyldCacheParser(nullptr, false); +} + +BinaryClosureData* ImageProxyGroup::makeClosure(Diagnostics& diag, const ClosureBuffer& buffer, task_t requestor, const std::vector& buildTimePrefixes) +{ + // unpack buffer + bool deallocCacheCopy; + DyldCacheParser dyldCache = findDyldCache(diag, buffer.cacheIndent(), requestor, &deallocCacheCopy); + if ( diag.hasError() ) + return nullptr; + const char* mainProg = buffer.targetPath(); + std::vector envVars; + int envCount = buffer.envVarCount(); + const char* envVarCStrings[envCount]; + buffer.copyImageGroups(envVarCStrings); + for (int i=0; i < envCount; ++i) { + envVars.push_back(envVarCStrings[i]); + } + + // make ImageProxyGroups: 0, 1, 2 + const BinaryImageGroupData* cachedDylibsGroupData = dyldCache.cachedDylibsGroup(); + const BinaryImageGroupData* otherDylibsGroupData = dyldCache.otherDylibsGroup(); + std::vector realBuildTimePrefixes; + for (const std::string& prefix : buildTimePrefixes) { + char resolvedPath[PATH_MAX]; + if ( realpath(prefix.c_str(), resolvedPath) != nullptr ) + realBuildTimePrefixes.push_back(resolvedPath); + else + realBuildTimePrefixes.push_back(prefix); + } + std::vector existingGroups = { cachedDylibsGroupData, otherDylibsGroupData }; + ImageProxyGroup dyldCacheDylibProxyGroup(0, dyldCache, cachedDylibsGroupData, nullptr, "", existingGroups, realBuildTimePrefixes, envVars); + ImageProxyGroup dyldCacheOtherProxyGroup(1, dyldCache, otherDylibsGroupData, &dyldCacheDylibProxyGroup, "", existingGroups, realBuildTimePrefixes, envVars); + ImageProxyGroup mainClosureGroupProxy( 2, dyldCache, nullptr, &dyldCacheOtherProxyGroup, mainProg, existingGroups, realBuildTimePrefixes, envVars, false, true, true); + + // add any DYLD_INSERTED_LIBRARIES then main program into closure + BinaryClosureData* result = nullptr; + if ( mainClosureGroupProxy.addInsertedDylibs(diag) ) { + ImageProxy* proxy = mainClosureGroupProxy.findAbsoluteImage(diag, mainProg, false, true); + if ( proxy != nullptr ) { + // build closure + result = mainClosureGroupProxy.makeClosureBinary(diag, proxy, false); + } + } + + // if client has a different cache, unmap our copy + if ( deallocCacheCopy ) + vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize); + + return result; +} + +ClosureBuffer closured_CreateImageGroup(const ClosureBuffer& input) +{ + Diagnostics diag; + const BinaryImageGroupData* newGroup = ImageProxyGroup::makeDlopenGroup(diag, input, mach_task_self(), {""}); + + if ( diag.noError() ) { + // on success return the ImageGroup binary in the ClosureBuffer + dyld3::ClosureBuffer result(newGroup); + free((void*)newGroup); + return result; + } + else { + // on failure return the error message in the ClosureBuffer + dyld3::ClosureBuffer err(diag.errorMessage().c_str()); + return err; + } +} + +const BinaryImageGroupData* ImageProxyGroup::makeDlopenGroup(Diagnostics& diag, const ClosureBuffer& buffer, task_t requestor, const std::vector& buildTimePrefixes) +{ + // unpack buffer + bool deallocCacheCopy; + DyldCacheParser dyldCache = findDyldCache(diag, buffer.cacheIndent(), requestor, &deallocCacheCopy); + if ( diag.hasError() ) + return nullptr; + + const char* targetDylib = buffer.targetPath(); + std::vector envVars; + int envCount = buffer.envVarCount(); + const char* envVarCStrings[envCount]; + buffer.copyImageGroups(envVarCStrings); + for (int i=0; i < envCount; ++i) { + envVars.push_back(envVarCStrings[i]); + } + uint32_t groupCount = buffer.imageGroupCount() + 2; + const launch_cache::BinaryImageGroupData* groupDataPtrs[groupCount]; + groupDataPtrs[0] = dyldCache.cachedDylibsGroup(); + groupDataPtrs[1] = dyldCache.otherDylibsGroup(); + buffer.copyImageGroups(&groupDataPtrs[2]); + + // build an ImageProxyGroup for each existing group, and one for new group being constructed + std::vector existingGroups; + std::vector> proxies; + ImageProxyGroup* prevProxy = nullptr; + for (uint32_t i=0; i < groupCount; ++i) { + const launch_cache::BinaryImageGroupData* groupData = groupDataPtrs[i]; + existingGroups.push_back(groupData); + launch_cache::ImageGroup group(groupData); + uint32_t groupNum = group.groupNum(); + assert(groupNum == proxies.size()); + proxies.emplace_back(new ImageProxyGroup(groupNum, dyldCache, groupData, prevProxy, "", existingGroups, buildTimePrefixes, envVars)); + prevProxy = proxies.back().get(); + } + ImageProxyGroup dlopenGroupProxy(groupCount, dyldCache, nullptr, prevProxy, targetDylib, existingGroups, buildTimePrefixes, envVars); + + // find and mmap() top level dylib + DyldSharedCache::MappedMachO* topMapping = dlopenGroupProxy.addMappingIfValidMachO(diag, targetDylib, true); + if ( topMapping == nullptr ) { + std::string allWarnings; + for (const std::string& warn : diag.warnings()) { + if ( allWarnings.empty() ) + allWarnings = warn; + else + allWarnings = allWarnings + ", " + warn; + } + diag.clearWarnings(); + diag.error("%s", allWarnings.c_str()); + if ( deallocCacheCopy ) + vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize); + return nullptr; + } + + // make ImageProxy for top level dylib + ImageProxy* topImageProxy = new ImageProxy(*topMapping, groupCount, 0, false); + if ( topImageProxy == nullptr ) { + diag.error("can't find slice matching dyld cache in %s", targetDylib); + if ( deallocCacheCopy ) + vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize); + return nullptr; + } + dlopenGroupProxy._images.push_back(topImageProxy); + dlopenGroupProxy._pathToProxy[targetDylib] = topImageProxy; + + // add all dylibs needed by dylib and are not in dyld cache + topImageProxy->addDependentsDeep(dlopenGroupProxy, nullptr, false); + if ( topImageProxy->diagnostics().hasError() ) { + diag.copy(topImageProxy->diagnostics()); + if ( deallocCacheCopy ) + vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize); + return nullptr; + } + + // construct ImageGroup from ImageProxies + const BinaryImageGroupData* result = dlopenGroupProxy.makeImageGroupBinary(diag); + + // clean up + if ( deallocCacheCopy ) + vm_deallocate(mach_task_self(), (vm_address_t)dyldCache.cacheHeader(), (long)buffer.cacheIndent().cacheMappedSize); + + return result; +} + + + + +// Used by closured and dyld_closure_util +BinaryClosureData* ImageProxyGroup::makeClosure(Diagnostics& diag, const DyldCacheParser& dyldCache, + const std::string& mainProg, bool includeDylibsInDir, + const std::vector& buildTimePrefixes, + const std::vector& envVars) +{ + const BinaryImageGroupData* cachedDylibsGroupData = dyldCache.cachedDylibsGroup(); + const BinaryImageGroupData* otherDylibsGroupData = dyldCache.otherDylibsGroup(); + std::vector realBuildTimePrefixes; + for (const std::string& prefix : buildTimePrefixes) { + char resolvedPath[PATH_MAX]; + if ( realpath(prefix.c_str(), resolvedPath) != nullptr ) + realBuildTimePrefixes.push_back(resolvedPath); + else + realBuildTimePrefixes.push_back(prefix); + } + std::vector existingGroups = { cachedDylibsGroupData, otherDylibsGroupData }; + ImageProxyGroup dyldCacheDylibProxyGroup(0, dyldCache, cachedDylibsGroupData, nullptr, "", existingGroups, realBuildTimePrefixes, envVars); + ImageProxyGroup dyldCacheOtherProxyGroup(1, dyldCache, otherDylibsGroupData, &dyldCacheDylibProxyGroup, "", existingGroups, realBuildTimePrefixes, envVars); + ImageProxyGroup mainClosureGroupProxy( 2, dyldCache, nullptr, &dyldCacheOtherProxyGroup, mainProg, existingGroups, realBuildTimePrefixes, envVars, false, true, true); + + // add any DYLD_INSERTED_LIBRARIES into closure + if ( !mainClosureGroupProxy.addInsertedDylibs(diag) ) + return nullptr; + + ImageProxy* proxy = mainClosureGroupProxy.findAbsoluteImage(diag, mainProg, false, true); + if ( proxy == nullptr ) + return nullptr; + + return mainClosureGroupProxy.makeClosureBinary(diag, proxy, includeDylibsInDir); +} + +const char* sSkipPrograms_macOS[] = { + "/Applications/iBooks.app/Contents/MacOS/iBooks", +}; + +const char* sSkipPrograms_embeddedOSes[] = { + "/sbin/launchd", + "/usr/local/sbin/launchd.debug", + "/usr/local/sbin/launchd.development" +}; + +BinaryClosureData* ImageProxyGroup::makeClosureBinary(Diagnostics& diag, ImageProxy* mainProgProxy, bool includeDylibsInDir) +{ + assert(mainProgProxy != nullptr); + assert(_images.size() >= 1); + + // check black list + if ( _platform == Platform::macOS ) { + for (const char* skipProg : sSkipPrograms_macOS) { + if ( mainProgProxy->runtimePath() == skipProg ) { + diag.error("black listed program"); + return nullptr; + } + } + } else { + for (const char* skipProg : sSkipPrograms_embeddedOSes) { + if ( mainProgProxy->runtimePath() == skipProg ) { + diag.error("black listed program"); + return nullptr; + } + } + } + + _mainExecutableIndex = (uint32_t)_images.size() - 1; + // add all dylibs needed by main excutable and are not in dyld cache + mainProgProxy->addDependentsDeep(*this, nullptr, true); + if ( mainProgProxy->diagnostics().hasError() ) { + diag.copy(mainProgProxy->diagnostics()); + return nullptr; + } + + // if main program is in .app bundle, look for other mach-o files to add to closure for use by dlopen + bool isAppMainExecutable = false; + std::string appDir; + std::string leafName = basePath(mainProgProxy->runtimePath()); + size_t posAppX = mainProgProxy->runtimePath().rfind(std::string("/") + leafName + ".appex/"); + size_t posApp = mainProgProxy->runtimePath().rfind(std::string("/") + leafName + ".app/"); + if ( posAppX != std::string::npos ) { + appDir = mainProgProxy->runtimePath().substr(0, posAppX+leafName.size()+7); + isAppMainExecutable = true; + } + else if ( posApp != std::string::npos ) { + appDir = mainProgProxy->runtimePath().substr(0, posApp+leafName.size()+5); + isAppMainExecutable = true; + } + if ( isAppMainExecutable ) { + addExtraMachOsInBundle(appDir); + for (size_t i=0; i < _images.size(); ++i) { + // note: addDependentsDeep() can append to _images, so can't use regular iterator + ImageProxy* aProxy = _images[i]; + ImageProxy::RPathChain base = { aProxy, nullptr, mainProgProxy->rpaths() }; + aProxy->addDependentsDeep(*this, &base, false); + if ( aProxy->diagnostics().hasError() ) { + aProxy->markInvalid(); + diag.warning("%s could not be added to closure because %s", aProxy->runtimePath().c_str(), aProxy->diagnostics().errorMessage().c_str()); + } + } + } + else if ( includeDylibsInDir ) { + size_t pos = mainProgProxy->runtimePath().rfind('/'); + if ( pos != std::string::npos ) { + std::string mainDir = mainProgProxy->runtimePath().substr(0, pos); + addExtraMachOsInBundle(mainDir); + for (size_t i=0; i < _images.size(); ++i) { + // note: addDependentsDeep() can append to _images, so can't use regular iterator + ImageProxy* aProxy = _images[i]; + aProxy->addDependentsDeep(*this, nullptr, false); + } + } + } + + // add addition dependents of any inserted libraries + if ( _mainExecutableIndex != 0 ) { + for (uint32_t i=0; i < _mainExecutableIndex; ++i) { + _images[i]->addDependentsDeep(*this, nullptr, true); + if ( _images[i]->diagnostics().hasError() ) + return nullptr; + } + } + + // gather warnings from all statically dependent images + for (ImageProxy* proxy : _images) { + if ( !proxy->staticallyReferenced() && proxy->diagnostics().hasError() ) + continue; + diag.copy(proxy->diagnostics()); + if ( diag.hasError() ) { + return nullptr; + } + } + + // get program entry + MachOParser mainExecutableParser(mainProgProxy->mh(), _dyldCache.cacheIsMappedRaw()); + bool usesCRT; + uint32_t entryOffset; + mainExecutableParser.getEntry(entryOffset, usesCRT); + + // build ImageGroupWriter + launch_cache::ImageGroupWriter groupWriter(_groupNum, mainExecutableParser.uses16KPages(), mainExecutableParser.is64(), _dylibsExpectedOnDisk, _inodesAreSameAsRuntime); + populateGroupWriter(diag, groupWriter); + if ( diag.hasError() ) + return nullptr; + + // pre-compute libSystem and libdyld into closure + ImageRef libdyldEntryImageRef = ImageRef::makeEmptyImageRef(); + uint32_t libdyldEntryOffset; + findLibdyldEntry(diag, libdyldEntryImageRef, libdyldEntryOffset); + if ( diag.hasError() ) + return nullptr; + ImageRef libSystemImageRef = ImageRef::makeEmptyImageRef(); + + findLibSystem(diag, mainExecutableParser.isSimulatorBinary(), libSystemImageRef); + if ( diag.hasError() ) + return nullptr; + + // build info about missing files and env vars + __block StringPool stringPool; + __block std::vector envVarOffsets; + std::vector missingFileComponentOffsets; + stringPool.add(" "); + for (const std::string& path : _mustBeMissingFiles) { + size_t start = 1; + size_t slashPos = path.find('/', start); + while (slashPos != std::string::npos) { + std::string component = path.substr(start, slashPos - start); + uint16_t offset = stringPool.add(component); + missingFileComponentOffsets.push_back(offset); + start = slashPos + 1; + slashPos = path.find('/', start); + } + std::string lastComponent = path.substr(start); + uint16_t offset = stringPool.add(lastComponent); + missingFileComponentOffsets.push_back(offset); + missingFileComponentOffsets.push_back(0); // mark end of a path + } + missingFileComponentOffsets.push_back(0); // mark end of all paths + if ( missingFileComponentOffsets.size() & 1 ) + missingFileComponentOffsets.push_back(0); // 4-byte align array + __block uint32_t envVarCount = 0; + _pathOverrides.forEachEnvVar(^(const char* envVar) { + envVarOffsets.push_back(stringPool.add(envVar)); + ++envVarCount; + }); + + // 4-byte align string pool size + stringPool.align(); + + // malloc a buffer and fill in ImageGroup part + uint32_t groupSize = groupWriter.size(); + uint32_t missingFilesArraySize = (uint32_t)((missingFileComponentOffsets.size()*sizeof(uint16_t) + 3) & (-4)); + uint32_t envVarsSize = (uint32_t)(envVarOffsets.size()*sizeof(uint32_t)); + uint32_t stringPoolSize = (uint32_t)stringPool.size(); + size_t allocSize = sizeof(launch_cache::binary_format::Closure) + + groupSize + + missingFilesArraySize + + envVarsSize + + stringPoolSize; + BinaryClosureData* clo = (BinaryClosureData*)malloc(allocSize); + groupWriter.finalizeTo(diag, _knownGroups, &clo->group); + launch_cache::ImageGroup cloGroup(&clo->group); + launch_cache::Image mainImage(cloGroup.imageBinary(_mainExecutableIndex)); + + uint32_t maxImageLoadCount = groupWriter.maxLoadCount(diag, _knownGroups, &clo->group); + + if ( mainImage.isInvalid() ) { + free((void*)clo); + diag.error("depends on invalid dylib"); + return nullptr; + } + + // fill in closure attributes + clo->magic = launch_cache::binary_format::Closure::magicV1; + clo->usesCRT = usesCRT; + clo->isRestricted = mainProgProxy->isSetUID() || mainExecutableParser.isRestricted(); + clo->usesLibraryValidation = mainExecutableParser.usesLibraryValidation(); + clo->padding = 0; + clo->missingFileComponentsOffset = offsetof(launch_cache::binary_format::Closure, group) + groupSize; + clo->dyldEnvVarsOffset = clo->missingFileComponentsOffset + missingFilesArraySize; + clo->dyldEnvVarsCount = envVarCount; + clo->stringPoolOffset = clo->dyldEnvVarsOffset + envVarsSize; + clo->stringPoolSize = stringPoolSize; + clo->libSystemRef = libSystemImageRef; + clo->libDyldRef = libdyldEntryImageRef; + clo->libdyldVectorOffset = libdyldEntryOffset; + clo->mainExecutableIndexInGroup = _mainExecutableIndex; + clo->mainExecutableEntryOffset = entryOffset; + clo->initialImageCount = maxImageLoadCount; + _dyldCache.cacheHeader()->getUUID(clo->dyldCacheUUID); + + if ( !mainExecutableParser.getCDHash(clo->mainExecutableCdHash) ) { + // if no code signature, fill in 16-bytes with UUID then 4 bytes of zero + bzero(clo->mainExecutableCdHash, 20); + mainExecutableParser.getUuid(clo->mainExecutableCdHash); + } + if ( missingFilesArraySize != 0 ) + memcpy((uint8_t*)clo + clo->missingFileComponentsOffset, &missingFileComponentOffsets[0], missingFileComponentOffsets.size()*sizeof(uint16_t)); + if ( envVarsSize != 0 ) + memcpy((uint8_t*)clo + clo->dyldEnvVarsOffset, &envVarOffsets[0], envVarsSize); + if ( stringPool.size() != 0 ) + memcpy((uint8_t*)clo + clo->stringPoolOffset, stringPool.buffer(), stringPool.size()); + + return clo; +} + +const BinaryImageGroupData* ImageProxyGroup::makeImageGroupBinary(Diagnostics& diag, const char* const neverEliminateStubs[]) +{ + const bool continueIfErrors = (_groupNum == 1); + bool uses16KPages = true; + bool is64 = true; + if ( !_images.empty() ) { + MachOParser firstParser(_images.front()->mh(), _dyldCache.cacheIsMappedRaw()); + uses16KPages = firstParser.uses16KPages(); + is64 = firstParser.is64(); + } + launch_cache::ImageGroupWriter groupWriter(_groupNum, uses16KPages, is64, _dylibsExpectedOnDisk, _inodesAreSameAsRuntime); + populateGroupWriter(diag, groupWriter, neverEliminateStubs); + if ( diag.hasError() ) + return nullptr; + + // malloc a buffer and fill in ImageGroup part + BinaryImageGroupData* groupData = (BinaryImageGroupData*)malloc(groupWriter.size()); + groupWriter.finalizeTo(diag, _knownGroups, groupData); + + if ( !continueIfErrors && groupWriter.isInvalid(0) ) { + free((void*)groupData); + diag.error("depends on invalid dylib"); + return nullptr; + } + + return groupData; +} + + +void ImageProxyGroup::findLibdyldEntry(Diagnostics& diag, ImageRef& ref, uint32_t& vmOffsetInLibDyld) +{ + Diagnostics libDyldDiag; + ImageProxy* libDyldProxy = findImage(libDyldDiag, "/usr/lib/system/libdyld.dylib", false, nullptr); + if ( libDyldProxy == nullptr ) { + diag.error("can't find libdyld.dylib"); + return; + } + ref = ImageRef(0, libDyldProxy->groupNum(), libDyldProxy->indexInGroup()); + + // find offset of "dyld3::entryVectorForDyld" in libdyld.dylib + Diagnostics entryDiag; + MachOParser::FoundSymbol dyldEntryInfo; + MachOParser libDyldParser(libDyldProxy->mh(), _dyldCache.cacheIsMappedRaw()); + if ( !libDyldParser.findExportedSymbol(entryDiag, "__ZN5dyld318entryVectorForDyldE", nullptr, dyldEntryInfo, nullptr) ) { + diag.error("can't find dyld entry point into libdyld.dylib"); + return; + } + vmOffsetInLibDyld = (uint32_t)dyldEntryInfo.value; + const LibDyldEntryVector* entry = (LibDyldEntryVector*)(libDyldParser.content(vmOffsetInLibDyld)); + if ( entry == nullptr ) { + diag.error("dyld entry point at offset 0x%0X not found in libdyld.dylib", vmOffsetInLibDyld); + return; + } + if ( entry->vectorVersion != LibDyldEntryVector::kCurrentVectorVersion ) + diag.error("libdyld.dylib vector version is incompatible with this dyld cache builder"); + else if ( entry->binaryFormatVersion != launch_cache::binary_format::kFormatVersion ) + diag.error("libdyld.dylib closures binary format version is incompatible with this dyld cache builder"); +} + +void ImageProxyGroup::findLibSystem(Diagnostics& diag, bool forSimulator, ImageRef& ref) +{ + Diagnostics libSysDiag; + ImageProxy* libSystemProxy = findImage(libSysDiag, forSimulator ? "/usr/lib/libSystem.dylib" : "/usr/lib/libSystem.B.dylib" , false, nullptr); + if ( libSystemProxy == nullptr ) { + diag.error("can't find libSystem.dylib"); + return; + } + ref = ImageRef(0, libSystemProxy->groupNum(), libSystemProxy->indexInGroup()); +} + + +std::vector ImageProxyGroup::flatLookupOrder() +{ + std::vector results; + // start with main executable and any inserted dylibs + for (uint32_t i=0; i <= _mainExecutableIndex; ++i) + results.push_back(_images[i]); + + // recursive add dependents of main executable + _images[_mainExecutableIndex]->addToFlatLookup(results); + + // recursive add dependents of any inserted dylibs + for (uint32_t i=0; i < _mainExecutableIndex; ++i) + _images[i]->addToFlatLookup(results); + + return results; +} + +void ImageProxyGroup::populateGroupWriter(Diagnostics& diag, launch_cache::ImageGroupWriter& groupWriter, const char* const neverEliminateStubs[]) +{ + const bool buildingDylibsInCache = (_groupNum == 0); + const bool continueIfErrors = (_groupNum == 1); + + std::unordered_set neverStubEliminate; + if ( neverEliminateStubs != nullptr ) { + for (const char* const* nes=neverEliminateStubs; *nes != nullptr; ++nes) + neverStubEliminate.insert(*nes); + } + + // pass 1: add all images + const uint64_t cacheUnslideBaseAddress = _dyldCache.cacheHeader()->unslidLoadAddress(); + const uint32_t imageCount = (uint32_t)_images.size(); + groupWriter.setImageCount(imageCount); + for (uint32_t i=0; i < imageCount; ++i) { + MachOParser imageParser(_images[i]->mh(), _dyldCache.cacheIsMappedRaw()); + assert((imageParser.inDyldCache() == buildingDylibsInCache) && "all images must be same type"); + // add info for each image + groupWriter.setImagePath(i, _images[i]->runtimePath().c_str()); + groupWriter.setImageIsBundle(i, (imageParser.fileType() == MH_BUNDLE)); + bool hasObjC = imageParser.hasObjC(); + groupWriter.setImageHasObjC(i, hasObjC); + bool isEncrypted = imageParser.isEncrypted(); + groupWriter.setImageIsEncrypted(i, isEncrypted); + bool mayHavePlusLoad = false; + if ( hasObjC ) { + mayHavePlusLoad = isEncrypted || imageParser.hasPlusLoadMethod(diag); + groupWriter.setImageMayHavePlusLoads(i, mayHavePlusLoad); + } + groupWriter.setImageHasWeakDefs(i, imageParser.hasWeakDefs()); + groupWriter.setImageMustBeThisDir(i, _images[i]->cwdMustBeThisDir()); + groupWriter.setImageIsPlatformBinary(i, _images[i]->isPlatformBinary()); + groupWriter.setImageOverridableDylib(i, !_stubEliminated || (neverStubEliminate.count(_images[i]->runtimePath()) != 0)); + uuid_t uuid; + if ( imageParser.getUuid(uuid) ) + groupWriter.setImageUUID(i, uuid); + if ( _inodesAreSameAsRuntime ) { + groupWriter.setImageFileMtimeAndInode(i, _images[i]->fileModTime(), _images[i]->fileInode()); + } + else { + uint8_t cdHash[20]; + if ( !imageParser.getCDHash(cdHash) ) + bzero(cdHash, 20); + // if image is not code signed, cdHash filled with all zeros + groupWriter.setImageCdHash(i, cdHash); + } + if ( !buildingDylibsInCache ) { + groupWriter.setImageSliceOffset(i, _images[i]->sliceFileOffset()); + uint32_t fairPlayTextOffset; + uint32_t fairPlaySize; + if ( imageParser.isFairPlayEncrypted(fairPlayTextOffset, fairPlaySize) ) + groupWriter.setImageFairPlayRange(i, fairPlayTextOffset, fairPlaySize); + uint32_t codeSigOffset; + uint32_t codeSigSize; + if ( imageParser.hasCodeSignature(codeSigOffset, codeSigSize) ) + groupWriter.setImageCodeSignatureLocation(i, codeSigOffset, codeSigSize); + } + groupWriter.setImageDependentsCount(i, imageParser.dependentDylibCount()); + // add segments to image + groupWriter.setImageSegments(i, imageParser, cacheUnslideBaseAddress); + // add initializers to image + __block std::vector initOffsets; + imageParser.forEachInitializer(diag, ^(uint32_t offset) { + initOffsets.push_back(offset); + }); + groupWriter.setImageInitializerOffsets(i, initOffsets); + if ( diag.hasError() && !continueIfErrors ) { + return; + } + // add DOFs to image + __block std::vector dofOffsets; + imageParser.forEachDOFSection(diag, ^(uint32_t offset) { + dofOffsets.push_back(offset); + }); + groupWriter.setImageDOFOffsets(i, dofOffsets); + if ( diag.hasError() && !continueIfErrors ) { + return; + } + bool neverUnload = false; + if ( buildingDylibsInCache ) + neverUnload = true; + if ( _images[i]->staticallyReferenced() ) + neverUnload = true; + if ( imageParser.hasObjC() && (imageParser.fileType() == MH_DYLIB) ) + neverUnload = true; + if ( imageParser.hasThreadLocalVariables() ) + neverUnload = true; + if ( !dofOffsets.empty() ) + neverUnload = true; + groupWriter.setImageNeverUnload(i, neverUnload); + if ( _images[i]->invalid() ) + groupWriter.setImageInvalid(i); + // record if this is an override of an OS dylib + ImageRef stdRef = _images[i]->overrideOf(); + if ( stdRef != ImageRef::weakImportMissing() ) { + ImageRef thisImageRef(0, _groupNum, i); + groupWriter.addImageIsOverride(stdRef, thisImageRef); + } + + // add alias if runtimepath does not match installName + if ( imageParser.fileType() == MH_DYLIB ) { + const char* installName = imageParser.installName(); + if ( installName[0] == '/' ) { + if ( _images[i]->runtimePath() != installName ) { + // add install name as an alias + groupWriter.addImageAliasPath(i, installName); + } + } + // IOKit.framework on embedded uses not flat bundle, but clients dlopen() it as if it were flat + if ( buildingDylibsInCache && (_platform != Platform::macOS) && (_images[i]->runtimePath() == "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit") ) { + groupWriter.addImageAliasPath(i, "/System/Library/Frameworks/IOKit.framework/IOKit"); + } + } + } + + // pass 2: add all dependencies (now that we have indexes defined) + for (uint32_t i=0; (i < imageCount) && diag.noError(); ++i) { + // add dependents to image + __block uint32_t depIndex = 0; + _images[i]->forEachDependent(^(ImageProxy* dep, LinkKind kind) { + if ( dep == nullptr ) { + if ( kind == LinkKind::weak ) + groupWriter.setImageDependent(i, depIndex, launch_cache::binary_format::ImageRef::weakImportMissing()); + else + groupWriter.setImageInvalid(i); + } + else { + launch_cache::binary_format::ImageRef ref((uint8_t)kind, dep->groupNum(), dep->indexInGroup()); + groupWriter.setImageDependent(i, depIndex, ref); + } + ++depIndex; + }); + } + + // pass 3: invalidate any images dependent on invalid images) + if ( continueIfErrors ) { + const launch_cache::binary_format::ImageRef missingRef = launch_cache::binary_format::ImageRef::weakImportMissing(); + __block bool somethingInvalidated = false; + do { + somethingInvalidated = false; + for (uint32_t i=0; i < imageCount; ++i) { + if ( groupWriter.isInvalid(i) ) + continue; + uint32_t depCount = groupWriter.imageDependentsCount(i); + for (uint32_t depIndex=0; depIndex < depCount; ++depIndex) { + launch_cache::binary_format::ImageRef ref = groupWriter.imageDependent(i, depIndex); + if ( ref == missingRef ) + continue; + if ( ref.groupNum() == _groupNum ) { + if ( groupWriter.isInvalid(ref.indexInGroup()) ) { + // this image depends on something invalid, so mark it invalid + //fprintf(stderr, "warning: image %s depends on invalid %s\n", _images[i]->runtimePath().c_str(), _images[ref.index()]->runtimePath().c_str()); + groupWriter.setImageInvalid(i); + somethingInvalidated = true; + break; + } + } + } + } + } while (somethingInvalidated); + } + + // pass 4: add fixups for each image, if needed + bool someBadFixups = false; + if ( !buildingDylibsInCache ) { + // compute fix ups for all images + __block std::vector fixupInfos; + fixupInfos.resize(imageCount); + for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) { + if ( groupWriter.isInvalid(imageIndex) ) + continue; + Diagnostics fixupDiag; + fixupInfos[imageIndex] = _images[imageIndex]->buildFixups(fixupDiag, cacheUnslideBaseAddress, groupWriter); + if ( fixupDiag.hasError() ) { + // disable image in group + someBadFixups = true; + groupWriter.setImageInvalid(imageIndex); + if ( continueIfErrors ) { + diag.warning("fixup problem in %s: %s", _images[imageIndex]->runtimePath().c_str(), fixupDiag.errorMessage().c_str()); + continue; + } + else { + diag.error("fixup problem in %s: %s", _images[imageIndex]->runtimePath().c_str(), fixupDiag.errorMessage().c_str()); + return; + } + } + } + // if building closure, build patches to shared cache + if ( _groupNum == 2) { + std::unordered_set staticImagesWithWeakDefs; + ImageProxyGroup* cacheGroup = _nextSearchGroup->_nextSearchGroup; + assert(cacheGroup->_basedOn != nullptr); + launch_cache::ImageGroup dyldCacheGroup(cacheGroup->_basedOn); + for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) { + if ( groupWriter.isInvalid(imageIndex) ) + continue; + ImageProxy* thisProxy = _images[imageIndex]; + // Only process interposing info on dylibs statically linked into closure + if ( !thisProxy->staticallyReferenced() ) + continue; + MachOParser imageParser(thisProxy->mh(), _dyldCache.cacheIsMappedRaw()); + // if any images in closure interpose on something in dyld cache, record the cache patches needed + imageParser.forEachInterposingTuple(diag, ^(uint32_t segIndex, uint64_t replacementSegOffset, uint64_t replaceeSegOffset, uint64_t replacementContent, bool& tupleStop) { + if ( _groupNum != 2 ) { + groupWriter.setImageInvalid(imageIndex); + return; + } + TargetSymbolValue interposeReplacee = TargetSymbolValue::makeInvalid(); + TargetSymbolValue interposeReplacement = TargetSymbolValue::makeInvalid(); + for (const FixUp& fixup : fixupInfos[imageIndex].fixups) { + if ( fixup.segIndex != segIndex ) + continue; + if ( fixup.segOffset == replacementSegOffset ) { + if ( fixup.type == launch_cache::ImageGroupWriter::FixupType::rebase ) { + uint64_t offsetInImage = replacementContent - imageParser.preferredLoadAddress(); + interposeReplacement = TargetSymbolValue::makeGroupValue(2, imageIndex, offsetInImage, false); + } + else { + diag.warning("bad interposing implementation in %s", _images[imageIndex]->runtimePath().c_str()); + return; + } + } + else if ( fixup.segOffset == replaceeSegOffset ) { + if ( fixup.type == launch_cache::ImageGroupWriter::FixupType::pointerBind ) { + interposeReplacee = fixup.target; + } + else { + diag.warning("bad interposing target in %s", _images[imageIndex]->runtimePath().c_str()); + return; + } + } + } + // scan through fixups of other images in closure looking to see what functions this entry references + for (uint32_t otherIndex=0; otherIndex < imageCount; ++otherIndex) { + if ( otherIndex == imageIndex ) + continue; + for (FixUp& fixup : fixupInfos[otherIndex].fixups) { + switch ( fixup.type ) { + case launch_cache::ImageGroupWriter::FixupType::pointerBind: + case launch_cache::ImageGroupWriter::FixupType::pointerLazyBind: + // alter fixup to use interposed function instead of requested + if ( fixup.target == interposeReplacee ) + fixup.target = interposeReplacement; + break; + case launch_cache::ImageGroupWriter::FixupType::rebase: + case launch_cache::ImageGroupWriter::FixupType::rebaseText: + case launch_cache::ImageGroupWriter::FixupType::ignore: + case launch_cache::ImageGroupWriter::FixupType::bindText: + case launch_cache::ImageGroupWriter::FixupType::bindTextRel: + case launch_cache::ImageGroupWriter::FixupType::bindImportJmpRel: + break; + } + } + } + if ( interposeReplacee.isInvalid() || interposeReplacement.isInvalid() ) { + diag.error("malformed interposing section in %s", _images[imageIndex]->runtimePath().c_str()); + tupleStop = true; + return; + } + // record any overrides in shared cache that will need to be applied at launch time + uint64_t offsetInCache; + if ( interposeReplacee.isSharedCacheTarget(offsetInCache) ) { + uint32_t patchTableIndex; + if ( dyldCacheGroup.hasPatchTableIndex((uint32_t)offsetInCache, patchTableIndex) ) { + uint32_t replacementGroupNum; + uint32_t replacementIndexInGroup; + uint64_t replacementOffsetInImage; + assert(interposeReplacement.isGroupImageTarget(replacementGroupNum, replacementIndexInGroup, replacementOffsetInImage)); + assert(replacementGroupNum == 2); + assert(replacementIndexInGroup < (1 << 8)); + assert(replacementOffsetInImage < 0xFFFFFFFFULL); + DyldCacheOverride cacheOverride; + cacheOverride.patchTableIndex = patchTableIndex; + cacheOverride.imageIndex = replacementIndexInGroup; + cacheOverride.imageOffset = replacementOffsetInImage; + _cacheOverrides.push_back(cacheOverride); + } + } + }); + if ( diag.hasError() && !continueIfErrors ) { + return; + } + // if any dylibs in the closure override a dyld cache dylib, then record the cache patches needed + ImageRef overrideOf = thisProxy->overrideOf(); + if ( (overrideOf != ImageRef::makeEmptyImageRef()) && (overrideOf.groupNum() == 0) ) { + //fprintf(stderr, "need to patch %s into cache\n", thisProxy->runtimePath().c_str()); + const launch_cache::Image imageInCache = dyldCacheGroup.image(overrideOf.indexInGroup()); + const mach_header* imageInCacheMH = (mach_header*)((char*)(_dyldCache.cacheHeader()) + imageInCache.cacheOffset()); + MachOParser inCacheParser(imageInCacheMH, _dyldCache.cacheIsMappedRaw()); + // walk all exported symbols in dylib in cache + inCacheParser.forEachExportedSymbol(diag, ^(const char* symbolName, uint64_t imageOffset, bool isReExport, bool &stop) { + if ( isReExport ) + return; + uint32_t cacheOffsetOfSymbol = (uint32_t)(imageInCache.cacheOffset() + imageOffset); + //fprintf(stderr, " patch cache offset 0x%08X which is %s\n", cacheOffsetOfSymbol, symbolName); + // for each exported symbol, see if it is in patch table (used by something else in cache) + uint32_t patchTableIndex; + if ( dyldCacheGroup.hasPatchTableIndex(cacheOffsetOfSymbol, patchTableIndex) ) { + //fprintf(stderr, " need patch cache offset 0x%08X\n", cacheOffsetOfSymbol); + // lookup address of symbol in override dylib and add patch info + MachOParser::FoundSymbol foundInfo; + if ( imageParser.findExportedSymbol(diag, symbolName, nullptr, foundInfo, nullptr) ) { + DyldCacheOverride cacheOverride; + assert(patchTableIndex < (1 << 24)); + assert(thisProxy->indexInGroup() < (1 << 8)); + assert(foundInfo.value < (1ULL << 32)); + cacheOverride.patchTableIndex = patchTableIndex; + cacheOverride.imageIndex = thisProxy->indexInGroup(); + cacheOverride.imageOffset = foundInfo.value; + _cacheOverrides.push_back(cacheOverride); + } + } + }); + } + // save off all images in closure with weak defines + if ( thisProxy->mh()->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK) ) { + staticImagesWithWeakDefs.insert(thisProxy); + } + } + // if any dylibs in the closure override a weak symbol in a cached dylib, then record the cache patches needed + if ( !staticImagesWithWeakDefs.empty() ) { + // build list of all weak def symbol names + __block std::unordered_map weakSymbols; + for (ImageProxy* proxy : staticImagesWithWeakDefs ) { + MachOParser weakDefParser(proxy->mh(), _dyldCache.cacheIsMappedRaw()); + weakDefParser.forEachWeakDef(diag, ^(bool strongDef, uint32_t segIndex, uint64_t segOffset, uint64_t addend, const char* symbolName, bool& stop) { + weakSymbols[symbolName] = { 0, 0, 0 }; + }); + } + // do a flat namespace walk of all images + std::vector flatSearchOrder = flatLookupOrder(); + for (ImageProxy* proxy : flatSearchOrder) { + // only look at images that participate in weak coalescing + if ( (proxy->mh()->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK)) == 0 ) + continue; + // look only at images in closure + if ( proxy->groupNum() == 2 ) { + MachOParser weakDefParser(proxy->mh(), _dyldCache.cacheIsMappedRaw()); + // check if this closure image defines any of the not-yet found weak symbols + for (auto& entry : weakSymbols ) { + if ( entry.second.imageOffset != 0 ) + continue; + Diagnostics weakDiag; + MachOParser::FoundSymbol foundInfo; + if ( weakDefParser.findExportedSymbol(weakDiag, entry.first.c_str(), nullptr, foundInfo, nullptr) ) { + assert(proxy->indexInGroup() < (1 << 8)); + assert(foundInfo.value < (1ULL << 32)); + entry.second.imageIndex = proxy->indexInGroup(); + entry.second.imageOffset = foundInfo.value; + } + } + } + } + for (ImageProxy* proxy : flatSearchOrder) { + // only look at images that participate in weak coalescing + if ( (proxy->mh()->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK)) == 0 ) + continue; + // look only at images in dyld cache + if ( proxy->groupNum() == 0 ) { + const launch_cache::Image imageInCache = dyldCacheGroup.image(proxy->indexInGroup()); + MachOParser inCacheParser(proxy->mh(), _dyldCache.cacheIsMappedRaw()); + Diagnostics cacheDiag; + for (auto& entry : weakSymbols) { + if ( entry.second.imageOffset == 0 ) + continue; + Diagnostics weakDiag; + MachOParser::FoundSymbol foundInfo; + if ( inCacheParser.findExportedSymbol(weakDiag, entry.first.c_str(), nullptr, foundInfo, nullptr) ) { + uint32_t cacheOffsetOfSymbol = (uint32_t)(imageInCache.cacheOffset() + foundInfo.value); + // see if this symbol is in patch table (used by something else in cache) + uint32_t patchTableIndex; + if ( dyldCacheGroup.hasPatchTableIndex(cacheOffsetOfSymbol, patchTableIndex) ) { + //fprintf(stderr, " need patch cache offset 0x%08X\n", cacheOffsetOfSymbol); + DyldCacheOverride cacheOverride; + cacheOverride.patchTableIndex = patchTableIndex; + cacheOverride.imageIndex = entry.second.imageIndex; + cacheOverride.imageOffset = entry.second.imageOffset; + _cacheOverrides.push_back(cacheOverride); + } + } + } + } + } + } + } + // record fixups for each image + for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) { + groupWriter.setImageFixups(diag, imageIndex, fixupInfos[imageIndex].fixups, fixupInfos[imageIndex].hasTextRelocs); + } + } + + // pass 5: invalidate any images dependent on invalid images) + if ( someBadFixups && continueIfErrors ) { + __block bool somethingInvalidated = false; + do { + somethingInvalidated = false; + for (uint32_t i=0; i < imageCount; ++i) { + if ( groupWriter.isInvalid(i) ) + continue; + uint32_t depCount = groupWriter.imageDependentsCount(i); + for (uint32_t depIndex=0; depIndex < depCount; ++depIndex) { + launch_cache::binary_format::ImageRef ref = groupWriter.imageDependent(i, depIndex); + if ( ref.groupNum() == _groupNum ) { + if ( groupWriter.isInvalid(ref.indexInGroup()) ) { + // this image depends on something invalid, so mark it invalid + //fprintf(stderr, "warning: image %s depends on invalid %s\n", _images[i]->runtimePath().c_str(), _images[ref.index()]->runtimePath().c_str()); + groupWriter.setImageInvalid(i); + somethingInvalidated = true; + break; + } + } + } + } + } while (somethingInvalidated); + } + + // pass 6: compute initializer lists for each image + const bool log = false; + for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) { + if ( groupWriter.isInvalid(imageIndex) ) + continue; + + auto inits = _images[imageIndex]->getInitBeforeList(*this); + if ( log && buildingDylibsInCache ) { + fprintf(stderr, "%s\n init list: ", _images[imageIndex]->runtimePath().c_str()); + for (launch_cache::binary_format::ImageRef ref : inits) { + if ( ref.groupNum() == 0 ) { + std::string dep = _images[ref.indexInGroup()]->runtimePath(); + size_t off = dep.rfind('/'); + fprintf(stderr, "%s, ", dep.substr(off+1).c_str()); + } + } + fprintf(stderr, "\n"); + } + groupWriter.setImageInitBefore(imageIndex, inits); + } + + // pass 7: compute DOFs + for (uint32_t imageIndex=0; imageIndex < imageCount; ++imageIndex) { + if ( groupWriter.isInvalid(imageIndex) ) + continue; + + auto inits = _images[imageIndex]->getInitBeforeList(*this); + if ( log && buildingDylibsInCache ) { + fprintf(stderr, "%s\n DOFs: ", _images[imageIndex]->runtimePath().c_str()); + for (launch_cache::binary_format::ImageRef ref : inits) { + if ( ref.groupNum() == 0 ) { + std::string dep = _images[ref.indexInGroup()]->runtimePath(); + size_t off = dep.rfind('/'); + fprintf(stderr, "%s, ", dep.substr(off+1).c_str()); + } + } + fprintf(stderr, "\n"); + } + groupWriter.setImageInitBefore(imageIndex, inits); + } + + // pass 8: add patch table entries iff this is dyld cache ImageGroup + assert(buildingDylibsInCache == (_patchTable != nullptr)); + if ( _patchTable != nullptr ) { + for (uint32_t i=0; i < imageCount; ++i) { + const auto pos = _patchTable->find(_images[i]->mh()); + if ( pos != _patchTable->end() ) { + for (const auto& entry : pos->second ) { + uint32_t defFunctionOffset = entry.first; + groupWriter.setImagePatchLocations(i, defFunctionOffset, entry.second); + } + } + } + } + + // if this is a main closure group with an interposing dylib, add cache overrides + if ( !_cacheOverrides.empty() ) { + groupWriter.setGroupCacheOverrides(_cacheOverrides); + } + + // align string pool + groupWriter.alignStringPool(); +} + + + +} // namespace dyld3 + + diff --git a/dyld3/shared-cache/ImageProxy.h b/dyld3/shared-cache/ImageProxy.h new file mode 100644 index 0000000..35bf42c --- /dev/null +++ b/dyld3/shared-cache/ImageProxy.h @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2017 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 ImageProxy_h +#define ImageProxy_h + +#include + +#include +#include +#include +#include + +#include "DyldSharedCache.h" +#include "Diagnostics.h" +#include "LaunchCache.h" +#include "LaunchCacheWriter.h" +#include "PathOverrides.h" +#include "ClosureBuffer.h" +#include "DyldCacheParser.h" + + +namespace dyld3 { + +typedef launch_cache::binary_format::Image BinaryImageData; +typedef launch_cache::binary_format::ImageGroup BinaryImageGroupData; +typedef launch_cache::binary_format::Closure BinaryClosureData; +typedef launch_cache::binary_format::ImageRef ImageRef; +typedef launch_cache::Image::LinkKind LinkKind; +typedef launch_cache::ImageGroupWriter::FixUp FixUp; +typedef launch_cache::binary_format::DyldCacheOverride DyldCacheOverride; +typedef launch_cache::ImageGroupList ImageGroupList; + + + + +class ImageProxyGroup; + +class ImageProxy +{ +public: + ImageProxy(const mach_header* mh, const BinaryImageData* image, uint32_t indexInGroup, bool dyldCacheIsRaw); + ImageProxy(const DyldSharedCache::MappedMachO& mapping, uint32_t groupNum, uint32_t indexInGroup, bool dyldCacheIsRaw); + + struct RPathChain { + ImageProxy* inProxy; + const RPathChain* prev; + const std::vector& rpaths; + }; + + struct InitOrderInfo { + bool beforeHas(ImageRef); + bool upwardHas(ImageProxy*); + void removeRedundantUpwards(); + std::vector initBefore; + std::vector danglingUpward; + }; + + struct FixupInfo { + std::vector fixups; + bool hasTextRelocs = false; + }; + + void recursiveBuildInitBeforeInfo(ImageProxyGroup& owningGroup); + void addDependentsShallow(ImageProxyGroup& owningGroup, RPathChain* chain=nullptr); + void addDependentsDeep(ImageProxyGroup& owningGroup, RPathChain* chain, bool staticallyReferenced); + void markInvalid() { _invalid = true; } + + uint32_t groupNum() const { return _groupNum; } + uint32_t indexInGroup() const { return _indexInGroup; } + const mach_header* mh() const { return _mh; } + const std::string& runtimePath() const { return _runtimePath; } + uint64_t sliceFileOffset() const { return _sliceFileOffset; } + uint64_t fileModTime() const { return _modTime; } + uint64_t fileInode() const { return _inode; } + bool isSetUID() const { return _isSetUID; } + bool invalid() const { return _invalid; } + bool staticallyReferenced() const { return _staticallyReferenced; } + bool cwdMustBeThisDir() const { return _cwdMustBeThisDir; } + bool isPlatformBinary() const { return _platformBinary; } + bool isProxyForCachedDylib() const { return _imageBinaryData != nullptr; } + const Diagnostics& diagnostics() const { return _diag; } + ImageRef overrideOf() const { return _overrideOf; } + bool inLibSystem() const; + void setCwdMustBeThisDir() { _cwdMustBeThisDir = true; } + void setPlatformBinary() { _platformBinary = true; } + void setOverrideOf(uint32_t groupNum, uint32_t indexInGroup); + void checkIfImageOverride(const std::string& runtimeLoadPath); + void forEachDependent(void (^handler)(ImageProxy* dep, LinkKind)) const; + FixupInfo buildFixups(Diagnostics& diag, uint64_t cacheUnslideBaseAddress, launch_cache::ImageGroupWriter& groupWriter) const; + bool findExportedSymbol(Diagnostics& diag, const char* symbolName, MachOParser::FoundSymbol& foundInfo) const; + void convertInitBeforeInfoToArray(ImageProxyGroup& owningGroup); + void addToFlatLookup(std::vector& imageList); + const std::vector& getInitBeforeList(ImageProxyGroup& owningGroup); + const std::vector& rpaths() { return _rpaths; } + +private: + void processRPaths(ImageProxyGroup& owningGroup); + + const mach_header* const _mh; + uint64_t const _sliceFileOffset; + uint64_t const _modTime; + uint64_t const _inode; + const BinaryImageData* const _imageBinaryData; // only used if proxy is for image in shared cache + std::string const _runtimePath; + bool const _isSetUID; + bool const _dyldCacheIsRaw; + uint32_t const _groupNum; + uint32_t const _indexInGroup; + bool _platformBinary; + Diagnostics _diag; + std::vector _dependents; + std::vector _dependentsKind; + std::vector _rpaths; + InitOrderInfo _initBeforesInfo; + std::vector _initBeforesArray; + ImageRef _overrideOf; + bool _directDependentsSet; + bool _deepDependentsSet; + bool _initBeforesArraySet; + bool _initBeforesComputed; + bool _invalid; + bool _staticallyReferenced; + bool _cwdMustBeThisDir; +}; + + +class ImageProxyGroup +{ +public: + ~ImageProxyGroup(); + + + typedef std::unordered_map>> PatchTable; + + + // used when building dyld shared cache + static ImageProxyGroup* makeDyldCacheDylibsGroup(Diagnostics& diag, const DyldCacheParser& dyldCache, const std::vector& cachedDylibs, + const std::vector& buildTimePrefixes, const PatchTable& patchTable, + bool stubEliminated, bool dylibsExpectedOnDisk); + + // used when building dyld shared cache + static ImageProxyGroup* makeOtherOsGroup(Diagnostics& diag, const DyldCacheParser& dyldCache, ImageProxyGroup* cachedDylibsGroup, + const std::vector& otherDylibsAndBundles, + bool inodesAreSameAsRuntime, const std::vector& buildTimePrefixes); + + const BinaryImageGroupData* makeImageGroupBinary(Diagnostics& diag, const char* const neverEliminateStubs[]=nullptr); + + // used when building dyld shared cache + static BinaryClosureData* makeClosure(Diagnostics& diag, const DyldCacheParser& dyldCache, ImageProxyGroup* cachedDylibsGroup, + ImageProxyGroup* otherOsDylibs, const DyldSharedCache::MappedMachO& mainProg, + bool inodesAreSameAsRuntime, const std::vector& buildTimePrefixes); + + // used by closured for dlopen of unknown dylibs + static const BinaryImageGroupData* makeDlopenGroup(Diagnostics& diag, const DyldCacheParser& dyldCache, uint32_t groupNum, + const std::vector& existingGroups, + const std::string& imagePath, const std::vector& envVars); + + static const BinaryImageGroupData* makeDlopenGroup(Diagnostics& diag, const ClosureBuffer& buffer, task_t requestor, const std::vector& buildTimePrefixes={}); + + static BinaryClosureData* makeClosure(Diagnostics& diag, const ClosureBuffer& buffer, task_t requestor, const std::vector& buildTimePrefixes={}); + + + // + // Creates a binary launch closure for the specified main executable. + // Used by closured and dyld_closure_util + // + // The closure is allocated with malloc(). Use free() to release when done. + // The size of the closure can be determined using Closure::size(). + // If the closure cannot be built (e.g. app needs a symbol not exported by a framework), + // the reason for the failure is returned as a string in the diag parameter. + // The mainProgRuntimePath path is the path the program will be at runtime. + // The buildTimePrefixes is a list of prefixes to add to each path during closure + // creation to find the files at buildtime. + // + static BinaryClosureData* makeClosure(Diagnostics& diag, const DyldCacheParser& dyldCache, + const std::string& mainProgRuntimePath, bool includeDylibsInDir, + const std::vector& buildTimePrefixes={}, + const std::vector& envVars={}); + + +private: + friend class ImageProxy; + + ImageProxyGroup(uint32_t groupNum, const DyldCacheParser& dyldCache, const BinaryImageGroupData* basedOn, + ImageProxyGroup* next, const std::string& mainProgRuntimePath, + const std::vector& knownGroups, + const std::vector& buildTimePrefixes, + const std::vector& envVars, + bool stubsEliminated=false, bool dylibsExpectedOnDisk=true, bool inodesAreSameAsRuntime=true); + + ImageProxy* findImage(Diagnostics& diag, const std::string& runtimePath, bool canBeMissing, ImageProxy::RPathChain*); + ImageProxy* findAbsoluteImage(Diagnostics& diag, const std::string& runtimePath, bool canBeMissing, bool makeErrorMessage, bool pathIsReal=false); + bool builtImageStillValid(const launch_cache::Image& image); + const std::string& mainProgRuntimePath() { return _mainProgRuntimePath; } + DyldSharedCache::MappedMachO* addMappingIfValidMachO(Diagnostics& diag, const std::string& runtimePath, bool ignoreMainExecutables=false); + BinaryClosureData* makeClosureBinary(Diagnostics& diag, ImageProxy* mainProg, bool includeDylibsInDir); + void findLibdyldEntry(Diagnostics& diag, ImageRef& ref, uint32_t& offset); + void findLibSystem(Diagnostics& diag, bool sim, ImageRef& ref); + void populateGroupWriter(Diagnostics& diag, launch_cache::ImageGroupWriter& groupWriter, const char* const neverEliminateStubs[]=nullptr); + std::string normalizedPath(const std::string& path); + void addExtraMachOsInBundle(const std::string& appDir); + bool addInsertedDylibs(Diagnostics& diag); + std::vector flatLookupOrder(); + + PathOverrides _pathOverrides; + const BinaryImageGroupData* _basedOn; // if not null, then lazily populate _images + const PatchTable* _patchTable; + ImageProxyGroup* const _nextSearchGroup; + const DyldCacheParser _dyldCache; + uint32_t const _groupNum; + bool const _stubEliminated; + bool const _dylibsExpectedOnDisk; + bool const _inodesAreSameAsRuntime; + uint32_t _mainExecutableIndex; + std::vector _knownGroups; + std::vector _images; + std::unordered_map _pathToProxy; + std::vector _ownedMappings; + std::vector _buildTimePrefixes; + std::vector _cacheOverrides; + std::string _mainProgRuntimePath; + std::string _archName; + Platform _platform; + std::set _mustBeMissingFiles; +}; + + + + + +} + +#endif // ImageProxy_h diff --git a/dyld3/shared-cache/MachOFileAbstraction.hpp b/dyld3/shared-cache/MachOFileAbstraction.hpp new file mode 100644 index 0000000..a15100f --- /dev/null +++ b/dyld3/shared-cache/MachOFileAbstraction.hpp @@ -0,0 +1,964 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2005 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@ +*/ +#ifndef __MACH_O_FILE_ABSTRACTION__ +#define __MACH_O_FILE_ABSTRACTION__ + +#include +#include +#include +#include + +// suport older versions of mach-o/loader.h +#ifndef LC_UUID +#define LC_UUID 0x1b +struct uuid_command { + uint32_t cmd; /* LC_UUID */ + uint32_t cmdsize; /* sizeof(struct uuid_command) */ + uint8_t uuid[16]; /* the 128-bit uuid */ +}; +#endif + +#ifndef S_16BYTE_LITERALS + #define S_16BYTE_LITERALS 0xE +#endif + +#ifndef CPU_SUBTYPE_ARM_V5TEJ + #define CPU_SUBTYPE_ARM_V5TEJ ((cpu_subtype_t) 7) +#endif +#ifndef CPU_SUBTYPE_ARM_XSCALE + #define CPU_SUBTYPE_ARM_XSCALE ((cpu_subtype_t) 8) +#endif +#ifndef CPU_SUBTYPE_ARM_V7 + #define CPU_SUBTYPE_ARM_V7 ((cpu_subtype_t) 9) +#endif +#ifndef CPU_SUBTYPE_ARM_V7F + #define CPU_SUBTYPE_ARM_V7F ((cpu_subtype_t) 10) +#endif +#ifndef CPU_SUBTYPE_ARM_V7K + #define CPU_SUBTYPE_ARM_V7K ((cpu_subtype_t) 12) +#endif +#ifndef CPU_SUBTYPE_ARM_V7S + #define CPU_SUBTYPE_ARM_V7S ((cpu_subtype_t) 11) +#endif +#ifndef CPU_SUBTYPE_ARM64_ALL + #define CPU_SUBTYPE_ARM64_ALL ((cpu_subtype_t) 0) +#endif +#ifndef CPU_TYPE_ARM64 + #define CPU_TYPE_ARM64 ((cpu_type_t) (CPU_TYPE_ARM | CPU_ARCH_ABI64)) +#endif + +#define ARM64_RELOC_UNSIGNED 0 // for pointers + + +#ifndef LC_LOAD_UPWARD_DYLIB + #define LC_LOAD_UPWARD_DYLIB (0x23|LC_REQ_DYLD) /* load of dylib whose initializers run later */ +#endif + +#ifndef EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER + #define EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER 0x10 +#endif +#ifndef EXPORT_SYMBOL_FLAGS_REEXPORT + #define EXPORT_SYMBOL_FLAGS_REEXPORT 0x08 +#endif + +#ifndef LC_FUNCTION_STARTS + #define LC_FUNCTION_STARTS 0x26 +#endif + +#ifndef LC_DATA_IN_CODE + #define LC_DATA_IN_CODE 0x29 +#endif + +#ifndef LC_DYLIB_CODE_SIGN_DRS + #define LC_DYLIB_CODE_SIGN_DRS 0x2B +#endif + +#ifndef CPU_SUBTYPE_X86_64_H + #define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t) 8) +#endif + + +#define DYLD_CACHE_ADJ_V2_FORMAT 0x7F + +#define DYLD_CACHE_ADJ_V2_POINTER_32 0x01 +#define DYLD_CACHE_ADJ_V2_POINTER_64 0x02 +#define DYLD_CACHE_ADJ_V2_DELTA_32 0x03 +#define DYLD_CACHE_ADJ_V2_DELTA_64 0x04 +#define DYLD_CACHE_ADJ_V2_ARM64_ADRP 0x05 +#define DYLD_CACHE_ADJ_V2_ARM64_OFF12 0x06 +#define DYLD_CACHE_ADJ_V2_ARM64_BR26 0x07 +#define DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT 0x08 +#define DYLD_CACHE_ADJ_V2_ARM_BR24 0x09 +#define DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT 0x0A +#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" + +// utility to pair together a cpu-type and cpu-sub-type +struct ArchPair +{ + uint32_t arch; + uint32_t subtype; + + ArchPair(uint32_t cputype, uint32_t cpusubtype) : arch(cputype), subtype(cpusubtype) {} + + bool operator<(const ArchPair& other) const { + if ( this->arch != other.arch ) + 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; + } +}; + + +// +// This abstraction layer makes every mach-o file look like a 64-bit mach-o file with native endianness +// + +// +// mach-o load command +// +template +class macho_load_command { +public: + uint32_t cmd() const INLINE { return E::get32(command.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(command.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(command.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(command.cmdsize, value); } + + typedef typename P::E E; +private: + load_command command; +}; + + +// +// mach-o segment load command +// +template struct macho_segment_content {}; +template <> struct macho_segment_content > { segment_command fields; enum { CMD = LC_SEGMENT }; }; +template <> struct macho_segment_content > { segment_command_64 fields; enum { CMD = LC_SEGMENT_64 }; }; +template <> struct macho_segment_content > { segment_command fields; enum { CMD = LC_SEGMENT }; }; +template <> struct macho_segment_content > { segment_command_64 fields; enum { CMD = LC_SEGMENT_64 }; }; + +template +class macho_segment_command { +public: + uint32_t cmd() const INLINE { return E::get32(segment.fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(segment.fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(segment.fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(segment.fields.cmdsize, value); } + + const char* segname() const INLINE { return segment.fields.segname; } + void set_segname(const char* value) INLINE { strncpy(segment.fields.segname, value, 16); } + + uint64_t vmaddr() const INLINE { return P::getP(segment.fields.vmaddr); } + void set_vmaddr(uint64_t value) INLINE { P::setP(segment.fields.vmaddr, value); } + + uint64_t vmsize() const INLINE { return P::getP(segment.fields.vmsize); } + void set_vmsize(uint64_t value) INLINE { P::setP(segment.fields.vmsize, value); } + + uint64_t fileoff() const INLINE { return P::getP(segment.fields.fileoff); } + void set_fileoff(uint64_t value) INLINE { P::setP(segment.fields.fileoff, value); } + + uint64_t filesize() const INLINE { return P::getP(segment.fields.filesize); } + void set_filesize(uint64_t value) INLINE { P::setP(segment.fields.filesize, value); } + + uint32_t maxprot() const INLINE { return E::get32(segment.fields.maxprot); } + void set_maxprot(uint32_t value) INLINE { E::set32((uint32_t&)segment.fields.maxprot, value); } + + uint32_t initprot() const INLINE { return E::get32(segment.fields.initprot); } + void set_initprot(uint32_t value) INLINE { E::set32((uint32_t&)segment.fields.initprot, value); } + + uint32_t nsects() const INLINE { return E::get32(segment.fields.nsects); } + void set_nsects(uint32_t value) INLINE { E::set32(segment.fields.nsects, value); } + + uint32_t flags() const INLINE { return E::get32(segment.fields.flags); } + void set_flags(uint32_t value) INLINE { E::set32(segment.fields.flags, value); } + + enum { + CMD = macho_segment_content

::CMD + }; + + typedef typename P::E E; +private: + macho_segment_content

segment; +}; + + +// +// mach-o section +// +template struct macho_section_content {}; +template <> struct macho_section_content > { section fields; }; +template <> struct macho_section_content > { section_64 fields; }; +template <> struct macho_section_content > { section fields; }; +template <> struct macho_section_content > { section_64 fields; }; + +template +class macho_section { +public: + const char* sectname() const INLINE { return section.fields.sectname; } + void set_sectname(const char* value) INLINE { strncpy(section.fields.sectname, value, 16); } + + const char* segname() const INLINE { return section.fields.segname; } + void set_segname(const char* value) INLINE { strncpy(section.fields.segname, value, 16); } + + uint64_t addr() const INLINE { return P::getP(section.fields.addr); } + void set_addr(uint64_t value) INLINE { P::setP(section.fields.addr, value); } + + uint64_t size() const INLINE { return P::getP(section.fields.size); } + void set_size(uint64_t value) INLINE { P::setP(section.fields.size, value); } + + uint32_t offset() const INLINE { return E::get32(section.fields.offset); } + void set_offset(uint32_t value) INLINE { E::set32(section.fields.offset, value); } + + uint32_t align() const INLINE { return E::get32(section.fields.align); } + void set_align(uint32_t value) INLINE { E::set32(section.fields.align, value); } + + uint32_t reloff() const INLINE { return E::get32(section.fields.reloff); } + void set_reloff(uint32_t value) INLINE { E::set32(section.fields.reloff, value); } + + uint32_t nreloc() const INLINE { return E::get32(section.fields.nreloc); } + void set_nreloc(uint32_t value) INLINE { E::set32(section.fields.nreloc, value); } + + uint32_t flags() const INLINE { return E::get32(section.fields.flags); } + void set_flags(uint32_t value) INLINE { E::set32(section.fields.flags, value); } + + uint32_t reserved1() const INLINE { return E::get32(section.fields.reserved1); } + void set_reserved1(uint32_t value) INLINE { E::set32(section.fields.reserved1, value); } + + uint32_t reserved2() const INLINE { return E::get32(section.fields.reserved2); } + void set_reserved2(uint32_t value) INLINE { E::set32(section.fields.reserved2, value); } + + typedef typename P::E E; +private: + macho_section_content

section; +}; + + +// +// mach-o dylib load command +// +template +class macho_dylib_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t name_offset() const INLINE { return E::get32(fields.dylib.name.offset); } + void set_name_offset(uint32_t value) INLINE { E::set32(fields.dylib.name.offset, value); } + + uint32_t timestamp() const INLINE { return E::get32(fields.dylib.timestamp); } + void set_timestamp(uint32_t value) INLINE { E::set32(fields.dylib.timestamp, value); } + + uint32_t current_version() const INLINE { return E::get32(fields.dylib.current_version); } + void set_current_version(uint32_t value) INLINE { E::set32(fields.dylib.current_version, value); } + + uint32_t compatibility_version() const INLINE { return E::get32(fields.dylib.compatibility_version); } + void set_compatibility_version(uint32_t value) INLINE { E::set32(fields.dylib.compatibility_version, value); } + + const char* name() const INLINE { return (const char*)&fields + name_offset(); } + void set_name_offset() INLINE { set_name_offset(sizeof(fields)); } + + typedef typename P::E E; +private: + dylib_command fields; +}; + + +// +// mach-o dylinker load command +// +template +class macho_dylinker_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t name_offset() const INLINE { return E::get32(fields.name.offset); } + void set_name_offset(uint32_t value) INLINE { E::set32(fields.name.offset, value); } + + const char* name() const INLINE { return (const char*)&fields + name_offset(); } + void set_name_offset() INLINE { set_name_offset(sizeof(fields)); } + + typedef typename P::E E; +private: + dylinker_command fields; +}; + + +// +// mach-o sub_framework load command +// +template +class macho_sub_framework_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t umbrella_offset() const INLINE { return E::get32(fields.umbrella.offset); } + void set_umbrella_offset(uint32_t value) INLINE { E::set32(fields.umbrella.offset, value); } + + const char* umbrella() const INLINE { return (const char*)&fields + umbrella_offset(); } + void set_umbrella_offset() INLINE { set_umbrella_offset(sizeof(fields)); } + + typedef typename P::E E; +private: + sub_framework_command fields; +}; + + +// +// mach-o sub_client load command +// +template +class macho_sub_client_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t client_offset() const INLINE { return E::get32(fields.client.offset); } + void set_client_offset(uint32_t value) INLINE { E::set32(fields.client.offset, value); } + + const char* client() const INLINE { return (const char*)&fields + client_offset(); } + void set_client_offset() INLINE { set_client_offset(sizeof(fields)); } + + typedef typename P::E E; +private: + sub_client_command fields; +}; + + +// +// mach-o sub_umbrella load command +// +template +class macho_sub_umbrella_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t sub_umbrella_offset() const INLINE { return E::get32(fields.sub_umbrella.offset); } + void set_sub_umbrella_offset(uint32_t value) INLINE { E::set32(fields.sub_umbrella.offset, value); } + + const char* sub_umbrella() const INLINE { return (const char*)&fields + sub_umbrella_offset(); } + void set_sub_umbrella_offset() INLINE { set_sub_umbrella_offset(sizeof(fields)); } + + typedef typename P::E E; +private: + sub_umbrella_command fields; +}; + + +// +// mach-o sub_library load command +// +template +class macho_sub_library_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t sub_library_offset() const INLINE { return E::get32(fields.sub_library.offset); } + void set_sub_library_offset(uint32_t value) INLINE { E::set32(fields.sub_library.offset, value); } + + const char* sub_library() const INLINE { return (const char*)&fields + sub_library_offset(); } + void set_sub_library_offset() INLINE { set_sub_library_offset(sizeof(fields)); } + + typedef typename P::E E; +private: + sub_library_command fields; +}; + + +// +// mach-o uuid load command +// +template +class macho_uuid_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + const uint8_t* uuid() const INLINE { return fields.uuid; } + void set_uuid(uint8_t value[16]) INLINE { memcpy(&fields.uuid, value, 16); } + + typedef typename P::E E; +private: + uuid_command fields; +}; + + +// +// mach-o routines load command +// +template struct macho_routines_content {}; +template <> struct macho_routines_content > { routines_command fields; enum { CMD = LC_ROUTINES }; }; +template <> struct macho_routines_content > { routines_command_64 fields; enum { CMD = LC_ROUTINES_64 }; }; +template <> struct macho_routines_content > { routines_command fields; enum { CMD = LC_ROUTINES }; }; +template <> struct macho_routines_content > { routines_command_64 fields; enum { CMD = LC_ROUTINES_64 }; }; + +template +class macho_routines_command { +public: + uint32_t cmd() const INLINE { return E::get32(routines.fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(routines.fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(routines.fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(routines.fields.cmdsize, value); } + + uint64_t init_address() const INLINE { return P::getP(routines.fields.init_address); } + void set_init_address(uint64_t value) INLINE { P::setP(routines.fields.init_address, value); } + + uint64_t init_module() const INLINE { return P::getP(routines.fields.init_module); } + void set_init_module(uint64_t value) INLINE { P::setP(routines.fields.init_module, value); } + + uint64_t reserved1() const INLINE { return P::getP(routines.fields.reserved1); } + void set_reserved1(uint64_t value) INLINE { P::setP(routines.fields.reserved1, value); } + + uint64_t reserved2() const INLINE { return P::getP(routines.fields.reserved2); } + void set_reserved2(uint64_t value) INLINE { P::setP(routines.fields.reserved2, value); } + + uint64_t reserved3() const INLINE { return P::getP(routines.fields.reserved3); } + void set_reserved3(uint64_t value) INLINE { P::setP(routines.fields.reserved3, value); } + + uint64_t reserved4() const INLINE { return P::getP(routines.fields.reserved4); } + void set_reserved4(uint64_t value) INLINE { P::setP(routines.fields.reserved4, value); } + + uint64_t reserved5() const INLINE { return P::getP(routines.fields.reserved5); } + void set_reserved5(uint64_t value) INLINE { P::setP(routines.fields.reserved5, value); } + + uint64_t reserved6() const INLINE { return P::getP(routines.fields.reserved6); } + void set_reserved6(uint64_t value) INLINE { P::setP(routines.fields.reserved6, value); } + + typedef typename P::E E; + enum { + CMD = macho_routines_content

::CMD + }; +private: + macho_routines_content

routines; +}; + + +// +// mach-o symbol table load command +// +template +class macho_symtab_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t symoff() const INLINE { return E::get32(fields.symoff); } + void set_symoff(uint32_t value) INLINE { E::set32(fields.symoff, value); } + + uint32_t nsyms() const INLINE { return E::get32(fields.nsyms); } + void set_nsyms(uint32_t value) INLINE { E::set32(fields.nsyms, value); } + + uint32_t stroff() const INLINE { return E::get32(fields.stroff); } + void set_stroff(uint32_t value) INLINE { E::set32(fields.stroff, value); } + + uint32_t strsize() const INLINE { return E::get32(fields.strsize); } + void set_strsize(uint32_t value) INLINE { E::set32(fields.strsize, value); } + + + typedef typename P::E E; +private: + symtab_command fields; +}; + + +// +// mach-o dynamic symbol table load command +// +template +class macho_dysymtab_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t ilocalsym() const INLINE { return E::get32(fields.ilocalsym); } + void set_ilocalsym(uint32_t value) INLINE { E::set32(fields.ilocalsym, value); } + + uint32_t nlocalsym() const INLINE { return E::get32(fields.nlocalsym); } + void set_nlocalsym(uint32_t value) INLINE { E::set32(fields.nlocalsym, value); } + + uint32_t iextdefsym() const INLINE { return E::get32(fields.iextdefsym); } + void set_iextdefsym(uint32_t value) INLINE { E::set32(fields.iextdefsym, value); } + + uint32_t nextdefsym() const INLINE { return E::get32(fields.nextdefsym); } + void set_nextdefsym(uint32_t value) INLINE { E::set32(fields.nextdefsym, value); } + + uint32_t iundefsym() const INLINE { return E::get32(fields.iundefsym); } + void set_iundefsym(uint32_t value) INLINE { E::set32(fields.iundefsym, value); } + + uint32_t nundefsym() const INLINE { return E::get32(fields.nundefsym); } + void set_nundefsym(uint32_t value) INLINE { E::set32(fields.nundefsym, value); } + + uint32_t tocoff() const INLINE { return E::get32(fields.tocoff); } + void set_tocoff(uint32_t value) INLINE { E::set32(fields.tocoff, value); } + + uint32_t ntoc() const INLINE { return E::get32(fields.ntoc); } + void set_ntoc(uint32_t value) INLINE { E::set32(fields.ntoc, value); } + + uint32_t modtaboff() const INLINE { return E::get32(fields.modtaboff); } + void set_modtaboff(uint32_t value) INLINE { E::set32(fields.modtaboff, value); } + + uint32_t nmodtab() const INLINE { return E::get32(fields.nmodtab); } + void set_nmodtab(uint32_t value) INLINE { E::set32(fields.nmodtab, value); } + + uint32_t extrefsymoff() const INLINE { return E::get32(fields.extrefsymoff); } + void set_extrefsymoff(uint32_t value) INLINE { E::set32(fields.extrefsymoff, value); } + + uint32_t nextrefsyms() const INLINE { return E::get32(fields.nextrefsyms); } + void set_nextrefsyms(uint32_t value) INLINE { E::set32(fields.nextrefsyms, value); } + + uint32_t indirectsymoff() const INLINE { return E::get32(fields.indirectsymoff); } + void set_indirectsymoff(uint32_t value) INLINE { E::set32(fields.indirectsymoff, value); } + + uint32_t nindirectsyms() const INLINE { return E::get32(fields.nindirectsyms); } + void set_nindirectsyms(uint32_t value) INLINE { E::set32(fields.nindirectsyms, value); } + + uint32_t extreloff() const INLINE { return E::get32(fields.extreloff); } + void set_extreloff(uint32_t value) INLINE { E::set32(fields.extreloff, value); } + + uint32_t nextrel() const INLINE { return E::get32(fields.nextrel); } + void set_nextrel(uint32_t value) INLINE { E::set32(fields.nextrel, value); } + + uint32_t locreloff() const INLINE { return E::get32(fields.locreloff); } + void set_locreloff(uint32_t value) INLINE { E::set32(fields.locreloff, value); } + + uint32_t nlocrel() const INLINE { return E::get32(fields.nlocrel); } + void set_nlocrel(uint32_t value) INLINE { E::set32(fields.nlocrel, value); } + + typedef typename P::E E; +private: + dysymtab_command fields; +}; + + +// +// mach-o two-level hints load command +// +template +class macho_twolevel_hints_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t offset() const INLINE { return E::get32(fields.offset); } + void set_offset(uint32_t value) INLINE { E::set32(fields.offset, value); } + + uint32_t nhints() const INLINE { return E::get32(fields.nhints); } + void set_nhints(uint32_t value) INLINE { E::set32(fields.nhints, value); } + + typedef typename P::E E; +private: + twolevel_hints_command fields; +}; + + +// +// mach-o threads load command +// +template +class macho_thread_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t flavor() const INLINE { return E::get32(fields_flavor); } + void set_flavor(uint32_t value) INLINE { E::set32(fields_flavor, value); } + + uint32_t count() const INLINE { return E::get32(fields_count); } + void set_count(uint32_t value) INLINE { E::set32(fields_count, value); } + + uint64_t thread_register(uint32_t index) const INLINE { return P::getP(thread_registers[index]); } + void set_thread_register(uint32_t index, uint64_t value) INLINE { P::setP(thread_registers[index], value); } + + typedef typename P::E E; + typedef typename P::uint_t pint_t; +private: + struct thread_command fields; + uint32_t fields_flavor; + uint32_t fields_count; + pint_t thread_registers[1]; +}; + + +// +// mach-o misc data +// +template +class macho_linkedit_data_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t dataoff() const INLINE { return E::get32(fields.dataoff); } + void set_dataoff(uint32_t value) INLINE { E::set32(fields.dataoff, value); } + + uint32_t datasize() const INLINE { return E::get32(fields.datasize); } + void set_datasize(uint32_t value)INLINE { E::set32(fields.datasize, value); } + + + typedef typename P::E E; +private: + linkedit_data_command fields; +}; + + +// +// mach-o symbol table entry +// +template struct macho_nlist_content {}; +template <> struct macho_nlist_content > { struct nlist fields; }; +template <> struct macho_nlist_content > { struct nlist_64 fields; }; +template <> struct macho_nlist_content > { struct nlist fields; }; +template <> struct macho_nlist_content > { struct nlist_64 fields; }; + +template +class macho_nlist { +public: + uint32_t n_strx() const INLINE { return E::get32(entry.fields.n_un.n_strx); } + void set_n_strx(uint32_t value) INLINE { E::set32((uint32_t&)entry.fields.n_un.n_strx, value); } + + uint8_t n_type() const INLINE { return entry.fields.n_type; } + void set_n_type(uint8_t value) INLINE { entry.fields.n_type = value; } + + uint8_t n_sect() const INLINE { return entry.fields.n_sect; } + void set_n_sect(uint8_t value) INLINE { entry.fields.n_sect = value; } + + uint16_t n_desc() const INLINE { return E::get16(entry.fields.n_desc); } + void set_n_desc(uint16_t value) INLINE { E::set16((uint16_t&)entry.fields.n_desc, value); } + + uint64_t n_value() const INLINE { return P::getP(entry.fields.n_value); } + void set_n_value(uint64_t value) INLINE { P::setP(entry.fields.n_value, value); } + + typedef typename P::E E; +private: + macho_nlist_content

entry; +}; + + + +// +// mach-o relocation info +// +template +class macho_relocation_info { +public: + uint32_t r_address() const INLINE { return E::get32(address); } + void set_r_address(uint32_t value) INLINE { E::set32(address, value); } + + uint32_t r_symbolnum() const INLINE { return E::getBits(other, 0, 24); } + void set_r_symbolnum(uint32_t value) INLINE { E::setBits(other, value, 0, 24); } + + bool r_pcrel() const INLINE { return E::getBits(other, 24, 1); } + void set_r_pcrel(bool value) INLINE { E::setBits(other, value, 24, 1); } + + uint8_t r_length() const INLINE { return E::getBits(other, 25, 2); } + void set_r_length(uint8_t value) INLINE { E::setBits(other, value, 25, 2); } + + bool r_extern() const INLINE { return E::getBits(other, 27, 1); } + void set_r_extern(bool value) INLINE { E::setBits(other, value, 27, 1); } + + uint8_t r_type() const INLINE { return E::getBits(other, 28, 4); } + void set_r_type(uint8_t value) INLINE { E::setBits(other, value, 28, 4); } + + void set_r_length() INLINE { set_r_length((sizeof(typename P::uint_t)==8) ? 3 : 2); } + + typedef typename P::E E; +private: + uint32_t address; + uint32_t other; +}; + + +// +// mach-o scattered relocation info +// The bit fields are always in big-endian order (see mach-o/reloc.h) +// +template +class macho_scattered_relocation_info { +public: + bool r_scattered() const INLINE { return BigEndian::getBitsRaw(E::get32(other), 0, 1); } + void set_r_scattered(bool x) INLINE { uint32_t temp = E::get32(other); BigEndian::setBitsRaw(temp, x, 0, 1); E::set32(other, temp); } + + bool r_pcrel() const INLINE { return BigEndian::getBitsRaw(E::get32(other), 1, 1); } + void set_r_pcrel(bool x) INLINE { uint32_t temp = E::get32(other); BigEndian::setBitsRaw(temp, x, 1, 1); E::set32(other, temp); } + + uint8_t r_length() const INLINE { return BigEndian::getBitsRaw(E::get32(other), 2, 2); } + void set_r_length(uint8_t x) INLINE { uint32_t temp = E::get32(other); BigEndian::setBitsRaw(temp, x, 2, 2); E::set32(other, temp); } + + uint8_t r_type() const INLINE { return BigEndian::getBitsRaw(E::get32(other), 4, 4); } + void set_r_type(uint8_t x) INLINE { uint32_t temp = E::get32(other); BigEndian::setBitsRaw(temp, x, 4, 4); E::set32(other, temp); } + + uint32_t r_address() const INLINE { return BigEndian::getBitsRaw(E::get32(other), 8, 24); } + void set_r_address(uint32_t x) INLINE { uint32_t temp = E::get32(other); BigEndian::setBitsRaw(temp, x, 8, 24); E::set32(other, temp); } + + uint32_t r_value() const INLINE { return E::get32(value); } + void set_r_value(uint32_t x) INLINE { E::set32(value, x); } + + uint32_t r_other() const INLINE { return other; } + + typedef typename P::E E; +private: + uint32_t other; + uint32_t value; +}; + + +// +// mach-o file header +// +template struct macho_header_content {}; +template <> struct macho_header_content > { mach_header fields; }; +template <> struct macho_header_content > { mach_header_64 fields; }; +template <> struct macho_header_content > { mach_header fields; }; +template <> struct macho_header_content > { mach_header_64 fields; }; + +template +class macho_header { +public: + uint32_t magic() const INLINE { return E::get32(header.fields.magic); } + void set_magic(uint32_t value) INLINE { E::set32(header.fields.magic, value); } + + uint32_t cputype() const INLINE { return E::get32(header.fields.cputype); } + void set_cputype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cputype, value); } + + uint32_t cpusubtype() const INLINE { return E::get32(header.fields.cpusubtype); } + void set_cpusubtype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cpusubtype, value); } + + uint32_t filetype() const INLINE { return E::get32(header.fields.filetype); } + void set_filetype(uint32_t value) INLINE { E::set32(header.fields.filetype, value); } + + uint32_t ncmds() const INLINE { return E::get32(header.fields.ncmds); } + void set_ncmds(uint32_t value) INLINE { E::set32(header.fields.ncmds, value); } + + uint32_t sizeofcmds() const INLINE { return E::get32(header.fields.sizeofcmds); } + void set_sizeofcmds(uint32_t value) INLINE { E::set32(header.fields.sizeofcmds, value); } + + uint32_t flags() const INLINE { return E::get32(header.fields.flags); } + void set_flags(uint32_t value) INLINE { E::set32(header.fields.flags, value); } + + uint32_t reserved() const INLINE { return E::get32(header.fields.reserved); } + void set_reserved(uint32_t value) INLINE { E::set32(header.fields.reserved, value); } + + const macho_segment_command

* getSegment(const char *segname) const + { + const macho_load_command

* cmds = (macho_load_command

*)((uint8_t*)this + sizeof(macho_header

)); + uint32_t cmd_count = this->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; + if (0 == strncmp(segname, segcmd->segname(), 16)) { + return segcmd; + } + } + cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } + return NULL; + } + + const macho_section

* getSection(const char *segname, const char *sectname) const + { + const macho_segment_command

* segcmd = getSegment(segname); + if (!segcmd) return NULL; + + const macho_section

* sectcmd = (macho_section

*)(segcmd+1); + uint32_t section_count = segcmd->nsects(); + for (uint32_t j = 0; j < section_count; ++j) { + if (0 == ::strncmp(sectcmd[j].sectname(), sectname, 16)) { + return sectcmd+j; + } + } + + if (strcmp(segname, "__DATA") == 0) + return getSection("__DATA_CONST", sectname); + return NULL; + } + + const macho_load_command

* getLoadCommand(int query) const + { + const macho_load_command

* cmds = (macho_load_command

*)((uint8_t*)this + sizeof(macho_header

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

* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd() == query ) { + return cmd; + } + cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } + return NULL; + } + + typedef typename P::E E; +private: + macho_header_content

header; +}; + + + +// +// compressed dyld info load command +// +template +class macho_dyld_info_command { +public: + uint32_t cmd() const INLINE { return E::get32(fields.cmd); } + void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); } + + uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); } + void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); } + + uint32_t rebase_off() const INLINE { return E::get32(fields.rebase_off); } + void set_rebase_off(uint32_t value) INLINE { E::set32(fields.rebase_off, value); } + + uint32_t rebase_size() const INLINE { return E::get32(fields.rebase_size); } + void set_rebase_size(uint32_t value) INLINE { E::set32(fields.rebase_size, value); } + + uint32_t bind_off() const INLINE { return E::get32(fields.bind_off); } + void set_bind_off(uint32_t value) INLINE { E::set32(fields.bind_off, value); } + + uint32_t bind_size() const INLINE { return E::get32(fields.bind_size); } + void set_bind_size(uint32_t value) INLINE { E::set32(fields.bind_size, value); } + + uint32_t weak_bind_off() const INLINE { return E::get32(fields.weak_bind_off); } + void set_weak_bind_off(uint32_t value) INLINE { E::set32(fields.weak_bind_off, value); } + + uint32_t weak_bind_size() const INLINE { return E::get32(fields.weak_bind_size); } + void set_weak_bind_size(uint32_t value) INLINE { E::set32(fields.weak_bind_size, value); } + + uint32_t lazy_bind_off() const INLINE { return E::get32(fields.lazy_bind_off); } + void set_lazy_bind_off(uint32_t value) INLINE { E::set32(fields.lazy_bind_off, value); } + + uint32_t lazy_bind_size() const INLINE { return E::get32(fields.lazy_bind_size); } + void set_lazy_bind_size(uint32_t value) INLINE { E::set32(fields.lazy_bind_size, value); } + + uint32_t export_off() const INLINE { return E::get32(fields.export_off); } + void set_export_off(uint32_t value) INLINE { E::set32(fields.export_off, value); } + + uint32_t export_size() const INLINE { return E::get32(fields.export_size); } + void set_export_size(uint32_t value) INLINE { E::set32(fields.export_size, value); } + + + typedef typename P::E E; +private: + dyld_info_command fields; +}; + +#ifndef NO_ULEB +inline uint64_t read_uleb128(const uint8_t*& p, const uint8_t* end) { + uint64_t result = 0; + int bit = 0; + do { + if (p == end) + throw "malformed uleb128 extends beyond trie"; + + uint64_t slice = *p & 0x7f; + + if (bit >= 64 || slice << bit >> bit != slice) + throw "uleb128 too big for 64-bits"; + else { + result |= (slice << bit); + bit += 7; + } + } + while (*p++ & 0x80); + return result; +} + + +inline int64_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; +} + +#endif + + +#endif // __MACH_O_FILE_ABSTRACTION__ + + diff --git a/dyld3/shared-cache/Manifest.h b/dyld3/shared-cache/Manifest.h new file mode 100644 index 0000000..3348cfa --- /dev/null +++ b/dyld3/shared-cache/Manifest.h @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2017 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 Manifest_h +#define Manifest_h + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#import + +#include "MachOParser.h" +#include "DyldSharedCache.h" +#include "Diagnostics.h" + +extern std::string toolDir(); + +namespace dyld3 { + +struct BuildQueueEntry { + DyldSharedCache::CreateOptions options; + std::vector dylibsForCache; + std::vector otherDylibsAndBundles; + std::vector mainExecutables; + std::string outputPath; + std::set configNames; +}; + +struct Manifest { + struct UUIDInfo { + const mach_header* mh; + uint64_t sliceFileOffset; + std::size_t size; + std::string runtimePath; + std::string buildPath; + std::string installName; + std::string arch; + UUID uuid; + UUIDInfo(const mach_header* M, std::size_t S, uint64_t SO, UUID U, std::string A, std::string RP, std::string BP, std::string IN) + : mh(M), size(S), arch(A), uuid(U), runtimePath(RP), buildPath(BP), installName(IN), sliceFileOffset(SO) {} + UUIDInfo() : UUIDInfo(nullptr, 0, 0, UUID(), "", "", "", "") {} + }; + + struct Project { + std::vector sources; + }; + + struct File { + MachOParser* parser; + File(MachOParser* P) + : parser(P) + { + } + }; + + struct SegmentInfo { + std::string name; + uint64_t startAddr; + uint64_t endAddr; + }; + + struct CacheInfo { + std::vector regions; + std::string cdHash; + }; + + struct CacheImageInfo { + bool included; + std::string exclusionInfo; + UUID uuid; + std::string installname; + std::vector segments; + CacheImageInfo(void) + : included(true) + { + } + }; + + struct Results { + std::string failure; + std::map dylibs; + std::map bundles; + std::map executables; + + std::set warnings; + CacheInfo developmentCache; + CacheInfo productionCache; + CacheImageInfo& dylibForInstallname(const std::string& installname); + void exclude(MachOParser* parser, const std::string& reason); + void exclude(Manifest& manifest, const UUID& uuid, const std::string& reason); + }; + + struct Architecture { + mutable Results results; + + bool operator==(const Architecture& O) const; + bool operator!=(const Architecture& other) const; + }; + + struct Configuration { + std::string platformName; + std::string device; + std::string disposition; + std::string metabomTag; + std::set metabomTags; + std::set metabomExcludeTags; + std::set metabomRestrictTags; + std::set restrictedInstallnames; + std::map architectures; + + bool operator==(const Configuration& O) const; + bool operator!=(const Configuration& other) const; + const Architecture& architecture(const std::string& architecture) const; + void forEachArchitecture(std::function lambda) const; + }; + + const std::map& projects(); + const Configuration& configuration(const std::string& configuration) const; + void forEachConfiguration(std::function lambda) const; + + void addProjectSource(const std::string& project, const std::string& source, bool first = false); + + const std::string projectPath(const std::string& projectName); + const bool empty(void); + const std::string dylibOrderFile() const; + void setDylibOrderFile(const std::string& dylibOrderFile); + + const std::string dirtyDataOrderFile() const; + void setDirtyDataOrderFile(const std::string& dirtyDataOrderFile); + + const std::string metabomFile() const; + void setMetabomFile(const std::string& metabomFile); + + const Platform platform() const; + void setPlatform(const Platform platform); + + const std::string& build() const; + void setBuild(const std::string& build); + const uint32_t version() const; + void setVersion(const uint32_t manifestVersion); + bool normalized; + + Manifest(Diagnostics& D, const std::string& path); + Manifest(Diagnostics& D, const std::string& path, const std::set& overlays); + + BuildQueueEntry makeQueueEntry(const std::string& outputPath, const std::set& configs, const std::string& arch, bool optimizeStubs, const std::string& prefix, bool verbose); + + void write(const std::string& path); + void writeJSON(const std::string& path); + void canonicalize(void); + void calculateClosure(); + MachOParser parserForUUID(const UUID& uuid) const; + const std::string buildPathForUUID(const UUID& uuid); + const std::string runtimePathForUUID(const UUID& uuid); + DyldSharedCache::MappedMachO machoForPathAndArch(const std::string& path, const std::string& arch) const; + void remove(const std::string& config, const std::string& arch); + const std::string removeLargestLeafDylib(const std::set& configurations, const std::string& architecture); + void runConcurrently(dispatch_queue_t queue, dispatch_semaphore_t concurrencyLimitingSemaphore, std::function lambda); + bool filterForConfig(const std::string& configName); + +private: + NSDictionary* _manifestDict; + Diagnostics& _diags; + std::map _uuidMap; + std::map, UUID> _installNameMap; + static dispatch_queue_t _identifierQueue; + uint32_t _manifestVersion; + std::string _build; + std::string _dylibOrderFile; + std::string _dirtyDataOrderFile; + std::string _metabomFile; + Platform _platform; + std::map _projects; + std::map _configurations; + std::map> _metabomTagMap; + std::map> _metabomExcludeTagMap; + std::map> _metabomRestrictedTagMap; + + std::vector dylibsForCache(const std::string& configuration, const std::string& architecture); + std::vector otherDylibsAndBundles(const std::string& configuration, const std::string& architecture); + std::vector mainExecutables(const std::string& configuration, const std::string& architecture); + + const UUIDInfo& infoForUUID(const UUID& uuid) const; + const UUIDInfo infoForInstallNameAndarch(const std::string& installName, const std::string arch) const; + void insert(std::vector& mappedMachOs, const CacheImageInfo& imageInfo); + bool loadParser(const void* p, size_t size, uint64_t sliceOffset, const std::string& runtimePath, const std::string& buildPath, const std::set& architectures); + bool loadParsers(const std::string& pathToMachO, const std::string& runtimePath, const std::set& architectures); + void removeDylib(MachOParser parser, const std::string& reason, const std::string& configuration, const std::string& architecture, + std::unordered_set& processedIdentifiers); + void dedupeDispositions(); + void calculateClosure(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/dyld3/shared-cache/Manifest.mm b/dyld3/shared-cache/Manifest.mm new file mode 100644 index 0000000..3dd9f33 --- /dev/null +++ b/dyld3/shared-cache/Manifest.mm @@ -0,0 +1,1134 @@ +/* + * Copyright (c) 2017 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@ + */ + + +extern "C" { +#include +#include +#include +#include +#include +}; + +#include +#include +#include +#include + +#include "MachOFileAbstraction.hpp" +#include "FileAbstraction.hpp" +#include "Trie.hpp" +#include "FileUtils.h" +#include "StringUtils.h" + +#include +#include + +#include +#include + +#include "Manifest.h" + +namespace { +//FIXME this should be in a class +static inline NSString* cppToObjStr(const std::string& str) { return [NSString stringWithUTF8String:str.c_str()]; } + +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; +} + +//hACK: If we declare this in manifest +static NSDictionary* gManifestDict; + +} /* Anonymous namespace */ + +namespace dyld3 { +void Manifest::Results::exclude(MachOParser* parser, const std::string& reason) +{ + auto dylibUUID = parser->uuid(); + dylibs[dylibUUID].uuid = dylibUUID; + dylibs[dylibUUID].installname = parser->installName(); + dylibs[dylibUUID].included = false; + dylibs[dylibUUID].exclusionInfo = reason; +} + +void Manifest::Results::exclude(Manifest& manifest, const UUID& uuid, const std::string& reason) +{ + auto parser = manifest.parserForUUID(uuid); + dylibs[uuid].uuid = uuid; + dylibs[uuid].installname = parser.installName(); + dylibs[uuid].included = false; + dylibs[uuid].exclusionInfo = reason; +} + +Manifest::CacheImageInfo& Manifest::Results::dylibForInstallname(const std::string& installname) +{ + auto i = find_if(dylibs.begin(), dylibs.end(), [&installname](std::pair d) { return d.second.installname == installname; }); + assert(i != dylibs.end()); + return i->second; +} + +bool Manifest::Architecture::operator==(const Architecture& O) const +{ + 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 + || Odylib->second.uuid != dylib.second.uuid) + return false; + } + } + + for (const 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 + || dylib->second.uuid != Odylib.second.uuid) + return false; + } + } + + for (auto& bundle : results.bundles) { + if (bundle.second.included) { + auto Obundle = O.results.bundles.find(bundle.first); + if (Obundle == O.results.bundles.end() + || Obundle->second.included == false + || Obundle->second.uuid != bundle.second.uuid) + return false; + } + } + + for (const auto& Obundle : O.results.bundles) { + if (Obundle.second.included) { + auto bundle = results.bundles.find(Obundle.first); + if (bundle == results.bundles.end() + || bundle->second.included == false + || bundle->second.uuid != Obundle.second.uuid) + return false; + } + } + + for (auto& executable : results.executables) { + if (executable.second.included) { + auto Oexecutable = O.results.executables.find(executable.first); + if (Oexecutable == O.results.executables.end() + || Oexecutable->second.included == false + || Oexecutable->second.uuid != executable.second.uuid) + return false; + } + } + + for (const auto& Oexecutable : O.results.executables) { + if (Oexecutable.second.included) { + auto executable = results.executables.find(Oexecutable.first); + if (executable == results.executables.end() + || executable->second.included == false + || executable->second.uuid != Oexecutable.second.uuid) + return false; + } + } + + return true; +} + +bool Manifest::Configuration::operator==(const Configuration& O) const +{ + return architectures == O.architectures; +} + +bool Manifest::Configuration::operator!=(const Configuration& other) const { return !(*this == other); } + +const Manifest::Architecture& Manifest::Configuration::architecture(const std::string& architecture) const +{ + assert(architectures.find(architecture) != architectures.end()); + return architectures.find(architecture)->second; +} + +void Manifest::Configuration::forEachArchitecture(std::function lambda) const +{ + for (const auto& architecutre : architectures) { + lambda(architecutre.first); + } +} + +bool Manifest::Architecture::operator!=(const Architecture& other) const { return !(*this == other); } + +const std::map& Manifest::projects() +{ + return _projects; +} + +const Manifest::Configuration& Manifest::configuration(const std::string& configuration) const +{ + assert(_configurations.find(configuration) != _configurations.end()); + return _configurations.find(configuration)->second; +} + +void Manifest::forEachConfiguration(std::function lambda) const +{ + for (const auto& configuration : _configurations) { + lambda(configuration.first); + } +} + +void Manifest::addProjectSource(const std::string& project, const std::string& source, bool first) +{ + auto& sources = _projects[project].sources; + if (std::find(sources.begin(), sources.end(), source) == sources.end()) { + if (first) { + sources.insert(sources.begin(), source); + } else { + sources.push_back(source); + } + } +} + +const std::string Manifest::projectPath(const std::string& projectName) +{ + auto project = _projects.find(projectName); + if (project == _projects.end()) + return ""; + if (project->second.sources.size() == 0) + return ""; + return project->second.sources[0]; +} + +const bool Manifest::empty(void) +{ + for (const auto& configuration : _configurations) { + if (configuration.second.architectures.size() != 0) + return false; + } + return true; +} + +const std::string Manifest::dylibOrderFile() const { return _dylibOrderFile; }; +void Manifest::setDylibOrderFile(const std::string& dylibOrderFile) { _dylibOrderFile = dylibOrderFile; }; + +const std::string Manifest::dirtyDataOrderFile() const { return _dirtyDataOrderFile; }; +void Manifest::setDirtyDataOrderFile(const std::string& dirtyDataOrderFile) { _dirtyDataOrderFile = dirtyDataOrderFile; }; + +const std::string Manifest::metabomFile() const { return _metabomFile; }; +void Manifest::setMetabomFile(const std::string& metabomFile) { _metabomFile = metabomFile; }; + +const Platform Manifest::platform() const { return _platform; }; +void Manifest::setPlatform(const Platform platform) { _platform = platform; }; + +const std::string& Manifest::build() const { return _build; }; +void Manifest::setBuild(const std::string& build) { _build = build; }; +const uint32_t Manifest::version() const { return _manifestVersion; }; +void Manifest::setVersion(const uint32_t manifestVersion) { _manifestVersion = manifestVersion; }; + +BuildQueueEntry Manifest::makeQueueEntry(const std::string& outputPath, const std::set& configs, const std::string& arch, bool optimizeStubs, const std::string& prefix, bool verbose) +{ + dyld3::BuildQueueEntry retval; + + DyldSharedCache::CreateOptions options; + options.archName = arch; + options.platform = platform(); + options.excludeLocalSymbols = true; + options.optimizeStubs = optimizeStubs; + options.optimizeObjC = true; + options.codeSigningDigestMode = (platform() == dyld3::Platform::watchOS) ? + DyldSharedCache::Agile : DyldSharedCache::SHA256only; + options.dylibsRemovedDuringMastering = true; + options.inodesAreSameAsRuntime = false; + options.cacheSupportsASLR = true; + options.forSimulator = false; + options.verbose = verbose; + options.evictLeafDylibsOnOverflow = false; + options.loggingPrefix = prefix; + options.pathPrefixes = { "" }; + options.dylibOrdering = loadOrderFile(_dylibOrderFile); + options.dirtyDataSegmentOrdering = loadOrderFile(_dirtyDataOrderFile); + + dyld3::BuildQueueEntry queueEntry; + retval.configNames = configs; + retval.options = options; + retval.outputPath = outputPath; + retval.dylibsForCache = dylibsForCache(*configs.begin(), arch); + retval.otherDylibsAndBundles = otherDylibsAndBundles(*configs.begin(), arch); + retval.mainExecutables = mainExecutables(*configs.begin(), arch); + + return retval; +} + +bool Manifest::loadParser(const void* p, size_t size, uint64_t sliceOffset, const std::string& runtimePath, const std::string& buildPath, const std::set& architectures) +{ + const mach_header* mh = reinterpret_cast(p); + if (!MachOParser::isValidMachO(_diags, "", _platform, p, size, runtimePath.c_str(), false)) { + return false; + } + + auto parser = MachOParser(mh); + if (_diags.hasError()) { + // Clear the error and punt + _diags.verbose("MachoParser error: %s\n", _diags.errorMessage().c_str()); + _diags.clearError(); + return false; + } + + auto uuid = parser.uuid(); + auto archName = parser.archName(); + + if (parser.fileType() == MH_DYLIB && architectures.count(parser.archName()) != 0) { + std::string installName = parser.installName(); + auto index = std::make_pair(installName, parser.archName()); + auto i = _installNameMap.find(index); + + if ( installName == "/System/Library/Caches/com.apple.xpc/sdk.dylib" + || installName == "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib" ) { + // HACK to deal with device specific dylibs. These must not be inseted into the installNameMap + _uuidMap.insert(std::make_pair(uuid, UUIDInfo(mh, size, sliceOffset, uuid, parser.archName(), runtimePath, buildPath, installName))); + } else if (i == _installNameMap.end()) { + _installNameMap.insert(std::make_pair(index, uuid)); + _uuidMap.insert(std::make_pair(uuid, UUIDInfo(mh, size, sliceOffset, uuid, parser.archName(), runtimePath, buildPath, installName))); + if (installName[0] != '@' && installName != runtimePath) { + _diags.warning("Dylib located at '%s' has installname '%s'", runtimePath.c_str(), installName.c_str()); + } + } else { + auto info = infoForUUID(i->second); + _diags.warning("Multiple dylibs claim installname '%s' ('%s' and '%s')", installName.c_str(), runtimePath.c_str(), info.runtimePath.c_str()); + + // This is the "Good" one, overwrite + if (runtimePath == installName) { + _uuidMap.erase(uuid); + _uuidMap.insert(std::make_pair(uuid, UUIDInfo(mh, size, sliceOffset, uuid, parser.archName(), runtimePath, buildPath, installName))); + } + } + } else { + _uuidMap.insert(std::make_pair(uuid, UUIDInfo(mh, size, sliceOffset, uuid, parser.archName(), runtimePath, buildPath, ""))); + } + return true; +} + +//FIXME: assert we have not errored first +bool Manifest::loadParsers(const std::string& buildPath, const std::string& runtimePath, const std::set& architectures) +{ + __block bool retval = false; + const void* p = (uint8_t*)(-1); + struct stat stat_buf; + + std::tie(p, stat_buf) = fileCache.cacheLoad(_diags, buildPath); + + if (p == (uint8_t*)(-1)) { + return false; + } + + if (FatUtil::isFatFile(p)) { + FatUtil::forEachSlice(_diags, p, stat_buf.st_size, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSize, bool& stop) { + if (loadParser(sliceStart, sliceSize, (uintptr_t)sliceStart-(uintptr_t)p, runtimePath, buildPath, architectures)) + retval = true; + }); + } else { + return loadParser(p, stat_buf.st_size, 0, runtimePath, buildPath, architectures); + } + return retval; +} + +const Manifest::UUIDInfo& Manifest::infoForUUID(const UUID& uuid) const { + auto i = _uuidMap.find(uuid); + assert(i != _uuidMap.end()); + return i->second; +} + +const Manifest::UUIDInfo Manifest::infoForInstallNameAndarch(const std::string& installName, const std::string arch) const { + UUIDInfo retval; + auto uuidI = _installNameMap.find(std::make_pair(installName, arch)); + if (uuidI == _installNameMap.end()) + return UUIDInfo(); + + auto i = _uuidMap.find(uuidI->second); + if (i == _uuidMap.end()) + return UUIDInfo(); + return i->second; +} + +MachOParser Manifest::parserForUUID(const UUID& uuid) const { + return MachOParser(infoForUUID(uuid).mh); +} + +const std::string Manifest::buildPathForUUID(const UUID& uuid) { + return infoForUUID(uuid).buildPath; +} + +const std::string Manifest::runtimePathForUUID(const UUID& uuid) { + return infoForUUID(uuid).runtimePath; +} + +Manifest::Manifest(Diagnostics& D, const std::string& path) : Manifest(D, path, std::set()) +{ +} + +Manifest::Manifest(Diagnostics& D, const std::string& path, const std::set& overlays) : + _diags(D) +{ + NSMutableDictionary* manifestDict = [NSMutableDictionary dictionaryWithContentsOfFile:cppToObjStr(path)]; + NSString* platStr = manifestDict[@"platform"]; + std::set architectures; + + if (platStr == nullptr) + platStr = @"ios"; + std::string platformString = [platStr UTF8String]; + setMetabomFile([manifestDict[@"metabomFile"] UTF8String]); + + if (platformString == "ios") { + setPlatform(dyld3::Platform::iOS); + } else if ( (platformString == "tvos") || (platformString == "atv") ) { + setPlatform(dyld3::Platform::tvOS); + } else if ( (platformString == "watchos") || (platformString == "watch") ) { + setPlatform(dyld3::Platform::watchOS); + } else if ( (platformString == "bridgeos") || (platformString == "bridge") ) { + setPlatform(dyld3::Platform::bridgeOS); + } else if ( (platformString == "macos") || (platformString == "osx") ) { + setPlatform(dyld3::Platform::macOS); + } else { + //Fixme should we error? + setPlatform(dyld3::Platform::iOS); + } + + for (NSString* project in manifestDict[@"projects"]) { + for (NSString* source in manifestDict[@"projects"][project]) { + addProjectSource([project UTF8String], [source UTF8String]); + } + } + + for (NSString* configuration in manifestDict[@"configurations"]) { + std::string configStr = [configuration UTF8String]; + std::string configTag = [manifestDict[@"configurations"][configuration][@"metabomTag"] UTF8String]; + + 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].metabomTags.insert(configTag); + _configurations[configStr].platformName = + [manifestDict[@"configurations"][configuration][@"platformName"] UTF8String]; + + if (endsWith(configStr, "InternalOS")) { + _configurations[configStr].disposition = "internal"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("InternalOS")); + } else if (endsWith(configStr, "VendorOS")) { + _configurations[configStr].disposition = "internal"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("VendorOS")); + } else if (endsWith(configStr, "VendorUIOS")) { + _configurations[configStr].disposition = "internal"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("VendorUIOS")); + } else if (endsWith(configStr, "CarrierOS")) { + _configurations[configStr].disposition = "internal"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("CarrierOS")); + } else if (endsWith(configStr, "FactoryOS")) { + _configurations[configStr].disposition = "internal"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("FactoryOS")); + } else if (endsWith(configStr, "DesenseOS")) { + _configurations[configStr].disposition = "internal"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("DesenseOS")); + } else if (endsWith(configStr, "MinosOS")) { + _configurations[configStr].disposition = "minos"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("MinosOS")); + } else if (endsWith(configStr, "DemoOS")) { + _configurations[configStr].disposition = "demo"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("DemoOS")); + } else if (endsWith(configStr, "MinosOS")) { + _configurations[configStr].disposition = "minos"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("MinosOS")); + } else if (endsWith(configStr, "DeveloperOS")) { + _configurations[configStr].disposition = "user"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("DeveloperOS")); + } else if (endsWith(configStr, "OS")) { + _configurations[configStr].disposition = "user"; + _configurations[configStr].device = configStr.substr(0, configStr.length()-strlen("OS")); + } + + for (NSString* architecutre in manifestDict[@"configurations"][configuration][@"architectures"]) { + //HACK until B&I stops mastering armv7s + if ([architecutre isEqual:@"armv7s"]) break; + _configurations[configStr].architectures[[architecutre UTF8String]] = Architecture(); + architectures.insert([architecutre UTF8String]); + } + } + + setVersion([manifestDict[@"manifest-version"] unsignedIntValue]); + setBuild([manifestDict[@"build"] UTF8String]); + if (manifestDict[@"dylibOrderFile"]) { + setDylibOrderFile([manifestDict[@"dylibOrderFile"] UTF8String]); + } + if (manifestDict[@"dirtyDataOrderFile"]) { + setDirtyDataOrderFile([manifestDict[@"dirtyDataOrderFile"] UTF8String]); + } + + auto metabom = MBMetabomOpen(metabomFile().c_str(), false); + auto metabomEnumerator = MBIteratorNewWithPath(metabom, ".", ""); + MBEntry entry; + + // FIXME error handling (NULL metabom) + + //First we iterate through the bom and build our objects + + while ((entry = MBIteratorNext(metabomEnumerator))) { + BOMFSObject fsObject = MBEntryGetFSObject(entry); + BOMFSObjType entryType = BOMFSObjectType(fsObject); + std::string entryPath = BOMFSObjectPathName(fsObject); + if (entryPath[0] == '.') { + entryPath.erase(0, 1); + } + + // Skip artifacts that happen to be in the build chain + if ( startsWith(entryPath, "/Applications/Xcode.app") ) { + continue; + } + + // Skip variants we can't deal with + if ( endsWith(entryPath, "_profile.dylib") || endsWith(entryPath, "_debug.dylib") || endsWith(entryPath, "_profile") || endsWith(entryPath, "_debug") || endsWith(entryPath, "/CoreADI") ) { + continue; + } + + // Skip images that are only used in InternalOS variants + if ( startsWith(entryPath, "/AppleInternal/") || startsWith(entryPath, "/usr/local/") || startsWith(entryPath, "/Developer/")) { + continue; + } + + // Skip genCache generated dylibs + if ( endsWith(entryPath, "/System/Library/Caches/com.apple.xpc/sdk.dylib") || endsWith(entryPath, "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib")) { + continue; + } + + MBTag tag; + auto tagCount = MBEntryGetNumberOfProjectTags(entry); + if (entryType == BOMFileType && BOMFSObjectIsBinaryObject(fsObject) && MBEntryGetNumberOfProjectTags(entry) != 0 && tagCount != 0) { + 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 + "'"; + } + _diags.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); + 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])); + } + + _metabomTagMap.insert(std::make_pair(entryPath, tagStrs)); + bool foundParser = false; + for (const auto& overlay : overlays) { + if (loadParsers(overlay + "/" + entryPath, entryPath, architectures)) { + foundParser = true; + break; + } + } + + if (!foundParser) { + (void)loadParsers(projectPath(projectName) + "/" + entryPath, entryPath, architectures); + } + } + } + + MBIteratorFree(metabomEnumerator); + MBMetabomFree(metabom); +} + +void Manifest::insert(std::vector& mappedMachOs, const CacheImageInfo& imageInfo) { + auto info = infoForUUID(imageInfo.uuid); + auto runtimePath = info.runtimePath; + mappedMachOs.emplace_back(runtimePath, info.mh, info.size, false, false, info.sliceFileOffset, 0, 0); +} + +std::vector Manifest::dylibsForCache(const std::string& configuration, const std::string& architecture) +{ + std::vector retval; + const auto& dylibs = _configurations[configuration].architectures[architecture].results.dylibs; + for (const auto& dylib : dylibs) { + if (dylib.second.included) { + insert(retval, dylib.second); + } + } + return retval; +} + +std::vector Manifest::otherDylibsAndBundles(const std::string& configuration, const std::string& architecture) +{ + std::vector retval; + const auto& dylibs = _configurations[configuration].architectures[architecture].results.dylibs; + for (const auto& dylib : dylibs) { + if (!dylib.second.included) { + insert(retval, dylib.second); + } + } + + const auto& bundles = _configurations[configuration].architectures[architecture].results.bundles; + for (const auto& bundle : bundles) { + insert(retval, bundle.second); + } + + return retval; +} + +std::vector Manifest::mainExecutables(const std::string& configuration, const std::string& architecture) +{ + std::vector retval; + const auto& executables = _configurations[configuration].architectures[architecture].results.executables; + for (const auto& executable : executables) { + insert(retval, executable.second); + } + + return retval; +} + +bool Manifest::filterForConfig(const std::string& configName) +{ + for (const auto configuration : _configurations) { + if (configName == configuration.first) { + std::map filteredConfigs; + filteredConfigs[configName] = configuration.second; + + _configurations = filteredConfigs; + + for (auto& arch : configuration.second.architectures) { + arch.second.results = Manifest::Results(); + } + return true; + } + } + return false; +} + +void Manifest::dedupeDispositions(void) { + // Since this is all hacky and inference based for now only do it for iOS until XBS + // is reved to give us real info. All the other platforms are way smaller anyway. + if (_platform != Platform::iOS) + return; + + std::map, std::set> dispositionSets; + + for (const auto& configuration : _configurations) { + dispositionSets[std::make_pair(configuration.second.device, configuration.second.disposition)].insert(configuration.first); + } + + for (const auto& dSet : dispositionSets) { + for (const auto &c1 : dSet.second) { + for (const auto &c2 : dSet.second) { + _configurations[c1].metabomTags.insert(_configurations[c2].metabomTag); + } + } + } +} + +void Manifest::calculateClosure() +{ + auto closureSemaphore = dispatch_semaphore_create(32); + auto closureGroup = dispatch_group_create(); + auto closureQueue = dispatch_queue_create("com.apple.dyld.cache.closure", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, 0)); + + dedupeDispositions(); + for (auto& config : _configurations) { + for (auto& arch : config.second.architectures) { + dispatch_semaphore_wait(closureSemaphore, DISPATCH_TIME_FOREVER); + dispatch_group_async(closureGroup, closureQueue, [&] { + calculateClosure(config.first, arch.first); + dispatch_semaphore_signal(closureSemaphore); + }); + } + } + + dispatch_group_wait(closureGroup, DISPATCH_TIME_FOREVER); +} + +void Manifest::remove(const std::string& config, const std::string& arch) +{ + if (_configurations.count(config)) + _configurations[config].architectures.erase(arch); +} + +void Manifest::removeDylib(MachOParser parser, const std::string& reason, const std::string& configuration, + const std::string& architecture, std::unordered_set& processedIdentifiers) +{ +#if 0 + 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(parser->uuid()) == 0) { + archManifest.results.dylibs[parser->uuid()].uuid = parser->uuid(); + archManifest.results.dylibs[parser->uuid()].installname = parser->installName(); + processedIdentifiers.insert(parser->uuid()); + } + archManifest.results.exclude(MachOProxy::forIdentifier(parser->uuid(), architecture), reason); + + processedIdentifiers.insert(parser->uuid()); + + for (const auto& dependent : proxy->dependentIdentifiers) { + auto dependentProxy = MachOProxy::forIdentifier(dependent, architecture); + auto dependentResultIter = archManifest.results.dylibs.find(dependentProxy->identifier); + if ( dependentProxy && + ( dependentResultIter == archManifest.results.dylibs.end() || dependentResultIter->second.included == true ) ) { + removeDylib(dependentProxy, "Missing dependency: " + proxy->installName, configuration, architecture, + processedIdentifiers); + } + } +#endif +} + +const std::string Manifest::removeLargestLeafDylib(const std::set& configurations, const std::string& architecture) +{ + // Find the leaf nodes + __block std::map dependentCounts; + for (const auto& dylib : _configurations[*configurations.begin()].architectures[architecture].results.dylibs) { + if (!dylib.second.included) + continue; + std::string installName; + auto info = infoForUUID(dylib.first); + auto parser = MachOParser(info.mh); + dependentCounts[parser.installName()] = 0; + } + + for (const auto& dylib : _configurations[*configurations.begin()].architectures[architecture].results.dylibs) { + if (!dylib.second.included) + continue; + auto info = infoForUUID(dylib.first); + auto parser = MachOParser(info.mh); + parser.forEachDependentDylib(^(const char *loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) { + if (!isWeak) { + dependentCounts[loadPath]++; + } + }); + } + + // Figure out which leaf is largest + UUIDInfo largestLeaf; + + for (const auto& dependentCount : dependentCounts) { + if (dependentCount.second == 0) { + auto info = infoForInstallNameAndarch(dependentCount.first, architecture); + assert(info.mh != nullptr); + if (info.size > largestLeaf.size) { + largestLeaf = info; + } + } + } + + if (largestLeaf.mh == nullptr) { + _diags.error("Fatal overflow, could not evict more dylibs"); + return ""; + } + + // Remove it ferom all configs + for (const auto& config : configurations) { + configuration(config).architecture(architecture).results.exclude(*this, largestLeaf.uuid, "Cache Overflow"); + } + + return largestLeaf.installName; +} + +void Manifest::calculateClosure(const std::string& configuration, const std::string& architecture) +{ + __block auto& configManifest = _configurations[configuration]; + __block auto& archManifest = _configurations[configuration].architectures[architecture]; + __block std::set newUuids; + std::set processedUuids; + std::set cachedUUIDs; + + // Seed anchors + for (auto& uuidInfo : _uuidMap) { + auto info = uuidInfo.second; + if (info.arch != architecture) { + continue; + } + + auto i = _metabomTagMap.find(info.runtimePath); + assert(i != _metabomTagMap.end()); + auto tags = i->second; + if (!is_disjoint(tags, configManifest.metabomTags)) { + newUuids.insert(info.uuid); + + } + } + + // Pull in all dependencies + while (!newUuids.empty()) { + std::set uuidsToProcess = newUuids; + newUuids.clear(); + + for (const auto& uuid : uuidsToProcess) { + if (processedUuids.count(uuid) > 0) { + continue; + } + processedUuids.insert(uuid); + + auto parser = parserForUUID(uuid); + auto runtimePath = runtimePathForUUID(uuid); + assert(parser.header() != 0); + + parser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + auto i = _installNameMap.find(std::make_pair(loadPath, architecture)); + if (i != _installNameMap.end()) + newUuids.insert(i->second); + }); + + if (parser.fileType() == MH_DYLIB) { + // Add the dylib to the results + if (archManifest.results.dylibs.count(uuid) == 0 ) { + archManifest.results.dylibs[uuid].uuid = uuid; + archManifest.results.dylibs[uuid].installname = parser.installName(); + } + + // HACK to insert device specific dylib closures into all caches + if ( parser.installName() == std::string("/System/Library/Caches/com.apple.xpc/sdk.dylib") + || parser.installName() == std::string("/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib") ) { + archManifest.results.exclude(&parser, "Device specific dylib"); + continue; + } + + std::set reasons; + if (parser.canBePlacedInDyldCache(runtimePath, reasons)) { + auto i = _metabomTagMap.find(runtimePath); + assert(i != _metabomTagMap.end()); + auto restrictions = _metabomRestrictedTagMap.find(configuration); + if (restrictions != _metabomRestrictedTagMap.end() && !is_disjoint(restrictions->second, i->second)) { + archManifest.results.exclude(&parser, "Dylib '" + runtimePath + "' removed due to explict restriction"); + } + + // It can be placed in the cache, grab its dependents and queue them for inclusion + cachedUUIDs.insert(parser.uuid()); + } else { + // It can't be placed in the cache, print out the reasons why + std::string reasonString = "Rejected from cached dylibs: " + runtimePath + " " + architecture + " (\""; + for (auto i = reasons.begin(); i != reasons.end(); ++i) { + reasonString += *i; + if (i != --reasons.end()) { + reasonString += "\", \""; + } + } + reasonString += "\")"; + archManifest.results.exclude(&parser, reasonString); + } + } else if (parser.fileType() == MH_BUNDLE) { + if (archManifest.results.bundles.count(uuid) == 0) { + archManifest.results.bundles[uuid].uuid = uuid; + } + } else if (parser.fileType() == MH_EXECUTE) { + //HACK exclude all launchd and installd variants until we can do something about xpcd_cache.dylib and friends + if (runtimePath == "/sbin/launchd" + || runtimePath == "/usr/local/sbin/launchd.debug" + || runtimePath == "/usr/local/sbin/launchd.development" + || runtimePath == "/usr/libexec/installd") { + continue; + } + if (archManifest.results.executables.count(uuid) == 0) { + archManifest.results.executables[uuid].uuid = uuid; + } + } + } + } + + __block std::set removedUUIDs; + __block bool doAgain = true; + + //Trim out dylibs that are missing dependencies + while ( doAgain ) { + doAgain = false; + for (const auto& uuid : cachedUUIDs) { + __block std::set badDependencies; + __block auto parser = parserForUUID(uuid); + parser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if (isWeak) + return; + + auto i = _installNameMap.find(std::make_pair(loadPath, architecture)); + if (i == _installNameMap.end() || removedUUIDs.count(i->second)) { + removedUUIDs.insert(uuid); + badDependencies.insert(loadPath); + doAgain = true; + } + + if (badDependencies.size()) { + std::string reasonString = "Rejected from cached dylibs: " + std::string(parser.installName()) + " " + architecture + " (\""; + for (auto i = badDependencies.begin(); i != badDependencies.end(); ++i) { + reasonString += *i; + if (i != --badDependencies.end()) { + reasonString += "\", \""; + } + } + reasonString += "\")"; + archManifest.results.exclude(&parser, reasonString); + } + }); + } + + for (const auto& removedUUID : removedUUIDs) { + cachedUUIDs.erase(removedUUID); + } + } + + //Trim out excluded leaf dylibs + __block std::set linkedDylibs; + + for(const auto& uuid : cachedUUIDs) { + auto parser = parserForUUID(uuid); + parser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + linkedDylibs.insert(loadPath); + }); + } + + for(const auto& uuid : cachedUUIDs) { + auto info = infoForUUID(uuid); + auto i = _metabomTagMap.find(info.runtimePath); + assert(i != _metabomTagMap.end()); + auto exclusions = _metabomExcludeTagMap.find(configuration); + if (exclusions == _metabomExcludeTagMap.end() || is_disjoint(exclusions->second, i->second)) + continue; + + if (linkedDylibs.count(info.installName) != 0) + continue; + + archManifest.results.exclude(*this, info.uuid, "Dylib '" + info.runtimePath + "' excluded leaf node"); + } +} + +void Manifest::writeJSON(const std::string& path) { + NSMutableDictionary* jsonDict = [[NSMutableDictionary alloc] init]; + for (auto& configuration : _configurations) { + jsonDict[cppToObjStr(configuration.first)] = [[NSMutableDictionary alloc] init]; + + for (auto& arch : configuration.second.architectures) { + NSMutableOrderedSet* includedDylibsSet = [[NSMutableOrderedSet alloc] init]; + NSMutableOrderedSet* executablesSet = [[NSMutableOrderedSet alloc] init]; + NSMutableOrderedSet* otherSet = [[NSMutableOrderedSet alloc] init]; + for (auto& dylib : arch.second.results.dylibs) { + NSString *runtimePath = cppToObjStr(runtimePathForUUID(dylib.second.uuid)); + if (dylib.second.included) { + [includedDylibsSet addObject:runtimePath]; + } else { + [otherSet addObject:runtimePath]; + } + } + + for (auto& executable : arch.second.results.executables) { + NSString *runtimePath = cppToObjStr(runtimePathForUUID(executable.second.uuid)); + [executablesSet addObject:runtimePath]; + } + + for (auto& bundle : arch.second.results.bundles) { + NSString *runtimePath = cppToObjStr(runtimePathForUUID(bundle.second.uuid)); + [otherSet addObject:runtimePath]; + } + + [includedDylibsSet sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { + return [obj1 compare:obj2]; + }]; + + [executablesSet sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { + return [obj1 compare:obj2]; + }]; + + [otherSet sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { + return [obj1 compare:obj2]; + }]; + + jsonDict[cppToObjStr(configuration.first)][cppToObjStr(arch.first)] = @{ @"cachedDylibs" : [includedDylibsSet array], @"mainExecutables" : [executablesSet array], @"other" : [otherSet array]};; + } + } + + NSError* error = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDict options:0x0 error:&error]; + (void)[jsonData writeToFile:cppToObjStr(path) atomically:YES]; +} + +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"] = @(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) { + dylibDict[@"included"] = @YES; + } else { + dylibDict[@"included"] = @NO; + dylibDict[@"exclusionInfo"] = cppToObjStr(dylib.second.exclusionInfo); + } + dylibsDict[cppToObjStr(dylib.second.installname)] = dylibDict; + } + + 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; + } + + switch (platform()) { + case Platform::iOS: + cacheDict[@"platform"] = @"ios"; + break; + case Platform::tvOS: + cacheDict[@"platform"] = @"tvos"; + break; + case Platform::watchOS: + cacheDict[@"platform"] = @"watchos"; + break; + case Platform::bridgeOS: + cacheDict[@"platform"] = @"bridgeos"; + break; + case Platform::macOS: + cacheDict[@"platform"] = @"macos"; + break; + case Platform::unknown: + cacheDict[@"platform"] = @"unknown"; + break; + } + + 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/dyld3/shared-cache/ObjC1Abstraction.hpp b/dyld3/shared-cache/ObjC1Abstraction.hpp new file mode 100644 index 0000000..93bf751 --- /dev/null +++ b/dyld3/shared-cache/ObjC1Abstraction.hpp @@ -0,0 +1,231 @@ +/* -*- 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@ + */ + +#define OBJC_IMAGE_SUPPORTS_GC (1<<1) +#define OBJC_IMAGE_REQUIRES_GC (1<<2) + +template +struct objc_image_info { + uint32_t version; + uint32_t 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 = P::E::get32(flags); P::E::set32(flags, old | bits); } + void setOptimizedByDyld() INLINE { setFlag(1<<3); } +}; + +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 P::E::get32(method_name); } + void setName(uint32_t newName) INLINE { P::E::set32(method_name, newName); } +}; + +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]; + + 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 +struct objc_class { + uint32_t isa; // struct objc_class * + uint32_t super_class; // struct objc_class * + uint32_t name; // const char * + uint32_t version; // long + uint32_t info; // long + uint32_t instance_size; // long + uint32_t ivars; // struct objc_ivar_list * + uint32_t methodList; // struct objc_method_list * + uint32_t method_cache; // struct objc_cache * + uint32_t protocols; // objc_protocol_list * + uint32_t ivar_layout; // const char * + uint32_t ext; // struct objc_class_ext * + + 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 +struct objc_category { + uint32_t category_name; // char * + uint32_t class_name; // char * + uint32_t instance_methods; // struct objc_method_list * + uint32_t class_methods; // struct objc_method_list * + uint32_t protocols; // objc_protocol_list * + uint32_t size; // uint32_t + uint32_t instance_properties; // struct objc_property_list * + + 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 +struct objc_symtab { + uint32_t sel_ref_cnt; // unsigned long + uint32_t refs; // SEL * + uint16_t cls_def_cnt; // unsigned short + uint16_t cat_def_cnt; // unsigned short + uint32_t defs[0]; // void * + + 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 +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(ContentAccessor* cache) const INLINE { return (struct objc_symtab

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

list[0]; + + uint32_t getCount() const INLINE { return P::E::get32(count); } +}; + +template +struct objc_protocol { + uint32_t isa; // danger! contains strange values! + uint32_t protocol_name; // const char * + uint32_t protocol_list; // struct objc_protocol_list + uint32_t instance_methods; // struct objc_method_description_list * + uint32_t class_methods; // struct objc_method_description_list * + + 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 +class LegacySelectorUpdater { + typedef typename P::uint_t pint_t; + + static void visitMethodList(objc_method_list

*mlist, V& visitor) + { + for (uint32_t m = 0; m < mlist->getCount(); m++) { + 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) + { + 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(ContentAccessor* cache, const macho_header

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

*symtab = modules.get(m).getSymtab(cache); + if (!symtab) continue; + + // Method lists in classes + 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); + } + if ((mlist = isa->getMethodList(cache))) { + visitMethodList(mlist, visitor); + } + } + + // Method lists from categories + 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); + } + if ((mlist = cat->getClassMethods(cache))) { + visitMethodList(mlist, visitor); + } + } + } + + // Method description lists from protocols + 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; + if ((mlist = protocol.getInstanceMethodDescriptions(cache))) { + visitMethodDescriptionList(mlist, visitor); + } + if ((mlist = protocol.getClassMethodDescriptions(cache))) { + visitMethodDescriptionList(mlist, visitor); + } + } + + // 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); + selrefs.setVMAddress(s, newValue); + } + } +}; diff --git a/dyld3/shared-cache/ObjC2Abstraction.hpp b/dyld3/shared-cache/ObjC2Abstraction.hpp new file mode 100644 index 0000000..59aa455 --- /dev/null +++ b/dyld3/shared-cache/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, Diagnostics& diag) { + if (sizeof(*this) < offsetof(objc_protocol_t

, demangledName) + sizeof(demangledName)) + diag.error("objc protocol has the wrong size"); + else + 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. + cache->diagnostics().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/dyld3/shared-cache/OptimizerBranches.cpp b/dyld3/shared-cache/OptimizerBranches.cpp new file mode 100644 index 0000000..332e045 --- /dev/null +++ b/dyld3/shared-cache/OptimizerBranches.cpp @@ -0,0 +1,1466 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "StringUtils.h" +#include "Trie.hpp" +#include "MachOFileAbstraction.hpp" +#include "MachOParser.h" +#include "Diagnostics.h" +#include "DyldSharedCache.h" +#include "CacheBuilder.h" + +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 +}; + + +static uint64_t branchPoolTextSize(const std::string& archName) +{ + if ( startsWith(archName, "arm64") ) + return 0x0000C000; // 48KB + else + return 0; +} + +static uint64_t branchPoolLinkEditSize(const std::string& archName) +{ + if ( startsWith(archName, "arm64") ) + return 0x00100000; // 1MB + else + return 0; +} + + +template +class BranchPoolDylib { +public: + BranchPoolDylib(DyldSharedCache* cache, uint64_t startAddr, + uint64_t textRegionStartAddr, uint64_t poolLinkEditStartAddr, uint64_t poolLinkEditStartOffset, Diagnostics& diags); + + 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: + Diagnostics& _diagnostics; + 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; + + DyldSharedCache* _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(DyldSharedCache* cache, uint64_t poolStartAddr, + uint64_t textRegionStartAddr, uint64_t poolLinkEditStartAddr, uint64_t poolLinkEditStartOffset, Diagnostics& diags) + : _cacheBuffer(cache), _startAddr(poolStartAddr), _nextIndex(0), _firstStubOffset(0x280), _diagnostics(diags) +{ + std::string archName = cache->archName(); + bool is64 = (sizeof(typename P::uint_t) == 8); + + const uint64_t textSegSize = branchPoolTextSize(archName); + const uint64_t linkEditSegSize = branchPoolLinkEditSize(archName); + 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*)cache + poolStartAddr - textRegionStartAddr); + mh->set_magic(is64 ? MH_MAGIC_64 : MH_MAGIC); + mh->set_cputype(dyld3::MachOParser::cpuTypeFromArchName(archName)); + mh->set_cpusubtype(dyld3::MachOParser::cpuSubtypeFromArchName(archName)); + 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(archName)); + 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*)cache + 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 ) { + _diagnostics.verbose("branch islands in image at 0x%0llX:\n", _startAddr); + for (uint32_t i=0; i < _nextIndex; ++i) { + _diagnostics.verbose(" 0x%llX %s\n", 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) { + _diagnostics.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) { + _diagnostics.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() +{ + _diagnostics.verbose(" island pool at 0x%0llX has %u stubs and stringPool size=%lu\n", _startAddr, _nextIndex, _nextString - _stringPoolStart); +} + + + +template +class StubOptimizer { +public: + StubOptimizer(void* cacheBuffer, macho_header

* mh, Diagnostics& diags); + 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: + Diagnostics _diagnostics; + 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, Diagnostics& diags) +: _mh(mh), _cacheBuffer(cacheBuffer), _diagnostics(diags) +{ + _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 ) { + _diagnostics.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 ) { + _diagnostics.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 ) { + _diagnostics.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 ) { + _diagnostics.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 ) { + _diagnostics.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() ) { + _diagnostics.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() ) { + _diagnostics.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* 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]); + lpVMAddr = (pint_t)sect->addr() + j * sizeof(pint_t); + if ( symbolIndex >= _symTabCmd->nsyms() ) { + _diagnostics.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() ) { + _diagnostics.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) ) { + _diagnostics.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; + } + } + } + } + } + cmd = (const macho_load_command

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

::forEachCallSiteToAStub(CallSiteHandler handler) +{ + if (_diagnostics.hasError()) + return; + const uint8_t* infoStart = &_linkeditBias[_splitSegInfoCmd->dataoff()]; + const uint8_t* infoEnd = &infoStart[_splitSegInfoCmd->datasize()]; + if ( *infoStart++ != DYLD_CACHE_ADJ_V2_FORMAT ) { + _diagnostics.error("malformed split seg info in %s", _installName); + return; + } + + 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) { + _diagnostics.error("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)) ) { + _diagnostics.error("thumb branch out of range at 0x%0X in %s", instrAddr, _installName); + return 0; + } + 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 ) { + _diagnostics.error("no pc-rel thumb branch instruction that switches to arm mode at 0x%0X in %s", instrAddr, _installName); + return 0; + } + } + else { + _diagnostics.error("not b/bl/blx at 0x%0X in %s", instrAddr, _installName); + return 0; + } + 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 ){ + _diagnostics.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 ) { + _diagnostics.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); + if (_diagnostics.hasError()) + return false; + _branchesDirectCount++; + return true; + } + } + else if ( kind == DYLD_CACHE_ADJ_V2_ARM_BR24 ) { + // too few of these to be worth trying to optimize + } + + return false; + }); + if (_diagnostics.hasError()) + return; +} + + +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 ) { + _diagnostics.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 + _diagnostics.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 + _diagnostics.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; + }); + if (_diagnostics.hasError()) + return; +} + + +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 ) { + _diagnostics.verbose("%5u branches in __text, %5u changed to direct branches, %5u changed to use islands for %s\n", + _branchesCount, _branchesDirectCount, _branchesIslandCount, _installName); + } + break; + case CPU_TYPE_ARM: + optimizeArmCallSites(); + optimizeArmStubs(); + if ( verbose ) { + _diagnostics.verbose("%3u of %3u stubs optimized. %5u branches in __text, %5u changed to direct branches for %s\n", + _stubOptimizedCount, _stubCount, _branchesCount, _branchesDirectCount, _installName); + } + break; + } +} + +template +void bypassStubs(DyldSharedCache* cache, const std::string& archName, const std::vector& branchPoolStartAddrs, + const char* const neverStubEliminateDylibs[], Diagnostics& diags) +{ + diags.verbose("Stub elimination optimization:\n"); + + // construct a StubOptimizer for each image + __block std::vector*> optimizers; + cache->forEachImage(^(const mach_header* mh, const char* installName) { + optimizers.push_back(new StubOptimizer

((void*)cache, (macho_header

*)mh, diags)); + }); + + // construct a BranchPoolDylib for each pool + std::vector*> pools; + + if ( startsWith(archName, "arm64") ) { + // Find hole at end of linkedit region for branch pool linkedits + __block uint64_t textRegionStartAddr = 0; + __block uint64_t linkEditRegionStartAddr = 0; + __block uint64_t linkEditRegionEndAddr = 0; + __block uint64_t linkEditRegionStartCacheOffset = 0; + cache->forEachRegion(^(const 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*)cache; + } + }); + __block uint64_t lastLinkEditRegionUsedOffset = 0; + cache->forEachImage(^(const mach_header* mh, const char* installName) { + dyld3::MachOParser parser(mh); + parser.forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) { + if ( strcmp(segName, "__LINKEDIT") == 0 ) { + if ( fileOffset >= lastLinkEditRegionUsedOffset ) + lastLinkEditRegionUsedOffset = fileOffset + vmSize; + } + }); + }); + 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

(cache, poolAddr, textRegionStartAddr, poolLinkEditStartAddr, poolLinkEditStartOffset, diags)); + 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* const* d=neverStubEliminateDylibs; *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) ) { + diags.error("malformed exports trie in %s", *d); + return; + } + 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; + } + diags.verbose(" cache contains %u call sites of which %u were direct bound and %u were bound through islands\n", callSiteCount, callSiteDirectOptCount, callSiteOneHopOptCount); + + // clean up + for (StubOptimizer

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

* p : pools) + delete p; + +} + +void bypassStubs(DyldSharedCache* cache, const std::vector& branchPoolStartAddrs, const char* const neverStubEliminateDylibs[], Diagnostics& diags) +{ + std::string archName = cache->archName(); + if ( startsWith(archName, "arm64") ) + bypassStubs>(cache, archName, branchPoolStartAddrs, neverStubEliminateDylibs, diags); + else if ( archName == "armv7k" ) + bypassStubs>(cache, archName, branchPoolStartAddrs, neverStubEliminateDylibs, diags); + // no stub optimization done for other arches +} + + +/* +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/dyld3/shared-cache/OptimizerLinkedit.cpp b/dyld3/shared-cache/OptimizerLinkedit.cpp new file mode 100644 index 0000000..84e2a65 --- /dev/null +++ b/dyld3/shared-cache/OptimizerLinkedit.cpp @@ -0,0 +1,1180 @@ +/* -*- 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 "MachOFileAbstraction.hpp" +#include "Trie.hpp" +#include "DyldSharedCache.h" +#include "CacheBuilder.h" + + +#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; + } + + size_t size() { + size_t size = 1; + for (auto& entry : _map) { + size += (entry.first.size() + 1); + } + return size; + } + + +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, Diagnostics& diag); + + 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; + Diagnostics& _diagnostics; + 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(DyldSharedCache* cache, uint64_t linkeditStartAddr, Diagnostics& diag, 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, Diagnostics& diag, 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; + dyld_cache_accelerator_info _acceleratorInfoHeader; +}; + + +template +void AcceleratorTables

::AcceleratorTables::DepNode::verifyUnreachable(AcceleratorTables

::DepNode* target, struct AcceleratorTables

::NodeChain& chain, Diagnostics& diag, + 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 == target ) { + foundCycle = true; + break; + } + } + 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; + } + diag.warning("%s", msg.c_str()); + return; + } + + if ( visitedNodes.count(node) ) + continue; + visitedNodes.insert(node); + NodeChain nextChain; + nextChain.prev = &chain; + nextChain.node = node; + verifyUnreachable(target, nextChain, diag, visitedNodes, node->_dependents); + } +} + +const uint16_t kBranchIslandDylibIndex = 0x7FFF; + +template +AcceleratorTables

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

* op : optimizers ) { + _machHeaderToOptimizer[op->machHeader()] = op; + } + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((uint8_t*)cache + cache->header.mappingOffset); + uint64_t cacheStartAddress = mappings[0].address; + const dyld_cache_image_info* images = (dyld_cache_image_info*)((uint8_t*)cache + cache->header.imagesOffset); + for (unsigned i=0; i < cache->header.imagesCount; ++i) { + uint64_t segCacheFileOffset = images[i].address - cacheStartAddress; + macho_header

* mhMapped = (macho_header

*)((uint8_t*)cache+segCacheFileOffset); + const char* path = (char*)cache + 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; + } + + + // 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, diag, 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 + dyld_cache_image_info_extra emptyExtra; + emptyExtra.exportsTrieAddr = 0; + emptyExtra.weakBindingsAddr = 0; + emptyExtra.exportsTrieSize = 0; + emptyExtra.weakBindingsSize = 0; + emptyExtra.dependentsStartArrayIndex = 0; + emptyExtra.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].dependentsStartArrayIndex = 0; + } + else { + _extraInfo[index].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].reExportsStartArrayIndex = 0; + } + else { + _extraInfo[index].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()); + dyld_cache_accelerator_initializer entry; + entry.functionOffset = (uint32_t)(initializer-cacheStartAddress); + entry.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()); + dyld_cache_accelerator_dof entry; + entry.sectionAddress = sect->addr(); + entry.sectionSize = (uint32_t)sect->size(); + entry.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].exportsTrieAddr = op->exportsTrieLinkEditOffset() + linkeditStartAddr; + _extraInfo[index].exportsTrieSize = op->exportsTrieLinkEditSize(); + _extraInfo[index].weakBindingsAddr = op->weakBindingLinkEditOffset() + linkeditStartAddr; + _extraInfo[index].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; + dyld_cache_range_entry entry; + entry.startAddress = segCmd->vmaddr(); + entry.size = (uint32_t)segCmd->vmsize(); + entry.imageIndex = imageIndex; + _rangeTable.push_back(entry); + } + } + std::sort(_rangeTable.begin(), _rangeTable.end(), + [&](const dyld_cache_range_entry& lRange, const dyld_cache_range_entry& 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 + _acceleratorInfoHeader.version = 1; + _acceleratorInfoHeader.imageExtrasCount = (uint32_t)_extraInfo.size(); + _acceleratorInfoHeader.imagesExtrasOffset = ALIGN_AS_TYPE(sizeof(dyld_cache_accelerator_info), dyld_cache_image_info_extra); + _acceleratorInfoHeader.bottomUpListOffset = _acceleratorInfoHeader.imagesExtrasOffset + _acceleratorInfoHeader.imageExtrasCount*sizeof(dyld_cache_image_info_extra); + _acceleratorInfoHeader.dylibTrieOffset = _acceleratorInfoHeader.bottomUpListOffset + _acceleratorInfoHeader.imageExtrasCount*sizeof(uint16_t); + _acceleratorInfoHeader.dylibTrieSize = (uint32_t)_trieBytes.size(); + _acceleratorInfoHeader.initializersOffset = ALIGN_AS_TYPE(_acceleratorInfoHeader.dylibTrieOffset + _acceleratorInfoHeader.dylibTrieSize, dyld_cache_accelerator_initializer); + _acceleratorInfoHeader.initializersCount = (uint32_t)_initializers.size(); + _acceleratorInfoHeader.dofSectionsOffset = ALIGN_AS_TYPE(_acceleratorInfoHeader.initializersOffset + _acceleratorInfoHeader.initializersCount*sizeof(dyld_cache_accelerator_initializer), dyld_cache_accelerator_initializer); + _acceleratorInfoHeader.dofSectionsCount = (uint32_t)_dofSections.size(); + _acceleratorInfoHeader.reExportListOffset = ALIGN_AS_TYPE(_acceleratorInfoHeader.dofSectionsOffset + _acceleratorInfoHeader.dofSectionsCount*sizeof(dyld_cache_accelerator_dof), dyld_cache_accelerator_dof); + _acceleratorInfoHeader.reExportCount = (uint32_t)_reExportArray.size(); + _acceleratorInfoHeader.depListOffset = ALIGN_AS_TYPE(_acceleratorInfoHeader.reExportListOffset + _acceleratorInfoHeader.reExportCount*sizeof(uint16_t), uint16_t); + _acceleratorInfoHeader.depListCount = (uint32_t)_dependencyArray.size(); + _acceleratorInfoHeader.rangeTableOffset = ALIGN_AS_TYPE(_acceleratorInfoHeader.depListOffset + _acceleratorInfoHeader.depListCount*sizeof(uint16_t), dyld_cache_range_entry); + _acceleratorInfoHeader.rangeTableCount = (uint32_t)_rangeTable.size(); + _acceleratorInfoHeader.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, Diagnostics& diag) +: _mh(mh), _cacheBuffer(cacheBuffer), _diagnostics(diag) +{ + _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(DyldSharedCache* cache, bool dontMapLocalSymbols, bool addAcceleratorTables, std::vector*>& optimizers, Diagnostics& diagnostics, dyld_cache_local_symbols_info** localsInfo) +{ + // 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; + + diagnostics.verbose("Merged LINKEDIT:\n"); + + // copy weak binding info + uint32_t startWeakBindInfosOffset = offset; + for (LinkeditOptimizer

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

* op : optimizers) { + op->copyExportInfo(newLinkEdit, offset); + } + diagnostics.verbose(" exports info size: %5uKB\n", (uint32_t)(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); + } + diagnostics.verbose(" bindings size: %5uKB\n", (uint32_t)(offset-startBindingsInfosOffset)/1024); + + // copy lazy binding info + uint32_t startLazyBindingsInfosOffset = offset; + for (LinkeditOptimizer

* op : optimizers) { + op->copyLazyBindingInfo(newLinkEdit, offset); + } + diagnostics.verbose(" lazy bindings size: %5uKB\n", (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); + } + diagnostics.verbose(" function starts size: %5uKB\n", (offset-startFunctionStartsOffset)/1024); + + // copy data-in-code info + uint32_t startDataInCodeOffset = offset; + for (LinkeditOptimizer

* op : optimizers) { + op->copyDataInCode(newLinkEdit, offset); + } + diagnostics.verbose(" data in code size: %5uKB\n", (offset-startDataInCodeOffset)/1024); + + // 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); + diagnostics.verbose(" symbol table size: %5uKB (%d exports, %d imports)\n", (sharedSymbolTableEndOffset-sharedSymbolTableStartOffset)/1024, sharedSymbolTableExportsCount, sharedSymbolTableImportsCount); + diagnostics.verbose(" symbol string pool size: %5uKB\n", sharedSymbolStringsSize/1024); + + // overwrite mapped LINKEDIT area in cache with new merged LINKEDIT content + diagnostics.verbose("LINKEDITS optimized from %uMB to %uMB\n", (uint32_t)totalUnoptLinkeditsSize/(1024*1024), (uint32_t)newLinkeditUnalignedSize/(1024*1024)); + ::memcpy((char*)cache + linkeditStartOffset, newLinkEdit, newLinkeditUnalignedSize); + ::bzero((char*)cache + linkeditStartOffset+newLinkeditUnalignedSize, totalUnoptLinkeditsSize-newLinkeditUnalignedSize); + ::free(newLinkEdit); + + // If making cache for customers, add extra accelerator tables for dyld + if ( addAcceleratorTables ) { + AcceleratorTables

tables(cache, linkeditStartAddr, diagnostics, optimizers); + uint32_t tablesSize = tables.totalSize(); + if ( tablesSize < (totalUnoptLinkeditsSize-newLinkeditUnalignedSize) ) { + tables.copyTo((uint8_t*)cache+newLinkeditEnd); + newLinkeditEnd += tablesSize; + uint64_t accelInfoAddr = align(linkeditStartAddr + newLinkeditUnalignedSize, 14); + cache->header.accelerateInfoAddr = accelInfoAddr; + cache->header.accelerateInfoSize = tablesSize; + diagnostics.verbose("Accelerator tables %uMB\n", (uint32_t)tablesSize/(1024*1024)); + } + else { + diagnostics.warning("not enough room to add dyld accelerator tables"); + } + } + + // update mapping to reduce linkedit size + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)cache + cache->header.mappingOffset); + mappings[2].size = newLinkeditEnd - mappings[2].fileOffset; + + // 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; + const uint32_t entriesOffset = sizeof(dyld_cache_local_symbols_info); + const uint32_t entriesCount = (uint32_t)localSymbolInfos.size(); + const uint32_t nlistOffset = (uint32_t)align(entriesOffset + entriesCount * sizeof(dyld_cache_local_symbols_info), 4); // 16-byte align start + const uint32_t nlistCount = (uint32_t)unmappedLocalSymbols.size(); + const uint32_t stringsSize = (uint32_t)localSymbolsStringPool.size(); + const uint32_t stringsOffset = nlistOffset + nlistCount * sizeof(macho_nlist

); + // allocate buffer for local symbols + const size_t localsBufferSize = align(stringsOffset + stringsSize, 14); + dyld_cache_local_symbols_info* infoHeader = (dyld_cache_local_symbols_info*)malloc(localsBufferSize); + // fill in header info + infoHeader->nlistOffset = nlistOffset; + infoHeader->nlistCount = nlistCount; + infoHeader->stringsOffset = stringsOffset; + infoHeader->stringsSize = stringsSize; + infoHeader->entriesOffset = entriesOffset; + infoHeader->entriesCount = entriesCount; + // copy info for each dylib + dyld_cache_local_symbols_entry* entries = (dyld_cache_local_symbols_entry*)(((uint8_t*)infoHeader)+entriesOffset); + for (int i=0; i < entriesCount; ++i) { + entries[i].dylibOffset = localSymbolInfos[i].dylibOffset; + entries[i].nlistStartIndex = localSymbolInfos[i].nlistStartIndex; + entries[i].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 + localSymbolsStringPool.copyPoolAndUpdateOffsets(((char*)infoHeader)+stringsOffset, newLocalsSymbolTable); + // return buffer of local symbols, caller to free() it + *localsInfo = infoHeader; + } + + // 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 +uint64_t optimizeLinkedit(DyldSharedCache* cache, bool dontMapLocalSymbols, bool addAcceleratorTables, const std::vector& branchPoolOffsets, Diagnostics& diag, dyld_cache_local_symbols_info** localsInfo) +{ + // construct a LinkeditOptimizer for each image + __block std::vector*> optimizers; + cache->forEachImage(^(const mach_header* mh, const char*) { + optimizers.push_back(new LinkeditOptimizer

(cache, (macho_header

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

* mh = (macho_header

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

(cache, mh, diag)); + } +#endif + // merge linkedit info + uint64_t newFileSize = mergeLinkedits(cache, dontMapLocalSymbols, addAcceleratorTables, optimizers, diag, localsInfo); + + // delete optimizers + for (LinkeditOptimizer

* op : optimizers) + delete op; + + return newFileSize; +} + +uint64_t optimizeLinkedit(DyldSharedCache* cache, bool is64, bool dontMapLocalSymbols, bool addAcceleratorTables, const std::vector& branchPoolOffsets, Diagnostics& diag, dyld_cache_local_symbols_info** localsInfo) +{ + if ( is64) { + return optimizeLinkedit>(cache, dontMapLocalSymbols, addAcceleratorTables, branchPoolOffsets, diag, localsInfo); + } + else { + return optimizeLinkedit>(cache, dontMapLocalSymbols, addAcceleratorTables, branchPoolOffsets, diag, localsInfo); + } +} + + + diff --git a/dyld3/shared-cache/OptimizerObjC.cpp b/dyld3/shared-cache/OptimizerObjC.cpp new file mode 100644 index 0000000..71fe9dc --- /dev/null +++ b/dyld3/shared-cache/OptimizerObjC.cpp @@ -0,0 +1,820 @@ +/* -*- 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 "DyldSharedCache.h" +#include "Diagnostics.h" +#include "CacheBuilder.h" +#include "FileAbstraction.hpp" +#include "MachOFileAbstraction.hpp" + + +// 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(const DyldSharedCache* cache, Diagnostics& diag) + : _diagnostics(diag) + { + __block int index = 0; + cache->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + _regions[index++] = { (uint8_t*)content, (uint8_t*)content+size, vmAddr, vmAddr+size }; + }); + } + + void* contentForVMAddr(uint64_t vmaddr) { + for (const Info& info : _regions) { + if ( (info.startAddr <= vmaddr) && (vmaddr < info.endAddr) ) + return (void*)(info.contentStart + vmaddr - info.startAddr); + } + if ( vmaddr != 0 ) + _diagnostics.error("invalid vmaddr 0x%0llX in ObjC data", vmaddr); + return nullptr; + } + + uint64_t vmAddrForContent(const void* content) { + for (const Info& info : _regions) { + if ( (info.contentStart <= content) && (content < info.contentEnd) ) + return info.startAddr + ((uint8_t*)content - (uint8_t*)info.contentStart); + } + _diagnostics.error("invalid content pointer %p in ObjC data", content); + return 0; + } + + Diagnostics& diagnostics() { return _diagnostics; } + +private: + struct Info { uint8_t* contentStart; uint8_t* contentEnd; uint64_t startAddr; uint64_t endAddr; }; + Diagnostics& _diagnostics; + Info _regions[3]; +}; + + +// 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 ) { + _cache->diagnostics().error("index out of range in section %s", _section->sectname()); + return 0; + } + 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 ) { + _cache->diagnostics().error("index out of range in section %s", _section->sectname()); + return; + } + 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) { + _cache->diagnostics().error("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; + Diagnostics& _diagnostics; + + 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(Diagnostics& diag) + : _protocolCount(0), _protocolReferenceCount(0), _diagnostics(diag) { + } + + 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

)) { + _diagnostics.error("objc protocol is too big"); + return; + } + + 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, _diagnostics); + } + 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(DyldSharedCache* cache, bool forProduction, std::vector& pointersForASLR, Diagnostics& diag) +{ + typedef typename P::E E; + typedef typename P::uint_t pint_t; + + diag.verbose("Optimizing objc metadata:\n"); + diag.verbose(" cache type is %s\n", forProduction ? "production" : "development"); + + ContentAccessor cacheAccessor(cache, diag); + + size_t headerSize = P::round_up(sizeof(objc_opt::objc_opt_t)); + if (headerSize != sizeof(objc_opt::objc_opt_t)) { + diag.warning("libobjc's optimization structure size is wrong (metadata not optimized)"); + } + + // + // Find libobjc's empty sections and build list of images with objc metadata + // + __block const macho_section

*optROSection = nullptr; + __block const macho_section

*optRWSection = nullptr; + __block const macho_section

*optPointerListSection = nullptr; + __block std::vector*> objcDylibs; + cache->forEachImage(^(const mach_header* machHeader, const char* installName) { + 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 ) { + diag.warning("libobjc's read-only section missing (metadata not optimized)"); + return; + } + if ( optRWSection == nullptr ) { + diag.warning("libobjc's read/write section missing (metadata not optimized)"); + return; + } + if ( optPointerListSection == nullptr ) { + diag.warning("libobjc's pointer list section missing (metadata not optimized)"); + return; + } + + uint8_t* optROData = (uint8_t*)cacheAccessor.contentForVMAddr(optROSection->addr()); + if ( optROData == nullptr ) { + diag.warning("libobjc's read-only section has bad content"); + return; + } + size_t optRORemaining = optROSection->size(); + uint8_t* optRWData = (uint8_t*)cacheAccessor.contentForVMAddr(optRWSection->addr()); + size_t optRWRemaining = optRWSection->size(); + if (optRORemaining < headerSize) { + diag.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) { + diag.warning("libobjc's read-only section version is unrecognized (metadata not optimized)"); + return; + } + + if (optPointerListSection->size() < sizeof(objc_opt::objc_opt_pointerlist_tt)) { + diag.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) { + diag.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) { + diag.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); + } + + diag.verbose(" uniqued %6lu selectors\n", uniq.strings().size()); + diag.verbose(" updated %6lu selector references\n", 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) { + diag.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; + + diag.verbose(" selector table occupancy %u/%u (%u%%)\n", + 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) { + diag.error("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); + } + + diag.verbose(" recorded % 6ld classes\n", 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) { + diag.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; + + diag.verbose(" found % 6ld duplicate classes\n", + duplicateCount); + diag.verbose(" class table occupancy %u/%u (%u%%)\n", + 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); + } + + diag.verbose(" sorted % 6ld method lists\n", 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(diag); + for (const macho_header

* mh : sizeSortedDylibs) { + protocolOptimizer.addProtocols(&cacheAccessor, mh); + } + + diag.verbose(" uniqued % 6ld protocols\n", + protocolOptimizer.protocolCount()); + + pint_t protocolClassVMAddr = (pint_t)P::getP(optPointerList->protocolClass); + err = protocolOptimizer.writeProtocols(&cacheAccessor, + optRWData, optRWRemaining, + optROData, optRORemaining, + pointersForASLR, protocolClassVMAddr); + if (err) { + diag.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) { + diag.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; + + diag.verbose(" protocol table occupancy %u/%u (%u%%)\n", + 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); + } + + diag.verbose(" updated % 6ld protocol references\n", 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); + } + + diag.verbose(" updated % 6ld ivar offsets\n", 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; + diag.verbose(" %lu/%llu bytes (%d%%) used in libobjc read-only optimization section\n", + roSize, optROSection->size(), percent(roSize, optROSection->size())); + diag.verbose(" %lu/%llu bytes (%d%%) used in libobjc read/write optimization section\n", + rwSize, optRWSection->size(), percent(rwSize, optRWSection->size())); + diag.verbose(" wrote objc metadata optimization version %d\n", objc_opt::VERSION); +} + + +} // anon namespace + +void optimizeObjC(DyldSharedCache* cache, bool is64, bool customerCache, std::vector& pointersForASLR, Diagnostics& diag) +{ + if ( is64 ) + optimizeObjC>(cache, customerCache, pointersForASLR, diag); + else + optimizeObjC>(cache, customerCache, pointersForASLR, diag); +} + + diff --git a/dyld3/shared-cache/StringUtils.h b/dyld3/shared-cache/StringUtils.h new file mode 100644 index 0000000..701e5fd --- /dev/null +++ b/dyld3/shared-cache/StringUtils.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017 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 StringUtils_h +#define StringUtils_h + +#include + +inline bool startsWith(const std::string& str, const std::string& prefix) +{ + return std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end(); +} + +inline bool endsWith(const std::string& str, const std::string& suffix) +{ + std::size_t index = str.find(suffix, str.size() - suffix.size()); + return (index != std::string::npos); +} + +inline bool contains(const std::string& str, const std::string& search) +{ + std::size_t index = str.find(search); + return (index != std::string::npos); +} + +inline char hexDigit(uint8_t value) +{ + if ( value < 10 ) + return '0' + value; + else + return 'a' + value - 10; +} + +inline void bytesToHex(const uint8_t* bytes, size_t byteCount, char buffer[]) +{ + char* p = buffer; + for (int i=0; i < byteCount; ++i) { + *p++ = hexDigit(bytes[i] >> 4); + *p++ = hexDigit(bytes[i] & 0x0F); + } + *p++ = '\0'; +} + +inline uint8_t hexCharToUInt(const char hexByte, uint8_t& value) { + if (hexByte >= '0' && hexByte <= '9') { + value = hexByte - '0'; + return true; + } else if (hexByte >= 'A' && hexByte <= 'F') { + value = hexByte - 'A' + 10; + return true; + } else if (hexByte >= 'a' && hexByte <= 'f') { + value = hexByte - 'a' + 10; + return true; + } + + return false; +} + +inline uint64_t hexToUInt64(const char* startHexByte, const char** endHexByte) { + if (startHexByte == nullptr) + return 0; + uint64_t retval = 0; + if (startHexByte[0] == '0' && startHexByte[1] == 'x') { + startHexByte +=2; + } + *endHexByte = startHexByte + 16; + + //FIXME overrun? + for (uint32_t i = 0; i < 16; ++i) { + uint8_t value; + if (!hexCharToUInt(startHexByte[i], value)) { + *endHexByte = &startHexByte[i]; + break; + } + retval = (retval << 4) + value; + } + return retval; +} + +inline bool hexToBytes(const char* startHexByte, uint32_t length, uint8_t buffer[]) { + if (startHexByte == nullptr) + return false; + const char *currentHexByte = startHexByte; + for (uint32_t i = 0; i < length; ++i) { + uint8_t value; + if (!hexCharToUInt(currentHexByte[i], value)) { + return false; + } + if (i%2 == 0) { + buffer[i/2] = value << 4; + } else { + buffer[(i-1)/2] |= value; + } + } + return true; +} + +#endif // StringUtils_h + diff --git a/dyld3/shared-cache/Trie.hpp b/dyld3/shared-cache/Trie.hpp new file mode 100644 index 0000000..e021095 --- /dev/null +++ b/dyld3/shared-cache/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 + +#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() : address(0), flags(0), other(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/dyld3/shared-cache/dyld_cache_format.h b/dyld3/shared-cache/dyld_cache_format.h new file mode 100644 index 0000000..de63bf9 --- /dev/null +++ b/dyld3/shared-cache/dyld_cache_format.h @@ -0,0 +1,279 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2006-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 __DYLD_CACHE_FORMAT__ +#define __DYLD_CACHE_FORMAT__ + +#include +#include + + +struct dyld_cache_header +{ + char magic[16]; // e.g. "dyld_v0 i386" + uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info + uint32_t mappingCount; // number of dyld_cache_mapping_info entries + uint32_t imagesOffset; // file offset to first dyld_cache_image_info + uint32_t imagesCount; // number of dyld_cache_image_info entries + uint64_t dyldBaseAddress; // base address of dyld when cache was built + uint64_t codeSignatureOffset; // file offset of code signature blob + uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) + uint64_t slideInfoOffset; // file offset of kernel slid info + uint64_t slideInfoSize; // size of kernel slid info + 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; // 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 + uint64_t dylibsImageGroupAddr; // (unslid) address of ImageGroup for dylibs in this cache + uint64_t dylibsImageGroupSize; // size of ImageGroup for dylibs in this cache + uint64_t otherImageGroupAddr; // (unslid) address of ImageGroup for other OS dylibs + uint64_t otherImageGroupSize; // size of oImageGroup for other OS dylibs + uint64_t progClosuresAddr; // (unslid) address of list of program launch closures + uint64_t progClosuresSize; // size of list of program launch closures + uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures + uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures + uint32_t platform; // platform number (macOS=1, etc) + uint32_t formatVersion : 8, // launch_cache::binary_format::kFormatVersion + dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid + simulator : 1; // for simulator of specified platform + uint64_t sharedRegionStart; // base load address of cache if not slid + uint64_t sharedRegionSize; // overall size of region cache can be mapped into + uint64_t maxSlide; // runtime slide of cache can be between zero and this value +}; + + +struct dyld_cache_mapping_info { + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint32_t maxProt; + uint32_t initProt; +}; + +struct dyld_cache_image_info +{ + uint64_t address; + uint64_t modTime; + uint64_t inode; + uint32_t pathFileOffset; + 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 + uint32_t toc_offset; + uint32_t toc_count; + uint32_t entries_offset; + uint32_t entries_count; + uint32_t entries_size; // currently 128 + // uint16_t toc[toc_count]; + // entrybitmap entries[entries_count]; +}; + + +// 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 + uint32_t nlistCount; // count of nlist entries + uint32_t stringsOffset; // offset into this chunk of string pool + uint32_t stringsSize; // byte count of string pool + uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry + uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array +}; + +struct dyld_cache_local_symbols_entry +{ + uint32_t dylibOffset; // offset in cache file of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib +}; + + + +#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; + + + + +#endif // __DYLD_CACHE_FORMAT__ + + diff --git a/dyld3/shared-cache/dyld_closure_util.cpp b/dyld3/shared-cache/dyld_closure_util.cpp new file mode 100644 index 0000000..db01f5f --- /dev/null +++ b/dyld3/shared-cache/dyld_closure_util.cpp @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2017 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 "LaunchCache.h" +#include "LaunchCacheWriter.h" +#include "DyldSharedCache.h" +#include "FileUtils.h" +#include "ImageProxy.h" +#include "StringUtils.h" +#include "ClosureBuffer.h" + +extern "C" { + #include "closuredProtocol.h" +} + +static const DyldSharedCache* mapCacheFile(const char* path) +{ + struct stat statbuf; + if (stat(path, &statbuf)) { + fprintf(stderr, "Error: stat failed for dyld shared cache at %s\n", path); + return nullptr; + } + + int cache_fd = open(path, O_RDONLY); + if (cache_fd < 0) { + fprintf(stderr, "Error: failed to open shared cache file at %s\n", path); + return nullptr; + } + + void* mapped_cache = mmap(NULL, (size_t)statbuf.st_size, PROT_READ, MAP_PRIVATE, cache_fd, 0); + if (mapped_cache == MAP_FAILED) { + fprintf(stderr, "Error: mmap() for shared cache at %s failed, errno=%d\n", path, errno); + return nullptr; + } + close(cache_fd); + + return (DyldSharedCache*)mapped_cache; +} + +struct CachedSections +{ + uint32_t mappedOffsetStart; + uint32_t mappedOffsetEnd; + uint64_t vmAddress; + const mach_header* mh; + std::string segmentName; + std::string sectionName; + const char* dylibPath; +}; + +static const CachedSections& find(uint32_t mappedOffset, const std::vector& sections) +{ + for (const CachedSections& entry : sections) { + //printf("0x%08X -> 0x%08X\n", entry.mappedOffsetStart, entry.mappedOffsetEnd); + if ( (entry.mappedOffsetStart <= mappedOffset) && (mappedOffset < entry.mappedOffsetEnd) ) + return entry; + } + assert(0 && "invalid offset"); +} + +/* +static const dyld3::launch_cache::BinaryClosureData* +callClosureDaemon(const std::string& mainPath, const std::string& cachePath, const std::vector& envArgs) +{ + + mach_port_t serverPort = MACH_PORT_NULL; + mach_port_t bootstrapPort = MACH_PORT_NULL; + kern_return_t kr = task_get_bootstrap_port(mach_task_self(), &bootstrapPort); + kr = bootstrap_look_up(bootstrapPort, "com.apple.dyld.closured", &serverPort); + switch( kr ) { + case BOOTSTRAP_SUCCESS : + // service currently registered, "a good thing" (tm) + break; + case BOOTSTRAP_UNKNOWN_SERVICE : + // service not currently registered, try again later + fprintf(stderr, "bootstrap_look_up(): %s\n", mach_error_string(kr)); + return nullptr; + default: + // service not currently registered, try again later + fprintf(stderr, "bootstrap_look_up(): %s [%d]\n", mach_error_string(kr), kr); + return nullptr; + } + + //printf("serverPort=%d, replyPort=%d\n", serverPort, replyPort); + + + + bool success; + char envBuffer[2048]; + vm_offset_t reply = 0; + uint32_t replySize = 0; + envBuffer[0] = '\0'; + envBuffer[1] = '\0'; +// kr = closured_CreateLaunchClosure(serverPort, mainPath.c_str(), cachePath.c_str(), uuid, envBuffer, &success, &reply, &replySize); + + printf("success=%d, buf=%p, bufLen=%d\n", success, (void*)reply, replySize); + + if (!success) + return nullptr; + return (const dyld3::launch_cache::BinaryClosureData*)reply; +} +*/ + +static void usage() +{ + printf("dyld_closure_util program to create of view dyld3 closures\n"); + printf(" mode:\n"); + printf(" -create_closure # create a closure for the specified main executable\n"); + printf(" -create_image_group # create an ImageGroup for the specified dylib/bundle\n"); + printf(" -list_dyld_cache_closures # list all closures in the dyld shared cache with size\n"); + printf(" -list_dyld_cache_other_dylibs # list all group-1 (non-cached dylibs/bundles)\n"); + printf(" -print_image_group # print specified ImageGroup file as JSON\n"); + printf(" -print_closure_file # print specified closure file as JSON\n"); + printf(" -print_dyld_cache_closure # find closure for specified program in dyld cache and print as JSON\n"); + printf(" -print_dyld_cache_dylibs # print group-0 (cached dylibs) as JSON\n"); + printf(" -print_dyld_cache_other_dylibs # print group-1 (non-cached dylibs/bundles) as JSON\n"); + printf(" -print_dyld_cache_other # print just one group-1 (non-cached dylib/bundle) as JSON\n"); + printf(" -print_dyld_cache_patch_table # print locations in shared cache that may need patching\n"); + printf(" options:\n"); + printf(" -cache_file # path to cache file to use (default is current cache)\n"); + printf(" -build_root # when building a closure, the path prefix when runtime volume is not current boot volume\n"); + printf(" -o # when building a closure, the file to write the (binary) closure to\n"); + printf(" -include_all_dylibs_in_dir # when building a closure, add other mach-o files found in directory\n"); + printf(" -env # when building a closure, DYLD_* env vars to assume\n"); + printf(" -dlopen # for use with -create_closure to append ImageGroup if target had called dlopen\n"); + printf(" -verbose_fixups # for use with -print* options to force printing fixups\n"); +} + +int main(int argc, const char* argv[]) +{ + const char* cacheFilePath = nullptr; + const char* inputMainExecutablePath = nullptr; + const char* inputTopImagePath = nullptr; + const char* outPath = nullptr; + const char* printPath = nullptr; + const char* printGroupPath = nullptr; + const char* printCacheClosure = nullptr; + const char* printCachedDylib = nullptr; + const char* printOtherDylib = nullptr; + bool listCacheClosures = false; + bool listOtherDylibs = false; + bool includeAllDylibs = false; + bool printClosures = false; + bool printCachedDylibs = false; + bool printOtherDylibs = false; + bool printPatchTable = false; + bool useClosured = false; + bool verboseFixups = false; + std::vector buildtimePrefixes; + std::vector envArgs; + std::vector dlopens; + + if ( argc == 1 ) { + usage(); + return 0; + } + + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + if ( strcmp(arg, "-cache_file") == 0 ) { + cacheFilePath = argv[++i]; + if ( cacheFilePath == nullptr ) { + fprintf(stderr, "-cache_file option requires path to cache file\n"); + return 1; + } + } + else if ( strcmp(arg, "-create_closure") == 0 ) { + inputMainExecutablePath = argv[++i]; + if ( inputMainExecutablePath == nullptr ) { + fprintf(stderr, "-create_closure option requires a path to an executable\n"); + return 1; + } + } + else if ( strcmp(arg, "-create_image_group") == 0 ) { + inputTopImagePath = argv[++i]; + if ( inputTopImagePath == nullptr ) { + fprintf(stderr, "-create_image_group option requires a path to a dylib or bundle\n"); + return 1; + } + } + else if ( strcmp(arg, "-dlopen") == 0 ) { + const char* path = argv[++i]; + if ( path == nullptr ) { + fprintf(stderr, "-dlopen option requires a path to a packed closure list\n"); + return 1; + } + dlopens.push_back(path); + } + else if ( strcmp(arg, "-verbose_fixups") == 0 ) { + verboseFixups = true; + } + else if ( strcmp(arg, "-build_root") == 0 ) { + const char* buildRootPath = argv[++i]; + if ( buildRootPath == nullptr ) { + fprintf(stderr, "-build_root option requires a path \n"); + return 1; + } + buildtimePrefixes.push_back(buildRootPath); + } + else if ( strcmp(arg, "-o") == 0 ) { + outPath = argv[++i]; + if ( outPath == nullptr ) { + fprintf(stderr, "-o option requires a path \n"); + return 1; + } + } + else if ( strcmp(arg, "-print_closure_file") == 0 ) { + printPath = argv[++i]; + if ( printPath == nullptr ) { + fprintf(stderr, "-print_closure_file option requires a path \n"); + return 1; + } + } + else if ( strcmp(arg, "-print_image_group") == 0 ) { + printGroupPath = argv[++i]; + if ( printGroupPath == nullptr ) { + fprintf(stderr, "-print_image_group option requires a path \n"); + return 1; + } + } + else if ( strcmp(arg, "-list_dyld_cache_closures") == 0 ) { + listCacheClosures = true; + } + else if ( strcmp(arg, "-list_dyld_cache_other_dylibs") == 0 ) { + listOtherDylibs = true; + } + else if ( strcmp(arg, "-print_dyld_cache_closure") == 0 ) { + printCacheClosure = argv[++i]; + if ( printCacheClosure == nullptr ) { + fprintf(stderr, "-print_dyld_cache_closure option requires a path \n"); + return 1; + } + } + else if ( strcmp(arg, "-print_dyld_cache_closures") == 0 ) { + printClosures = true; + } + else if ( strcmp(arg, "-print_dyld_cache_dylibs") == 0 ) { + printCachedDylibs = true; + } + else if ( strcmp(arg, "-print_dyld_cache_other_dylibs") == 0 ) { + printOtherDylibs = true; + } + else if ( strcmp(arg, "-print_dyld_cache_dylib") == 0 ) { + printCachedDylib = argv[++i]; + if ( printCachedDylib == nullptr ) { + fprintf(stderr, "-print_dyld_cache_dylib option requires a path \n"); + return 1; + } + } + else if ( strcmp(arg, "-print_dyld_cache_other") == 0 ) { + printOtherDylib = argv[++i]; + if ( printOtherDylib == nullptr ) { + fprintf(stderr, "-print_dyld_cache_other option requires a path \n"); + return 1; + } + } + else if ( strcmp(arg, "-print_dyld_cache_patch_table") == 0 ) { + printPatchTable = true; + } + else if ( strcmp(arg, "-include_all_dylibs_in_dir") == 0 ) { + includeAllDylibs = true; + } + else if ( strcmp(arg, "-env") == 0 ) { + const char* envArg = argv[++i]; + if ( (envArg == nullptr) || (strchr(envArg, '=') == nullptr) ) { + fprintf(stderr, "-env option requires KEY=VALUE\n"); + return 1; + } + envArgs.push_back(envArg); + } + else if ( strcmp(arg, "-use_closured") == 0 ) { + useClosured = true; + } + else { + fprintf(stderr, "unknown option %s\n", arg); + return 1; + } + } + + if ( (inputMainExecutablePath || inputTopImagePath) && printPath ) { + fprintf(stderr, "-create_closure and -print_closure_file are mutually exclusive"); + return 1; + } + + const DyldSharedCache* dyldCache = nullptr; + bool dyldCacheIsRaw = false; + if ( cacheFilePath != nullptr ) { + dyldCache = mapCacheFile(cacheFilePath); + dyldCacheIsRaw = true; + } + else { +#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) + size_t cacheLength; + dyldCache = (DyldSharedCache*)_dyld_get_shared_cache_range(&cacheLength); + dyldCacheIsRaw = false; +#endif + } + dyld3::ClosureBuffer::CacheIdent cacheIdent; + dyldCache->getUUID(cacheIdent.cacheUUID); + cacheIdent.cacheAddress = (unsigned long)dyldCache; + cacheIdent.cacheMappedSize = dyldCache->mappedSize(); + dyld3::DyldCacheParser cacheParser(dyldCache, dyldCacheIsRaw); + + if ( buildtimePrefixes.empty() ) + buildtimePrefixes.push_back(""); + + std::vector existingGroups; + const dyld3::launch_cache::BinaryClosureData* mainClosure = nullptr; + if ( inputMainExecutablePath != nullptr ) { + dyld3::PathOverrides pathStuff(envArgs); + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3+dlopens.size(), theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + dyld3::launch_cache::DynArray groupList(2, &theGroups[0]); + dyld3::ClosureBuffer clsBuffer(cacheIdent, inputMainExecutablePath, groupList, pathStuff); + + std::string mainPath = inputMainExecutablePath; + for (const std::string& prefix : buildtimePrefixes) { + if ( startsWith(mainPath, prefix) ) { + mainPath = mainPath.substr(prefix.size()); + if ( mainPath[0] != '/' ) + mainPath = "/" + mainPath; + break; + } + } + + Diagnostics closureDiag; + //if ( useClosured ) + // mainClosure = closured_makeClosure(closureDiag, clsBuffer); + // else + mainClosure = dyld3::ImageProxyGroup::makeClosure(closureDiag, clsBuffer, mach_task_self(), buildtimePrefixes); + if ( closureDiag.hasError() ) { + fprintf(stderr, "dyld_closure_util: %s\n", closureDiag.errorMessage().c_str()); + return 1; + } + for (const std::string& warn : closureDiag.warnings() ) + fprintf(stderr, "dyld_closure_util: warning: %s\n", warn.c_str()); + + dyld3::launch_cache::Closure closure(mainClosure); + if ( outPath != nullptr ) { + safeSave(mainClosure, closure.size(), outPath); + } + else { + dyld3::launch_cache::Closure theClosure(mainClosure); + theGroups[2] = theClosure.group().binaryData(); + if ( !dlopens.empty() ) + printf("[\n"); + closure.printAsJSON(dyld3::launch_cache::ImageGroupList(3, &theGroups[0]), true); + + int groupIndex = 3; + for (const char* path : dlopens) { + printf(",\n"); + dyld3::launch_cache::DynArray groupList2(groupIndex-2, &theGroups[2]); + dyld3::ClosureBuffer dlopenBuffer(cacheIdent, path, groupList2, pathStuff); + Diagnostics dlopenDiag; + //if ( useClosured ) + // theGroups[groupIndex] = closured_makeDlopenGroup(closureDiag, clsBuffer); + //else + theGroups[groupIndex] = dyld3::ImageProxyGroup::makeDlopenGroup(dlopenDiag, dlopenBuffer, mach_task_self(), buildtimePrefixes); + if ( dlopenDiag.hasError() ) { + fprintf(stderr, "dyld_closure_util: %s\n", dlopenDiag.errorMessage().c_str()); + return 1; + } + for (const std::string& warn : dlopenDiag.warnings() ) + fprintf(stderr, "dyld_closure_util: warning: %s\n", warn.c_str()); + dyld3::launch_cache::ImageGroup dlopenGroup(theGroups[groupIndex]); + dlopenGroup.printAsJSON(dyld3::launch_cache::ImageGroupList(groupIndex+1, &theGroups[0]), true); + ++groupIndex; + } + if ( !dlopens.empty() ) + printf("]\n"); + } + + } +#if 0 + else if ( inputTopImagePath != nullptr ) { + std::string imagePath = inputTopImagePath; + for (const std::string& prefix : buildtimePrefixes) { + if ( startsWith(imagePath, prefix) ) { + imagePath = imagePath.substr(prefix.size()); + if ( imagePath[0] != '/' ) + imagePath = "/" + imagePath; + break; + } + } + + Diagnostics igDiag; + existingGroups.push_back(dyldCache->cachedDylibsGroup()); + existingGroups.push_back(dyldCache->otherDylibsGroup()); + if ( existingClosuresPath != nullptr ) { + size_t mappedSize; + const void* imageGroups = mapFileReadOnly(existingClosuresPath, mappedSize); + if ( imageGroups == nullptr ) { + fprintf(stderr, "dyld_closure_util: could not read file %s\n", printPath); + return 1; + } + uint32_t sentGroups = *(uint32_t*)imageGroups; + uint16_t lastGroupNum = 2; + existingGroups.resize(sentGroups+2); + const uint8_t* p = (uint8_t*)(imageGroups)+4; + //const uint8_t* end = (uint8_t*)(imageGroups) + mappedSize; + for (uint32_t i=0; i < sentGroups; ++i) { + const dyld3::launch_cache::binary_format::ImageGroup* aGroup = (const dyld3::launch_cache::binary_format::ImageGroup*)p; + existingGroups[2+i] = aGroup; + dyld3::launch_cache::ImageGroup imgrp(aGroup); + lastGroupNum = imgrp.groupNum(); + p += imgrp.size(); + } + } + const dyld3::launch_cache::binary_format::ImageGroup* ig = dyld3::ImageProxyGroup::makeDlopenGroup(igDiag, dyldCache, existingGroups.size(), existingGroups, imagePath, envArgs); + if ( igDiag.hasError() ) { + fprintf(stderr, "dyld_closure_util: %s\n", igDiag.errorMessage().c_str()); + return 1; + } + + dyld3::launch_cache::ImageGroup group(ig); + group.printAsJSON(dyldCache, true); + } +#endif + else if ( printPath != nullptr ) { + size_t mappedSize; + const void* buff = mapFileReadOnly(printPath, mappedSize); + if ( buff == nullptr ) { + fprintf(stderr, "dyld_closure_util: could not read file %s\n", printPath); + return 1; + } + dyld3::launch_cache::Closure theClosure((dyld3::launch_cache::binary_format::Closure*)buff); + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3, theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + theGroups[2] = theClosure.group().binaryData(); + theClosure.printAsJSON(theGroups, verboseFixups); + //closure.printStatistics(); + munmap((void*)buff, mappedSize); + } + else if ( printGroupPath != nullptr ) { + size_t mappedSize; + const void* buff = mapFileReadOnly(printGroupPath, mappedSize); + if ( buff == nullptr ) { + fprintf(stderr, "dyld_closure_util: could not read file %s\n", printPath); + return 1; + } + dyld3::launch_cache::ImageGroup group((dyld3::launch_cache::binary_format::ImageGroup*)buff); +// group.printAsJSON(dyldCache, verboseFixups); + munmap((void*)buff, mappedSize); + } + else if ( listCacheClosures ) { + cacheParser.forEachClosure(^(const char* runtimePath, const dyld3::launch_cache::binary_format::Closure* closureBinary) { + dyld3::launch_cache::Closure closure(closureBinary); + printf("%6lu %s\n", closure.size(), runtimePath); + }); + } + else if ( listOtherDylibs ) { + dyld3::launch_cache::ImageGroup dylibGroup(cacheParser.otherDylibsGroup()); + for (uint32_t i=0; i < dylibGroup.imageCount(); ++i) { + dyld3::launch_cache::Image image = dylibGroup.image(i); + printf("%s\n", image.path()); + } + } + else if ( printCacheClosure ) { + const dyld3::launch_cache::BinaryClosureData* cls = cacheParser.findClosure(printCacheClosure); + if ( cls != nullptr ) { + dyld3::launch_cache::Closure theClosure(cls); + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3, theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + theGroups[2] = theClosure.group().binaryData(); + theClosure.printAsJSON(theGroups, verboseFixups); + } + else { + fprintf(stderr, "no closure in cache for %s\n", printCacheClosure); + } + } + else if ( printClosures ) { + cacheParser.forEachClosure(^(const char* runtimePath, const dyld3::launch_cache::binary_format::Closure* closureBinary) { + dyld3::launch_cache::Closure theClosure(closureBinary); + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3, theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + theGroups[2] = theClosure.group().binaryData(); + theClosure.printAsJSON(theGroups, verboseFixups); + }); + } + else if ( printCachedDylibs ) { + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 2, theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + dyld3::launch_cache::ImageGroup dylibGroup(theGroups[0]); + dylibGroup.printAsJSON(theGroups, verboseFixups); + } + else if ( printCachedDylib != nullptr ) { + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 2, theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + dyld3::launch_cache::ImageGroup dylibGroup(cacheParser.cachedDylibsGroup()); + uint32_t imageIndex; + const dyld3::launch_cache::binary_format::Image* binImage = dylibGroup.findImageByPath(printCachedDylib, imageIndex); + if ( binImage != nullptr ) { + dyld3::launch_cache::Image image(binImage); + image.printAsJSON(theGroups, true); + } + else { + fprintf(stderr, "no such other image found\n"); + } + } + else if ( printOtherDylibs ) { + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 2, theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + dyld3::launch_cache::ImageGroup dylibGroup(theGroups[1]); + dylibGroup.printAsJSON(theGroups, verboseFixups); + } + else if ( printOtherDylib != nullptr ) { + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 2, theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + dyld3::launch_cache::ImageGroup dylibGroup(cacheParser.otherDylibsGroup()); + uint32_t imageIndex; + const dyld3::launch_cache::binary_format::Image* binImage = dylibGroup.findImageByPath(printOtherDylib, imageIndex); + if ( binImage != nullptr ) { + dyld3::launch_cache::Image image(binImage); + image.printAsJSON(theGroups, true); + } + else { + fprintf(stderr, "no such other image found\n"); + } + } + else if ( printPatchTable ) { + __block uint64_t cacheBaseAddress = 0; + dyldCache->forEachRegion(^(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions) { + if ( cacheBaseAddress == 0 ) + cacheBaseAddress = vmAddr; + }); + __block std::vector sections; + __block bool hasError = false; + dyldCache->forEachImage(^(const mach_header* mh, const char* installName) { + dyld3::MachOParser parser(mh, dyldCacheIsRaw); + parser.forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, + uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop) { + if ( illegalSectionSize ) { + fprintf(stderr, "dyld_closure_util: section size extends beyond the end of the segment %s/%s\n", segName, sectionName); + stop = true; + return; + } + uint32_t offsetStart = (uint32_t)(addr - cacheBaseAddress); + uint32_t offsetEnd = (uint32_t)(offsetStart + size); + sections.push_back({offsetStart, offsetEnd, addr, mh, segName, sectionName, installName}); + }); + }); + if (hasError) + return 1; + dyld3::launch_cache::ImageGroup dylibGroup(cacheParser.cachedDylibsGroup()); + dylibGroup.forEachDyldCachePatchLocation(cacheParser, ^(uint32_t targetCacheVmOffset, const std::vector& usesPointersCacheVmOffsets, bool& stop) { + const CachedSections& targetSection = find(targetCacheVmOffset, sections); + dyld3::MachOParser targetParser(targetSection.mh, dyldCacheIsRaw); + const char* symbolName; + uint64_t symbolAddress; + if ( targetParser.findClosestSymbol(targetSection.vmAddress + targetCacheVmOffset - targetSection.mappedOffsetStart, &symbolName, &symbolAddress) ) { + printf("%s: [cache offset = 0x%08X]\n", symbolName, targetCacheVmOffset); + } + else { + printf("0x%08X from %40s %10s %16s + 0x%06X\n", targetCacheVmOffset, strrchr(targetSection.dylibPath, '/')+1, targetSection.segmentName.c_str(), targetSection.sectionName.c_str(), targetCacheVmOffset - targetSection.mappedOffsetStart); + } + for (uint32_t offset : usesPointersCacheVmOffsets) { + const CachedSections& usedInSection = find(offset, sections); + printf("%40s %10s %16s + 0x%06X\n", strrchr(usedInSection.dylibPath, '/')+1, usedInSection.segmentName.c_str(), usedInSection.sectionName.c_str(), offset - usedInSection.mappedOffsetStart); + } + }); + } + + + return 0; +} diff --git a/dyld3/shared-cache/dyld_shared_cache_builder.mm b/dyld3/shared-cache/dyld_shared_cache_builder.mm new file mode 100644 index 0000000..cbedce2 --- /dev/null +++ b/dyld3/shared-cache/dyld_shared_cache_builder.mm @@ -0,0 +1,381 @@ +/* -*- 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 "Manifest.h" +#include "Diagnostics.h" +#include "DyldSharedCache.h" +#include "BuilderUtils.h" +#include "FileUtils.h" +#include "StringUtils.h" +#include "MachOParser.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; + +static const char* tempRootDirTemplate = "/tmp/dyld_shared_cache_builder.XXXXXX"; +static char* tempRootDir = nullptr; + +int runCommandAndWait(Diagnostics& diags, const char* args[]) +{ + pid_t pid; + int status; + int res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ); + if (res != 0) + diags.error("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; + } + } + + return res; +} + +void processRoots(Diagnostics& diags, std::set& roots) +{ + std::set processedRoots; + struct stat sb; + int res = 0; + const char* args[8]; + + for (const auto& root : roots) { + res = stat(root.c_str(), &sb); + + if (res == 0 && S_ISDIR(sb.st_mode)) { + roots.insert(root); + return; + } else if (endsWith(root, ".cpio") || endsWith(root, ".cpio.gz") || endsWith(root, ".cpgz") || endsWith(root, ".cpio.bz2") || endsWith(root, ".cpbz2") || endsWith(root, ".pax") || endsWith(root, ".pax.gz") || endsWith(root, ".pgz") || endsWith(root, ".pax.bz2") || endsWith(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 (endsWith(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 (endsWith(root, ".tar.gz") || endsWith(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 (endsWith(root, ".tar.bz2") + || endsWith(root, ".tbz2") + || endsWith(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 (endsWith(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 (endsWith(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 { + diags.error("unknown archive type: %s", root.c_str()); + continue; + } + + if (res != runCommandAndWait(diags, args)) { + fprintf(stderr, "Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res); + exit(-1); + } + for (auto& existingRoot : processedRoots) { + if (existingRoot == tempRootDir) + return; + } + + processedRoots.insert(tempRootDir); + } + + roots = processedRoots; +} + +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 { + __block Diagnostics diags; + std::set roots; + std::string dylibCacheDir; + std::string release; + bool emitDevCaches = true; + bool emitElidedDylibs = true; + bool listConfigs = false; + bool copyRoots = false; + bool debug = false; + std::string dstRoot; + std::string configuration; + std::string resultPath; + + 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) { + diags = Diagnostics(true); + debug = true; + } else if (strcmp(arg, "-list_configs") == 0) { + listConfigs = true; + } else if (strcmp(arg, "-root") == 0) { + roots.insert(realPath(argv[++i])); + } else if (strcmp(arg, "-copy_roots") == 0) { + copyRoots = true; + } else if (strcmp(arg, "-dylib_cache") == 0) { + dylibCacheDir = realPath(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 = realPath(argv[++i]); + } else if (strcmp(arg, "-release") == 0) { + release = argv[++i]; + } else if (strcmp(arg, "-results") == 0) { + resultPath = realPath(argv[++i]); + } else { + //usage(); + diags.error("unknown option: %s\n", arg); + } + } else { + if (!configuration.empty()) { + diags.error("You may only specify one configuration"); + } + configuration = argv[i]; + } + } + + time_t mytime = time(0); + fprintf(stderr, "Started: %s", asctime(localtime(&mytime))); + processRoots(diags, roots); + + struct rlimit rl = { OPEN_MAX, OPEN_MAX }; + (void)setrlimit(RLIMIT_NOFILE, &rl); + + if (dylibCacheDir.empty() && release.empty()) { + fprintf(stderr, "you must specify either -dylib_cache or -release"); + exit(-1); + } else if (!dylibCacheDir.empty() && !release.empty()) { + fprintf(stderr, "you may not use -dylib_cache and -release at the same time"); + exit(-1); + } + + if ((configuration.empty() || dstRoot.empty()) && !listConfigs) { + fprintf(stderr, "Must specify a configuration and a valid -dst_root OR -list_configs\n"); + exit(-1); + } + + 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()); + + dispatch_async(dispatch_get_main_queue(), ^{ + auto manifest = dyld3::Manifest(diags, dylibCacheDir + "/Manifest.plist", roots); + + if (manifest.build().empty()) { + fprintf(stderr, "No manifest found at '%s/Manifest.plist'\n", dylibCacheDir.c_str()); + exit(-1); + } + fprintf(stderr, "Building Caches for %s\n", manifest.build().c_str()); + + if (listConfigs) { + manifest.forEachConfiguration([](const std::string& configName) { + printf("%s\n", configName.c_str()); + }); + } + + if (!manifest.filterForConfig(configuration)) { + fprintf(stderr, "No config %s. Please run with -list_configs to see configurations available for this %s.\n", + configuration.c_str(), manifest.build().c_str()); + exit(-1); + } + manifest.calculateClosure(); + + std::vector buildQueue; + + bool cacheBuildSuccess = build(diags, manifest, dstRoot, false, debug, false, false); + + if (!cacheBuildSuccess) { + exit(-1); + } + + writeRootList(dstRoot, roots); + + if (copyRoots) { + manifest.forEachConfiguration([&manifest](const std::string& configName) { + for (auto& arch : manifest.configuration(configName).architectures) { + for (auto& dylib : arch.second.results.dylibs) { + if (dylib.second.included) { + dyld3::MachOParser parser = manifest.parserForUUID(dylib.first); + cachePaths.insert(parser.installName()); + } + } + } + }); + + BOMCopier copier = BOMCopierNewWithSys(BomSys_default()); + BOMCopierSetCopyFileStartedHandler(copier, filteredCopy); + for (auto& root : roots) { + BOMCopierCopy(copier, root.c_str(), dstRoot.c_str()); + } + BOMCopierFree(copier); + } + + + + + int err = sync_volume_np(dstRoot.c_str(), SYNC_VOLUME_FULLSYNC | SYNC_VOLUME_WAIT); + if (err) { + fprintf(stderr, "Volume sync failed errnor=%d (%s)\n", err, strerror(err)); + } + + // 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. + + if (!resultPath.empty()) { + manifest.write(resultPath); + } + + const char* args[8]; + args[0] = (char*)"/bin/rm"; + args[1] = (char*)"-rf"; + args[2] = (char*)tempRootDir; + args[3] = nullptr; + (void)runCommandAndWait(diags, args); + + for (const std::string& warn : diags.warnings()) { + fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", warn.c_str()); + } + exit(0); + }); + } + + dispatch_main(); + + return 0; +} diff --git a/dyld3/shared-cache/make_ios_dyld_cache.cpp b/dyld3/shared-cache/make_ios_dyld_cache.cpp new file mode 100644 index 0000000..588d7d8 --- /dev/null +++ b/dyld3/shared-cache/make_ios_dyld_cache.cpp @@ -0,0 +1,356 @@ +/* -*- 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 "MachOParser.h" +#include "FileUtils.h" +#include "StringUtils.h" +#include "DyldSharedCache.h" + + + +struct MappedMachOsByCategory +{ + std::string archName; + std::vector dylibsForCache; + std::vector otherDylibsAndBundles; + std::vector mainExecutables; +}; + +static bool verbose = false; + + +static bool addIfMachO(const std::string& buildRootPath, const std::string& runtimePath, const struct stat& statBuf, dyld3::Platform platform, std::vector& files) +{ + // read start of file to determine if it is mach-o or a fat file + std::string fullPath = buildRootPath + runtimePath; + int fd = ::open(fullPath.c_str(), O_RDONLY); + if ( fd < 0 ) + return false; + bool result = false; + const void* wholeFile = ::mmap(NULL, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if ( wholeFile != MAP_FAILED ) { + Diagnostics diag; + bool usedWholeFile = false; + for (MappedMachOsByCategory& file : files) { + size_t sliceOffset; + size_t sliceLength; + bool fatButMissingSlice; + const void* slice = MAP_FAILED; + if ( dyld3::FatUtil::isFatFileWithSlice(diag, wholeFile, statBuf.st_size, file.archName, sliceOffset, sliceLength, fatButMissingSlice) ) { + slice = ::mmap(NULL, sliceLength, PROT_READ, MAP_PRIVATE, fd, sliceOffset); + if ( slice != MAP_FAILED ) { + //fprintf(stderr, "mapped slice at %p size=0x%0lX, offset=0x%0lX for %s\n", p, len, offset, fullPath.c_str()); + if ( !dyld3::MachOParser::isValidMachO(diag, file.archName, platform, slice, sliceLength, fullPath.c_str(), false) ) { + ::munmap((void*)slice, sliceLength); + slice = MAP_FAILED; + } + } + } + else if ( !fatButMissingSlice && dyld3::MachOParser::isValidMachO(diag, file.archName, platform, wholeFile, statBuf.st_size, fullPath.c_str(), false) ) { + slice = wholeFile; + sliceLength = statBuf.st_size; + sliceOffset = 0; + usedWholeFile = true; + //fprintf(stderr, "mapped whole file at %p size=0x%0lX for %s\n", p, len, inputPath.c_str()); + } + if ( slice != MAP_FAILED ) { + const mach_header* mh = (mach_header*)slice; + dyld3::MachOParser parser(mh); + if ( parser.platform() != platform ) { + fprintf(stderr, "skipped wrong platform binary: %s\n", fullPath.c_str()); + result = false; + } + else { + bool sip = true; // assume anything found in the simulator runtime is a platform binary + if ( parser.isDynamicExecutable() ) { + bool issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID)); + file.mainExecutables.emplace_back(runtimePath, mh, sliceLength, issetuid, sip, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + else { + if ( parser.canBePlacedInDyldCache(runtimePath) ) { + file.dylibsForCache.emplace_back(runtimePath, mh, sliceLength, false, sip, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + else { + file.otherDylibsAndBundles.emplace_back(runtimePath, mh, sliceLength, false, sip, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + } + result = true; + } + } + } + if ( !usedWholeFile ) + ::munmap((void*)wholeFile, statBuf.st_size); + } + ::close(fd); + return result; +} + + +static bool parsePathsFile(const std::string& filePath, std::vector& paths) { + 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.push_back(line); + } + myfile.close(); + return true; + } + return false; +} + + +static void mapAllFiles(const std::string& dylibsRootDir, const std::vector& paths, dyld3::Platform platform, std::vector& files) +{ + for (const std::string& runtimePath : paths) { + std::string fullPath = dylibsRootDir + runtimePath; + struct stat statBuf; + if ( (stat(fullPath.c_str(), &statBuf) != 0) || !addIfMachO(dylibsRootDir, runtimePath, statBuf, platform, files) ) + fprintf(stderr, "could not load: %s\n", fullPath.c_str()); + } +} + + + +inline uint32_t absolutetime_to_milliseconds(uint64_t abstime) +{ + return (uint32_t)(abstime/1000/1000); +} + + +#define TERMINATE_IF_LAST_ARG( s ) \ + do { \ + if ( i == argc - 1 ) { \ + fprintf(stderr, s ); \ + return 1; \ + } \ + } while ( 0 ) + +int main(int argc, const char* argv[]) +{ + std::string rootPath; + std::string dylibListFile; + bool force = false; + std::string cacheDir; + std::string dylibsList; + std::unordered_set archStrs; + + dyld3::Platform platform = dyld3::Platform::iOS; + + // parse command line options + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + if (strcmp(arg, "-debug") == 0) { + verbose = true; + } + else if (strcmp(arg, "-verbose") == 0) { + verbose = true; + } + else if (strcmp(arg, "-tvOS") == 0) { + platform = dyld3::Platform::tvOS; + } + else if (strcmp(arg, "-iOS") == 0) { + platform = dyld3::Platform::iOS; + } + else if (strcmp(arg, "-watchOS") == 0) { + platform = dyld3::Platform::watchOS; + } + else if ( strcmp(arg, "-root") == 0 ) { + TERMINATE_IF_LAST_ARG("-root missing path argument\n"); + rootPath = argv[++i]; + } + else if ( strcmp(arg, "-dylibs_list") == 0 ) { + TERMINATE_IF_LAST_ARG("-dylibs_list missing path argument\n"); + dylibsList = 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 { + //usage(); + fprintf(stderr, "update_dyld_sim_shared_cache: unknown option: %s\n", arg); + return 1; + } + } + + if ( cacheDir.empty() ) { + fprintf(stderr, "missing -cache_dir option to specify directory in which to write cache file(s)\n"); + return 1; + } + + if ( rootPath.empty() ) { + fprintf(stderr, "missing -runtime_dir option to specify directory which is root of simulator runtime)\n"); + return 1; + } + else { + // canonicalize rootPath + char resolvedPath[PATH_MAX]; + if ( realpath(rootPath.c_str(), resolvedPath) != NULL ) { + rootPath = resolvedPath; + } + if ( rootPath.back() != '/' ) + rootPath = rootPath + "/"; + } + + int err = mkpath_np(cacheDir.c_str(), S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + if ( (err != 0) && (err != EEXIST) ) { + fprintf(stderr, "mkpath_np fail: %d", err); + return 1; + } + + if ( archStrs.empty() ) { + switch ( platform ) { + case dyld3::Platform::iOS: + case dyld3::Platform::tvOS: + archStrs.insert("arm64"); + break; + case dyld3::Platform::watchOS: + archStrs.insert("armv7k"); + break; + case dyld3::Platform::unknown: + case dyld3::Platform::macOS: + assert(0 && "macOS not support with this tool"); + break; + } + } + + uint64_t t1 = mach_absolute_time(); + + // find all mach-o files for requested architectures + std::vector allFileSets; + if ( archStrs.count("arm64") ) + allFileSets.push_back({"arm64"}); + if ( archStrs.count("armv7k") ) + allFileSets.push_back({"armv7k"}); + std::vector paths; + parsePathsFile(dylibsList, paths); + mapAllFiles(rootPath, paths, platform, allFileSets); + + uint64_t t2 = mach_absolute_time(); + + fprintf(stderr, "time to scan file system and construct lists of mach-o files: %ums\n", absolutetime_to_milliseconds(t2-t1)); + + // build all caches in parallel + __block bool cacheBuildFailure = false; + dispatch_apply(allFileSets.size(), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) { + const MappedMachOsByCategory& fileSet = allFileSets[index]; + const std::string outFile = cacheDir + "/dyld_shared_cache_" + fileSet.archName; + + fprintf(stderr, "make %s cache with %lu dylibs, %lu other dylibs, %lu programs\n", fileSet.archName.c_str(), fileSet.dylibsForCache.size(), fileSet.otherDylibsAndBundles.size(), fileSet.mainExecutables.size()); + + // build cache new cache file + DyldSharedCache::CreateOptions options; + options.archName = fileSet.archName; + options.platform = platform; + options.excludeLocalSymbols = true; + options.optimizeStubs = false; + options.optimizeObjC = true; + options.codeSigningDigestMode = (platform() == dyld3::Platform::watchOS) ? + DyldSharedCache::Agile : DyldSharedCache::SHA256only; + options.dylibsRemovedDuringMastering = true; + options.inodesAreSameAsRuntime = false; + options.cacheSupportsASLR = true; + options.forSimulator = false; + options.verbose = verbose; + options.evictLeafDylibsOnOverflow = false; + options.pathPrefixes = { rootPath }; + DyldSharedCache::CreateResults results = DyldSharedCache::create(options, fileSet.dylibsForCache, fileSet.otherDylibsAndBundles, fileSet.mainExecutables); + + // print any warnings + for (const std::string& warn : results.warnings) { + fprintf(stderr, "update_dyld_sim_shared_cache: warning: %s %s\n", fileSet.archName.c_str(), warn.c_str()); + } + if ( !results.errorMessage.empty() ) { + // print error (if one) + fprintf(stderr, "update_dyld_sim_shared_cache: %s\n", results.errorMessage.c_str()); + cacheBuildFailure = true; + } + else { + // save new cache file to disk and write new .map file + assert(results.cacheContent != nullptr); + if ( !safeSave(results.cacheContent, results.cacheLength, outFile) ) + cacheBuildFailure = true; + if ( !cacheBuildFailure ) { + std::string mapStr = results.cacheContent->mapFile(); + std::string outFileMap = cacheDir + "/dyld_shared_cache_" + fileSet.archName + ".map"; + safeSave(mapStr.c_str(), mapStr.size(), outFileMap); + } + // free created cache buffer + vm_deallocate(mach_task_self(), (vm_address_t)results.cacheContent, results.cacheLength); + } + }); + + // we could unmap all input files, but tool is about to quit + + return (cacheBuildFailure ? 1 : 0); +} + diff --git a/dyld3/shared-cache/multi_dyld_shared_cache_builder.mm b/dyld3/shared-cache/multi_dyld_shared_cache_builder.mm new file mode 100644 index 0000000..8d106d1 --- /dev/null +++ b/dyld3/shared-cache/multi_dyld_shared_cache_builder.mm @@ -0,0 +1,282 @@ +/* -*- 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 "Manifest.h" +#include "FileUtils.h" +#include "BuilderUtils.h" + +#define CACHE_BUILDER_COPY_FILE_MODE COPYFILE_ALL + +#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.cache-builder.build", DISPATCH_QUEUE_CONCURRENT); + +#define kDylibCachePrefix "/AppleInternal/Developer/DylibCaches/" + +void createArtifact(Diagnostics& diags, dyld3::Manifest& manifest, const std::string& dylibCachePath, bool includeExecutables) +{ + auto copy_state = copyfile_state_alloc(); + mkpath_np((dylibCachePath + "/Metadata").c_str(), 0755); + (void)copyfile(manifest.metabomFile().c_str(), (dylibCachePath + "/Metadata/metabom.bom").c_str(), copy_state, CACHE_BUILDER_COPY_FILE_MODE); + + if (!manifest.dylibOrderFile().empty()) { + (void)copyfile(realPath(manifest.dylibOrderFile()).c_str(), (dylibCachePath + "/Metadata/dylibOrderFile.txt").c_str(), copy_state, CACHE_BUILDER_COPY_FILE_MODE); + } + + if (!manifest.dirtyDataOrderFile().empty()) { + (void)copyfile(realPath(manifest.dirtyDataOrderFile()).c_str(), (dylibCachePath + "/Metadata/dirtyDataOrderFile.txt").c_str(), copy_state, CACHE_BUILDER_COPY_FILE_MODE); + } + + std::set uuids; + std::map copy_pairs; + + manifest.forEachConfiguration([&manifest, &uuids](const std::string& config) { + manifest.configuration(config).forEachArchitecture([&manifest, &config, &uuids](const std::string& arch) { + auto results = manifest.configuration(config).architecture(arch).results; + for (const auto& image : results.dylibs) { + uuids.insert(image.first); + } + for (const auto& image : results.bundles) { + uuids.insert(image.first); + } + for (const auto& image : results.executables) { + uuids.insert(image.first); + } + }); + }); + + for (auto& uuid : uuids) { + auto buildPath = manifest.buildPathForUUID(uuid); + auto installPath = manifest.runtimePathForUUID(uuid); + assert(!buildPath.empty() && !installPath.empty()); + copy_pairs.insert(std::make_pair(installPath, buildPath)); + } + + for (const auto& copy_pair : copy_pairs) { + std::string from = realPath(copy_pair.second); + std::string to = dylibCachePath + "/Root/" + copy_pair.first; + mkpath_np(dirPath(to).c_str(), 0755); + int err = copyfile(from.c_str(), to.c_str(), copy_state, CACHE_BUILDER_COPY_FILE_MODE); + diags.verbose("COPYING (%d) %s -> %s\n", err, from.c_str(), to.c_str()); + + } + copyfile_state_free(copy_state); + + fprintf(stderr, "[Artifact] dylibs copied\n"); +} + +void addArtifactPaths(Diagnostics& diags, dyld3::Manifest& manifest) +{ + manifest.setDylibOrderFile("./Metadata/dylibOrderFile.txt"); + manifest.setDirtyDataOrderFile("./Metadata/dirtyDataOrderFile.txt"); + manifest.setMetabomFile("./Metadata/metabom.bom"); + + for (auto& projects : manifest.projects()) { + manifest.addProjectSource(projects.first, "./Root", true); + } +} + +int main(int argc, const char* argv[]) +{ + @autoreleasepool { + __block Diagnostics diags; + bool verbose = false; + std::string masterDstRoot; + std::string dylibCacheDir; + std::string resultPath; + std::string manifestPath; + bool preflight = false; + __block bool allBuildsSucceeded = true; + bool skipWrites = false; + bool skipBuilds = false; + bool agileChooseSHA256CdHash = false; + time_t mytime = time(0); + fprintf(stderr, "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) { + verbose = true; + diags = Diagnostics(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()) { + diags.error("-master_dst_root missing path argument"); + } + } else if (strcmp(arg, "-results") == 0) { + resultPath = argv[++i]; + } else if (strcmp(arg, "-plist") == 0) { + manifestPath = argv[++i]; + } else if (strcmp(arg, "-agile_choose_sha256_cdhash") == 0) { + agileChooseSHA256CdHash = true; + } else { + // usage(); + diags.error("unknown option: %s", arg); + } + } else { + manifestPath = argv[i]; + } + } + + if (getenv("LGG_SKIP_CACHE_FUN") != nullptr) { + skipBuilds = true; + } + + if (diags.hasError()) { + printf("%s\n", diags.errorMessage().c_str()); + exit(-1); + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if (manifestPath.empty()) { + fprintf(stderr, "mainfest path argument is required\n"); + exit(-1); + } + + (void)chdir(dirname(strdup(manifestPath.c_str()))); + __block auto manifest = dyld3::Manifest(diags, manifestPath); + + if (manifest.build().empty()) { + fprintf(stderr, "No version found in manifest\n"); + exit(-1); + } + + fprintf(stderr, "Building Caches for %s\n", manifest.build().c_str()); + + if (masterDstRoot.empty()) { + fprintf(stderr, "-master_dst_root required path argument\n"); + exit(-1); + } + + if (manifest.version() < 4) { + fprintf(stderr, "must specify valid manifest file\n"); + exit(-1); + } + + struct rlimit rl = { OPEN_MAX, OPEN_MAX }; + (void)setrlimit(RLIMIT_NOFILE, &rl); + + manifest.calculateClosure(); + + if (!skipWrites && !skipBuilds) { + (void)mkpath_np((masterDstRoot + "/Boms/").c_str(), 0755); + dispatch_group_async(buildGroup(), build_queue, ^{ + createArtifact(diags, manifest, masterDstRoot + "/Artifact.dlc/", true); + }); + } + + if (!dylibCacheDir.empty()) { + dispatch_group_async(buildGroup(), build_queue, ^{ + createArtifact(diags, manifest, dylibCacheDir + "/AppleInternal/Developer/DylibCaches/" + manifest.build() + ".dlc/", false); + }); + } + + if (!skipBuilds) { + dispatch_group_async(buildGroup(), build_queue, ^{ + makeBoms(manifest, masterDstRoot); + }); + allBuildsSucceeded = build(diags, manifest, masterDstRoot, true, verbose, skipWrites, + agileChooseSHA256CdHash); + } + + manifest.write(resultPath); + + addArtifactPaths(diags, manifest); + if (!dylibCacheDir.empty()) { + manifest.write(dylibCacheDir + "/AppleInternal/Developer/DylibCaches/" + manifest.build() + ".dlc/Manifest.plist"); + manifest.writeJSON(dylibCacheDir + "/AppleInternal/Developer/DylibCaches/" + manifest.build() + ".dlc/Manifest.json"); + } + + if (!skipWrites) { + mkpath_np((masterDstRoot + "/Artifact.dlc").c_str(), 0755); + auto copy_state = copyfile_state_alloc(); + (void)copyfile(realPath(manifestPath).c_str(), (masterDstRoot + "/Artifact.dlc/BNIManifest.plist").c_str(), copy_state, COPYFILE_ALL); + copyfile_state_free(copy_state); + manifest.write(masterDstRoot + "/Artifact.dlc/Manifest.plist"); + manifest.writeJSON(masterDstRoot + "/Artifact.dlc/Manifest.json"); + } + + dispatch_group_wait(buildGroup(), DISPATCH_TIME_FOREVER); + time_t mytime = time(0); + fprintf(stderr, "Finished: %s", asctime(localtime(&mytime))); + + if (preflight && !allBuildsSucceeded) { + exit(-1); + } + + exit(0); + }); + } + dispatch_main(); + return 0; +} diff --git a/dyld3/shared-cache/update_dyld_shared_cache.cpp b/dyld3/shared-cache/update_dyld_shared_cache.cpp new file mode 100644 index 0000000..1e82704 --- /dev/null +++ b/dyld3/shared-cache/update_dyld_shared_cache.cpp @@ -0,0 +1,839 @@ +/* -*- 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "MachOParser.h" +#include "FileUtils.h" +#include "StringUtils.h" +#include "DyldSharedCache.h" + +struct MappedMachOsByCategory +{ + std::string archName; + std::vector dylibsForCache; + std::vector otherDylibsAndBundles; + std::vector mainExecutables; +}; + +static const char* sAllowedPrefixes[] = { + "/bin/", + "/sbin/", + "/usr/", + "/System", + "/Applications/App Store.app/", + "/Applications/Automator.app/", + "/Applications/Calculator.app/", + "/Applications/Calendar.app/", + "/Applications/Chess.app/", + "/Applications/Contacts.app/", +// "/Applications/DVD Player.app/", + "/Applications/Dashboard.app/", + "/Applications/Dictionary.app/", + "/Applications/FaceTime.app/", + "/Applications/Font Book.app/", + "/Applications/Image Capture.app/", + "/Applications/Launchpad.app/", + "/Applications/Mail.app/", + "/Applications/Maps.app/", + "/Applications/Messages.app/", + "/Applications/Mission Control.app/", + "/Applications/Notes.app/", + "/Applications/Photo Booth.app/", +// "/Applications/Photos.app/", + "/Applications/Preview.app/", + "/Applications/QuickTime Player.app/", + "/Applications/Reminders.app/", + "/Applications/Safari.app/", + "/Applications/Siri.app/", + "/Applications/Stickies.app/", + "/Applications/System Preferences.app/", + "/Applications/TextEdit.app/", + "/Applications/Time Machine.app/", + "/Applications/iBooks.app/", + "/Applications/iTunes.app/", + "/Applications/Utilities/Activity Monitor.app", + "/Applications/Utilities/AirPort Utility.app", + "/Applications/Utilities/Audio MIDI Setup.app", + "/Applications/Utilities/Bluetooth File Exchange.app", + "/Applications/Utilities/Boot Camp Assistant.app", + "/Applications/Utilities/ColorSync Utility.app", + "/Applications/Utilities/Console.app", + "/Applications/Utilities/Digital Color Meter.app", + "/Applications/Utilities/Disk Utility.app", + "/Applications/Utilities/Grab.app", + "/Applications/Utilities/Grapher.app", + "/Applications/Utilities/Keychain Access.app", + "/Applications/Utilities/Migration Assistant.app", + "/Applications/Utilities/Script Editor.app", + "/Applications/Utilities/System Information.app", + "/Applications/Utilities/Terminal.app", + "/Applications/Utilities/VoiceOver Utility.app", + "/Library/CoreMediaIO/Plug-Ins/DAL/" // temp until plugins moved or closured working +}; + +static const char* sDontUsePrefixes[] = { + "/usr/share", + "/usr/local/", + "/System/Library/Assets", + "/System/Library/StagedFrameworks", + "/System/Library/Kernels/", + "/bin/zsh", // until is fixed + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Support/mdworker", // these load third party plugins + "/usr/bin/mdimport", // these load third party plugins +}; + + +static bool verbose = false; + + + +static bool addIfMachO(const std::string& pathPrefix, const std::string& runtimePath, const struct stat& statBuf, bool requireSIP, std::vector& files) +{ + // don't precompute closure info for any debug or profile dylibs + if ( endsWith(runtimePath, "_profile.dylib") || endsWith(runtimePath, "_debug.dylib") || endsWith(runtimePath, "_profile") || endsWith(runtimePath, "_debug") ) + return false; + + // read start of file to determine if it is mach-o or a fat file + std::string fullPath = pathPrefix + runtimePath; + int fd = ::open(fullPath.c_str(), O_RDONLY); + if ( fd < 0 ) + return false; + bool result = false; + const void* wholeFile = ::mmap(NULL, statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if ( wholeFile != MAP_FAILED ) { + Diagnostics diag; + bool usedWholeFile = false; + for (MappedMachOsByCategory& file : files) { + size_t sliceOffset; + size_t sliceLength; + bool fatButMissingSlice; + const void* slice = MAP_FAILED; + if ( dyld3::FatUtil::isFatFileWithSlice(diag, wholeFile, statBuf.st_size, file.archName, sliceOffset, sliceLength, fatButMissingSlice) ) { + slice = ::mmap(NULL, sliceLength, PROT_READ, MAP_PRIVATE | MAP_RESILIENT_CODESIGN, fd, sliceOffset); + if ( slice != MAP_FAILED ) { + //fprintf(stderr, "mapped slice at %p size=0x%0lX, offset=0x%0lX for %s\n", p, len, offset, fullPath.c_str()); + if ( !dyld3::MachOParser::isValidMachO(diag, file.archName, dyld3::Platform::macOS, slice, sliceLength, fullPath.c_str(), false) ) { + ::munmap((void*)slice, sliceLength); + slice = MAP_FAILED; + } + } + } + else if ( !fatButMissingSlice && dyld3::MachOParser::isValidMachO(diag, file.archName, dyld3::Platform::macOS, wholeFile, statBuf.st_size, fullPath.c_str(), false) ) { + slice = wholeFile; + sliceLength = statBuf.st_size; + sliceOffset = 0; + usedWholeFile = true; + //fprintf(stderr, "mapped whole file at %p size=0x%0lX for %s\n", p, len, inputPath.c_str()); + } + std::vector nonArchWarnings; + for (const std::string& warning : diag.warnings()) { + if ( !contains(warning, "required architecture") && !contains(warning, "not a dylib") ) + nonArchWarnings.push_back(warning); + } + diag.clearWarnings(); + if ( !nonArchWarnings.empty() ) { + fprintf(stderr, "update_dyld_shared_cache: warning: %s for %s: ", file.archName.c_str(), runtimePath.c_str()); + for (const std::string& warning : nonArchWarnings) { + fprintf(stderr, "%s ", warning.c_str()); + } + fprintf(stderr, "\n"); + } + if ( slice != MAP_FAILED ) { + const mach_header* mh = (mach_header*)slice; + dyld3::MachOParser parser((mach_header*)slice); + bool sipProtected = isProtectedBySIP(fd); + bool issetuid = false; + if ( parser.isDynamicExecutable() ) { + // When SIP enabled, only build closures for SIP protected programs + if ( !requireSIP || sipProtected ) { + //fprintf(stderr, "requireSIP=%d, sipProtected=%d, path=%s\n", requireSIP, sipProtected, fullPath.c_str()); + issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID)); + file.mainExecutables.emplace_back(runtimePath, mh, sliceLength, issetuid, sipProtected, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + } + else if ( parser.canBePlacedInDyldCache(runtimePath) ) { + // when SIP is enabled, only dylib protected by SIP can go in cache + if ( !requireSIP || sipProtected ) + file.dylibsForCache.emplace_back(runtimePath, mh, sliceLength, issetuid, sipProtected, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + else + file.otherDylibsAndBundles.emplace_back(runtimePath, mh, sliceLength, issetuid, sipProtected, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + else { + if ( parser.fileType() == MH_DYLIB ) { + std::string installName = parser.installName(); + if ( startsWith(installName, "@") && !contains(runtimePath, ".app/") ) { + if ( startsWith(runtimePath, "/usr/lib/") || startsWith(runtimePath, "/System/Library/") ) + fprintf(stderr, "update_dyld_shared_cache: warning @rpath install name for system framework: %s\n", runtimePath.c_str()); + } + } + file.otherDylibsAndBundles.emplace_back(runtimePath, mh, sliceLength, issetuid, sipProtected, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + result = true; + } + } + if ( !usedWholeFile ) + ::munmap((void*)wholeFile, statBuf.st_size); + } + ::close(fd); + return result; +} + +static void findAllFiles(const std::vector& pathPrefixes, bool requireSIP, std::vector& files) +{ + std::unordered_set skipDirs; + for (const char* s : sDontUsePrefixes) + skipDirs.insert(s); + + __block std::unordered_set alreadyUsed; + bool multiplePrefixes = (pathPrefixes.size() > 1); + for (const std::string& prefix : pathPrefixes) { + // get all files from overlay for this search dir + for (const char* searchDir : sAllowedPrefixes ) { + iterateDirectoryTree(prefix, searchDir, ^(const std::string& dirPath) { return (skipDirs.count(dirPath) != 0); }, ^(const std::string& path, const struct stat& statBuf) { + // ignore files that don't have 'x' bit set (all runnable mach-o files do) + const bool hasXBit = ((statBuf.st_mode & S_IXOTH) == S_IXOTH); + if ( !hasXBit && !endsWith(path, ".dylib") ) + return; + + // ignore files too small + if ( statBuf.st_size < 0x3000 ) + return; + + // don't add paths already found using previous prefix + if ( multiplePrefixes && (alreadyUsed.count(path) != 0) ) + return; + + // if the file is mach-o, add to list + if ( addIfMachO(prefix, path, statBuf, requireSIP, files) ) { + if ( multiplePrefixes ) + alreadyUsed.insert(path); + } + }); + } + } +} + + +static void findOSFilesViaBOMS(const std::vector& pathPrefixes, bool requireSIP, std::vector& files) +{ + __block std::unordered_set runtimePathsFound; + for (const std::string& prefix : pathPrefixes) { + iterateDirectoryTree(prefix, "/System/Library/Receipts", ^(const std::string&) { return false; }, ^(const std::string& path, const struct stat& statBuf) { + if ( !contains(path, "com.apple.pkg.") ) + return; + if ( !endsWith(path, ".bom") ) + return; + std::string fullPath = prefix + path; + BOMBom bom = BOMBomOpenWithSys(fullPath.c_str(), false, NULL); + if ( bom == nullptr ) + return; + BOMFSObject rootFso = BOMBomGetRootFSObject(bom); + if ( rootFso == nullptr ) { + BOMBomFree(bom); + return; + } + BOMBomEnumerator e = BOMBomEnumeratorNew(bom, rootFso); + if ( e == nullptr ) { + fprintf(stderr, "Can't get enumerator for BOM root FSObject\n"); + return; + } + BOMFSObjectFree(rootFso); + //fprintf(stderr, "using BOM %s\n", path.c_str()); + while (BOMFSObject fso = BOMBomEnumeratorNext(e)) { + if ( BOMFSObjectIsBinaryObject(fso) ) { + const char* runPath = BOMFSObjectPathName(fso); + if ( (runPath[0] == '.') && (runPath[1] == '/') ) + ++runPath; + if ( runtimePathsFound.count(runPath) == 0 ) { + // only add files from sAllowedPrefixes and not in sDontUsePrefixes + bool inSearchDir = false; + for (const char* searchDir : sAllowedPrefixes ) { + if ( strncmp(searchDir, runPath, strlen(searchDir)) == 0 ) { + inSearchDir = true; + break; + } + } + if ( inSearchDir ) { + bool inSkipDir = false; + for (const char* skipDir : sDontUsePrefixes) { + if ( strncmp(skipDir, runPath, strlen(skipDir)) == 0 ) { + inSkipDir = true; + break; + } + } + if ( !inSkipDir ) { + for (const std::string& prefix2 : pathPrefixes) { + struct stat statBuf2; + std::string fullPath2 = prefix2 + runPath; + if ( stat(fullPath2.c_str(), &statBuf2) == 0 ) { + addIfMachO(prefix2, runPath, statBuf2, requireSIP, files); + runtimePathsFound.insert(runPath); + break; + } + } + } + } + } + } + BOMFSObjectFree(fso); + } + + BOMBomEnumeratorFree(e); + BOMBomFree(bom); + }); + } +} + + +static bool dontCache(const std::string& volumePrefix, const std::string& archName, + const std::unordered_set& pathsWithDuplicateInstallName, + const DyldSharedCache::MappedMachO& aFile, bool warn, + const std::unordered_set& skipDylibs) +{ + if ( skipDylibs.count(aFile.runtimePath) ) + return true; + if ( startsWith(aFile.runtimePath, "/usr/lib/system/introspection/") ) + return true; + if ( startsWith(aFile.runtimePath, "/System/Library/QuickTime/") ) + return true; + if ( startsWith(aFile.runtimePath, "/System/Library/Tcl/") ) + return true; + if ( startsWith(aFile.runtimePath, "/System/Library/Perl/") ) + return true; + if ( startsWith(aFile.runtimePath, "/System/Library/MonitorPanels/") ) + return true; + if ( startsWith(aFile.runtimePath, "/System/Library/Accessibility/") ) + return true; + if ( startsWith(aFile.runtimePath, "/usr/local/") ) + return true; + + // anything inside a .app bundle is specific to app, so should not be in shared cache + if ( aFile.runtimePath.find(".app/") != std::string::npos ) + return true; + + if ( archName == "i386" ) { + if ( startsWith(aFile.runtimePath, "/System/Library/CoreServices/") ) + return true; + if ( startsWith(aFile.runtimePath, "/System/Library/Extensions/") ) + return true; + } + + if ( aFile.runtimePath.find("//") != std::string::npos ) { + if (warn) fprintf(stderr, "update_dyld_shared_cache: warning: %s skipping because of bad install name %s\n", archName.c_str(), aFile.runtimePath.c_str()); + return true; + } + + dyld3::MachOParser parser(aFile.mh); + const char* installName = parser.installName(); + if ( (pathsWithDuplicateInstallName.count(aFile.runtimePath) != 0) && (aFile.runtimePath != installName) ) { + if (warn) fprintf(stderr, "update_dyld_shared_cache: warning: %s skipping because of duplicate install name %s\n", archName.c_str(), aFile.runtimePath.c_str()); + return true; + } + + if ( aFile.runtimePath != installName ) { + // see if install name is a symlink to actual path + std::string fullInstall = volumePrefix + installName; + char resolvedPath[PATH_MAX]; + if ( realpath(fullInstall.c_str(), resolvedPath) != NULL ) { + std::string resolvedSymlink = resolvedPath; + if ( !volumePrefix.empty() ) { + resolvedSymlink = resolvedSymlink.substr(volumePrefix.size()); + } + if ( aFile.runtimePath == resolvedSymlink ) { + return false; + } + } + if (warn) fprintf(stderr, "update_dyld_shared_cache: warning: %s skipping because of bad install name %s\n", archName.c_str(), aFile.runtimePath.c_str()); + return true; + } + return false; +} + +static void pruneCachedDylibs(const std::string& volumePrefix, const std::unordered_set& skipDylibs, MappedMachOsByCategory& fileSet) +{ + std::unordered_set pathsWithDuplicateInstallName; + + std::unordered_map installNameToFirstPath; + for (DyldSharedCache::MappedMachO& aFile : fileSet.dylibsForCache) { + dyld3::MachOParser parser(aFile.mh); + const char* installName = parser.installName(); + auto pos = installNameToFirstPath.find(installName); + if ( pos == installNameToFirstPath.end() ) { + installNameToFirstPath[installName] = aFile.runtimePath; + } + else { + pathsWithDuplicateInstallName.insert(aFile.runtimePath); + pathsWithDuplicateInstallName.insert(installNameToFirstPath[installName]); + } + } + + for (DyldSharedCache::MappedMachO& aFile : fileSet.dylibsForCache) { + if ( dontCache(volumePrefix, fileSet.archName, pathsWithDuplicateInstallName, aFile, true, skipDylibs) ) + fileSet.otherDylibsAndBundles.push_back(aFile); + } + fileSet.dylibsForCache.erase(std::remove_if(fileSet.dylibsForCache.begin(), fileSet.dylibsForCache.end(), + [&](const DyldSharedCache::MappedMachO& aFile) { return dontCache(volumePrefix, fileSet.archName, pathsWithDuplicateInstallName, aFile, false, skipDylibs); }), + fileSet.dylibsForCache.end()); +} + +static void pruneOtherDylibs(const std::string& volumePrefix, MappedMachOsByCategory& fileSet) +{ + // other OS dylibs should not contain dylibs that are embedded in some .app bundle + fileSet.otherDylibsAndBundles.erase(std::remove_if(fileSet.otherDylibsAndBundles.begin(), fileSet.otherDylibsAndBundles.end(), + [&](const DyldSharedCache::MappedMachO& aFile) { return (aFile.runtimePath.find(".app/") != std::string::npos); }), + fileSet.otherDylibsAndBundles.end()); +} + + +static void pruneExecutables(const std::string& volumePrefix, MappedMachOsByCategory& fileSet) +{ + // don't build closures for xcode shims in /usr/bin (e.g. /usr/bin/clang) which re-exec themselves to a tool inside Xcode.app + fileSet.mainExecutables.erase(std::remove_if(fileSet.mainExecutables.begin(), fileSet.mainExecutables.end(), + [&](const DyldSharedCache::MappedMachO& aFile) { + if ( !startsWith(aFile.runtimePath, "/usr/bin/") ) + return false; + dyld3::MachOParser parser(aFile.mh); + __block bool isXcodeShim = false; + parser.forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t, uint32_t, bool &stop) { + if ( strcmp(loadPath, "/usr/lib/libxcselect.dylib") == 0 ) + isXcodeShim = true; + }); + return isXcodeShim; + }), fileSet.mainExecutables.end()); +} + +static bool existingCacheUpToDate(const std::string& existingCache, const std::vector& currentDylibs) +{ + // if no existing cache, it is not up-to-date + int fd = ::open(existingCache.c_str(), O_RDONLY); + if ( fd < 0 ) + return false; + + // build map of found dylibs + std::unordered_map currentDylibMap; + for (const DyldSharedCache::MappedMachO& aFile : currentDylibs) { + //fprintf(stderr, "0x%0llX 0x%0llX %s\n", aFile.inode, aFile.modTime, aFile.runtimePath.c_str()); + currentDylibMap[aFile.runtimePath] = &aFile; + } + + // make sure all dylibs in existing cache have same mtime and inode as found dylib + __block bool foundMismatch = false; + const uint64_t cacheMapLen = 0x40000000; + void *p = ::mmap(NULL, cacheMapLen, PROT_READ, MAP_PRIVATE, fd, 0); + if ( p != MAP_FAILED ) { + const DyldSharedCache* cache = (DyldSharedCache*)p; + cache->forEachImageEntry(^(const char* installName, uint64_t mTime, uint64_t inode) { + bool foundMatch = false; + auto pos = currentDylibMap.find(installName); + if ( pos != currentDylibMap.end() ) { + const DyldSharedCache::MappedMachO* foundDylib = pos->second; + if ( (foundDylib->inode == inode) && (foundDylib->modTime == mTime) ) { + foundMatch = true; + } + } + if ( !foundMatch ) { + // use slow path and look for any dylib with a matching inode and mtime + bool foundSlow = false; + for (const DyldSharedCache::MappedMachO& aFile : currentDylibs) { + if ( (aFile.inode == inode) && (aFile.modTime == mTime) ) { + foundSlow = true; + break; + } + } + if ( !foundSlow ) { + foundMismatch = true; + if ( verbose ) + fprintf(stderr, "rebuilding dyld cache because dylib changed: %s\n", installName); + } + } + }); + ::munmap(p, cacheMapLen); + } + + ::close(fd); + + return !foundMismatch; +} + + +inline uint32_t absolutetime_to_milliseconds(uint64_t abstime) +{ + return (uint32_t)(abstime/1000/1000); +} + +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 ) { \ + fprintf(stderr, s ); \ + return 1; \ + } \ + } while ( 0 ) + +int main(int argc, const char* argv[]) +{ + std::string rootPath; + std::string overlayPath; + std::string dylibListFile; + bool universal = false; + bool force = false; + bool searchDisk = false; + bool dylibsRemoved = false; + std::string cacheDir; + std::unordered_set archStrs; + std::unordered_set skipDylibs; + + // parse command line options + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + if (strcmp(arg, "-debug") == 0) { + verbose = true; + } + else if (strcmp(arg, "-verbose") == 0) { + verbose = true; + } + else if (strcmp(arg, "-dont_map_local_symbols") == 0) { + //We are going to ignore this + } + 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, "-search_disk") == 0) { + searchDisk = true; + } + else if (strcmp(arg, "-dylibs_removed_in_mastering") == 0) { + dylibsRemoved = true; + } + 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 if (strcmp(arg, "-skip") == 0) { + TERMINATE_IF_LAST_ARG("-skip missing argument\n"); + skipDylibs.insert(argv[++i]); + } + else { + //usage(); + fprintf(stderr, "update_dyld_shared_cache: unknown option: %s\n", arg); + return 1; + } + } + + if ( !rootPath.empty() & !overlayPath.empty() ) { + fprintf(stderr, "-root and -overlay cannot be used together\n"); + return 1; + } + // canonicalize rootPath + if ( !rootPath.empty() ) { + char resolvedPath[PATH_MAX]; + if ( realpath(rootPath.c_str(), resolvedPath) != NULL ) { + rootPath = resolvedPath; + } + // when building closures for boot volume, pathPrefixes should be empty + if ( rootPath == "/" ) { + rootPath = ""; + } + } + // canonicalize overlayPath + if ( !overlayPath.empty() ) { + char resolvedPath[PATH_MAX]; + if ( realpath(overlayPath.c_str(), resolvedPath) != NULL ) { + overlayPath = resolvedPath; + } + } + // + // pathPrefixes for three modes: + // 1) no options: { "" } // search only boot volume + // 2) -overlay: { overlay, "" } // search overlay, then boot volume + // 3) -root: { root } // search only -root volume + // + std::vector pathPrefixes; + if ( !overlayPath.empty() ) + pathPrefixes.push_back(overlayPath); + pathPrefixes.push_back(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; + } + + int err = mkpath_np(cacheDir.c_str(), S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + if ( (err != 0) && (err != EEXIST) ) { + fprintf(stderr, "mkpath_np fail: %d", err); + return 1; + } + + if ( archStrs.empty() ) { + 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"); + } + } + + uint64_t t1 = mach_absolute_time(); + + // find all mach-o files for requested architectures + bool requireDylibsBeRootlessProtected = isProtectedBySIP(cacheDir); + __block std::vector allFileSets; + if ( archStrs.count("x86_64") ) + allFileSets.push_back({"x86_64"}); + if ( archStrs.count("x86_64h") ) + allFileSets.push_back({"x86_64h"}); + if ( archStrs.count("i386") ) + allFileSets.push_back({"i386"}); + if ( searchDisk ) + findAllFiles(pathPrefixes, requireDylibsBeRootlessProtected, allFileSets); + else { + std::unordered_set runtimePathsFound; + findOSFilesViaBOMS(pathPrefixes, requireDylibsBeRootlessProtected, allFileSets); + } + + // nothing in OS uses i386 dylibs, so only dylibs used by third party apps need to be in cache + for (MappedMachOsByCategory& fileSet : allFileSets) { + pruneCachedDylibs(rootPath, skipDylibs, fileSet); + pruneOtherDylibs(rootPath, fileSet); + pruneExecutables(rootPath, fileSet); + } + + uint64_t t2 = mach_absolute_time(); + if ( verbose ) { + if ( searchDisk ) + fprintf(stderr, "time to scan file system and construct lists of mach-o files: %ums\n", absolutetime_to_milliseconds(t2-t1)); + else + fprintf(stderr, "time to read BOM and construct lists of mach-o files: %ums\n", absolutetime_to_milliseconds(t2-t1)); + } + + // build caches in parallel on machines with at leat 4GB of RAM + uint64_t memSize = 0; + size_t sz = sizeof(memSize);; + bool buildInParallel = false; + if ( sysctlbyname("hw.memsize", &memSize, &sz, NULL, 0) == 0 ) { + if ( memSize >= 0x100000000ULL ) + buildInParallel = true; + } + dispatch_queue_t dqueue = buildInParallel ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) + : dispatch_queue_create("serial-queue", DISPATCH_QUEUE_SERIAL); + + // build all caches + __block bool cacheBuildFailure = false; + __block bool wroteSomeCacheFile = false; + dispatch_apply(allFileSets.size(), dqueue, ^(size_t index) { + MappedMachOsByCategory& fileSet = allFileSets[index]; + const std::string outFile = cacheDir + "/dyld_shared_cache_" + fileSet.archName; + + DyldSharedCache::MappedMachO (^loader)(const std::string&) = ^DyldSharedCache::MappedMachO(const std::string& runtimePath) { + if ( skipDylibs.count(runtimePath) ) + return DyldSharedCache::MappedMachO(); + for (const std::string& prefix : pathPrefixes) { + std::string fullPath = prefix + runtimePath; + struct stat statBuf; + if ( stat(fullPath.c_str(), &statBuf) == 0 ) { + std::vector mappedFiles; + mappedFiles.push_back({fileSet.archName}); + if ( addIfMachO(prefix, runtimePath, statBuf, requireDylibsBeRootlessProtected, mappedFiles) ) { + if ( !mappedFiles.back().dylibsForCache.empty() ) + return mappedFiles.back().dylibsForCache.back(); + } + } + } + return DyldSharedCache::MappedMachO(); + }; + size_t startCount = fileSet.dylibsForCache.size(); + std::vector>> excludes; + DyldSharedCache::verifySelfContained(fileSet.dylibsForCache, loader, excludes); + for (size_t i=startCount; i < fileSet.dylibsForCache.size(); ++i) { + fprintf(stderr, "update_dyld_shared_cache: warning: %s not in .bom, but adding required dylib %s\n", fileSet.archName.c_str(), fileSet.dylibsForCache[i].runtimePath.c_str()); + } + for (auto& exclude : excludes) { + std::string reasons = "(\""; + for (auto i = exclude.second.begin(); i != exclude.second.end(); ++i) { + reasons += *i; + if (i != --exclude.second.end()) { + reasons += "\", \""; + } + } + reasons += "\")"; + fprintf(stderr, "update_dyld_shared_cache: warning: %s rejected from cached dylibs: %s (%s)\n", fileSet.archName.c_str(), exclude.first.runtimePath.c_str(), reasons.c_str()); + fileSet.otherDylibsAndBundles.push_back(exclude.first); + } + + // check if cache is already up to date + if ( !force ) { + if ( existingCacheUpToDate(outFile, fileSet.dylibsForCache) ) + return; + } + + // add any extra dylibs needed which were not in .bom + fprintf(stderr, "update_dyld_shared_cache: %s incorporating %lu OS dylibs, tracking %lu others, building closures for %lu executables\n", fileSet.archName.c_str(), fileSet.dylibsForCache.size(), fileSet.otherDylibsAndBundles.size(), fileSet.mainExecutables.size()); + //for (const DyldSharedCache::MappedMachO& aFile : fileSet.dylibsForCache) { + // fprintf(stderr, " %s\n", aFile.runtimePath.c_str()); + //} + + + // build cache new cache file + DyldSharedCache::CreateOptions options; + options.archName = fileSet.archName; + options.platform = dyld3::Platform::macOS; + options.excludeLocalSymbols = false; + options.optimizeStubs = false; + options.optimizeObjC = true; + options.codeSigningDigestMode = DyldSharedCache::SHA256only; + options.dylibsRemovedDuringMastering = dylibsRemoved; + options.inodesAreSameAsRuntime = true; + options.cacheSupportsASLR = (fileSet.archName != "i386"); + options.forSimulator = false; + options.verbose = verbose; + options.evictLeafDylibsOnOverflow = true; + options.pathPrefixes = pathPrefixes; + DyldSharedCache::CreateResults results = DyldSharedCache::create(options, fileSet.dylibsForCache, fileSet.otherDylibsAndBundles, fileSet.mainExecutables); + + // print any warnings + for (const std::string& warn : results.warnings) { + fprintf(stderr, "update_dyld_shared_cache: warning: %s %s\n", fileSet.archName.c_str(), warn.c_str()); + } + if ( !results.errorMessage.empty() ) { + // print error (if one) + fprintf(stderr, "update_dyld_shared_cache: %s\n", results.errorMessage.c_str()); + cacheBuildFailure = true; + } + else { + // save new cache file to disk and write new .map file + assert(results.cacheContent != nullptr); + if ( !safeSave(results.cacheContent, results.cacheLength, outFile) ) + cacheBuildFailure = true; + if ( !cacheBuildFailure ) { + std::string mapStr = results.cacheContent->mapFile(); + std::string outFileMap = cacheDir + "/dyld_shared_cache_" + fileSet.archName + ".map"; + safeSave(mapStr.c_str(), mapStr.size(), outFileMap); + wroteSomeCacheFile = true; + } + // free created cache buffer + vm_deallocate(mach_task_self(), (vm_address_t)results.cacheContent, results.cacheLength); + } + }); + + + // Save off spintrace data + if ( wroteSomeCacheFile ) { + void* h = dlopen("/usr/lib/libdscsym.dylib", 0); + if ( h != nullptr ) { + typedef int (*dscym_func)(const char*); + dscym_func func = (dscym_func)dlsym(h, "dscsym_save_dscsyms_for_current_caches"); + std::string nuggetRoot = rootPath; + if ( nuggetRoot.empty() ) + nuggetRoot = overlayPath; + if ( nuggetRoot.empty() ) + nuggetRoot = "/"; + (*func)(nuggetRoot.c_str()); + } + } + + + // we could unmap all input files, but tool is about to quit + + return (cacheBuildFailure ? 1 : 0); +} + diff --git a/dyld3/shared-cache/update_dyld_shared_cache_entitlements.plist b/dyld3/shared-cache/update_dyld_shared_cache_entitlements.plist new file mode 100644 index 0000000..5ed9d1c --- /dev/null +++ b/dyld3/shared-cache/update_dyld_shared_cache_entitlements.plist @@ -0,0 +1,8 @@ + + + + + com.apple.rootless.storage.dyld + + + diff --git a/dyld3/shared-cache/update_dyld_sim_shared_cache.cpp b/dyld3/shared-cache/update_dyld_sim_shared_cache.cpp new file mode 100644 index 0000000..404bdc4 --- /dev/null +++ b/dyld3/shared-cache/update_dyld_sim_shared_cache.cpp @@ -0,0 +1,541 @@ +/* -*- 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 "MachOParser.h" +#include "FileUtils.h" +#include "StringUtils.h" +#include "DyldSharedCache.h" + + + +struct MappedMachOsByCategory +{ + std::string archName; + std::vector dylibsForCache; + std::vector otherDylibsAndBundles; + std::vector mainExecutables; +}; + +static const char* sSearchDirs[] = { + "/bin", + "/sbin", + "/usr", + "/System", +}; + +static const char* sSkipDirs[] = { + "/usr/share", + "/usr/local/include", +}; + + +static const char* sMacOsAdditions[] = { + "/usr/lib/system/libsystem_kernel.dylib", + "/usr/lib/system/libsystem_platform.dylib", + "/usr/lib/system/libsystem_pthread.dylib", +}; + + +static bool verbose = false; + +static bool addIfMachO(const std::string& simRuntimeRootPath, const std::string& runtimePath, const struct stat& statBuf, dyld3::Platform platform, std::vector& files) +{ + // read start of file to determine if it is mach-o or a fat file + std::string fullPath = simRuntimeRootPath + runtimePath; + int fd = ::open(fullPath.c_str(), O_RDONLY); + if ( fd < 0 ) + return false; + bool result = false; + const void* wholeFile = ::mmap(NULL, statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if ( wholeFile != MAP_FAILED ) { + Diagnostics diag; + bool usedWholeFile = false; + for (MappedMachOsByCategory& file : files) { + size_t sliceOffset; + size_t sliceLength; + bool fatButMissingSlice; + const void* slice = MAP_FAILED; + if ( dyld3::FatUtil::isFatFileWithSlice(diag, wholeFile, statBuf.st_size, file.archName, sliceOffset, sliceLength, fatButMissingSlice) ) { + slice = ::mmap(NULL, sliceLength, PROT_READ, MAP_PRIVATE, fd, sliceOffset); + if ( slice != MAP_FAILED ) { + //fprintf(stderr, "mapped slice at %p size=0x%0lX, offset=0x%0lX for %s\n", p, len, offset, fullPath.c_str()); + if ( !dyld3::MachOParser::isValidMachO(diag, file.archName, platform, slice, sliceLength, fullPath.c_str(), false) ) { + ::munmap((void*)slice, sliceLength); + slice = MAP_FAILED; + } + } + } + else if ( !fatButMissingSlice && dyld3::MachOParser::isValidMachO(diag, file.archName, platform, wholeFile, statBuf.st_size, fullPath.c_str(), false) ) { + slice = wholeFile; + sliceLength = statBuf.st_size; + sliceOffset = 0; + usedWholeFile = true; + //fprintf(stderr, "mapped whole file at %p size=0x%0lX for %s\n", p, len, inputPath.c_str()); + } + if ( slice != MAP_FAILED ) { + const mach_header* mh = (mach_header*)slice; + dyld3::MachOParser parser(mh); + if ( parser.platform() != platform ) { + fprintf(stderr, "skipped wrong platform binary: %s\n", fullPath.c_str()); + result = false; + } + else { + bool sip = true; // assume anything found in the simulator runtime is a platform binary + if ( parser.isDynamicExecutable() ) { + bool issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID)); + file.mainExecutables.emplace_back(runtimePath, mh, sliceLength, issetuid, sip, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + else { + if ( parser.canBePlacedInDyldCache(runtimePath) ) { + file.dylibsForCache.emplace_back(runtimePath, mh, sliceLength, false, sip, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + else { + file.otherDylibsAndBundles.emplace_back(runtimePath, mh, sliceLength, false, sip, sliceOffset, statBuf.st_mtime, statBuf.st_ino); + } + } + result = true; + } + } + } + if ( !usedWholeFile ) + ::munmap((void*)wholeFile, statBuf.st_size); + } + ::close(fd); + return result; +} + +static void findAllFiles(const std::string& simRuntimeRootPath, dyld3::Platform platform, std::vector& files) +{ + std::unordered_set skipDirs; + for (const char* s : sSkipDirs) + skipDirs.insert(s); + + for (const char* searchDir : sSearchDirs ) { + iterateDirectoryTree(simRuntimeRootPath, searchDir, ^(const std::string& dirPath) { return (skipDirs.count(dirPath) != 0); }, ^(const std::string& path, const struct stat& statBuf) { + // ignore files that don't have 'x' bit set (all runnable mach-o files do) + const bool hasXBit = ((statBuf.st_mode & S_IXOTH) == S_IXOTH); + if ( !hasXBit && !endsWith(path, ".dylib") ) + return; + + // ignore files too small + if ( statBuf.st_size < 0x3000 ) + return; + + // if the file is mach-o add to list + addIfMachO(simRuntimeRootPath, path, statBuf, platform, files); + }); + } +} + +static void addMacOSAdditions(std::vector& allFileSets) +{ + for (const char* addPath : sMacOsAdditions) { + struct stat statBuf; + if ( stat(addPath, &statBuf) == 0 ) + addIfMachO("", addPath, statBuf, dyld3::Platform::macOS, allFileSets); + } +} + + +static bool dontCache(const std::string& simRuntimeRootPath, const std::string& archName, + const std::unordered_set& pathsWithDuplicateInstallName, + const DyldSharedCache::MappedMachO& aFile, bool warn) +{ + if ( startsWith(aFile.runtimePath, "/usr/lib/system/introspection/") ) + return true; + if ( startsWith(aFile.runtimePath, "/usr/local/") ) + return true; + + // anything inside a .app bundle is specific to app, so should be in shared cache + if ( aFile.runtimePath.find(".app/") != std::string::npos ) + return true; + + if ( aFile.runtimePath.find("//") != std::string::npos ) { + if (warn) fprintf(stderr, "update_dyld_sim_shared_cache: warning: %s double-slash in install name %s\n", archName.c_str(), aFile.runtimePath.c_str()); + } + + dyld3::MachOParser parser(aFile.mh); + const char* installName = parser.installName(); + if ( (pathsWithDuplicateInstallName.count(aFile.runtimePath) != 0) && (aFile.runtimePath != installName) ) { + if (warn) fprintf(stderr, "update_dyld_sim_shared_cache: warning: %s skipping because of duplicate install name %s\n", archName.c_str(), aFile.runtimePath.c_str()); + return true; + } + + if ( aFile.runtimePath != installName ) { + // see if install name is a symlink to actual path + std::string fullInstall = simRuntimeRootPath + installName; + char resolvedPath[PATH_MAX]; + if ( realpath(fullInstall.c_str(), resolvedPath) != NULL ) { + std::string resolvedSymlink = resolvedPath; + if ( !simRuntimeRootPath.empty() ) { + resolvedSymlink = resolvedSymlink.substr(simRuntimeRootPath.size()); + } + if ( aFile.runtimePath == resolvedSymlink ) { + return false; + } + } + if (warn) fprintf(stderr, "update_dyld_sim_shared_cache: warning: %s skipping because of bad install name %s\n", archName.c_str(), aFile.runtimePath.c_str()); + return true; + } + + return false; +} + +static void pruneCachedDylibs(const std::string& simRuntimeRootPath, MappedMachOsByCategory& fileSet) +{ + std::unordered_set pathsWithDuplicateInstallName; + + std::unordered_map installNameToFirstPath; + for (DyldSharedCache::MappedMachO& aFile : fileSet.dylibsForCache) { + //fprintf(stderr, "dylib: %s\n", aFile.runtimePath.c_str()); + dyld3::MachOParser parser(aFile.mh); + const char* installName = parser.installName(); + auto pos = installNameToFirstPath.find(installName); + if ( pos == installNameToFirstPath.end() ) { + installNameToFirstPath[installName] = aFile.runtimePath; + } + else { + pathsWithDuplicateInstallName.insert(aFile.runtimePath); + pathsWithDuplicateInstallName.insert(installNameToFirstPath[installName]); + } + } + + for (DyldSharedCache::MappedMachO& aFile : fileSet.dylibsForCache) { + if ( dontCache(simRuntimeRootPath, fileSet.archName, pathsWithDuplicateInstallName, aFile, true) ) + fileSet.otherDylibsAndBundles.push_back(aFile); + } + fileSet.dylibsForCache.erase(std::remove_if(fileSet.dylibsForCache.begin(), fileSet.dylibsForCache.end(), + [&](const DyldSharedCache::MappedMachO& aFile) { return dontCache(simRuntimeRootPath, fileSet.archName, pathsWithDuplicateInstallName, aFile, false); }), + fileSet.dylibsForCache.end()); +} + +static bool existingCacheUpToDate(const std::string& existingCache, const std::vector& currentDylibs) +{ + // if no existing cache, it is not up-to-date + int fd = ::open(existingCache.c_str(), O_RDONLY); + if ( fd < 0 ) + return false; + + // build map of found dylibs + std::unordered_map currentDylibMap; + for (const DyldSharedCache::MappedMachO& aFile : currentDylibs) { + //fprintf(stderr, "0x%0llX 0x%0llX %s\n", aFile.inode, aFile.modTime, aFile.runtimePath.c_str()); + currentDylibMap[aFile.runtimePath] = &aFile; + } + + // make sure all dylibs in existing cache have same mtime and inode as found dylib + __block bool foundMismatch = false; + const uint64_t cacheMapLen = 0x40000000; + void *p = ::mmap(NULL, cacheMapLen, PROT_READ, MAP_PRIVATE, fd, 0); + if ( p != MAP_FAILED ) { + const DyldSharedCache* cache = (DyldSharedCache*)p; + cache->forEachImageEntry(^(const char* installName, uint64_t mTime, uint64_t inode) { + bool foundMatch = false; + auto pos = currentDylibMap.find(installName); + if ( pos != currentDylibMap.end() ) { + const DyldSharedCache::MappedMachO* foundDylib = pos->second; + if ( (foundDylib->inode == inode) && (foundDylib->modTime == mTime) ) { + foundMatch = true; + } + } + if ( !foundMatch ) { + // use slow path and look for any dylib with a matching inode and mtime + bool foundSlow = false; + for (const DyldSharedCache::MappedMachO& aFile : currentDylibs) { + if ( (aFile.inode == inode) && (aFile.modTime == mTime) ) { + foundSlow = true; + break; + } + } + if ( !foundSlow ) { + foundMismatch = true; + if ( verbose ) + fprintf(stderr, "rebuilding dyld cache because dylib changed: %s\n", installName); + } + } + }); + ::munmap(p, cacheMapLen); + } + + ::close(fd); + + return !foundMismatch; +} + + +inline uint32_t absolutetime_to_milliseconds(uint64_t abstime) +{ + return (uint32_t)(abstime/1000/1000); +} + + +#define TERMINATE_IF_LAST_ARG( s ) \ + do { \ + if ( i == argc - 1 ) { \ + fprintf(stderr, s ); \ + return 1; \ + } \ + } while ( 0 ) + +int main(int argc, const char* argv[]) +{ + std::string rootPath; + std::string dylibListFile; + bool force = false; + std::string cacheDir; + std::unordered_set archStrs; + + dyld3::Platform platform = dyld3::Platform::iOS; + + // parse command line options + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + if (strcmp(arg, "-debug") == 0) { + verbose = true; + } + else if (strcmp(arg, "-verbose") == 0) { + verbose = true; + } + else if (strcmp(arg, "-tvOS") == 0) { + platform = dyld3::Platform::tvOS; + } + else if (strcmp(arg, "-iOS") == 0) { + platform = dyld3::Platform::iOS; + } + else if (strcmp(arg, "-watchOS") == 0) { + platform = dyld3::Platform::watchOS; + } + else if ( strcmp(arg, "-runtime_dir") == 0 ) { + TERMINATE_IF_LAST_ARG("-runtime_dir missing path argument\n"); + rootPath = 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 { + //usage(); + fprintf(stderr, "update_dyld_sim_shared_cache: unknown option: %s\n", arg); + return 1; + } + } + + if ( cacheDir.empty() ) { + fprintf(stderr, "missing -cache_dir option to specify directory in which to write cache file(s)\n"); + return 1; + } + + if ( rootPath.empty() ) { + fprintf(stderr, "missing -runtime_dir option to specify directory which is root of simulator runtime)\n"); + return 1; + } + else { + // canonicalize rootPath + char resolvedPath[PATH_MAX]; + if ( realpath(rootPath.c_str(), resolvedPath) != NULL ) { + rootPath = resolvedPath; + } + } + + int err = mkpath_np(cacheDir.c_str(), S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + if ( (err != 0) && (err != EEXIST) ) { + fprintf(stderr, "mkpath_np fail: %d", err); + return 1; + } + + if ( archStrs.empty() ) { + switch ( platform ) { + case dyld3::Platform::iOS: + archStrs.insert("x86_64"); + break; + case dyld3::Platform::tvOS: + archStrs.insert("x86_64"); + break; + case dyld3::Platform::watchOS: + archStrs.insert("i386"); + break; + case dyld3::Platform::unknown: + case dyld3::Platform::macOS: + assert(0 && "macOS does not have a simulator"); + break; + case dyld3::Platform::bridgeOS: + assert(0 && "bridgeOS does not have a simulator"); + break; + } + } + + uint64_t t1 = mach_absolute_time(); + + // find all mach-o files for requested architectures + __block std::vector allFileSets; + if ( archStrs.count("x86_64") ) + allFileSets.push_back({"x86_64"}); + if ( archStrs.count("i386") ) + allFileSets.push_back({"i386"}); + findAllFiles(rootPath, platform, allFileSets); + addMacOSAdditions(allFileSets); + for (MappedMachOsByCategory& fileSet : allFileSets) { + pruneCachedDylibs(rootPath, fileSet); + } + + uint64_t t2 = mach_absolute_time(); + + fprintf(stderr, "time to scan file system and construct lists of mach-o files: %ums\n", absolutetime_to_milliseconds(t2-t1)); + + // build all caches in parallel + __block bool cacheBuildFailure = false; + dispatch_apply(allFileSets.size(), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) { + MappedMachOsByCategory& fileSet = allFileSets[index]; + const std::string outFile = cacheDir + "/dyld_shared_cache_" + fileSet.archName; + __block std::unordered_set knownMissingDylib; + + DyldSharedCache::MappedMachO (^loader)(const std::string&) = ^DyldSharedCache::MappedMachO(const std::string& runtimePath) { + std::string fullPath = rootPath + runtimePath; + struct stat statBuf; + if ( stat(fullPath.c_str(), &statBuf) == 0 ) { + std::vector mappedFiles; + mappedFiles.push_back({fileSet.archName}); + if ( addIfMachO(rootPath, runtimePath, statBuf, platform, mappedFiles) ) { + if ( !mappedFiles.back().dylibsForCache.empty() ) + return mappedFiles.back().dylibsForCache.back(); + } + } + if ( knownMissingDylib.count(runtimePath) == 0 ) { + fprintf(stderr, "update_dyld_sim_shared_cache: warning: %s could not use in dylid cache: %s\n", fileSet.archName.c_str(), runtimePath.c_str()); + knownMissingDylib.insert(runtimePath); + } + return DyldSharedCache::MappedMachO(); + }; + size_t startCount = fileSet.dylibsForCache.size(); + std::vector>> excludes; + DyldSharedCache::verifySelfContained(fileSet.dylibsForCache, loader, excludes); + for (size_t i=startCount; i < fileSet.dylibsForCache.size(); ++i) { + fprintf(stderr, "update_dyld_sim_shared_cache: warning: %s not found in initial scan, but adding required dylib %s\n", fileSet.archName.c_str(), fileSet.dylibsForCache[i].runtimePath.c_str()); + } + for (auto& exclude : excludes) { + std::string reasons = "(\""; + for (auto i = exclude.second.begin(); i != exclude.second.end(); ++i) { + reasons += *i; + if (i != --exclude.second.end()) { + reasons += "\", \""; + } + } + reasons += "\")"; + fprintf(stderr, "update_dyld_shared_cache: warning: %s rejected from cached dylibs: %s (%s)\n", fileSet.archName.c_str(), exclude.first.runtimePath.c_str(), reasons.c_str()); + fileSet.otherDylibsAndBundles.push_back(exclude.first); + } + + // check if cache is already up to date + if ( !force ) { + if ( existingCacheUpToDate(outFile, fileSet.dylibsForCache) ) + return; + } + fprintf(stderr, "make %s cache with %lu dylibs, %lu other dylibs, %lu programs\n", fileSet.archName.c_str(), fileSet.dylibsForCache.size(), fileSet.otherDylibsAndBundles.size(), fileSet.mainExecutables.size()); + + // build cache new cache file + DyldSharedCache::CreateOptions options; + options.archName = fileSet.archName; + options.platform = platform; + options.excludeLocalSymbols = false; + options.optimizeStubs = false; + options.optimizeObjC = true; + options.codeSigningDigestMode = DyldSharedCache::SHA256only; + options.dylibsRemovedDuringMastering = false; + options.inodesAreSameAsRuntime = true; + options.cacheSupportsASLR = false; + options.forSimulator = true; + options.verbose = verbose; + options.evictLeafDylibsOnOverflow = true; + options.pathPrefixes = { rootPath }; + DyldSharedCache::CreateResults results = DyldSharedCache::create(options, fileSet.dylibsForCache, fileSet.otherDylibsAndBundles, fileSet.mainExecutables); + + // print any warnings + for (const std::string& warn : results.warnings) { + fprintf(stderr, "update_dyld_sim_shared_cache: warning: %s %s\n", fileSet.archName.c_str(), warn.c_str()); + } + if ( !results.errorMessage.empty() ) { + // print error (if one) + fprintf(stderr, "update_dyld_sim_shared_cache: %s\n", results.errorMessage.c_str()); + cacheBuildFailure = true; + } + else { + // save new cache file to disk and write new .map file + assert(results.cacheContent != nullptr); + if ( !safeSave(results.cacheContent, results.cacheLength, outFile) ) + cacheBuildFailure = true; + if ( !cacheBuildFailure ) { + std::string mapStr = results.cacheContent->mapFile(); + std::string outFileMap = cacheDir + "/dyld_shared_cache_" + fileSet.archName + ".map"; + safeSave(mapStr.c_str(), mapStr.size(), outFileMap); + } + // free created cache buffer + vm_deallocate(mach_task_self(), (vm_address_t)results.cacheContent, results.cacheLength); + } + }); + + // we could unmap all input files, but tool is about to quit + + return (cacheBuildFailure ? 1 : 0); +} + diff --git a/include/mach-o/dyld.h b/include/mach-o/dyld.h index ce41147..770abd4 100644 --- a/include/mach-o/dyld.h +++ b/include/mach-o/dyld.h @@ -93,8 +93,28 @@ extern int _NSGetExecutablePath(char* buf, uint32_t* bufsize) __ +/* + * Registers a function to be called when the current thread terminates. + * Called by c++ compiler to implement destructors on thread_local object variables. + */ +extern void _tlv_atexit(void (*termFunc)(void* objAddr), void* objAddr) __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0); + + +/* + * Never called. On-disk thread local variables contain a pointer to this. Once + * the thread local is prepared, the pointer changes to a real handler such as tlv_get_addr. + */ +extern void _tlv_bootstrap() __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0); +// FIXME: remove when Availability.h updated +#ifndef __BRIDGEOS_UNAVAILABLE + #ifdef __ENVIRONMENT_BRIDGE_OS_VERSION_MIN_REQUIRED__ + #define __BRIDGEOS_UNAVAILABLE __OS_AVAILABILITY(bridgeos,unavailable) + #else + #define __BRIDGEOS_UNAVAILABLE + #endif +#endif /* * The following dyld API's are deprecated as of Mac OS X 10.5. They are either @@ -134,24 +154,25 @@ typedef enum { typedef struct __NSObjectFileImage* NSObjectFileImage; + + /* NSObjectFileImage can only be used with MH_BUNDLE files */ -extern NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* pathName, NSObjectFileImage *objectFileImage) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern NSObjectFileImageReturnCode NSCreateObjectFileImageFromMemory(const void *address, size_t size, NSObjectFileImage *objectFileImage) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern bool NSDestroyObjectFileImage(NSObjectFileImage objectFileImage) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); - -extern uint32_t NSSymbolDefinitionCountInObjectFileImage(NSObjectFileImage objectFileImage) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern const char* NSSymbolDefinitionNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern uint32_t NSSymbolReferenceCountInObjectFileImage(NSObjectFileImage objectFileImage) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern const char* NSSymbolReferenceNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal, bool *tentative_definition) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern bool NSIsSymbolDefinedInObjectFileImage(NSObjectFileImage objectFileImage, const char* symbolName) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern void* NSGetSectionDataInObjectFileImage(NSObjectFileImage objectFileImage, const char* segmentName, const char* sectionName, size_t *size) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern bool NSHasModInitObjectFileImage(NSObjectFileImage objectFileImage) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_3,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* pathName, NSObjectFileImage *objectFileImage) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlopen()"); +extern NSObjectFileImageReturnCode NSCreateObjectFileImageFromMemory(const void *address, size_t size, NSObjectFileImage *objectFileImage) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern bool NSDestroyObjectFileImage(NSObjectFileImage objectFileImage) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlclose()"); + +extern uint32_t NSSymbolDefinitionCountInObjectFileImage(NSObjectFileImage objectFileImage) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern const char* NSSymbolDefinitionNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern uint32_t NSSymbolReferenceCountInObjectFileImage(NSObjectFileImage objectFileImage) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern const char* NSSymbolReferenceNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal, bool *tentative_definition) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern bool NSIsSymbolDefinedInObjectFileImage(NSObjectFileImage objectFileImage, const char* symbolName) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern void* NSGetSectionDataInObjectFileImage(NSObjectFileImage objectFileImage, const char* segmentName, const char* sectionName, size_t *size) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "getsectiondata()"); typedef struct __NSModule* NSModule; -extern const char* NSNameOfModule(NSModule m) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern const char* NSLibraryNameForModule(NSModule m) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern const char* NSNameOfModule(NSModule m) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern const char* NSLibraryNameForModule(NSModule m) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); -extern NSModule NSLinkModule(NSObjectFileImage objectFileImage, const char* moduleName, uint32_t options) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern NSModule NSLinkModule(NSObjectFileImage objectFileImage, const char* moduleName, uint32_t options) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlopen()"); #define NSLINKMODULE_OPTION_NONE 0x0 #define NSLINKMODULE_OPTION_BINDNOW 0x1 #define NSLINKMODULE_OPTION_PRIVATE 0x2 @@ -159,27 +180,27 @@ extern NSModule NSLinkModule(NSObjectFileImage objectFileImage, const char* modu #define NSLINKMODULE_OPTION_DONT_CALL_MOD_INIT_ROUTINES 0x8 #define NSLINKMODULE_OPTION_TRAILING_PHYS_NAME 0x10 -extern bool NSUnLinkModule(NSModule module, uint32_t options) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern bool NSUnLinkModule(NSModule module, uint32_t options) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); #define NSUNLINKMODULE_OPTION_NONE 0x0 #define NSUNLINKMODULE_OPTION_KEEP_MEMORY_MAPPED 0x1 #define NSUNLINKMODULE_OPTION_RESET_LAZY_REFERENCES 0x2 /* symbol API */ typedef struct __NSSymbol* NSSymbol; -extern bool NSIsSymbolNameDefined(const char* symbolName) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern bool NSIsSymbolNameDefinedWithHint(const char* symbolName, const char* libraryNameHint) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern bool NSIsSymbolNameDefinedInImage(const struct mach_header* image, const char* symbolName) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern NSSymbol NSLookupAndBindSymbol(const char* symbolName) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern NSSymbol NSLookupAndBindSymbolWithHint(const char* symbolName, const char* libraryNameHint) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern NSSymbol NSLookupSymbolInModule(NSModule module, const char* symbolName) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern NSSymbol NSLookupSymbolInImage(const struct mach_header* image, const char* symbolName, uint32_t options) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern bool NSIsSymbolNameDefined(const char* symbolName) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern bool NSIsSymbolNameDefinedWithHint(const char* symbolName, const char* libraryNameHint) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern bool NSIsSymbolNameDefinedInImage(const struct mach_header* image, const char* symbolName) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern NSSymbol NSLookupAndBindSymbol(const char* symbolName) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern NSSymbol NSLookupAndBindSymbolWithHint(const char* symbolName, const char* libraryNameHint) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern NSSymbol NSLookupSymbolInModule(NSModule module, const char* symbolName) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlsym()"); +extern NSSymbol NSLookupSymbolInImage(const struct mach_header* image, const char* symbolName, uint32_t options) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlsym()"); #define NSLOOKUPSYMBOLINIMAGE_OPTION_BIND 0x0 #define NSLOOKUPSYMBOLINIMAGE_OPTION_BIND_NOW 0x1 #define NSLOOKUPSYMBOLINIMAGE_OPTION_BIND_FULLY 0x2 #define NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR 0x4 -extern const char* NSNameOfSymbol(NSSymbol symbol) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern void * NSAddressOfSymbol(NSSymbol symbol) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern NSModule NSModuleForSymbol(NSSymbol symbol) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern const char* NSNameOfSymbol(NSSymbol symbol) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern void * NSAddressOfSymbol(NSSymbol symbol) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlsym()"); +extern NSModule NSModuleForSymbol(NSSymbol symbol) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dladdr()"); /* error handling API */ typedef enum { @@ -207,7 +228,7 @@ typedef enum { NSOtherErrorInvalidArgs } NSOtherErrorNumbers; -extern void NSLinkEditError(NSLinkEditErrors *c, int *errorNumber, const char** fileName, const char** errorString) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern void NSLinkEditError(NSLinkEditErrors *c, int *errorNumber, const char** fileName, const char** errorString) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlerror()"); typedef struct { void (*undefined)(const char* symbolName); @@ -216,28 +237,27 @@ typedef struct { const char* fileName, const char* errorString); } NSLinkEditErrorHandlers; -extern void NSInstallLinkEditErrorHandlers(const NSLinkEditErrorHandlers *handlers) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern void NSInstallLinkEditErrorHandlers(const NSLinkEditErrorHandlers *handlers) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); -extern bool NSAddLibrary(const char* pathName) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern bool NSAddLibraryWithSearching(const char* pathName) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern const struct mach_header* NSAddImage(const char* image_name, uint32_t options) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern bool NSAddLibrary(const char* pathName) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlopen()"); +extern bool NSAddLibraryWithSearching(const char* pathName) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlopen()"); +extern const struct mach_header* NSAddImage(const char* image_name, uint32_t options) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlopen()"); #define NSADDIMAGE_OPTION_NONE 0x0 #define NSADDIMAGE_OPTION_RETURN_ON_ERROR 0x1 #define NSADDIMAGE_OPTION_WITH_SEARCHING 0x2 #define NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED 0x4 #define NSADDIMAGE_OPTION_MATCH_FILENAME_BY_INSTALLNAME 0x8 -extern bool _dyld_present(void) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern bool _dyld_launched_prebound(void) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern bool _dyld_all_twolevel_modules_prebound(void) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_3,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern void _dyld_bind_objc_module(const void* objc_module) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern bool _dyld_bind_fully_image_containing_address(const void* address) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern bool _dyld_image_containing_address(const void* address) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_3,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); -extern void _dyld_lookup_and_bind(const char* symbol_name, void **address, NSModule* module) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern void _dyld_lookup_and_bind_with_hint(const char* symbol_name, const char* library_name_hint, void** address, NSModule* module) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_4,__IPHONE_NA,__IPHONE_NA); -extern void _dyld_lookup_and_bind_fully(const char* symbol_name, void** address, NSModule* module) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_1,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); - -extern const struct mach_header* _dyld_get_image_header_containing_address(const void* address) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_3,__MAC_10_5,__IPHONE_NA,__IPHONE_NA); +extern bool _dyld_present(void) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "always true"); +extern bool _dyld_launched_prebound(void) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "moot"); +extern bool _dyld_all_twolevel_modules_prebound(void) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.3, 10.5, "moot"); +extern bool _dyld_bind_fully_image_containing_address(const void* address) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlopen(RTLD_NOW)"); +extern bool _dyld_image_containing_address(const void* address) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.3, 10.5, "dladdr()"); +extern void _dyld_lookup_and_bind(const char* symbol_name, void **address, NSModule* module) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern void _dyld_lookup_and_bind_with_hint(const char* symbol_name, const char* library_name_hint, void** address, NSModule* module) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern void _dyld_lookup_and_bind_fully(const char* symbol_name, void** address, NSModule* module) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlsym()"); + +extern const struct mach_header* _dyld_get_image_header_containing_address(const void* address) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.3, 10.5, "dladdr()"); #if __cplusplus diff --git a/include/mach-o/dyld_gdb.h b/include/mach-o/dyld_gdb.h index eaadf64..f249515 100644 --- a/include/mach-o/dyld_gdb.h +++ b/include/mach-o/dyld_gdb.h @@ -23,100 +23,5 @@ #ifndef _DYLD_GDB_ #define _DYLD_GDB_ -/* - * For Mac OS X 10.4 or later, use the interface in mach-o/dylib_images.h - */ -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Prior to Mac OS 10.4, this is the interface gdb used to discover the mach-o images loaded in a process - */ -#if __i386__ -/* - * gdb_dyld_version is the version of gdb interface that dyld is currently - * exporting. For the interface described in this header file gdb_dyld_version - * is 2. As the gdb/dyld interface changes this number will be incremented and - * comments will be added as to what are the are changes for the various - * versions. - */ -extern unsigned int gdb_dyld_version; - -/* - * gdb_dyld_state_changed is the internal dyld routine called by dyld to notify - * gdb that the state of the data structures has changed. gdb is expected to - * put a break point on this routine and re-read the internal dyld data - * structures below when this break point is hit. - */ -extern void gdb_dyld_state_changed(void); - -/* - * gdb looks directly at parts of two of dyld's internal data structures. The - * list of object file images and the list of library images. The parts of - * these structures that gdb looks at will not change unless the value of - * gdb_dyld_version changes. The size of these structures and the other fields - * that gdb does not look at may change. - * - * struct object_images { - * struct object_image images[NOBJECT_IMAGES]; - * unsigned long nimages; - * struct object_images *next_images; - * ... - * }; - * - * struct library_images { - * struct library_image images[NLIBRARY_IMAGES]; - * unsigned long nimages; - * struct library_images *next_images; - * ... - * }; - * - * Both the object_image structure and the library_image structure - * start with a structure containing the following fields: - * - * struct image { - * char *physical_name; physical image name (file name) - * unsigned long vmaddr_slide; the slide from the staticly linked address - * struct mach_header *mh; address of the mach header of the image - * unsigned long valid; TRUE if this is struct is valid - * char *name; image name for reporting errors - * ... - * }; - * - * In gdb_dyld_version 1 the first field was "name". In gdb_dyld_version 2 the - * first field was changed to "physical_name" and a new fifth field "name" was - * added. These two fields are set to the same values except in the case of - * zero-link. In zero-link the NSLinkModule() option - * NSLINKMODULE_OPTION_TRAILING_PHYS_NAME is used and then the physical_name is - * the file name of the module zero-link loaded that is part of the logical - * image "name". - */ - -/* object_images is the global object_images structure */ - -/* the number of gdb_object_image structures present per bucket */ -extern unsigned int gdb_nobject_images; - -/* the size of each gdb_object_image structure */ -extern unsigned int gdb_object_image_size; - -/* library_images is the global library_images structure */ - -/* the number of gdb_library_image structures present per bucket */ -extern unsigned int gdb_nlibrary_images; - -/* the size of each gdb_library_image structure */ -extern unsigned int gdb_library_image_size; - -#endif - - - -#ifdef __cplusplus -} -#endif #endif /* _DYLD_GDB_ */ diff --git a/include/mach-o/dyld_images.h b/include/mach-o/dyld_images.h index 25e4afc..4b2da8f 100644 --- a/include/mach-o/dyld_images.h +++ b/include/mach-o/dyld_images.h @@ -135,11 +135,13 @@ struct dyld_all_image_infos { #if __LP64__ uintptr_t reserved[13-(DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT/2)]; #else - uintptr_t reserved[12-DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; + uintptr_t reserved[13-DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; #endif + /* the following field is only in version 16 (macOS 10.13, iOS 11.0) and later */ + uintptr_t compact_dyld_image_info_addr; + size_t compact_dyld_image_info_size; }; - /* * Beginning in Mac OS X 10.5, this is how gdb discovers where the shared cache is in a process. * Images that are in the shared cache have their segments rearranged, so when using imageFilePath @@ -158,7 +160,7 @@ struct dyld_shared_cache_ranges { uintptr_t length; } ranges[4]; /* max regions */ }; -extern struct dyld_shared_cache_ranges dyld_shared_cache_ranges; +extern struct dyld_shared_cache_ranges dyld_shared_cache_ranges __attribute__((visibility("hidden"))); diff --git a/include/mach-o/dyld_priv.h b/include/mach-o/dyld_priv.h index 2cd4006..93f1b90 100644 --- a/include/mach-o/dyld_priv.h +++ b/include/mach-o/dyld_priv.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -35,11 +36,6 @@ extern "C" { -// -// private interface between libSystem.dylib and dyld -// -extern int _dyld_func_lookup(const char* dyld_func_name, void **address); - // // private interface between libSystem.dylib and dyld // @@ -136,14 +132,6 @@ dyld_enumerate_tlv_storage(dyld_tlv_state_change_handler handler); extern intptr_t _dyld_get_image_slide(const struct mach_header* mh); -// -// get pointer to this process's dyld_all_image_infos -// 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() __attribute__((deprecated)); - - struct dyld_unwind_sections { @@ -164,7 +152,9 @@ struct dyld_unwind_sections // info->compact_unwind_section_length length of __TEXT/__unwind_info section // // Exists in Mac OS X 10.6 and later +#if !__USING_SJLJ_EXCEPTIONS__ extern bool _dyld_find_unwind_sections(void* addr, struct dyld_unwind_sections* info); +#endif // @@ -205,8 +195,8 @@ extern uint32_t dyld_get_sdk_version(const struct mach_header* mh); // This finds the SDK version that the main executable was built against. // Returns zero on error, or if SDK version could not be determined. // -// Note on WatchOS, this returns the equivalent iOS SDK version number -// (i.e an app built against WatchOS 2.0 SDK returne 9.0). To see the +// Note on watchOS, this returns the equivalent iOS SDK version number +// (i.e an app built against watchOS 2.0 SDK returne 9.0). To see the // platform specific sdk version use dyld_get_program_sdk_watch_os_version(). // // Exists in Mac OS X 10.8 and later @@ -214,20 +204,36 @@ extern uint32_t dyld_get_sdk_version(const struct mach_header* mh); extern uint32_t dyld_get_program_sdk_version(); -// Watch OS only. +#if __WATCH_OS_VERSION_MIN_REQUIRED +// watchOS only. // This finds the Watch OS SDK version that the main executable was built against. // Exists in Watch OS 2.0 and later -extern uint32_t dyld_get_program_sdk_watch_os_version(); // __WATCHOS_AVAILABLE(2.0); +extern uint32_t dyld_get_program_sdk_watch_os_version() __IOS_UNAVAILABLE __OSX_UNAVAILABLE __WATCHOS_AVAILABLE(2.0); -// Watch OS only. +// watchOS 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); +#endif +#if TARGET_OS_BRIDGE +// bridgeOS only. +// This finds the bridgeOS SDK version that the main executable was built against. +// Exists in bridgeOSOS 2.0 and later +extern uint32_t dyld_get_program_sdk_bridge_os_version(); + +// bridgeOS 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 bridgeOS version (e.g. 2.0). +// Exists in bridgeOS 2.0 and later +extern uint32_t dyld_get_program_min_bridge_os_version(); +#endif + // // 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. @@ -301,9 +307,13 @@ struct dyld_shared_cache_dylib_text_info { uint64_t textSegmentSize; uuid_t dylibUuid; const char* path; // pointer invalid at end of iterations + // following fields all exist in version 2 + uint64_t textSegmentOffset; // offset from start of cache }; typedef struct dyld_shared_cache_dylib_text_info dyld_shared_cache_dylib_text_info; + +#ifdef __BLOCKS__ // // 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. @@ -321,6 +331,7 @@ extern int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callbac // 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)); +#endif /* __BLOCKS */ // @@ -389,6 +400,32 @@ struct dyld_abort_payload { }; typedef struct dyld_abort_payload dyld_abort_payload; + +// These global variables are implemented in libdyld.dylib +// Old programs that used crt1.o also defined these globals. +// The ones in dyld are not used when an old program is run. +extern int NXArgc; +extern const char** NXArgv; +extern char** environ; // POSIX says this not const, because it pre-dates const +extern const char* __progname; + + +// called by libSystem_initializer only +extern void _dyld_initializer(); + +// never called from source code. Used by static linker to implement lazy binding +extern void dyld_stub_binder() __asm__("dyld_stub_binder"); + + +// called by exit() before it calls cxa_finalize() so that thread_local +// objects are destroyed before global objects. +extern void _tlv_exit(); + + +// temp exports to keep tapi happy, until ASan stops using dyldVersionNumber +extern double dyldVersionNumber; +extern const char* dyldVersionString; + #if __cplusplus } #endif /* __cplusplus */ diff --git a/include/mach-o/dyld_process_info.h b/include/mach-o/dyld_process_info.h index 8e7629f..7db413f 100644 --- a/include/mach-o/dyld_process_info.h +++ b/include/mach-o/dyld_process_info.h @@ -43,10 +43,11 @@ extern "C" { // 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[]); -void _dyld_debugger_notification(enum dyld_notify_mode, unsigned long count, uint64_t machHeaders[]); struct dyld_process_cache_info { diff --git a/interlinked-dylibs/AdjustForNewSegmentLocation.cpp b/interlinked-dylibs/AdjustForNewSegmentLocation.cpp deleted file mode 100644 index f03abc6..0000000 --- a/interlinked-dylibs/AdjustForNewSegmentLocation.cpp +++ /dev/null @@ -1,1059 +0,0 @@ -/* -*- 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 deleted file mode 100644 index 0fbc09c..0000000 --- a/interlinked-dylibs/BindAllImages.cpp +++ /dev/null @@ -1,705 +0,0 @@ -/* -*- 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" - -#include "MachOProxy.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, const std::map>& segmentMap, macho_header

* mh, MachOProxy* proxy); - - 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::vector dylibs, const std::map>& segmentMap, std::vector& pointersForASLR); - -private: - typedef typename P::uint_t pint_t; - typedef typename P::E E; - - 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; - MachOProxy* _proxy; - 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; - const std::map>& _segmentMap; - std::vector _reExportedDylibNames; - std::vector*> _reExportedDylibs; - std::vector*> _dependentDylibs; - pint_t _baseAddress; - ResolverClientsMap _resolverClients; - ResolverToBlessedLazyPointerMap _resolverBlessedMap; -}; - -template -BindInfo

::BindInfo(void* cacheBuffer, const std::map>& segmentMap, macho_header

* mh, MachOProxy* proxy) - : _cacheBuffer(cacheBuffer) - , _mh(mh) - , _segmentMap(segmentMap) - , _proxy(proxy) - , _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); - } -} - -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); - } - - - // 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 info = _proxy->symbolInfo(symbolName); - if (info != nullptr) { - if (info->isSymbolReExport) { - const char* importName = symbolName; - if (!info->reExportName.empty()) - importName = info->reExportName.c_str(); - std::string& depPath = _dependentPaths[info->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 = (pint_t)_proxy->addressOf(symbolName, _segmentMap); - *foundIn = this; - *isResolverSymbol = info->isResolver; - *isAbsolute = info->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 -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; - const auto info = _proxy->symbolInfo(resolverSymbolName); - if (info) { - 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::vector dylibs, const std::map>& segmentMap, std::vector& pointersForASLR) -{ - std::unordered_map*> dylibPathToBindInfo; - std::unordered_map*, BindInfo

*> headersToBindInfo; - for (auto& dylib : dylibs) { - auto dylibI = segmentMap.find(dylib); - assert(dylibI != segmentMap.end()); - macho_header

* mh = (macho_header

*)((uint8_t*)cacheBuffer + dylibI->second[0].cacheFileOffset); - if (headersToBindInfo.count(mh) == 0) { - headersToBindInfo[mh] = new BindInfo

(cacheBuffer, segmentMap, mh, dylib); - } - dylibPathToBindInfo[dylib->installName] = headersToBindInfo[mh]; - for (const auto& alias : dylib->installNameAliases) { - dylibPathToBindInfo[alias] = 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); - } - - // clean up - for (const auto& entry: headersToBindInfo) { - delete entry.second; - } -} - - -} // anonymous namespace - -void SharedCache::bindAllImagesInCache(const std::vector dylibs, const std::map>& segmentMap, std::vector& pointersForASLR) -{ - switch ( _arch.arch ) { - case CPU_TYPE_ARM: - case CPU_TYPE_I386: - BindInfo>::bindAllImagesInCache(_buffer.get(), dylibs, segmentMap, pointersForASLR); - break; - case CPU_TYPE_X86_64: - case CPU_TYPE_ARM64: - BindInfo>::bindAllImagesInCache(_buffer.get(), dylibs, segmentMap, pointersForASLR); - break; - default: - terminate("unsupported arch 0x%08X", _arch.arch); - } -} - - - diff --git a/interlinked-dylibs/CodeSigningTypes.h b/interlinked-dylibs/CodeSigningTypes.h deleted file mode 100644 index e4a4049..0000000 --- a/interlinked-dylibs/CodeSigningTypes.h +++ /dev/null @@ -1,130 +0,0 @@ -/* -*- 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 deleted file mode 100644 index 4178426..0000000 --- a/interlinked-dylibs/FileCache.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* -*- 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_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0)); -} - - -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) { - entries[normalizedPath] = fill(normalizedPath); - } - }); -} - -std::tuple FileCache::cacheLoad(const std::string path) { - bool found = false; - std::tuple retval; - std::string normalizedPath = normalize_absolute_file_path(path); - cacheBuilderDispatchSync(cache_queue, [=, &found, &retval] { - auto entry = entries.find(normalizedPath); - if (entry != entries.end()) { - retval = entry->second; - found = true; - } - }); - - if (!found) { - auto info = fill(normalizedPath); - cacheBuilderDispatchSync(cache_queue, [=, &found, &retval] { - auto entry = entries.find(normalizedPath); - if (entry != entries.end()) { - retval = entry->second; - }else { - retval = entries[normalizedPath] = info; - retval = info; - } - }); - } - - return retval; -} - -//FIXME error handling -std::tuple 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); - return std::make_tuple((uint8_t*)(-1), stat_buf, false); - } - - if ( fstat(fd, &stat_buf) == -1) { - verboseLog("can't stat open file '%s', errno=%d", path.c_str(), errno); - ::close(fd); - return std::make_tuple((uint8_t*)(-1), stat_buf, false); - } - - if ( stat_buf.st_size < 4096 ) { - verboseLog("file too small '%s'", path.c_str()); - ::close(fd); - return std::make_tuple((uint8_t*)(-1), stat_buf, false); - } - - 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 file at %s failed, errno=%d", path.c_str(), errno); - ::close(fd); - return std::make_tuple((uint8_t*)(-1), stat_buf, false); - } - - ::close(fd); - - //PERF-HACK: touch bits of the MachO before we need them to speed things up on a spinning disk - madvise((void*)buffer_ptr, 4096, MADV_WILLNEED); - - //if it is fat we need to touch each architecture - const fat_header* fh = (fat_header*)buffer_ptr; - if ( OSSwapBigToHostInt32( fh->magic ) == FAT_MAGIC ) { - 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 ) { - uint32_t fileOffset = OSSwapBigToHostInt32( slices[i].offset ); - madvise((void*)((uint8_t *)buffer_ptr+fileOffset), 4096, MADV_WILLNEED); - } - } - - return std::make_tuple((uint8_t*)buffer_ptr, stat_buf, rootlessProtected); -} - - - - - - diff --git a/interlinked-dylibs/Logging.cpp b/interlinked-dylibs/Logging.cpp deleted file mode 100644 index 296b551..0000000 --- a/interlinked-dylibs/Logging.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* -*- 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" - -//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) -{ - dispatch_async(getLogQueue(), ^{ - (void)fprintf(fd, "%s", str); - free((void*)str); - }); -} - -#define VLOG(header) \ - va_list list; \ - va_start(list, format); \ - char *temp, *temp2; \ - vasprintf(&temp, format, list); \ - auto ctx = getLoggingContext(); \ - if (ctx && !ctx->name().empty()) { \ - asprintf(&temp2, "[%s] %s%s\n", ctx->name().c_str(), header, temp); \ - } else { \ - asprintf(&temp2, "%s%s\n", header, temp); \ - } \ - free(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* blockTemp; - vasprintf(&blockTemp, format, list); - - auto ctx = getLoggingContext(); - if (ctx) { - for (auto& target : ctx->targets().second) { - ctx->targets().first->configuration(target.first).architecture(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(temp2); - } 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 deleted file mode 100644 index 807261a..0000000 --- a/interlinked-dylibs/Logging.h +++ /dev/null @@ -1,132 +0,0 @@ -/* -*- 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) { - if (context) { - WarningTargets warningTargets = context->targets(); - for (auto& target : warningTargets.second) { - warningTargets.first->configuration(target.first).architecture(target.second).results.failure = exception; - } - 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 deleted file mode 100644 index a7418b9..0000000 --- a/interlinked-dylibs/MachOProxy.cpp +++ /dev/null @@ -1,504 +0,0 @@ -// -// DylibProxy.cpp -// dyld -// -// Created by Louis Gerbarg on 1/27/16. -// -// - -#include -#include - -#include "mega-dylib-utils.h" -#include "Logging.h" - -#include "Trie.hpp" -#include "MachOProxy.h" - -#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE -#define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 -#endif - -namespace { -std::vector mapMachOFile(const std::string& buildPath, const std::string& path) -{ - std::vector retval; - const uint8_t* p = (uint8_t*)( -1 ); - struct stat stat_buf; - bool rootless; - - std::tie(p, stat_buf, rootless) = fileCache.cacheLoad(buildPath); - - 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.push_back(new MachOProxy(buildPath, path, stringForArch(arch), stat_buf.st_ino, stat_buf.st_mtime, fileOffset, fileSize, rootless)); - //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.push_back(new MachOProxy(buildPath, path, stringForArch(arch), stat_buf.st_ino, stat_buf.st_mtime, fileOffset, fileSize, rootless)); - //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; -} - -} /* Anonymous namespace */ - -template -std::vector MachOProxy::dependencies() -{ - const uint8_t* buffer = getBuffer(); - const macho_header

* mh = (const macho_header

*)buffer; - 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; - std::vector retval; - - 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: { - macho_dylib_command

* dylib = (macho_dylib_command

*)cmd; - std::string depName = dylib->name(); - - retval.push_back(depName); - } break; - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd) + cmd->cmdsize()); - } - - return retval; -} - -template -std::vector MachOProxy::reexports() -{ - const uint8_t* buffer = getBuffer(); - const macho_header

* mh = (const macho_header

*)buffer; - 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; - std::vector retval; - - for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd()) { - case LC_REEXPORT_DYLIB: { - macho_dylib_command

* dylib = (macho_dylib_command

*)cmd; - std::string depName = dylib->name(); - - retval.push_back(depName); - } break; - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd) + cmd->cmdsize()); - } - - return retval; -} - -std::vector MachOProxy::dependencies() -{ - switch (archForString(arch).arch) { - case CPU_TYPE_ARM: - case CPU_TYPE_I386: - return dependencies>(); - case CPU_TYPE_X86_64: - case CPU_TYPE_ARM64: - return dependencies>(); - break; - default: - return std::vector(); - } -} - -std::vector MachOProxy::reexports() -{ - switch (archForString(arch).arch) { - case CPU_TYPE_ARM: - case CPU_TYPE_I386: - return reexports>(); - case CPU_TYPE_X86_64: - case CPU_TYPE_ARM64: - return reexports>(); - break; - default: - return std::vector(); - } -} - -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 macho_dyld_info_command

* dyldInfo = nullptr; - const uint32_t cmd_count = mh->ncmds(); - const macho_load_command

* cmd = cmds; - uint64_t baseAddr = 0; - _filetype = mh->filetype(); - if (_filetype == MH_DYLIB_STUB) { - return "stub dylib"; - } - if (_filetype == MH_DSYM) { - return "DSYM"; - } - 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(); - addAlias(path); - } break; - case LC_UUID: { - const macho_uuid_command

* uuidCmd = (macho_uuid_command

*)cmd; - uuid = UUID(uuidCmd->uuid()); - } 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 ( isExecutable() && 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)"; - } - } break; - case macho_segment_command

::CMD: { - const macho_segment_command

* segCmd = (macho_segment_command

*)cmd; - MachOProxySegment seg; - seg.name = segCmd->segname(); - seg.size = align(segCmd->vmsize(), 12); - seg.vmaddr = segCmd->vmaddr(); - 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); - if (seg.name == "__TEXT") { - baseAddr = seg.vmaddr; - } - } 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: - dyldInfo = (macho_dyld_info_command

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

*)(((uint8_t*)cmd) + cmd->cmdsize()); - } - - identifier = uuid; - - if (!hasDylidInfo) { - return "built for old OS"; - } - - if (dyldInfo && dyldInfo->bind_size() != 0) { - _bind_offset = dyldInfo->bind_off(); - _bind_size = dyldInfo->bind_size(); - } - - if (dyldInfo && dyldInfo->lazy_bind_size() != 0) { - _lazy_bind_offset = dyldInfo->lazy_bind_off(); - _lazy_bind_size = dyldInfo->lazy_bind_size(); - } - - // if no export info, no _exports map to build - if (dyldInfo && dyldInfo->export_size() != 0) { - std::vector exports; - const uint8_t* exportsStart = &buffer[dyldInfo->export_off()]; - const uint8_t* exportsEnd = &exportsStart[dyldInfo->export_size()]; - if (!ExportInfoTrie::parseTrie(exportsStart, exportsEnd, exports)) { - terminate("malformed exports trie in %s", path.c_str()); - } - - for (const ExportInfoTrie::Entry& entry : exports) { - if (!_exports[entry.name].isAbsolute) { - for (const auto& seg : segments) { - if (seg.size > 0 && (seg.vmaddr - baseAddr) <= entry.info.address && entry.info.address < (seg.vmaddr - baseAddr) + seg.size) { - _exports[entry.name].segmentOffset = entry.info.address - (seg.vmaddr - baseAddr); - _exports[entry.name].segmentName = seg.name; - break; - } - } - } else { - _exports[entry.name].segmentOffset = (uint64_t)entry.info.address; - _exports[entry.name].segmentName = ""; - } - - 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; - break; - default: - terminate("non-regular symbol binding not supported for %s in %s", entry.name.c_str(), path.c_str()); - break; - } - } - } - - if (!isDylib()) { - return ""; - } - - 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 (_filetype == MH_DYLIB); -} - -const bool MachOProxy::isExecutable() -{ - return (_filetype == MH_EXECUTE); -} - -static std::map identifierMap; -std::map, MachOProxy*> archMap; -static dispatch_queue_t identifierQueue; - -MachOProxy* MachOProxy::forIdentifier(const ImageIdentifier& identifier, const std::string preferredArch) -{ - auto i = identifierMap.find(identifier); - // We need an identifier - if (i == identifierMap.end()) - return nullptr; - - // Is the identifier the arch we want? - if (i->second->arch == preferredArch) - return i->second; - - // Fallback to a slow path to try to find a best fit - return forInstallnameAndArch(i->second->installName, preferredArch); -} - -MachOProxy* MachOProxy::forInstallnameAndArch(const std::string& installname, const std::string& arch) -{ - auto i = archMap.find(std::make_pair(installname, arch)); - if (i == archMap.end()) - i = archMap.find(std::make_pair(installname, fallbackArchStringForArchString(arch))); - if (i != archMap.end()) - return i->second; - return nullptr; -} - -void MachOProxy::mapDependencies() -{ - // Build a complete map of all installname/alias,archs to their proxies - runOnAllProxies(false, [&](MachOProxy* proxy) { - archMap[std::make_pair(proxy->path, proxy->arch)] = proxy; - for (auto& alias : proxy->installNameAliases) { - archMap[std::make_pair(alias, proxy->arch)] = proxy; - } - }); - - //Wire up the dependencies - runOnAllProxies(false, [&](MachOProxy* proxy) { - auto dependencyInstallnames = proxy->dependencies(); - for (auto dependencyInstallname : dependencyInstallnames) { - auto dependencyProxy = forInstallnameAndArch(dependencyInstallname, proxy->arch); - if (dependencyProxy == nullptr) { - proxy->error = "Missing dependency: " + dependencyInstallname; - } else { - proxy->requiredIdentifiers.push_back(dependencyProxy->identifier); - dependencyProxy->dependentIdentifiers.push_back(proxy->identifier); - } - } - - auto reexportInstallnames = proxy->reexports(); - for (auto reexportInstallname : reexportInstallnames) { - auto reexportProxy = forInstallnameAndArch(reexportInstallname, proxy->arch); - if (reexportProxy == nullptr) { - proxy->error = "Missing reexport dylib: " + reexportInstallname; - } else { - proxy->_reexportProxies.push_back(reexportProxy); - } - } - - }); -} - -void MachOProxy::runOnAllProxies(bool concurrently, std::function lambda) -{ - dispatch_group_t runGroup = dispatch_group_create(); - dispatch_queue_t runQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, NULL); - - for (auto& identifier : identifierMap) { - if (concurrently) { - cacheBuilderDispatchGroupAsync(runGroup, runQueue, [&] { - lambda(identifier.second); - }); - } else { - lambda(identifier.second); - } - } - - dispatch_group_wait(runGroup, DISPATCH_TIME_FOREVER); -} - -std::map MachOProxy::loadProxies(const std::string& buildPath, const std::string& path, bool warnOnProblems, bool ignoreUncacheableDylibsInExecutables) -{ - std::vector slices = mapMachOFile(buildPath, path); - std::map retval; - - for ( auto& slice : slices ) { - std::string errorMessage; - verboseLog( "analyzing file '%s'", path.c_str() ); - switch (archForString(slice->arch).arch) { - case CPU_TYPE_ARM: - case CPU_TYPE_I386: - errorMessage = slice->machoParser>(ignoreUncacheableDylibsInExecutables); - break; - case CPU_TYPE_X86_64: - case CPU_TYPE_ARM64: - errorMessage = slice->machoParser>(ignoreUncacheableDylibsInExecutables); - break; - default: - errorMessage = "unsupported arch '" + slice->arch + "'"; - break; - } - - if (errorMessage.empty()) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - identifierQueue = dispatch_queue_create("com.apple.dyld.cache.metabom.ids", DISPATCH_QUEUE_SERIAL); - }); - retval[slice->arch] = slice; - dispatch_sync(identifierQueue, ^{ - identifierMap[slice->identifier] = slice; - }); - } else { - if (warnOnProblems) - warning("%s (%s)", errorMessage.c_str(), path.c_str()); - } - } - - return retval; -} - -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(buildPath); - 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 deleted file mode 100644 index 4781504..0000000 --- a/interlinked-dylibs/MachOProxy.h +++ /dev/null @@ -1,160 +0,0 @@ -// -// DylibProxy.h -// dyld -// -// Created by Louis Gerbarg on 1/27/16. -// -// - -#ifndef MachOProxy_h -#define MachOProxy_h - -#include -#include -#include -#include -#include - -#include - -#include "mega-dylib-utils.h" - -struct MachOProxy; - -struct MachOProxySegment { - std::string name; - uint64_t size; - uint64_t sizeOfSections; - uint64_t vmaddr; - uint32_t diskSize; - uint32_t fileOffset; - uint8_t p2align; - uint8_t protection; -}; - -struct MachOProxy { - MachOProxy(const std::string& bp, const std::string& p, const std::string& a, ino_t i, time_t t, uint32_t o, uint32_t s, bool r) - : buildPath(bp) - , path(p) - , arch(a) - , fatFileOffset(o) - , fileSize(s) - , lastModTime(t) - , inode(i) - , installNameOffsetInTEXT(0) - , rootlessProtected(r) - , _bind_offset(0) - , _bind_size(0) - , queue(dispatch_queue_create("com.apple.dyld.proxy", NULL)) - { - } - - struct SymbolInfo { - SymbolInfo() {} - std::string segmentName; - uint64_t segmentOffset = 0; - bool isResolver = false; - bool isAbsolute = false; - bool isSymbolReExport = false; - bool isThreadLocal = false; - int reExportDylibIndex = 0; - std::string reExportName; - }; - - const std::string buildPath; - const std::string path; - const std::string arch; - const uint32_t fatFileOffset; - const uint32_t fileSize; - const time_t lastModTime; - const ino_t inode; - const bool rootlessProtected; - dispatch_queue_t queue; - std::string installName; - std::set installNameAliases; - uint32_t installNameOffsetInTEXT; - std::string error; - std::vector requiredIdentifiers; - std::vector dependentIdentifiers; - UUID uuid; - ImageIdentifier identifier; - std::vector segments; - - const uint8_t* getBuffer(); - const uint8_t* getBindStart() { return &(getBuffer())[_bind_offset]; } - const uint8_t* getBindEnd() { return &(getBuffer())[_bind_offset + _bind_size]; } - const uint8_t* getLazyBindStart() { return &(getBuffer())[_lazy_bind_offset]; } - const uint8_t* getLazyBindEnd() { return &(getBuffer())[_lazy_bind_offset + _lazy_bind_size]; } - - const bool isDylib(); - const bool isExecutable(); - bool addAlias(const std::string& alias); - static void mapDependencies(); - - const uint64_t addressOf(const std::string& symbol, const std::map>& segmentMap) - { - auto info = symbolInfo(symbol); - assert(info != nullptr); - if (info->isAbsolute) - return info->segmentOffset; - auto proxyI = segmentMap.find(this); - assert(proxyI != segmentMap.end()); - - for (const auto& seg : proxyI->second) { - if (seg.base->name == info->segmentName) { - assert(!info->segmentName.empty()); - return seg.address + info->segmentOffset; - } - } - - return 0; - } - - SymbolInfo* symbolInfo(const std::string& symbol) - { - auto i = _exports.find(symbol); - if (i != _exports.end()) - return &i->second; - return nullptr; - } - - bool providesSymbol(const std::string& symbol) - { - if (_exports.find(symbol) != _exports.end()) - return true; - - for (const auto& proxy : _reexportProxies) { - if (proxy->providesSymbol(symbol)) - return true; - } - return false; - } - static std::map loadProxies(const std::string& prefix, const std::string& path, bool warnOnProblems = false, bool ignoreUncacheableDylibsInExecutables = false); - static void runOnAllProxies(bool concurrently, std::function lambda); - static MachOProxy* forIdentifier(const ImageIdentifier& identifier, const std::string preferredArch); - static MachOProxy* forInstallnameAndArch(const std::string& installname, const std::string& arch); - - std::vector dependencies(); - std::vector reexports(); - -private: - uint32_t _filetype; - std::map _exports; - uint32_t _bind_offset; - uint32_t _bind_size; - uint32_t _lazy_bind_offset; - uint32_t _lazy_bind_size; - std::vector _reexportProxies; - - template - std::string machoParser(bool ignoreUncacheableDylibsInExecutables); - - template - std::vector dependencies(); - - template - std::vector reexports(); -}; - - -#endif /* MachOProxy_h */ diff --git a/interlinked-dylibs/Manifest.h b/interlinked-dylibs/Manifest.h deleted file mode 100644 index 6611ef0..0000000 --- a/interlinked-dylibs/Manifest.h +++ /dev/null @@ -1,253 +0,0 @@ -// -// Manifest.h -// dyld -// -// Created by Louis Gerbarg on 7/23/15. -// -// - -#ifndef Manifest_h -#define Manifest_h - -#include -#include -#include -#include - -#include -#include - -#include - -struct MachOProxy; - -extern void terminate(const char* format, ...) __printflike(1, 2) __attribute__((noreturn)); -extern std::string toolDir(); - -struct SharedCache; -struct Manifest; - -struct Manifest { - struct Project { - std::vector sources; - }; - - struct File { - MachOProxy* proxy; - - File( MachOProxy* P ) : proxy( P ) {} - }; - - struct Anchor { - ImageIdentifier identifier; - bool required; - Anchor( const ImageIdentifier& I ) : identifier( I ) {} - }; - - 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 uuid; - std::string installname; - std::vector segments; - DylibInfo(void) : included(true) {} - }; - - struct Results { - std::string failure; - std::map dylibs; - std::vector warnings; - CacheInfo developmentCache; - CacheInfo productionCache; - DylibInfo& dylibForInstallname(const std::string& installname) - { - auto i = find_if(dylibs.begin(), dylibs.end(), [&installname](std::pair d) { return d.second.installname == installname; }); - assert(i != dylibs.end()); - return i->second; - } - void exclude(MachOProxy* proxy, const std::string& reason); - }; - - struct Architecture { - std::vector anchors; - mutable Results results; - - bool operator==(const Architecture& O) const - { - 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 - || Odylib->second.uuid != dylib.second.uuid) - return false; - } - } - - for (const 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 - || dylib->second.uuid != Odylib.second.uuid) - return false; - } - } - - return true; - } - - bool operator!=(const Architecture& other) const { return !(*this == other); } - }; - - struct Configuration { - std::string platformName; - std::string metabomTag; - std::set metabomExcludeTags; - std::set metabomRestrictTags; - std::set restrictedInstallnames; - std::map architectures; - - bool operator==(const Configuration& O) const - { - return architectures == O.architectures; - } - - bool operator!=(const Configuration& other) const { return !(*this == other); } - - const Architecture& architecture(const std::string& architecture) const - { - assert(architectures.find(architecture) != architectures.end()); - return architectures.find(architecture)->second; - } - - void forEachArchitecture(std::function lambda) - { - for (const auto& architecutre : architectures) { - lambda(architecutre.first); - } - } - }; - - const std::map& projects() - { - return _projects; - } - - const Configuration& configuration(const std::string& configuration) const - { - assert(_configurations.find(configuration) != _configurations.end()); - return _configurations.find(configuration)->second; - } - - void forEachConfiguration(std::function lambda) - { - for (const auto& configuration : _configurations) { - lambda(configuration.first); - } - } - - void addProjectSource(const std::string& project, const std::string& source, bool first = false) - { - auto& sources = _projects[project].sources; - if (std::find(sources.begin(), sources.end(), source) == sources.end()) { - if (first) { - sources.insert(sources.begin(), source); - } else { - sources.push_back(source); - } - } - } - - const std::string projectPath(const std::string& projectName) - { - auto project = _projects.find(projectName); - if (project == _projects.end()) - return ""; - if (project->second.sources.size() == 0) - return ""; - return project->second.sources[0]; - } - - - const bool empty(void) { - for (const auto& configuration : _configurations) { - if (configuration.second.architectures.size() != 0) - return false; - } - return true; - } - - const std::string dylibOrderFile() const { return _dylibOrderFile; }; - void setDylibOrderFile(const std::string& dylibOrderFile) { _dylibOrderFile = dylibOrderFile; }; - - const std::string dirtyDataOrderFile() const { return _dirtyDataOrderFile; }; - void setDirtyDataOrderFile(const std::string& dirtyDataOrderFile) { _dirtyDataOrderFile = dirtyDataOrderFile; }; - - const std::string metabomFile() const { return _metabomFile; }; - void setMetabomFile(const std::string& metabomFile) { _metabomFile = metabomFile; }; - - const std::string platform() const { return _platform; }; - void setPlatform(const std::string& platform) { _platform = platform; }; - - const std::string& build() const { return _build; }; - void setBuild(const std::string& build) { _build = build; }; - const uint32_t version() const { return _manifestVersion; }; - void setVersion(const uint32_t manifestVersion) { _manifestVersion = manifestVersion; }; - bool normalized; - - Manifest(void) {} - Manifest(const std::set& archs, const std::string& overlayPath, const std::string& rootPath, const std::set& paths); -#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); - bool sameContentsAsCacheAtPath(const std::string& configuration, const std::string& architecture, - const std::string& path) const; - void remove(const std::string& config, const std::string& arch); - MachOProxy* removeLargestLeafDylib(const std::string& configuration, const std::string& architecture); - bool checkLinks(); - void runConcurrently(dispatch_queue_t queue, dispatch_semaphore_t concurrencyLimitingSemaphore, std::function lambda); - bool filterForConfig(const std::string& configName); - -private: - uint32_t _manifestVersion; - std::string _build; - std::string _dylibOrderFile; - std::string _dirtyDataOrderFile; - std::string _metabomFile; - std::string _platform; - std::map _projects; - std::map _configurations; - void removeDylib(MachOProxy* proxy, const std::string& reason, const std::string& configuration, const std::string& architecture, - std::unordered_set& processedIdentifiers); - void calculateClosure(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); - MachOProxy* dylibProxy(const std::string& installname, const std::string& arch); -}; - - -#endif /* Manifest_h */ diff --git a/interlinked-dylibs/Manifest.mm b/interlinked-dylibs/Manifest.mm deleted file mode 100644 index dad8f43..0000000 --- a/interlinked-dylibs/Manifest.mm +++ /dev/null @@ -1,768 +0,0 @@ -// -// 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 "dsc_iterator.h" -#include "MachOProxy.h" -#include "mega-dylib-utils.h" - -#include "Manifest.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()]; } - -std::string fileExists(const std::string& path) -{ - const uint8_t* p = (uint8_t*)(-1); - struct stat stat_buf; - - std::tie(p, stat_buf, rootless) = fileCache.cacheLoad(path); - if (p != (uint8_t*)(-1)) { - return normalize_absolute_file_path(path); - } - - return ""; -} - -} /* Anonymous namespace */ - -void Manifest::Results::exclude(MachOProxy* proxy, const std::string& reason) -{ - dylibs[proxy->identifier].uuid = proxy->uuid; - dylibs[proxy->identifier].installname = proxy->installName; - dylibs[proxy->identifier].included = false; - dylibs[proxy->identifier].exclusionInfo = reason; -} - -Manifest::Manifest(const std::set& archs, const std::string& overlayPath, const std::string& rootPath, const std::set& paths) -{ - 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::loadProxies(fullPath, path); - - for (const auto& arch : archs) { - auto proxyI = proxies.find(arch); - if (proxyI == proxies.end()) - proxyI = proxies.find(fallbackArchStringForArchString(arch)); - if (proxyI == proxies.end()) - continue; - - auto dependecies = proxyI->second->dependencies(); - for (const auto& dependency : dependecies) { - unprocessedPaths.insert(dependency); - } - _configurations["localhost"].architectures[arch].anchors.push_back(proxyI->second->identifier); - } - - //Stuff - } - - pathsToProcess.clear(); - std::set_difference(unprocessedPaths.begin(), unprocessedPaths.end(), processedPaths.begin(), processedPaths.end(), - std::inserter(pathsToProcess, pathsToProcess.begin())); - } - MachOProxy::mapDependencies(); -} - -#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; - std::vector> configProxies; - - setMetabomFile([manifestDict[@"metabomFile"] UTF8String]); - - for (NSString* project in manifestDict[@"projects"]) { - for (NSString* source in manifestDict[@"projects"][project]) { - addProjectSource([project UTF8String], [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]; - } - - setVersion([manifestDict[@"manifest-version"] unsignedIntValue]); - setBuild([manifestDict[@"build"] UTF8String]); - if (manifestDict[@"dylibOrderFile"]) { - setDylibOrderFile([manifestDict[@"dylibOrderFile"] UTF8String]); - } - if (manifestDict[@"dirtyDataOrderFile"]) { - setDirtyDataOrderFile([manifestDict[@"dirtyDataOrderFile"] UTF8String]); - } - - auto metabom = MBMetabomOpen(metabomFile().c_str(), false); - auto metabomEnumerator = MBIteratorNewWithPath(metabom, ".", ""); - MBEntry entry; - - auto bomSemaphore = dispatch_semaphore_create(32); - auto bomGroup = dispatch_group_create(); - auto bomQueue = dispatch_queue_create("com.apple.dyld.cache.metabom.bom", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0)); - auto archQueue = dispatch_queue_create("com.apple.dyld.cache.metabom.arch", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0)); - auto manifestQueue = dispatch_queue_create("com.apple.dyld.cache.metabom.arch", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, 0)); - - // FIXME error handling (NULL metabom) - - //First we iterate through the bom and build our objects - - while ((entry = MBIteratorNext(metabomEnumerator))) { - dispatch_semaphore_wait(bomSemaphore, DISPATCH_TIME_FOREVER); - cacheBuilderDispatchGroupAsync(bomGroup, manifestQueue, [this, &bomSemaphore, &archQueue, &bomGroup, &bomQueue, &metabom, entry, &overlays, &metabomTagMap, &metabomRestrictedTagMap, &metabomExcludeTagMap, &manifestDict, &configProxies] { - BOMFSObject fsObject = nullptr; - std::string entryPath; - BOMFSObjType entryType; - cacheBuilderDispatchSync(bomQueue, [&entry, &fsObject, &entryPath, &entryType] { - fsObject = MBEntryGetFSObject(entry); - entryPath = BOMFSObjectPathName(fsObject); - if (entryPath[0] == '.') { - entryPath.erase(0, 1); - } - entryType = BOMFSObjectType(fsObject); - }); - - MBTag tag; - auto tagCount = MBEntryGetNumberOfProjectTags(entry); - if ( entryType == BOMFileType && BOMFSObjectIsBinaryObject(fsObject) && MBEntryGetNumberOfProjectTags(entry) != 0 && tagCount != 0 ) { - 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) { - cacheBuilderDispatchSync(bomQueue, [i, &metabom, &tagStrs, &tagStrMap, &tags] { - 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; - cacheBuilderDispatchSync(bomQueue, [&projectName, &metabom, &tag] { - projectName = MBMetabomGetProjectForTag(metabom, tag); - }); - - std::map proxies; - for (const auto& overlay : overlays) { - proxies = MachOProxy::loadProxies(overlay + "/" + entryPath, entryPath); - if (proxies.size() > 0) - break; - } - - if (proxies.size() == 0) { - proxies = MachOProxy::loadProxies(projectPath(projectName) + "/" + entryPath, entryPath); - } - - tagCount = MBEntryGetNumberOfPackageTags(entry); - MBTag* tags = (MBTag*)malloc(sizeof(MBTag) * tagCount); - MBEntryGetPackageTags(entry, tags); - std::set tagStrs; - - cacheBuilderDispatchSync(bomQueue, [&] { - for (auto i = 0; i < tagCount; ++i) { - tagStrs.insert(MBMetabomGetPackageForTag(metabom, tags[i])); - } - }); - - for (auto& proxy : proxies) { - 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; - } - cacheBuilderDispatchGroupAsync(bomGroup, archQueue, [this, &manifestDict, &configProxies, configuration, proxy, tagStr] { - if ([manifestDict[@"configurations"][cppToObjStr(configuration->second)][@"architectures"] - containsObject:cppToObjStr(proxy.second->arch)]) { - _configurations[configuration->second].architectures[proxy.second->arch].anchors.push_back(proxy.second->identifier); - } - }); - } - } - } - dispatch_semaphore_signal(bomSemaphore); - }); - } - - dispatch_group_wait(bomGroup, DISPATCH_TIME_FOREVER); - MBIteratorFree(metabomEnumerator); - MBMetabomFree(metabom); - MachOProxy::mapDependencies(); -} - -#endif - -template -bool checkLink(MachOProxy* proxy, const uint8_t* p, const uint8_t* end) -{ - bool retval = true; - std::vector dylibs = proxy->dependencies(); - - std::string symbolName; - int libraryOrdinal = 0; - 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: - break; - case BIND_OPCODE_SET_ADDEND_SLEB: - (void)read_sleb128(p, end); - break; - case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: - case BIND_OPCODE_ADD_ADDR_ULEB: - (void)read_uleb128(p, end); - break; - case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: - (void)read_uleb128(p, end); - case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - (void)read_uleb128(p, end); - case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - case BIND_OPCODE_DO_BIND: { - if (libraryOrdinal <= 0) - break; - if ( libraryOrdinal > dylibs.size() ) { - warning("Illegal library ordinal (%d) in dylib %s bind opcode (max ordinal %lu)", libraryOrdinal, proxy->path.c_str(), dylibs.size()); - retval = false; - } - else { - auto dependencyProxy = MachOProxy::forInstallnameAndArch(dylibs[libraryOrdinal - 1], proxy->arch); - if (!weakImport && (!dependencyProxy || !dependencyProxy->providesSymbol(symbolName))) { - warning("Could not find symbol %s in dylib %s for %s", symbolName.c_str(), dylibs[libraryOrdinal - 1].c_str(), proxy->path.c_str()); - retval = false; - } - } - } break; - default: - warning("bad bind opcode in binary 0x%02X in %s", *p, proxy->path.c_str()); - } - } - - return retval; -} - -bool checkLink(MachOProxy* proxy) -{ - switch (archForString(proxy->arch).arch) { - case CPU_TYPE_ARM: - case CPU_TYPE_I386: - return (checkLink>(proxy, proxy->getBindStart(), proxy->getBindEnd()) - && checkLink>(proxy, proxy->getLazyBindStart(), proxy->getLazyBindEnd())); - case CPU_TYPE_ARM64: - case CPU_TYPE_X86_64: - return (checkLink>(proxy, proxy->getBindStart(), proxy->getBindEnd()) - && checkLink>(proxy, proxy->getLazyBindStart(), proxy->getLazyBindEnd())); - default: - terminate("unsupported arch 0x%08X", archForString(proxy->arch).arch); - } -} - -bool Manifest::checkLinks() -{ - dispatch_queue_t linkCheckQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, NULL); - dispatch_semaphore_t linkCheckSemphore = dispatch_semaphore_create(32); - - dispatch_group_t linkCheckGroup = dispatch_group_create(); - - runConcurrently(linkCheckQueue, linkCheckSemphore, [this](const std::string configuration, const std::string architecture) { - for (const auto& anchor : this->configuration(configuration).architecture(architecture).anchors) { - const auto identifier = anchor.identifier; - const auto proxy = MachOProxy::forIdentifier(identifier, architecture); - if (proxy->isExecutable()) { - checkLink(proxy); - } - } - }); - - dispatch_group_wait(linkCheckGroup, DISPATCH_TIME_FOREVER); - - return true; -} - -void Manifest::runConcurrently(dispatch_queue_t queue, dispatch_semaphore_t concurrencyLimitingSemaphore, std::function lambda) -{ - dispatch_group_t runGroup = dispatch_group_create(); - for (auto& config : _configurations) { - for (auto& architecture : config.second.architectures) { - dispatch_semaphore_wait(concurrencyLimitingSemaphore, DISPATCH_TIME_FOREVER); - cacheBuilderDispatchGroupAsync(runGroup, queue, [&] { - WarningTargets targets; - targets.first = this; - 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); -} - -bool Manifest::filterForConfig(const std::string& configName) -{ - for (const auto configuration : _configurations) { - if (configName == configuration.first) { - std::map filteredConfigs; - filteredConfigs[configName] = configuration.second; - - _configurations = filteredConfigs; - - for (auto& arch : configuration.second.architectures) { - arch.second.results = Manifest::Results(); - } - return true; - } - } - return false; -} - -void Manifest::calculateClosure(bool enforceRootless) -{ - auto closureSemaphore = dispatch_semaphore_create(32); - auto closureGroup = dispatch_group_create(); - auto closureQueue = dispatch_queue_create("com.apple.dyld.cache.closure", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, 0)); - rootless = enforceRootless; - - for (auto& config : _configurations) { - for (auto& arch : config.second.architectures) { - dispatch_semaphore_wait(closureSemaphore, DISPATCH_TIME_FOREVER); - cacheBuilderDispatchGroupAsync(closureGroup, closureQueue, [&] { - calculateClosure(config.first, arch.first); - dispatch_semaphore_signal(closureSemaphore); - }); - } - } - - dispatch_group_wait(closureGroup, DISPATCH_TIME_FOREVER); -} - -void Manifest::remove(const std::string& config, const std::string& arch) -{ - if (_configurations.count(config)) - _configurations[config].architectures.erase(arch); -} - -bool -Manifest::sameContentsAsCacheAtPath(const std::string& configuration, const std::string& architecture, const std::string& path) const { - 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; - - Architecture existingArch; - (void)dyld_shared_cache_iterate(mappedCache, (uint32_t)statbuf.st_size, - [&existingArch, &architecture](const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo) { - UUID uuid = *dylibInfo->uuid; - DylibInfo info; - info.uuid = uuid; - existingArch.results.dylibs[ImageIdentifier(uuid)] = info; - }); - - return (existingArch == _configurations.find(configuration)->second.architectures.find(architecture)->second); -} - -void Manifest::removeDylib(MachOProxy* proxy, const std::string& reason, const std::string& configuration, - const std::string& architecture, std::unordered_set& processedIdentifiers) -{ - 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->identifier) == 0) { - archManifest.results.dylibs[proxy->identifier].uuid = proxy->uuid; - archManifest.results.dylibs[proxy->identifier].installname = proxy->installName; - processedIdentifiers.insert(proxy->identifier); - } - archManifest.results.exclude(MachOProxy::forIdentifier(proxy->identifier, architecture), reason); - - processedIdentifiers.insert(proxy->identifier); - - for (const auto& dependent : proxy->dependentIdentifiers) { - auto dependentProxy = MachOProxy::forIdentifier(dependent, architecture); - auto dependentResultIter = archManifest.results.dylibs.find(dependentProxy->identifier); - if ( dependentProxy && - ( dependentResultIter == archManifest.results.dylibs.end() || dependentResultIter->second.included == true ) ) { - removeDylib(dependentProxy, "Missing dependency: " + proxy->installName, configuration, architecture, - processedIdentifiers); - } - } -} - -MachOProxy* Manifest::removeLargestLeafDylib( const std::string& configuration, const std::string& architecture ) { - std::set activeIdentifiers; - 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 ) { - activeIdentifiers.insert(dylibInfo.first); - } - } - for (const auto& identifier : activeIdentifiers) { - auto dylib = MachOProxy::forIdentifier(identifier, architecture); - bool dependents = false; - for (const auto& depedent : dylib->dependentIdentifiers) { - if (depedent != identifier && activeIdentifiers.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; -} - -void Manifest::calculateClosure( const std::string& configuration, const std::string& architecture ) { - auto& archManifest = _configurations[configuration].architectures[architecture]; - std::unordered_set newIdentifiers; - - for ( auto& anchor : archManifest.anchors ) { - newIdentifiers.insert(anchor.identifier); - } - - std::unordered_set processedIdentifiers; - - while (!newIdentifiers.empty()) { - std::unordered_set identifiersToProcess = newIdentifiers; - newIdentifiers.clear(); - - for (const auto& identifier : identifiersToProcess) { - if (processedIdentifiers.count(identifier) > 0) { - continue; - } - - auto proxy = MachOProxy::forIdentifier(identifier, architecture); - - if (proxy == nullptr) { - // No path - continue; - } - - // HACK: This is a policy decision we may want to revisit. - if (!proxy->isDylib()) { - continue; - } - - if (!proxy->error.empty()) { - archManifest.results.exclude(proxy, proxy->error); - processedIdentifiers.insert(proxy->identifier); - continue; - } - - if (proxy->isDylib()) { - if (_configurations[configuration].restrictedInstallnames.count(proxy->installName) != 0) { - removeDylib(proxy, "Dylib '" + proxy->installName + "' removed due to explict restriction", configuration, architecture, - processedIdentifiers); - continue; - } - - if (archManifest.results.dylibs.count(proxy->identifier) == 0) { - archManifest.results.dylibs[proxy->identifier].uuid = proxy->uuid; - archManifest.results.dylibs[proxy->identifier].installname = proxy->installName; - archManifest.results.dylibs[proxy->identifier].included = true; - - processedIdentifiers.insert(proxy->identifier); - } - } - - for (const auto& dependency : proxy->requiredIdentifiers) { - if (processedIdentifiers.count(dependency) == 0) { - newIdentifiers.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"] = @(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.second.installname )] = 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 deleted file mode 100644 index cd29ebd..0000000 --- a/interlinked-dylibs/MultiCacheBuilder.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// 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 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 deleted file mode 100644 index 4658cbc..0000000 --- a/interlinked-dylibs/MultiCacheBuilder.mm +++ /dev/null @@ -1,421 +0,0 @@ -// -// 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 "MachOProxy.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.configuration(*configurations.begin()).architecture(architecture).results.dylibs; - - if ( _skipBuilds ) { - log( "Build Skipped" ); - - for ( auto& config : configurations ) { - for ( auto& dylib : configResults ) { - _manifest.configuration(config).architecture(architecture).results.exclude(MachOProxy::forIdentifier(dylib.first, architecture), "All dylibs excluded"); - } - } - return; - } - - std::vector> dylibs; - std::vector emptyList; - std::shared_ptr cache = std::make_shared(_manifest, *configurations.begin(), architecture); - - for (auto& config : configurations) { - auto& results = _manifest.configuration(config).architecture(architecture).results; - - for (auto& dylib : configResults) { - if (dylib.second.included == false - && results.dylibs.count(dylib.first) - && results.dylibs[dylib.first].included == true) { - results.exclude(MachOProxy::forIdentifier(dylib.first, architecture), 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.configuration(config).architecture(architecture).results.developmentCache.regions.push_back({ prot, vmAddr, vmAddr + size }); - } else { - _manifest.configuration(config).architecture(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.configuration(config).architecture(architecture).results.dylibForInstallname(installName).segments.push_back({ seg.name, vmAddr, vmAddr + seg.size }); - if (_manifest.configuration(config).architecture(architecture).results.dylibForInstallname(installName).segments.size() == 0) { - warning("Attempting to write info for non-existent dylib"); - } - } - } - }); - 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.configuration(config).architecture(architecture).results.developmentCache.cdHash = cache->cdHashString(); - } else { - _manifest.configuration(config).architecture(architecture).results.productionCache.cdHash = cache->cdHashString(); - } - } -} - -void MultiCacheBuilder::buildCaches(std::string masterDstRoot) { - if (_bniMode) { - std::vector> dedupedCacheSets; - _manifest.forEachConfiguration([&dedupedCacheSets, this](const std::string& configName) { - auto config = _manifest.configuration(configName); - bool dupeFound = false; - - for (auto& cacheSet : dedupedCacheSets) { - if (config == _manifest.configuration(*cacheSet.begin())) { - cacheSet.insert(configName); - dupeFound = true; - break; - } - } - - if (!dupeFound) { - std::set temp; - temp.insert(configName); - 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.configuration(*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) { - _manifest.forEachConfiguration([this, &masterDstRoot](const std::string& configName) { - auto config = _manifest.configuration(configName); - // for ( auto& configuration : _manifest.configurations ) { - std::vector prodBomPaths; - std::vector devBomPaths; - - for (auto& arch : config.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(), configName.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(), configName.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(), configName.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 { - _manifest.runConcurrently(_buildQueue, _concurrencyLimitingSemaphore, - [&](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/interlinked-dylibs/ObjC1Abstraction.hpp b/interlinked-dylibs/ObjC1Abstraction.hpp deleted file mode 100644 index 93bf751..0000000 --- a/interlinked-dylibs/ObjC1Abstraction.hpp +++ /dev/null @@ -1,231 +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@ - */ - -#define OBJC_IMAGE_SUPPORTS_GC (1<<1) -#define OBJC_IMAGE_REQUIRES_GC (1<<2) - -template -struct objc_image_info { - uint32_t version; - uint32_t 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 = P::E::get32(flags); P::E::set32(flags, old | bits); } - void setOptimizedByDyld() INLINE { setFlag(1<<3); } -}; - -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 P::E::get32(method_name); } - void setName(uint32_t newName) INLINE { P::E::set32(method_name, newName); } -}; - -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]; - - 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 -struct objc_class { - uint32_t isa; // struct objc_class * - uint32_t super_class; // struct objc_class * - uint32_t name; // const char * - uint32_t version; // long - uint32_t info; // long - uint32_t instance_size; // long - uint32_t ivars; // struct objc_ivar_list * - uint32_t methodList; // struct objc_method_list * - uint32_t method_cache; // struct objc_cache * - uint32_t protocols; // objc_protocol_list * - uint32_t ivar_layout; // const char * - uint32_t ext; // struct objc_class_ext * - - 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 -struct objc_category { - uint32_t category_name; // char * - uint32_t class_name; // char * - uint32_t instance_methods; // struct objc_method_list * - uint32_t class_methods; // struct objc_method_list * - uint32_t protocols; // objc_protocol_list * - uint32_t size; // uint32_t - uint32_t instance_properties; // struct objc_property_list * - - 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 -struct objc_symtab { - uint32_t sel_ref_cnt; // unsigned long - uint32_t refs; // SEL * - uint16_t cls_def_cnt; // unsigned short - uint16_t cat_def_cnt; // unsigned short - uint32_t defs[0]; // void * - - 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 -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(ContentAccessor* cache) const INLINE { return (struct objc_symtab

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

list[0]; - - uint32_t getCount() const INLINE { return P::E::get32(count); } -}; - -template -struct objc_protocol { - uint32_t isa; // danger! contains strange values! - uint32_t protocol_name; // const char * - uint32_t protocol_list; // struct objc_protocol_list - uint32_t instance_methods; // struct objc_method_description_list * - uint32_t class_methods; // struct objc_method_description_list * - - 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 -class LegacySelectorUpdater { - typedef typename P::uint_t pint_t; - - static void visitMethodList(objc_method_list

*mlist, V& visitor) - { - for (uint32_t m = 0; m < mlist->getCount(); m++) { - 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) - { - 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(ContentAccessor* cache, const macho_header

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

*symtab = modules.get(m).getSymtab(cache); - if (!symtab) continue; - - // Method lists in classes - 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); - } - if ((mlist = isa->getMethodList(cache))) { - visitMethodList(mlist, visitor); - } - } - - // Method lists from categories - 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); - } - if ((mlist = cat->getClassMethods(cache))) { - visitMethodList(mlist, visitor); - } - } - } - - // Method description lists from protocols - 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; - if ((mlist = protocol.getInstanceMethodDescriptions(cache))) { - visitMethodDescriptionList(mlist, visitor); - } - if ((mlist = protocol.getClassMethodDescriptions(cache))) { - visitMethodDescriptionList(mlist, visitor); - } - } - - // 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); - selrefs.setVMAddress(s, newValue); - } - } -}; diff --git a/interlinked-dylibs/ObjC2Abstraction.hpp b/interlinked-dylibs/ObjC2Abstraction.hpp deleted file mode 100644 index 35e9aba..0000000 --- a/interlinked-dylibs/ObjC2Abstraction.hpp +++ /dev/null @@ -1,1217 +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 -#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 deleted file mode 100644 index 110b123..0000000 --- a/interlinked-dylibs/OptimizerBranches.cpp +++ /dev/null @@ -1,1473 +0,0 @@ -/* -*- 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 - -#include "MachOProxy.h" - -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 (const auto& 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 deleted file mode 100644 index ae50c53..0000000 --- a/interlinked-dylibs/OptimizerBranches.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- 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 deleted file mode 100644 index a3b8f4a..0000000 --- a/interlinked-dylibs/OptimizerLinkedit.cpp +++ /dev/null @@ -1,1185 +0,0 @@ -/* -*- 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 deleted file mode 100644 index 7b1eef3..0000000 --- a/interlinked-dylibs/OptimizerObjC.cpp +++ /dev/null @@ -1,826 +0,0 @@ -/* -*- 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 deleted file mode 100644 index 55ee8ae..0000000 --- a/interlinked-dylibs/SharedCache.cpp +++ /dev/null @@ -1,1625 +0,0 @@ -/* -*- 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 "MachOProxy.h" - -#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.configuration(configuration).architecture(architecture)) - , _buffer(nullptr) - , _fileSize(0) - , _vmSize(0) - , _aliasCount(0) - , _slideInfoFileOffset(0) - , _slideInfoBufferSize(0) -{ - auto maxCacheVMSize = sharedRegionRegionSize(_arch); - - for (auto& includedIdentifier : _archManifest.results.dylibs) { - if (includedIdentifier.second.included) { - //assert(manifest.dylibs.count(includedDylib.first) > 0); - //assert(manifest.dylibs.find(includedDylib.first)->second.proxies.count(architecture) > 0); - MachOProxy* proxy = MachOProxy::forIdentifier(includedIdentifier.first, architecture); - assert(proxy != nullptr); - assert(proxy->isDylib()); - _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 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]; - } - } - 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; - MachOProxySegment seg; - seg.name = segCmd->segname(); - seg.name = segCmd->segname(); - seg.size = segCmd->vmsize(); - seg.vmaddr = segCmd->vmaddr(); - 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; - - // FIXME: Confirm these numbers for all platform/arch combos - // assume LINKEDIT optimzation reduces LINKEDITs to %40 of original size - if (_manifest.platform() == "osx") { - _vmSize = _readOnlyRegion.address + (_readOnlyRegion.size * 9 / 10) - _textRegion.address; - } else { - _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.get()); - textImages->set_loadAddress(_segmentMap[dylib][0].address); - textImages->set_textSegmentSize((uint32_t)_segmentMap[dylib].front().cacheSegSize); - 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) -{ - bindAllImagesInCache(_dylibs, _segmentMap, _pointersForASLR); -} - -void SharedCache::writeCacheSegments(void) -{ - uint8_t* cacheBytes = (uint8_t*)_buffer.get(); - for (auto& dylib : _dylibs) { - const uint8_t* srcDylib = dylib->getBuffer(); - - for (auto& seg : _segmentMap[dylib]) { - 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[seg.base->fileOffset], 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 deleted file mode 100644 index d77a21a..0000000 --- a/interlinked-dylibs/Trie.hpp +++ /dev/null @@ -1,498 +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@ - */ - -/* - //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 deleted file mode 100644 index 0ce8429..0000000 --- a/interlinked-dylibs/dyld_shared_cache_builder.mm +++ /dev/null @@ -1,383 +0,0 @@ -/* -*- 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 "mega-dylib-utils.h" - -#include "MultiCacheBuilder.h" - -#include "MachOProxy.h" -#include "Manifest.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::string realPath(const std::string& path) { - char resolvedPath[PATH_MAX]; - if (realpath( path.c_str(), &resolvedPath[0]) != nullptr) { - return resolvedPath; - } else { - return ""; - } -} - -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::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 = realPath(argv[++i]); - processRoot(root, roots); - } else if (strcmp(arg, "-copy_roots") == 0) { - copyRoots = true; - } else if (strcmp(arg, "-dylib_cache") == 0) { - dylibCacheDir = realPath(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 = realPath(argv[++i]); - } else if (strcmp(arg, "-release") == 0) { - release = argv[++i]; - } else if (strcmp(arg, "-results") == 0) { - resultPath = realPath(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) { - manifest.forEachConfiguration([](const std::string& configName) { - printf("%s\n", configName.c_str()); - }); - exit(0); - } - - if (!manifest.filterForConfig(configuration)) { - 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.calculateClosure(false); - manifest.checkLinks(); - - // FIXME: Plumb through no_development - - std::shared_ptr builder = std::make_shared(manifest, false, false, true); - builder->buildCaches(dstRoot); - writeRootList(dstRoot, roots); - - if (copyRoots) { - manifest.forEachConfiguration([&manifest](const std::string& configName) { - for (auto& arch : manifest.configuration(configName).architectures) { - for (auto& dylib : arch.second.results.dylibs) { - if (dylib.second.included) { - MachOProxy* proxy = MachOProxy::forIdentifier(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 deleted file mode 100644 index 16fc4ab..0000000 --- a/interlinked-dylibs/mega-dylib-utils.h +++ /dev/null @@ -1,316 +0,0 @@ -/* -*- 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 - -#ifndef UUID -#include - -struct UUID { - UUID() {} - UUID(const UUID& other) { uuid_copy(&_bytes[0], &other._bytes[0]); } - UUID(const uuid_t other_uuid) { uuid_copy(&_bytes[0], other_uuid); } - bool operator<(const UUID& other) const { return uuid_compare(&_bytes[0], &other._bytes[0]) < 0; } - bool operator==(const UUID& other) const { return uuid_compare(&_bytes[0], &other._bytes[0]) == 0; } - bool operator!=(const UUID& other) const { return !(*this == other); } - - size_t hash() const { - size_t retval = 0; - for (auto i = 0; i < 16/sizeof(size_t); ++i) { - retval ^= ((size_t *)(&_bytes[0]))[i]; - } - return retval; - } - const unsigned char* get() const { return &_bytes[0]; }; -private: - std::array _bytes; -}; - -struct ImageIdentifier { - ImageIdentifier() {} - ImageIdentifier(const UUID &U) : _uuid(U) {} - size_t hash() const { return _uuid.hash(); } - bool operator<(const ImageIdentifier& other) const { return _uuid < other._uuid; } - bool operator==(const ImageIdentifier& other) const { return _uuid == other._uuid; } - bool operator!=(const ImageIdentifier& other) const { return !(*this == other); } - -private: - UUID _uuid; -}; - -namespace std { -template <> -struct hash { - size_t operator()(const UUID& x) const - { - return x.hash(); - } -}; - -template <> -struct hash { - size_t operator()(const ImageIdentifier& x) const - { - return x.hash(); - } -}; -} - -#endif - -#include "CacheFileAbstraction.hpp" - -#include "MachOFileAbstraction.hpp" - -#include "Manifest.h" - -struct MachOProxy; -struct MachOProxySegment; -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: - std::tuple 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 MachOProxySegment* seg) - : base(seg) - , address(0) - , cacheFileOffset(0) - , cacheSegSize(0) - { - } - - const MachOProxySegment* 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::vector dylibs, const std::map>& segmentMap, 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); - 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::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 deleted file mode 100644 index b21264f..0000000 --- a/interlinked-dylibs/multi_dyld_shared_cache_builder.mm +++ /dev/null @@ -1,270 +0,0 @@ -/* -*- 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 "mega-dylib-utils.h" - -#include "MultiCacheBuilder.h" -#include "Manifest.h" -#include "MachOProxy.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; - MachOProxy::runOnAllProxies(false, [&](MachOProxy* proxy) { - if (copied.count(proxy->path) > 0) { - return; - } - if (!includeExecutables && !proxy->isDylib()) - return; - (void)copyFile(proxy->buildPath, normalize_absolute_file_path(dylibCachePath + "/Root/" + proxy->path)); - copied.insert(proxy->path); - }); - - // HACK for 10.e - (void)symlink("libstdc++.6.0.9.dylib", (dylibCachePath + "/Root/usr/lib/libstdc++.6.dylib").c_str()); - (void)symlink("libstdc++.6.0.9.dylib", (dylibCachePath + "/Root/usr/lib/libstdc++.dylib").c_str()); - log("Artifact dylibs copied"); -} - -void addArtifactPaths(Manifest &manifest) { - manifest.setDylibOrderFile("./Metadata/dylibOrderFile.txt"); - manifest.setDirtyDataOrderFile("./Metadata/dirtyDataOrderFile.txt"); - manifest.setMetabomFile("./Metadata/metabom.bom"); - - for (auto& projects : manifest.projects()) { - manifest.addProjectSource(projects.first, "./Root", true); - } -} - -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.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); - } - - auto dylibCacheCtx = std::make_shared("DylibCache"); - setLoggingContext(dylibCacheCtx); - - if (!skipWrites && !skipBuilds) { - 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); - manifest.checkLinks(); - 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 deleted file mode 100644 index bd7fbea..0000000 --- a/interlinked-dylibs/update_dyld_shared_cache.mm +++ /dev/null @@ -1,375 +0,0 @@ -/* -*- 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 "mega-dylib-utils.h" -#include "MultiCacheBuilder.h" -#include "MachOProxy.h" -#include "Manifest.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 - -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); -} - -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); - } - - 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 manifest(archStrs, overlayPath, rootPath, paths); - - manifest.setPlatform(platform); - - // 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 ); - - for (const std::string& archStr : archStrs) { - std::string cachePath = cacheDir + "/dyld_shared_cache_" + archStr; - if (!force && manifest.sameContentsAsCacheAtPath("localhost", archStr, cachePath)) { - manifest.remove("localhost", archStr); - verboseLog("%s is already up to date", cachePath.c_str()); - } - } - - if (manifest.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/launch-cache/CacheFileAbstraction.hpp b/launch-cache/CacheFileAbstraction.hpp index b2dddbc..b1ac76c 100644 --- a/launch-cache/CacheFileAbstraction.hpp +++ b/launch-cache/CacheFileAbstraction.hpp @@ -97,6 +97,7 @@ private: }; + template class dyldCacheFileMapping { public: @@ -292,6 +293,7 @@ private: dyld_cache_accelerator_dof fields; }; + template class dyldCacheSlideInfo { public: diff --git a/launch-cache/MachOBinder.hpp b/launch-cache/MachOBinder.hpp deleted file mode 100644 index d87fe23..0000000 --- a/launch-cache/MachOBinder.hpp +++ /dev/null @@ -1,931 +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@ - */ - -#ifndef __MACHO_BINDER__ -#define __MACHO_BINDER__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "MachOFileAbstraction.hpp" -#include "Architectures.hpp" -#include "MachOLayout.hpp" -#include "MachORebaser.hpp" -#include "MachOTrie.hpp" - -#ifndef EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER - #define EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER 0x10 -#endif - -#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE - #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 -#endif - - -template -class Binder : public Rebaser -{ -public: - class CStringHash { - public: - size_t operator()(const char* __s) const { - size_t __h = 0; - for ( ; *__s; ++__s) - __h = 5 * __h + *__s; - return __h; - }; - }; - struct CStringEquals { - bool operator()(const char* left, const char* right) const { return (strcmp(left, right) == 0); } - }; - typedef std::unordered_map*, CStringHash, CStringEquals> Map; - - - Binder(const MachOLayoutAbstraction&); - virtual ~Binder() {} - - const char* getDylibID() const; - void setDependentBinders(const Map& map); - void bind(std::vector&); - void optimize(); - void addResolverClient(Binder* clientDylib, const char* symbolName); -private: - typedef typename A::P P; - typedef typename A::P::E E; - typedef typename A::P::uint_t pint_t; - struct BinderAndReExportFlag { Binder* binder; bool reExport; }; - struct SymbolReExport { const char* exportName; int dylibOrdinal; const char* importName; }; - typedef std::unordered_map NameToAddrMap; - typedef std::unordered_set NameSet; - typedef std::unordered_map*>, CStringHash, CStringEquals> ResolverClientsMap; - - - static bool isPublicLocation(const char* pth); - void hoistPrivateRexports(); - int ordinalOfDependentBinder(Binder* dep); - void doBindDyldInfo(std::vector& pointersInData); - void doBindDyldLazyInfo(std::vector& pointersInData); - void bindDyldInfoAt(uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, - int libraryOrdinal, int64_t addend, - const char* symbolName, bool lazyPointer, bool weakImport, - std::vector& pointersInData); - bool findExportedSymbolAddress(const char* name, pint_t* result, Binder** foundIn, - bool* isResolverSymbol, bool* isAbsolute); - const char* parentUmbrella(); - pint_t runtimeAddressFromNList(const macho_nlist

* sym); - void switchStubToUseSharedLazyPointer(const char* symbolName, pint_t lpVMAddr); - void switchStubsLazyPointer(uint8_t* stubMappedAddress, pint_t stubVMAddress, uint32_t stubSize, pint_t lpVMAddr); - pint_t findLazyPointerFor(const char* symbolName); - void shareLazyPointersToResolvers(); - - - static uint8_t pointerRelocSize(); - static uint8_t pointerRelocType(); - - std::vector fDependentDylibs; - NameToAddrMap fHashTable; - NameSet fSymbolResolvers; - NameSet fAbsoluteSymbols; - std::vector fReExportedSymbols; - const macho_nlist

* fSymbolTable; - const char* fStrings; - const macho_dysymtab_command

* fDynamicInfo; - const macho_segment_command

* fFristWritableSegment; - const macho_dylib_command

* fDylibID; - const macho_dylib_command

* fParentUmbrella; - const macho_dyld_info_command

* fDyldInfo; - bool fOriginallyPrebound; - bool fReExportedSymbolsResolved; - ResolverClientsMap fResolverInfo; -}; - -template <> -uint32_t Binder::runtimeAddressFromNList(const macho_nlist >* sym) -{ - if (sym->n_desc() & N_ARM_THUMB_DEF) - return sym->n_value() + 1; - else - return sym->n_value(); -} - -template -typename A::P::uint_t Binder::runtimeAddressFromNList(const macho_nlist

* sym) -{ - return sym->n_value(); -} - - -template -Binder::Binder(const MachOLayoutAbstraction& layout) - : Rebaser(layout), - fSymbolTable(NULL), fStrings(NULL), fDynamicInfo(NULL), - fFristWritableSegment(NULL), fDylibID(NULL), fDyldInfo(NULL), - fParentUmbrella(NULL), fReExportedSymbolsResolved(false) -{ - fOriginallyPrebound = ((this->fHeader->flags() & MH_PREBOUND) != 0); - // update header flags so the cache looks prebound split-seg (0x80000000 is in-shared-cache bit) - ((macho_header

*)this->fHeader)->set_flags(this->fHeader->flags() | MH_PREBOUND | MH_SPLIT_SEGS | 0x80000000); - - // calculate fDynamicInfo, fStrings, fSymbolTable - const macho_symtab_command

* symtab; - const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)this->fHeader + sizeof(macho_header

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

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

*)cmd; - fSymbolTable = (macho_nlist

*)(&this->fLinkEditBase[symtab->symoff()]); - fStrings = (const char*)&this->fLinkEditBase[symtab->stroff()]; - break; - case LC_DYSYMTAB: - fDynamicInfo = (macho_dysymtab_command

*)cmd; - break; - case LC_ID_DYLIB: - ((macho_dylib_command

*)cmd)->set_timestamp(0); - fDylibID = (macho_dylib_command

*)cmd; - break; - case LC_LOAD_DYLIB: - case LC_LOAD_WEAK_DYLIB: - case LC_REEXPORT_DYLIB: - case LC_LOAD_UPWARD_DYLIB: - ((macho_dylib_command

*)cmd)->set_timestamp(0); - break; - case LC_SUB_FRAMEWORK: - fParentUmbrella = (macho_dylib_command

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

*)cmd; - break; - case LC_RPATH: - fprintf(stderr, "update_dyld_shared_cache: warning: dyld shared cache does not support LC_RPATH found in %s\n", layout.getFilePath()); - break; - default: - if ( cmd->cmd() & LC_REQ_DYLD ) - throwf("unknown required load command 0x%08X", cmd->cmd()); - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } - if ( fDynamicInfo == NULL ) - throw "no LC_DYSYMTAB"; - if ( fSymbolTable == NULL ) - throw "no LC_SYMTAB"; - if ( fDyldInfo == NULL ) - throw "no LC_DYLD_INFO"; - // build hash table - //fprintf(stderr, "exports for %s\n", layout.getFilePath()); - std::vector exports; - const uint8_t* exportsStart = layout.getDyldInfoExports(); - const uint8_t* exportsEnd = &exportsStart[fDyldInfo->export_size()]; - mach_o::trie::parseTrie(exportsStart, exportsEnd, exports); - pint_t baseAddress = layout.getSegments()[0].newAddress(); - for(std::vector::iterator it = exports.begin(); it != exports.end(); ++it) { - switch ( it->flags & EXPORT_SYMBOL_FLAGS_KIND_MASK ) { - case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: - if ( (it->flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) ) { - fSymbolResolvers.insert(it->name); - } - if ( it->flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) { - //fprintf(stderr, "found re-export %s in %s\n", sym.exportName, this->getDylibID()); - SymbolReExport sym; - sym.exportName = it->name; - sym.dylibOrdinal = it->other; - sym.importName = it->importName; - if ( (sym.importName == NULL) || (sym.importName[0] == '\0') ) - sym.importName = sym.exportName; - fReExportedSymbols.push_back(sym); - // fHashTable entry will be added in first call to findExportedSymbolAddress() - } - else { - fHashTable[it->name] = it->address + baseAddress; - } - break; - case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: - fHashTable[it->name] = it->address + baseAddress; - break; - case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: - fHashTable[it->name] = it->address; - fAbsoluteSymbols.insert(it->name); - break; - default: - throwf("non-regular symbol binding not supported for %s in %s", it->name, layout.getFilePath()); - break; - } - //fprintf(stderr, "0x%08llX %s\n", it->address + baseAddress, it->name); - } -} - -template <> uint8_t Binder::pointerRelocSize() { return 2; } -template <> uint8_t Binder::pointerRelocSize() { return 3; } -template <> uint8_t Binder::pointerRelocSize() { return 2; } -template <> uint8_t Binder::pointerRelocSize() { return 3; } - -template <> uint8_t Binder::pointerRelocType() { return GENERIC_RELOC_VANILLA; } -template <> uint8_t Binder::pointerRelocType() { return X86_64_RELOC_UNSIGNED; } -template <> uint8_t Binder::pointerRelocType() { return ARM_RELOC_VANILLA; } -template <> uint8_t Binder::pointerRelocType() { return ARM64_RELOC_UNSIGNED; } - - -template -const char* Binder::getDylibID() const -{ - if ( fDylibID != NULL ) - return fDylibID->name(); - else - return NULL; -} - -template -const char* Binder::parentUmbrella() -{ - if ( fParentUmbrella != NULL ) - return fParentUmbrella->name(); - else - return NULL; -} - - -template -bool Binder::isPublicLocation(const char* pth) -{ - // /usr/lib is a public location - if ( (strncmp(pth, "/usr/lib/", 9) == 0) && (strchr(&pth[9], '/') == NULL) ) - return true; - - // /System/Library/Frameworks/ is a public location - if ( strncmp(pth, "/System/Library/Frameworks/", 27) == 0 ) { - const char* frameworkDot = strchr(&pth[27], '.'); - // but only top level framework - // /System/Library/Frameworks/Foo.framework/Versions/A/Foo ==> true - // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib ==> false - // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar ==> false - // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo ==> false - if ( frameworkDot != NULL ) { - int frameworkNameLen = frameworkDot - &pth[27]; - if ( strncmp(&pth[strlen(pth)-frameworkNameLen-1], &pth[26], frameworkNameLen+1) == 0 ) - return true; - } - } - - return false; -} - -template -void Binder::setDependentBinders(const Map& map) -{ - // build vector of dependent dylibs - const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)this->fHeader + sizeof(macho_header

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

* cmd = cmds; - 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: - const char* path = ((struct macho_dylib_command

*)cmd)->name(); - typename Map::const_iterator pos = map.find(path); - if ( pos != map.end() ) { - BinderAndReExportFlag entry; - entry.binder = pos->second; - entry.reExport = ( cmd->cmd() == LC_REEXPORT_DYLIB ); - fDependentDylibs.push_back(entry); - } - else { - // the load command string does not match the install name of any loaded dylib - // this could happen if there was not a world build and some dylib changed its - // install path to be some symlinked path - - // use realpath() and walk map looking for a realpath match - bool found = false; - char targetPath[PATH_MAX]; - if ( realpath(path, targetPath) != NULL ) { - for(typename Map::const_iterator it=map.begin(); it != map.end(); ++it) { - char aPath[PATH_MAX]; - if ( realpath(it->first, aPath) != NULL ) { - if ( strcmp(targetPath, aPath) == 0 ) { - BinderAndReExportFlag entry; - entry.binder = it->second; - entry.reExport = ( cmd->cmd() == LC_REEXPORT_DYLIB ); - fDependentDylibs.push_back(entry); - found = true; - fprintf(stderr, "update_dyld_shared_cache: warning mismatched install path in %s for %s\n", - this->getDylibID(), path); - break; - } - } - } - } - if ( ! found ) { - if ( cmd->cmd() == LC_LOAD_WEAK_DYLIB ) { - BinderAndReExportFlag entry; - entry.binder = NULL; - entry.reExport = false; - fDependentDylibs.push_back(entry); - break; - } - else { - throwf("in %s can't find dylib %s", this->getDylibID(), path); - } - } - } - break; - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } -} - -template -int Binder::ordinalOfDependentBinder(Binder* dep) -{ - for (int i=0; i < fDependentDylibs.size(); ++i) { - if ( fDependentDylibs[i].binder == dep ) - return i+1; - } - throw "dependend dylib not found"; -} - -template -void Binder::hoistPrivateRexports() -{ - std::vector*> privateReExportedDylibs; - for (typename std::vector::iterator it = fDependentDylibs.begin(); it != fDependentDylibs.end(); ++it) { - if ( it->reExport && ! isPublicLocation(it->binder->getDylibID()) ) - privateReExportedDylibs.push_back(it->binder); - } - if ( privateReExportedDylibs.size() != 0 ) { - // parse export info into vector of exports - const uint8_t* exportsStart = this->fLayout.getDyldInfoExports(); - const uint8_t* exportsEnd = &exportsStart[fDyldInfo->export_size()]; - std::vector exports; - mach_o::trie::parseTrie(exportsStart, exportsEnd, exports); - //fprintf(stderr, "%s exports %lu symbols from trie of size %u \n", this->fLayout.getFilePath(), exports.size(), fDyldInfo->export_size()); - - // add re-exports for each export from an re-exported dylib - for(typename std::vector*>::iterator it = privateReExportedDylibs.begin(); it != privateReExportedDylibs.end(); ++it) { - Binder* binder = *it; - int ordinal = ordinalOfDependentBinder(binder); - const uint8_t* aDylibsExportsStart = binder->fLayout.getDyldInfoExports(); - const uint8_t* aDylibsExportsEnd = &aDylibsExportsStart[binder->fDyldInfo->export_size()]; - std::vector aDylibsExports; - mach_o::trie::parseTrie(aDylibsExportsStart, aDylibsExportsEnd, aDylibsExports); - //fprintf(stderr, "%s re-exports %lu symbols from %s\n", this->fLayout.getFilePath(), aDylibsExports.size(), binder->getDylibID()); - for(std::vector::iterator eit = aDylibsExports.begin(); eit != aDylibsExports.end(); ++eit) { - mach_o::trie::Entry entry = *eit; - entry.flags |= EXPORT_SYMBOL_FLAGS_REEXPORT; - entry.other = ordinal; - entry.importName = NULL; - exports.push_back(entry); - } - } - // rebuild new combined trie - std::vector newExportTrieBytes; - newExportTrieBytes.reserve(fDyldInfo->export_size()); - mach_o::trie::makeTrie(exports, newExportTrieBytes); - //fprintf(stderr, "%s now exports %lu symbols from trie of size %lu\n", this->fLayout.getFilePath(), exports.size(), newExportTrieBytes.size()); - - // allocate new buffer and set export_off to use new buffer instead - uint32_t newExportsSize = newExportTrieBytes.size(); - uint8_t* sideTrie = new uint8_t[newExportsSize]; - memcpy(sideTrie, &newExportTrieBytes[0], newExportsSize); - this->fLayout.setDyldInfoExports(sideTrie); - ((macho_dyld_info_command

*)fDyldInfo)->set_export_off(0); // invalidate old trie - ((macho_dyld_info_command

*)fDyldInfo)->set_export_size(newExportsSize); - } -} - - -template -void Binder::bind(std::vector& pointersInData) -{ - this->doBindDyldInfo(pointersInData); - this->doBindDyldLazyInfo(pointersInData); - // weak bind info is processed at launch time -} - -template -void Binder::bindDyldInfoAt(uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, int libraryOrdinal, - int64_t addend, const char* symbolName, bool lazyPointer, bool weakImport, std::vector& pointersInData) -{ - //printf("%d 0x%08llX type=%d, lib=%d, addend=%lld, symbol=%s\n", segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName); - const std::vector& segments = this->fLayout.getSegments(); - if ( segmentIndex > segments.size() ) - throw "bad segment index in rebase info"; - - if ( libraryOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) - throw "dynamic lookup linkage not allowed in dyld shared cache"; - - if ( libraryOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) - throw "linkage to main executable not allowed in dyld shared cache"; - - if ( libraryOrdinal < 0 ) - throw "bad mach-o binary, special library ordinal not allowd in dyld shared cache"; - - if ( (unsigned)libraryOrdinal > fDependentDylibs.size() ) - throw "bad mach-o binary, library ordinal too big"; - - Binder* binder; - if ( libraryOrdinal == BIND_SPECIAL_DYLIB_SELF ) - binder = this; - else - binder = fDependentDylibs[libraryOrdinal-1].binder; - pint_t targetSymbolAddress; - bool isResolverSymbol = false; - bool isAbsolute = false; - Binder* foundIn; - if ( weakImport && (binder == NULL) ) { - targetSymbolAddress = 0; - foundIn = NULL; - } - else { - if ( ! binder->findExportedSymbolAddress(symbolName, &targetSymbolAddress, &foundIn, &isResolverSymbol, &isAbsolute) ) - throwf("could not bind symbol %s in %s expected in %s", symbolName, this->getDylibID(), binder->getDylibID()); - } - - // don't bind lazy pointers to resolver stubs in shared cache - if ( lazyPointer && isResolverSymbol ) { - if ( foundIn != this ) { - // record that this dylib has a lazy pointer to a resolver function - foundIn->addResolverClient(this, symbolName); - // fprintf(stderr, "have lazy pointer to resolver %s in %s\n", symbolName, this->getDylibID()); - } - return; - } - - // do actual update - const MachOLayoutAbstraction::Segment& seg = segments[segmentIndex]; - uint8_t* mappedAddr = (uint8_t*)seg.mappedAddress() + segmentOffset; - pint_t* mappedAddrP = (pint_t*)mappedAddr; - uint32_t* mappedAddr32 = (uint32_t*)mappedAddr; - int32_t svalue32new; - switch ( type ) { - case BIND_TYPE_POINTER: - P::setP(*mappedAddrP, targetSymbolAddress + addend); - break; - - case BIND_TYPE_TEXT_ABSOLUTE32: - E::set32(*mappedAddr32, targetSymbolAddress + addend); - break; - - case BIND_TYPE_TEXT_PCREL32: - svalue32new = seg.address() + segmentOffset + 4 - (targetSymbolAddress + addend); - E::set32(*mappedAddr32, svalue32new); - break; - - default: - throw "bad bind type"; - } - if ( !isAbsolute ) - pointersInData.push_back(mappedAddr); -} - - - -template -void Binder::doBindDyldLazyInfo(std::vector& pointersInData) -{ - const uint8_t* p = &this->fLinkEditBase[fDyldInfo->lazy_bind_off()]; - const uint8_t* end = &p[fDyldInfo->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 = 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: - bindDyldInfoAt(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, true, weakImport, pointersInData); - 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: - throwf("bad lazy bind opcode %d", *p); - } - } - - -} - -template -void Binder::doBindDyldInfo(std::vector& pointersInData) -{ - const uint8_t* p = &this->fLinkEditBase[fDyldInfo->bind_off()]; - const uint8_t* end = &p[fDyldInfo->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; - uint32_t count; - uint32_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 = 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: - bindDyldInfoAt(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, false, weakImport, pointersInData); - segmentOffset += sizeof(pint_t); - break; - case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - bindDyldInfoAt(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, false, weakImport, pointersInData); - segmentOffset += read_uleb128(p, end) + sizeof(pint_t); - break; - case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - bindDyldInfoAt(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, false, weakImport, pointersInData); - 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) { - bindDyldInfoAt(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, false, weakImport, pointersInData); - segmentOffset += skip + sizeof(pint_t); - } - break; - default: - throwf("bad bind opcode %d", *p); - } - } -} - -template -bool Binder::findExportedSymbolAddress(const char* name, pint_t* result, Binder** foundIn, bool* isResolverSymbol, bool* isAbsolute) -{ - *foundIn = NULL; - // since re-export chains can be any length, re-exports cannot be resolved in setDependencies() - // instead we lazily, recursively update - if ( !fReExportedSymbolsResolved ) { - - // update fHashTable with any individual symbol re-exports - for (typename std::vector::iterator it=fReExportedSymbols.begin(); it != fReExportedSymbols.end(); ++it) { - pint_t targetSymbolAddress; - bool isResolver; - bool isAb; - - if ( it->dylibOrdinal <= 0 ) - throw "bad mach-o binary, special library ordinal not allowed in re-exported symbols in dyld shared cache"; - - Binder* binder = fDependentDylibs[it->dylibOrdinal-1].binder; - - if ( ! binder->findExportedSymbolAddress(it->importName, &targetSymbolAddress, foundIn, &isResolver, &isAb) ) - throwf("could not bind symbol %s in %s expected in %s", it->importName, this->getDylibID(), binder->getDylibID()); - - if ( isResolver ) - fSymbolResolvers.insert(name); - - fHashTable[it->exportName] = targetSymbolAddress; - } - // mark as done - fReExportedSymbolsResolved = true; - } - - *isResolverSymbol = false; - if ( !fSymbolResolvers.empty() && fSymbolResolvers.count(name) ) { - // lazy pointers should be left unbound, rather than bind to resolver stub - *isResolverSymbol = true; - } - - // search this dylib - typename NameToAddrMap::iterator pos = fHashTable.find(name); - if ( pos != fHashTable.end() ) { - *result = pos->second; - //fprintf(stderr, "findExportedSymbolAddress(%s) => 0x%08llX in %s\n", name, (uint64_t)*result, this->getDylibID()); - *foundIn = this; - *isAbsolute = (fAbsoluteSymbols.count(name) != 0); - return true; - } - - // search re-exported dylibs - for (typename std::vector::iterator it = fDependentDylibs.begin(); it != fDependentDylibs.end(); ++it) { - if ( it->reExport ) { - if ( it->binder->findExportedSymbolAddress(name, result, foundIn, isResolverSymbol, isAbsolute) ) - return true; - } - } - //fprintf(stderr, "findExportedSymbolAddress(%s) => not found in %s\n", name, this->getDylibID()); - return false; -} - -// record which dylibs will be using this dylibs lazy pointer -template -void Binder::addResolverClient(Binder* clientDylib, const char* symbolName) -{ - fResolverInfo[symbolName].insert(clientDylib); -} - - -template -typename A::P::uint_t Binder::findLazyPointerFor(const char* symbolName) -{ - static const bool log = false; - - // lookup in lazy pointer section - const uint32_t* const indirectTable = (uint32_t*)&this->fLinkEditBase[fDynamicInfo->indirectsymoff()]; - const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)this->fHeader + sizeof(macho_header

)); - const uint32_t cmd_count = this->fHeader->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

* seg = (macho_segment_command

*)cmd; - 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 = sect->size() / sizeof(pint_t); - const uint32_t indirectTableOffset = sect->reserved1(); - pint_t vmlocation = 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 = &fSymbolTable[symbolIndex]; - const char* aName = &fStrings[aSymbol->n_strx()]; - //fprintf(stderr, " sect=%s, index=%d, symbolIndex=%d, sym=%s\n", sect->sectname(), j, symbolIndex, &fStrings[undefinedSymbol->n_strx()]); - if ( strcmp(aName, symbolName) == 0 ) { - if ( log ) fprintf(stderr, "found shared lazy pointer at 0x%llX for %s in %s\n", (uint64_t)vmlocation, symbolName, this->getDylibID()); - return vmlocation; - } - break; - } - } - } - } - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } - - if ( log ) fprintf(stderr, "not found shared lazy pointer for %s in %s, checking for re-export symbol\n", symbolName, this->getDylibID()); - for (typename std::vector::iterator it=fReExportedSymbols.begin(); it != fReExportedSymbols.end(); ++it) { - if ( strcmp(it->exportName, symbolName) != 0 ) - continue; - - if ( it->dylibOrdinal <= 0 ) - throw "bad mach-o binary, special library ordinal not allowed in re-exported symbols in dyld shared cache"; - - Binder* binder = fDependentDylibs[it->dylibOrdinal-1].binder; - return binder->findLazyPointerFor(it->importName); - } - - if ( log ) fprintf(stderr, "not found shared lazy pointer for %s in %s, checking re-export dylibs\n", symbolName, this->getDylibID()); - for (typename std::vector::iterator it = fDependentDylibs.begin(); it != fDependentDylibs.end(); ++it) { - if ( it->reExport ) { - pint_t result = it->binder->findLazyPointerFor(symbolName); - if ( result != 0 ) - return result; - } - } - - if ( log ) fprintf(stderr, "NOT found shared lazy pointer for %s in %s\n", symbolName, this->getDylibID()); - return 0; -} - -// called after all binding is done to optimize lazy pointers -template -void Binder::optimize() -{ - hoistPrivateRexports(); - shareLazyPointersToResolvers(); -} - -template -void Binder::shareLazyPointersToResolvers() -{ - for (const auto &entry : fResolverInfo) { - const char* resolverSymbolName = entry.first; - if ( pint_t lpVMAddr = findLazyPointerFor(resolverSymbolName) ) { - for (Binder* clientDylib : entry.second) { - clientDylib->switchStubToUseSharedLazyPointer(resolverSymbolName, lpVMAddr); - } - } - else { - fprintf(stderr, "not able to optimize lazy pointer for %s in %s\n", resolverSymbolName, this->getDylibID()); - } - } -} - - - -template <> -void Binder::switchStubsLazyPointer(uint8_t* stubMappedAddress, pint_t stubVMAddress, uint32_t stubSize, pint_t lpVMAddr) -{ - if ( stubSize != 16 ) { - fprintf(stderr, "could not optimize ARM stub to resolver function in %s because it is wrong size\n", this->getDylibID()); - return; - } - uint32_t* instructions = (uint32_t*)stubMappedAddress; - if ( (E::get32(instructions[0]) != 0xe59fc004) - || (E::get32(instructions[1]) != 0xe08fc00c) - || (E::get32(instructions[2]) != 0xe59cf000) - ) { - fprintf(stderr, "could not optimize ARM stub to resolver function in %s because instructions are not as expected\n", this->getDylibID()); - return; - } - // last .long in stub is: lazyPtr - (stub+8) - // alter to point to more optimal lazy pointer - uint32_t betterOffset = lpVMAddr - (stubVMAddress + 12); - E::set32(instructions[3], betterOffset); -} - - -template <> -void Binder::switchStubsLazyPointer(uint8_t* stubMappedAddress, pint_t stubVMAddress, uint32_t stubSize, pint_t lpVMAddr) -{ - if ( stubSize != 6 ) { - fprintf(stderr, "could not optimize x86_64 stub to resolver function in %s because it is wrong size\n", this->getDylibID()); - return; - } - if ( (stubMappedAddress[0] != 0xFF) || (stubMappedAddress[1] != 0x25) ) { - fprintf(stderr, "could not optimize stub to resolver function in %s because instructions are not as expected\n", this->getDylibID()); - return; - } - // last four bytes in stub is RIP relative offset to lazy pointer - // alter to point to more optimal lazy pointer - uint32_t betterOffset = lpVMAddr - (stubVMAddress + 6); - E::set32(*((uint32_t*)(&stubMappedAddress[2])), betterOffset); -} - -template -void Binder::switchStubsLazyPointer(uint8_t* stubMappedAddress, pint_t stubVMAddress, uint32_t stubSize, pint_t lpVMAddress) -{ - // Remaining architectures are not optimized - //fprintf(stderr, "optimize stub at %p in %s to use lazyPointer at 0x%llX\n", stubMappedAddress, this->getDylibID(), (uint64_t)lpVMAddress); -} - -// search for stub in this image that call target symbol name and then optimize its lazy pointer -template -void Binder::switchStubToUseSharedLazyPointer(const char* stubName, pint_t lpVMAddr) -{ - // find named stub - const uint32_t* const indirectTable = (uint32_t*)&this->fLinkEditBase[fDynamicInfo->indirectsymoff()]; - const macho_load_command

* const cmds = (macho_load_command

*)((uint8_t*)this->fHeader + sizeof(macho_header

)); - const uint32_t cmd_count = this->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; - 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 = sect->addr(); - uint8_t* stubsMappingStart = (uint8_t*)this->mappedAddressForNewAddress(stubsVMStart); - const uint32_t indirectTableOffset = sect->reserved1(); - const uint32_t stubSize = sect->reserved2(); - uint32_t elementCount = 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

* sym = &this->fSymbolTable[symbolIndex]; - const char* symName = &fStrings[sym->n_strx()]; - if ( strcmp(symName, stubName) == 0 ) - this->switchStubsLazyPointer(stubMappedAddr, stubVMAddr, stubSize, lpVMAddr); - } - break; - } - } - } - } - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } -} - - -#endif // __MACHO_BINDER__ - - - - diff --git a/launch-cache/MachOFileAbstraction.hpp b/launch-cache/MachOFileAbstraction.hpp index f92a95b..ebd298d 100644 --- a/launch-cache/MachOFileAbstraction.hpp +++ b/launch-cache/MachOFileAbstraction.hpp @@ -116,6 +116,10 @@ struct uuid_command { #define MH_HAS_OBJC 0x40000000 +#ifndef CPU_SUBTYPE_ARM64_E + #define CPU_SUBTYPE_ARM64_E 2 +#endif + #include "FileAbstraction.hpp" #include "Architectures.hpp" @@ -947,7 +951,7 @@ inline int64_t read_sleb128(const uint8_t*& p, const uint8_t* end) if (p == end) throw "malformed sleb128"; byte = *p++; - result |= ((byte & 0x7f) << bit); + result |= (((int64_t)(byte & 0x7f)) << bit); bit += 7; } while (byte & 0x80); // sign extend negative numbers diff --git a/launch-cache/MachOLayout.hpp b/launch-cache/MachOLayout.hpp deleted file mode 100644 index 49f593e..0000000 --- a/launch-cache/MachOLayout.hpp +++ /dev/null @@ -1,772 +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@ - */ - -#ifndef __MACHO_LAYOUT__ -#define __MACHO_LAYOUT__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "MachOFileAbstraction.hpp" -#include "Architectures.hpp" - - -void throwf(const char* format, ...) __attribute__((format(printf, 1, 2))); - -__attribute__((noreturn)) -void throwf(const char* format, ...) -{ - va_list list; - char* p; - va_start(list, format); - vasprintf(&p, format, list); - va_end(list); - - const char* t = p; - throw t; -} - - -class MachOLayoutAbstraction -{ -public: - struct Segment - { - public: - Segment(uint64_t addr, uint64_t vmsize, uint64_t offset, uint64_t file_size, uint64_t sectionsSize, - uint64_t sectionsAlignment, uint64_t align, - uint32_t prot, uint32_t sectionCount, const char* segName) : fOrigAddress(addr), fOrigSize(vmsize), - fOrigFileOffset(offset), fOrigFileSize(file_size), fOrigPermissions(prot), - fSize(vmsize), fFileOffset(offset), fFileSize(file_size), fAlignment(align), - fPermissions(prot), fSectionCount(sectionCount), fSectionsSize(sectionsSize), - fSectionsAlignment(sectionsAlignment), fNewAddress(0), fMappedAddress(NULL) { - strlcpy(fOrigName, segName, 16); - } - - uint64_t address() const { return fOrigAddress; } - uint64_t size() const { return fSize; } - uint64_t fileOffset() const { return fFileOffset; } - uint64_t fileSize() const { return fFileSize; } - uint32_t permissions() const { return fPermissions; } - bool readable() const { return fPermissions & VM_PROT_READ; } - bool writable() const { return fPermissions & VM_PROT_WRITE; } - bool executable() const { return fPermissions & VM_PROT_EXECUTE; } - uint64_t alignment() const { return fAlignment; } - const char* name() const { return fOrigName; } - uint64_t newAddress() const { return fNewAddress; } - uint32_t sectionCount() const{ return fSectionCount; } - uint64_t sectionsSize() const{ return fSectionsSize; } - uint64_t sectionsAlignment() const { return fSectionsAlignment; } - void* mappedAddress() const { return fMappedAddress; } - void setNewAddress(uint64_t addr) { fNewAddress = addr; } - void setMappedAddress(void* addr) { fMappedAddress = addr; } - void setSize(uint64_t new_size) { fSize = new_size; } - void setFileOffset(uint64_t new_off) { fFileOffset = new_off; } - void setFileSize(uint64_t new_size) { fFileSize = new_size; } - void setWritable(bool w) { if (w) fPermissions |= VM_PROT_WRITE; else fPermissions &= ~VM_PROT_WRITE; } - void setSectionsAlignment(uint64_t v){ fSectionsAlignment = v; } - void reset() { fSize=fOrigSize; fFileOffset=fOrigFileOffset; fFileSize=fOrigFileSize; fPermissions=fOrigPermissions; } - private: - uint64_t fOrigAddress; - uint64_t fOrigSize; - uint64_t fOrigFileOffset; - uint64_t fOrigFileSize; - uint32_t fOrigPermissions; - char fOrigName[16]; - uint64_t fSize; - uint64_t fFileOffset; - uint64_t fFileSize; - uint64_t fAlignment; - uint32_t fPermissions; - uint32_t fSectionCount; - uint64_t fSectionsSize; - uint64_t fSectionsAlignment; - uint64_t fNewAddress; - void* fMappedAddress; - }; - - struct Library - { - const char* name; - uint32_t currentVersion; - uint32_t compatibilityVersion; - bool weakImport; - }; - - - virtual ArchPair getArchPair() const = 0; - virtual const char* getFilePath() const = 0; - virtual uint64_t getOffsetInUniversalFile() const = 0; - virtual uint32_t getFileType() const = 0; - virtual uint32_t getFlags() const = 0; - virtual Library getID() const = 0; - virtual bool isDylib() const = 0; - virtual bool isSplitSeg() const = 0; - virtual bool hasSplitSegInfo() const = 0; - virtual bool hasSplitSegInfoV2() const = 0; - virtual bool inSharableLocation() const = 0; - virtual bool hasDynamicLookupLinkage() const = 0; - virtual bool hasMainExecutableLookupLinkage() const = 0; - virtual bool isTwoLevelNamespace() const = 0; - virtual bool hasDyldInfo() const = 0; - virtual bool hasMultipleReadWriteSegments() const = 0; - virtual uint32_t getNameFileOffset() const = 0; - virtual time_t getLastModTime() const = 0; - virtual ino_t getInode() const = 0; - virtual std::vector& getSegments() = 0; - virtual const std::vector& getSegments() const = 0; - virtual const Segment* getSegment(const char* name) const = 0; - virtual const std::vector& getLibraries() const = 0; - virtual uint64_t getBaseAddress() const = 0; - virtual uint64_t getVMSize() const = 0; - virtual uint64_t getBaseExecutableAddress() const = 0; - virtual uint64_t getBaseWritableAddress() const = 0; - virtual uint64_t getBaseReadOnlyAddress() const = 0; - virtual uint64_t getExecutableVMSize() const = 0; - virtual uint64_t getWritableVMSize() const = 0; - virtual uint64_t getReadOnlyVMSize() const = 0; - // need getDyldInfoExports because export info uses ULEB encoding and size could grow - virtual const uint8_t* getDyldInfoExports() const = 0; - virtual void setDyldInfoExports(const uint8_t* newExports) const = 0; - virtual void uuid(uuid_t u) const = 0; -}; - - - - -template -class MachOLayout : public MachOLayoutAbstraction -{ -public: - MachOLayout(const void* machHeader, uint64_t offset, const char* path, - ino_t inode, time_t modTime, uid_t uid); - virtual ~MachOLayout() {} - - virtual ArchPair getArchPair() const { return fArchPair; } - virtual const char* getFilePath() const { return fPath; } - virtual uint64_t getOffsetInUniversalFile() const { return fOffset; } - virtual uint32_t getFileType() const { return fFileType; } - virtual uint32_t getFlags() const { return fFlags; } - virtual Library getID() const { return fDylibID; } - virtual bool isDylib() const { return fIsDylib; } - virtual bool isSplitSeg() const; - virtual bool hasSplitSegInfo() const { return fSplitSegInfo != NULL; } - virtual bool hasSplitSegInfoV2() const{ return fHasSplitSegInfoV2; } - virtual bool inSharableLocation() const { return fShareableLocation; } - virtual bool hasDynamicLookupLinkage() const { return fDynamicLookupLinkage; } - virtual bool hasMainExecutableLookupLinkage() const { return fMainExecutableLookupLinkage; } - virtual bool isTwoLevelNamespace() const { return (fFlags & MH_TWOLEVEL); } - virtual bool hasDyldInfo() const { return fHasDyldInfo; } - virtual bool hasMultipleReadWriteSegments() const { return fHasTooManyWritableSegments; } - virtual uint32_t getNameFileOffset() const{ return fNameFileOffset; } - virtual time_t getLastModTime() const { return fMTime; } - virtual ino_t getInode() const { return fInode; } - virtual std::vector& getSegments() { return fSegments; } - virtual const std::vector& getSegments() const { return fSegments; } - virtual const Segment* getSegment(const char* name) const; - virtual const std::vector& getLibraries() const { return fLibraries; } - virtual uint64_t getBaseAddress() const { return fLowSegment->address(); } - virtual uint64_t getVMSize() const { return fVMSize; } - virtual uint64_t getBaseExecutableAddress() const { return fLowExecutableSegment->address(); } - virtual uint64_t getBaseWritableAddress() const { return fLowWritableSegment->address(); } - virtual uint64_t getBaseReadOnlyAddress() const { return fLowReadOnlySegment->address(); } - virtual uint64_t getExecutableVMSize() const { return fVMExecutableSize; } - virtual uint64_t getWritableVMSize() const { return fVMWritablSize; } - virtual uint64_t getReadOnlyVMSize() const { return fVMReadOnlySize; } - virtual const uint8_t* getDyldInfoExports() const { return fDyldInfoExports; } - virtual void setDyldInfoExports(const uint8_t* newExports) const { fDyldInfoExports = newExports; } - virtual void uuid(uuid_t u) const { memcpy(u, fUUID, 16); } - -private: - typedef typename A::P P; - typedef typename A::P::E E; - typedef typename A::P::uint_t pint_t; - - uint64_t segmentSize(const macho_segment_command* segCmd) const; - uint64_t segmentFileSize(const macho_segment_command* segCmd) const; - uint64_t segmentAlignment(const macho_segment_command* segCmd) const; - uint64_t sectionsSize(const macho_segment_command* segCmd) const; - uint64_t sectionsAlignment(const macho_segment_command* segCmd) const; - - bool validReadWriteSeg(const Segment& seg) const; - - static cpu_type_t arch(); - - const char* fPath; - uint64_t fOffset; - uint32_t fFileType; - ArchPair fArchPair; - uint32_t fFlags; - std::vector fSegments; - std::vector fLibraries; - const Segment* fLowSegment; - const Segment* fLowExecutableSegment; - const Segment* fLowWritableSegment; - const Segment* fLowReadOnlySegment; - Library fDylibID; - uint32_t fNameFileOffset; - time_t fMTime; - ino_t fInode; - uint64_t fVMSize; - uint64_t fVMExecutableSize; - uint64_t fVMWritablSize; - uint64_t fVMReadOnlySize; - const macho_linkedit_data_command

* fSplitSegInfo; - bool fHasSplitSegInfoV2; - bool fShareableLocation; - bool fDynamicLookupLinkage; - bool fMainExecutableLookupLinkage; - bool fIsDylib; - bool fHasDyldInfo; - bool fHasTooManyWritableSegments; - mutable const uint8_t* fDyldInfoExports; - uuid_t fUUID; -}; - - - -class UniversalMachOLayout -{ -public: - UniversalMachOLayout(const char* path, const std::set* onlyArchs=NULL); - ~UniversalMachOLayout() {} - - static const UniversalMachOLayout& find(const char* path, const std::set* onlyArchs=NULL); - const MachOLayoutAbstraction* getSlice(ArchPair ap) const; - const std::vector& allLayouts() const { return fLayouts; } - -private: - class CStringHash { - public: - size_t operator()(const char* __s) const { - size_t __h = 0; - for ( ; *__s; ++__s) - __h = 5 * __h + *__s; - return __h; - }; - }; - struct CStringEquals { - bool operator()(const char* left, const char* right) const { return (strcmp(left, right) == 0); } - }; - typedef std::unordered_map PathToNode; - - static bool requestedSlice(const std::set* onlyArchs, cpu_type_t cpuType, cpu_subtype_t cpuSubType); - - static PathToNode fgLayoutCache; - const char* fPath; - std::vector fLayouts; -}; - -UniversalMachOLayout::PathToNode UniversalMachOLayout::fgLayoutCache; - - - - -const MachOLayoutAbstraction* UniversalMachOLayout::getSlice(ArchPair ap) const -{ - // use matching cputype and cpusubtype - for(std::vector::const_iterator it=fLayouts.begin(); it != fLayouts.end(); ++it) { - const MachOLayoutAbstraction* layout = *it; - if ( layout->getArchPair().arch == ap.arch ) { - switch ( ap.arch ) { - case CPU_TYPE_ARM: - case CPU_TYPE_X86_64: - if ( (layout->getArchPair().subtype & ~CPU_SUBTYPE_MASK) == (ap.subtype & ~CPU_SUBTYPE_MASK) ) - return layout; - break; - default: - return layout; - } - } - } - // if requesting x86_64h and it did not exist, try x86_64 as a fallback - if ((ap.arch == CPU_TYPE_X86_64) && (ap.subtype == CPU_SUBTYPE_X86_64_H)) { - ap.subtype = CPU_SUBTYPE_X86_64_ALL; - return this->getSlice(ap); - } - return NULL; -} - - -const UniversalMachOLayout& UniversalMachOLayout::find(const char* path, const std::set* onlyArchs) -{ - // look in cache - PathToNode::iterator pos = fgLayoutCache.find(path); - if ( pos != fgLayoutCache.end() ) - return *pos->second; - - // create UniversalMachOLayout - const UniversalMachOLayout* result = new UniversalMachOLayout(path, onlyArchs); - - // add it to cache - fgLayoutCache[result->fPath] = result; - - return *result; -} - - -bool UniversalMachOLayout::requestedSlice(const std::set* onlyArchs, cpu_type_t cpuType, cpu_subtype_t cpuSubType) -{ - if ( onlyArchs == NULL ) - return true; - // must match cputype and cpusubtype - for (std::set::const_iterator it = onlyArchs->begin(); it != onlyArchs->end(); ++it) { - ArchPair anArch = *it; - if ( cpuType == anArch.arch ) { - switch ( cpuType ) { - case CPU_TYPE_ARM: - if ( cpuSubType == anArch.subtype ) - return true; - break; - default: - return true; - } - } - } - return false; -} - - -UniversalMachOLayout::UniversalMachOLayout(const char* path, const std::set* onlyArchs) - : fPath(strdup(path)) -{ - // map in whole file - int fd = ::open(path, O_RDONLY, 0); - if ( fd == -1 ) { - int err = errno; - if ( err == ENOENT ) - throwf("file not found"); - else - throwf("can't open file, errno=%d", err); - } - struct stat stat_buf; - if ( fstat(fd, &stat_buf) == -1) - throwf("can't stat open file %s, errno=%d", path, errno); - if ( stat_buf.st_size < 20 ) - throwf("file too small %s", path); - uint8_t* p = (uint8_t*)::mmap(NULL, stat_buf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0); - if ( p == (uint8_t*)(-1) ) - throwf("can't map file %s, errno=%d", path, errno); - ::close(fd); - - try { - // if fat file, process each architecture - const fat_header* fh = (fat_header*)p; - const mach_header* mh = (mach_header*)p; - if ( fh->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { - // Fat header is always big-endian - const struct fat_arch* slices = (struct fat_arch*)(p + sizeof(struct fat_header)); - const uint32_t sliceCount = OSSwapBigToHostInt32(fh->nfat_arch); - for (uint32_t i=0; i < sliceCount; ++i) { - if ( requestedSlice(onlyArchs, OSSwapBigToHostInt32(slices[i].cputype), OSSwapBigToHostInt32(slices[i].cpusubtype)) ) { - uint32_t fileOffset = OSSwapBigToHostInt32(slices[i].offset); - if ( fileOffset > stat_buf.st_size ) { - throwf("malformed universal file, slice %u for architecture 0x%08X is beyond end of file: %s", - i, OSSwapBigToHostInt32(slices[i].cputype), path); - } - if ( (fileOffset+OSSwapBigToHostInt32(slices[i].size)) > stat_buf.st_size ) { - throwf("malformed universal file, slice %u for architecture 0x%08X is beyond end of file: %s", - i, OSSwapBigToHostInt32(slices[i].cputype), path); - } - try { - switch ( OSSwapBigToHostInt32(slices[i].cputype) ) { - case CPU_TYPE_I386: - fLayouts.push_back(new MachOLayout(&p[fileOffset], fileOffset, fPath, stat_buf.st_ino, stat_buf.st_mtime, stat_buf.st_uid)); - break; - case CPU_TYPE_X86_64: - fLayouts.push_back(new MachOLayout(&p[fileOffset], fileOffset, fPath, stat_buf.st_ino, stat_buf.st_mtime, stat_buf.st_uid)); - break; - case CPU_TYPE_ARM: - fLayouts.push_back(new MachOLayout(&p[fileOffset], fileOffset, fPath, stat_buf.st_ino, stat_buf.st_mtime, stat_buf.st_uid)); - break; - case CPU_TYPE_ARM64: - fLayouts.push_back(new MachOLayout(&p[fileOffset], fileOffset, fPath, stat_buf.st_ino, stat_buf.st_mtime, stat_buf.st_uid)); - break; - default: - throw "unknown slice in fat file"; - } - } - catch (const char* msg) { - fprintf(stderr, "warning: %s for %s\n", msg, path); - } - } - } - } - else { - try { - if ( (OSSwapLittleToHostInt32(mh->magic) == MH_MAGIC) && (OSSwapLittleToHostInt32(mh->cputype) == CPU_TYPE_I386)) { - if ( requestedSlice(onlyArchs, OSSwapLittleToHostInt32(mh->cputype), OSSwapLittleToHostInt32(mh->cpusubtype)) ) - fLayouts.push_back(new MachOLayout(mh, 0, fPath, stat_buf.st_ino, stat_buf.st_mtime, stat_buf.st_uid)); - } - else if ( (OSSwapLittleToHostInt32(mh->magic) == MH_MAGIC_64) && (OSSwapLittleToHostInt32(mh->cputype) == CPU_TYPE_X86_64)) { - if ( requestedSlice(onlyArchs, OSSwapLittleToHostInt32(mh->cputype), OSSwapLittleToHostInt32(mh->cpusubtype)) ) - fLayouts.push_back(new MachOLayout(mh, 0, fPath, stat_buf.st_ino, stat_buf.st_mtime, stat_buf.st_uid)); - } - else if ( (OSSwapLittleToHostInt32(mh->magic) == MH_MAGIC) && (OSSwapLittleToHostInt32(mh->cputype) == CPU_TYPE_ARM)) { - if ( requestedSlice(onlyArchs, OSSwapLittleToHostInt32(mh->cputype), OSSwapLittleToHostInt32(mh->cpusubtype)) ) - fLayouts.push_back(new MachOLayout(mh, 0, fPath, stat_buf.st_ino, stat_buf.st_mtime, stat_buf.st_uid)); - } - else if ( (OSSwapLittleToHostInt32(mh->magic) == MH_MAGIC_64) && (OSSwapLittleToHostInt32(mh->cputype) == CPU_TYPE_ARM64)) { - if ( requestedSlice(onlyArchs, OSSwapLittleToHostInt32(mh->cputype), OSSwapLittleToHostInt32(mh->cpusubtype)) ) - fLayouts.push_back(new MachOLayout(mh, 0, fPath, stat_buf.st_ino, stat_buf.st_mtime, stat_buf.st_uid)); - } - else { - throw "unknown file format"; - } - } - catch (const char* msg) { - fprintf(stderr, "warning: %s for %s\n", msg, path); - } - } - } - catch (...) { - ::munmap(p, stat_buf.st_size); - throw; - } -} - - -template -uint64_t MachOLayout::segmentSize(const macho_segment_command* segCmd) const -{ - // segments may have 16KB alignment padding at end, if so we can remove that in cache - if ( segCmd->nsects() > 0 ) { - const macho_section

* const sectionsStart = (macho_section

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

)); - const macho_section

* const lastSection = §ionsStart[segCmd->nsects()-1]; - uint64_t endSectAddr = lastSection->addr() + lastSection->size(); - uint64_t endSectAddrPage = (endSectAddr + 4095) & (-4096); - if ( endSectAddrPage < (segCmd->vmaddr() + segCmd->vmsize()) ) { - uint64_t size = endSectAddrPage - segCmd->vmaddr(); - //if ( size != segCmd->vmsize() ) - // fprintf(stderr, "trim %s size=0x%08llX instead of 0x%08llX for %s\n", - // segCmd->segname(), size, segCmd->vmsize(), getFilePath()); - return size; - } - } - return segCmd->vmsize(); -} - -template -uint64_t MachOLayout::segmentAlignment(const macho_segment_command* segCmd) const -{ - int p2align = 12; - if ( segCmd->nsects() > 0 ) { - const macho_section

* const sectionsStart = (macho_section

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

)); - const macho_section

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

* sect=sectionsStart; sect < sectionsEnd; ++sect) { - if ( sect->align() > p2align ) - p2align = sect->align(); - } - } - return (1 << p2align); -} - -template -uint64_t MachOLayout::segmentFileSize(const macho_segment_command* segCmd) const -{ - // segments may have 16KB alignment padding at end, if so we can remove that in cache - if ( segCmd->nsects() > 0 ) { - uint64_t endOffset = segCmd->fileoff(); - 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->offset() != 0 ) - endOffset = sect->offset() + sect->size(); - } - uint64_t size = (endOffset - segCmd->fileoff() + 4095) & (-4096); - //if ( size != segCmd->filesize() ) - // fprintf(stderr, "trim %s filesize=0x%08llX instead of 0x%08llX for %s\n", - // segCmd->segname(), size, segCmd->filesize(), getFilePath()); - return size; - } - return segCmd->filesize(); -} - -template -uint64_t MachOLayout::sectionsSize(const macho_segment_command* segCmd) const -{ - if ( segCmd->nsects() > 0 ) { - const macho_section

* const sectionsStart = (macho_section

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

)); - const macho_section

* const lastSection = §ionsStart[segCmd->nsects()-1]; - uint64_t endSectAddr = lastSection->addr() + lastSection->size(); - if ( endSectAddr < (segCmd->vmaddr() + segCmd->vmsize()) ) { - uint64_t size = endSectAddr - segCmd->vmaddr(); - return size; - } - } - return segCmd->vmsize(); -} - -template -uint64_t MachOLayout::sectionsAlignment(const macho_segment_command* segCmd) const -{ - int p2align = 4; - if ( hasSplitSegInfoV2() && (segCmd->nsects() > 0) ) { - const macho_section

* const sectionsStart = (macho_section

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

)); - const macho_section

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

* sect=sectionsStart; sect < sectionsEnd; ++sect) { - if ( sect->align() > p2align ) - p2align = sect->align(); - } - } - return (1 << p2align); -} - - - -template -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), - fShareableLocation(false), fDynamicLookupLinkage(false), fMainExecutableLookupLinkage(false), fIsDylib(false), - fHasDyldInfo(false), fHasTooManyWritableSegments(false), fDyldInfoExports(NULL) -{ - fDylibID.name = NULL; - fDylibID.currentVersion = 0; - fDylibID.compatibilityVersion = 0; - bzero(fUUID, sizeof(fUUID)); - - const macho_header

* mh = (const macho_header

*)machHeader; - if ( mh->cputype() != arch() ) - throw "Layout object is wrong architecture"; - switch ( mh->filetype() ) { - case MH_DYLIB: - fIsDylib = true; - break; - case MH_BUNDLE: - case MH_EXECUTE: - case MH_DYLIB_STUB: - case MH_DYLINKER: - break; - default: - throw "file is not a mach-o final linked image"; - } - fFlags = mh->flags(); - fFileType = mh->filetype(); - fArchPair.arch = mh->cputype(); - fArchPair.subtype = mh->cpusubtype(); - - const macho_dyld_info_command

* dyldInfo = NULL; - const macho_symtab_command

* symbolTableCmd = NULL; - const macho_dysymtab_command

* dynamicSymbolTableCmd = NULL; - 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) { - switch ( cmd->cmd() ) { - case LC_ID_DYLIB: - { - macho_dylib_command

* dylib = (macho_dylib_command

*)cmd; - fDylibID.name = strdup(dylib->name()); - fDylibID.currentVersion = dylib->current_version(); - fDylibID.compatibilityVersion = dylib->compatibility_version(); - fNameFileOffset = dylib->name() - (char*)machHeader; - fShareableLocation = ( (strncmp(fDylibID.name, "/usr/lib/", 9) == 0) || (strncmp(fDylibID.name, "/System/Library/", 16) == 0) ); - } - 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; - Library lib; - lib.name = strdup(dylib->name()); - lib.currentVersion = dylib->current_version(); - lib.compatibilityVersion = dylib->compatibility_version(); - lib.weakImport = ( cmd->cmd() == LC_LOAD_WEAK_DYLIB ); - fLibraries.push_back(lib); - } - break; - case LC_SEGMENT_SPLIT_INFO: - fSplitSegInfo = (macho_linkedit_data_command

*)cmd; - break; - case macho_segment_command

::CMD: - { - const macho_segment_command

* segCmd = (macho_segment_command

*)cmd; - fSegments.push_back(Segment(segCmd->vmaddr(), segmentSize(segCmd), segCmd->fileoff(), - segmentFileSize(segCmd), sectionsSize(segCmd), sectionsAlignment(segCmd), - segmentAlignment(segCmd), segCmd->initprot(), - segCmd->nsects(), segCmd->segname())); - } - break; - case LC_SYMTAB: - symbolTableCmd = (macho_symtab_command

*)cmd; - break; - case LC_DYSYMTAB: - dynamicSymbolTableCmd = (macho_dysymtab_command

*)cmd; - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - fHasDyldInfo = true; - dyldInfo = (struct macho_dyld_info_command

*)cmd; - break; - case LC_UUID: - { - const macho_uuid_command

* uc = (macho_uuid_command

*)cmd; - memcpy(&fUUID, uc->uuid(), 16); - } - break; - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } - - fLowSegment = NULL; - fLowExecutableSegment = NULL; - fLowWritableSegment = NULL; - fLowReadOnlySegment = NULL; - fVMExecutableSize = 0; - fVMWritablSize = 0; - fVMReadOnlySize = 0; - fVMSize = 0; - const Segment* highSegment = NULL; - for(std::vector::const_iterator it = fSegments.begin(); it != fSegments.end(); ++it) { - const Segment& seg = *it; - if ( (fLowSegment == NULL) || (seg.address() < fLowSegment->address()) ) - fLowSegment = &seg; - if ( (highSegment == NULL) || (seg.address() > highSegment->address()) ) - highSegment = &seg; - if ( seg.executable() ) { - if ( (fLowExecutableSegment == NULL) || (seg.address() < fLowExecutableSegment->address()) ) - fLowExecutableSegment = &seg; - fVMExecutableSize += seg.size(); - } - else if ( seg.writable()) { - if ( (fLowWritableSegment == NULL) || (seg.address() < fLowWritableSegment->address()) ) - fLowWritableSegment = &seg; - fVMWritablSize += seg.size(); - if ( !validReadWriteSeg(seg) ) { - fHasTooManyWritableSegments = true; - } - } - else { - if ( (fLowReadOnlySegment == NULL) || (seg.address() < fLowReadOnlySegment->address()) ) - fLowReadOnlySegment = &seg; - fVMReadOnlySize += seg.size(); - } - } - if ( (highSegment != NULL) && (fLowSegment != NULL) ) - fVMSize = (highSegment->address() + highSegment->size() - fLowSegment->address() + 4095) & (-4096); - - // scan undefines looking, for magic ordinals - if ( (symbolTableCmd != NULL) && (dynamicSymbolTableCmd != NULL) ) { - const macho_nlist

* symbolTable = (macho_nlist

*)((uint8_t*)machHeader + symbolTableCmd->symoff()); - const uint32_t startUndefs = dynamicSymbolTableCmd->iundefsym(); - const uint32_t endUndefs = startUndefs + dynamicSymbolTableCmd->nundefsym(); - for (uint32_t i=startUndefs; i < endUndefs; ++i) { - uint8_t ordinal = GET_LIBRARY_ORDINAL(symbolTable[i].n_desc()); - if ( ordinal == DYNAMIC_LOOKUP_ORDINAL ) - fDynamicLookupLinkage = true; - else if ( ordinal == EXECUTABLE_ORDINAL ) - fMainExecutableLookupLinkage = true; - } - } - - if ( dyldInfo != NULL ) { - if ( dyldInfo->export_off() != 0 ) { - fDyldInfoExports = (uint8_t*)machHeader + dyldInfo->export_off(); - } - } - - if ( fSplitSegInfo != NULL ) { - const uint8_t* infoStart = (uint8_t*)machHeader + fSplitSegInfo->dataoff(); - fHasSplitSegInfoV2 = ( *infoStart == DYLD_CACHE_ADJ_V2_FORMAT ); - if ( !fHasSplitSegInfoV2 ) { - // split seg version not known when segments created - // v1 does not support packing, so simulate that by forcing alignment - for (Segment& seg : fSegments) { - seg.setSectionsAlignment(4096); - } - } - } - -} - -template <> cpu_type_t MachOLayout::arch() { return CPU_TYPE_I386; } -template <> cpu_type_t MachOLayout::arch() { return CPU_TYPE_X86_64; } -template <> cpu_type_t MachOLayout::arch() { return CPU_TYPE_ARM; } -template <> cpu_type_t MachOLayout::arch() { return CPU_TYPE_ARM64; } - -template <> -bool MachOLayout::validReadWriteSeg(const Segment& seg) const -{ - return (strcmp(seg.name(), "__DATA") == 0) || (strcmp(seg.name(), "__OBJC") == 0); -} - -template -bool MachOLayout::validReadWriteSeg(const Segment& seg) const -{ - return (strcmp(seg.name(), "__DATA") == 0); -} - - -template <> -bool MachOLayout::isSplitSeg() const -{ - return ( (this->getFlags() & MH_SPLIT_SEGS) != 0 ); -} - -template <> -bool MachOLayout::isSplitSeg() const -{ - return ( (this->getFlags() & MH_SPLIT_SEGS) != 0 ); -} - -template -bool MachOLayout::isSplitSeg() const -{ - return false; -} - -template -const MachOLayoutAbstraction::Segment* MachOLayout::getSegment(const char* name) const -{ - for(std::vector::const_iterator it = fSegments.begin(); it != fSegments.end(); ++it) { - const Segment& seg = *it; - if ( strcmp(seg.name(), name) == 0 ) - return &seg; - } - return NULL; -} - - - -#endif // __MACHO_LAYOUT__ - - - diff --git a/launch-cache/MachORebaser.hpp b/launch-cache/MachORebaser.hpp deleted file mode 100644 index 464220a..0000000 --- a/launch-cache/MachORebaser.hpp +++ /dev/null @@ -1,1124 +0,0 @@ -/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- - * - * Copyright (c) 2006 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@ - */ - -#ifndef __MACHO_REBASER__ -#define __MACHO_REBASER__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "MachOFileAbstraction.hpp" -#include "Architectures.hpp" -#include "MachOLayout.hpp" -#include "MachOTrie.hpp" - - - -class AbstractRebaser -{ -public: - virtual cpu_type_t getArchitecture() const = 0; - virtual uint64_t getBaseAddress() const = 0; - virtual uint64_t getVMSize() const = 0; - virtual bool rebase(std::vector&) = 0; -}; - - -template -class Rebaser : public AbstractRebaser -{ -public: - Rebaser(const MachOLayoutAbstraction&); - virtual ~Rebaser() {} - - virtual cpu_type_t getArchitecture() const; - virtual uint64_t getBaseAddress() const; - virtual uint64_t getVMSize() const; - virtual bool rebase(std::vector&); - -protected: - typedef typename A::P P; - typedef typename A::P::E E; - typedef typename A::P::uint_t pint_t; - - pint_t* mappedAddressForNewAddress(pint_t vmaddress); - pint_t getSlideForNewAddress(pint_t newAddress); - -private: - void adjustLoadCommands(); - void adjustSymbolTable(); - void adjustDATA(); - void adjustCode(); - void applyRebaseInfo(std::vector& pointersInData); - void adjustReferencesUsingInfoV2(std::vector& pointersInData); - 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& pointersInData); - bool adjustExportInfo(); - void doRebase(int segIndex, uint64_t segOffset, uint8_t type, std::vector& pointersInData); - pint_t getSlideForVMAddress(pint_t vmaddress); - pint_t maskedVMAddress(pint_t vmaddress); - pint_t* mappedAddressForVMAddress(pint_t vmaddress); - const uint8_t* doCodeUpdateForEachULEB128Address(const uint8_t* p, uint8_t kind, uint64_t orgBaseAddress, int64_t codeToDataDelta, int64_t codeToImportDelta); - void doCodeUpdate(uint8_t kind, uint64_t address, int64_t codeToDataDelta, int64_t codeToImportDelta); - void doLocalRelocation(const macho_relocation_info

* reloc); - bool unequalSlides() const; - -protected: - const macho_header

* fHeader; - uint8_t* fLinkEditBase; // add file offset to this to get linkedit content - const MachOLayoutAbstraction& fLayout; -private: - const macho_symtab_command

* fSymbolTable; - const macho_dysymtab_command

* fDynamicSymbolTable; - const macho_dyld_info_command

* fDyldInfo; - const macho_linkedit_data_command

* fSplitSegInfo; - bool fSplittingSegments; - bool fHasSplitSegInfoV2; - std::vector fSectionOffsetsInSegment; -}; - - -template -Rebaser::Rebaser(const MachOLayoutAbstraction& layout) - : fLayout(layout), fLinkEditBase(0), fSymbolTable(NULL), fDynamicSymbolTable(NULL), - fDyldInfo(NULL), fSplitSegInfo(NULL), fSplittingSegments(false), fHasSplitSegInfoV2(false) -{ - fHeader = (const macho_header

*)fLayout.getSegments()[0].mappedAddress(); - switch ( fHeader->filetype() ) { - case MH_DYLIB: - case MH_BUNDLE: - break; - default: - throw "file is not a dylib or bundle"; - } - - 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(); - break; - } - } - if ( fLinkEditBase == NULL ) - throw "no __LINKEDIT segment"; - - // get symbol table info - 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: - fSymbolTable = (macho_symtab_command

*)cmd; - 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_SEGMENT_SPLIT_INFO: - fSplitSegInfo = (macho_linkedit_data_command

*)cmd; - break; - case macho_segment_command

::CMD: { - // update segment/section file offsets - macho_segment_command

* segCmd = (macho_segment_command

*)cmd; - 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) { - fSectionOffsetsInSegment.push_back(sect->addr() - segCmd->vmaddr()); - } - } - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } - - if ( fDyldInfo == NULL ) - throw "no LC_DYLD_INFO load command"; - - fSplittingSegments = layout.hasSplitSegInfo() && this->unequalSlides(); - - if ( fSplitSegInfo != NULL ) { - const uint8_t* infoStart = &fLinkEditBase[fSplitSegInfo->dataoff()]; - fHasSplitSegInfoV2 = ( *infoStart == DYLD_CACHE_ADJ_V2_FORMAT ); - } -} - -template <> cpu_type_t Rebaser::getArchitecture() const { return CPU_TYPE_I386; } -template <> cpu_type_t Rebaser::getArchitecture() const { return CPU_TYPE_X86_64; } -template <> cpu_type_t Rebaser::getArchitecture() const { return CPU_TYPE_ARM; } -template <> cpu_type_t Rebaser::getArchitecture() const { return CPU_TYPE_ARM64; } - -template -bool Rebaser::unequalSlides() const -{ - const std::vector& segments = fLayout.getSegments(); - uint64_t slide = segments[0].newAddress() - segments[0].address(); - for(std::vector::const_iterator it = segments.begin(); it != segments.end(); ++it) { - const MachOLayoutAbstraction::Segment& seg = *it; - if ( (seg.newAddress() - seg.address()) != slide ) - return true; - } - return false; -} - -template -uint64_t Rebaser::getBaseAddress() const -{ - return fLayout.getSegments()[0].address(); -} - -template -uint64_t Rebaser::getVMSize() const -{ - uint64_t highestVMAddress = 0; - const std::vector& segments = fLayout.getSegments(); - for(std::vector::const_iterator it = segments.begin(); it != segments.end(); ++it) { - const MachOLayoutAbstraction::Segment& seg = *it; - if ( seg.address() > highestVMAddress ) - highestVMAddress = seg.address(); - } - return (((highestVMAddress - getBaseAddress()) + 4095) & (-4096)); -} - - - -template -bool Rebaser::rebase(std::vector& pointersInData) -{ - if ( fHasSplitSegInfoV2 ) { - this->adjustReferencesUsingInfoV2(pointersInData); - } - else { - //fprintf(stderr, "warning: dylib with old split-seg info: %s\n", fLayout.getFilePath()); - // update writable segments that have internal pointers - this->applyRebaseInfo(pointersInData); - - // if splitting segments, update code-to-data references - this->adjustCode(); - } - - // update load commands - this->adjustLoadCommands(); - - // update symbol table - this->adjustSymbolTable(); - - // update export info - return this->adjustExportInfo(); -} - -template -void Rebaser::adjustLoadCommands() -{ - 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_ID_DYLIB: - if ( (fHeader->flags() & MH_PREBOUND) != 0 ) { - // clear timestamp so that any prebound clients are invalidated - macho_dylib_command

* dylib = (macho_dylib_command

*)cmd; - dylib->set_timestamp(1); - } - break; - case LC_LOAD_DYLIB: - case LC_LOAD_WEAK_DYLIB: - case LC_REEXPORT_DYLIB: - case LC_LOAD_UPWARD_DYLIB: - if ( (fHeader->flags() & MH_PREBOUND) != 0 ) { - // clear expected timestamps so that this image will load with invalid prebinding - macho_dylib_command

* dylib = (macho_dylib_command

*)cmd; - dylib->set_timestamp(2); - } - break; - case macho_routines_command

::CMD: - // update -init command - { - struct macho_routines_command

* routines = (struct macho_routines_command

*)cmd; - routines->set_init_address(routines->init_address() + this->getSlideForVMAddress(routines->init_address())); - } - break; - case macho_segment_command

::CMD: - // update segment commands - { - macho_segment_command

* seg = (macho_segment_command

*)cmd; - pint_t slide = this->getSlideForVMAddress(seg->vmaddr()); - seg->set_vmaddr(seg->vmaddr() + slide); - 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) { - sect->set_addr(sect->addr() + slide); - } - } - break; - } - cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); - } -} - -template <> -uint64_t Rebaser::maskedVMAddress(pint_t vmaddress) -{ - return (vmaddress & 0x0FFFFFFFFFFFFFFF); -} - -template -typename A::P::uint_t Rebaser::maskedVMAddress(pint_t vmaddress) -{ - return vmaddress; -} - - -template -typename A::P::uint_t Rebaser::getSlideForVMAddress(pint_t vmaddress) -{ - pint_t vmaddr = this->maskedVMAddress(vmaddress); - const std::vector& segments = fLayout.getSegments(); - for(std::vector::const_iterator it = segments.begin(); it != segments.end(); ++it) { - const MachOLayoutAbstraction::Segment& seg = *it; - if ( (seg.address() <= vmaddr) && (seg.size() != 0) && ((vmaddr < (seg.address()+seg.size())) || (seg.address() == vmaddr)) ) { - return seg.newAddress() - seg.address(); - } - } - throwf("vm address 0x%08llX not found", (uint64_t)vmaddr); -} - - -template -typename A::P::uint_t* Rebaser::mappedAddressForVMAddress(pint_t vmaddress) -{ - pint_t vmaddr = this->maskedVMAddress(vmaddress); - const std::vector& segments = fLayout.getSegments(); - for(std::vector::const_iterator it = segments.begin(); it != segments.end(); ++it) { - const MachOLayoutAbstraction::Segment& seg = *it; - if ( (seg.address() <= vmaddr) && (vmaddr < (seg.address()+seg.size())) ) { - return (pint_t*)((vmaddr - seg.address()) + (uint8_t*)seg.mappedAddress()); - } - } - throwf("mappedAddressForVMAddress(0x%08llX) not found", (uint64_t)vmaddr); -} - -template -typename A::P::uint_t* Rebaser::mappedAddressForNewAddress(pint_t vmaddress) -{ - const std::vector& segments = fLayout.getSegments(); - for(std::vector::const_iterator it = segments.begin(); it != segments.end(); ++it) { - const MachOLayoutAbstraction::Segment& seg = *it; - if ( (seg.newAddress() <= vmaddress) && (vmaddress < (seg.newAddress()+seg.size())) ) { - return (pint_t*)((vmaddress - seg.newAddress()) + (uint8_t*)seg.mappedAddress()); - } - } - throwf("mappedAddressForNewAddress(0x%08llX) not found", (uint64_t)vmaddress); -} - -template -typename A::P::uint_t Rebaser::getSlideForNewAddress(pint_t newAddress) -{ - const std::vector& segments = fLayout.getSegments(); - for(std::vector::const_iterator it = segments.begin(); it != segments.end(); ++it) { - const MachOLayoutAbstraction::Segment& seg = *it; - if ( (seg.newAddress() <= newAddress) && (newAddress < (seg.newAddress()+seg.size())) ) { - return seg.newAddress() - seg.address(); - } - } - throwf("new address 0x%08llX not found", (uint64_t)newAddress); -} - -template -void Rebaser::adjustSymbolTable() -{ - macho_nlist

* symbolTable = (macho_nlist

*)(&fLinkEditBase[fSymbolTable->symoff()]); - - // walk all exports and slide their n_value - macho_nlist

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

* entry = &symbolTable[fDynamicSymbolTable->iextdefsym()]; entry < lastExport; ++entry) { - if ( (entry->n_type() & N_TYPE) == N_SECT ) - entry->set_n_value(entry->n_value() + this->getSlideForVMAddress(entry->n_value())); - } - - // walk all local symbols and slide their n_value (don't adjust any stabs) - macho_nlist

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

* entry = &symbolTable[fDynamicSymbolTable->ilocalsym()]; entry < lastLocal; ++entry) { - if ( (entry->n_sect() != NO_SECT) && ((entry->n_type() & N_STAB) == 0) ) - entry->set_n_value(entry->n_value() + this->getSlideForVMAddress(entry->n_value())); - } -} - -template -bool Rebaser::adjustExportInfo() -{ - // if no export info, nothing to adjust - if ( fDyldInfo->export_size() == 0 ) - return true; - - // 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 = fLayout.getDyldInfoExports(); - const uint8_t* end = &start[fDyldInfo->export_size()]; - std::vector originalExports; - try { - parseTrie(start, end, originalExports); - } - catch (const char* msg) { - throwf("%s in %s", msg, fLayout.getFilePath()); - } - - std::vector newExports; - newExports.reserve(originalExports.size()); - pint_t baseAddress = this->getBaseAddress(); - pint_t baseAddressSlide = this->getSlideForVMAddress(baseAddress); - for (std::vector::iterator it=originalExports.begin(); it != originalExports.end(); ++it) { - // remove symbols used by the static linker only - if ( (strncmp(it->name, "$ld$", 4) == 0) - || (strncmp(it->name, ".objc_class_name",16) == 0) - || (strncmp(it->name, ".objc_category_name",19) == 0) ) { - //fprintf(stderr, "ignoring symbol %s\n", it->name); - continue; - } - // adjust symbols in slid segments - //uint32_t oldOffset = it->address; - it->address += (this->getSlideForVMAddress(it->address + baseAddress) - baseAddressSlide); - //fprintf(stderr, "orig=0x%08X, new=0x%08llX, sym=%s\n", oldOffset, it->address, it->name); - newExports.push_back(*it); - } - - // rebuild export trie - std::vector newExportTrieBytes; - newExportTrieBytes.reserve(fDyldInfo->export_size()); - mach_o::trie::makeTrie(newExports, newExportTrieBytes); - // align - while ( (newExportTrieBytes.size() % sizeof(pint_t)) != 0 ) - newExportTrieBytes.push_back(0); - - uint32_t newExportsSize = newExportTrieBytes.size(); - if ( newExportsSize <= fDyldInfo->export_size() ) { - // override existing trie in place - uint8_t *realStart = &fLinkEditBase[fDyldInfo->export_off()]; - bzero(realStart, fDyldInfo->export_size()); - memcpy(realStart, &newExportTrieBytes[0], newExportsSize); - fLayout.setDyldInfoExports(realStart); - return true; - } - else { - // allocate new buffer and set export_off in layout object to use new buffer instead - uint8_t* sideTrie = new uint8_t[newExportsSize]; - memcpy(sideTrie, &newExportTrieBytes[0], newExportsSize); - fLayout.setDyldInfoExports(sideTrie); - ((macho_dyld_info_command

*)fDyldInfo)->set_export_off(0); // invalidate old trie - ((macho_dyld_info_command

*)fDyldInfo)->set_export_size(newExportsSize); - return false; - } -} - - - -template -void Rebaser::doCodeUpdate(uint8_t kind, uint64_t address, int64_t codeToDataDelta, int64_t codeToImportDelta) -{ - //fprintf(stderr, "doCodeUpdate(kind=%d, address=0x%0llX, dataDelta=0x%08llX, importDelta=0x%08llX, path=%s)\n", - // kind, address, codeToDataDelta, codeToImportDelta, fLayout.getFilePath()); - uint32_t* p; - uint32_t instruction; - uint32_t value; - uint64_t value64; - switch (kind) { - case 1: // 32-bit pointer - p = (uint32_t*)mappedAddressForVMAddress(address); - value = A::P::E::get32(*p); - value += codeToDataDelta; - A::P::E::set32(*p, value); - break; - case 2: // 64-bit pointer - p = (uint32_t*)mappedAddressForVMAddress(address); - value64 = A::P::E::get64(*(uint64_t*)p); - value64 += codeToDataDelta; - A::P::E::set64(*(uint64_t*)p, value64); - break; - case 4: // only used for i386, a reference to something in the IMPORT segment - p = (uint32_t*)mappedAddressForVMAddress(address); - value = A::P::E::get32(*p); - value += codeToImportDelta; - A::P::E::set32(*p, value); - break; - case 5: // used by thumb2 movw - p = (uint32_t*)mappedAddressForVMAddress(address); - instruction = A::P::E::get32(*p); - // codeToDataDelta is always a multiple of 4096, so only top 4 bits of lo16 will ever need adjusting - value = (instruction & 0x0000000F) + (codeToDataDelta >> 12); - instruction = (instruction & 0xFFFFFFF0) | (value & 0x0000000F); - A::P::E::set32(*p, instruction); - break; - case 6: // used by ARM movw - p = (uint32_t*)mappedAddressForVMAddress(address); - instruction = A::P::E::get32(*p); - // codeToDataDelta is always a multiple of 4096, so only top 4 bits of lo16 will ever need adjusting - value = ((instruction & 0x000F0000) >> 16) + (codeToDataDelta >> 12); - instruction = (instruction & 0xFFF0FFFF) | ((value <<16) & 0x000F0000); - A::P::E::set32(*p, 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) - { - p = (uint32_t*)mappedAddressForVMAddress(address); - instruction = A::P::E::get32(*p); - // 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 + 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); - A::P::E::set32(*p, 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) - { - p = (uint32_t*)mappedAddressForVMAddress(address); - instruction = A::P::E::get32(*p); - // 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 + 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_; - A::P::E::set32(*p, newInstruction); - } - break; - case 3: // used for arm64 ADRP - p = (uint32_t*)mappedAddressForVMAddress(address); - instruction = A::P::E::get32(*p); - 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); - A::P::E::set32(*p, instruction); - } - break; - default: - throwf("invalid kind=%d in split seg info", kind); - } -} - -template -const uint8_t* Rebaser::doCodeUpdateForEachULEB128Address(const uint8_t* p, uint8_t kind, uint64_t orgBaseAddress, int64_t codeToDataDelta, int64_t codeToImportDelta) -{ - uint64_t address = 0; - uint64_t delta = 0; - uint32_t shift = 0; - bool more = true; - do { - uint8_t byte = *p++; - delta |= ((byte & 0x7F) << shift); - shift += 7; - if ( byte < 0x80 ) { - if ( delta != 0 ) { - address += delta; - doCodeUpdate(kind, address+orgBaseAddress, codeToDataDelta, codeToImportDelta); - delta = 0; - shift = 0; - } - else { - more = false; - } - } - } while (more); - return p; -} - -template -void Rebaser::adjustCode() -{ - if ( fSplittingSegments ) { - // get uleb128 compressed runs of code addresses to update - const uint8_t* infoStart = &fLinkEditBase[fSplitSegInfo->dataoff()]; - const uint8_t* infoEnd = &infoStart[fSplitSegInfo->datasize()];; - // calculate how much we need to slide writable segments - const uint64_t orgBaseAddress = this->getBaseAddress(); - int64_t codeToDataDelta = 0; - int64_t codeToImportDelta = 0; - const std::vector& segments = fLayout.getSegments(); - const MachOLayoutAbstraction::Segment& codeSeg = segments[0]; - for(std::vector::const_iterator it = segments.begin(); it != segments.end(); ++it) { - const MachOLayoutAbstraction::Segment& dataSeg = *it; - if ( dataSeg.writable() ) { - if ( (strcmp(dataSeg.name(), "__DATA") != 0) && (strcmp(dataSeg.name(), "__OBJC") != 0) ) - throwf("only one rw segment named '__DATA' can be used in dylibs placed in the dyld shared cache (%s)", fLayout.getFilePath()); - codeToDataDelta = (dataSeg.newAddress() - codeSeg.newAddress()) - (dataSeg.address() - codeSeg.address()); - } - } - // decompress and call doCodeUpdate() on each address - for(const uint8_t* p = infoStart; (*p != 0) && (p < infoEnd);) { - uint8_t kind = *p++; - p = this->doCodeUpdateForEachULEB128Address(p, kind, orgBaseAddress, codeToDataDelta, codeToImportDelta); - } - } -} - -template -void Rebaser::doRebase(int segIndex, uint64_t segOffset, uint8_t type, std::vector& pointersInData) -{ - const std::vector& segments = fLayout.getSegments(); - if ( segIndex > segments.size() ) - throw "bad segment index in rebase info"; - const MachOLayoutAbstraction::Segment& seg = segments[segIndex]; - uint8_t* mappedAddr = (uint8_t*)seg.mappedAddress() + segOffset; - pint_t* mappedAddrP = (pint_t*)mappedAddr; - uint32_t* mappedAddr32 = (uint32_t*)mappedAddr; - pint_t valueP; - pint_t valuePnew; - uint32_t value32; - int32_t svalue32; - int32_t svalue32new; - switch ( type ) { - case REBASE_TYPE_POINTER: - valueP= P::getP(*mappedAddrP); - try { - P::setP(*mappedAddrP, valueP + this->getSlideForVMAddress(valueP)); - } - catch (const char* msg) { - throwf("at offset=0x%08llX in seg=%s, pointer cannot be rebased because it does not point to __TEXT or __DATA. %s\n", - segOffset, seg.name(), msg); - } - break; - - case REBASE_TYPE_TEXT_ABSOLUTE32: - value32 = E::get32(*mappedAddr32); - E::set32(*mappedAddr32, value32 + this->getSlideForVMAddress(value32)); - break; - - case REBASE_TYPE_TEXT_PCREL32: - svalue32 = E::get32(*mappedAddr32); - valueP = seg.address() + segOffset + 4 + svalue32; - valuePnew = valueP + this->getSlideForVMAddress(valueP); - svalue32new = seg.address() + segOffset + 4 - valuePnew; - E::set32(*mappedAddr32, svalue32new); - break; - - default: - throw "bad rebase type"; - } - pointersInData.push_back(mappedAddr); -} - - -template -void Rebaser::applyRebaseInfo(std::vector& pointersInData) -{ - const uint8_t* p = &fLinkEditBase[fDyldInfo->rebase_off()]; - const uint8_t* end = &p[fDyldInfo->rebase_size()]; - - uint8_t type = 0; - int segIndex; - uint64_t segOffset = 0; - uint32_t count; - uint32_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) { - doRebase(segIndex, segOffset, type, pointersInData); - 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) { - doRebase(segIndex, segOffset, type, pointersInData); - segOffset += sizeof(pint_t); - } - break; - case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: - doRebase(segIndex, segOffset, type, pointersInData); - 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) { - doRebase(segIndex, segOffset, type, pointersInData); - segOffset += skip + sizeof(pint_t); - } - break; - default: - throwf("bad rebase opcode %d", *p); - } - } -} - -template <> -void Rebaser::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& pointersInData) -{ - uint64_t value64; - uint64_t* mappedAddr64; - uint32_t value32; - uint32_t* mappedAddr32; - uint32_t instruction; - int64_t offsetAdjust; - switch ( kind ) { - case DYLD_CACHE_ADJ_V2_DELTA_32: - mappedAddr32 = (uint32_t*)mappedAddr; - value32 = arm64::P::E::get32(*mappedAddr32); - E::set32(*mappedAddr32, value32 + adjust); - break; - case DYLD_CACHE_ADJ_V2_POINTER_64: - mappedAddr64 = (uint64_t*)mappedAddr; - if ( toNewAddress != (E::get64(*mappedAddr64) + targetSlide) ) - throwf("bad DYLD_CACHE_ADJ_V2_POINTER_64 value not as expected at address 0x%llX\n", fromNewAddress); - E::set64(*mappedAddr64, toNewAddress); - pointersInData.push_back(mappedAddr); - break; - case DYLD_CACHE_ADJ_V2_DELTA_64: - mappedAddr64 = (uint64_t*)mappedAddr; - value64 = arm64::P::E::get64(*mappedAddr64); - E::set64(*mappedAddr64, value64 + adjust); - break; - case DYLD_CACHE_ADJ_V2_IMAGE_OFF_32: - mappedAddr32 = (uint32_t*)mappedAddr; - value64 = toNewAddress - imageStartAddress; - E::set32(*mappedAddr32, (uint32_t)value64); - break; - case DYLD_CACHE_ADJ_V2_ARM64_ADRP: - mappedAddr32 = (uint32_t*)mappedAddr; - instruction = arm64::P::E::get32(*mappedAddr32); - if ( (instruction & 0x9F000000) == 0x90000000 ) { - //value64 = ((instruction & 0x60000000) >> 17) | ((instruction & 0x00FFFFE0) << 9); - uint32_t newPage21 = ((toNewAddress & ~0xFFF) - (fromNewAddress & ~0xFFF)) >> 12; - instruction = (instruction & 0x9F00001F) | ((newPage21 << 29) & 0x60000000) | ((newPage21 << 3) & 0x00FFFFE0); - arm64::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 = arm64::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; - switch ( instruction & 0xC0000000 ) { - case 0x00000000: - if ( (instruction & 0x04800000) == 0x04800000 ) { - if ( offsetAdjust & 0xF ) - throwf("can't adjust off12 scale=16 instruction by %lld bytes at mapped address=%p", offsetAdjust, mappedAddr); - if ( encodedAddend*16 >= 4096 ) - throwf("off12 scale=16 instruction points outside its page at mapped address=%p", mappedAddr); - newAddend = (encodedAddend + offsetAdjust/16) % 256; - } - else { - // scale=1 - newAddend = encodedAddend + offsetAdjust; - } - break; - case 0x40000000: - if ( offsetAdjust & 1 ) - throwf("can't adjust off12 scale=2 instruction by %lld bytes at mapped address=%p", offsetAdjust, mappedAddr); - if ( encodedAddend*2 >= 4096 ) - throwf("off12 scale=2 instruction points outside its page at mapped address=%p", mappedAddr); - newAddend = (encodedAddend + offsetAdjust/2) % 2048; - break; - case 0x80000000: - if ( offsetAdjust & 3 ) - throwf("can't adjust off12 scale=4 instruction by %lld bytes at mapped address=%p", offsetAdjust, mappedAddr); - if ( encodedAddend*4 >= 4096 ) - throwf("off12 scale=4 instruction points outside its page at mapped address=%p", mappedAddr); - newAddend = (encodedAddend + offsetAdjust/4) % 1024; - break; - case 0xC0000000: - if ( offsetAdjust & 7 ) - throwf("can't adjust off12 scale=8 instruction by %lld bytes at mapped address=%p", offsetAdjust, mappedAddr); - if ( encodedAddend*8 >= 4096 ) - throwf("off12 scale=8 instruction points outside its page at mapped address=%p", mappedAddr); - newAddend = (encodedAddend + offsetAdjust/8) % 512; - break; - } - uint32_t newInstruction = (instruction & 0xFFC003FF) | (newAddend << 10); - arm64::P::E::set32(*mappedAddr32, newInstruction); - } - } - else if ( (instruction & 0xFFC00000) == 0x91000000 ) { - // ADD imm12 - if ( instruction & 0x00C00000 ) - throwf("ADD off12 uses shift at mapped address=%p", mappedAddr); - uint32_t encodedAddend = ((instruction & 0x003FFC00) >> 10); - uint32_t newAddend = (encodedAddend + offsetAdjust) & 0xFFF; - uint32_t newInstruction = (instruction & 0xFFC003FF) | (newAddend << 10); - arm64::P::E::set32(*mappedAddr32, newInstruction); - } - else if ( instruction != 0xD503201F ) { - // ignore imm12 instructions optimized into a NOP, but warn about others - fprintf(stderr, "unknown off12 instruction 0x%08X at 0x%0llX in %s\n", instruction, fromNewAddress, fLayout.getFilePath()); - } - break; - case DYLD_CACHE_ADJ_V2_ARM64_BR26: - // nothing to do with calls to stubs - break; - default: - throwf("unknown split seg kind=%d", kind); - } -} - -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 Rebaser::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& pointersInData) -{ - uint32_t value32; - uint32_t* mappedAddr32 = (uint32_t*)mappedAddr; - static uint32_t* lastMappedAddr32 = NULL; - static uint32_t lastKind = 0; - static uint32_t lastToNewAddress = 0; - switch ( kind ) { - case DYLD_CACHE_ADJ_V2_DELTA_32: - value32 = arm64::P::E::get32(*mappedAddr32); - E::set32(*mappedAddr32, value32 + adjust); - break; - case DYLD_CACHE_ADJ_V2_POINTER_32: - if ( toNewAddress != (E::get32(*mappedAddr32) + targetSlide) ) - throwf("bad DYLD_CACHE_ADJ_V2_POINTER_32 value not as expected at address 0x%llX\n", fromNewAddress); - E::set32(*mappedAddr32, toNewAddress); - 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); - break; - case DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT: - // 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 = E::get32(*lastMappedAddr32); - uint32_t instruction2 = 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 { - throw "two DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT in a row but not"; - } - E::set32(*lastMappedAddr32, instruction1); - E::set32(*mappedAddr32, instruction2); - kind = 0; - } - else { - throw "two DYLD_CACHE_ADJ_V2_THUMB_MOVW_MOVT in a row but target different addresses"; - } - } - break; - case DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT: - // 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 = E::get32(*lastMappedAddr32); - uint32_t instruction2 = 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 { - throw "two DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT in a row but not"; - } - E::set32(*lastMappedAddr32, instruction1); - E::set32(*mappedAddr32, instruction2); - kind = 0; - } - else { - throw "two DYLD_CACHE_ADJ_V2_ARM_MOVW_MOVT in a row but target different addresses"; - } - } - break; - case DYLD_CACHE_ADJ_V2_ARM_BR24: - case DYLD_CACHE_ADJ_V2_THUMB_BR22: - // nothing to do with calls to stubs - break; - default: - throwf("v2 split seg info kind (%d) not supported yet", kind); - } - lastKind = kind; - lastToNewAddress = toNewAddress; - lastMappedAddr32 = mappedAddr32; -} - - -template -void Rebaser::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& pointersInData) -{ - throw "v2 split seg info not supported yet"; -} - - -template -void Rebaser::adjustReferencesUsingInfoV2(std::vector& pointersInData) -{ - static const bool log = false; - - const uint8_t* infoStart = &fLinkEditBase[fSplitSegInfo->dataoff()]; - const uint8_t* infoEnd = &infoStart[fSplitSegInfo->datasize()];; - if ( *infoStart++ != DYLD_CACHE_ADJ_V2_FORMAT ) - throw "malformed split seg info"; - - // 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 - const MachOLayoutAbstraction::Segment& textSeg = fLayout.getSegments().front(); - sectionMappedAddress.push_back((uint8_t*)textSeg.mappedAddress()); - sectionSlides.push_back(textSeg.newAddress() - textSeg.address()); - sectionNewAddress.push_back(textSeg.newAddress()); - // section 1 and later refer to real sections - unsigned sectionIndex = 0; - for (const MachOLayoutAbstraction::Segment& seg : fLayout.getSegments()) { - uint64_t segSlide = seg.newAddress() - seg.address(); - for (uint32_t i=0; i < seg.sectionCount(); ++i) { - if (log) fprintf(stderr, "seg=%s, sectIndex=%d, mapped at=%p, offsetInSeg=0x%08llX\n", seg.name(), sectionIndex, seg.mappedAddress(), fSectionOffsetsInSegment[sectionIndex]); - sectionMappedAddress.push_back((uint8_t*)seg.mappedAddress() + fSectionOffsetsInSegment[sectionIndex]); - sectionSlides.push_back(segSlide); - sectionNewAddress.push_back(seg.newAddress() + fSectionOffsetsInSegment[sectionIndex]); - ++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) { - 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); - 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; - adjustReference(kind, fromMappedAddr, fromNewAddress, toNewAddress, deltaAdjust, toSectionSlide, sectionNewAddress.front(), sectionNewAddress.back(), pointersInData); - } - } - } - } - - -} - -#endif // __MACHO_REBASER__ - - - - diff --git a/launch-cache/dsc_extractor.cpp b/launch-cache/dsc_extractor.cpp index 30ba094..e55d74a 100644 --- a/launch-cache/dsc_extractor.cpp +++ b/launch-cache/dsc_extractor.cpp @@ -497,7 +497,7 @@ size_t dylib_maker(const void* mapped_cache, std::vector &dylib_data, c optimize_linkedit(((macho_header

*)(base_ptr+offsetInFatFile)), textOffsetInCache, mapped_cache, &newSize); // update fat header with new file size - dylib_data.resize(offsetInFatFile+newSize); + dylib_data.resize((size_t)(offsetInFatFile+newSize)); base_ptr = &dylib_data.front(); FA->size = OSSwapHostToBigInt32(newSize); #undef FH @@ -521,7 +521,7 @@ int dyld_shared_cache_extract_dylibs_progress(const char* shared_cache_file_path return -1; } - void* mapped_cache = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, cache_fd, 0); + void* mapped_cache = mmap(NULL, (size_t)statbuf.st_size, PROT_READ, MAP_PRIVATE, cache_fd, 0); if (mapped_cache == MAP_FAILED) { fprintf(stderr, "Error: mmap() for shared cache at %s failed, errno=%d\n", shared_cache_file_path, errno); return -1; @@ -547,9 +547,11 @@ int dyld_shared_cache_extract_dylibs_progress(const char* shared_cache_file_path dylib_create_func = dylib_maker; else if ( strcmp((char*)mapped_cache, "dyld_v1 arm64") == 0 ) dylib_create_func = dylib_maker; + else if ( strcmp((char*)mapped_cache, "dyld_v1 arm64e") == 0 ) + dylib_create_func = dylib_maker; else { fprintf(stderr, "Error: unrecognized dyld shared cache magic.\n"); - munmap(mapped_cache, statbuf.st_size); + munmap(mapped_cache, (size_t)statbuf.st_size); return -1; } @@ -561,7 +563,7 @@ int dyld_shared_cache_extract_dylibs_progress(const char* shared_cache_file_path if(result != 0) { fprintf(stderr, "Error: dyld_shared_cache_iterate_segments_with_slide failed.\n"); - munmap(mapped_cache, statbuf.st_size); + munmap(mapped_cache, (size_t)statbuf.st_size); return result; } @@ -602,7 +604,7 @@ int dyld_shared_cache_extract_dylibs_progress(const char* shared_cache_file_path return; } - std::vector *vec = new std::vector(statbuf.st_size); + std::vector *vec = new std::vector((size_t)statbuf.st_size); if(pread(fd, &vec->front(), vec->size(), 0) != (long)vec->size()) { fprintf(stderr, "can't read dylib file %s, errnor=%d\n", dylib_path, errno); close(fd); @@ -635,7 +637,7 @@ int dyld_shared_cache_extract_dylibs_progress(const char* shared_cache_file_path dispatch_release(group); dispatch_release(writer_queue); - munmap(mapped_cache, statbuf.st_size); + munmap(mapped_cache, (size_t)statbuf.st_size); return result; } diff --git a/launch-cache/dsc_iterator.cpp b/launch-cache/dsc_iterator.cpp index 2c7c212..7aa84ca 100644 --- a/launch-cache/dsc_iterator.cpp +++ b/launch-cache/dsc_iterator.cpp @@ -59,7 +59,7 @@ namespace dyld { // call the callback block on each segment in this image template - int walkSegments(const uint8_t* cache, const uint8_t* cacheEnd, const uint8_t* firstSeg, const char* dylibPath, uint64_t inode,uint64_t modTime, const uint8_t* machHeader, + int walkSegments(const uint8_t* cache, const uint8_t* cacheEnd, const uint8_t* firstSeg, const char* dylibPath, uint64_t inode,uint64_t modTime, const uint8_t* machHeader, uint64_t cache_unslid_base_address, void (^callback)(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo)) { typedef typename A::P P; @@ -104,13 +104,14 @@ namespace dyld { if ( (fileOffset+sizem) > (uint64_t)(cacheEnd-cache) ) sizem = (cacheEnd-cache)-fileOffset; } - segInfo.version = 1; + segInfo.version = 2; segInfo.name = segCmd->segname(); segInfo.fileOffset = fileOffset; segInfo.fileSize = sizem; if ( segCmd->filesize() > segCmd->vmsize() ) return -1; segInfo.address = segCmd->vmaddr(); + segInfo.addressOffset = segInfo.address - cache_unslid_base_address; callback(&dylibInfo, &segInfo); } cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); @@ -131,6 +132,7 @@ namespace dyld { const dyldCacheHeader* header = (dyldCacheHeader*)cache; const dyldCacheImageInfo* dylibs = (dyldCacheImageInfo*)&cache[header->imagesOffset()]; const dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)&cache[header->mappingOffset()]; + uint64_t unslid_base_address = mappings[0].address(); uint64_t greatestMappingOffset = 0; for (uint32_t i=0; i < header->mappingCount(); ++i) { if ( (size != 0) && (mappings[i].file_offset() > size) ) @@ -168,7 +170,7 @@ namespace dyld { return -1; if ( firstSeg == NULL ) firstSeg = machHeader; - int result = walkSegments(cache, cacheEnd, firstSeg, dylibPath, inode, modTime, machHeader, callback); + int result = walkSegments(cache, cacheEnd, firstSeg, dylibPath, inode, modTime, machHeader, unslid_base_address, callback); if ( result != 0 ) return result; } @@ -201,6 +203,8 @@ extern int dyld_shared_cache_iterate(const void* shared_cache_file, uint32_t sha return dyld::walkImages(cache, shared_cache_size, callback); else if ( strcmp((char*)cache, "dyld_v1 arm64") == 0 ) return dyld::walkImages(cache, shared_cache_size, callback); + else if ( strcmp((char*)cache, "dyld_v1 arm64e") == 0 ) + return dyld::walkImages(cache, shared_cache_size, callback); else return -1; } diff --git a/launch-cache/dsc_iterator.h b/launch-cache/dsc_iterator.h index 6c75d0e..d0ea94d 100644 --- a/launch-cache/dsc_iterator.h +++ b/launch-cache/dsc_iterator.h @@ -40,11 +40,13 @@ typedef struct dyld_shared_cache_dylib_info dyld_shared_cache_dylib_info; struct dyld_shared_cache_segment_info { uint64_t version; // initial version 1 + // following fields exist in version 1 const char* name; // of segment uint64_t fileOffset; // of segment in cache file uint64_t fileSize; // of segment uint64_t address; // of segment when cache mapped with ASLR (sliding) off - // above fields all exist in version 1 + // following fields exist in version 2 + uint64_t addressOffset; // of segment from base of mapped cache }; typedef struct dyld_shared_cache_segment_info dyld_shared_cache_segment_info; diff --git a/launch-cache/dyld_cache_format.h b/launch-cache/dyld_cache_format.h index fd9d1c2..57b5d07 100644 --- a/launch-cache/dyld_cache_format.h +++ b/launch-cache/dyld_cache_format.h @@ -54,6 +54,7 @@ struct dyld_cache_header uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries }; + struct dyld_cache_mapping_info { uint64_t address; uint64_t size; @@ -131,7 +132,6 @@ struct dyld_cache_image_text_info 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 diff --git a/launch-cache/dyld_shared_cache_util.cpp b/launch-cache/dyld_shared_cache_util.cpp index 63d6b73..4c56c35 100644 --- a/launch-cache/dyld_shared_cache_util.cpp +++ b/launch-cache/dyld_shared_cache_util.cpp @@ -136,7 +136,9 @@ static const char* default_shared_cache_path() { return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "armv7f"; #elif __ARM_ARCH_7S__ return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "armv7s"; -#elif __arm64__ +#elif __arm64e__ + return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "arm64e"; +#elif __arm64__ return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "arm64"; #else #error unsupported architecture @@ -249,7 +251,6 @@ void collect_size(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shar info.textSize = segInfo->fileSize; info.path = dylibInfo->path; results.textSegments.push_back(info); - size_t size = segInfo->fileSize; } @@ -475,7 +476,7 @@ int main (int argc, const char* argv[]) { fprintf(stderr, "Error: open() failed for shared cache file at %s, errno=%d\n", sharedCachePath, errno); exit(1); } - options.mappedCache = ::mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, cache_fd, 0); + options.mappedCache = ::mmap(NULL, (size_t)statbuf.st_size, PROT_READ, MAP_PRIVATE, cache_fd, 0); if (options.mappedCache == MAP_FAILED) { fprintf(stderr, "Error: mmap() for shared cache at %s failed, errno=%d\n", sharedCachePath, errno); exit(1); @@ -549,6 +550,39 @@ int main (int argc, const char* argv[]) { else { printf("n/a\n"); } + if ( header->mappingOffset() >= 0xE0 ) { + // HACK until this uses new header + uint32_t platform = *((uint32_t*)(((char*)header) + 0xD8)); + uint32_t simulator = *((uint32_t*)(((char*)header) + 0xDC)); + switch (platform) { + case 1: + printf("platform: macOS\n"); + break; + case 2: + if ( simulator & 0x400 ) + printf("platform: iOS simulator\n"); + else + printf("platform: iOS\n"); + break; + case 3: + if ( simulator & 0x400 ) + printf("platform: tvOS simulator\n"); + else + printf("platform: tvOS\n"); + break; + case 4: + if ( simulator & 0x400 ) + printf("platform: watchOS simulator\n"); + else + printf("platform: watchOS\n"); + break; + case 5: + printf("platform: bridgeOS\n"); + break; + default: + printf("platform: 0x%08X 0x%08X\n", platform, simulator); + } + } printf("image count: %u\n", header->imagesCount()); if ( (header->mappingOffset() >= 0x78) && (header->branchPoolsOffset() != 0) ) { printf("branch pool count: %u\n", header->branchPoolsCount()); @@ -712,7 +746,7 @@ int main (int argc, const char* argv[]) { 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; + //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); @@ -756,7 +790,7 @@ int main (int argc, const char* argv[]) { return result; } else { - segment_callback_t callback; + segment_callback_t callback = nullptr; if ( strcmp((char*)options.mappedCache, "dyld_v1 i386") == 0 ) { switch ( options.mode ) { case modeList: @@ -840,7 +874,8 @@ int main (int argc, const char* argv[]) { break; } } - else if ( strcmp((char*)options.mappedCache, "dyld_v1 arm64") == 0 ) { + else if ( (strcmp((char*)options.mappedCache, "dyld_v1 arm64") == 0) + || (strcmp((char*)options.mappedCache, "dyld_v1 arm64e") == 0) ) { switch ( options.mode ) { case modeList: callback = print_list; diff --git a/launch-cache/update_dyld_shared_cache_entitlements.plist b/launch-cache/update_dyld_shared_cache_entitlements.plist deleted file mode 100644 index 5ed9d1c..0000000 --- a/launch-cache/update_dyld_shared_cache_entitlements.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.rootless.storage.dyld - - - diff --git a/src/ImageLoader.cpp b/src/ImageLoader.cpp index 89d9cdc..13117d5 100644 --- a/src/ImageLoader.cpp +++ b/src/ImageLoader.cpp @@ -618,14 +618,12 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli bool depLibRequired = false; bool depLibCheckSumsMatch = false; DependentLibraryInfo& requiredLibInfo = libraryInfos[i]; -#if DYLD_SHARED_CACHE_SUPPORT if ( preflightOnly && context.inSharedCache(requiredLibInfo.name) ) { // dlopen_preflight() on image in shared cache leaves it loaded but not objc initialized // in preflight mode, don't even load dylib that are in the shared cache because they will never be unloaded setLibImage(i, NULL, false, false); continue; } -#endif try { unsigned cacheIndex; dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(), &thisRPaths, cacheIndex); @@ -1317,7 +1315,7 @@ uintptr_t ImageLoader::read_uleb128(const uint8_t*& p, const uint8_t* end) bit += 7; } } while (*p++ & 0x80); - return result; + return (uintptr_t)result; } @@ -1336,7 +1334,7 @@ intptr_t ImageLoader::read_sleb128(const uint8_t*& p, const uint8_t* end) // sign extend negative numbers if ( (byte & 0x40) != 0 ) result |= (-1LL) << bit; - return result; + return (intptr_t)result; } diff --git a/src/ImageLoader.h b/src/ImageLoader.h index 334b9c7..fd70e2d 100644 --- a/src/ImageLoader.h +++ b/src/ImageLoader.h @@ -102,7 +102,6 @@ #define SPLIT_SEG_DYLIB_SUPPORT 0 #define PREBOUND_IMAGE_SUPPORT __arm__ #define TEXT_RELOC_SUPPORT __i386__ - #define DYLD_SHARED_CACHE_SUPPORT (__arm__ || __arm64__) #define SUPPORT_OLD_CRT_INITIALIZATION 0 #define SUPPORT_LC_DYLD_ENVIRONMENT 1 #define SUPPORT_VERSIONED_PATHS 1 @@ -116,7 +115,6 @@ #define SPLIT_SEG_DYLIB_SUPPORT __i386__ #define PREBOUND_IMAGE_SUPPORT __i386__ #define TEXT_RELOC_SUPPORT __i386__ - #define DYLD_SHARED_CACHE_SUPPORT 1 #define SUPPORT_OLD_CRT_INITIALIZATION __i386__ #define SUPPORT_LC_DYLD_ENVIRONMENT (__i386__ || __x86_64__) #define SUPPORT_VERSIONED_PATHS 1 @@ -609,6 +607,7 @@ public: dyld_image_states getState() { return (dyld_image_states)fState; } ino_t getInode() const { return fInode; } + dev_t getDevice() const { return fDevice; } // used to sort images bottom-up int compare(const ImageLoader* right) const; diff --git a/src/ImageLoaderMachO.cpp b/src/ImageLoaderMachO.cpp index 3369bb7..feba249 100644 --- a/src/ImageLoaderMachO.cpp +++ b/src/ImageLoaderMachO.cpp @@ -52,6 +52,7 @@ #include "ImageLoaderMachOClassic.h" #endif #include "mach-o/dyld_images.h" +#include "Tracing.h" #include "dyld.h" // use stack guard random value to add padding between dylibs @@ -69,6 +70,43 @@ extern "C" long __stack_chk_guard; #define LC_VERSION_MIN_WATCHOS 0x30 #endif +#ifndef LC_BUILD_VERSION + #define LC_BUILD_VERSION 0x32 /* build for platform min OS version */ + + /* + * The build_version_command contains the min OS version on which this + * binary was built to run for its platform. The list of known platforms and + * tool values following it. + */ + struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ + }; + + struct build_tool_version { + uint32_t tool; /* enum for the tool */ + uint32_t version; /* version number of the tool */ + }; + + /* Known values for the platform field above. */ + #define PLATFORM_MACOS 1 + #define PLATFORM_IOS 2 + #define PLATFORM_TVOS 3 + #define PLATFORM_WATCHOS 4 + #define PLATFORM_BRIDGEOS 5 + + /* Known values for the tool field above. */ + #define TOOL_CLANG 1 + #define TOOL_SWIFT 2 + #define TOOL_LD 3 +#endif + + #if TARGET_IPHONE_SIMULATOR #define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.dylib" @@ -224,7 +262,7 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat 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->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 ) { @@ -356,7 +394,7 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat throw "load commands not in a segment"; if ( linkeditSegCmd == NULL ) throw "malformed mach-o image: missing __LINKEDIT segment"; - if ( startOfFileSegCmd == NULL ) + if ( !inCache && (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.strictMachORequired ) { @@ -1258,6 +1296,7 @@ uint32_t ImageLoaderMachO::sdkVersion(const mach_header* mh) 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; + const struct build_version_command* buildVersCmd; for (uint32_t i = 0; i < cmd_count; ++i) { switch ( cmd->cmd ) { case LC_VERSION_MIN_MACOSX: @@ -1266,6 +1305,9 @@ uint32_t ImageLoaderMachO::sdkVersion(const mach_header* mh) case LC_VERSION_MIN_WATCHOS: versCmd = (version_min_command*)cmd; return versCmd->sdk; + case LC_BUILD_VERSION: + buildVersCmd = (build_version_command*)cmd; + return buildVersCmd->sdk; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } @@ -1283,6 +1325,7 @@ uint32_t ImageLoaderMachO::minOSVersion(const mach_header* mh) 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; + const struct build_version_command* buildVersCmd; for (uint32_t i = 0; i < cmd_count; ++i) { switch ( cmd->cmd ) { case LC_VERSION_MIN_MACOSX: @@ -1291,6 +1334,9 @@ uint32_t ImageLoaderMachO::minOSVersion(const mach_header* mh) case LC_VERSION_MIN_WATCHOS: versCmd = (version_min_command*)cmd; return versCmd->version; + case LC_BUILD_VERSION: + buildVersCmd = (build_version_command*)cmd; + return buildVersCmd->minos; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } @@ -1794,7 +1840,7 @@ intptr_t ImageLoaderMachO::computeSlide(const mach_header* mh) 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) ) + if ( strcmp(seg->segname, "__TEXT") == 0 ) return (char*)mh - (char*)(seg->vmaddr); } cmd = (const load_command*)(((char*)cmd)+cmd->cmdsize); @@ -2004,7 +2050,7 @@ struct DATAdyld { // These are defined in dyldStartup.s extern "C" void stub_binding_helper(); - +extern "C" int _dyld_func_lookup(const char* name, void** address); void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context) { @@ -2160,7 +2206,9 @@ void ImageLoaderMachO::doImageInit(const LinkContext& context) } 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); + dyld3::kdebug_trace_dyld_duration(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)func, 0, ^{ + func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + }); break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); @@ -2202,7 +2250,9 @@ void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) 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); + dyld3::kdebug_trace_dyld_duration(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)func, 0, ^{ + 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 @@ -2422,7 +2472,7 @@ void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInF } // map in all segments for(unsigned int i=0, e=segmentCount(); i < e; ++i) { - vm_offset_t fileOffset = segFileOffset(i) + offsetInFat; + vm_offset_t fileOffset = (vm_offset_t)(segFileOffset(i) + offsetInFat); vm_size_t size = segFileSize(i); uintptr_t requestedLoadAddress = segPreferredLoadAddress(i) + slide; int protection = 0; @@ -2569,8 +2619,9 @@ const char* ImageLoaderMachO::findClosestSymbol(const mach_header* mh, const voi unslidLinkEditBase = (uint8_t*)(seg->vmaddr - seg->fileoff); linkEditBaseFound = true; } - else if ( (seg->fileoff == 0) && (seg->filesize != 0) ) + else if ( strcmp(seg->segname, "__TEXT") == 0 ) { slide = (uintptr_t)mh - seg->vmaddr; + } break; case LC_SYMTAB: symtab = (symtab_command*)cmd; diff --git a/src/ImageLoaderMachOClassic.cpp b/src/ImageLoaderMachOClassic.cpp index fa71b09..86723e7 100644 --- a/src/ImageLoaderMachOClassic.cpp +++ b/src/ImageLoaderMachOClassic.cpp @@ -436,14 +436,14 @@ ImageLoaderMachOClassic::mapSplitSegDylibOutsideSharedRegion(int fd, while ( ! foundRoom ) { foundRoom = true; for(unsigned int i=0; i < regionCount; ++i) { - vm_address_t addr = nextAltLoadAddress + regions[i].sfm_address - regions[0].sfm_address; - vm_size_t size = regions[i].sfm_size ; + vm_address_t addr = (vm_address_t)(nextAltLoadAddress + regions[i].sfm_address - regions[0].sfm_address); + vm_size_t size = (vm_size_t)regions[i].sfm_size ; r = vm_allocate(mach_task_self(), &addr, size, false /*only this range*/); if ( 0 != r ) { // no room here, deallocate what has succeeded so far for(unsigned int j=0; j < i; ++j) { - addr = nextAltLoadAddress + regions[j].sfm_address - regions[0].sfm_address; - size = regions[j].sfm_size ; + addr = (vm_address_t)(nextAltLoadAddress + regions[j].sfm_address - regions[0].sfm_address); + size = (vm_size_t)(regions[j].sfm_size); (void)vm_deallocate(mach_task_self(), addr, size); } nextAltLoadAddress += 0x00100000; // skip ahead 1MB and try again @@ -459,7 +459,7 @@ ImageLoaderMachOClassic::mapSplitSegDylibOutsideSharedRegion(int fd, } // map in each region - uintptr_t slide = nextAltLoadAddress - regions[0].sfm_address; + uintptr_t slide = (uintptr_t)(nextAltLoadAddress - regions[0].sfm_address); this->setSlide(slide); for(unsigned int i=0; i < regionCount; ++i) { if ( ((regions[i].sfm_init_prot & VM_PROT_ZF) != 0) || (regions[i].sfm_size == 0) ) { @@ -467,7 +467,7 @@ ImageLoaderMachOClassic::mapSplitSegDylibOutsideSharedRegion(int fd, } else { void* mmapAddress = (void*)(uintptr_t)(regions[i].sfm_address + slide); - size_t size = regions[i].sfm_size; + size_t size = (size_t)regions[i].sfm_size; int protection = 0; if ( regions[i].sfm_init_prot & VM_PROT_EXECUTE ) protection |= PROT_EXEC; @@ -728,7 +728,7 @@ void ImageLoaderMachOClassic::resetPreboundLazyPointers(const LinkContext& conte { // loop through all local (internal) relocation records looking for pre-bound-lazy-pointer values const uintptr_t relocBase = this->getRelocBase(); - register const uintptr_t slide = this->fSlide; + const uintptr_t slide = this->fSlide; const relocation_info* const relocsStart = (struct relocation_info*)(&fLinkEditBase[fDynamicInfo->locreloff]); const relocation_info* const relocsEnd = &relocsStart[fDynamicInfo->nlocrel]; for (const relocation_info* reloc=relocsStart; reloc < relocsEnd; ++reloc) { diff --git a/src/ImageLoaderMachOCompressed.cpp b/src/ImageLoaderMachOCompressed.cpp index fb3c04e..020ec51 100644 --- a/src/ImageLoaderMachOCompressed.cpp +++ b/src/ImageLoaderMachOCompressed.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,12 @@ struct macho_routines_command : public routines_command {}; #endif +#if __arm__ || __arm64__ +bool ImageLoaderMachOCompressed::sVmAccountingDisabled = false; +bool ImageLoaderMachOCompressed::sVmAccountingSuspended = false; +#endif + + // create image for main executable ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, @@ -150,6 +157,10 @@ ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateFromFile(cons // make sure path is stable before recording in dyld_all_image_infos image->setMapped(context); + // dylibs with thread local variables cannot be unloaded because there is no way to clean up all threads + if ( image->machHeader()->flags & MH_HAS_TLV_DESCRIPTORS ) + image->setNeverUnload(); + // pre-fetch content of __DATA and __LINKEDIT segment for faster launches // don't do this on prebound images or if prefetching is disabled if ( !context.preFetchDisabled && !image->isPrebindable()) { @@ -838,8 +849,45 @@ void ImageLoaderMachOCompressed::doBindJustLazies(const LinkContext& context) eachLazyBind(context, &ImageLoaderMachOCompressed::bindAt); } +#if __arm__ || __arm64__ +int ImageLoaderMachOCompressed::vmAccountingSetSuspended(bool suspend, const LinkContext& context) +{ + if ( context.verboseBind ) + dyld::log("vm.footprint_suspend=%d\n", suspend); + int newValue = suspend ? 1 : 0; + int oldValue = 0; + size_t newlen = sizeof(newValue); + size_t oldlen = sizeof(oldValue); + return sysctlbyname("vm.footprint_suspend", &oldValue, &oldlen, &newValue, newlen); +} +#endif + void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handler handler) { +#if __arm__ || __arm64__ + // dyld should tell the kernel when it is doing root fix-ups + if ( !sVmAccountingDisabled ) { + if ( fInSharedCache ) { + if ( !sVmAccountingSuspended ) { + int ret = vmAccountingSetSuspended(true, context); + if ( context.verboseBind && (ret != 0) ) + dyld::log("vm.footprint_suspend => %d, errno=%d\n", ret, errno); + if ( ret == 0 ) + sVmAccountingSuspended = true; + else + sVmAccountingDisabled = true; + } + } + else if ( sVmAccountingSuspended ) { + int ret = vmAccountingSetSuspended(false, context); + if ( ret == 0 ) + sVmAccountingSuspended = false; + else if ( errno == ENOENT ) + sVmAccountingDisabled = true; + } + } +#endif + try { uint8_t type = 0; int segmentIndex = -1; diff --git a/src/ImageLoaderMachOCompressed.h b/src/ImageLoaderMachOCompressed.h index 013bb90..d042387 100644 --- a/src/ImageLoaderMachOCompressed.h +++ b/src/ImageLoaderMachOCompressed.h @@ -136,8 +136,14 @@ private: 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); - + const struct dyld_info_command* fDyldInfo; + +#if __arm__ || __arm64__ + static int vmAccountingSetSuspended(bool suspend, const LinkContext& context); + static bool sVmAccountingDisabled; // sysctl not availble + static bool sVmAccountingSuspended; // kernel is currently ignoring COWs +#endif }; diff --git a/src/ImageLoaderMegaDylib.cpp b/src/ImageLoaderMegaDylib.cpp index 2e45f21..a54625d 100644 --- a/src/ImageLoaderMegaDylib.cpp +++ b/src/ImageLoaderMegaDylib.cpp @@ -45,11 +45,14 @@ #include "ImageLoaderMachO.h" #include "mach-o/dyld_images.h" #include "dyldLibSystemInterface.h" +#include "Tracing.h" #include "dyld.h" // from dyld_gdb.cpp extern void addImagesToAllImages(uint32_t infoCount, const dyld_image_info info[]); +extern "C" int _dyld_func_lookup(const char* name, void** address); + #ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 @@ -77,9 +80,9 @@ extern void addImagesToAllImages(uint32_t infoCount, const dyld_image_info info[ #if SUPPORT_ACCELERATE_TABLES -ImageLoaderMegaDylib* ImageLoaderMegaDylib::makeImageLoaderMegaDylib(const dyld_cache_header* header, long slide, const LinkContext& context) +ImageLoaderMegaDylib* ImageLoaderMegaDylib::makeImageLoaderMegaDylib(const dyld_cache_header* header, long slide, const macho_header* mainMH, const LinkContext& context) { - return new ImageLoaderMegaDylib(header, slide, context); + return new ImageLoaderMegaDylib(header, slide, mainMH, context); } struct DATAdyld { @@ -91,7 +94,7 @@ struct DATAdyld { -ImageLoaderMegaDylib::ImageLoaderMegaDylib(const dyld_cache_header* header, long slide, const LinkContext& context) +ImageLoaderMegaDylib::ImageLoaderMegaDylib(const dyld_cache_header* header, long slide, const macho_header* mainMH, const LinkContext& context) : ImageLoader("dyld shared cache", 0), _header(header), _linkEditBias(NULL), _slide(slide), _lockArray(NULL), _lockArrayInUseCount(0) { @@ -123,7 +126,7 @@ ImageLoaderMegaDylib::ImageLoaderMegaDylib(const dyld_cache_header* header, long 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(); + dyldSection->vars.mh = mainMH; context.setNewProgramVars(dyldSection->vars); } @@ -269,6 +272,7 @@ unsigned ImageLoaderMegaDylib::findImageIndex(const LinkContext& context, const if ( strncmp(path, "@rpath/", 7) == 0 ) { std::vector rpathsFromMainExecutable; context.mainExecutable->getRPaths(context, rpathsFromMainExecutable); + rpathsFromMainExecutable.push_back("/System/Library/Frameworks/"); const char* trailingPath = &path[7]; for (const char* anRPath : rpathsFromMainExecutable) { if ( anRPath[0] != '/' ) @@ -283,17 +287,17 @@ unsigned ImageLoaderMegaDylib::findImageIndex(const LinkContext& context, const } } } - - // handle symlinks embedded in load commands - char resolvedPath[PATH_MAX]; - realpath(path, resolvedPath); - int realpathErrno = errno; - // If realpath() resolves to a path which does not exist on disk, errno is set to ENOENT - if ( (realpathErrno == ENOENT) || (realpathErrno == 0) ) { - if ( strcmp(resolvedPath, path) != 0 ) - return findImageIndex(context, resolvedPath); - } - + else { + // handle symlinks embedded in load commands + char resolvedPath[PATH_MAX]; + realpath(path, resolvedPath); + int realpathErrno = errno; + // If realpath() resolves to a path which does not exist on disk, errno is set to ENOENT + if ( (realpathErrno == ENOENT) || (realpathErrno == 0) ) { + if ( strcmp(resolvedPath, path) != 0 ) + return findImageIndex(context, resolvedPath); + } + } dyld::throwf("no cache image with name (%s)", path); } @@ -839,7 +843,7 @@ void ImageLoaderMegaDylib::recursiveSpinLockRelease(unsigned int imageIndex, mac void ImageLoaderMegaDylib::recursiveInitialization(const LinkContext& context, mach_port_t thisThread, unsigned int imageIndex, - InitializerTimingList& timingInfo) + InitializerTimingList& timingInfo, UpwardIndexes& upInits) { // Don't do any locking until libSystem.dylib is initialized, so we can malloc() the lock array bool useLock = dyld::gProcessInfo->libSystemInitialized; @@ -858,7 +862,9 @@ void ImageLoaderMegaDylib::recursiveInitialization(const LinkContext& context, m unsigned subDepIndex = _dependenciesArray[i]; // ignore upward links if ( (subDepIndex & 0x8000) == 0 ) - recursiveInitialization(context, thisThread, subDepIndex, timingInfo); + recursiveInitialization(context, thisThread, subDepIndex, timingInfo, upInits); + else + upInits.images[upInits.count++] = (subDepIndex & 0x7FFF); } // notify objc about this image @@ -875,7 +881,9 @@ void ImageLoaderMegaDylib::recursiveInitialization(const LinkContext& context, m 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); + dyld3::kdebug_trace_dyld_duration(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)func, 0, ^{ + func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + }); bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL); ranSomeInitializers = true; if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) { @@ -904,11 +912,20 @@ void ImageLoaderMegaDylib::recursiveInitialization(const LinkContext& context, m void ImageLoaderMegaDylib::recursiveInitialization(const LinkContext& context, mach_port_t thisThread, const char* pathToInitialize, - InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) + InitializerTimingList& timingInfo, UninitedUpwards&) { unsigned imageIndex; if ( hasDylib(pathToInitialize, &imageIndex) ) { - this->recursiveInitialization(context, thisThread, imageIndex, timingInfo); + UpwardIndexes upsBuffer[256]; + UpwardIndexes& ups = upsBuffer[0]; + ups.count = 0; + this->recursiveInitialization(context, thisThread, imageIndex, timingInfo, ups); + for (int i=0; i < ups.count; ++i) { + UpwardIndexes upsBuffer2[256]; + UpwardIndexes& ignoreUp = upsBuffer2[0]; + ignoreUp.count = 0; + this->recursiveInitialization(context, thisThread, ups.images[i], timingInfo, ignoreUp); + } } } @@ -947,9 +964,8 @@ unsigned ImageLoaderMegaDylib::appendImagesToNotify(dyld_image_states state, boo uint8_t targetCacheState = dyldStateToCacheState(state); if ( targetCacheState == kStateUnused ) return 0; - unsigned usedCount = 0; - for (int i=_imageCount-1; i > 0; --i) { + for (int i=_imageCount-1; i >= 0; --i) { uint16_t index = _bottomUpArray[i]; uint8_t imageState = _stateFlags[index]; if ( imageState == kStateFlagWeakBound ) @@ -994,7 +1010,17 @@ bool ImageLoaderMegaDylib::dlopenFromCache(const LinkContext& context, const cha InitializerTimingList timingInfo[_initializerCount]; timingInfo[0].count = 0; mach_port_t thisThread = mach_thread_self(); - this->recursiveInitialization(context, thisThread, imageIndex, timingInfo[0]); + UpwardIndexes upsBuffer[256]; // room for 511 dangling upward links + UpwardIndexes& ups = upsBuffer[0]; + ups.count = 0; + this->recursiveInitialization(context, thisThread, imageIndex, timingInfo[0], ups); + // make sure any upward linked dylibs were initialized + for (int i=0; i < ups.count; ++i) { + UpwardIndexes upsBuffer2[256]; + UpwardIndexes& ignoreUp = upsBuffer2[0]; + ignoreUp.count = 0; + this->recursiveInitialization(context, thisThread, ups.images[i], timingInfo[0], ignoreUp); + } mach_port_deallocate(mach_task_self(), thisThread); context.notifyBatch(dyld_image_state_initialized, false); } diff --git a/src/ImageLoaderMegaDylib.h b/src/ImageLoaderMegaDylib.h index 425ec8e..6046da6 100644 --- a/src/ImageLoaderMegaDylib.h +++ b/src/ImageLoaderMegaDylib.h @@ -39,7 +39,7 @@ // class ImageLoaderMegaDylib : public ImageLoader { public: - static ImageLoaderMegaDylib* makeImageLoaderMegaDylib(const dyld_cache_header*, long slide, const LinkContext&); + static ImageLoaderMegaDylib* makeImageLoaderMegaDylib(const dyld_cache_header*, long slide, const macho_header* mainMH, const LinkContext&); virtual ~ImageLoaderMegaDylib(); @@ -185,7 +185,13 @@ protected: virtual void setWeakSymbolsBound(unsigned index); private: - ImageLoaderMegaDylib(const dyld_cache_header*, long slide, const LinkContext&); + ImageLoaderMegaDylib(const dyld_cache_header*, long slide, const macho_header* mainMH, const LinkContext&); + + struct UpwardIndexes + { + uint16_t count; + uint16_t images[1]; + }; const macho_header* getIndexedMachHeader(unsigned index) const; const uint8_t* getIndexedTrie(unsigned index, uint32_t& trieSize) const; @@ -213,7 +219,7 @@ private: 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); + InitializerTimingList& timingInfo, UpwardIndexes&); void recursiveSpinLockAcquire(unsigned int imageIndex, mach_port_t thisThread); void recursiveSpinLockRelease(unsigned int imageIndex, mach_port_t thisThread); diff --git a/src/dyld.cpp b/src/dyld.cpp index 4aff26a..c98a9e3 100644 --- a/src/dyld.cpp +++ b/src/dyld.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -61,7 +62,11 @@ #include #include +extern "C" int __fork(); + #include +#include +#include #ifndef CPU_SUBTYPE_ARM_V5TEJ #define CPU_SUBTYPE_ARM_V5TEJ ((cpu_subtype_t) 7) @@ -89,22 +94,21 @@ #define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t) 8) #endif +#ifndef CPU_SUBTYPE_ARM64_E + #define CPU_SUBTYPE_ARM64_E 2 +#endif + #ifndef VM_PROT_SLIDE #define VM_PROT_SLIDE 0x20 #endif -#include -#include - #include "mach-o/dyld_gdb.h" #include "dyld.h" #include "ImageLoader.h" #include "ImageLoaderMachO.h" #include "dyldLibSystemInterface.h" -#if DYLD_SHARED_CACHE_SUPPORT #include "dyld_cache_format.h" -#endif #include "dyld_process_info_internal.h" #include #if TARGET_IPHONE_SIMULATOR @@ -124,13 +128,27 @@ #include "dyldSyscallInterface.h" #endif +#include "LaunchCache.h" +#include "libdyldEntryVector.h" +#include "MachOParser.h" +#include "Loading.h" +#include "DyldSharedCache.h" +#include "SharedCacheRuntime.h" +#include "StringUtils.h" +#include "Tracing.h" +#include "DyldCacheParser.h" + +extern "C" { + #include "closuredProtocol.h" +} + // not libc header for send() syscall interface extern "C" ssize_t __sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); // ARM and x86_64 are the only architecture that use cpu-sub-types -#define CPU_SUBTYPES_SUPPORTED ((__arm__ || __x86_64__) && !TARGET_IPHONE_SIMULATOR) +#define CPU_SUBTYPES_SUPPORTED ((__arm__ || __arm64__ || __x86_64__) && !TARGET_IPHONE_SIMULATOR) #if __LP64__ #define LC_SEGMENT_COMMAND LC_SEGMENT_64 @@ -213,8 +231,7 @@ struct EnvironmentVariables { bool DYLD_PRINT_ENV; bool DYLD_DISABLE_DOFS; bool DYLD_PRINT_CS_NOTIFICATIONS; - // DYLD_SHARED_CACHE_DONT_VALIDATE ==> sSharedCacheIgnoreInodeAndTimeStamp - // DYLD_SHARED_CACHE_DIR ==> sSharedCacheDir + // DYLD_SHARED_CACHE_DIR ==> sSharedCacheOverrideDir // DYLD_ROOT_PATH ==> gLinkContext.rootPaths // DYLD_IMAGE_SUFFIX ==> gLinkContext.imageSuffix // DYLD_PRINT_OPTS ==> gLinkContext.verboseOpts @@ -248,6 +265,7 @@ enum EnvVarMode { envNone, envPrintOnly, envAll }; static const char* sExecPath = NULL; static const char* sExecShortName = NULL; static const macho_header* sMainExecutableMachHeader = NULL; +static uintptr_t sMainExecutableSlide = 0; #if CPU_SUBTYPES_SUPPORTED static cpu_type_t sHostCPU; static cpu_subtype_t sHostCPUsubtype; @@ -269,26 +287,17 @@ static EnvironmentVariables sEnv; #if __MAC_OS_X_VERSION_MIN_REQUIRED static const char* sFrameworkFallbackPaths[] = { "$HOME/Library/Frameworks", "/Library/Frameworks", "/Network/Library/Frameworks", "/System/Library/Frameworks", NULL }; static const char* sLibraryFallbackPaths[] = { "$HOME/lib", "/usr/local/lib", "/usr/lib", NULL }; +static const char* sRestrictedFrameworkFallbackPaths[] = { "/System/Library/Frameworks", NULL }; +static const char* sRestrictedLibraryFallbackPaths[] = { "/usr/lib", NULL }; #else 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 -static const dyld_cache_header* sSharedCache = NULL; -static long sSharedCacheSlide = 0; -static bool sSharedCacheIgnoreInodeAndTimeStamp = false; - bool gSharedCacheOverridden = false; -#if __IPHONE_OS_VERSION_MIN_REQUIRED - static const char* sSharedCacheDir = IPHONE_DYLD_SHARED_CACHE_DIR; - #define ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE 1024 -#else - static const char* sSharedCacheDir = MACOSX_DYLD_SHARED_CACHE_DIR; -#endif -#endif +static dyld3::SharedCacheLoadInfo sSharedCacheLoadInfo; +static const char* sSharedCacheOverrideDir; + bool gSharedCacheOverridden = false; ImageLoader::LinkContext gLinkContext; bool gLogAPIs = false; #if SUPPORT_ACCELERATE_TABLES @@ -303,7 +312,7 @@ static std::vector sDylibOverrides; static int sLogSocket = -1; #endif static bool sFrameworksFoundAsDylibs = false; -#if __x86_64__ && DYLD_SHARED_CACHE_SUPPORT +#if __x86_64__ && !TARGET_IPHONE_SIMULATOR static bool sHaswell = false; #endif static std::vector sDynamicReferences; @@ -322,12 +331,15 @@ static bool sForceStderr = false; #endif - #if SUPPORT_ACCELERATE_TABLES static ImageLoaderMegaDylib* sAllCacheImagesProxy = NULL; static bool sDisableAcceleratorTables = false; #endif +bool gUseDyld3 = false; +static bool sSkipMain = false; +static bool sEnableClosures = false; + // // 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 @@ -557,7 +569,8 @@ void warn(const char* format, ...) va_end(list); } - +#else + extern void vlog(const char* format, va_list list); #endif // !TARGET_IPHONE_SIMULATOR @@ -755,16 +768,19 @@ static void notifySingleFromCache(dyld_image_states state, const mach_header* mh #endif static mach_port_t sNotifyReplyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; - +static bool sZombieNotifiers[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; static void notifyMonitoringDyld(bool unloading, unsigned portSlot, unsigned imageCount, const dyld_image_info infos[]) { + if ( sZombieNotifiers[portSlot] ) + return; + 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 + unsigned totalSize = (sizeof(dyld_process_info_notify_header) + MAX_TRAILER_SIZE + 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. @@ -822,7 +838,7 @@ static void notifyMonitoringDyld(bool unloading, unsigned portSlot, unsigned ima 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); + kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_RCV_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[portSlot], 5000, 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 @@ -832,28 +848,35 @@ static void notifyMonitoringDyld(bool unloading, unsigned portSlot, unsigned ima dyld::gProcessInfo->notifyPorts[portSlot] = 0; sNotifyReplyPorts[portSlot] = 0; } + else if ( sendResult == MACH_RCV_TIMED_OUT ) { + // client took too long, ignore him from now on + sZombieNotifiers[portSlot] = true; + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]); + sNotifyReplyPorts[portSlot] = 0; + } } static void notifyMonitoringDyldMain() { for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { - if ( dyld::gProcessInfo->notifyPorts[slot] != 0 ) { + if ( (dyld::gProcessInfo->notifyPorts[slot] != 0 ) && !sZombieNotifiers[slot] ) { if ( sNotifyReplyPorts[slot] == 0 ) { if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[slot]) ) mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[slot], sNotifyReplyPorts[slot], MACH_MSG_TYPE_MAKE_SEND); //dyld::log("allocated reply port %d\n", sNotifyReplyPorts[slot]); } //dyld::log("found port to send to\n"); - mach_msg_header_t h; - 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 = DYLD_PROCESS_INFO_NOTIFY_MAIN_ID; - h.msgh_local_port = sNotifyReplyPorts[slot]; - h.msgh_remote_port = dyld::gProcessInfo->notifyPorts[slot]; - h.msgh_reserved = 0; - h.msgh_size = (mach_msg_size_t)sizeof(mach_msg_header_t); - //dyld::log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", slot, dyld::gProcessInfo->notifyPorts[slot], h.msgh_size, sNotifyReplyPorts[slot], 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[slot], 100, MACH_PORT_NULL); - //dyld::log("send result = 0x%X, msg_id=%d, msg_size=%d\n", sendResult, h.msgh_id, h.msgh_size); + uint8_t messageBuffer[sizeof(mach_msg_header_t) + MAX_TRAILER_SIZE]; + mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer; + 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 = DYLD_PROCESS_INFO_NOTIFY_MAIN_ID; + h->msgh_local_port = sNotifyReplyPorts[slot]; + h->msgh_remote_port = dyld::gProcessInfo->notifyPorts[slot]; + h->msgh_reserved = 0; + h->msgh_size = (mach_msg_size_t)sizeof(messageBuffer); + //dyld::log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", slot, dyld::gProcessInfo->notifyPorts[slot], h->msgh_size, sNotifyReplyPorts[slot], h->msgh_id); + kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_RCV_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[slot], 5000, 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[slot], sNotifyReplyPorts[slot]); @@ -862,44 +885,24 @@ static void notifyMonitoringDyldMain() dyld::gProcessInfo->notifyPorts[slot] = 0; sNotifyReplyPorts[slot] = 0; } + else if ( sendResult == MACH_RCV_TIMED_OUT ) { + // client took too long, ignore him from now on + sZombieNotifiers[slot] = true; + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); + sNotifyReplyPorts[slot] = 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) { +void notifyKernel(const ImageLoader& image, bool loading) { if ( !image.inSharedCache() ) { + uint32_t baseCode = loading ? DBG_DYLD_UUID_MAP_A : DBG_DYLD_UUID_UNMAP_A; + uuid_t uuid; 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++; + image.getUUID(uuid); + dyld3::kdebug_trace_dyld_image(baseCode, (const uuid_t *)&uuid, *(fsobj_id_t*)&inode, {{ image.getDevice(), 0 }}, image.machHeader()); } - 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) @@ -967,6 +970,7 @@ static void notifySingle(dyld_image_states state, const ImageLoader* image, Imag //dyld::log("deallocated reply port %d\n", sNotifyReplyPorts[slot]); mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); sNotifyReplyPorts[slot] = 0; + sZombieNotifiers[slot] = false; } } } @@ -1022,9 +1026,6 @@ static int imageSorter(const void* l, const void* r) 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); - 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(); @@ -1056,12 +1057,11 @@ static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image 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); + notifyKernel(*image, true); // special case for add_image hook if ( state == dyld_image_state_bound ) notifyAddImageCallbacks(image); } - flushKernelNotifications(true, true, kernelInfos, kernelInfoCount); } #if SUPPORT_ACCELERATE_TABLES if ( sAllCacheImagesProxy != NULL ) { @@ -1070,7 +1070,7 @@ static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image if ( state == dyld_image_state_bound ) { for (ImageCallback callback : sAddImageCallbacks) { for (unsigned i=0; i < cacheCount; ++i) - (*callback)(infos[imageCount+i].imageLoadAddress, sSharedCacheSlide); + (*callback)(infos[imageCount+i].imageLoadAddress, sSharedCacheLoadInfo.slide); } } imageCount += cacheCount; @@ -1792,6 +1792,8 @@ static void paths_dump(const char **paths) } #endif + + static void printOptions(const char* argv[]) { uint32_t i = 0; @@ -1952,14 +1954,13 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch dyld::warn("unknown option to DYLD_SHARED_REGION. Valid options are: use, private, avoid\n"); } } -#if DYLD_SHARED_CACHE_SUPPORT else if ( (strcmp(key, "DYLD_SHARED_CACHE_DIR") == 0) && !sSafeMode ) { - sSharedCacheDir = value; + sSharedCacheOverrideDir = value; } - else if ( (strcmp(key, "DYLD_SHARED_CACHE_DONT_VALIDATE") == 0) && !sSafeMode ) { - sSharedCacheIgnoreInodeAndTimeStamp = true; + else if ( strcmp(key, "DYLD_USE_CLOSURES") == 0 ) { + if ( dyld3::loader::internalInstall() ) + sEnableClosures = true; } -#endif else if ( strcmp(key, "DYLD_IGNORE_PREBINDING") == 0 ) { if ( strcmp(value, "all") == 0 ) { gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding; @@ -2002,6 +2003,10 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch dyld::log("dyld: could not open DYLD_PRINT_TO_FILE='%s', errno=%d\n", value, errno); } } + else if ( (strcmp(key, "DYLD_SKIP_MAIN") == 0)) { + if ( dyld3::loader::internalInstall() ) + sSkipMain = true; + } #endif else { dyld::warn("unknown environment variable: %s\n", key); @@ -2101,10 +2106,17 @@ static void pruneEnvironmentVariables(const char* envp[], const char*** applep) checkLoadCommandEnvironmentVariables(); #endif + // Are we testing dyld on an internal config? + if ( _simple_getenv(envp, "DYLD_SKIP_MAIN") != NULL ) { + if ( dyld3::loader::internalInstall() ) + sSkipMain = true; + } + // delete all DYLD_* and LD_LIBRARY_PATH environment variables int removedCount = 0; const char** d = envp; for(const char** s = envp; *s != NULL; s++) { + if ( (strncmp(*s, "DYLD_", 5) != 0) && (strncmp(*s, "LD_LIBRARY_PATH=", 16) != 0) ) { *d++ = *s; } @@ -2212,7 +2224,7 @@ static void checkEnvironmentVariables(const char* envp[]) #endif } -#if __x86_64__ && DYLD_SHARED_CACHE_SUPPORT +#if __x86_64__ && !TARGET_IPHONE_SIMULATOR static bool isGCProgram(const macho_header* mh, uintptr_t slide) { const uint32_t cmd_count = mh->ncmds; @@ -2260,6 +2272,12 @@ static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExec #elif __ARM_ARCH_7S__ sHostCPU = CPU_TYPE_ARM; sHostCPUsubtype = CPU_SUBTYPE_ARM_V7S; +#elif __arm64e__ + sHostCPU = CPU_TYPE_ARM64; + sHostCPUsubtype = CPU_SUBTYPE_ARM64_E; +#elif __arm64__ + sHostCPU = CPU_TYPE_ARM64; + sHostCPUsubtype = CPU_SUBTYPE_ARM64_V8; #else struct host_basic_info info; mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; @@ -2271,7 +2289,10 @@ 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 DYLD_SHARED_CACHE_SUPPORT + // host_info returns CPU_TYPE_I386 even for x86_64. Override that here so that + // we don't need to mask the cpu type later. + sHostCPU = CPU_TYPE_X86_64; + #if !TARGET_IPHONE_SIMULATOR sHaswell = (sHostCPUsubtype == CPU_SUBTYPE_X86_64_H); // x86_64h: Fall back to the x86_64 slice if an app requires GC. if ( sHaswell ) { @@ -2288,12 +2309,21 @@ static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExec #endif } -static void checkSharedRegionDisable() +static void checkSharedRegionDisable(const mach_header* mainExecutableMH) { #if __MAC_OS_X_VERSION_MIN_REQUIRED - // if main executable has segments that overlap the shared region, + // if main executable has segments that overlap the shared region, // then disable using the shared region - if ( sMainExecutable->overlapsWithAddressRange((void*)(uintptr_t)SHARED_REGION_BASE, (void*)(uintptr_t)(SHARED_REGION_BASE + SHARED_REGION_SIZE)) ) { + dyld3::MachOParser parser(mainExecutableMH); + uintptr_t slide = parser.getSlide(); + dyld3::launch_cache::MemoryRange sharedRegion = { (void*)(long)(SHARED_REGION_BASE), SHARED_REGION_SIZE }; + __block bool disable = false; + parser.forEachSegment(^(const char *segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool &stop) { + dyld3::launch_cache::MemoryRange segRegion = { (void*)(long)(vmAddr+slide), vmSize }; + if ( segRegion.intersects(sharedRegion) ) + disable = true; + }); + if ( disable ) { gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; if ( gLinkContext.verboseMapping ) dyld::warn("disabling shared region because main executable overlaps\n"); @@ -2305,7 +2335,7 @@ static void checkSharedRegionDisable() } #endif #endif - // iPhoneOS cannot run without shared region + // iOS cannot run without shared region } bool validImage(const ImageLoader* possibleImage) @@ -2503,6 +2533,21 @@ static const cpu_subtype_t kARM[kARM_RowCount][9] = { }; #endif +#if __arm64__ +// +// ARM64 sub-type lists +// +const int kARM64_RowCount = 2; +static const cpu_subtype_t kARM64[kARM64_RowCount][4] = { + + // armv64e can run: 64e, 64 + { CPU_SUBTYPE_ARM64_E, CPU_SUBTYPE_ARM64_V8, CPU_SUBTYPE_ARM64_ALL, CPU_SUBTYPE_END_OF_LIST }, + + // armv64 can run: 64 + { CPU_SUBTYPE_ARM64_V8, CPU_SUBTYPE_ARM64_ALL, CPU_SUBTYPE_END_OF_LIST }, +}; +#endif + #if __x86_64__ // // x86_64 sub-type lists @@ -2511,10 +2556,10 @@ const int kX86_64_RowCount = 2; static const cpu_subtype_t kX86_64[kX86_64_RowCount][5] = { // x86_64h can run: x86_64h, x86_64h(lib), x86_64(lib), and x86_64 - { CPU_SUBTYPE_X86_64_H, CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_H, CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_END_OF_LIST }, + { CPU_SUBTYPE_X86_64_H, (cpu_subtype_t)(CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_H), (cpu_subtype_t)(CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_ALL), CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_END_OF_LIST }, // x86_64 can run: x86_64(lib) and x86_64 - { CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_END_OF_LIST }, + { CPU_SUBTYPE_X86_64_ALL, (cpu_subtype_t)(CPU_SUBTYPE_LIB64|CPU_SUBTYPE_X86_64_ALL), CPU_SUBTYPE_END_OF_LIST }, }; #endif @@ -2532,6 +2577,14 @@ static const cpu_subtype_t* findCPUSubtypeList(cpu_type_t cpu, cpu_subtype_t sub } break; #endif +#if __arm64__ + case CPU_TYPE_ARM64: + for (int i=0; i < kARM64_RowCount ; ++i) { + if ( kARM64[i][0] == subtype ) + return kARM64[i]; + } + break; +#endif #if __x86_64__ case CPU_TYPE_X86_64: for (int i=0; i < kX86_64_RowCount ; ++i) { @@ -2595,6 +2648,15 @@ static bool fatFindRunsOnAllCPUs(cpu_type_t cpu, const fat_header* fh, uint64_t* } break; #endif +#if __arm64__ + case CPU_TYPE_ARM64: + if ( (cpu_subtype_t)OSSwapBigToHostInt32(archs[i].cpusubtype) == CPU_SUBTYPE_ARM64_ALL ) { + *offset = OSSwapBigToHostInt32(archs[i].offset); + *len = OSSwapBigToHostInt32(archs[i].size); + return true; + } + break; +#endif #if __x86_64__ case CPU_TYPE_X86_64: if ( (cpu_subtype_t)OSSwapBigToHostInt32(archs[i].cpusubtype) == CPU_SUBTYPE_X86_64_ALL ) { @@ -2687,7 +2749,7 @@ static bool fatFindBest(const fat_header* fh, uint64_t* offset, uint64_t* len) const cpu_type_t cpu = sMainExecutableMachHeader->cputype; // We only know the subtype to use if the main executable cpu type matches the host - if ( (cpu & CPU_TYPE_MASK) == sHostCPU ) { + if ( cpu == sHostCPU ) { // get preference ordered list of subtypes const cpu_subtype_t* subTypePreferenceList = findCPUSubtypeList(cpu, sHostCPUsubtype); @@ -2733,7 +2795,7 @@ bool isCompatibleMachO(const uint8_t* firstPage, const char* path) const mach_header* mh = (mach_header*)firstPage; if ( mh->magic == sMainExecutableMachHeader->magic ) { if ( mh->cputype == sMainExecutableMachHeader->cputype ) { - if ( (mh->cputype & CPU_TYPE_MASK) == sHostCPU ) { + if ( mh->cputype == sHostCPU ) { // get preference ordered list of subtypes that this machine can use const cpu_subtype_t* subTypePreferenceList = findCPUSubtypeList(mh->cputype, sHostCPUsubtype); if ( subTypePreferenceList != NULL ) { @@ -2789,104 +2851,47 @@ static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uint throw "main executable not a known format"; } -#if DYLD_SHARED_CACHE_SUPPORT - -#if __IPHONE_OS_VERSION_MIN_REQUIRED +#if SUPPORT_ACCELERATE_TABLES static bool dylibsCanOverrideCache() { - uint32_t devFlags = *((uint32_t*)_COMM_PAGE_DEV_FIRM); - if ( (devFlags & 1) == 0 ) + if ( !dyld3::loader::internalInstall() ) return false; - return ( (sSharedCache != NULL) && (sSharedCache->cacheType == kDyldSharedCacheTypeDevelopment) ); + return ( (sSharedCacheLoadInfo.loadAddress != nullptr) && (sSharedCacheLoadInfo.loadAddress->header.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) +const void* imMemorySharedCacheHeader() { - if ( sSharedCache != NULL ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - // Mac OS X always requires inode/mtime to valid cache - // if stat() not done yet, do it now - struct stat statb; - if ( stat_buf == NULL ) { - if ( my_stat(path, &statb) == -1 ) - return false; - stat_buf = &statb; - } -#endif -#if __IPHONE_OS_VERSION_MIN_REQUIRED - uint64_t hash = 0; - for (const char* s=path; *s != '\0'; ++s) - hash += hash*4 + *s; -#endif + return sSharedCacheLoadInfo.loadAddress; +} - // walk shared cache to see if there is a cached image that matches the inode/mtime/path desired - const dyld_cache_image_info* const start = (dyld_cache_image_info*)((uint8_t*)sSharedCache + sSharedCache->imagesOffset); - const dyld_cache_image_info* const end = &start[sSharedCache->imagesCount]; -#if __IPHONE_OS_VERSION_MIN_REQUIRED - const bool cacheHasHashInfo = (start->modTime == 0); -#endif - for( const dyld_cache_image_info* p = start; p != end; ++p) { -#if __IPHONE_OS_VERSION_MIN_REQUIRED - // just check path - const char* aPath = (char*)sSharedCache + p->pathFileOffset; - if ( cacheHasHashInfo && (p->inode != hash) ) - continue; - if ( strcmp(path, aPath) == 0 ) { - // found image in cache - *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 - // check mtime and inode first because it is fast - bool inodeMatch = ( ((time_t)p->modTime == stat_buf->st_mtime) && ((ino_t)p->inode == stat_buf->st_ino) ); - if ( searchByPath || sSharedCacheIgnoreInodeAndTimeStamp || inodeMatch ) { - // mod-time and inode match an image in the shared cache, now check path - const char* aPath = (char*)sSharedCache + p->pathFileOffset; - bool cacheHit = (strcmp(path, aPath) == 0); - if ( inodeMatch && !cacheHit ) { - // path does not match install name of dylib in cache, but inode and mtime does match - // perhaps path is a symlink to the cached dylib - struct stat pathInCacheStatBuf; - if ( my_stat(aPath, &pathInCacheStatBuf) != -1 ) - cacheHit = ( (pathInCacheStatBuf.st_dev == stat_buf->st_dev) && (pathInCacheStatBuf.st_ino == stat_buf->st_ino) ); - } - 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=%s\n", - // *mh, p->address, sSharedCacheSlide, aPath); - *pathInCache = aPath; - *slide = sSharedCacheSlide; - return true; - } - } -#endif - } + +const char* getStandardSharedCacheFilePath() +{ + if ( sSharedCacheLoadInfo.loadAddress != nullptr ) + return sSharedCacheLoadInfo.path; + else + return nullptr; +} + + +static bool findInSharedCacheImage(const char* path, bool searchByPath, const struct stat* stat_buf, const macho_header** mh, const char** pathInCache, long* slide) +{ + dyld3::SharedCacheFindDylibResults results; + if ( dyld3::findInSharedCacheImage(sSharedCacheLoadInfo, path, &results) ) { + *mh = (macho_header*)results.mhInCache; + *pathInCache = results.pathInCache; + *slide = results.slideInCache; + return true; } return false; } bool inSharedCache(const char* path) { - const macho_header* mhInCache; - const char* pathInCache; - long slide; - return findInSharedCacheImage(path, true, NULL, &mhInCache, &pathInCache, &slide); + return dyld3::pathIsInSharedCacheImage(sSharedCacheLoadInfo, path); } -#endif static ImageLoader* checkandAddImage(ImageLoader* image, const LoadContext& context) { @@ -2978,7 +2983,7 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* // min mach-o file is 4K if ( fileLength < 4096 ) { - if ( pread(fd, firstPages, fileLength, 0) != (ssize_t)fileLength ) + if ( pread(fd, firstPages, (size_t)fileLength, 0) != (ssize_t)fileLength ) throwf("pread of short file failed: %d", errno); shortPage = true; } @@ -3104,143 +3109,92 @@ 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, 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 ) { - 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 ) - return image; - - // do nothing if not already loaded and if RTLD_NOLOAD or NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED - if ( context.dontLoad ) - return NULL; - -#if DYLD_SHARED_CACHE_SUPPORT - // see if this image is in shared cache - const macho_header* mhInCache; - const char* pathInCache; - long slideInCache; - if ( findInSharedCacheImage(path, false, &stat_buf, &mhInCache, &pathInCache, &slideInCache) ) { - image = ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext); - return checkandAddImage(image, context); - } -#endif - // file exists and is not in dyld shared cache, so open it - return loadPhase5open(path, context, stat_buf, exceptions); -} -#endif // __MAC_OS_X_VERSION_MIN_REQUIRED - - - -#if __IPHONE_OS_VERSION_MIN_REQUIRED -static ImageLoader* loadPhase5stat(const char* path, const LoadContext& context, struct stat* stat_buf, - int* statErrNo, bool* imageFound, std::vector* exceptions) -{ - ImageLoader* image = NULL; - *imageFound = false; - *statErrNo = 0; - if ( my_stat(path, stat_buf) == 0 ) { - // in case image was renamed or found via symlinks, check for inode match - image = findLoadedImage(*stat_buf); - if ( image != NULL ) { - *imageFound = true; - return image; - } - // do nothing if not already loaded and if RTLD_NOLOAD - if ( context.dontLoad ) { - *imageFound = true; - return NULL; - } - image = loadPhase5open(path, context, *stat_buf, exceptions); - if ( image != NULL ) { - *imageFound = true; - return image; - } - } - else { - *statErrNo = errno; - } - return NULL; -} // try to open file 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; - bool imageFound; - int statErrNo; - ImageLoader* image; -#if DYLD_SHARED_CACHE_SUPPORT - #if SUPPORT_ACCELERATE_TABLES +#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 ) - return image; - } - // see if this image is in shared cache - const macho_header* mhInCache; - const char* pathInCache; - long slideInCache; - if ( findInSharedCacheImage(path, true, NULL, &mhInCache, &pathInCache, &slideInCache) ) { +#endif +#if TARGET_IPHONE_SIMULATOR + // in simulators, 'path' has DYLD_ROOT_PATH prepended, but cache index does not have the prefix, so use orgPath + const char* pathToFindInCache = orgPath; +#else + const char* pathToFindInCache = path; +#endif + uint statErrNo; + struct stat statBuf; + bool didStat = false; + bool existsOnDisk; + dyld3::SharedCacheFindDylibResults shareCacheResults; + if ( dyld3::findInSharedCacheImage(sSharedCacheLoadInfo, pathToFindInCache, &shareCacheResults) ) { // see if this image in the cache was already loaded via a different path for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); ++it) { ImageLoader* anImage = *it; - if ( (const macho_header*)anImage->machHeader() == mhInCache ) + if ( (const mach_header*)anImage->machHeader() == shareCacheResults.mhInCache ) return anImage; } - // do nothing if not already loaded and if RTLD_NOLOAD + // if RTLD_NOLOAD, do nothing if not already loaded if ( context.dontLoad ) return NULL; - // nope, so instantiate a new image from dyld shared cache - // zero out stat buffer so mtime, etc are zero for items from the shared cache - bzero(&stat_buf, sizeof(stat_buf)); - image = ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext); - return checkandAddImage(image, context); + bool useCache = false; + if ( shareCacheResults.imageData == nullptr ) { + // HACK to support old caches + existsOnDisk = ( my_stat(path, &statBuf) == 0 ); + didStat = true; + statErrNo = errno; + useCache = !existsOnDisk; + } + else { + // zero out stat buffer so mtime, etc are zero for items from the shared cache + bzero(&statBuf, sizeof(statBuf)); + dyld3::launch_cache::Image image(shareCacheResults.imageData); + if ( image.overridableDylib() ) { + existsOnDisk = ( my_stat(path, &statBuf) == 0 ); + didStat = true; + statErrNo = errno; + if ( sSharedCacheLoadInfo.loadAddress->header.dylibsExpectedOnDisk ) { + if ( (image.fileModTime() == statBuf.st_mtime) && (image.fileINode() == statBuf.st_ino) ) + useCache = true; + } + else { + if ( !existsOnDisk ) + useCache = true; + } + } + else { + useCache = true; + } + } + if ( useCache ) { + ImageLoader* imageLoader = ImageLoaderMachO::instantiateFromCache((macho_header*)shareCacheResults.mhInCache, shareCacheResults.pathInCache, shareCacheResults.slideInCache, statBuf, gLinkContext); + return checkandAddImage(imageLoader, context); + } } - - 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 ) - return image; + + // not in cache or cache not usable + if ( !didStat ) { + existsOnDisk = ( my_stat(path, &statBuf) == 0 ); + statErrNo = errno; } -#else - image = loadPhase5stat(path, context, &stat_buf, &statErrNo, &imageFound, exceptions); - if ( imageFound ) - return image; -#endif + if ( existsOnDisk ) { + // in case image was renamed or found via symlinks, check for inode match + ImageLoader* imageLoader = findLoadedImage(statBuf); + if ( imageLoader != NULL ) + return imageLoader; + // do nothing if not already loaded and if RTLD_NOLOAD + if ( context.dontLoad ) + return NULL; + // try opening file + imageLoader = loadPhase5open(path, context, statBuf, exceptions); + if ( imageLoader != NULL ) + return imageLoader; + } + // just return NULL if file not found, but record any other errors if ( (statErrNo != ENOENT) && (statErrNo != 0) ) { if ( (statErrNo == EPERM) && sandboxBlockedStat(path) ) @@ -3250,8 +3204,6 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const } return NULL; } -#endif // __IPHONE_OS_VERSION_MIN_REQUIRED - // look for path match with existing loaded images static ImageLoader* loadPhase5check(const char* path, const char* orgPath, const LoadContext& context) @@ -3581,15 +3533,13 @@ static ImageLoader* loadPhase0(const char* path, const char* orgPath, const Load return loadPhase1(path, orgPath, context, cacheIndex, exceptions); } -#if DYLD_SHARED_CACHE_SUPPORT - static bool cacheablePath(const char* path) { - if (strncmp(path, "/usr/lib/", 9) == 0) - return true; - if (strncmp(path, "/System/Library/", 16) == 0) - return true; - return false; - } -#endif +static bool cacheablePath(const char* path) { + if (strncmp(path, "/usr/lib/", 9) == 0) + return true; + if (strncmp(path, "/System/Library/", 16) == 0) + return true; + return false; +} // // Given all the DYLD_ environment variables, the general case for loading libraries @@ -3641,13 +3591,11 @@ ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheI for (std::vector::iterator it = exceptions.begin(); it != exceptions.end(); ++it) { free((void*)(*it)); } -#if DYLD_SHARED_CACHE_SUPPORT // if loaded image is not from cache, but original path is in cache // set gSharedCacheOverridden flag to disable some ObjC optimizations if ( !gSharedCacheOverridden && !image->inSharedCache() && image->isDylib() && cacheablePath(path) && inSharedCache(path) ) { gSharedCacheOverridden = true; } -#endif return image; } else if ( exceptions.size() == 0 ) { @@ -3676,728 +3624,84 @@ ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheI -#if DYLD_SHARED_CACHE_SUPPORT - -#if __i386__ - #define ARCH_NAME "i386" - #define ARCH_CACHE_MAGIC "dyld_v1 i386" -#elif __x86_64__ - #define ARCH_NAME "x86_64" - #define ARCH_CACHE_MAGIC "dyld_v1 x86_64" - #define ARCH_NAME_H "x86_64h" - #define ARCH_CACHE_MAGIC_H "dyld_v1 x86_64h" -#elif __ARM_ARCH_5TEJ__ - #define ARCH_NAME "armv5" - #define ARCH_CACHE_MAGIC "dyld_v1 armv5" -#elif __ARM_ARCH_6K__ - #define ARCH_NAME "armv6" - #define ARCH_CACHE_MAGIC "dyld_v1 armv6" -#elif __ARM_ARCH_7F__ - #define ARCH_NAME "armv7f" - #define ARCH_CACHE_MAGIC "dyld_v1 armv7f" -#elif __ARM_ARCH_7K__ - #define ARCH_NAME "armv7k" - #define ARCH_CACHE_MAGIC "dyld_v1 armv7k" -#elif __ARM_ARCH_7A__ - #define ARCH_NAME "armv7" - #define ARCH_CACHE_MAGIC "dyld_v1 armv7" -#elif __ARM_ARCH_7S__ - #define ARCH_NAME "armv7s" - #define ARCH_CACHE_MAGIC "dyld_v1 armv7s" -#elif __arm64__ - #define ARCH_NAME "arm64" - #define ARCH_CACHE_MAGIC "dyld_v1 arm64" +static void mapSharedCache() +{ + dyld3::SharedCacheOptions opts; + opts.cacheDirOverride = sSharedCacheOverrideDir; + opts.forcePrivate = (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion); +#if __x86_64__ && !TARGET_IPHONE_SIMULATOR + opts.useHaswell = sHaswell; +#else + opts.useHaswell = false; #endif + opts.verbose = gLinkContext.verboseMapping; + loadDyldCache(opts, &sSharedCacheLoadInfo); + // update global state + if ( sSharedCacheLoadInfo.loadAddress != nullptr ) { + dyld::gProcessInfo->processDetachedFromSharedRegion = opts.forcePrivate; + dyld::gProcessInfo->sharedCacheSlide = sSharedCacheLoadInfo.slide; + dyld::gProcessInfo->sharedCacheBaseAddress = (unsigned long)sSharedCacheLoadInfo.loadAddress; + sSharedCacheLoadInfo.loadAddress->getUUID(dyld::gProcessInfo->sharedCacheUUID); + dyld3::kdebug_trace_dyld_image(DBG_DYLD_UUID_SHARED_CACHE_A, (const uuid_t *)&dyld::gProcessInfo->sharedCacheUUID[0], {0,0}, {{ 0, 0 }}, (const mach_header *)sSharedCacheLoadInfo.loadAddress); + } -static int __attribute__((noinline)) _shared_region_check_np(uint64_t* start_address) -{ - if ( gLinkContext.sharedRegionMode == ImageLoader::kUseSharedRegion ) - return syscall(294, start_address); - return -1; +//#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR +// RAM disk booting does not have shared cache yet +// Don't make lack of a shared cache fatal in that case +// if ( sSharedCacheLoadInfo.loadAddress == nullptr ) { +// if ( sSharedCacheLoadInfo.errorMessage != nullptr ) +// halt(sSharedCacheLoadInfo.errorMessage); +// else +// halt("error loading dyld shared cache"); +// } +//#endif } -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; - } +// create when NSLinkModule is called for a second time on a bundle +ImageLoader* cloneImage(ImageLoader* image) +{ + // open file (automagically closed when this function exits) + FileOpener file(image->getPath()); + + struct stat stat_buf; + if ( fstat(file.getFileDescriptor(), &stat_buf) == -1) + throw "stat error"; + + dyld::LoadContext context; + context.useSearchPaths = false; + context.useFallbackPaths = false; + context.useLdLibraryPath = false; + context.implicitRPath = false; + context.matchByInstallName = false; + context.dontLoad = false; + context.mustBeBundle = true; + context.mustBeDylib = false; + context.canBePIE = false; + context.origin = NULL; + context.rpath = NULL; + return loadPhase6(file.getFileDescriptor(), stat_buf, image->getPath(), context); } -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) +ImageLoader* loadFromMemory(const uint8_t* mem, uint64_t len, const char* moduleName) { - // register code signature blob for whole dyld cache - 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 ) { -#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); - } - - // remove the shared region sub-map - vm_deallocate(mach_task_self(), (vm_address_t)SHARED_REGION_BASE, SHARED_REGION_SIZE); - - // notify gdb or other lurkers that this process is no longer using the shared region - dyld::gProcessInfo->processDetachedFromSharedRegion = true; - - // map cache just for this process with mmap() - const shared_file_mapping_np* const start = mappings; - const shared_file_mapping_np* const end = &mappings[count]; - for (const shared_file_mapping_np* p = start; p < end; ++p ) { - void* mmapAddress = (void*)(uintptr_t)(p->sfm_address); - size_t size = p->sfm_size; - //dyld::log("dyld: mapping address %p with size 0x%08lX\n", mmapAddress, size); - int protection = 0; - if ( p->sfm_init_prot & VM_PROT_EXECUTE ) - protection |= PROT_EXEC; - if ( p->sfm_init_prot & VM_PROT_READ ) - protection |= PROT_READ; - if ( p->sfm_init_prot & VM_PROT_WRITE ) - protection |= PROT_WRITE; - off_t offset = p->sfm_file_offset; - if ( mmap(mmapAddress, size, protection, MAP_FIXED | MAP_PRIVATE, fd, offset) != mmapAddress ) { - // failed to map some chunk of this shared cache file - // clear shared region - vm_deallocate(mach_task_self(), (vm_address_t)SHARED_REGION_BASE, SHARED_REGION_SIZE); - // go back to not using shared region at all - gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; - if ( gLinkContext.verboseMapping ) { - dyld::log("dyld: shared cached region cannot be mapped at address %p with size 0x%08lX\n", - mmapAddress, size); - } - // return failure - return -1; - } - } - - // update all __DATA pages with slide info - 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 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) { - const uint8_t* entry = &entries[toc[i]*slideInfoHeader->entries_size]; - const uint8_t* page = (uint8_t*)(long)(dataPagesStart + (4096*i)); - //dyld::log("page=%p toc[%d]=%d entries=%p\n", page, i, toc[i], entry); - for(int j=0; j < 128; ++j) { - uint8_t b = entry[j]; - //dyld::log(" entry[%d] = 0x%02X\n", j, b); - if ( b != 0 ) { - for(int k=0; k < 8; ++k) { - if ( b & (1< highAddress ) - highAddress = mappings[i].sfm_address + mappings[i].sfm_size; - } - } -} - -static long pickCacheSlide(uint32_t mappingsCount, shared_file_mapping_np mappings[]) -{ -#if __x86_64__ - // x86_64 has a two memory regions: - // 256MB at 0x00007FFF70000000 - // 1024MB at 0x00007FFF80000000 - // Some old shared caches have r/w region after rx region, so all regions slide within 1GB range - // Newer shared caches have r/w region based at 0x7FFF70000000 and r/o regions at 0x7FFF80000000, so each part has max slide - if ( (mappingsCount >= 3) && (mappings[1].sfm_init_prot == (VM_PROT_READ|VM_PROT_WRITE)) && (mappings[1].sfm_address == 0x00007FFF70000000) ) { - const uint64_t rwSize = mappings[1].sfm_size; - const uint64_t rwSlop = 0x10000000ULL - rwSize; - const uint64_t roSize = (mappings[2].sfm_address + mappings[2].sfm_size) - mappings[0].sfm_address; - const uint64_t roSlop = 0x40000000ULL - roSize; - const uint64_t space = (rwSlop < roSlop) ? rwSlop : roSlop; - - // choose new random slide - long slide = (arc4random() % space) & (-4096); - //dyld::log("rwSlop=0x%0llX, roSlop=0x%0llX\n", rwSlop, roSlop); - //dyld::log("space=0x%0llX, slide=0x%0lX\n", space, slide); - - // update mappings - for(uint32_t i=0; i < mappingsCount; ++i) { - mappings[i].sfm_address += slide; - } - - return slide; - } - // else fall through to handle old style cache -#endif - // get bounds of cache - uint64_t lowAddress; - uint64_t highAddress; - getCacheBounds(mappingsCount, mappings, lowAddress, highAddress); - - // find slop space - 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; - } - - return slide; -} - -static void mapSharedCache() -{ - uint64_t cacheBaseAddress = 0; - // quick check if a cache is already mapped into shared region - if ( _shared_region_check_np(&cacheBaseAddress) == 0 ) { - sSharedCache = (dyld_cache_header*)cacheBaseAddress; - // if we don't understand the currently mapped shared cache, then ignore -#if __x86_64__ - const char* magic = (sHaswell ? ARCH_CACHE_MAGIC_H : ARCH_CACHE_MAGIC); -#else - const char* magic = ARCH_CACHE_MAGIC; -#endif - if ( strcmp(sSharedCache->magic, magic) != 0 ) { - sSharedCache = NULL; - if ( gLinkContext.verboseMapping ) { - dyld::log("dyld: existing shared cached in memory is not compatible\n"); - return; - } - } - dyld::gProcessInfo->sharedCacheBaseAddress = cacheBaseAddress; - // check if cache file is slidable - const dyld_cache_header* header = sSharedCache; - if ( (header->mappingOffset >= 0x48) && (header->slideInfoSize != 0) ) { - // solve for slide by comparing loaded address to address of first region - const uint8_t* loadedAddress = (uint8_t*)sSharedCache; - const dyld_cache_mapping_info* const mappings = (dyld_cache_mapping_info*)(loadedAddress+header->mappingOffset); - const uint8_t* preferedLoadAddress = (uint8_t*)(long)(mappings[0].address); - sSharedCacheSlide = loadedAddress - preferedLoadAddress; - dyld::gProcessInfo->sharedCacheSlide = sSharedCacheSlide; - //dyld::log("sSharedCacheSlide=0x%08lX, loadedAddress=%p, preferedLoadAddress=%p\n", sSharedCacheSlide, loadedAddress, preferedLoadAddress); - } - // if cache has a uuid, copy it - if ( header->mappingOffset >= 0x68 ) { - memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16); - } - // verbose logging - if ( gLinkContext.verboseMapping ) { - 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 { -#if __i386__ || __x86_64__ - // Safe Boot should disable dyld shared cache - // if we are in safe-boot mode and the cache was not made during this boot cycle, - // delete the cache file - uint32_t safeBootValue = 0; - size_t safeBootValueSize = sizeof(safeBootValue); - if ( (sysctlbyname("kern.safeboot", &safeBootValue, &safeBootValueSize, NULL, 0) == 0) && (safeBootValue != 0) ) { - // user booted machine in safe-boot mode - struct stat dyldCacheStatInfo; - // Don't use custom DYLD_SHARED_CACHE_DIR if provided, use standard path - if ( my_stat(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &dyldCacheStatInfo) == 0 ) { - struct timeval bootTimeValue; - size_t bootTimeValueSize = sizeof(bootTimeValue); - if ( (sysctlbyname("kern.boottime", &bootTimeValue, &bootTimeValueSize, NULL, 0) == 0) && (bootTimeValue.tv_sec != 0) ) { - // if the cache file was created before this boot, then throw it away and let it rebuild itself - if ( dyldCacheStatInfo.st_mtime < bootTimeValue.tv_sec ) { - ::unlink(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME); - gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; - return; - } - } - } - } -#endif - // map in shared cache to shared region - int fd = openSharedCacheFile(); - if ( fd != -1 ) { - uint8_t firstPages[8192]; - if ( ::read(fd, firstPages, 8192) == 8192 ) { - dyld_cache_header* header = (dyld_cache_header*)firstPages; - #if __x86_64__ - const char* magic = (sHaswell ? ARCH_CACHE_MAGIC_H : ARCH_CACHE_MAGIC); - #else - const char* magic = ARCH_CACHE_MAGIC; - #endif - 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]; - #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 readWriteMappingIndex = -1; - int readOnlyMappingIndex = -1; - // validate that the cache file has not been truncated - bool goodCache = false; - struct stat stat_buf; - if ( fstat(fd, &stat_buf) == 0 ) { - goodCache = true; - int i=0; - for (const dyld_cache_mapping_info* p = fileMappingsStart; p < fileMappingsEnd; ++p, ++i) { - mappings[i].sfm_address = p->address; - mappings[i].sfm_size = p->size; - mappings[i].sfm_file_offset = p->fileOffset; - mappings[i].sfm_max_prot = p->maxProt; - mappings[i].sfm_init_prot = p->initProt; - // rdar://problem/5694507 old update_dyld_shared_cache tool could make a cache file - // that is not page aligned, but otherwise ok. - if ( p->fileOffset+p->size > (uint64_t)(stat_buf.st_size+4095 & (-4096)) ) { - dyld::log("dyld: shared cached file is corrupt: %s" DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME "\n", sSharedCacheDir); - goodCache = false; - } - if ( (mappings[i].sfm_init_prot & (VM_PROT_READ|VM_PROT_WRITE)) == (VM_PROT_READ|VM_PROT_WRITE) ) { - readWriteMappingIndex = i; - } - if ( mappings[i].sfm_init_prot == VM_PROT_READ ) { - readOnlyMappingIndex = i; - } - } - // if shared cache is code signed, add a mapping for the code signature - uint64_t signatureSize = header->codeSignatureSize; - // zero size in header means signature runs to end-of-file - if ( signatureSize == 0 ) - signatureSize = stat_buf.st_size - header->codeSignatureOffset; - if ( signatureSize != 0 ) { -#if __arm__ || __arm64__ - size_t alignedSignatureSize = (signatureSize+16383) & (-16384); -#else - size_t alignedSignatureSize = (signatureSize+4095) & (-4096); -#endif - // 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 - if ( header->imagesCount * sizeof(dyld_cache_image_info) + header->imagesOffset < 8192 ) { - bool foundLibSystem = false; - if ( my_stat("/usr/lib/libSystem.B.dylib", &stat_buf) == 0 ) { - const dyld_cache_image_info* images = (dyld_cache_image_info*)&firstPages[header->imagesOffset]; - const dyld_cache_image_info* const imagesEnd = &images[header->imagesCount]; - for (const dyld_cache_image_info* p = images; p < imagesEnd; ++p) { - if ( ((time_t)p->modTime == stat_buf.st_mtime) && ((ino_t)p->inode == stat_buf.st_ino) ) { - foundLibSystem = true; - break; - } - } - } - if ( !sSharedCacheIgnoreInodeAndTimeStamp && !foundLibSystem ) { - dyld::log("dyld: shared cached file was built against a different libSystem.dylib, ignoring cache.\n" - "to update dyld shared cache run: 'sudo update_dyld_shared_cache' then reboot.\n"); - goodCache = false; - } - } -#endif -#if __IPHONE_OS_VERSION_MIN_REQUIRED - { - uint64_t lowAddress; - uint64_t highAddress; - getCacheBounds(mappingCount, mappings, lowAddress, highAddress); - if ( (highAddress-lowAddress) > SHARED_REGION_SIZE ) - throw "dyld shared cache is too big to fit in shared region"; - } -#endif - - if ( goodCache && (readWriteMappingIndex == -1) ) { - dyld::log("dyld: shared cached file is missing read/write mapping: %s" DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME "\n", sSharedCacheDir); - goodCache = false; - } - if ( goodCache && (readOnlyMappingIndex == -1) ) { - dyld::log("dyld: shared cached file is missing read-only mapping: %s" DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME "\n", sSharedCacheDir); - goodCache = false; - } - if ( goodCache ) { - long cacheSlide = 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 ( slideInfoSize != 0 ) { - // don't slide shared cache if ASLR disabled (main executable didn't slide) - if ( sMainExecutable->isPositionIndependentExecutable() && (sMainExecutable->getSlide() == 0) ) { - cacheSlide = 0; - } - else { - // generate random slide amount - cacheSlide = pickCacheSlide(mappingCount, mappings); - } - - 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"); - for (int i=0; i < mappingCount; ++i) { - 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, 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 - 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"); - } - } - } - else { - if ( gLinkContext.verboseMapping ) - dyld::log("dyld: shared cached file is invalid\n"); - } - } - else { - if ( gLinkContext.verboseMapping ) - dyld::log("dyld: shared cached file cannot be read\n"); - } - close(fd); - } - else { - if ( gLinkContext.verboseMapping ) - dyld::log("dyld: shared cached file cannot be opened\n"); - } - } - - // remember if dyld loaded at same address as when cache built - if ( sSharedCache != NULL ) { - gLinkContext.dyldLoadedAtSameAddressNeededBySharedCache = ((uintptr_t)(sSharedCache->dyldBaseAddress) == (uintptr_t)&_mh_dylinker_header); - } - - // tell gdb where the shared cache is - if ( sSharedCache != NULL ) { - const dyld_cache_mapping_info* const start = (dyld_cache_mapping_info*)((uint8_t*)sSharedCache + sSharedCache->mappingOffset); - dyld_shared_cache_ranges.sharedRegionsCount = sSharedCache->mappingCount; - // only room to tell gdb about first four regions - if ( dyld_shared_cache_ranges.sharedRegionsCount > 4 ) - dyld_shared_cache_ranges.sharedRegionsCount = 4; - const dyld_cache_mapping_info* const end = &start[dyld_shared_cache_ranges.sharedRegionsCount]; - int index = 0; - for (const dyld_cache_mapping_info* p = start; p < end; ++p, ++index ) { - dyld_shared_cache_ranges.ranges[index].start = p->address+sSharedCacheSlide; - dyld_shared_cache_ranges.ranges[index].length = p->size; - if ( gLinkContext.verboseMapping ) { - dyld::log(" 0x%08llX->0x%08llX %s%s%s init=%x, max=%x\n", - p->address+sSharedCacheSlide, p->address+sSharedCacheSlide+p->size-1, - ((p->initProt & VM_PROT_READ) ? "read " : ""), - ((p->initProt & VM_PROT_WRITE) ? "write " : ""), - ((p->initProt & VM_PROT_EXECUTE) ? "execute " : ""), p->initProt, p->maxProt); - } - #if __i386__ - // If a non-writable and executable region is found in the R/W shared region, then this is __IMPORT segments - // This is an old cache. Make writable. dyld no longer supports turn W on and off as it binds - if ( (p->initProt == (VM_PROT_READ|VM_PROT_EXECUTE)) && ((p->address & 0xF0000000) == 0xA0000000) ) { - if ( p->size != 0 ) { - vm_prot_t prot = VM_PROT_EXECUTE | PROT_READ | VM_PROT_WRITE; - vm_protect(mach_task_self(), p->address, p->size, false, prot); - if ( gLinkContext.verboseMapping ) { - dyld::log("%18s at 0x%08llX->0x%08llX altered permissions to %c%c%c\n", "", p->address, - p->address+p->size-1, - (prot & PROT_READ) ? 'r' : '.', (prot & PROT_WRITE) ? 'w' : '.', (prot & PROT_EXEC) ? 'x' : '.' ); - } - } - } - #endif - } - if ( gLinkContext.verboseMapping ) { - // list the code blob - dyld_cache_header* header = (dyld_cache_header*)sSharedCache; - uint64_t signatureSize = header->codeSignatureSize; - // zero size in header means signature runs to end-of-file - if ( signatureSize == 0 ) { - struct stat stat_buf; - // FIXME: need size of cache file actually used - if ( my_stat(IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &stat_buf) == 0 ) - signatureSize = stat_buf.st_size - header->codeSignatureOffset; - } - if ( signatureSize != 0 ) { - const dyld_cache_mapping_info* const last = &start[dyld_shared_cache_ranges.sharedRegionsCount-1]; - uint64_t codeBlobStart = last->address + last->size; - dyld::log(" 0x%08llX->0x%08llX (code signature)\n", codeBlobStart, codeBlobStart+signatureSize); - } - } - #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 - - - -// create when NSLinkModule is called for a second time on a bundle -ImageLoader* cloneImage(ImageLoader* image) -{ - // open file (automagically closed when this function exits) - FileOpener file(image->getPath()); - - struct stat stat_buf; - if ( fstat(file.getFileDescriptor(), &stat_buf) == -1) - throw "stat error"; - - dyld::LoadContext context; - context.useSearchPaths = false; - context.useFallbackPaths = false; - context.useLdLibraryPath = false; - context.implicitRPath = false; - context.matchByInstallName = false; - context.dontLoad = false; - context.mustBeBundle = true; - context.mustBeDylib = false; - context.canBePIE = false; - context.origin = NULL; - context.rpath = NULL; - return loadPhase6(file.getFileDescriptor(), stat_buf, image->getPath(), context); -} - - -ImageLoader* loadFromMemory(const uint8_t* mem, uint64_t len, const char* moduleName) -{ - // if fat wrapper, find usable sub-file - const fat_header* memStartAsFat = (fat_header*)mem; - uint64_t fileOffset = 0; - uint64_t fileLength = len; - if ( memStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { - if ( fatFindBest(memStartAsFat, &fileOffset, &fileLength) ) { - mem = &mem[fileOffset]; - len = fileLength; - } - else { - throw "no matching architecture in universal wrapper"; - } + // if fat wrapper, find usable sub-file + const fat_header* memStartAsFat = (fat_header*)mem; + uint64_t fileOffset = 0; + uint64_t fileLength = len; + if ( memStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { + if ( fatFindBest(memStartAsFat, &fileOffset, &fileLength) ) { + mem = &mem[fileOffset]; + len = fileLength; + } + else { + throw "no matching architecture in universal wrapper"; + } } // try each loader @@ -4441,7 +3745,7 @@ void registerAddCallback(ImageCallback func) 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); + (*func)(infos[i].imageLoadAddress, sSharedCacheLoadInfo.slide); } } #endif @@ -4790,19 +4094,23 @@ void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_in catch (const char* msg) { // ignore request to abort during registration } + + // call 'init' function on all images already init'ed (below libSystem) + for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { + ImageLoader* image = *it; + if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) { + (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); + } + } } bool sharedCacheUUID(uuid_t uuid) { -#if DYLD_SHARED_CACHE_SUPPORT - if ( sSharedCache == NULL ) + if ( sSharedCacheLoadInfo.loadAddress == nullptr ) return false; - memcpy(uuid, sSharedCache->uuid, 16); + sSharedCacheLoadInfo.loadAddress->getUUID(uuid); return true; -#else - return false; -#endif } #if SUPPORT_ACCELERATE_TABLES @@ -4939,9 +4247,7 @@ static void setContext(const macho_header* mainExecutableMH, int argc, const cha gLinkContext.printAllDepths = &printAllDepths; gLinkContext.imageCount = &imageCount; gLinkContext.setNewProgramVars = &setNewProgramVars; -#if DYLD_SHARED_CACHE_SUPPORT gLinkContext.inSharedCache = &inSharedCache; -#endif gLinkContext.setErrorStrings = &setErrorStrings; #if SUPPORT_OLD_CRT_INITIALIZATION gLinkContext.setRunInitialzersOldWay= &setRunInitialzersOldWay; @@ -4970,7 +4276,7 @@ static void setContext(const macho_header* mainExecutableMH, int argc, const cha gLinkContext.dynamicInterposeCount = 0; gLinkContext.prebindUsage = ImageLoader::kUseAllPrebinding; #if TARGET_IPHONE_SIMULATOR - gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; + gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; #else gLinkContext.sharedRegionMode = ImageLoader::kUseSharedRegion; #endif @@ -4983,6 +4289,7 @@ static void setContext(const macho_header* mainExecutableMH, int argc, const cha // Its presences means that the binary wants to have DYLD ignore // DYLD_ environment variables. // +#if __MAC_OS_X_VERSION_MIN_REQUIRED static bool hasRestrictedSegment(const macho_header* mh) { const uint32_t cmd_count = mh->ncmds; @@ -5011,8 +4318,9 @@ static bool hasRestrictedSegment(const macho_header* mh) return false; } +#endif -#if __IPHONE_OS_VERSION_MIN_REQUIRED +#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR static bool isFairPlayEncrypted(const macho_header* mh) { const uint32_t cmd_count = mh->ncmds; @@ -5070,7 +4378,6 @@ static bool getDylibVersionAndInstallname(const char* dylibPath, uint32_t* versi uint8_t firstPage[4096]; const macho_header* mh = (macho_header*)firstPage; if ( !readFirstPage(dylibPath, firstPage) ) { - #if DYLD_SHARED_CACHE_SUPPORT // If file cannot be read, check to see if path is in shared cache const macho_header* mhInCache; const char* pathInCache; @@ -5078,9 +4385,6 @@ static bool getDylibVersionAndInstallname(const char* dylibPath, uint32_t* versi if ( !findInSharedCacheImage(dylibPath, true, NULL, &mhInCache, &pathInCache, &slideInCache) ) return false; mh = mhInCache; - #else - return false; - #endif } // check mach-o header @@ -5351,13 +4655,13 @@ static void loadInsertedDylib(const char* path) // 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; + uint32_t flags; if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { if ( flags & CS_ENFORCEMENT ) { if ( flags & CS_GET_TASK_ALLOW ) { @@ -5392,6 +4696,7 @@ static void configureProcessRestrictions(const macho_header* mainExecutableMH) gLinkContext.processIsRestricted = true; } bool usingSIP = (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0); + uint32_t flags; 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) && usingSIP ) { @@ -5441,9 +4746,14 @@ static void addDyldImageToUUIDList() } } -void notifyKernelAboutDyld() +void notifyKernelAboutImage(const struct macho_header* mh, const char* fileInfo) { - const struct macho_header* mh = (macho_header*)&__dso_handle; + const char *endptr = nullptr; + uint64_t fsid_scalar = hexToUInt64(fileInfo, &endptr); + uint64_t fsobj_id_scalar = 0; + if (endptr != nullptr) { + fsobj_id_scalar = hexToUInt64(endptr+1, &endptr); + } 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; @@ -5452,14 +4762,7 @@ void notifyKernelAboutDyld() 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); + dyld3::kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, (const uuid_t *)&uc->uuid[0], *reinterpret_cast(&fsobj_id_scalar), *reinterpret_cast(&fsid_scalar), (const mach_header *)mh); return; } } @@ -5473,7 +4776,7 @@ 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 = { - 7, + 8, // added in version 1 (open_proc_t)&open, &close, @@ -5527,7 +4830,12 @@ static SyscallHelpers sSysCalls = { &task_get_dyld_image_infos, &task_register_dyld_shared_cache_image_info, &task_register_dyld_set_dyld_state, - &task_register_dyld_get_process_state + &task_register_dyld_get_process_state, + // Added in version 8 + &task_info, + &thread_info, + &kdebug_is_enabled, + &kdebug_trace }; __attribute__((noinline)) @@ -5571,7 +4879,7 @@ static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH return "pread(dyld_sim) failed"; } else if ( !isCompatibleMachO(firstPage, dyldPath) ) { - return "dyld_sim not compatible mach-o"; + return "dyld_sim is not compatible with the loaded process, likely due to architecture mismatch"; } // calculate total size of dyld segments @@ -5694,7 +5002,6 @@ static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH if ( siginfo.fs_file_start < codeSigCmd->dataoff ) 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; cmd = (struct load_command*)(((char*)loadAddress)+sizeof(macho_header)); @@ -5743,6 +5050,563 @@ static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH } #endif +// +// If the DYLD_SKIP_MAIN environment is set to 1, dyld will return the +// address of this function instead of main() in the target program which +// __dyld_start jumps to. Useful for qualifying dyld itself. +// +int +fake_main() +{ + return 0; +} + + + + +static bool envVarMatches(dyld3::launch_cache::Closure mainClosure, const char* envp[], const char* varName) +{ + __block const char* valueFromClosure = nullptr; + mainClosure.forEachEnvVar(^(const char* keyEqualValue, bool& stop) { + size_t keyLen = strlen(varName); + if ( (strncmp(varName, keyEqualValue, keyLen) == 0) && (keyEqualValue[keyLen] == '=') ) { + valueFromClosure = &keyEqualValue[keyLen+1]; + stop = true; + } + }); + + const char* valueFromEnv = _simple_getenv(envp, varName); + + bool inClosure = (valueFromClosure != nullptr); + bool inEnv = (valueFromEnv != nullptr); + if ( inClosure != inEnv ) + return false; + if ( !inClosure && !inEnv ) + return true; + return ( strcmp(valueFromClosure, valueFromEnv) == 0 ); +} + +static const char* const sEnvVarsToCheck[] = { + "DYLD_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", + "DYLD_FALLBACK_LIBRARY_PATH", + "DYLD_FALLBACK_FRAMEWORK_PATH", + "DYLD_INSERT_LIBRARIES", + "DYLD_IMAGE_SUFFIX", + "DYLD_VERSIONED_FRAMEWORK_PATH", + "DYLD_VERSIONED_LIBRARY_PATH", + "DYLD_ROOT_PATH" +}; + +static bool envVarsMatch(dyld3::launch_cache::Closure mainClosure, const char* envp[]) +{ + for (const char* envVar : sEnvVarsToCheck) { + if ( !envVarMatches(mainClosure, envp, envVar) ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because %s changed\n", mainClosure.binaryData(), envVar); + return false; + } + } + + return true; +} + +static bool closureValid(const dyld3::launch_cache::BinaryClosureData* mainClosureData, const mach_header* mainExecutableMH, const uint8_t* mainExecutableCDHash, bool closureInCache, const char* envp[]) +{ + const dyld3::launch_cache::Closure mainClosure(mainClosureData); + const dyld3::launch_cache::ImageGroup mainGroup = mainClosure.group(); + + // verify current dyld cache is same as expected + if ( sSharedCacheLoadInfo.loadAddress == nullptr ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p dyld cache not loaded\n", mainClosureData); + return false; + } + if ( !closureInCache ) { + // closures in cache don't have cache's UUID + uuid_t cacheUUID; + sSharedCacheLoadInfo.loadAddress->getUUID(cacheUUID); + if ( memcmp(mainClosure.dyldCacheUUID(), cacheUUID, sizeof(uuid_t)) != 0 ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because built against different dyld cache\n", mainClosureData); + return false; + } + } +#if __MAC_OS_X_VERSION_MIN_REQUIRED + else { + // HACK until closured for dlopen can run against live cache file + int fd = my_open(sSharedCacheLoadInfo.path, O_RDONLY, 0); + if ( fd != -1 ) { + dyld_cache_header fileHeader; + if ( pread(fd, &fileHeader, sizeof(fileHeader), 0) == sizeof(fileHeader) ) { + uuid_t cacheUUID; + sSharedCacheLoadInfo.loadAddress->getUUID(cacheUUID); + if ( memcmp(fileHeader.uuid, cacheUUID, sizeof(uuid_t)) != 0 ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because current cache on disk is not they one being used\n", mainClosureData); + ::close(fd); + return false; + } + } + ::close(fd); + } + } +#endif + + // verify main executable file has not changed since closure was built + const dyld3::launch_cache::Image mainImage = mainGroup.image(mainClosure.mainExecutableImageIndex()); + if ( mainImage.validateUsingModTimeAndInode() ) { + struct stat statBuf; + if ( ::stat(mainImage.path(), &statBuf) != 0 ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because stat() failed on main executable\n", mainClosureData); + return false; + } + else if ( (statBuf.st_mtime != mainImage.fileModTime()) || (statBuf.st_ino != mainImage.fileINode()) ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because mtime/inode changed since closure was built\n", mainClosureData); + return false; + } + } + + // verify cdHash of main executable is same as recorded in closure + if ( mainImage.validateUsingCdHash() ) { + if ( mainExecutableCDHash == nullptr ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because main executable is not code signed but was expected to be\n", mainClosureData); + return false; + } + if ( memcmp(mainExecutableCDHash, mainClosure.cdHash(), 20) != 0 ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because main executable cd-hash changed since closure was built\n", mainClosureData); + return false; + } + } + + // verify UUID of main executable is same as recorded in closure + const uuid_t* closureMainUUID = mainImage.uuid(); + dyld3::MachOParser parser(mainExecutableMH); + uuid_t actualUUID; + parser.getUuid(actualUUID); + if ( memcmp(actualUUID, closureMainUUID, sizeof(uuid_t)) != 0 ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because UUID of executable changed since closure was built\n", mainClosureData); + return false; + } + + // verify DYLD_* env vars are same as when closure was built + if ( !envVarsMatch(mainClosure, envp) ) { + return false; + } + + // verify files that are supposed to be missing actually are missing + __block bool foundFileThatInvalidatesClosure = false; + mainClosure.forEachMustBeMissingFile(^(const char* path, bool& stop) { + struct stat statBuf; + if ( ::stat(path, &statBuf) == 0 ) { + stop = true; + foundFileThatInvalidatesClosure = true; + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because found unexpected file '%s'\n", mainClosureData, path); + } + }); + +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // verify no key frameworks have been overridden since cache was built + if ( dyld3::loader::internalInstall() ) { + dyld3::loader::forEachLineInFile("/AppleInternal/Library/Preferences/dyld-potential-framework-overrides", ^(const char* path, bool& stop) { + dyld3::SharedCacheFindDylibResults shareCacheResults; + if ( dyld3::findInSharedCacheImage(sSharedCacheLoadInfo, path, &shareCacheResults) ) { + dyld3::launch_cache::Image image(shareCacheResults.imageData); + struct stat statBuf; + if ( ::stat(path, &statBuf) == 0 ) { + if ( (image.fileModTime() != statBuf.st_mtime) || (image.fileINode() != statBuf.st_ino)) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because framework has changed: '%s'\n", mainClosureData, path); + foundFileThatInvalidatesClosure = true; + stop = true; + } + } + } + }); + } +#endif + + return !foundFileThatInvalidatesClosure; +} + +static bool nolog(const char* format, ...) +{ + return false; +} + +static bool dolog(const char* format, ...) +{ + va_list list; + va_start(list, format); + vlog(format, list); + va_end(list); + return true; +} + +static bool launchWithClosure(const dyld3::launch_cache::BinaryClosureData* mainClosureData, + const DyldSharedCache* dyldCache, + const mach_header* mainExecutableMH, uintptr_t mainExecutableSlide, + int argc, const char* argv[], const char* envp[], const char* apple[], + uintptr_t* entry, uintptr_t* startGlue) +{ + dyld3::launch_cache::Closure mainClosure(mainClosureData); + const dyld3::launch_cache::ImageGroup mainGroup = mainClosure.group(); + const uint32_t mainExecutableIndex = mainClosure.mainExecutableImageIndex(); + const dyld3::launch_cache::Image mainImage = mainGroup.image(mainExecutableIndex); + const uint32_t loadedImageCount = mainClosure.initialImageCount(); + + // construct array of groups + dyld3::DyldCacheParser cacheParser(dyldCache, false); + STACK_ALLOC_DYNARRAY(const dyld3::launch_cache::binary_format::ImageGroup*, 3, theGroups); + theGroups[0] = cacheParser.cachedDylibsGroup(); + theGroups[1] = cacheParser.otherDylibsGroup(); + theGroups[2] = mainClosure.group().binaryData(); + + // construct array of all Image*, starting with any inserted dylibs, then main executable + const dyld3::launch_cache::BinaryImageData* images[loadedImageCount]; + dyld3::launch_cache::SlowLoadSet imageSet(&images[0], &images[loadedImageCount]); + for (uint32_t i=0; i <= mainExecutableIndex; ++i) { + imageSet.add(mainGroup.image(i).binaryData()); + } + // add all dependents of main executable + if ( !mainImage.recurseAllDependentImages(theGroups, imageSet, nullptr) ) { + dyld::log("initial image list overflow, expected only %d\n", loadedImageCount); + return false; + } + // add dependents of any inserted dylibs + for (uint32_t i=0; i < mainExecutableIndex; ++i) { + if ( !mainGroup.image(i).recurseAllDependentImages(theGroups, imageSet, nullptr) ) { + dyld::log("initial image list overflow in inserted libraries, expected only %d\n", loadedImageCount); + return false; + } + } + const uint32_t actualImageCount = (uint32_t)imageSet.count(); + // construct array of allImages + STACK_ALLOC_DYNARRAY(dyld3::loader::ImageInfo, actualImageCount, allImages); + for (int i=0; i < actualImageCount; ++i) { + dyld3::launch_cache::Image img(images[i]); + dyld3::launch_cache::ImageGroup grp = img.group(); + allImages[i].imageData = img.binaryData(); + allImages[i].loadAddress = nullptr; + allImages[i].groupNum = grp.groupNum(); + allImages[i].indexInGroup = grp.indexInGroup(img.binaryData()); + allImages[i].previouslyFixedUp = false; + allImages[i].justMapped = false; + allImages[i].justUsedFromDyldCache = false; + allImages[i].neverUnload = false; + } + // prefill address of main executable to mark it is already loaded + allImages[mainExecutableIndex].loadAddress = mainExecutableMH; + + // map new images and apply all fixups + Diagnostics diag; + mapAndFixupImages(diag, allImages, (const uint8_t*)dyldCache, (gLinkContext.verboseLoading ? &dolog : &nolog), + (gLinkContext.verboseMapping ? &dolog : &nolog), + (gLinkContext.verboseBind ? &dolog : &nolog), + (gLinkContext.verboseDOF ? &dolog : &nolog)); + if ( diag.hasError() ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: %s\n", diag.errorMessage()); + return false; + } + + //dyld::log("loaded image list:\n"); + //for (int i=0; i < allImages.count(); ++i) { + // dyld3::launch_cache::Image img(allImages[i].imageData); + // dyld::log("binImage[%d]=%p, mh=%p, path=%s\n", i, allImages[i].imageData, allImages[i].loadAddress, img.path()); + //} + + // find special images + const dyld3::launch_cache::BinaryImageData* libSystemImage = mainClosure.libSystem(theGroups); + const dyld3::launch_cache::BinaryImageData* libDyldImage = mainClosure.libDyld(theGroups); + const mach_header* libdyldMH = nullptr; + const mach_header* libSystemMH = nullptr; + for (int i=0; i < allImages.count(); ++i) { + if ( allImages[i].imageData == libSystemImage ) + libSystemMH = allImages[i].loadAddress; + else if ( allImages[i].imageData == libDyldImage ) + libdyldMH = allImages[i].loadAddress; + } + + // send info on all images to libdyld.dylb + const dyld3::LibDyldEntryVector* libDyldEntry = (dyld3::LibDyldEntryVector*)((uint8_t*)libdyldMH + mainClosure.libdyldVectorOffset()); + libDyldEntry->setVars(mainExecutableMH, argc, argv, envp, apple); + libDyldEntry->setHaltFunction(&halt); + if ( libDyldEntry->vectorVersion > 2 ) + libDyldEntry->setChildForkFunction(&_dyld_fork_child); +#if !TARGET_IPHONE_SIMULATOR + if ( libDyldEntry->vectorVersion > 3 ) + libDyldEntry->setLogFunction(&dyld::vlog); +#endif + libDyldEntry->setOldAllImageInfo(gProcessInfo); + libDyldEntry->setInitialImageList(mainClosureData, dyldCache, sSharedCacheLoadInfo.path, allImages, libSystemMH, libSystemImage); + // run initializers + CRSetCrashLogMessage("dyld3: launch, running initializers"); + libDyldEntry->runInitialzersBottomUp((mach_header*)mainExecutableMH); + //dyld::log("returned from runInitialzersBottomUp()\n"); + + dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN, 0, 0); + if ( mainClosure.mainExecutableUsesCRT() ) { + // old style app linked with crt1.o + // entry is "start" function in program + *startGlue = 0; + *entry = (uintptr_t)mainExecutableMH + mainClosure.mainExecutableEntryOffset(); + } + else { + // modern app with LC_MAIN + // set startGlue to "start" function in libdyld.dylib + // set entry to "main" function in program + *startGlue = (uintptr_t)(libDyldEntry->startFunc); + *entry =(uintptr_t)mainExecutableMH + mainClosure.mainExecutableEntryOffset(); + } + CRSetCrashLogMessage(NULL); + return true; +} + +static void putHexNibble(uint8_t value, char*& p) +{ + if ( value < 10 ) + *p++ = '0' + value; + else + *p++ = 'A' + value - 10; +} + +static void putHexByte(uint8_t value, char*& p) +{ + value &= 0xFF; + putHexNibble(value >> 4, p); + putHexNibble(value & 0x0F, p); +} + +static void makeHexLong(unsigned long value, char* p) +{ + *p++ = '0'; + *p++ = 'x'; +#if __LP64__ + putHexByte(value >> 56, p); + putHexByte(value >> 48, p); + putHexByte(value >> 40, p); + putHexByte(value >> 32, p); +#endif + putHexByte(value >> 24, p); + putHexByte(value >> 16, p); + putHexByte(value >> 8, p); + putHexByte(value, p); + *p = '\0'; +} + +static void makeUUID(uint8_t uuid[16], char* p) +{ + putHexByte(uuid[0], p); + putHexByte(uuid[1], p); + putHexByte(uuid[2], p); + putHexByte(uuid[3], p); + *p++ = '-'; + putHexByte(uuid[4], p); + putHexByte(uuid[5], p); + *p++ = '-'; + putHexByte(uuid[6], p); + putHexByte(uuid[7], p); + *p++ = '-'; + putHexByte(uuid[8], p); + putHexByte(uuid[9], p); + *p++ = '-'; + putHexByte(uuid[10], p); + putHexByte(uuid[11], p); + putHexByte(uuid[12], p); + putHexByte(uuid[13], p); + putHexByte(uuid[14], p); + putHexByte(uuid[15], p); + *p = '\0'; +} + +#if !TARGET_IPHONE_SIMULATOR +static const dyld3::launch_cache::BinaryClosureData* callClosureDaemon(const char* mainExecPath, const char* envp[]) +{ + // temp, until we can get a bootstrap_lookup that works from dyld +#if 1 + // Create a pipe + int sockets[2]; + if ( ::pipe(sockets) < 0 ) { + dyld::log("error opening stream socket pair to closured\n"); + return NULL; + } + //dyld::log("created sockets %d and %d\n", sockets[0], sockets[1]); + // use fork/exec to launch closured + int child = ::__fork(); + if ( child == -1 ) { + dyld::log("error forking, errno=%d\n", errno); + return NULL; + } + if ( child ) { + // parent side + //dyld::log("parent side pid=%d\n", getpid()); + ::close(sockets[1]); + SocketBasedClousureHeader header; + long amount = ::read(sockets[0], &header, sizeof(SocketBasedClousureHeader)); + if ( amount != sizeof(SocketBasedClousureHeader) ) { + dyld::log("error reading, errno=%d\n", errno); + return NULL; + } + vm_address_t bufferAddress = 0; + if ( ::vm_allocate(mach_task_self(), &bufferAddress, header.length, VM_FLAGS_ANYWHERE) != 0 ) { + dyld::log("error allocating buffer\n"); + return NULL; + } + amount = ::read(sockets[0], (void*)bufferAddress, header.length); + close(sockets[0]); + if ( amount != header.length ) { + dyld::log("dyld: error reading buffer header from closured, amount=%ld, errno=%d\n", amount, errno); + return NULL; + } + if ( header.success ) { + // make buffer read-only + vm_protect(mach_task_self(), bufferAddress, header.length, VM_PROT_READ, VM_PROT_READ); + return (const dyld3::launch_cache::BinaryClosureData*)bufferAddress; + } + else { + // buffer contains error message as to why closure could not be built + dyld::log("%s", (char*)bufferAddress); + ::vm_deallocate(mach_task_self(), bufferAddress, header.length); + return NULL; + } + } + else { + // child side + //dyld::log("child side pid=%d\n", getpid()); + close(sockets[0]); + const char* closuredPath = "/usr/libexec/closured"; + char pipeStr[8]; + pipeStr[0] = '0' + sockets[1]; + pipeStr[1] = '\0'; + const char* argv[32]; + char cacheUuidString[64]; + char cacheAddrString[64]; + char cacheSizeString[64]; + int i = 0; + uuid_t cacheUUID; + sSharedCacheLoadInfo.loadAddress->getUUID(cacheUUID); + makeHexLong((long)sSharedCacheLoadInfo.loadAddress, cacheAddrString); + makeHexLong((long)sSharedCacheLoadInfo.loadAddress->mappedSize(), cacheSizeString); + makeUUID(cacheUUID, cacheUuidString); + argv[i++] = closuredPath; + argv[i++] = "-create_closure"; + argv[i++] = mainExecPath; + argv[i++] = "-pipefd"; + argv[i++] = pipeStr; + argv[i++] = "-cache_uuid"; + argv[i++] = cacheUuidString; + argv[i++] = "-cache_address"; + argv[i++] = cacheAddrString; + argv[i++] = "-cache_size"; + argv[i++] = cacheSizeString; + for (const char**p=envp; *p != NULL; ++p) { + const char* envToCheck = *p; + for (const char* dyldEnvVar : sEnvVarsToCheck) { + size_t dyldEnvVarLen = strlen(dyldEnvVar); + if ( (strncmp(dyldEnvVar, envToCheck, dyldEnvVarLen) == 0) && (envToCheck[dyldEnvVarLen] == '=') ) { + argv[i++] = "-env"; + argv[i++] = envToCheck; + } + } + } + argv[i] = nullptr; + //dyld::log("closured args:\n"); + //for (int j=0; argv[j] != nullptr; ++j) + // dyld::log(" argv[%d]=%s\n", j, argv[j]); + execve(closuredPath, (char**)argv, nullptr); + dyld::log("exec() of closured failed, errno=%d\n", errno); + } + return NULL; + +#else + // get port to closured + mach_port_t serverPort = dyld3::loader::lookupClosuredPort(); + if ( serverPort == MACH_PORT_NULL ) + return NULL; + + // build env var list + char envBuffer[2048]; + char* s = envBuffer; + for (const char* envVar : sEnvVarsToCheck) { + if ( const char* valueFromEnv = _simple_getenv(envp, envVar) ) { + strcpy(s, envVar); + strcat(s, "="); + strcat(s, valueFromEnv); + s += strlen(s)+1; + } + } + *s++ = '\0'; + + // get uuid of main executable + dyld3::MachOParser mainParser((mach_header*)sMainExecutableMachHeader); + uuid_t mainUuid; + mainParser.getUuid(mainUuid); + + // message closured to build closure + bool success = false; + vm_offset_t reply = 0; + uint32_t replySize = 0; + if ( closured_CreateLaunchClosure(serverPort, sExecPath, sSharedCachePath, mainUuid, envBuffer, &success, &reply, &replySize) != KERN_SUCCESS ) + return NULL; + + // release server port + mach_port_deallocate(mach_task_self(), serverPort); + + if ( success ) + return (const dyld3::launch_cache::BinaryClosureData*)reply; + + dyld::log("closure failed to build: %s\n", (char*)reply); + return NULL; +#endif +} +#endif // !TARGET_IPHONE_SIMULATOR + + +#if !__MAC_OS_X_VERSION_MIN_REQUIRED +static const char* sWhiteListDirs[] = { + "/bin/", + "/sbin/", + "/usr/bin/" +}; +#endif + +static bool inWhiteList(const char* execPath) +{ + // First test to see if we forced in dyld2 via a kernel boot-arg + if ( dyld3::loader::bootArgsContains("force_dyld2=1") ) + return false; + +#if __MAC_OS_X_VERSION_MIN_REQUIRED + + // rdar://problem/32701418: Don't use dyld3 for i386 for now. +#if __i386__ + return false; +#else + + + return true; +#endif // #if __i386__ + +#else + // enable dyld3 mode for all OS programs when using customer dyld cache (no roots) + if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && (sSharedCacheLoadInfo.loadAddress->header.cacheType == kDyldSharedCacheTypeProduction) ) + return true; + + for (const char* dir : sWhiteListDirs) { + if ( strncmp(dir, sExecPath, strlen(dir)) == 0 ) { + return true; + } + } + return dyld3::loader::bootArgsContains("force_dyld3=1"); +#endif +} // // Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which @@ -5755,14 +5619,28 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) { + dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_DYLD, 0, 0); + + // Grab the cdHash of the main executable from the environment + uint8_t mainExecutableCDHashBuffer[20]; + const uint8_t* mainExecutableCDHash = nullptr; + if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) ) + mainExecutableCDHash = mainExecutableCDHashBuffer; + + // Trace dyld's load + notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file")); +#if !TARGET_IPHONE_SIMULATOR + // Trace the main executable's load + notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file")); +#endif + uintptr_t result = 0; sMainExecutableMachHeader = mainExecutableMH; + sMainExecutableSlide = mainExecutableSlide; #if __MAC_OS_X_VERSION_MIN_REQUIRED // 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]; @@ -5800,6 +5678,7 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, sExecPath = s; } } + // Remember short name of process for later logging sExecShortName = ::strrchr(sExecPath, '/'); if ( sExecShortName != NULL ) @@ -5826,6 +5705,96 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, if ( sEnv.DYLD_PRINT_ENV ) printEnvironmentVariables(envp); getHostInfo(mainExecutableMH, mainExecutableSlide); + + // load shared cache + checkSharedRegionDisable((mach_header*)mainExecutableMH); +#if TARGET_IPHONE_SIMULATOR + // until is fixed + gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; + // +#endif + if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) { + mapSharedCache(); + } + + if ( (sEnableClosures || inWhiteList(sExecPath)) && (sSharedCacheLoadInfo.loadAddress != nullptr) ) { + if ( sSharedCacheLoadInfo.loadAddress->header.formatVersion == dyld3::launch_cache::binary_format::kFormatVersion ) { + const dyld3::launch_cache::BinaryClosureData* mainClosureData; + // check for closure in cache first + dyld3::DyldCacheParser cacheParser(sSharedCacheLoadInfo.loadAddress, false); + mainClosureData = cacheParser.findClosure(sExecPath); + #if __IPHONE_OS_VERSION_MIN_REQUIRED + if ( mainClosureData == nullptr ) { + // see if this is an OS app that was moved + if ( strncmp(sExecPath, "/var/containers/Bundle/Application/", 35) == 0 ) { + dyld3::MachOParser mainParser((mach_header*)mainExecutableMH); + uint32_t textOffset; + uint32_t textSize; + if ( !mainParser.isFairPlayEncrypted(textOffset, textSize) ) { + __block bool hasEmbeddedDylibs = false; + mainParser.forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t, uint32_t, bool& stop) { + if ( loadPath[0] == '@' ) { + hasEmbeddedDylibs = true; + stop = true; + } + }); + if ( !hasEmbeddedDylibs ) { + char altPath[1024]; + const char* lastSlash = strrchr(sExecPath, '/'); + if ( lastSlash != nullptr ) { + strlcpy(altPath, "/private/var/staged_system_apps", sizeof(altPath)); + strlcat(altPath, lastSlash, sizeof(altPath)); + strlcat(altPath, ".app", sizeof(altPath)); + strlcat(altPath, lastSlash, sizeof(altPath)); + if ( gLinkContext.verboseWarnings ) + dyld::log("try path: %s\n", altPath); + mainClosureData = cacheParser.findClosure(altPath); + } + } + } + } + } + #endif + if ( gLinkContext.verboseWarnings && (mainClosureData != nullptr) ) + dyld::log("dyld: found closure %p in dyld shared cache\n", mainClosureData); + #if !TARGET_IPHONE_SIMULATOR + if ( (mainClosureData == nullptr) || !closureValid(mainClosureData, (mach_header*)mainExecutableMH, mainExecutableCDHash, true, envp) ) { + mainClosureData = nullptr; + if ( sEnableClosures ) { + // if forcing closures, and no closure in cache, or it is invalid, then RPC to closured + mainClosureData = callClosureDaemon(sExecPath, envp); + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closured return %p for %s\n", mainClosureData, sExecPath); + if ( (mainClosureData != nullptr) && !closureValid(mainClosureData, (mach_header*)mainExecutableMH, mainExecutableCDHash, false, envp) ) { + // some how freshly generated closure is invalid... + mainClosureData = nullptr; + } + } + } + #endif + // try using launch closure + if ( mainClosureData != nullptr ) { + CRSetCrashLogMessage("dyld3: launch started"); + if ( launchWithClosure(mainClosureData, sSharedCacheLoadInfo.loadAddress, (mach_header*)mainExecutableMH, mainExecutableSlide, + argc, argv, envp, apple, &result, startGlue) ) { + if (sSkipMain) + result = (uintptr_t)&fake_main; + return result; + } + else { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: unable to use closure %p\n", mainClosureData); + } + } + } + else { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: not using closure because shared cache format version does not match dyld's\n"); + } + // could not use closure info, launch old way + } + + // install gdb notifier stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB); stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages); @@ -5847,10 +5816,14 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, try { // add dyld itself to UUID list addDyldImageToUUIDList(); - notifyKernelAboutDyld(); #if SUPPORT_ACCELERATE_TABLES bool mainExcutableAlreadyRebased = false; + if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) { + struct stat statBuf; + if ( ::stat(IPHONE_DYLD_SHARED_CACHE_DIR "no-dyld2-accelerator-tables", &statBuf) != 0 ) + sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(&sSharedCacheLoadInfo.loadAddress->header, sSharedCacheLoadInfo.slide, mainExecutableMH, gLinkContext); + } reloadAllImages: #endif @@ -5900,23 +5873,6 @@ reloadAllImages: gLinkContext.strictMachORequired = true; #endif - // load shared cache - checkSharedRegionDisable(); - #if DYLD_SHARED_CACHE_SUPPORT - 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 @@ -6036,16 +5992,15 @@ reloadAllImages: // 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; + if ( (sSharedCacheLoadInfo.loadAddress != NULL) && (sSharedCacheLoadInfo.loadAddress->header.mappingOffset >= 0x78) && (sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset != 0) ) { + uint32_t count = sSharedCacheLoadInfo.loadAddress->header.branchPoolsCount; dyld_image_info info[count]; - const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCache + sSharedCache->branchPoolsOffset); + const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCacheLoadInfo.loadAddress + sSharedCacheLoadInfo.loadAddress->header.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; + uint64_t poolAddr = poolAddress[poolIndex] + sSharedCacheLoadInfo.slide; info[poolIndex].imageLoadAddress = (mach_header*)(long)poolAddr; info[poolIndex].imageFilePath = "dyld_shared_cache_branch_islands"; info[poolIndex].imageFileModDate = 0; @@ -6056,7 +6011,6 @@ reloadAllImages: gProcessInfo->notification(dyld_image_adding, count, info); } } - #endif CRSetCrashLogMessage("dyld: launch, running initializers"); #if SUPPORT_OLD_CRT_INITIALIZATION @@ -6069,6 +6023,7 @@ reloadAllImages: #endif // notify any montoring proccesses that this process is about to enter main() + dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN_DYLD2, 0, 0); notifyMonitoringDyldMain(); // find entry point for main executable @@ -6095,12 +6050,17 @@ reloadAllImages: } CRSetCrashLogMessage(NULL); + + if (sSkipMain) { + dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN, 0, 0); + result = (uintptr_t)&fake_main; + *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit; + } return result; } - } // namespace diff --git a/src/dyld.exp b/src/dyld.exp index e5f4086..59a6660 100644 --- a/src/dyld.exp +++ b/src/dyld.exp @@ -28,7 +28,6 @@ # 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 diff --git a/src/dyld.h b/src/dyld.h index d8bca0d..de649ab 100644 --- a/src/dyld.h +++ b/src/dyld.h @@ -65,9 +65,7 @@ namespace dyld { #if SUPPORT_ACCELERATE_TABLES extern bool gLogAppAPIs; #endif -#if DYLD_SHARED_CACHE_SUPPORT extern bool gSharedCacheOverridden; -#endif extern const struct LibSystemHelpers* gLibSystemHelpers; #if SUPPORT_OLD_CRT_INITIALIZATION extern bool gRunInitializersOldWay; @@ -110,12 +108,9 @@ namespace dyld { 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(); extern uintptr_t fastBindLazySymbol(ImageLoader** imageLoaderCache, uintptr_t lazyBindingInfoOffset); -#if DYLD_SHARED_CACHE_SUPPORT extern bool inSharedCache(const char* path); -#endif #if LOG_BINDINGS extern void logBindings(const char* format, ...); #endif diff --git a/src/dyldAPIs.cpp b/src/dyldAPIs.cpp index c87aaff..65e5681 100644 --- a/src/dyldAPIs.cpp +++ b/src/dyldAPIs.cpp @@ -54,10 +54,17 @@ #include "ImageLoaderMachO.h" #include "dyld.h" #include "dyldLibSystemInterface.h" +#include "DyldSharedCache.h" #undef _POSIX_C_SOURCE #include "dlfcn.h" + +// this was in dyld_priv.h but it is no longer exported +extern "C" { + const struct dyld_all_image_infos* _dyld_get_all_image_infos(); +} + // from dyldExceptions.c extern "C" void __Unwind_SjLj_SetThreadKey(pthread_key_t key); @@ -67,6 +74,7 @@ extern uint32_t allImagesCount(); extern const mach_header* allImagesIndexedMachHeader(uint32_t index); extern const char* allImagesIndexedPath(uint32_t index); +extern "C" int _dyld_func_lookup(const char* name, void** address); // deprecated APIs are still availble on Mac OS X, but not on iPhone OS #if __IPHONE_OS_VERSION_MIN_REQUIRED @@ -153,9 +161,7 @@ static struct dyld_func dyld_funcs[] = { {"__dyld_shared_cache_some_image_overridden", (void*)dyld_shared_cache_some_image_overridden }, {"__dyld_process_is_restricted", (void*)dyld::processIsRestricted }, {"__dyld_dynamic_interpose", (void*)dyld_dynamic_interpose }, -#if DYLD_SHARED_CACHE_SUPPORT {"__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 }, @@ -171,7 +177,6 @@ static struct dyld_func dyld_funcs[] = { {"__dyld_install_handlers", (void*)_dyld_install_handlers }, {"__dyld_link_edit_error", (void*)NSLinkEditError }, {"__dyld_unlink_module", (void*)NSUnLinkModule }, - {"__dyld_bind_objc_module", (void*)_dyld_bind_objc_module }, {"__dyld_bind_fully_image_containing_address", (void*)_dyld_bind_fully_image_containing_address }, {"__dyld_image_containing_address", (void*)_dyld_image_containing_address }, {"__dyld_register_binding_handler", (void*)_dyld_register_binding_handler }, @@ -198,7 +203,6 @@ static struct dyld_func dyld_funcs[] = { {"__dyld_NSCreateObjectFileImageFromMemory", (void*)NSCreateObjectFileImageFromMemory }, {"__dyld_NSDestroyObjectFileImage", (void*)NSDestroyObjectFileImage }, {"__dyld_NSLinkModule", (void*)NSLinkModule }, - {"__dyld_NSHasModInitObjectFileImage", (void*)NSHasModInitObjectFileImage }, {"__dyld_NSSymbolDefinitionCountInObjectFileImage", (void*)NSSymbolDefinitionCountInObjectFileImage }, {"__dyld_NSSymbolDefinitionNameInObjectFileImage", (void*)NSSymbolDefinitionNameInObjectFileImage }, {"__dyld_NSIsSymbolDefinedInObjectFileImage", (void*)NSIsSymbolDefinedInObjectFileImage }, @@ -734,14 +738,6 @@ bool _dyld_all_twolevel_modules_prebound(void) return FALSE; } -void _dyld_bind_objc_module(const void *objc_module) -{ - if ( dyld::gLogAPIs ) - dyld::log("%s(%p)\n", __func__, objc_module); - // do nothing, with new dyld everything already bound -} - - bool _dyld_bind_fully_image_containing_address(const void* address) { if ( dyld::gLogAPIs ) @@ -849,8 +845,8 @@ NSObjectFileImageReturnCode NSCreateObjectFileImageFromMemory(const void* addres static bool validOFI(NSObjectFileImage objectFileImage) { - const int ofiCount = sObjectFileImages.size(); - for (int i=0; i < ofiCount; ++i) { + const size_t ofiCount = sObjectFileImages.size(); + for (size_t i=0; i < ofiCount; ++i) { if ( sObjectFileImages[i] == objectFileImage ) return true; } @@ -905,13 +901,6 @@ bool NSDestroyObjectFileImage(NSObjectFileImage objectFileImage) return false; } -bool NSHasModInitObjectFileImage(NSObjectFileImage objectFileImage) -{ - if ( dyld::gLogAPIs ) - dyld::log("%s(%p)\n", __func__, objectFileImage); - return objectFileImage->image->needsInitialization(); -} - uint32_t NSSymbolDefinitionCountInObjectFileImage(NSObjectFileImage objectFileImage) { if ( dyld::gLogAPIs ) @@ -1145,9 +1134,9 @@ bool NSUnLinkModule(NSModule module, uint32_t options) // Only delete image if there is no ofi referencing it // That means the ofi was destroyed after linking, so no one is left to delete this image - const int ofiCount = sObjectFileImages.size(); + const size_t ofiCount = sObjectFileImages.size(); bool found = false; - for (int i=0; i < ofiCount; ++i) { + for (size_t i=0; i < ofiCount; ++i) { NSObjectFileImage ofi = sObjectFileImages[i]; if ( ofi->image == image ) found = true; @@ -1346,12 +1335,10 @@ bool dlopen_preflight(const char* 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 if ( dyld::inSharedCache(path) ) return true; -#endif bool result = false; std::vector rpathsFromCallerImage; @@ -1520,12 +1507,13 @@ void* dlopen(const char* path, int mode) 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; + // found in cache, but under a different path + const char* betterPath = dyld::getPathFromIndex(cacheIndex); + if ( (betterPath != NULL) && dyld::dlopenFromCache(betterPath, mode, &result) ) { + // Note: dlopenFromCache() releases the lock + if ( dyld::gLogAPIs ) + dyld::log(" %s(%s) ==> %p\n", __func__, path, result); + return result; } } #endif @@ -1950,11 +1938,7 @@ const char* dyld_image_path_containing_address(const void* address) bool dyld_shared_cache_some_image_overridden() { - #if DYLD_SHARED_CACHE_SUPPORT return dyld::gSharedCacheOverridden; - #else - return true; - #endif } @@ -1992,15 +1976,15 @@ bool _dyld_is_memory_immutable(const void* addr, size_t 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; + const DyldSharedCache* cache = (DyldSharedCache*)dyld::imMemorySharedCacheHeader(); + if ( cache != nullptr ) { + const dyld_cache_mapping_info* const mappings = (dyld_cache_mapping_info*)((char*)cache + cache->header.mappingOffset); + uintptr_t roStart = (uintptr_t)cache; + uintptr_t roEnd = roStart + (uintptr_t)mappings[0].size; if ( (roStart < checkStart) && (checkEnd < roEnd) ) return true; - } -#endif + } // Otherwise find if addr is in a dyld loaded image ImageLoader* image = dyld::findImageContainingAddress(addr); @@ -2036,13 +2020,13 @@ bool _dyld_get_shared_cache_uuid(uuid_t uuid) const void* _dyld_get_shared_cache_range(size_t* length) { -#if DYLD_SHARED_CACHE_SUPPORT - uintptr_t cacheEndAddr = (dyld_shared_cache_ranges.ranges[2].start + dyld_shared_cache_ranges.ranges[2].length); - *length = cacheEndAddr - dyld_shared_cache_ranges.ranges[0].start; - return (void*)(dyld_shared_cache_ranges.ranges[0].start); -#else - return NULL; -#endif + const DyldSharedCache* cache = (DyldSharedCache*)dyld::imMemorySharedCacheHeader(); + if ( cache != nullptr ) { + const dyld_cache_mapping_info* const mappings = (dyld_cache_mapping_info*)((char*)cache + cache->header.mappingOffset); + *length = (size_t)((mappings[2].address + mappings[2].size) - mappings[0].address); + return cache; + } + return nullptr; } diff --git a/src/dyldAPIsInLibSystem.cpp b/src/dyldAPIsInLibSystem.cpp index 35abc2a..6d72472 100644 --- a/src/dyldAPIsInLibSystem.cpp +++ b/src/dyldAPIsInLibSystem.cpp @@ -43,10 +43,27 @@ #include "dyldLock.h" #include "start_glue.h" +#include "../dyld3/APIs.h" +#include "../dyld3/AllImages.h" + + +// this was in dyld_priv.h but it is no longer exported +extern "C" { + const struct dyld_all_image_infos* _dyld_get_all_image_infos() __attribute__((visibility("hidden"))); +} + + extern "C" int __cxa_atexit(void (*func)(void *), void *arg, void *dso); extern "C" void __cxa_finalize(const void *dso); extern "C" void __cxa_finalize_ranges(const struct __cxa_range_t ranges[], int count); +// +// private interface between libSystem.dylib and dyld +// +extern "C" int _dyld_func_lookup(const char* dyld_func_name, void **address); + + +extern bool gUseDyld3; #ifndef LC_VERSION_MIN_MACOSX #define LC_VERSION_MIN_MACOSX 0x24 @@ -76,6 +93,42 @@ extern "C" void __cxa_finalize_ranges(const struct __cxa_range_t ranges[], int c #define LC_LOAD_UPWARD_DYLIB (0x23|LC_REQ_DYLD) /* load of dylib whose initializers run later */ #endif +#ifndef LC_BUILD_VERSION + #define LC_BUILD_VERSION 0x32 /* build for platform min OS version */ + + /* + * The build_version_command contains the min OS version on which this + * binary was built to run for its platform. The list of known platforms and + * tool values following it. + */ + struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ + }; + + struct build_tool_version { + uint32_t tool; /* enum for the tool */ + uint32_t version; /* version number of the tool */ + }; + + /* Known values for the platform field above. */ + #define PLATFORM_MACOS 1 + #define PLATFORM_IOS 2 + #define PLATFORM_TVOS 3 + #define PLATFORM_WATCHOS 4 + #define PLATFORM_BRIDGEOS 5 + + /* Known values for the tool field above. */ + #define TOOL_CLANG 1 + #define TOOL_SWIFT 2 + #define TOOL_LD 3 +#endif + // deprecated APIs are still availble on Mac OS X, but not on iPhone OS #if __IPHONE_OS_VERSION_MIN_REQUIRED @@ -142,6 +195,9 @@ const char* libraryName) void NSInstallLinkEditErrorHandlers( const NSLinkEditErrorHandlers* handlers) { + if ( gUseDyld3 ) + return dyld3::NSInstallLinkEditErrorHandlers(handlers); + DYLD_LOCK_THIS_BLOCK; typedef void (*ucallback_t)(const char* symbol_name); typedef NSModule (*mcallback_t)(NSSymbol s, NSModule old, NSModule newhandler); @@ -159,6 +215,9 @@ const char* NSNameOfModule( NSModule module) { + if ( gUseDyld3 ) + return dyld3::NSNameOfModule(module); + DYLD_LOCK_THIS_BLOCK; static const char* (*p)(NSModule module) = NULL; @@ -171,6 +230,9 @@ const char* NSLibraryNameForModule( NSModule module) { + if ( gUseDyld3 ) + return dyld3::NSLibraryNameForModule(module); + DYLD_LOCK_THIS_BLOCK; static const char* (*p)(NSModule module) = NULL; @@ -183,6 +245,9 @@ bool NSIsSymbolNameDefined( const char* symbolName) { + if ( gUseDyld3 ) + return dyld3::NSIsSymbolNameDefined(symbolName); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(const char* symbolName) = NULL; @@ -196,6 +261,9 @@ NSIsSymbolNameDefinedWithHint( const char* symbolName, const char* libraryNameHint) { + if ( gUseDyld3 ) + return dyld3::NSIsSymbolNameDefinedWithHint(symbolName, libraryNameHint); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(const char* symbolName, const char* libraryNameHint) = NULL; @@ -210,6 +278,9 @@ NSIsSymbolNameDefinedInImage( const struct mach_header *image, const char* symbolName) { + if ( gUseDyld3 ) + return dyld3::NSIsSymbolNameDefinedInImage(image, symbolName); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(const struct mach_header *image, const char* symbolName) = NULL; @@ -223,6 +294,9 @@ NSSymbol NSLookupAndBindSymbol( const char* symbolName) { + if ( gUseDyld3 ) + return dyld3::NSLookupAndBindSymbol(symbolName); + DYLD_LOCK_THIS_BLOCK; static NSSymbol (*p)(const char* symbolName) = NULL; @@ -236,6 +310,9 @@ NSLookupAndBindSymbolWithHint( const char* symbolName, const char* libraryNameHint) { + if ( gUseDyld3 ) + return dyld3::NSLookupAndBindSymbolWithHint(symbolName, libraryNameHint); + DYLD_LOCK_THIS_BLOCK; static NSSymbol (*p)(const char* symbolName, const char* libraryNameHint) = NULL; @@ -250,6 +327,9 @@ NSLookupSymbolInModule( NSModule module, const char* symbolName) { + if ( gUseDyld3 ) + return dyld3::NSLookupSymbolInModule(module, symbolName); + DYLD_LOCK_THIS_BLOCK; static NSSymbol (*p)(NSModule module, const char* symbolName) = NULL; @@ -264,8 +344,11 @@ const struct mach_header *image, const char* symbolName, uint32_t options) { + if ( gUseDyld3 ) + return dyld3::NSLookupSymbolInImage(image, symbolName, options); + DYLD_LOCK_THIS_BLOCK; - static NSSymbol (*p)(const struct mach_header *image, + static NSSymbol (*p)(const struct mach_header *image, const char* symbolName, uint32_t options) = NULL; @@ -278,6 +361,9 @@ const char* NSNameOfSymbol( NSSymbol symbol) { + if ( gUseDyld3 ) + return dyld3::NSNameOfSymbol(symbol); + DYLD_LOCK_THIS_BLOCK; static char * (*p)(NSSymbol symbol) = NULL; @@ -290,6 +376,9 @@ void * NSAddressOfSymbol( NSSymbol symbol) { + if ( gUseDyld3 ) + return dyld3::NSAddressOfSymbol(symbol); + DYLD_LOCK_THIS_BLOCK; static void * (*p)(NSSymbol symbol) = NULL; @@ -302,6 +391,9 @@ NSModule NSModuleForSymbol( NSSymbol symbol) { + if ( gUseDyld3 ) + return dyld3::NSModuleForSymbol(symbol); + DYLD_LOCK_THIS_BLOCK; static NSModule (*p)(NSSymbol symbol) = NULL; @@ -314,6 +406,9 @@ bool NSAddLibrary( const char* pathName) { + if ( gUseDyld3 ) + return dyld3::NSAddLibrary(pathName); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(const char* pathName) = NULL; @@ -326,6 +421,9 @@ bool NSAddLibraryWithSearching( const char* pathName) { + if ( gUseDyld3 ) + return dyld3::NSAddLibrary(pathName); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(const char* pathName) = NULL; @@ -339,6 +437,9 @@ NSAddImage( const char* image_name, uint32_t options) { + if ( gUseDyld3 ) + return dyld3::NSAddImage(image_name, options); + DYLD_LOCK_THIS_BLOCK; static const struct mach_header * (*p)(const char* image_name, uint32_t options) = NULL; @@ -362,6 +463,9 @@ uint32_t options) */ int32_t NSVersionOfLinkTimeLibrary(const char* libraryName) { + if ( gUseDyld3 ) + return dyld3::NSVersionOfLinkTimeLibrary(libraryName); + // Lazily call _NSGetMachExecuteHeader() and cache result #if __LP64__ static mach_header_64* mh = NULL; @@ -400,6 +504,9 @@ int32_t NSVersionOfLinkTimeLibrary(const char* libraryName) */ int32_t NSVersionOfRunTimeLibrary(const char* libraryName) { + if ( gUseDyld3 ) + return dyld3::NSVersionOfRunTimeLibrary(libraryName); + uint32_t n = _dyld_image_count(); for(uint32_t i = 0; i < n; i++){ const mach_header* mh = _dyld_get_image_header(i); @@ -429,7 +536,7 @@ int32_t NSVersionOfRunTimeLibrary(const char* libraryName) #define PACKED_VERSION(major, minor, tiny) ((((major) & 0xffff) << 16) | (((minor) & 0xff) << 8) | ((tiny) & 0xff)) -static bool getVersionLoadCommandInfo(const mach_header* mh, uint32_t* loadCommand, uint32_t* minOS, uint32_t* sdk) +static bool getVersionLoadCommandInfo(const mach_header* mh, uint32_t* platform, uint32_t* minOS, uint32_t* sdk) { const load_command* startCmds = NULL; if ( mh->magic == MH_MAGIC_64 ) @@ -447,15 +554,37 @@ static bool getVersionLoadCommandInfo(const mach_header* mh, uint32_t* loadComma return 0; } const version_min_command* versCmd; + const build_version_command* buildVersCmd; switch ( cmd->cmd ) { case LC_VERSION_MIN_IPHONEOS: + versCmd = (version_min_command*)cmd; + *platform = PLATFORM_IOS; + *minOS = versCmd->version; + *sdk = versCmd->sdk; + return true; case LC_VERSION_MIN_MACOSX: + versCmd = (version_min_command*)cmd; + *platform = PLATFORM_MACOS; + *minOS = versCmd->version; + *sdk = versCmd->sdk; + return true; case LC_VERSION_MIN_TVOS: + versCmd = (version_min_command*)cmd; + *platform = PLATFORM_TVOS; + *minOS = versCmd->version; + *sdk = versCmd->sdk; + return true; case LC_VERSION_MIN_WATCHOS: versCmd = (version_min_command*)cmd; - *loadCommand = versCmd->cmd; - *minOS = versCmd->version; - *sdk = versCmd->sdk; + *platform = PLATFORM_WATCHOS; + *minOS = versCmd->version; + *sdk = versCmd->sdk; + return true; + case LC_BUILD_VERSION: + buildVersCmd = (build_version_command*)cmd; + *platform = buildVersCmd->platform; + *minOS = buildVersCmd->minos; + *sdk = buildVersCmd->sdk; return true; } cmd = nextCmd; @@ -589,27 +718,71 @@ static uint32_t watchVersToIOSVers(uint32_t vers) uint32_t dyld_get_program_sdk_watch_os_version() { + if ( gUseDyld3 ) + return dyld3::dyld_get_program_sdk_watch_os_version(); + const mach_header* mh = (mach_header*)_NSGetMachExecuteHeader(); - uint32_t loadCommand; + uint32_t platform; uint32_t minOS; uint32_t sdk; - if ( getVersionLoadCommandInfo(mh, &loadCommand, &minOS, &sdk) ) { - if ( loadCommand == LC_VERSION_MIN_WATCHOS ) + if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { + if ( platform == PLATFORM_WATCHOS ) return sdk; } return 0; } uint32_t dyld_get_program_min_watch_os_version() +{ + if ( gUseDyld3 ) + return dyld3::dyld_get_program_min_watch_os_version(); + + const mach_header* mh = (mach_header*)_NSGetMachExecuteHeader(); + uint32_t platform; + uint32_t minOS; + uint32_t sdk; + + if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { + if ( platform == PLATFORM_WATCHOS ) + return minOS; // return raw minOS (not mapped to iOS version) + } + return 0; +} + +#endif + + + +#if TARGET_OS_BRIDGE +static uint32_t bridgeVersToIOSVers(uint32_t vers) +{ + return vers + 0x00090000; +} + +uint32_t dyld_get_program_sdk_bridge_os_version() +{ + const mach_header* mh = (mach_header*)_NSGetMachExecuteHeader(); + uint32_t platform; + uint32_t minOS; + uint32_t sdk; + + if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { + if ( platform == PLATFORM_BRIDGEOS ) + return sdk; + } + return 0; +} + +uint32_t dyld_get_program_min_bridge_os_version() { const mach_header* mh = (mach_header*)_NSGetMachExecuteHeader(); - uint32_t loadCommand; + uint32_t platform; uint32_t minOS; uint32_t sdk; - if ( getVersionLoadCommandInfo(mh, &loadCommand, &minOS, &sdk) ) { - if ( loadCommand == LC_VERSION_MIN_WATCHOS ) + if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { + if ( platform == PLATFORM_BRIDGEOS ) return minOS; // return raw minOS (not mapped to iOS version) } return 0; @@ -628,30 +801,40 @@ uint32_t dyld_get_program_min_watch_os_version() */ uint32_t dyld_get_sdk_version(const mach_header* mh) { - uint32_t loadCommand; + if ( gUseDyld3 ) + return dyld3::dyld_get_sdk_version(mh); + + uint32_t platform; uint32_t minOS; uint32_t sdk; - if ( getVersionLoadCommandInfo(mh, &loadCommand, &minOS, &sdk) ) { - switch (loadCommand) { -#if __WATCH_OS_VERSION_MIN_REQUIRED - case LC_VERSION_MIN_WATCHOS: + if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { + switch (platform) { +#if TARGET_OS_BRIDGE + case PLATFORM_BRIDGEOS: + // new binary. sdk version looks like "2.0" but API wants "11.0" + return bridgeVersToIOSVers(sdk); + case PLATFORM_IOS: + // old binary. sdk matches API semantics so can return directly. + return sdk; +#elif __WATCH_OS_VERSION_MIN_REQUIRED + case PLATFORM_WATCHOS: // new binary. sdk version looks like "2.0" but API wants "9.0" return watchVersToIOSVers(sdk); - case LC_VERSION_MIN_IPHONEOS: + case PLATFORM_IOS: // old binary. sdk matches API semantics so can return directly. return sdk; #elif __TV_OS_VERSION_MIN_REQUIRED - case LC_VERSION_MIN_TVOS: - case LC_VERSION_MIN_IPHONEOS: + case PLATFORM_TVOS: + case PLATFORM_IOS: return sdk; #elif __IPHONE_OS_VERSION_MIN_REQUIRED - case LC_VERSION_MIN_IPHONEOS: + case PLATFORM_IOS: if ( sdk != 0 ) // old binaries might not have SDK set return sdk; break; #else - case LC_VERSION_MIN_MACOSX: + case PLATFORM_MACOS: if ( sdk != 0 ) // old binaries might not have SDK set return sdk; break; @@ -659,7 +842,7 @@ uint32_t dyld_get_sdk_version(const mach_header* mh) } } -#if __WATCH_OS_VERSION_MIN_REQUIRED ||__TV_OS_VERSION_MIN_REQUIRED +#if __WATCH_OS_VERSION_MIN_REQUIRED || __TV_OS_VERSION_MIN_REQUIRED || TARGET_OS_BRIDGE // All WatchOS and tv OS binaries should have version load command. return 0; #else @@ -670,33 +853,46 @@ uint32_t dyld_get_sdk_version(const mach_header* mh) uint32_t dyld_get_program_sdk_version() { + if ( gUseDyld3 ) + return dyld3::dyld_get_program_sdk_version(); + return dyld_get_sdk_version((mach_header*)_NSGetMachExecuteHeader()); } uint32_t dyld_get_min_os_version(const struct mach_header* mh) { - uint32_t loadCommand; + if ( gUseDyld3 ) + return dyld3::dyld_get_min_os_version(mh); + + uint32_t platform; uint32_t minOS; uint32_t sdk; - if ( getVersionLoadCommandInfo(mh, &loadCommand, &minOS, &sdk) ) { - switch (loadCommand) { -#if __WATCH_OS_VERSION_MIN_REQUIRED - case LC_VERSION_MIN_WATCHOS: + if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { + switch (platform) { +#if TARGET_OS_BRIDGE + case PLATFORM_BRIDGEOS: + // new binary. sdk version looks like "2.0" but API wants "11.0" + return bridgeVersToIOSVers(minOS); + case PLATFORM_IOS: + // old binary. sdk matches API semantics so can return directly. + return minOS; +#elif __WATCH_OS_VERSION_MIN_REQUIRED + case PLATFORM_WATCHOS: // new binary. OS version looks like "2.0" but API wants "9.0" return watchVersToIOSVers(minOS); - case LC_VERSION_MIN_IPHONEOS: + case PLATFORM_IOS: // old binary. OS matches API semantics so can return directly. return minOS; #elif __TV_OS_VERSION_MIN_REQUIRED - case LC_VERSION_MIN_TVOS: - case LC_VERSION_MIN_IPHONEOS: + case PLATFORM_TVOS: + case PLATFORM_IOS: return minOS; #elif __IPHONE_OS_VERSION_MIN_REQUIRED - case LC_VERSION_MIN_IPHONEOS: + case PLATFORM_IOS: return minOS; #else - case LC_VERSION_MIN_MACOSX: + case PLATFORM_MACOS: return minOS; #endif } @@ -707,12 +903,18 @@ uint32_t dyld_get_min_os_version(const struct mach_header* mh) uint32_t dyld_get_program_min_os_version() { + if ( gUseDyld3 ) + return dyld3::dyld_get_program_min_os_version(); + return dyld_get_min_os_version((mach_header*)_NSGetMachExecuteHeader()); } bool _dyld_get_image_uuid(const struct mach_header* mh, uuid_t uuid) { + if ( gUseDyld3 ) + return dyld3::_dyld_get_image_uuid(mh, uuid); + const load_command* startCmds = NULL; if ( mh->magic == MH_MAGIC_64 ) startCmds = (load_command*)((char *)mh + sizeof(mach_header_64)); @@ -754,6 +956,9 @@ NSCreateObjectFileImageFromFile( const char* pathName, NSObjectFileImage *objectFileImage) { + if ( gUseDyld3 ) + return dyld3::NSCreateObjectFileImageFromFile(pathName, objectFileImage); + DYLD_LOCK_THIS_BLOCK; static NSObjectFileImageReturnCode (*p)(const char*, NSObjectFileImage*) = NULL; @@ -776,6 +981,9 @@ const void* address, size_t size, NSObjectFileImage *objectFileImage) { + if ( gUseDyld3 ) + return dyld3::NSCreateObjectFileImageFromMemory(address, size, objectFileImage); + DYLD_LOCK_THIS_BLOCK; static NSObjectFileImageReturnCode (*p)(const void*, size_t, NSObjectFileImage*) = NULL; @@ -809,6 +1017,9 @@ bool NSDestroyObjectFileImage( NSObjectFileImage objectFileImage) { + if ( gUseDyld3 ) + return dyld3::NSDestroyObjectFileImage(objectFileImage); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(NSObjectFileImage) = NULL; @@ -824,6 +1035,9 @@ NSObjectFileImage objectFileImage, const char* moduleName, uint32_t options) { + if ( gUseDyld3 ) + return dyld3::NSLinkModule(objectFileImage, moduleName, options); + DYLD_LOCK_THIS_BLOCK; static NSModule (*p)(NSObjectFileImage, const char*, unsigned long) = NULL; @@ -844,6 +1058,9 @@ uint32_t NSSymbolDefinitionCountInObjectFileImage( NSObjectFileImage objectFileImage) { + if ( gUseDyld3 ) + return dyld3::NSSymbolDefinitionCountInObjectFileImage(objectFileImage); + DYLD_LOCK_THIS_BLOCK; static uint32_t (*p)(NSObjectFileImage) = NULL; @@ -864,6 +1081,9 @@ NSSymbolDefinitionNameInObjectFileImage( NSObjectFileImage objectFileImage, uint32_t ordinal) { + if ( gUseDyld3 ) + return dyld3::NSSymbolDefinitionNameInObjectFileImage(objectFileImage, ordinal); + DYLD_LOCK_THIS_BLOCK; static const char* (*p)(NSObjectFileImage, uint32_t) = NULL; @@ -881,6 +1101,9 @@ uint32_t NSSymbolReferenceCountInObjectFileImage( NSObjectFileImage objectFileImage) { + if ( gUseDyld3 ) + return dyld3::NSSymbolReferenceCountInObjectFileImage(objectFileImage); + DYLD_LOCK_THIS_BLOCK; static uint32_t (*p)(NSObjectFileImage) = NULL; @@ -902,6 +1125,9 @@ NSObjectFileImage objectFileImage, uint32_t ordinal, bool *tentative_definition) /* can be NULL */ { + if ( gUseDyld3 ) + return dyld3::NSSymbolReferenceNameInObjectFileImage(objectFileImage, ordinal, tentative_definition); + DYLD_LOCK_THIS_BLOCK; static const char* (*p)(NSObjectFileImage, uint32_t, bool*) = NULL; @@ -920,6 +1146,9 @@ NSIsSymbolDefinedInObjectFileImage( NSObjectFileImage objectFileImage, const char* symbolName) { + if ( gUseDyld3 ) + return dyld3::NSIsSymbolDefinedInObjectFileImage(objectFileImage, symbolName); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(NSObjectFileImage, const char*) = NULL; @@ -943,6 +1172,9 @@ const char* segmentName, const char* sectionName, unsigned long *size) /* can be NULL */ { + if ( gUseDyld3 ) + return dyld3::NSGetSectionDataInObjectFileImage(objectFileImage, segmentName, sectionName, size); + DYLD_LOCK_THIS_BLOCK; static void* (*p)(NSObjectFileImage, const char*, const char*, unsigned long*) = NULL; @@ -960,6 +1192,9 @@ int *errorNumber, const char* *fileName, const char* *errorString) { + if ( gUseDyld3 ) + return dyld3::NSLinkEditError(c, errorNumber, fileName, errorString); + DYLD_LOCK_THIS_BLOCK; static void (*p)(NSLinkEditErrors *c, int *errorNumber, @@ -977,6 +1212,9 @@ NSUnLinkModule( NSModule module, uint32_t options) { + if ( gUseDyld3 ) + return dyld3::NSUnLinkModule(module, options); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(NSModule module, uint32_t options) = NULL; @@ -1014,6 +1252,9 @@ _NSGetExecutablePath( char *buf, uint32_t *bufsize) { + if ( gUseDyld3 ) + return dyld3::_NSGetExecutablePath(buf, bufsize); + DYLD_NO_LOCK_THIS_BLOCK; static int (*p)(char *buf, uint32_t *bufsize) = NULL; @@ -1106,6 +1347,9 @@ void _dyld_register_func_for_add_image( void (*func)(const struct mach_header *mh, intptr_t vmaddr_slide)) { + if ( gUseDyld3 ) + return dyld3::_dyld_register_func_for_add_image(func); + DYLD_LOCK_THIS_BLOCK; typedef void (*callback_t)(const struct mach_header *mh, intptr_t vmaddr_slide); static void (*p)(callback_t func) = NULL; @@ -1124,6 +1368,9 @@ void _dyld_register_func_for_remove_image( void (*func)(const struct mach_header *mh, intptr_t vmaddr_slide)) { + if ( gUseDyld3 ) + return dyld3::_dyld_register_func_for_remove_image(func); + DYLD_LOCK_THIS_BLOCK; typedef void (*callback_t)(const struct mach_header *mh, intptr_t vmaddr_slide); static void (*p)(callback_t func) = NULL; @@ -1207,21 +1454,6 @@ unsigned long *size) p(module, objc_module, size); } -/* - * _dyld_bind_objc_module() is passed a pointer to something in an (__OBJC, - * __module) section and causes the module that is associated with that address - * to be bound. - */ -void -_dyld_bind_objc_module(const void* objc_module) -{ - DYLD_LOCK_THIS_BLOCK; - static void (*p)(const void *objc_module) = NULL; - - if(p == NULL) - _dyld_func_lookup("__dyld_bind_objc_module", (void**)&p); - p(objc_module); -} #endif #if DEPRECATED_APIS_SUPPORTED @@ -1236,6 +1468,9 @@ _dyld_present(void) uint32_t _dyld_image_count(void) { + if ( gUseDyld3 ) + return dyld3::_dyld_image_count(); + DYLD_NO_LOCK_THIS_BLOCK; static uint32_t (*p)(void) = NULL; @@ -1247,6 +1482,9 @@ _dyld_image_count(void) const struct mach_header * _dyld_get_image_header(uint32_t image_index) { + if ( gUseDyld3 ) + return dyld3::_dyld_get_image_header(image_index); + DYLD_NO_LOCK_THIS_BLOCK; static struct mach_header * (*p)(uint32_t image_index) = NULL; @@ -1258,6 +1496,9 @@ _dyld_get_image_header(uint32_t image_index) intptr_t _dyld_get_image_vmaddr_slide(uint32_t image_index) { + if ( gUseDyld3 ) + return dyld3::_dyld_get_image_vmaddr_slide(image_index); + DYLD_NO_LOCK_THIS_BLOCK; static unsigned long (*p)(uint32_t image_index) = NULL; @@ -1269,6 +1510,9 @@ _dyld_get_image_vmaddr_slide(uint32_t image_index) const char* _dyld_get_image_name(uint32_t image_index) { + if ( gUseDyld3 ) + return dyld3::_dyld_get_image_name(image_index); + DYLD_NO_LOCK_THIS_BLOCK; static const char* (*p)(uint32_t image_index) = NULL; @@ -1280,6 +1524,9 @@ _dyld_get_image_name(uint32_t image_index) // SPI in Mac OS X 10.6 intptr_t _dyld_get_image_slide(const struct mach_header* mh) { + if ( gUseDyld3 ) + return dyld3::_dyld_get_image_slide(mh); + DYLD_NO_LOCK_THIS_BLOCK; static intptr_t (*p)(const struct mach_header*) = NULL; @@ -1289,9 +1536,13 @@ intptr_t _dyld_get_image_slide(const struct mach_header* mh) } +#if DEPRECATED_APIS_SUPPORTED bool _dyld_image_containing_address(const void* address) { + if ( gUseDyld3 ) + return dyld3::_dyld_image_containing_address(address); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(const void*) = NULL; @@ -1304,6 +1555,9 @@ const struct mach_header * _dyld_get_image_header_containing_address( const void* address) { + if ( gUseDyld3 ) + return dyld3::_dyld_get_image_header_containing_address(address); + DYLD_LOCK_THIS_BLOCK; static const struct mach_header * (*p)(const void*) = NULL; @@ -1312,8 +1566,6 @@ const void* address) return p(address); } - -#if DEPRECATED_APIS_SUPPORTED bool _dyld_launched_prebound(void) { DYLD_LOCK_THIS_BLOCK; @@ -1407,7 +1659,6 @@ static bool isLaunchdOwned() return ( val != 0 ); } -#if DYLD_SHARED_CACHE_SUPPORT static void shared_cache_missing() { // leave until dyld's that might call this are rare @@ -1417,17 +1668,12 @@ static void shared_cache_out_of_date() { // leave until dyld's that might call this are rare } -#endif // DYLD_SHARED_CACHE_SUPPORT // the table passed to dyld containing thread helpers static dyld::LibSystemHelpers sHelpers = { 13, &dyldGlobalLockAcquire, &dyldGlobalLockRelease, &getPerThreadBufferFor_dlerror, &malloc, &free, &__cxa_atexit, - #if DYLD_SHARED_CACHE_SUPPORT &shared_cache_missing, &shared_cache_out_of_date, - #else - NULL, NULL, - #endif NULL, NULL, &pthread_key_create, &pthread_setspecific, &malloc_size, @@ -1447,21 +1693,28 @@ static dyld::LibSystemHelpers sHelpers = { 13, &dyldGlobalLockAcquire, &dyldGlob // and call dyld, registering the helper functions. // extern "C" void tlv_initializer(); -extern "C" void _dyld_initializer(); void _dyld_initializer() { void (*p)(dyld::LibSystemHelpers*); - _dyld_func_lookup("__dyld_register_thread_helpers", (void**)&p); - if(p != NULL) - p(&sHelpers); - + if ( gUseDyld3 ) { + dyld3::gAllImages.applyInitialImages(); + } + else { + _dyld_func_lookup("__dyld_register_thread_helpers", (void**)&p); + if(p != NULL) + p(&sHelpers); + } + tlv_initializer(); } char* dlerror() { + if ( gUseDyld3 ) + return dyld3::dlerror(); + DYLD_LOCK_THIS_BLOCK; static char* (*p)() = NULL; @@ -1472,6 +1725,9 @@ char* dlerror() int dladdr(const void* addr, Dl_info* info) { + if ( gUseDyld3 ) + return dyld3::dladdr(addr, info); + DYLD_LOCK_THIS_BLOCK; static int (*p)(const void* , Dl_info*) = NULL; @@ -1482,6 +1738,9 @@ int dladdr(const void* addr, Dl_info* info) int dlclose(void* handle) { + if ( gUseDyld3 ) + return dyld3::dlclose(handle); + DYLD_LOCK_THIS_BLOCK; static int (*p)(void* handle) = NULL; @@ -1492,6 +1751,9 @@ int dlclose(void* handle) void* dlopen(const char* path, int mode) { + if ( gUseDyld3 ) + return dyld3::dlopen(path, mode); + // dlopen is special. locking is done inside dyld to allow initializer to run without lock DYLD_NO_LOCK_THIS_BLOCK; @@ -1510,6 +1772,9 @@ void* dlopen(const char* path, int mode) bool dlopen_preflight(const char* path) { + if ( gUseDyld3 ) + return dyld3::dlopen_preflight(path); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(const char* path) = NULL; @@ -1520,6 +1785,9 @@ bool dlopen_preflight(const char* path) void* dlsym(void* handle, const char* symbol) { + if ( gUseDyld3 ) + return dyld3::dlsym(handle, symbol); + DYLD_LOCK_THIS_BLOCK; static void* (*p)(void* handle, const char* symbol) = NULL; @@ -1528,9 +1796,11 @@ void* dlsym(void* handle, const char* symbol) return(p(handle, symbol)); } - const struct dyld_all_image_infos* _dyld_get_all_image_infos() { + if ( gUseDyld3 ) + return dyld3::_dyld_get_all_image_infos(); + DYLD_NO_LOCK_THIS_BLOCK; static struct dyld_all_image_infos* (*p)() = NULL; @@ -1542,6 +1812,9 @@ const struct dyld_all_image_infos* _dyld_get_all_image_infos() #if SUPPORT_ZERO_COST_EXCEPTIONS bool _dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info) { + if ( gUseDyld3 ) + return dyld3::_dyld_find_unwind_sections(addr, info); + DYLD_NO_LOCK_THIS_BLOCK; static void* (*p)(void*, dyld_unwind_sections*) = NULL; @@ -1568,6 +1841,9 @@ void* _dyld_fast_stub_entry(void* loadercache, long lazyinfo) const char* dyld_image_path_containing_address(const void* addr) { + if ( gUseDyld3 ) + return dyld3::dyld_image_path_containing_address(addr); + DYLD_NO_LOCK_THIS_BLOCK; static const char* (*p)(const void*) = NULL; @@ -1578,6 +1854,9 @@ const char* dyld_image_path_containing_address(const void* addr) const struct mach_header* dyld_image_header_containing_address(const void* addr) { + if ( gUseDyld3 ) + return dyld3::dyld_image_header_containing_address(addr); + DYLD_NO_LOCK_THIS_BLOCK; static const mach_header* (*p)(const void*) = NULL; @@ -1589,6 +1868,9 @@ const struct mach_header* dyld_image_header_containing_address(const void* addr) bool dyld_shared_cache_some_image_overridden() { + if ( gUseDyld3 ) + return dyld3::dyld_shared_cache_some_image_overridden(); + DYLD_NO_LOCK_THIS_BLOCK; static bool (*p)() = NULL; @@ -1599,6 +1881,9 @@ bool dyld_shared_cache_some_image_overridden() bool _dyld_get_shared_cache_uuid(uuid_t uuid) { + if ( gUseDyld3 ) + return dyld3::_dyld_get_shared_cache_uuid(uuid); + DYLD_NO_LOCK_THIS_BLOCK; static bool (*p)(uuid_t) = NULL; @@ -1609,6 +1894,9 @@ bool _dyld_get_shared_cache_uuid(uuid_t uuid) const void* _dyld_get_shared_cache_range(size_t* length) { + if ( gUseDyld3 ) + return dyld3::_dyld_get_shared_cache_range(length); + DYLD_NO_LOCK_THIS_BLOCK; static const void* (*p)(size_t*) = NULL; @@ -1620,6 +1908,9 @@ const void* _dyld_get_shared_cache_range(size_t* length) bool dyld_process_is_restricted() { + if ( gUseDyld3 ) + return dyld3::dyld_process_is_restricted(); + DYLD_NO_LOCK_THIS_BLOCK; static bool (*p)() = NULL; @@ -1628,9 +1919,11 @@ bool dyld_process_is_restricted() return p(); } -#if DYLD_SHARED_CACHE_SUPPORT const char* dyld_shared_cache_file_path() { + if ( gUseDyld3 ) + return dyld3::dyld_shared_cache_file_path(); + DYLD_NO_LOCK_THIS_BLOCK; static const char* (*p)() = NULL; @@ -1638,10 +1931,12 @@ const char* dyld_shared_cache_file_path() _dyld_func_lookup("__dyld_shared_cache_file_path", (void**)&p); return p(); } -#endif void dyld_dynamic_interpose(const struct mach_header* mh, const struct dyld_interpose_tuple array[], size_t count) { + if ( gUseDyld3 ) + return dyld3::dyld_dynamic_interpose(mh, array, count); + DYLD_LOCK_THIS_BLOCK; static void (*p)(const struct mach_header* mh, const struct dyld_interpose_tuple array[], size_t count) = NULL; @@ -1654,6 +1949,9 @@ void dyld_dynamic_interpose(const struct mach_header* mh, const struct dyld_inte // SPI called __fork void _dyld_fork_child() { + if ( gUseDyld3 ) + return dyld3::_dyld_fork_child(); + DYLD_NO_LOCK_THIS_BLOCK; static void (*p)() = NULL; @@ -1724,14 +2022,17 @@ static const dyld_cache_header* findCacheInDirAndMap(const uuid_t cacheUuid, con int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extraSearchDirs[], void (^callback)(const dyld_shared_cache_dylib_text_info* info)) { + if ( gUseDyld3 ) + return dyld3::dyld_shared_cache_find_iterate_text(cacheUuid, extraSearchDirs, callback); + 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) ) { + if ( (allInfo != NULL) && (allInfo->sharedCacheBaseAddress != 0) && (memcmp(allInfo->sharedCacheUUID, cacheUuid, 16) == 0) ) { // requested cache is already mapped, just re-use it - cacheHeader = (dyld_cache_header*)(SHARED_REGION_BASE + allInfo->sharedCacheSlide); + cacheHeader = (dyld_cache_header*)(allInfo->sharedCacheBaseAddress); needToUnmap = false; } else { @@ -1763,15 +2064,18 @@ int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extr } // walk imageText table and call callback for each entry + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)cacheHeader + cacheHeader->mappingOffset); + const uint64_t cacheUnslidBaseAddress = mappings[0].address; 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.version = 2; dylibTextInfo.loadAddressUnslid = p->loadAddress; dylibTextInfo.textSegmentSize = p->textSegmentSize; dylibTextInfo.path = (char*)cacheHeader + p->pathOffset; ::memcpy(dylibTextInfo.dylibUuid, p->uuid, 16); + dylibTextInfo.textSegmentOffset = p->loadAddress - cacheUnslidBaseAddress; callback(&dylibTextInfo); } @@ -1783,6 +2087,9 @@ int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extr int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callback)(const dyld_shared_cache_dylib_text_info* info)) { + if ( gUseDyld3 ) + return dyld3::dyld_shared_cache_iterate_text(cacheUuid, callback); + const char* extraSearchDirs[] = { NULL }; return dyld_shared_cache_find_iterate_text(cacheUuid, extraSearchDirs, callback); } @@ -1790,6 +2097,9 @@ int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, void (^callback)(cons bool _dyld_is_memory_immutable(const void* addr, size_t length) { + if ( gUseDyld3 ) + return dyld3::_dyld_is_memory_immutable(addr, length); + DYLD_NO_LOCK_THIS_BLOCK; static bool (*p)(const void*, size_t) = NULL; @@ -1803,6 +2113,9 @@ void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) { + if ( gUseDyld3 ) + return dyld3::_dyld_objc_notify_register(mapped, init, unmapped); + DYLD_LOCK_THIS_BLOCK; static bool (*p)(_dyld_objc_notify_mapped, _dyld_objc_notify_init, _dyld_objc_notify_unmapped) = NULL; diff --git a/src/dyldSyscallInterface.h b/src/dyldSyscallInterface.h index 947d39a..c8b90d3 100644 --- a/src/dyldSyscallInterface.h +++ b/src/dyldSyscallInterface.h @@ -92,9 +92,12 @@ namespace dyld { 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); - }; - + kern_return_t (*task_register_dyld_get_process_state)(task_t task, dyld_kernel_process_info_t *dyld_process_state); + kern_return_t (*task_info)(task_name_t target_task, task_flavor_t flavor, task_info_t task_info_out, mach_msg_type_number_t *task_info_outCnt); + kern_return_t (*thread_info)(thread_inspect_t target_act, thread_flavor_t flavor, thread_info_t thread_info_out, mach_msg_type_number_t *thread_info_outCnt); + bool (*kdebug_is_enabled)(uint32_t code); + int (*kdebug_trace)(uint32_t code, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4); + }; extern const struct SyscallHelpers* gSyscallHelpers; diff --git a/src/dyld_gdb.cpp b/src/dyld_gdb.cpp index f13064d..5f4e7b5 100644 --- a/src/dyld_gdb.cpp +++ b/src/dyld_gdb.cpp @@ -36,6 +36,8 @@ #include "ImageLoader.h" #include "dyld.h" +extern "C" void _dyld_debugger_notification(enum dyld_notify_mode mode, unsigned long count, uint64_t machHeaders[]); + #if __IPHONE_OS_VERSION_MIN_REQUIRED #define INITIAL_UUID_IMAGE_COUNT 4 #else diff --git a/src/dyld_process_info.cpp b/src/dyld_process_info.cpp index 51f7ad4..c6ed46a 100644 --- a/src/dyld_process_info.cpp +++ b/src/dyld_process_info.cpp @@ -38,6 +38,62 @@ #include "dyld_images.h" #include "dyld_priv.h" +// this was in dyld_priv.h but it is no longer exported +extern "C" { + const struct dyld_all_image_infos* _dyld_get_all_image_infos(); +} + +namespace { + +void withRemoteBuffer(task_t task, vm_address_t remote_address, size_t remote_size, bool allow_truncation, kern_return_t *kr, void (^block)(void *buffer)) { + kern_return_t r = KERN_SUCCESS; + mach_vm_address_t local_address = 0; + mach_vm_address_t local_size = remote_size; + while (1) { + vm_prot_t cur_protection, max_protection; + r = mach_vm_remap(mach_task_self(), + &local_address, + local_size, + 0, // mask + VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR | VM_FLAGS_RESILIENT_CODESIGN, + task, + remote_address, + TRUE, // Copy semantics: changes to this memory by the target process will not be visible in this process + &cur_protection, + &max_protection, + VM_INHERIT_DEFAULT); + //Do this here to allow chaining of multiple embedded blocks with a single error out; + if (kr != NULL) { + *kr = r; + } + if (r == KERN_SUCCESS) { + // We got someting, call the block and then exit + block(reinterpret_cast(local_address)); + vm_deallocate(mach_task_self(), local_address, local_size); + break; + } + if (!allow_truncation) { + break; + } + // We did not get something, but we are allowed to truncate and try again + uint64_t trunc_size = ((remote_address + local_size - 1) & PAGE_MASK) + 1; + if (local_size <= trunc_size) { + //Even if we truncate it will be in the same page, time to accept defeat + break; + } else { + local_size -= trunc_size; + } + } +} + +template +void withRemoteObject(task_t task, vm_address_t remote_address, kern_return_t *kr, void (^block)(T t)) +{ + withRemoteBuffer(task, remote_address, sizeof(T), false, kr, ^(void *buffer) { + block(*reinterpret_cast(buffer)); + }); +} +}; // // Opaque object returned by _dyld_process_info_create() @@ -123,7 +179,10 @@ dyld_process_info_base* dyld_process_info_base::make(task_t task, const dyld_all { // 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)); + bool sameCacheAsThisProcess = !allImageInfo.processDetachedFromSharedRegion + && !myInfo->processDetachedFromSharedRegion + && ((memcmp(myInfo->sharedCacheUUID, allImageInfo.sharedCacheUUID, 16) == 0) + && (myInfo->sharedCacheSlide == allImageInfo.sharedCacheSlide)); unsigned countOfPathsNeedingCopying = 0; if ( sameCacheAsThisProcess ) { for (int i=0; i < allImageInfo.infoArrayCount; ++i) { @@ -181,7 +240,7 @@ dyld_process_info_base* dyld_process_info_base::make(task_t task, const dyld_all 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) ) { @@ -212,11 +271,13 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ return NULL; } - unsigned imageCount = 0; // main executable and dyld - uint64_t mainExecutableAddress = 0; - uint64_t dyldAddress = 0; + __block unsigned imageCount = 0; // main executable and dyld + __block uint64_t mainExecutableAddress = 0; + __block uint64_t dyldAddress = 0; char dyldPathBuffer[PATH_MAX+1]; char mainExecutablePathBuffer[PATH_MAX+1]; + __block char * dyldPath = &dyldPathBuffer[0]; + __block char * mainExecutablePath = &mainExecutablePathBuffer[0]; mach_vm_size_t size; for (mach_vm_address_t address = 0; ; address += size) { vm_region_basic_info_data_64_t info; @@ -226,31 +287,31 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ (vm_region_info_t)&info, &infoCount, &objectName); if ( result != KERN_SUCCESS ) break; - if ( info.protection == (VM_PROT_READ|VM_PROT_EXECUTE) ) { + if ( info.protection != (VM_PROT_READ|VM_PROT_EXECUTE) ) + continue; // read start of vm region to verify it is a mach header - mach_vm_size_t readSize = sizeof(mach_header_64); - mach_header_64 mhBuffer; - if ( mach_vm_read_overwrite(task, address, sizeof(mach_header_64), (vm_address_t)&mhBuffer, &readSize) != KERN_SUCCESS ) - continue; - if ( (mhBuffer.magic != MH_MAGIC) && (mhBuffer.magic != MH_MAGIC_64) ) - continue; - // now know the region is the start of a mach-o file - if ( mhBuffer.filetype == MH_EXECUTE ) { - mainExecutableAddress = address; - int len = proc_regionfilename(pid, mainExecutableAddress, mainExecutablePathBuffer, PATH_MAX); - if ( len != 0 ) - mainExecutablePathBuffer[len] = '\0'; - ++imageCount; - } - else if ( mhBuffer.filetype == MH_DYLINKER ) { - dyldAddress = address; - int len = proc_regionfilename(pid, dyldAddress, dyldPathBuffer, PATH_MAX); - if ( len != 0 ) - dyldPathBuffer[len] = '\0'; - ++imageCount; - } + withRemoteObject(task, address, NULL, ^(mach_header_64 mhBuffer){ + if ( (mhBuffer.magic != MH_MAGIC) && (mhBuffer.magic != MH_MAGIC_64) ) + return; + // now know the region is the start of a mach-o file + if ( mhBuffer.filetype == MH_EXECUTE ) { + mainExecutableAddress = address; + int len = proc_regionfilename(pid, mainExecutableAddress, mainExecutablePath, PATH_MAX); + if ( len != 0 ) { + mainExecutablePath[len] = '\0'; + } + ++imageCount; + } + else if ( mhBuffer.filetype == MH_DYLINKER ) { + dyldAddress = address; + int len = proc_regionfilename(pid, dyldAddress, dyldPath, PATH_MAX); + if ( len != 0 ) { + dyldPath[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); @@ -280,7 +341,7 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ // fill in info for dyld if ( dyldAddress != 0 ) { - if ( kern_return_t r = obj->addDyldImage(task, dyldAddress, 0, dyldPathBuffer) ) { + if ( kern_return_t r = obj->addDyldImage(task, dyldAddress, 0, dyldPath) ) { if ( kr != NULL ) *kr = r; free(obj); @@ -290,7 +351,7 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ // fill in info for each image if ( mainExecutableAddress != 0 ) { - if ( kern_return_t r = obj->addImage(task, false, mainExecutableAddress, 0, mainExecutablePathBuffer) ) { + if ( kern_return_t r = obj->addImage(task, false, mainExecutableAddress, 0, mainExecutablePath) ) { if ( kr != NULL ) *kr = r; free(obj); @@ -313,42 +374,13 @@ const char* dyld_process_info_base::addString(const char* str) 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); + __block const char* retval = NULL; + withRemoteBuffer(task, stringAddressInTask, PATH_MAX+8, true, kr, ^(void *buffer) { + retval = addString(static_cast(buffer)); + }); + return retval; } - 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; @@ -369,19 +401,16 @@ kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThis 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; + __block kern_return_t kr = KERN_SUCCESS; + withRemoteObject(task, imageAddress, &kr, ^(mach_header_64 mhBuffer) { + size_t headerPagesSize = sizeof(mach_header_64) + mhBuffer.sizeofcmds; + withRemoteBuffer(task, imageAddress, headerPagesSize, false, &kr, ^(void * buffer) { + addInfoFromLoadCommands((mach_header*)buffer, imageAddress, headerPagesSize); + }); + }); + if (kr != KERN_SUCCESS) { + return kr; } - addInfoFromLoadCommands((mach_header*)localCopyBuffer, imageAddress, localCopyBufferSize); - vm_deallocate(mach_task_self(), localCopyBuffer, localCopyBufferSize); } _curImage->segmentsCount = _curSegmentIndex - _curImage->segmentStartIndex; _curImage++; @@ -391,7 +420,7 @@ kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThis kern_return_t dyld_process_info_base::addDyldImage(task_t task, uint64_t dyldAddress, uint64_t dyldPathAddress, const char* localPath) { - kern_return_t kr; + __block kern_return_t kr = KERN_SUCCESS; _curImage->loadAddress = dyldAddress; _curImage->segmentStartIndex = _curSegmentIndex; if ( localPath != NULL ) { @@ -403,19 +432,16 @@ kern_return_t dyld_process_info_base::addDyldImage(task_t task, uint64_t dyldAdd 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; + withRemoteObject(task, dyldAddress, &kr, ^(mach_header_64 mhBuffer) { + size_t headerPagesSize = sizeof(mach_header_64) + mhBuffer.sizeofcmds; + withRemoteBuffer(task, dyldAddress, headerPagesSize, false, &kr, ^(void * buffer) { + addInfoFromLoadCommands((mach_header*)buffer, dyldAddress, headerPagesSize); + }); + }); + if (kr != KERN_SUCCESS) { + return kr; } - addInfoFromLoadCommands((mach_header*)localCopyBuffer, dyldAddress, localCopyBufferSize); - vm_deallocate(mach_task_self(), localCopyBuffer, localCopyBufferSize); + _curImage->segmentsCount = _curSegmentIndex - _curImage->segmentStartIndex; _curImage++; return KERN_SUCCESS; @@ -431,7 +457,7 @@ void dyld_process_info_base::addInfoFromLoadCommands(const mach_header* mh, uint 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) { diff --git a/src/dyld_process_info_internal.h b/src/dyld_process_info_internal.h index 057278a..cd8c607 100644 --- a/src/dyld_process_info_internal.h +++ b/src/dyld_process_info_internal.h @@ -59,8 +59,10 @@ struct dyld_all_image_infos_32 { uint32_t sharedCacheBaseAddress; uint64_t infoArrayChangeTimestamp; uint32_t dyldPath; - uint32_t notifyMachPorts[2]; - uint32_t reserved[11]; + uint32_t notifyMachPorts[8]; + uint32_t reserved[5]; + uint32_t compact_dyld_image_info_addr; + uint32_t compact_dyld_image_info_size; }; struct dyld_all_image_infos_64 { @@ -91,8 +93,10 @@ struct dyld_all_image_infos_64 { uint64_t sharedCacheBaseAddress; uint64_t infoArrayChangeTimestamp; uint64_t dyldPath; - uint32_t notifyMachPorts[2]; - uint64_t reserved[12]; + uint32_t notifyMachPorts[8]; + uint64_t reserved[9]; + uint64_t compact_dyld_image_info_addr; + uint64_t compact_dyld_image_info_size; }; struct dyld_image_info_32 { diff --git a/src/dyld_process_info_notify.cpp b/src/dyld_process_info_notify.cpp index 760f06c..6d5eee3 100644 --- a/src/dyld_process_info_notify.cpp +++ b/src/dyld_process_info_notify.cpp @@ -35,6 +35,11 @@ #include "dyld_images.h" #include "dyld_priv.h" +#include "LaunchCache.h" +#include "Loading.h" +#include "AllImages.h" + + typedef void (^Notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path); typedef void (^NotifyExit)(); typedef void (^NotifyMain)(); @@ -46,18 +51,21 @@ typedef void (^NotifyMain)(); 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(); + ~dyld_process_info_notify_base(); uint32_t& retainCount() const { return _retainCount; } void setNotifyMain(NotifyMain notifyMain) const { _notifyMain = notifyMain; } + // override new and delete so we don't need to link with libc++ + static void* operator new(size_t sz) { return malloc(sz); } + static void operator delete(void* p) { return free(p); } + 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; @@ -101,8 +109,7 @@ dyld_process_info_notify_base::~dyld_process_info_notify_base() 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); + dyld_process_info_notify_base* obj = new dyld_process_info_notify_base(queue, notify, notifyExit, task); if ( kern_return_t r = obj->makePorts() ) { if ( kr != NULL ) @@ -123,7 +130,7 @@ dyld_process_info_notify_base* dyld_process_info_notify_base::make(task_t task, return obj; fail: - free(obj); + delete obj; return NULL; } @@ -322,11 +329,178 @@ void _dyld_process_info_notify_retain(dyld_process_info_notify object) 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); - } + if ( object->retainCount() == 0 ) + delete object; +} + + + + + + + +namespace dyld3 { + + +static mach_port_t sNotifyReplyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; +static bool sZombieNotifiers[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; + +static void notifyMonitoringDyld(bool unloading, unsigned portSlot, const launch_cache::DynArray& imageInfos) +{ + if ( sZombieNotifiers[portSlot] ) + return; + + unsigned entriesSize = (unsigned)imageInfos.count()*sizeof(dyld_process_info_image_entry); + unsigned pathsSize = 0; + for (uintptr_t i=0; i < imageInfos.count(); ++i) { + launch_cache::Image image(imageInfos[i].imageData); + pathsSize += (strlen(image.path()) + 1); + } + unsigned totalSize = (sizeof(dyld_process_info_notify_header) + MAX_TRAILER_SIZE + 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 = (unsigned)imageInfos.count()/2; + const launch_cache::DynArray firstHalf(imageHalfCount, (loader::ImageInfo*)&imageInfos[0]); + const launch_cache::DynArray secondHalf(imageInfos.count() - imageHalfCount, (loader::ImageInfo*)&imageInfos[imageHalfCount]); + notifyMonitoringDyld(unloading, portSlot, firstHalf); + notifyMonitoringDyld(unloading, portSlot, secondHalf); + return; + } + // build buffer to send + dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo(); + uint8_t buffer[totalSize]; + dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)buffer; + header->version = 1; + header->imageCount = (uint32_t)imageInfos.count(); + header->imagesOffset = sizeof(dyld_process_info_notify_header); + header->stringsOffset = sizeof(dyld_process_info_notify_header) + entriesSize; + header->timestamp = allImageInfo->infoArrayChangeTimestamp; + 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 (uintptr_t i=0; i < imageInfos.count(); ++i) { + launch_cache::Image image(imageInfos[i].imageData); + strcpy(pathPool, image.path()); + uint32_t len = (uint32_t)strlen(pathPool); + memcpy(entries->uuid, image.uuid(), sizeof(uuid_t)); + entries->loadAddress = (uint64_t)imageInfos[i].loadAddress; + entries->pathStringOffset = (uint32_t)(pathPool - pathPoolStart); + entries->pathLength = len; + pathPool += (len +1); + ++entries; + } + // lazily alloc reply port + 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); + //log("allocated reply port %d\n", sNotifyReplyPorts[portSlot]); + } + //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 = allImageInfo->notifyPorts[portSlot]; + h->msgh_reserved = 0; + h->msgh_size = (mach_msg_size_t)sizeof(buffer); + //log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", portSlot, allImageInfo->notifyPorts[portSlot], h->msgh_size, sNotifyReplyPorts[portSlot], h->msgh_id); + kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_RCV_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[portSlot], 2000, MACH_PORT_NULL); + //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 + //log("process requesting notification gone. deallocation send port %d and receive port %d\n", allImageInfo->notifyPorts[portSlot], sNotifyReplyPorts[portSlot]); + mach_port_deallocate(mach_task_self(), allImageInfo->notifyPorts[portSlot]); + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]); + allImageInfo->notifyPorts[portSlot] = 0; + sNotifyReplyPorts[portSlot] = 0; + } + else if ( sendResult == MACH_RCV_TIMED_OUT ) { + // client took too long, ignore him from now on + sZombieNotifiers[portSlot] = true; + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]); + sNotifyReplyPorts[portSlot] = 0; + } } +void AllImages::notifyMonitorMain() +{ + dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo(); + for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { + if ( (allImageInfo->notifyPorts[slot] != 0 ) && !sZombieNotifiers[slot] ) { + if ( sNotifyReplyPorts[slot] == 0 ) { + if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[slot]) ) + mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[slot], sNotifyReplyPorts[slot], MACH_MSG_TYPE_MAKE_SEND); + //dyld::log("allocated reply port %d\n", sNotifyReplyPorts[slot]); + } + //dyld::log("found port to send to\n"); + uint8_t messageBuffer[sizeof(mach_msg_header_t) + MAX_TRAILER_SIZE]; + mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer; + 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 = DYLD_PROCESS_INFO_NOTIFY_MAIN_ID; + h->msgh_local_port = sNotifyReplyPorts[slot]; + h->msgh_remote_port = allImageInfo->notifyPorts[slot]; + h->msgh_reserved = 0; + h->msgh_size = (mach_msg_size_t)sizeof(messageBuffer); + //dyld::log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", slot, allImageInfo->notifyPorts[slot], h->msgh_size, sNotifyReplyPorts[slot], h->msgh_id); + kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_RCV_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[slot], 2000, 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", allImageInfo->notifyPorts[slot], sNotifyReplyPorts[slot]); + mach_port_deallocate(mach_task_self(), allImageInfo->notifyPorts[slot]); + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); + allImageInfo->notifyPorts[slot] = 0; + sNotifyReplyPorts[slot] = 0; + } + else if ( sendResult == MACH_RCV_TIMED_OUT ) { + // client took too long, ignore him from now on + sZombieNotifiers[slot] = true; + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); + sNotifyReplyPorts[slot] = 0; + } + } + } +} + +void AllImages::notifyMonitorLoads(const launch_cache::DynArray& newImages) +{ + // notify each monitoring process + dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo(); + for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { + if ( allImageInfo->notifyPorts[slot] != 0 ) { + notifyMonitoringDyld(false, slot, newImages); + } + 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; + sZombieNotifiers[slot] = false; + } + } +} + +void AllImages::notifyMonitorUnloads(const launch_cache::DynArray& unloadingImages) +{ + // notify each monitoring process + dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo(); + for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { + if ( allImageInfo->notifyPorts[slot] != 0 ) { + notifyMonitoringDyld(true, slot, unloadingImages); + } + 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; + sZombieNotifiers[slot] = false; + } + } +} + +} // namespace dyld3 + + diff --git a/src/glue.c b/src/glue.c index 6eeed0a..99a6644 100644 --- a/src/glue.c +++ b/src/glue.c @@ -108,6 +108,38 @@ void _ZN10__cxxabiv112__unexpectedEPFvvE() _ZN4dyld4haltEPKc("dyld std::__unexpected()\n"); } +// std::__terminate() called by C++ unwinding code +void _ZSt11__terminatePFvvE(void (*func)()) +{ + _ZN4dyld4haltEPKc("dyld std::__terminate()\n"); +} + +// std::__unexpected() called by C++ unwinding code +void _ZSt12__unexpectedPFvvE(void (*func)()) +{ + _ZN4dyld4haltEPKc("dyld std::__unexpected()\n"); +} + +// terminate_handler get_terminate() +void* _ZSt13get_terminatev() +{ + return NULL; +} + +// unexpected_handler get_unexpected() +void* _ZSt14get_unexpectedv() +{ + return NULL; +} + +// new_handler get_new_handler() +void* _ZSt15get_new_handlerv() +{ + return NULL; +} + + + // __cxxabiv1::__terminate_handler void* _ZN10__cxxabiv119__terminate_handlerE = &_ZSt9terminatev; @@ -425,6 +457,16 @@ void _ZN4dyld3logEPKcz(const char* format, ...) { va_end(list); } +#if __i386__ +void _ZN4dyld4vlogEPKcPc(const char* format, va_list list) { +#else +void _ZN4dyld4vlogEPKcP13__va_list_tag(const char* format, va_list list) { +#endif + gSyscallHelpers->vlog(format, list); +} + + + void _ZN4dyld4warnEPKcz(const char* format, ...) { va_list list; va_start(list, format); @@ -708,6 +750,30 @@ kern_return_t task_register_dyld_get_process_state(task_t task, dyld_kernel_proc return KERN_NOT_SUPPORTED; } +kern_return_t task_info(task_name_t target_task, task_flavor_t flavor, task_info_t task_info_out, mach_msg_type_number_t *task_info_outCnt) { + if ( gSyscallHelpers->version >= 8 ) + return gSyscallHelpers->task_info(target_task, flavor, task_info_out, task_info_outCnt); + return KERN_NOT_SUPPORTED; +} + +kern_return_t thread_info(thread_inspect_t target_act, thread_flavor_t flavor, thread_info_t thread_info_out, mach_msg_type_number_t *thread_info_outCnt) { + if ( gSyscallHelpers->version >= 8 ) + return gSyscallHelpers->task_info(target_act, flavor, thread_info_out, thread_info_outCnt); + return KERN_NOT_SUPPORTED; +} + +bool kdebug_is_enabled(uint32_t code) { + if ( gSyscallHelpers->version >= 8 ) + return gSyscallHelpers->kdebug_is_enabled(code); + return false; +} + +int kdebug_trace(uint32_t code, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4) { + if ( gSyscallHelpers->version >= 8 ) + return gSyscallHelpers->kdebug_trace(code, arg1, arg2, arg3, arg4); + return 0; +} + int* __error(void) { return gSyscallHelpers->errnoAddress(); } @@ -735,5 +801,20 @@ int myerrno_fallback = 0; #endif +void* _NSConcreteStackBlock[32]; +void* _NSConcreteGlobalBlock[32]; + +void _Block_object_assign() +{ + _ZN4dyld4haltEPKc("_Block_object_assign()"); +} + +void _Block_object_dispose(const void* object, int flags) +{ + // only support stack blocks in dyld: BLOCK_FIELD_IS_BYREF=8 + if ( flags != 8 ) + _ZN4dyld4haltEPKc("_Block_object_dispose()"); +} + diff --git a/src/threadLocalVariables.c b/src/threadLocalVariables.c index 8132225..a0b3661 100644 --- a/src/threadLocalVariables.c +++ b/src/threadLocalVariables.c @@ -214,6 +214,9 @@ void* tlv_allocate_and_initialize_for_key(pthread_key_t key) } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } + // no thread local storage in image: should never happen + if ( size == 0 ) + return NULL; // allocate buffer and fill with template void* buffer = malloc(size); @@ -306,7 +309,7 @@ static void tlv_initialize_descriptors(const struct mach_header* mh) } -void tlv_load_notification(const struct mach_header* mh, intptr_t slide) +static 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. @@ -394,7 +397,7 @@ void _tlv_atexit(TermFunc func, void* objAddr) pthread_setspecific(tlv_terminators_key, list); } else { - if ( list->allocCount == list->allocCount ) { + if ( list->useCount == list->allocCount ) { // handle resizing allocation uint32_t newAllocCount = list->allocCount * 2; size_t newAllocSize = offsetof(struct TLVTerminatorList, entries[newAllocCount]); diff --git a/testing/build_tests.py b/testing/build_tests.py index 4bb4482..ab4d4fd 100755 --- a/testing/build_tests.py +++ b/testing/build_tests.py @@ -133,15 +133,24 @@ if __name__ == "__main__": 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", "/") + dyldSrcDir = os.getenv("SRCROOT", "") + if not dyldSrcDir: + dyldSrcDir = os.getcwd() + testsSrcTopDir = dyldSrcDir + "/testing/test-cases/" + sdkDir = os.getenv("SDKROOT", "") + if not sdkDir: + #sdkDir = subprocess.check_output(["xcrun", "--show-sdk-path"]).rstrip() + sdkDir = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.Internal.sdk" toolsDir = os.getenv("TOOLCHAIN_DIR", "/") defaultMinOS = "" + minVersNum = "10.12" minOSOption = os.getenv("DEPLOYMENT_TARGET_CLANG_FLAG_NAME", "") if minOSOption: minOSVersName = os.getenv("DEPLOYMENT_TARGET_CLANG_ENV_NAME", "") if minOSVersName: minVersNum = os.getenv(minOSVersName, "") + else: + minOSOption = "mmacosx-version-min" platformName = os.getenv("PLATFORM_NAME", "osx") archOptions = "" archList = os.getenv("RC_ARCHS", "") @@ -155,9 +164,11 @@ if __name__ == "__main__": elif platformName == "appletvos": archOptions = "-arch arm64" else: - archOptions = "" - for arch in string.split(archList, " "): - archOptions = archOptions + " -arch " + arch + if archList: + for arch in string.split(archList, " "): + archOptions = archOptions + " -arch " + arch + else: + archOptions = "-arch x86_64" allTests = [] for f in os.listdir(testsSrcTopDir): if f.endswith(".dtest"): diff --git a/testing/test-cases/NSAddImage-fail.dtest/main.c b/testing/test-cases/NSAddImage-fail.dtest/main.c new file mode 100644 index 0000000..74bf245 --- /dev/null +++ b/testing/test-cases/NSAddImage-fail.dtest/main.c @@ -0,0 +1,35 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC main.c -o $BUILD_DIR/NSAddImage-fail.exe -Wno-deprecated-declarations + +// RUN: ./NSAddImage-fail.exe return +// RUN: NOCR_TEST_NAME="NSAddImage-fail expected abort" $REQUIRE_CRASH ./NSAddImage-fail.exe abort + + + +#include +#include +#include +#include + + +int main(int argc, const char* argv[]) +{ + const char* arg = argv[1]; + + if ( strcmp(arg, "return") == 0 ) { + printf("[BEGIN] NSAddImage-fail %s\n", arg); + const struct mach_header* mh = NSAddImage("/xqz/42/libnotfound.xxx", NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED); + if ( mh == NULL ) + printf("[PASS] NSAddImage-fail %s\n", arg); + else + printf("[FAIL] NSAddImage-fail %s\n", arg); + } + else { + // run with nocr which print BEGIN/PASS/FAIL + NSAddImage("/xqz/42/libnotfound.xxx", 0); + } + + return 0; +} + diff --git a/testing/test-cases/NSAddImage-loaded.dtest/main.c b/testing/test-cases/NSAddImage-loaded.dtest/main.c new file mode 100644 index 0000000..db7ca4b --- /dev/null +++ b/testing/test-cases/NSAddImage-loaded.dtest/main.c @@ -0,0 +1,33 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC main.c -o $BUILD_DIR/NSAddImage-loaded.exe -Wno-deprecated-declarations + +// RUN: ./NSAddImage-loaded.exe return + + + +#include +#include +#include +#include + + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] NSAddImage-loaded\n"); + + // verify value is returned for image already loaded + const struct mach_header* mh = NSAddImage("/usr/lib/libSystem.B.dylib", NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED); + if ( mh == NULL ) + printf("[FAIL] NSAddImage-loaded\n"); + + // verify existing dylib is not loaded if it is not already loaded + mh = NSAddImage("/usr/lib/libz.dylib", NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED); + if ( mh != NULL ) + printf("[FAIL] NSAddImage-loaded\n"); + + printf("[PASS] NSAddImage-loaded\n"); + + return 0; +} + diff --git a/testing/test-cases/NSAddressOfSymbol-basic.dtest/main.c b/testing/test-cases/NSAddressOfSymbol-basic.dtest/main.c new file mode 100644 index 0000000..ddd9c74 --- /dev/null +++ b/testing/test-cases/NSAddressOfSymbol-basic.dtest/main.c @@ -0,0 +1,40 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC main.c -o $BUILD_DIR/NSAddressOfSymbol-basic.exe -Wno-deprecated-declarations + +// RUN: ./NSAddressOfSymbol-basic.exe + + + +#include +#include +#include +#include + +extern struct mach_header __dso_handle; + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] NSAddressOfSymbol-basic\n"); + + NSSymbol sym = NSLookupSymbolInImage(&__dso_handle, "_main", NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR); + if ( sym == NULL ) { + printf("[FAIL] NSAddressOfSymbol-basic can't find main\n"); + return 0; + } + void* mainAddr = NSAddressOfSymbol(sym); + if ( mainAddr != &main ) { + printf("[FAIL] NSAddressOfSymbol-basic address returned %p is not &main=%p\n", mainAddr, &main); + return 0; + } + + // verify NULL works + if ( NSAddressOfSymbol(NULL) != NULL ) { + printf("[FAIL] NSAddressOfSymbol-basic NULL not handle\n"); + return 0; + } + + printf("[PASS] NSAddressOfSymbol-basic\n"); + return 0; +} + diff --git a/testing/test-cases/NSCreateObjectFileImageFromFile-basic.dtest/foo.c b/testing/test-cases/NSCreateObjectFileImageFromFile-basic.dtest/foo.c new file mode 100644 index 0000000..837ec00 --- /dev/null +++ b/testing/test-cases/NSCreateObjectFileImageFromFile-basic.dtest/foo.c @@ -0,0 +1,7 @@ + +#include +#include + +void fooInBundle() +{ +} diff --git a/testing/test-cases/NSCreateObjectFileImageFromFile-basic.dtest/main.c b/testing/test-cases/NSCreateObjectFileImageFromFile-basic.dtest/main.c new file mode 100644 index 0000000..49341b3 --- /dev/null +++ b/testing/test-cases/NSCreateObjectFileImageFromFile-basic.dtest/main.c @@ -0,0 +1,70 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC main.c -o $BUILD_DIR/NSCreateObjectFileImageFromFile-basic.exe -Wno-deprecated-declarations +// BUILD: $CC foo.c -o $BUILD_DIR/foo.bundle -bundle + +// RUN: ./NSCreateObjectFileImageFromFile-basic.exe $RUN_DIR/foo.bundle + + +#include +#include +#include +#include + + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] NSCreateObjectFileImageFromFile-basic\n"); + + const char* path = argv[1]; + + NSObjectFileImage ofi; + if ( NSCreateObjectFileImageFromFile(path, &ofi) != NSObjectFileImageSuccess ) { + printf("[FAIL] NSCreateObjectFileImageFromFile failed\n"); + return 0; + } + + NSModule mod = NSLinkModule(ofi, path, NSLINKMODULE_OPTION_NONE); + if ( mod == NULL ) { + printf("[FAIL] NSLinkModule failed\n"); + return 0; + } + + NSSymbol sym = NSLookupSymbolInModule(mod, "_fooInBundle"); + if ( sym == NULL ) { + printf("[FAIL] NSLookupSymbolInModule failed\n"); + return 0; + } + + void* func = NSAddressOfSymbol(sym); + if ( func == NULL ) { + printf("[FAIL] NSAddressOfSymbol failed\n"); + return 0; + } + + Dl_info info; + if ( dladdr(func, &info) == 0 ) { + printf("[FAIL] dladdr(&p, xx) failed"); + return 0; + } + //printf("_fooInBundle found in %s\n", info.dli_fname); + + if ( !NSUnLinkModule(mod, NSUNLINKMODULE_OPTION_NONE) ) { + printf("[FAIL] NSUnLinkModule failed\n"); + return 0; + } + + if ( dladdr(func, &info) != 0 ) { + printf("[FAIL] dladdr(&p, xx) found but should not have\n"); + return 0; + } + + if ( !NSDestroyObjectFileImage(ofi) ) { + printf("[FAIL] NSDestroyObjectFileImage failed\n"); + return 0; + } + + printf("[PASS] NSCreateObjectFileImageFromFile-basic\n"); + return 0; +} + diff --git a/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/foo.c b/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/foo.c new file mode 100644 index 0000000..837ec00 --- /dev/null +++ b/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/foo.c @@ -0,0 +1,7 @@ + +#include +#include + +void fooInBundle() +{ +} diff --git a/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/main.c b/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/main.c new file mode 100644 index 0000000..888c043 --- /dev/null +++ b/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/main.c @@ -0,0 +1,115 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC main.c -o $BUILD_DIR/NSCreateObjectFileImageFromMemory-basic.exe -Wno-deprecated-declarations +// BUILD: $CC foo.c -o $BUILD_DIR/foo.bundle -bundle + +// RUN: ./NSCreateObjectFileImageFromMemory-basic.exe $RUN_DIR/foo.bundle + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static void checkBundle(const char* path, bool unlinkBeforeDestroy) +{ + int fd = open(path, O_RDONLY, 0); + if ( fd == -1 ) { + printf("[FAIL] open(%s) failed", path); + exit(0); + } + + struct stat stat_buf; + if ( fstat(fd, &stat_buf) == -1) { + printf("[FAIL] fstat() failed\n"); + exit(0); + } + + void* loadAddress = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0); + if ( loadAddress == ((void*)(-1)) ) { + printf("[FAIL] mmap() failed\n"); + exit(0); + } + + close(fd); + + NSObjectFileImage ofi; + if ( NSCreateObjectFileImageFromMemory(loadAddress, stat_buf.st_size, &ofi) != NSObjectFileImageSuccess ) { + printf("[FAIL] NSCreateObjectFileImageFromMemory failed\n"); + exit(0); + } + + NSModule mod = NSLinkModule(ofi, path, NSLINKMODULE_OPTION_NONE); + if ( mod == NULL ) { + printf("[FAIL] NSLinkModule failed\n"); + exit(0); + } + + if ( !unlinkBeforeDestroy ) { + // API lets you destroy ofi and NSModule lives on + if ( !NSDestroyObjectFileImage(ofi) ) { + printf("[FAIL] NSDestroyObjectFileImage failed\n"); + exit(0); + } + } + + NSSymbol sym = NSLookupSymbolInModule(mod, "_fooInBundle"); + if ( sym == NULL ) { + printf("[FAIL] NSLookupSymbolInModule failed\n"); + exit(0); + } + + void* func = NSAddressOfSymbol(sym); + if ( func == NULL ) { + printf("[FAIL] NSAddressOfSymbol failed\n"); + exit(0); + } + + Dl_info info; + if ( dladdr(func, &info) == 0 ) { + printf("[FAIL] dladdr(&p, xx) failed\n"); + exit(0); + } + //printf("_fooInBundle found in %s\n", info.dli_fname); + + if ( !NSUnLinkModule(mod, NSUNLINKMODULE_OPTION_NONE) ) { + printf("[FAIL] NSUnLinkModule failed\n"); + exit(0); + } + + if ( dladdr(func, &info) != 0 ) { + printf("[FAIL] dladdr(&p, xx) found but should not have\n"); + exit(0); + } + + if ( unlinkBeforeDestroy ) { + if ( !NSDestroyObjectFileImage(ofi) ) { + printf("[FAIL] NSDestroyObjectFileImage failed\n"); + exit(0); + } + } +} + + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] NSCreateObjectFileImageFromMemory-basic\n"); + + checkBundle(argv[1], true); + checkBundle(argv[1], false); + + // Now go again enough times to flush out any limits in our dlopen encodings. + for (unsigned i = 0; i != 255; ++i) + checkBundle(argv[1], false); + + printf("[PASS] NSCreateObjectFileImageFromMemory-basic\n"); + return 0; +} + diff --git a/testing/test-cases/NSLookupSymbolInImage-basic.dtest/main.c b/testing/test-cases/NSLookupSymbolInImage-basic.dtest/main.c new file mode 100644 index 0000000..1adab58 --- /dev/null +++ b/testing/test-cases/NSLookupSymbolInImage-basic.dtest/main.c @@ -0,0 +1,39 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC main.c -o $BUILD_DIR/NSLookupSymbolInImage-basic.exe -Wno-deprecated-declarations + +// RUN: ./NSLookupSymbolInImage-basic.exe + + +#include +#include +#include +#include + +extern struct mach_header __dso_handle; + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] NSLookupSymbolInImage-basic\n"); + + // verify known symbol works + NSSymbol sym = NSLookupSymbolInImage(&__dso_handle, "_main", NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR); + if ( sym == NULL ) { + printf("[FAIL] NSLookupSymbolInImage-basic _main\n"); + return 0; + } + + // verify mode where NSLookupSymbolInImage() returns NULL if symbol not found + sym = NSLookupSymbolInImage(&__dso_handle, "_42hhg", NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR); + if ( sym != NULL ) { + printf("[FAIL] NSLookupSymbolInImage-basic _42hhg\n"); + return 0; + } + + // Note: NSLookupSymbolInImage is documented to abort if symbol not found and NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR not used, + // but dyld 2 just returned NULL, so no need to test that. + + printf("[PASS] NSLookupSymbolInImage-basic\n"); + return 0; +} + diff --git a/testing/test-cases/_dyld_is_memory_immutable.dtest/bar.c b/testing/test-cases/_dyld_is_memory_immutable.dtest/bar.c new file mode 100644 index 0000000..70b4d1b --- /dev/null +++ b/testing/test-cases/_dyld_is_memory_immutable.dtest/bar.c @@ -0,0 +1,4 @@ +const char* bar() +{ + return "bar"; +} diff --git a/testing/test-cases/_dyld_is_memory_immutable.dtest/foo.c b/testing/test-cases/_dyld_is_memory_immutable.dtest/foo.c new file mode 100644 index 0000000..a5db092 --- /dev/null +++ b/testing/test-cases/_dyld_is_memory_immutable.dtest/foo.c @@ -0,0 +1,4 @@ +const char* foo() +{ + return "foo"; +} diff --git a/testing/test-cases/_dyld_is_memory_immutable.dtest/main.c b/testing/test-cases/_dyld_is_memory_immutable.dtest/main.c new file mode 100644 index 0000000..a58b85b --- /dev/null +++ b/testing/test-cases/_dyld_is_memory_immutable.dtest/main.c @@ -0,0 +1,79 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CC bar.c -dynamiclib -install_name $RUN_DIR/libbar.dylib -o $BUILD_DIR/libbar.dylib +// BUILD: $CC main.c -DRUN_DIR="$RUN_DIR" $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/dyld_immutable_test.exe + +// RUN: ./dyld_immutable_test.exe + +#include +#include +#include +#include +#include + +typedef const char* (*BarProc)(void); + +extern uint32_t _cpu_capabilities; +extern const char* foo(); + +const char* myStr = "myStr"; + +int myInt; + + +int main() +{ + printf("[BEGIN] _dyld_is_memory_immutable\n"); + + if ( !_dyld_is_memory_immutable(myStr, 6) ) { + printf("[FAIL] _dyld_is_memory_immutable() returned false for string in main executable\n"); + return 0; + } + + if ( _dyld_is_memory_immutable(strdup("hello"), 6) ) { + printf("[FAIL] _dyld_is_memory_immutable() returned true for result from strdup()\n"); + return 0; + } + + if ( _dyld_is_memory_immutable(&myInt, 4) ) { + printf("[FAIL] _dyld_is_memory_immutable() returned true for global variabe in main executable\n"); + return 0; + } + + if ( !_dyld_is_memory_immutable(foo(), 4) ) { + printf("[FAIL] _dyld_is_memory_immutable() returned false for string in statically linked dylib\n"); + return 0; + } + + if ( !_dyld_is_memory_immutable(&strcpy, 4) ) { + printf("[FAIL] _dyld_is_memory_immutable() returned false for function in dyld shared cache\n"); + return 0; + } + + if ( _dyld_is_memory_immutable(&_cpu_capabilities, 4) ) { + printf("[FAIL] _dyld_is_memory_immutable() returned true for global variable in shared cache\n"); + return 0; + } + + void* handle = dlopen(RUN_DIR "/libbar.dylib", RTLD_FIRST); + if ( handle == NULL ) { + printf("[FAIL] dlopen(libbar.dylib) failed"); + return 0; + } + + BarProc proc = dlsym(handle, "bar"); + if ( proc == NULL ) { + printf("[FAIL] dlsym(bar) failed\n"); + return 0; + } + const char* barStr = (*proc)(); + if ( _dyld_is_memory_immutable(barStr, 4) ) { + printf("[FAIL] _dyld_is_memory_immutable() returned true for string in unloadable dylib\n"); + return 0; + } + + + printf("[PASS] _dyld_is_memory_immutable\n"); + return 0; +} + diff --git a/testing/test-cases/_dyld_register_func_for_add_image.dtest/foo.c b/testing/test-cases/_dyld_register_func_for_add_image.dtest/foo.c new file mode 100644 index 0000000..85e6cd8 --- /dev/null +++ b/testing/test-cases/_dyld_register_func_for_add_image.dtest/foo.c @@ -0,0 +1 @@ +void foo() {} diff --git a/testing/test-cases/_dyld_register_func_for_add_image.dtest/main.cxx b/testing/test-cases/_dyld_register_func_for_add_image.dtest/main.cxx new file mode 100644 index 0000000..905a153 --- /dev/null +++ b/testing/test-cases/_dyld_register_func_for_add_image.dtest/main.cxx @@ -0,0 +1,77 @@ + +// BUILD: $CXX main.cxx -o $BUILD_DIR/dyld_register_test.exe -DRUN_DIR="$RUN_DIR" +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib + +// RUN: ./dyld_register_test.exe + +#include +#include +#include +#include +#include + +#include + +extern mach_header __dso_handle; + +static std::unordered_set sCurrentImages; + +static void notify(const mach_header* mh, intptr_t vmaddr_slide) +{ + //fprintf(stderr, "mh=%p\n", mh); + if ( sCurrentImages.count(mh) != 0 ) { + printf("[FAIL] _dyld_register_func_for_add_image: notified twice about %p\n", mh); + exit(0); + } + sCurrentImages.insert(mh); +} + + +int main() +{ + printf("[BEGIN] _dyld_register_func_for_add_image\n"); + + _dyld_register_func_for_add_image(¬ify); + + // verify we were notified about already loaded images + if ( sCurrentImages.count(&__dso_handle) == 0 ) { + printf("[FAIL] _dyld_register_func_for_add_image() did not notify us about main executable"); + exit(0); + } + const mach_header* libSysMH = dyld_image_header_containing_address((void*)&printf); + if ( sCurrentImages.count(libSysMH) == 0 ) { + printf("[FAIL] _dyld_register_func_for_add_image() did not notify us about libsystem_c.dylib"); + exit(0); + } + + void* handle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_FIRST); + if ( handle == NULL ) { + printf("[FAIL] dlopen(\"%s\") failed with: %s", RUN_DIR "/libfoo.dylib", dlerror()); + exit(0); + } + + void* sym = dlsym(handle, "foo"); + if ( sym == NULL ) { + printf("[FAIL] dlsym(handle, \"foo\") failed"); + exit(0); + } + + // verify we were notified about load of libfoo.dylib + const mach_header* libfooMH = dyld_image_header_containing_address(sym); + if ( sCurrentImages.count(libfooMH) == 0 ) { + printf("[FAIL] _dyld_register_func_for_add_image() did not notify us about libfoo.dylib"); + exit(0); + } + + + int result = dlclose(handle); + if ( result != 0 ) { + printf("[FAIL] dlclose(handle) returned %d", result); + exit(0); + } + + + printf("[PASS] _dyld_register_func_for_add_image\n"); + return 0; +} + diff --git a/testing/test-cases/dladdr-basic.dtest/main-no-syms.c b/testing/test-cases/dladdr-basic.dtest/main-no-syms.c index c79c78a..672595d 100644 --- a/testing/test-cases/dladdr-basic.dtest/main-no-syms.c +++ b/testing/test-cases/dladdr-basic.dtest/main-no-syms.c @@ -24,12 +24,12 @@ int main() Dl_info info; if ( dladdr(&main, &info) == 0 ) { - printf("[FAIL] dladdr(&main, xx) failed"); + printf("[FAIL] dladdr(&main, xx) failed\n"); return 0; } if ( info.dli_sname != NULL ){ - printf("[FAIL] dladdr() returned: \"%s\" instead of NULL", info.dli_sname); + printf("[FAIL] dladdr() returned: \"%s\" instead of NULL\n", info.dli_sname); return 0; } diff --git a/testing/test-cases/dladdr-basic.dtest/main.c b/testing/test-cases/dladdr-basic.dtest/main.c index d047353..eeeabca 100644 --- a/testing/test-cases/dladdr-basic.dtest/main.c +++ b/testing/test-cases/dladdr-basic.dtest/main.c @@ -30,19 +30,19 @@ static void verifybar() { Dl_info info; if ( dladdr(&bar, &info) == 0 ) { - printf("[FAIL] dladdr(&bar, xx) failed"); + printf("[FAIL] dladdr(&bar, xx) failed\n"); exit(0); } if ( strcmp(info.dli_sname, "bar") != 0 ) { - printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"bar\"", info.dli_sname); + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"bar\"\n", info.dli_sname); exit(0); } if ( info.dli_saddr != &bar) { - printf("[FAIL] dladdr()->dli_saddr is not &bar"); + printf("[FAIL] dladdr()->dli_saddr is not &bar\n"); exit(0); } if ( info.dli_fbase != dyld_image_header_containing_address(&bar) ) { - printf("[FAIL] dladdr()->dli_fbase is not image that contains &bar"); + printf("[FAIL] dladdr()->dli_fbase is not image that contains &bar\n"); exit(0); } } @@ -52,19 +52,19 @@ static void verifyfoo() { Dl_info info; if ( dladdr(&foo, &info) == 0 ) { - printf("[FAIL] dladdr(&foo, xx) failed"); + printf("[FAIL] dladdr(&foo, xx) failed\n"); exit(0); } if ( strcmp(info.dli_sname, "foo") != 0 ) { - printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"foo\"", info.dli_sname); + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"foo\"\n", info.dli_sname); exit(0); } if ( info.dli_saddr != &foo) { - printf("[FAIL] dladdr()->dli_saddr is not &foo"); + printf("[FAIL] dladdr()->dli_saddr is not &foo\n"); exit(0); } if ( info.dli_fbase != dyld_image_header_containing_address(&foo) ) { - printf("[FAIL] dladdr()->dli_fbase is not image that contains &foo"); + printf("[FAIL] dladdr()->dli_fbase is not image that contains &foo\n"); exit(0); } } @@ -74,19 +74,19 @@ static void verifyhide() { Dl_info info; if ( dladdr(&hide, &info) == 0 ) { - printf("[FAIL] dladdr(&hide, xx) failed"); + printf("[FAIL] dladdr(&hide, xx) failed\n"); exit(0); } if ( strcmp(info.dli_sname, "hide") != 0 ) { - printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"hide\"", info.dli_sname); + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"hide\"\n", info.dli_sname); exit(0); } if ( info.dli_saddr != &hide) { - printf("[FAIL] dladdr()->dli_saddr is not &hide"); + printf("[FAIL] dladdr()->dli_saddr is not &hide\n"); exit(0); } if ( info.dli_fbase != dyld_image_header_containing_address(&hide) ) { - printf("[FAIL] dladdr()->dli_fbase is not image that contains &hide"); + printf("[FAIL] dladdr()->dli_fbase is not image that contains &hide\n"); exit(0); } } @@ -96,19 +96,19 @@ static void verifymalloc() { Dl_info info; if ( dladdr(&malloc, &info) == 0 ) { - printf("[FAIL] dladdr(&malloc, xx) failed"); + printf("[FAIL] dladdr(&malloc, xx) failed\n"); exit(0); } if ( strcmp(info.dli_sname, "malloc") != 0 ) { - printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"malloc\"", info.dli_sname); + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"malloc\"\n", info.dli_sname); exit(0); } if ( info.dli_saddr != &malloc) { - printf("[FAIL] dladdr()->dli_saddr is not &malloc"); + printf("[FAIL] dladdr()->dli_saddr is not &malloc\n"); exit(0); } if ( info.dli_fbase != dyld_image_header_containing_address(&malloc) ) { - printf("[FAIL] dladdr()->dli_fbase is not image that contains &malloc"); + printf("[FAIL] dladdr()->dli_fbase is not image that contains &malloc\n"); exit(0); } } diff --git a/testing/test-cases/dladdr-dylib.dtest/foo.c b/testing/test-cases/dladdr-dylib.dtest/foo.c new file mode 100644 index 0000000..e45b27f --- /dev/null +++ b/testing/test-cases/dladdr-dylib.dtest/foo.c @@ -0,0 +1,99 @@ + + +#include +#include +#include +#include +#include + +extern void* __dso_handle; + +int dylib_bar() +{ + return 2; +} + +static int dylib_foo() +{ + return 3; +} + +__attribute__((visibility("hidden"))) int dylib_hide() +{ + return 4; +} + +// checks global symbol +static void verifybar() +{ + Dl_info info; + if ( dladdr(&dylib_bar, &info) == 0 ) { + printf("[FAIL] dladdr(&dylib_bar, xx) failed\n"); + exit(0); + } + if ( strcmp(info.dli_sname, "dylib_bar") != 0 ) { + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"dylib_bar\"\n", info.dli_sname); + exit(0); + } + if ( info.dli_saddr != &dylib_bar) { + printf("[FAIL] dladdr()->dli_saddr is not &dylib_bar\n"); + exit(0); + } + if ( info.dli_fbase != &__dso_handle ) { + printf("[FAIL] dladdr()->dli_fbase is not image that contains &dylib_bar\n"); + exit(0); + } +} + +// checks local symbol +static void verifyfoo() +{ + Dl_info info; + if ( dladdr(&dylib_foo, &info) == 0 ) { + printf("[FAIL] dladdr(&dylib_foo, xx) failed\n"); + exit(0); + } + if ( strcmp(info.dli_sname, "dylib_foo") != 0 ) { + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"dylib_foo\"\n", info.dli_sname); + exit(0); + } + if ( info.dli_saddr != &dylib_foo) { + printf("[FAIL] dladdr()->dli_saddr is not &dylib_foo\n"); + exit(0); + } + if ( info.dli_fbase != &__dso_handle ) { + printf("[FAIL] dladdr()->dli_fbase is not image that contains &dylib_foo\n"); + exit(0); + } +} + +// checks hidden symbol +static void verifyhide() +{ + Dl_info info; + if ( dladdr(&dylib_hide, &info) == 0 ) { + printf("[FAIL] dladdr(&dylib_hide, xx) failed\n"); + exit(0); + } + if ( strcmp(info.dli_sname, "dylib_hide") != 0 ) { + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"dylib_hide\"\n", info.dli_sname); + exit(0); + } + if ( info.dli_saddr != &dylib_hide) { + printf("[FAIL] dladdr()->dli_saddr is not &dylib_hide\n"); + exit(0); + } + if ( info.dli_fbase != &__dso_handle ) { + printf("[FAIL] dladdr()->dli_fbase is not image that contains &dylib_hide\n"); + exit(0); + } +} + + +void verifyDylib() +{ + verifybar(); + verifyfoo(); + verifyhide(); +} + diff --git a/testing/test-cases/dladdr-dylib.dtest/main.c b/testing/test-cases/dladdr-dylib.dtest/main.c new file mode 100644 index 0000000..5d9d0c9 --- /dev/null +++ b/testing/test-cases/dladdr-dylib.dtest/main.c @@ -0,0 +1,134 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CC main.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/dladdr-basic.exe + +// RUN: ./dladdr-basic.exe + +#include +#include +#include +#include +#include + +extern void* __dso_handle; + +extern void verifyDylib(); + +int bar() +{ + return 2; +} + +static int foo() +{ + return 3; +} + +__attribute__((visibility("hidden"))) int hide() +{ + return 4; +} + +// checks global symbol +static void verifybar() +{ + Dl_info info; + if ( dladdr(&bar, &info) == 0 ) { + printf("[FAIL] dladdr(&bar, xx) failed\n"); + exit(0); + } + if ( strcmp(info.dli_sname, "bar") != 0 ) { + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"bar\"\n", info.dli_sname); + exit(0); + } + if ( info.dli_saddr != &bar) { + printf("[FAIL] dladdr()->dli_saddr is not &bar\n"); + exit(0); + } + if ( info.dli_fbase != &__dso_handle ) { + printf("[FAIL] dladdr()->dli_fbase is not image that contains &bar\n"); + exit(0); + } +} + +// checks local symbol +static void verifyfoo() +{ + Dl_info info; + if ( dladdr(&foo, &info) == 0 ) { + printf("[FAIL] dladdr(&foo, xx) failed\n"); + exit(0); + } + if ( strcmp(info.dli_sname, "foo") != 0 ) { + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"foo\"\n", info.dli_sname); + exit(0); + } + if ( info.dli_saddr != &foo) { + printf("[FAIL] dladdr()->dli_saddr is not &foo\n"); + exit(0); + } + if ( info.dli_fbase != &__dso_handle ) { + printf("[FAIL] dladdr()->dli_fbase is not image that contains &foo\n"); + exit(0); + } +} + +// checks hidden symbol +static void verifyhide() +{ + Dl_info info; + if ( dladdr(&hide, &info) == 0 ) { + printf("[FAIL] dladdr(&hide, xx) failed\n"); + exit(0); + } + if ( strcmp(info.dli_sname, "hide") != 0 ) { + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"hide\"\n", info.dli_sname); + exit(0); + } + if ( info.dli_saddr != &hide) { + printf("[FAIL] dladdr()->dli_saddr is not &hide\n"); + exit(0); + } + if ( info.dli_fbase != &__dso_handle ) { + printf("[FAIL] dladdr()->dli_fbase is not image that contains &hide\n"); + exit(0); + } +} + +// checks dylib symbol +static void verifymalloc() +{ + Dl_info info; + if ( dladdr(&malloc, &info) == 0 ) { + printf("[FAIL] dladdr(&malloc, xx) failed\n"); + exit(0); + } + if ( strcmp(info.dli_sname, "malloc") != 0 ) { + printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"malloc\"\n", info.dli_sname); + exit(0); + } + if ( info.dli_saddr != &malloc) { + printf("[FAIL] dladdr()->dli_saddr is not &malloc\n"); + exit(0); + } + if ( info.dli_fbase != dyld_image_header_containing_address(&malloc) ) { + printf("[FAIL] dladdr()->dli_fbase is not image that contains &malloc\n"); + exit(0); + } +} + + +int main() +{ + printf("[BEGIN] dladdr-basic\n"); + verifybar(); + verifyhide(); + verifyfoo(); + verifymalloc(); + + verifyDylib(); + + printf("[PASS] dladdr-basic\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/bar.c b/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/bar.c new file mode 100644 index 0000000..39ac49e --- /dev/null +++ b/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/bar.c @@ -0,0 +1,5 @@ +int bar() +{ + return VALUE; +} + diff --git a/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/foo.c b/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/foo.c new file mode 100644 index 0000000..958e1af --- /dev/null +++ b/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/foo.c @@ -0,0 +1,7 @@ +extern int bar(); + +int foo() +{ + return bar() + VALUE; +} + diff --git a/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/main.c b/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/main.c new file mode 100644 index 0000000..96eb08b --- /dev/null +++ b/testing/test-cases/dlopen-DYLD_LIBRARY_PATH.dtest/main.c @@ -0,0 +1,84 @@ + +// BUILD: mkdir -p $BUILD_DIR/door1 $BUILD_DIR/door2 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/door1/libbar.dylib -install_name $RUN_DIR/libbar.dylib -DVALUE=3 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/door2/libbar.dylib -install_name $RUN_DIR/libbar.dylib -DVALUE=17 +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/door1/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib -DVALUE=10 $BUILD_DIR/door1/libbar.dylib +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/door2/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib -DVALUE=25 $BUILD_DIR/door2/libbar.dylib +// BUILD: $CC main.c -o $BUILD_DIR/main.exe +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main.exe + +// RUN: DYLD_LIBRARY_PATH=$RUN_DIR/door1/ ./main.exe 13 +// RUN: DYLD_LIBRARY_PATH=$RUN_DIR/door2 ./main.exe 42 +// RUN: DYLD_LIBRARY_PATH=$RUN_DIR/door3/:$RUN_DIR/door2/ ./main.exe 42 + +#include +#include +#include + +// Program dlopen()s libfoo.dylib which was linked against libbar.dylib +// Neither have valid paths and must be found via DYLD_LIBRARY_PATH +// This test direct and indirect loading. + +int main(int argc, const char* argv[]) +{ + const char* env = getenv("DYLD_LIBRARY_PATH"); + if ( env == NULL ) { + printf("[BEGIN] dlopen-DYLD_LIBRARY_PATH\n"); + printf("[FAIL] dlopen-DYLD_LIBRARY_PATH, env not set\n"); + return 0; + } + const char* valueStr = argv[1]; + if ( valueStr == NULL ) { + printf("[BEGIN] dlopen-DYLD_LIBRARY_PATH\n"); + printf("[FAIL] dlopen-DYLD_LIBRARY_PATH, arg1 value not set\n"); + return 0; + } + char* end; + long value = strtol(valueStr, &end, 0); + + printf("[BEGIN] dlopen-DYLD_LIBRARY_PATH %s\n", env); + + void* handle = dlopen("/bogus/libfoo.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-DYLD_LIBRARY_PATH %s\n", env); + return 0; + } + + typedef int (*FooProc)(); + + FooProc sym = (FooProc)dlsym(handle, "foo"); + if ( sym == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-DYLD_LIBRARY_PATH %s\n", env); + return 0; + } + + int result = (*sym)(); + if ( result != value ) { + printf("result=%d, expected %ld (str=%s)\n", result, value, valueStr); + printf("[FAIL] dlopen-DYLD_LIBRARY_PATH %s\n", env); + return 0; + } + + int r = dlclose(handle); + if ( r != 0 ) { + printf("dlclose() returned %d\n", r); + printf("[FAIL] dlopen-DYLD_LIBRARY_PATH %s\n", env); + return 0; + } + + void* handle2 = dlopen("/junk/libfoo.dylib", RTLD_LAZY); + if ( handle2 == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-DYLD_LIBRARY_PATH %s\n", env); + return 0; + } + + + + printf("[PASS] dlopen-DYLD_LIBRARY_PATH %s\n", env); + + return 0; +} + diff --git a/testing/test-cases/dlopen-bad-file.dtest/bad.txt b/testing/test-cases/dlopen-bad-file.dtest/bad.txt new file mode 100644 index 0000000..43b3565 --- /dev/null +++ b/testing/test-cases/dlopen-bad-file.dtest/bad.txt @@ -0,0 +1 @@ +bad file diff --git a/testing/test-cases/dlopen-bad-file.dtest/main.c b/testing/test-cases/dlopen-bad-file.dtest/main.c new file mode 100644 index 0000000..63108fd --- /dev/null +++ b/testing/test-cases/dlopen-bad-file.dtest/main.c @@ -0,0 +1,47 @@ + +// BUILD: cp bad.txt $BUILD_DIR/libnota.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-bad-file.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlopen-bad-file.exe + +#include +#include +#include + + + +int main() +{ + printf("[BEGIN] dlopen-bad-file\n"); + + // try to dlopen() a text file + void* handle = dlopen(RUN_DIR "/libnota.dylib", RTLD_FIRST); + if ( handle != NULL ) { + printf("[FAIL] dlopen-bad-file should have failed on non-mach-o file %s\n", RUN_DIR "/libnota.dylib"); + return 0; + } + const char* message = dlerror(); + if ( (strstr(message, "mach-o") == NULL) && (strstr(message, "too short") == NULL) ) { + printf("dlerror: %s\n", message); + printf("[FAIL] dlopen-bad-file dlerror() message did not contain 'mach-o'\n"); + return 0; + } + + // try to dlopen() a directory + handle = dlopen(RUN_DIR, RTLD_FIRST); + if ( handle != NULL ) { + printf("[FAIL] dlopen-bad-file should have failed on dir %s\n", RUN_DIR); + return 0; + } + message = dlerror(); + if ( strstr(message, "not a file") == NULL ) { + printf("dlerror: %s\n", message); + printf("[FAIL] dlopen-bad-file dlerror() message did not contain 'not a file'\n"); + return 0; + } + + printf("[PASS] dlopen-bad-file\n"); + + return 0; +} + diff --git a/testing/test-cases/dlopen-empty-data.dtest/foo.c b/testing/test-cases/dlopen-empty-data.dtest/foo.c new file mode 100644 index 0000000..fd55770 --- /dev/null +++ b/testing/test-cases/dlopen-empty-data.dtest/foo.c @@ -0,0 +1,8 @@ + +int dummy; + +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlopen-empty-data.dtest/main.c b/testing/test-cases/dlopen-empty-data.dtest/main.c new file mode 100644 index 0000000..9bf8051 --- /dev/null +++ b/testing/test-cases/dlopen-empty-data.dtest/main.c @@ -0,0 +1,27 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-static.dylib -o $BUILD_DIR/libfoo-static.dylib +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-dynamic.dylib -o $BUILD_DIR/libfoo-dynamic.dylib +// BUILD: $CC main.c $BUILD_DIR/libfoo-static.dylib -o $BUILD_DIR/dlopen-empty-data.exe -DRUN_DIR="$RUN_DIR" + + +// RUN: ./dlopen-empty-data.exe + +#include +#include + +// libfoo-static.dylib and libfoo-dynamic.dylib each have an empty (no disk size) DATA segment + +int main() +{ + printf("[BEGIN] dlopen-empty-data\n"); + + void* handle = dlopen(RUN_DIR "/libfoo-dynamic.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlopen-empty-data: libfoo-dynamic.dylib could not be loaded: %s\n", dlerror()); + return 0; + } + + printf("[PASS] dlopen-empty-data\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-flat.dtest/bar.c b/testing/test-cases/dlopen-flat.dtest/bar.c new file mode 100644 index 0000000..0f338d1 --- /dev/null +++ b/testing/test-cases/dlopen-flat.dtest/bar.c @@ -0,0 +1,15 @@ + +extern int foo(); + +extern int gInitialisersCalled; + +__attribute__((constructor)) +static void onLoad() { + ++gInitialisersCalled; +} + +typedef int(*retTy)(); + +retTy bar() { + return &foo; +} \ No newline at end of file diff --git a/testing/test-cases/dlopen-flat.dtest/foo.c b/testing/test-cases/dlopen-flat.dtest/foo.c new file mode 100644 index 0000000..2414437 --- /dev/null +++ b/testing/test-cases/dlopen-flat.dtest/foo.c @@ -0,0 +1,13 @@ + + +extern int gInitialisersCalled; + +__attribute__((constructor)) +static void onLoad() { + ++gInitialisersCalled; +} + + +int foo() { + return 0; +} \ No newline at end of file diff --git a/testing/test-cases/dlopen-flat.dtest/main.c b/testing/test-cases/dlopen-flat.dtest/main.c new file mode 100644 index 0000000..194b6af --- /dev/null +++ b/testing/test-cases/dlopen-flat.dtest/main.c @@ -0,0 +1,96 @@ + +// BUILD: $CC foo.c -dynamiclib -Wl,-U,_gInitialisersCalled -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CC bar.c -dynamiclib -Wl,-U,_gInitialisersCalled $BUILD_DIR/libfoo.dylib -flat_namespace -install_name $RUN_DIR/libbar.dylib -o $BUILD_DIR/libbar.dylib +// BUILD: $CC main.c -DRUN_DIR="$RUN_DIR" -o $BUILD_DIR/dlopen-flat.exe + +// RUN: DYLD_LIBRARY_PATH=$RUN_DIR ./dlopen-flat.exe + +#include +#include + +int gInitialisersCalled = 0; + +int main() { + printf("[BEGIN] dlopen-flat\n"); + int result; + + // Foo exports foo() + void* fooHandle = 0; + { + const char* path = RUN_DIR "/libfoo.dylib"; + fooHandle = dlopen(path, RTLD_LAZY); + if (!fooHandle) { + printf("dlopen failed with error: %s\n", dlerror()); + return 1; + } + if (gInitialisersCalled != 1) { + printf("gInitialisersCalled != 1\n"); + printf("[FAIL] dlopen-flat\n"); + return 1; + } + } + // Now unload foo which should do something. + result = dlclose(fooHandle); + if (result != 0) { + printf("dlclose() returned %c\n", result); + printf("[FAIL] dlopen-flat\n"); + return 1; + } + + // Open foo again which should do something. + { + const char* path = RUN_DIR "/libfoo.dylib"; + fooHandle = dlopen(path, RTLD_LAZY); + if (!fooHandle) { + printf("dlopen failed with error: %s\n", dlerror()); + return 1; + } + if (gInitialisersCalled != 2) { + printf("gInitialisersCalled != 2\n"); + printf("[FAIL] dlopen-flat\n"); + return 1; + } + } + + // Bar is going to resolve foo() + void* barHandle = 0; + { + const char* path = RUN_DIR "/libbar.dylib"; + barHandle = dlopen(path, RTLD_LAZY); + if (!barHandle) { + printf("dlopen failed with error: %s\n", dlerror()); + return 1; + } + if (gInitialisersCalled != 3) { + printf("gInitialisersCalled != 3\n"); + printf("[FAIL] dlopen-flat\n"); + return 1; + } + } + // Now unload foo which shouldn't do anything. + result = dlclose(fooHandle); + if (result != 0) { + printf("dlclose() returned %c\n", result); + printf("[FAIL] dlopen-flat\n"); + return 1; + } + + // Open foo again which shouldn't do anything. + { + const char* path = RUN_DIR "/libfoo.dylib"; + fooHandle = dlopen(path, RTLD_LAZY); + if (!fooHandle) { + printf("dlopen failed with error: %s\n", dlerror()); + return 1; + } + if (gInitialisersCalled != 3) { + printf("gInitialisersCalled != 3\n"); + printf("[FAIL] dlopen-flat\n"); + return 1; + } + } + + printf("[PASS] dlopen-flat\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-indirect-groupNum.dtest/bar.c b/testing/test-cases/dlopen-indirect-groupNum.dtest/bar.c new file mode 100644 index 0000000..4381650 --- /dev/null +++ b/testing/test-cases/dlopen-indirect-groupNum.dtest/bar.c @@ -0,0 +1,7 @@ + +#include +#include + +void barInDylib() +{ +} diff --git a/testing/test-cases/dlopen-indirect-groupNum.dtest/baz.c b/testing/test-cases/dlopen-indirect-groupNum.dtest/baz.c new file mode 100644 index 0000000..98ea4f1 --- /dev/null +++ b/testing/test-cases/dlopen-indirect-groupNum.dtest/baz.c @@ -0,0 +1,10 @@ + +#include +#include + +extern void barInDylib(); + +void bazInDylib() +{ + return barInDylib(); +} diff --git a/testing/test-cases/dlopen-indirect-groupNum.dtest/foo.c b/testing/test-cases/dlopen-indirect-groupNum.dtest/foo.c new file mode 100644 index 0000000..837ec00 --- /dev/null +++ b/testing/test-cases/dlopen-indirect-groupNum.dtest/foo.c @@ -0,0 +1,7 @@ + +#include +#include + +void fooInBundle() +{ +} diff --git a/testing/test-cases/dlopen-indirect-groupNum.dtest/main.c b/testing/test-cases/dlopen-indirect-groupNum.dtest/main.c new file mode 100644 index 0000000..4547e90 --- /dev/null +++ b/testing/test-cases/dlopen-indirect-groupNum.dtest/main.c @@ -0,0 +1,147 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-indirect-groupNum.exe -Wno-deprecated-declarations +// BUILD: $CC foo.c -o $BUILD_DIR/foo.bundle -bundle +// BUILD: $CC bar.c -dynamiclib -install_name $RUN_DIR/libbar.dylib -o $BUILD_DIR/libbar.dylib +// BUILD: $CC baz.c -dynamiclib -install_name $RUN_DIR/libbaz.dylib -o $BUILD_DIR/libbaz.dylib $BUILD_DIR/libbar.dylib + +// RUN: ./dlopen-indirect-groupNum.exe $RUN_DIR/foo.bundle $RUN_DIR/libbar.dylib $RUN_DIR/libbaz.dylib + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static void checkBundle(const char* path, bool unlinkBeforeDestroy) +{ + int fd = open(path, O_RDONLY, 0); + if ( fd == -1 ) { + printf("[FAIL] open(%s) failed", path); + exit(0); + } + + struct stat stat_buf; + if ( fstat(fd, &stat_buf) == -1) { + printf("[FAIL] fstat() failed\n"); + exit(0); + } + + void* loadAddress = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0); + if ( loadAddress == ((void*)(-1)) ) { + printf("[FAIL] mmap() failed\n"); + exit(0); + } + + close(fd); + + NSObjectFileImage ofi; + if ( NSCreateObjectFileImageFromMemory(loadAddress, stat_buf.st_size, &ofi) != NSObjectFileImageSuccess ) { + printf("[FAIL] NSCreateObjectFileImageFromMemory failed\n"); + exit(0); + } + + NSModule mod = NSLinkModule(ofi, path, NSLINKMODULE_OPTION_NONE); + if ( mod == NULL ) { + printf("[FAIL] NSLinkModule failed\n"); + exit(0); + } + + if ( !unlinkBeforeDestroy ) { + // API lets you destroy ofi and NSModule lives on + if ( !NSDestroyObjectFileImage(ofi) ) { + printf("[FAIL] NSDestroyObjectFileImage failed\n"); + exit(0); + } + } + + NSSymbol sym = NSLookupSymbolInModule(mod, "_fooInBundle"); + if ( sym == NULL ) { + printf("[FAIL] NSLookupSymbolInModule failed\n"); + exit(0); + } + + void* func = NSAddressOfSymbol(sym); + if ( func == NULL ) { + printf("[FAIL] NSAddressOfSymbol failed\n"); + exit(0); + } + + Dl_info info; + if ( dladdr(func, &info) == 0 ) { + printf("[FAIL] dladdr(&p, xx) failed\n"); + exit(0); + } + //printf("_fooInBundle found in %s\n", info.dli_fname); + + if ( !NSUnLinkModule(mod, NSUNLINKMODULE_OPTION_NONE) ) { + printf("[FAIL] NSUnLinkModule failed\n"); + exit(0); + } + + if ( dladdr(func, &info) != 0 ) { + printf("[FAIL] dladdr(&p, xx) found but should not have\n"); + exit(0); + } + + if ( unlinkBeforeDestroy ) { + if ( !NSDestroyObjectFileImage(ofi) ) { + printf("[FAIL] NSDestroyObjectFileImage failed\n"); + exit(0); + } + } +} + + + +static void tryImage(const char* path, const char* symbol) +{ + void* handle = dlopen(path, RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-indirect-groupNum %s\n", path); + exit(0); + } + + void* sym = dlsym(handle, symbol); + if ( sym == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-indirect-groupNum %s\n", path); + exit(0); + } + + int result = dlclose(handle); + if ( result != 0 ) { + printf("dlclose() returned %c\n", result); + printf("[FAIL] dlopen-indirect-groupNum %s\n", path); + exit(0); + } +} + + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] dlopen-indirect-groupNum\n"); + + checkBundle(argv[1], true); + checkBundle(argv[1], false); + + // Now go again enough times to flush out any limits in our dlopen encodings. + for (unsigned i = 0; i != 255; ++i) + checkBundle(argv[1], false); + + // Open bar.dylib + tryImage(argv[2], "barInDylib"); + + // And now open baz.dylib which depends on bar.dylib + tryImage(argv[3], "bazInDylib"); + + printf("[PASS] dlopen-indirect-groupNum\n"); + return 0; +} \ No newline at end of file diff --git a/testing/test-cases/dlopen-intertwined.dtest/A.c b/testing/test-cases/dlopen-intertwined.dtest/A.c new file mode 100644 index 0000000..2fadfc3 --- /dev/null +++ b/testing/test-cases/dlopen-intertwined.dtest/A.c @@ -0,0 +1,11 @@ +#include +#include + +extern void setState(const char* from); + + +void a(const char* from) { + char buffer[100]; + sprintf(buffer, "a() from %s", from); + setState(buffer); +} diff --git a/testing/test-cases/dlopen-intertwined.dtest/B.c b/testing/test-cases/dlopen-intertwined.dtest/B.c new file mode 100644 index 0000000..aed2a47 --- /dev/null +++ b/testing/test-cases/dlopen-intertwined.dtest/B.c @@ -0,0 +1,11 @@ +extern void c(const char*); + +void b() { } + +void __attribute__((constructor)) +initB() +{ + c("initB"); +} + + diff --git a/testing/test-cases/dlopen-intertwined.dtest/C.c b/testing/test-cases/dlopen-intertwined.dtest/C.c new file mode 100644 index 0000000..338ddb5 --- /dev/null +++ b/testing/test-cases/dlopen-intertwined.dtest/C.c @@ -0,0 +1,16 @@ +#include +#include + +extern void setState(const char* from); + +void c(const char* from) { + char buffer[100]; + sprintf(buffer, "c() from %s", from); + setState(buffer); +} + +void __attribute__((constructor)) +initC() +{ + setState("initC"); +} diff --git a/testing/test-cases/dlopen-intertwined.dtest/D.c b/testing/test-cases/dlopen-intertwined.dtest/D.c new file mode 100644 index 0000000..4d9da2c --- /dev/null +++ b/testing/test-cases/dlopen-intertwined.dtest/D.c @@ -0,0 +1,20 @@ +#include +#include + +extern void setState(const char* from); +extern void c(const char* from); + +void d(const char* from) { + char buffer[100]; + sprintf(buffer, "d() from %s", from); + setState(buffer); +} + +void __attribute__((constructor)) +initD() +{ + c("initD"); +} + + + diff --git a/testing/test-cases/dlopen-intertwined.dtest/E.c b/testing/test-cases/dlopen-intertwined.dtest/E.c new file mode 100644 index 0000000..2700f3c --- /dev/null +++ b/testing/test-cases/dlopen-intertwined.dtest/E.c @@ -0,0 +1,13 @@ +extern void a(const char*); + +void e() { } + + +void __attribute__((constructor)) +initE() +{ + a("initE"); +} + + + diff --git a/testing/test-cases/dlopen-intertwined.dtest/F.c b/testing/test-cases/dlopen-intertwined.dtest/F.c new file mode 100644 index 0000000..88789cf --- /dev/null +++ b/testing/test-cases/dlopen-intertwined.dtest/F.c @@ -0,0 +1,12 @@ +extern void d(const char*); + +void f() { } + +void __attribute__((constructor)) +initF() +{ + d("initF"); +} + + + diff --git a/testing/test-cases/dlopen-intertwined.dtest/base.c b/testing/test-cases/dlopen-intertwined.dtest/base.c new file mode 100644 index 0000000..e3143fb --- /dev/null +++ b/testing/test-cases/dlopen-intertwined.dtest/base.c @@ -0,0 +1,26 @@ +#include +#include +#include + +static const char* expectedStrings[] = { + "a() from main", + "initC", + "c() from initB", + "c() from initD", + "a() from initE", + "d() from initF", + "DONE" +}; + +static const char** curState = expectedStrings; + +void setState(const char* from) +{ + printf("%s\n", from); + if ( strcmp(*curState, from) != 0 ) { + printf("[FAIL] dlopen-intertwined: expected %s\n", *curState); + exit(0); + } + ++curState; +} + diff --git a/testing/test-cases/dlopen-intertwined.dtest/main.c b/testing/test-cases/dlopen-intertwined.dtest/main.c new file mode 100644 index 0000000..11580d3 --- /dev/null +++ b/testing/test-cases/dlopen-intertwined.dtest/main.c @@ -0,0 +1,63 @@ + + +// BUILD: $CC base.c -dynamiclib -o $BUILD_DIR/libbase.dylib -install_name $RUN_DIR/libbase.dylib +// BUILD: $CC A.c -dynamiclib -o $BUILD_DIR/libA.dylib -install_name $RUN_DIR/libA.dylib $BUILD_DIR/libbase.dylib +// BUILD: $CC C.c -dynamiclib -o $BUILD_DIR/libC.dylib -install_name $RUN_DIR/libC.dylib $BUILD_DIR/libbase.dylib +// BUILD: $CC B.c -dynamiclib -o $BUILD_DIR/libB.dylib -install_name $RUN_DIR/libB.dylib $BUILD_DIR/libbase.dylib $BUILD_DIR/libC.dylib +// BUILD: $CC D.c -dynamiclib -o $BUILD_DIR/libD.dylib -install_name $RUN_DIR/libD.dylib $BUILD_DIR/libbase.dylib $BUILD_DIR/libC.dylib +// BUILD: $CC E.c -dynamiclib -o $BUILD_DIR/libE.dylib -install_name $RUN_DIR/libE.dylib $BUILD_DIR/libbase.dylib $BUILD_DIR/libA.dylib +// BUILD: $CC F.c -dynamiclib -o $BUILD_DIR/libF.dylib -install_name $RUN_DIR/libF.dylib $BUILD_DIR/libbase.dylib $BUILD_DIR/libD.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-intertwined.exe $BUILD_DIR/libbase.dylib $BUILD_DIR/libA.dylib -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlopen-intertwined.exe + +#include +#include +#include +#include + +// main deps on A +// main dlopens B which deps on C +// main dlopens D which deps on C +// main dlopens E which deps on A +// main dlopens F which deps on D + +extern void a(const char*); +extern void setState(const char* from); + +int main() +{ + printf("[BEGIN] dlopen-intertwined\n"); + + a("main"); + + void* handle = dlopen(RUN_DIR "/libB.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlopen-intertwined: %s\n", dlerror()); + exit(0); + } + + handle = dlopen(RUN_DIR "/libD.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlopen-intertwined: %s\n", dlerror()); + exit(0); + } + + handle = dlopen(RUN_DIR "/libE.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlopen-intertwined: %s\n", dlerror()); + exit(0); + } + + handle = dlopen(RUN_DIR "/libF.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlopen-intertwined: %s\n", dlerror()); + exit(0); + } + + setState("DONE"); + + printf("[PASS] dlopen-intertwined\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-long-error-message.dtest/main.c b/testing/test-cases/dlopen-long-error-message.dtest/main.c new file mode 100644 index 0000000..8b0a16a --- /dev/null +++ b/testing/test-cases/dlopen-long-error-message.dtest/main.c @@ -0,0 +1,30 @@ + +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-long-error-message.exe + +// RUN: ./dlopen-long-error-message.exe + +#include +#include +#include +#include + + + +int main() +{ + printf("[BEGIN] dlopen-long-error-message\n"); + + for (int i=0; i < 10; ++i) { + void* handle = dlopen("/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/bogus/path/libbogus.dylib", RTLD_FIRST); + if ( handle != NULL ) { + printf("[FAIL] dlopen-long-error-message should have failed on non-existent file\n"); + return 0; + } + free(strdup("hello there")); + } + + printf("[PASS] dlopen-long-error-message\n"); + + return 0; +} + diff --git a/testing/test-cases/dlopen-race.dtest/foo.c b/testing/test-cases/dlopen-race.dtest/foo.c new file mode 100644 index 0000000..c8e9924 --- /dev/null +++ b/testing/test-cases/dlopen-race.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlopen-race.dtest/main.c b/testing/test-cases/dlopen-race.dtest/main.c new file mode 100644 index 0000000..bad9ff7 --- /dev/null +++ b/testing/test-cases/dlopen-race.dtest/main.c @@ -0,0 +1,33 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-race.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlopen-race.exe + +#include +#include +#include +#include + + + +int main() +{ + printf("[BEGIN] dlopen-read\n"); + + __block bool allGood = true; + dispatch_apply(6, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) { + for (int i=0; i < 500; ++i) { + void* handle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlopen-read: %s\n", dlerror()); + exit(0); + } + dlclose(handle); + } + }); + + printf("[PASS] dlopen-read\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-recurse.dtest/bar.c b/testing/test-cases/dlopen-recurse.dtest/bar.c new file mode 100644 index 0000000..c86856e --- /dev/null +++ b/testing/test-cases/dlopen-recurse.dtest/bar.c @@ -0,0 +1,5 @@ +int bar() +{ + return 0; +} + diff --git a/testing/test-cases/dlopen-recurse.dtest/foo.c b/testing/test-cases/dlopen-recurse.dtest/foo.c new file mode 100644 index 0000000..eb1114d --- /dev/null +++ b/testing/test-cases/dlopen-recurse.dtest/foo.c @@ -0,0 +1,16 @@ +#include + + +void myinit() __attribute__((constructor)); + +void myinit() +{ + // call dlopen() in initializer + void* handle = dlopen(RUN_DIR "/libbar.dylib", RTLD_LAZY); +} + +int foo() +{ + return 0; +} + diff --git a/testing/test-cases/dlopen-recurse.dtest/main.c b/testing/test-cases/dlopen-recurse.dtest/main.c new file mode 100644 index 0000000..53c244e --- /dev/null +++ b/testing/test-cases/dlopen-recurse.dtest/main.c @@ -0,0 +1,26 @@ + +// BUILD: $CC bar.c -dynamiclib -install_name $RUN_DIR/libbar.dylib -o $BUILD_DIR/libbar.dylib +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib -DRUN_DIR="$RUN_DIR" +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-recurse.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlopen-recurse.exe + +#include +#include +#include +#include + + + +int main() +{ + printf("[BEGIN] dlopen-recurse\n"); + + // libfoo's initializer calls dlopen(). If that hangs, we have a locking bug + void* handle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_LAZY); + dlclose(handle); + + printf("[PASS] dlopen-recurse\n"); + return 0; +} + diff --git a/testing/test-cases/dlsym-RTLD_DEFAULT.dtest/foo.c b/testing/test-cases/dlsym-RTLD_DEFAULT.dtest/foo.c new file mode 100644 index 0000000..1f6bcd8 --- /dev/null +++ b/testing/test-cases/dlsym-RTLD_DEFAULT.dtest/foo.c @@ -0,0 +1,6 @@ + +void foo() { } + +#if DYN +void foo2() {} +#endif diff --git a/testing/test-cases/dlsym-RTLD_DEFAULT.dtest/main.c b/testing/test-cases/dlsym-RTLD_DEFAULT.dtest/main.c new file mode 100644 index 0000000..de667dd --- /dev/null +++ b/testing/test-cases/dlsym-RTLD_DEFAULT.dtest/main.c @@ -0,0 +1,85 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-static.dylib -o $BUILD_DIR/libfoo-static.dylib +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-dynamic.dylib -o $BUILD_DIR/libfoo-dynamic.dylib -DDYN +// BUILD: $CC main.c $BUILD_DIR/libfoo-static.dylib -o $BUILD_DIR/dlsym-RTLD_DEFAULT.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlsym-RTLD_DEFAULT.exe + +#include +#include +#include + + +// verify RTLD_DEFAULT search order + +int mainSymbol = 4; + + +// my local implemention of free +void free(void* p) { } + + +static bool symbolInImage(const char* symName, const char* image) +{ + void* sym = dlsym(RTLD_DEFAULT, symName); + if ( sym == NULL ) + return false; + const char* imagePath = dyld_image_path_containing_address(sym); + if ( imagePath == NULL ) + return false; + return (strstr(imagePath, image) != NULL); +} + + + + +int main() +{ + printf("[BEGIN] dlsym-RTLD_DEFAULT\n"); + + // verify mainSymbol is found in main executable + if ( !symbolInImage("mainSymbol", "dlsym-RTLD_DEFAULT") ) { + printf("[FAIL] dlsym-RTLD_DEFAULT: mainSymbol\n"); + return 0; + } + + // verify free is found in main executable, overrideing one in OS + if ( !symbolInImage("free", "dlsym-RTLD_DEFAULT") ) { + printf("[FAIL] dlsym-RTLD_DEFAULT: free\n"); + return 0; + } + + // verify foo is found in libfoo-static.dylib + if ( !symbolInImage("foo", "libfoo-static.dylib") ) { + printf("[FAIL] dlsym-RTLD_DEFAULT: foo not in libfoo-static.dylib\n"); + return 0; + } + + void* handle = dlopen(RUN_DIR "/libfoo-dynamic.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlsym-RTLD_DEFAULT: libfoo-dynamic.dylib could not be loaded\n"); + return 0; + } + + // verify foo is still found in statically linked lib + if ( !symbolInImage("foo", "libfoo-static.dylib") ) { + printf("[FAIL] dlsym-RTLD_DEFAULT: foo not in libfoo-static.dylib\n"); + return 0; + } + + // verify foo2 is found in libfoo-dynamic.dylib" + if ( !symbolInImage("foo2", "libfoo-dynamic.dylib") ) { + printf("[FAIL] dlsym-RTLD_DEFAULT: foo2 not in libfoo-dynamic.dylib\n"); + return 0; + } + + // renamed and re-exported symbols work + if ( dlsym(RTLD_DEFAULT, "strcmp") == NULL ) { + printf("[FAIL] dlsym-RTLD_DEFAULT: strcmp not found\n"); + return 0; + } + + printf("[PASS] dlsym-RTLD_DEFAULT\n"); + return 0; +} + diff --git a/testing/test-cases/dlsym-RTLD_MAIN_ONLY.dtest/foo.c b/testing/test-cases/dlsym-RTLD_MAIN_ONLY.dtest/foo.c new file mode 100644 index 0000000..1f6bcd8 --- /dev/null +++ b/testing/test-cases/dlsym-RTLD_MAIN_ONLY.dtest/foo.c @@ -0,0 +1,6 @@ + +void foo() { } + +#if DYN +void foo2() {} +#endif diff --git a/testing/test-cases/dlsym-RTLD_MAIN_ONLY.dtest/main.c b/testing/test-cases/dlsym-RTLD_MAIN_ONLY.dtest/main.c new file mode 100644 index 0000000..80c9ef5 --- /dev/null +++ b/testing/test-cases/dlsym-RTLD_MAIN_ONLY.dtest/main.c @@ -0,0 +1,79 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-static.dylib -o $BUILD_DIR/libfoo-static.dylib +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-dynamic.dylib -o $BUILD_DIR/libfoo-dynamic.dylib -DDYN +// BUILD: $CC main.c $BUILD_DIR/libfoo-static.dylib -o $BUILD_DIR/dlsym-RTLD_MAIN_ONLY.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlsym-RTLD_MAIN_ONLY.exe + +#include +#include +#include + + +// verify RTLD_MAIN_ONLY search order + +int mainSymbol = 4; + + +// my local implemention of free +void free(void* p) { } + + +static bool symbolInImage(const char* symName, const char* image) +{ + void* sym = dlsym(RTLD_MAIN_ONLY, symName); + if ( sym == NULL ) + return false; + const char* imagePath = dyld_image_path_containing_address(sym); + if ( imagePath == NULL ) + return false; + return (strstr(imagePath, image) != NULL); +} + + + + +int main() +{ + printf("[BEGIN] dlsym-RTLD_MAIN_ONLY\n"); + + // verify mainSymbol is found + if ( !symbolInImage("mainSymbol", "dlsym-RTLD_MAIN_ONLY") ) { + printf("[FAIL] dlsym-RTLD_MAIN_ONLY: mainSymbol should have been found\n"); + return 0; + } + + // verify free is found in this program - not in OS + if ( !symbolInImage("free", "dlsym-RTLD_MAIN_ONLY") ) { + printf("[FAIL] dlsym-RTLD_MAIN_ONLY: free\n"); + return 0; + } + + // verify foo is not found + if ( dlsym(RTLD_MAIN_ONLY, "foo") != NULL ) { + printf("[FAIL] dlsym-RTLD_MAIN_ONLY: foo should not have been found\n"); + return 0; + } + + void* handle = dlopen(RUN_DIR "/libfoo-dynamic.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlsym-RTLD_MAIN_ONLY: libfoo-dynamic.dylib could not be loaded\n"); + return 0; + } + + // verify foo is still not found + if ( dlsym(RTLD_MAIN_ONLY, "foo") != NULL ) { + printf("[FAIL] dlsym-RTLD_MAIN_ONLY: foo should not have been found after dlopen\n"); + return 0; + } + + // verify foo2 is not found in libfoo-dynamic.dylib", because RTLD_MAIN_ONLY only searches main executable + if ( dlsym(RTLD_MAIN_ONLY, "foo2") != NULL ) { + printf("[FAIL] dlsym-RTLD_MAIN_ONLY: foo2 found but should not have been\n"); + return 0; + } + + printf("[PASS] dlsym-RTLD_MAIN_ONLY\n"); + return 0; +} + diff --git a/testing/test-cases/dlsym-RTLD_NEXT.dtest/foo.c b/testing/test-cases/dlsym-RTLD_NEXT.dtest/foo.c new file mode 100644 index 0000000..1f6bcd8 --- /dev/null +++ b/testing/test-cases/dlsym-RTLD_NEXT.dtest/foo.c @@ -0,0 +1,6 @@ + +void foo() { } + +#if DYN +void foo2() {} +#endif diff --git a/testing/test-cases/dlsym-RTLD_NEXT.dtest/main.c b/testing/test-cases/dlsym-RTLD_NEXT.dtest/main.c new file mode 100644 index 0000000..7d81e99 --- /dev/null +++ b/testing/test-cases/dlsym-RTLD_NEXT.dtest/main.c @@ -0,0 +1,79 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-static.dylib -o $BUILD_DIR/libfoo-static.dylib +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-dynamic.dylib -o $BUILD_DIR/libfoo-dynamic.dylib -DDYN +// BUILD: $CC main.c $BUILD_DIR/libfoo-static.dylib -o $BUILD_DIR/dlsym-RTLD_NEXT.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlsym-RTLD_NEXT.exe + +#include +#include +#include + + +// verify RTLD_NEXT search order + +int mainSymbol = 4; + + +// my local implemention of free +void free(void* p) { } + + +static bool symbolInImage(const char* symName, const char* image) +{ + void* sym = dlsym(RTLD_NEXT, symName); + if ( sym == NULL ) + return false; + const char* imagePath = dyld_image_path_containing_address(sym); + if ( imagePath == NULL ) + return false; + return (strstr(imagePath, image) != NULL); +} + + + + +int main() +{ + printf("[BEGIN] dlsym-RTLD_NEXT\n"); + + // verify mainSymbol is not found + if ( dlsym(RTLD_NEXT, "mainSymbol") != NULL ) { + printf("[FAIL] dlsym-RTLD_NEXT: mainSymbol should not have been found\n"); + return 0; + } + + // verify free is found in OS (not local one) + if ( !symbolInImage("free", "/usr/lib/") ) { + printf("[FAIL] dlsym-RTLD_NEXT: free\n"); + return 0; + } + + // verify foo is found in libfoo-static.dylib + if ( !symbolInImage("foo", "libfoo-static.dylib") ) { + printf("[FAIL] dlsym-RTLD_NEXT: foo not in libfoo-static.dylib\n"); + return 0; + } + + void* handle = dlopen(RUN_DIR "/libfoo-dynamic.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlsym-RTLD_NEXT: libfoo-dynamic.dylib could not be loaded\n"); + return 0; + } + + // verify foo is still found in statically linked lib + if ( !symbolInImage("foo", "libfoo-static.dylib") ) { + printf("[FAIL] dlsym-RTLD_NEXT: foo not in libfoo-static.dylib\n"); + return 0; + } + + // verify foo2 is not found in libfoo-dynamic.dylib", because RTLD_NEXT only searches thing this image would have seen + if ( symbolInImage("foo2", "libfoo-dynamic.dylib") ) { + printf("[FAIL] dlsym-RTLD_NEXT: foo2 found but should not have been\n"); + return 0; + } + + printf("[PASS] dlsym-RTLD_NEXT\n"); + return 0; +} + diff --git a/testing/test-cases/dlsym-RTLD_SELF.dtest/foo.c b/testing/test-cases/dlsym-RTLD_SELF.dtest/foo.c new file mode 100644 index 0000000..1f6bcd8 --- /dev/null +++ b/testing/test-cases/dlsym-RTLD_SELF.dtest/foo.c @@ -0,0 +1,6 @@ + +void foo() { } + +#if DYN +void foo2() {} +#endif diff --git a/testing/test-cases/dlsym-RTLD_SELF.dtest/main.c b/testing/test-cases/dlsym-RTLD_SELF.dtest/main.c new file mode 100644 index 0000000..cd921ff --- /dev/null +++ b/testing/test-cases/dlsym-RTLD_SELF.dtest/main.c @@ -0,0 +1,79 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-static.dylib -o $BUILD_DIR/libfoo-static.dylib +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo-dynamic.dylib -o $BUILD_DIR/libfoo-dynamic.dylib -DDYN +// BUILD: $CC main.c $BUILD_DIR/libfoo-static.dylib -o $BUILD_DIR/dlsym-RTLD_SELF.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlsym-RTLD_SELF.exe + +#include +#include +#include + + +// verify RTLD_SELF search order + +int mainSymbol = 4; + + +// my local implemention of free +void free(void* p) { } + + +static bool symbolInImage(const char* symName, const char* image) +{ + void* sym = dlsym(RTLD_SELF, symName); + if ( sym == NULL ) + return false; + const char* imagePath = dyld_image_path_containing_address(sym); + if ( imagePath == NULL ) + return false; + return (strstr(imagePath, image) != NULL); +} + + + + +int main() +{ + printf("[BEGIN] dlsym-RTLD_SELF\n"); + + // verify mainSymbol is found + if ( dlsym(RTLD_SELF, "mainSymbol") == NULL ) { + printf("[FAIL] dlsym-RTLD_SELF: mainSymbol should have been found\n"); + return 0; + } + + // verify free is found in this program - not in OS + if ( !symbolInImage("free", "dlsym-RTLD_SELF") ) { + printf("[FAIL] dlsym-RTLD_SELF: free\n"); + return 0; + } + + // verify foo is found in libfoo-static.dylib + if ( !symbolInImage("foo", "libfoo-static.dylib") ) { + printf("[FAIL] dlsym-RTLD_SELF: foo not in libfoo-static.dylib\n"); + return 0; + } + + void* handle = dlopen(RUN_DIR "/libfoo-dynamic.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlsym-RTLD_SELF: libfoo-dynamic.dylib could not be loaded\n"); + return 0; + } + + // verify foo is still found in statically linked lib + if ( !symbolInImage("foo", "libfoo-static.dylib") ) { + printf("[FAIL] dlsym-RTLD_SELF: foo not in libfoo-static.dylib\n"); + return 0; + } + + // verify foo2 is not found in libfoo-dynamic.dylib", because RTLD_SELF only searches thing this image would have seen + if ( symbolInImage("foo2", "libfoo-dynamic.dylib") ) { + printf("[FAIL] dlsym-RTLD_SELF: foo2 found but should not have been\n"); + return 0; + } + + printf("[PASS] dlsym-RTLD_SELF\n"); + return 0; +} + diff --git a/testing/test-cases/dlsym-handle.dtest/bar.c b/testing/test-cases/dlsym-handle.dtest/bar.c new file mode 100644 index 0000000..30ade8f --- /dev/null +++ b/testing/test-cases/dlsym-handle.dtest/bar.c @@ -0,0 +1,2 @@ + +void bar() { } diff --git a/testing/test-cases/dlsym-handle.dtest/base.c b/testing/test-cases/dlsym-handle.dtest/base.c new file mode 100644 index 0000000..e543e16 --- /dev/null +++ b/testing/test-cases/dlsym-handle.dtest/base.c @@ -0,0 +1,3 @@ + +void base() { } + diff --git a/testing/test-cases/dlsym-handle.dtest/foo.c b/testing/test-cases/dlsym-handle.dtest/foo.c new file mode 100644 index 0000000..ab61e80 --- /dev/null +++ b/testing/test-cases/dlsym-handle.dtest/foo.c @@ -0,0 +1,2 @@ + +void foo() { } diff --git a/testing/test-cases/dlsym-handle.dtest/main.c b/testing/test-cases/dlsym-handle.dtest/main.c new file mode 100644 index 0000000..52e7750 --- /dev/null +++ b/testing/test-cases/dlsym-handle.dtest/main.c @@ -0,0 +1,99 @@ + +// BUILD: $CC base.c -dynamiclib -install_name $RUN_DIR/libbase.dylib -o $BUILD_DIR/libbase.dylib +// BUILD: $CC foo.c -dynamiclib $BUILD_DIR/libbase.dylib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CC bar.c -dynamiclib $BUILD_DIR/libbase.dylib -install_name $RUN_DIR/libbar.dylib -o $BUILD_DIR/libbar.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlsym-handle.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlsym-handle.exe + +#include +#include +#include +#include + + +// verify RTLD_DEFAULT search order + +int mainSymbol = 4; + +int main() +{ + printf("[BEGIN] dlsym-handle\n"); + + void* fooHandle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_LAZY); + if ( fooHandle == NULL ) { + printf("[FAIL] dlsym-handle: libfoo.dylib could not be loaded, %s\n", dlerror()); + return 0; + } + + void* barHandle = dlopen(RUN_DIR "/libbar.dylib", RTLD_LAZY); + if ( barHandle == NULL ) { + printf("[FAIL] dlsym-handle: libbar.dylib could not be loaded, %s\n", dlerror()); + return 0; + } + + // verify fooHandle does not find mainSymbol + if ( dlsym(fooHandle, "mainSymbol") != NULL ) { + printf("[FAIL] dlsym-handle: mainSymbol was found with fooHandle\n"); + return 0; + } + + // verify fooHandle can find foo + if ( dlsym(fooHandle, "foo") == NULL ) { + printf("[FAIL] dlsym-handle: foo not found with fooHandle\n"); + return 0; + } + + // verify fooHandle can find base + if ( dlsym(fooHandle, "base") == NULL ) { + printf("[FAIL] dlsym-handle: base not found with fooHandle\n"); + return 0; + } + + // verify fooHandle cannot find bar + if ( dlsym(fooHandle, "bar") != NULL ) { + printf("[FAIL] dlsym-handle: bar found with fooHandle\n"); + return 0; + } + + // verify barHandle can find bar + if ( dlsym(barHandle, "bar") == NULL ) { + printf("[FAIL] dlsym-handle: bar not found with barHandle\n"); + return 0; + } + + // verify barHandle can find base + if ( dlsym(barHandle, "base") == NULL ) { + printf("[FAIL] dlsym-handle: base not found with barHandle\n"); + return 0; + } + + // verify barHandle cannot find foo + if ( dlsym(barHandle, "foo") != NULL ) { + printf("[FAIL] dlsym-handle: foo found with barHandle\n"); + return 0; + } + + // verify renamed and re-exported symbols work + if ( dlsym(RTLD_DEFAULT, "strcmp") == NULL ) { + printf("[FAIL] dlsym-handle: strcmp not found\n"); + return 0; + } + + // verify bad handle errors + if ( dlsym((void*)0xdeadbeef, "malloc") != NULL ) { + printf("[FAIL] dlsym-handle: malloc found with bad handle\n"); + return 0; + } + else { + const char* message = dlerror(); + if ( strstr(message, "invalid") == NULL ) { + printf("[FAIL] dlsym-handle: invalid handle error message missing 'invalid'\n"); + return 0; + } + } + + printf("[PASS] dlsym-handle\n"); + return 0; +} + diff --git a/testing/test-cases/dlsym-re-export.dtest/foo.c b/testing/test-cases/dlsym-re-export.dtest/foo.c new file mode 100644 index 0000000..c8e9924 --- /dev/null +++ b/testing/test-cases/dlsym-re-export.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlsym-re-export.dtest/main.c b/testing/test-cases/dlsym-re-export.dtest/main.c new file mode 100644 index 0000000..b1752d6 --- /dev/null +++ b/testing/test-cases/dlsym-re-export.dtest/main.c @@ -0,0 +1,43 @@ + +// BUILD: mkdir -p $BUILD_DIR/sub1 $BUILD_DIR/sub2 +// BUILD: $CC sub1.c -dynamiclib -install_name @rpath/libsub1.dylib -o $BUILD_DIR/sub1/libsub1.dylib +// BUILD: $CC sub2.c -dynamiclib -install_name @rpath/libsub2.dylib -o $BUILD_DIR/sub2/libsub2.dylib +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib -rpath @loader_path/sub1 -Wl,-reexport_library,$BUILD_DIR/sub1/libsub1.dylib -Wl,-reexport_library,$BUILD_DIR/sub2/libsub2.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlsym-reexport.exe -DRUN_DIR="$RUN_DIR" -rpath @loader_path/sub2 + +// RUN: ./dlsym-reexport.exe + +// rpath for sub1 is found in libfoo.dylib. rpath for sub2 is found in dlsym-reexport.exe + +#include +#include + +int main() +{ + printf("[BEGIN] dlsym-re-export\n"); + // RTLD_FIRST means dlsym() should only search libfoo.dylib (and any re-exports) + void* handle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_FIRST); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlsym-re-export\n"); + return 0; + } + + void* sym1 = dlsym(handle, "sub1"); + if ( sym1 == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlsym-re-export\n"); + return 0; + } + + void* sym2 = dlsym(handle, "sub2"); + if ( sym2 == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlsym-re-export\n"); + return 0; + } + + printf("[PASS] dlsym-re-export\n"); + return 0; +} + diff --git a/testing/test-cases/dlsym-re-export.dtest/sub1.c b/testing/test-cases/dlsym-re-export.dtest/sub1.c new file mode 100644 index 0000000..98c93f6 --- /dev/null +++ b/testing/test-cases/dlsym-re-export.dtest/sub1.c @@ -0,0 +1,5 @@ +int sub1() +{ + return 1; +} + diff --git a/testing/test-cases/dlsym-re-export.dtest/sub2.c b/testing/test-cases/dlsym-re-export.dtest/sub2.c new file mode 100644 index 0000000..852b89b --- /dev/null +++ b/testing/test-cases/dlsym-re-export.dtest/sub2.c @@ -0,0 +1,5 @@ +int sub2() +{ + return 2; +} + diff --git a/testing/test-cases/dtrace.dtest/main.c b/testing/test-cases/dtrace.dtest/main.c new file mode 100644 index 0000000..237dbd1 --- /dev/null +++ b/testing/test-cases/dtrace.dtest/main.c @@ -0,0 +1,27 @@ +// BUILD: /usr/sbin/dtrace -h -s main.d -o $TEMP_DIR/probes.h +// BUILD: $CC main.c -I$TEMP_DIR -o $BUILD_DIR/dtrace.exe +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/dtrace.exe + +// RUN: $SUDO dtrace -l -n 'dyld_testing*:dtrace.exe:main:callback' -c ./dtrace.exe + + + +#include +#include +#include +#include + +#include "probes.h" + +int main() +{ + printf("[BEGIN] dtrace\n"); + + DYLD_TESTING_CALLBACK(); + + if (!DYLD_TESTING_CALLBACK_ENABLED()) + printf("[FAIL] !DYLD_TESTING_CALLBACK_ENABLED\n"); + + printf("[PASS] dtrace\n"); + return 0; +} diff --git a/testing/test-cases/dtrace.dtest/main.d b/testing/test-cases/dtrace.dtest/main.d new file mode 100644 index 0000000..5918888 --- /dev/null +++ b/testing/test-cases/dtrace.dtest/main.d @@ -0,0 +1,3 @@ +provider dyld_testing { + probe callback(); +}; diff --git a/testing/test-cases/dyld_get_sdk_version.dtest/bad.txt b/testing/test-cases/dyld_get_sdk_version.dtest/bad.txt new file mode 100644 index 0000000..43b3565 --- /dev/null +++ b/testing/test-cases/dyld_get_sdk_version.dtest/bad.txt @@ -0,0 +1 @@ +bad file diff --git a/testing/test-cases/dyld_get_sdk_version.dtest/main.c b/testing/test-cases/dyld_get_sdk_version.dtest/main.c new file mode 100644 index 0000000..efa9be1 --- /dev/null +++ b/testing/test-cases/dyld_get_sdk_version.dtest/main.c @@ -0,0 +1,33 @@ + +// BUILD: $CC main.c -o $BUILD_DIR/sdk-check.exe + +// RUN: ./sdk-check.exe + +#include +#include +#include + +extern struct mach_header __dso_handle; + +int main() +{ + printf("[BEGIN] dyld_get_sdk_version\n"); + + // should succeed + if ( dyld_get_sdk_version(&__dso_handle) == 0 ) { + printf("[FAIL] dyld_get_sdk_version: expected SDK\n"); + return 0; + } + + // should fail + const char* text = "bad text"; + if ( dyld_get_sdk_version((struct mach_header*)text) != 0 ) { + printf("[FAIL] dyld_get_sdk_version: expected failure\n"); + return 0; + } + + printf("[PASS] dyld_get_sdk_version\n"); + + return 0; +} + diff --git a/testing/test-cases/dyld_image_path_containing_address.dtest/main.c b/testing/test-cases/dyld_image_path_containing_address.dtest/main.c new file mode 100644 index 0000000..9064caf --- /dev/null +++ b/testing/test-cases/dyld_image_path_containing_address.dtest/main.c @@ -0,0 +1,32 @@ + +// BUILD: $CC main.c -o $BUILD_DIR/dyld_image_path_containing_address-test.exe + +// RUN: ./dyld_image_path_containing_address-test.exe + + +#include +#include +#include +#include +#include + + +int main() +{ + printf("[BEGIN] dyld_image_path_containing_address-test\n"); + + int count = _dyld_image_count(); + for (int i=0; i < count; ++i) { + const struct mach_header* mh = _dyld_get_image_header(i); + const char* name1 = _dyld_get_image_name(i); + const char* name2 = dyld_image_path_containing_address(mh); + if ( strcmp(name1, name2) != 0 ) { + printf("[FAIL] dyld_image_path_containing_address-test: %s != %s\n", name1, name2); + return 0; + } + } + + printf("[PASS] dyld_image_path_containing_address-test\n"); + return 0; +} + diff --git a/testing/test-cases/dyld_process_info.dtest/linksWithCF.c b/testing/test-cases/dyld_process_info.dtest/linksWithCF.c index ffbe74e..4f6e00a 100644 --- a/testing/test-cases/dyld_process_info.dtest/linksWithCF.c +++ b/testing/test-cases/dyld_process_info.dtest/linksWithCF.c @@ -1,8 +1,10 @@ +#include +#include #include int main() { - task_suspend(mach_task_self()); + (void)kill(getpid(), SIGSTOP); return 0; } diff --git a/testing/test-cases/dyld_process_info.dtest/main.c b/testing/test-cases/dyld_process_info.dtest/main.c index 9e5b41e..41c5ccb 100644 --- a/testing/test-cases/dyld_process_info.dtest/main.c +++ b/testing/test-cases/dyld_process_info.dtest/main.c @@ -15,6 +15,7 @@ #include #include #include +#include extern char** environ; @@ -29,9 +30,14 @@ extern char** environ; cpu_type_t otherArch[] = { CPU_TYPE_ARM64 }; #endif -static task_t launchTest(const char* testProgPath, bool launchOtherArch, bool launchSuspended) +struct task_and_pid { + pid_t pid; + task_t task; +}; + +static struct task_and_pid launchTest(const char* testProgPath, bool launchOtherArch, bool launchSuspended) { - posix_spawnattr_t attr; + posix_spawnattr_t attr = 0; if ( posix_spawnattr_init(&attr) != 0 ) { printf("[FAIL] dyld_process_info posix_spawnattr_init()\n"); exit(0); @@ -50,31 +56,42 @@ static task_t launchTest(const char* testProgPath, bool launchOtherArch, bool la } } - pid_t childPid; + struct task_and_pid child = {0, 0}; const char* argv[] = { testProgPath, NULL }; - int psResult = posix_spawn(&childPid, testProgPath, NULL, &attr, (char**)argv, environ); + int psResult = posix_spawn(&child.pid, 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); + if (posix_spawnattr_destroy(&attr) != 0) { + printf("[FAIL] dyld_process_info posix_spawnattr_destroy()\n"); + exit(0); + } - task_t childTask = 0; - if ( task_for_pid(mach_task_self(), childPid, &childTask) != KERN_SUCCESS ) { + if ( task_for_pid(mach_task_self(), child.pid, &child.task) != KERN_SUCCESS ) { printf("[FAIL] dyld_process_info task_for_pid()\n"); - kill(childPid, SIGKILL); + kill(child.pid, SIGKILL); exit(0); } +#if __x86_64__ + //printf("child pid=%d task=%d (%s, %s)\n", child.pid, child.task, launchOtherArch ? "i386" : "x86_64", launchSuspended ? "suspended" : "active"); +#endif + // 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); + kern_return_t kr = task_info(child.task, TASK_BASIC_INFO, (task_info_t)&info, &count); sleep(1); } while ( info.suspend_count == 0 ); - return childTask; + return child; +} + +static void killTest(struct task_and_pid tp) { + int r = kill(tp.pid, SIGKILL); + waitpid(tp.pid, &r, 0); } static bool hasCF(task_t task, bool launchedSuspended) @@ -127,43 +144,55 @@ static bool hasCF(task_t task, bool launchedSuspended) int main(int argc, const char* argv[]) { + kern_return_t kr = KERN_SUCCESS; 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; + struct task_and_pid child; // launch test program same arch as this program - childTask = launchTest(testProgPath, false, false); - if ( ! hasCF(childTask, false) ) { + child = launchTest(testProgPath, false, false); + if ( ! hasCF(child.task, false) ) { printf("[FAIL] dyld_process_info same arch does not link with CF and dyld\n"); - task_terminate(childTask); + killTest(child); exit(0); } - task_terminate(childTask); - + killTest(child); + // launch test program suspended - childTask = launchTest(testProgPath, false, true); - if ( ! hasCF(childTask, true) ) { + child = launchTest(testProgPath, false, true); + if ( ! hasCF(child.task, true) ) { printf("[FAIL] dyld_process_info suspended does not link with CF and dyld\n"); - task_terminate(childTask); + killTest(child); exit(0); } - task_resume(childTask); - task_terminate(childTask); + (void)kill(child.pid, SIGCONT); + killTest(child); -#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) ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // only mac supports multiple architectures, run test program as other arch too + child = launchTest(testProgPath, true, false); + if ( ! hasCF(child.task, false) ) { printf("[FAIL] dyld_process_info other arch does not link with CF and dyld\n"); - task_terminate(childTask); + killTest(child); + exit(0); + } + killTest(child); + + // launch test program suspended + child = launchTest(testProgPath, true, true); + if ( ! hasCF(child.task, true) ) { + printf("[FAIL] dyld_process_info suspended does not link with CF and dyld\n"); + killTest(child); exit(0); } - task_terminate(childTask); + (void)kill(child.pid, SIGCONT); + killTest(child); #endif // verify this program does not use CF diff --git a/testing/test-cases/dyld_process_info_notify.dtest/main.c b/testing/test-cases/dyld_process_info_notify.dtest/main.c index e04e618..d76d44f 100644 --- a/testing/test-cases/dyld_process_info_notify.dtest/main.c +++ b/testing/test-cases/dyld_process_info_notify.dtest/main.c @@ -18,6 +18,7 @@ #include #include #include +#include extern char** environ; @@ -32,10 +33,15 @@ extern char** environ; cpu_type_t otherArch[] = { CPU_TYPE_ARM64 }; #endif -static task_t launchTest(const char* testProgPath, const char* arg1, bool launchOtherArch, bool launchSuspended) +struct task_and_pid { + pid_t pid; + task_t task; +}; + +static struct task_and_pid 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; + posix_spawnattr_t attr = 0; if ( posix_spawnattr_init(&attr) != 0 ) { printf("[FAIL] dyld_process_info_notify posix_spawnattr_init()\n"); exit(0); @@ -54,23 +60,29 @@ static task_t launchTest(const char* testProgPath, const char* arg1, bool launch } } - pid_t childPid; + struct task_and_pid child; const char* argv[] = { testProgPath, arg1, NULL }; - int psResult = posix_spawn(&childPid, testProgPath, NULL, &attr, (char**)argv, environ); + int psResult = posix_spawn(&child.pid, 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 ) { + if (posix_spawnattr_destroy(&attr) != 0) { + printf("[FAIL] dyld_process_info_notify posix_spawnattr_destroy()\n"); + exit(0); + } + if ( task_for_pid(mach_task_self(), child.pid, &child.task) != KERN_SUCCESS ) { printf("[FAIL] dyld_process_info_notify task_for_pid()\n"); - kill(childPid, SIGKILL); + (void)kill(child.pid, SIGKILL); exit(0); } - return childTask; + return child; +} + +static void killTest(struct task_and_pid tp) { + int r = kill(tp.pid, SIGKILL); + waitpid(tp.pid, &r, 0); } static void wait_util_task_suspended(task_t task) @@ -85,7 +97,7 @@ static void wait_util_task_suspended(task_t task) } -static bool monitor(task_t task, bool disconnectEarly, bool attachLate) +static bool monitor(struct task_and_pid tp, bool disconnectEarly, bool attachLate) { kern_return_t kr; __block bool sawMainExecutable = false; @@ -105,7 +117,7 @@ static bool monitor(task_t task, bool disconnectEarly, bool attachLate) unsigned count = 0; dyld_process_info_notify handle; do { - handle = _dyld_process_info_notify(task, serviceQueue, + handle = _dyld_process_info_notify(tp.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; @@ -142,20 +154,23 @@ static bool monitor(task_t task, bool disconnectEarly, bool attachLate) return false; } - // register for notification that it is entrying main() - _dyld_process_info_notify_main(handle, ^{ - //fprintf(stderr, "target entering main()\n"); - gotMainNotice = true; - if ( !sawMainExecutable || !sawlibSystem ) - gotMainNoticeBeforeAllInitialDylibs = true; - }); - - // if process suspends itself, wait until it has done so - if ( attachLate ) - wait_util_task_suspended(task); + if (!attachLate) { + // If the process starts suspended register for main(), + // otherwise skip since this test is a race between + // process setup and notification registration + _dyld_process_info_notify_main(handle, ^{ + //fprintf(stderr, "target entering main()\n"); + gotMainNotice = true; + if ( !sawMainExecutable || !sawlibSystem ) + gotMainNoticeBeforeAllInitialDylibs = true; + }); + } else { + // if process suspends itself, wait until it has done so + wait_util_task_suspended(tp.task); + } // resume from initial suspend - task_resume(task); + kill(tp.pid, SIGCONT); // block waiting for notification that target has exited bool gotSignal = (dispatch_semaphore_wait(taskDone, dispatch_time(DISPATCH_TIME_NOW, 10LL * NSEC_PER_SEC)) == 0); @@ -167,29 +182,33 @@ static bool monitor(task_t task, bool disconnectEarly, bool attachLate) return false; } - if ( !attachLate && !sawMainExecutable ) { - fprintf(stderr, "did not get load notification of main executable\n"); - return false; - } + // Do not run any tests associated with startup unless the kernel suspended us + // before main() + if (!attachLate) { + if ( !sawMainExecutable ) { + fprintf(stderr, "did not get load notification of main executable\n"); + return false; + } - if ( !gotMainNotice ) { - fprintf(stderr, "did not get notification of main()\n"); - return false; - } + if ( !gotMainNotice ) { + fprintf(stderr, "did not get notification of main()\n"); + return false; + } - if ( gotMainNoticeBeforeAllInitialDylibs ) { - fprintf(stderr, "notification of main() arrived before all initial dylibs\n"); - return false; - } + if ( gotMainNoticeBeforeAllInitialDylibs ) { + fprintf(stderr, "notification of main() arrived before all initial dylibs\n"); + return false; + } - if ( gotFooNoticeBeforeMain ) { - fprintf(stderr, "notification of main() arrived after libfoo load notice\n"); - return false; - } + if ( gotFooNoticeBeforeMain ) { + fprintf(stderr, "notification of main() arrived after libfoo load notice\n"); + return false; + } - if ( !attachLate && !sawlibSystem ) { - fprintf(stderr, "did not get load notification of libSystem\n"); - return false; + if ( !sawlibSystem ) { + fprintf(stderr, "did not get load notification of libSystem\n"); + return false; + } } if ( disconnectEarly ) { @@ -216,13 +235,13 @@ static bool monitor(task_t task, bool disconnectEarly, bool attachLate) return true; } -static void validateMaxNotifies(task_t task) +static void validateMaxNotifies(struct task_and_pid tp) { 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, + handles[i] = _dyld_process_info_notify(tp.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], @@ -241,7 +260,7 @@ static void validateMaxNotifies(task_t task) } else { fprintf(stderr, "_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d\n", kr, i); - task_terminate(task); + killTest(tp); exit(0); } } @@ -265,55 +284,55 @@ int main(int argc, const char* argv[]) const char* testProgPath = argv[1]; dispatch_async(dispatch_get_main_queue(), ^{ - task_t childTask; + struct task_and_pid child; // test 1) launch test program suspended in same arch as this program - childTask = launchTest(testProgPath, "", false, true); - if ( ! monitor(childTask, false, false) ) { + child = launchTest(testProgPath, "", false, true); + if ( ! monitor(child, false, false) ) { printf("[FAIL] dyld_process_info_notify launch suspended missed some notifications\n"); - task_terminate(childTask); + killTest(child); exit(0); } - task_terminate(childTask); + killTest(child); // 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) ) { + child = launchTest(testProgPath, "suspend-in-main", false, false); + validateMaxNotifies(child); + if ( ! monitor(child, false, true) ) { printf("[FAIL] dyld_process_info_notify launch suspend-in-main missed some notifications\n"); - task_terminate(childTask); + killTest(child); exit(0); } - task_terminate(childTask); + killTest(child); -#if !TARGET_OS_WATCH && !TARGET_OS_TV && __LP64__ +#if __MAC_OS_X_VERSION_MIN_REQUIRED // test 3) launch test program suspended in opposite arch as this program - childTask = launchTest(testProgPath, "", true, true); - if ( ! monitor(childTask, false, false) ) { + child = launchTest(testProgPath, "", true, true); + if ( ! monitor(child, false, false) ) { printf("[FAIL] dyld_process_info_notify launch suspended other arch missed some notifications\n"); - task_terminate(childTask); + killTest(child); exit(0); } - task_terminate(childTask); + killTest(child); // 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) ) { + child = launchTest(testProgPath, "suspend-in-main", true, false); + if ( ! monitor(child, false, true) ) { printf("[FAIL] dyld_process_info_notify launch other arch suspend-in-main missed some notifications\n"); - task_terminate(childTask); + killTest(child); exit(0); } - task_terminate(childTask); + killTest(child); #endif // test 5) launch test program where we disconnect from it after first dlopen - childTask = launchTest(testProgPath, "", false, true); - if ( ! monitor(childTask, true, false) ) { + child = launchTest(testProgPath, "", false, true); + if ( ! monitor(child, true, false) ) { printf("[FAIL] dyld_process_info_notify connect/disconnect missed some notifications\n"); - task_terminate(childTask); + killTest(child); exit(0); } - task_terminate(childTask); + killTest(child); printf("[PASS] dyld_process_info_notify\n"); exit(0); diff --git a/testing/test-cases/dyld_process_info_notify.dtest/target.c b/testing/test-cases/dyld_process_info_notify.dtest/target.c index f73e5a0..55fd668 100644 --- a/testing/test-cases/dyld_process_info_notify.dtest/target.c +++ b/testing/test-cases/dyld_process_info_notify.dtest/target.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -10,7 +11,7 @@ int main(int argc, const char* argv[]) { if ( (argc > 1) && (strcmp(argv[1], "suspend-in-main") == 0) ) - task_suspend(mach_task_self()); + (void)kill(getpid(), SIGSTOP); for (int i=0; i < 3; ++i) { void* h = dlopen("./libfoo.dylib", 0); diff --git a/testing/test-cases/dyld_process_info_unload.dtest/main.c b/testing/test-cases/dyld_process_info_unload.dtest/main.c index 3a8bfd5..6954ad1 100644 --- a/testing/test-cases/dyld_process_info_unload.dtest/main.c +++ b/testing/test-cases/dyld_process_info_unload.dtest/main.c @@ -30,40 +30,47 @@ extern char** environ; cpu_type_t otherArch[] = { CPU_TYPE_ARM64 }; #endif -static task_t launchTest(const char* testProgPath, bool launchOtherArch, bool launchSuspended) +struct task_and_pid { + pid_t pid; + task_t task; +}; + +static struct task_and_pid launchTest(const char* testProgPath, bool launchOtherArch, bool launchSuspended) { - posix_spawnattr_t attr; + posix_spawnattr_t attr = 0; if ( posix_spawnattr_init(&attr) != 0 ) { - printf("[FAIL] dyld_process_info posix_spawnattr_init()\n"); + printf("[FAIL] dyld_process_info_unload 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"); + printf("[FAIL] dyld_process_info_unload 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"); + printf("[FAIL] dyld_process_info_unload posix_spawnattr_setbinpref_np()\n"); exit(0); } } - pid_t childPid; + struct task_and_pid child; const char* argv[] = { testProgPath, NULL }; - int psResult = posix_spawn(&childPid, testProgPath, NULL, &attr, (char**)argv, environ); + int psResult = posix_spawn(&child.pid, testProgPath, NULL, &attr, (char**)argv, environ); if ( psResult != 0 ) { - printf("[FAIL] dyld_process_info posix_spawn(%s) failed, err=%d\n", testProgPath, psResult); + printf("[FAIL] dyld_process_info_unload posix_spawn(%s) failed, err=%d\n", testProgPath, psResult); + exit(0); + } + if (posix_spawnattr_destroy(&attr) != 0) { + printf("[FAIL] dyld_process_info_unload posix_spawnattr_destroy()\n"); 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); + if ( task_for_pid(mach_task_self(), child.pid, &child.task) != KERN_SUCCESS ) { + printf("[FAIL] dyld_process_info_unload task_for_pid()\n"); + kill(child.pid, SIGKILL); exit(0); } @@ -71,22 +78,27 @@ static task_t launchTest(const char* testProgPath, bool launchOtherArch, bool la 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); + kern_return_t kr = task_info(child.task, TASK_BASIC_INFO, (task_info_t)&info, &count); sleep(1); } while ( info.suspend_count == 0 ); - return childTask; + return child; } -static bool alwaysGetImages(task_t task, bool launchedSuspended) +static void killTest(struct task_and_pid tp) { + int r = kill(tp.pid, SIGKILL); + waitpid(tp.pid, &r, 0); +} + +static bool alwaysGetImages(struct task_and_pid tp, bool launchedSuspended) { int failCount = 0; for (int i=0; i < 100; ++i ) { kern_return_t result; - dyld_process_info info = _dyld_process_info_create(task, 0, &result); + dyld_process_info info = _dyld_process_info_create(tp.task, 0, &result); //fprintf(stderr, "info=%p, result=%08X\n", info, result); if ( i == 0 ) - task_resume(task); + (void)kill(tp.pid, SIGCONT); if ( info == NULL ) { failCount++; //fprintf(stderr, "info=%p, result=%08X\n", info, result); @@ -96,7 +108,10 @@ static bool alwaysGetImages(task_t task, bool launchedSuspended) _dyld_process_info_release(info); } } - if ( failCount !=0 ) { + // ideally the fail count would be zero. But the target is dlopen/dlclosing in a tight loop, so there may never be a stable set of images. + // The real bug driving this test case was _dyld_process_info_create() crashing when the the image list changed too fast. + // The important thing is to not crash. Getting NULL back is ok. + if ( failCount > 50 ) { printf("[FAIL] dyld_process_info_unload %d out of 100 calls to _dyld_process_info_create() failed\n", failCount); return false; } @@ -113,16 +128,15 @@ int main(int argc, const char* argv[]) exit(0); } const char* testProgPath = argv[1]; - task_t childTask; + struct task_and_pid child; // launch test program suspended - childTask = launchTest(testProgPath, false, true); - if ( ! alwaysGetImages(childTask, true) ) { - task_terminate(childTask); + child = launchTest(testProgPath, false, true); + if ( ! alwaysGetImages(child, true) ) { + killTest(child); exit(0); } - task_terminate(childTask); - + killTest(child); printf("[PASS] dyld_process_info_unload\n"); return 0; diff --git a/testing/test-cases/env-DYLD_FALLBACK_FRAMEWORK_PATH.dtest/foo.c b/testing/test-cases/env-DYLD_FALLBACK_FRAMEWORK_PATH.dtest/foo.c new file mode 100644 index 0000000..b6dfea2 --- /dev/null +++ b/testing/test-cases/env-DYLD_FALLBACK_FRAMEWORK_PATH.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return VALUE; +} + diff --git a/testing/test-cases/env-DYLD_FALLBACK_FRAMEWORK_PATH.dtest/main.c b/testing/test-cases/env-DYLD_FALLBACK_FRAMEWORK_PATH.dtest/main.c new file mode 100644 index 0000000..c26697b --- /dev/null +++ b/testing/test-cases/env-DYLD_FALLBACK_FRAMEWORK_PATH.dtest/main.c @@ -0,0 +1,25 @@ + +// BUILD: mkdir -p $TEMP_DIR/Foo.framework $BUILD_DIR/FallbackFrameworks/Foo.framework +// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/Foo.framework/Foo -install_name $RUN_DIR/Foo.framework/Foo -DVALUE=1 +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/FallbackFrameworks/Foo.framework/Foo -install_name $RUN_DIR/Foo.framework/Foo -DVALUE=42 +// BUILD: $CC main.c -o $BUILD_DIR/main.exe $TEMP_DIR/Foo.framework/Foo +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main.exe + +// RUN: DYLD_FALLBACK_FRAMEWORK_PATH=$RUN_DIR/FallbackFrameworks/ ./main.exe + +#include +#include + +extern int foo(); + +int main() +{ + printf("[BEGIN] env-DYLD_FALLBACK_FRAMEWORK_PATH\n"); + + if ( foo() == 42 ) + printf("[PASS] env-DYLD_FALLBACK_FRAMEWORK_PATH\n"); + else + printf("[FAIL] env-DYLD_FALLBACK_FRAMEWORK_PATH\n"); + + return 0; +} diff --git a/testing/test-cases/env-DYLD_FALLBACK_LIBRARY_PATH.dtest/foo.c b/testing/test-cases/env-DYLD_FALLBACK_LIBRARY_PATH.dtest/foo.c new file mode 100644 index 0000000..b6dfea2 --- /dev/null +++ b/testing/test-cases/env-DYLD_FALLBACK_LIBRARY_PATH.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return VALUE; +} + diff --git a/testing/test-cases/env-DYLD_FALLBACK_LIBRARY_PATH.dtest/main.c b/testing/test-cases/env-DYLD_FALLBACK_LIBRARY_PATH.dtest/main.c new file mode 100644 index 0000000..1c94836 --- /dev/null +++ b/testing/test-cases/env-DYLD_FALLBACK_LIBRARY_PATH.dtest/main.c @@ -0,0 +1,25 @@ + +// BUILD: mkdir -p $BUILD_DIR/fallback +// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib -DVALUE=1 +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/fallback/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib -DVALUE=42 +// BUILD: $CC main.c -o $BUILD_DIR/main.exe $TEMP_DIR/libfoo.dylib +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main.exe + +// RUN: DYLD_FALLBACK_LIBRARY_PATH=$RUN_DIR/fallback/ ./main.exe + +#include + +extern int foo(); + +int main() +{ + printf("[BEGIN] env-DYLD_FALLBACK_LIBRARY_PATH\n"); + + if ( foo() == 42 ) + printf("[PASS] env-DYLD_FALLBACK_LIBRARY_PATH\n"); + else + printf("[FAIL] env-DYLD_FALLBACK_LIBRARY_PATH\n"); + + return 0; +} + diff --git a/testing/test-cases/env-DYLD_FRAMEWORK_PATH.dtest/foo.c b/testing/test-cases/env-DYLD_FRAMEWORK_PATH.dtest/foo.c new file mode 100644 index 0000000..b6dfea2 --- /dev/null +++ b/testing/test-cases/env-DYLD_FRAMEWORK_PATH.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return VALUE; +} + diff --git a/testing/test-cases/env-DYLD_FRAMEWORK_PATH.dtest/main.c b/testing/test-cases/env-DYLD_FRAMEWORK_PATH.dtest/main.c new file mode 100644 index 0000000..915729f --- /dev/null +++ b/testing/test-cases/env-DYLD_FRAMEWORK_PATH.dtest/main.c @@ -0,0 +1,29 @@ + +// BUILD: mkdir -p $BUILD_DIR/Frameworks/Foo.framework $BUILD_DIR/Frameworks-alt/Foo.framework +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/Frameworks/Foo.framework/Foo -install_name $RUN_DIR/Frameworks/Foo.framework/Foo -DVALUE=1 +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/Frameworks-alt/Foo.framework/Foo -install_name $RUN_DIR/Frameworks/Foo.framework/Foo -DVALUE=42 +// BUILD: $CC main.c -o $BUILD_DIR/main.exe $BUILD_DIR/Frameworks/Foo.framework/Foo +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main.exe + +// RUN: ./main.exe +// RUN: DYLD_FRAMEWORK_PATH=$RUN_DIR/Frameworks-alt/ ./main.exe + +#include +#include + +extern int foo(); + +int main() +{ + int expected = (getenv("DYLD_FRAMEWORK_PATH") != NULL) ? 42 : 1; + + printf("[BEGIN] env-DYLD_FRAMEWORK_PATH, expect %d\n", expected); + + if ( foo() == expected ) + printf("[PASS] env-DYLD_FRAMEWORK_PATH\n"); + else + printf("[FAIL] env-DYLD_FRAMEWORK_PATH\n"); + + return 0; +} + diff --git a/testing/test-cases/env-DYLD_LIBRARY_PATH.dtest/foo.c b/testing/test-cases/env-DYLD_LIBRARY_PATH.dtest/foo.c new file mode 100644 index 0000000..b6dfea2 --- /dev/null +++ b/testing/test-cases/env-DYLD_LIBRARY_PATH.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return VALUE; +} + diff --git a/testing/test-cases/env-DYLD_LIBRARY_PATH.dtest/main.c b/testing/test-cases/env-DYLD_LIBRARY_PATH.dtest/main.c new file mode 100644 index 0000000..69d199e --- /dev/null +++ b/testing/test-cases/env-DYLD_LIBRARY_PATH.dtest/main.c @@ -0,0 +1,29 @@ + +// BUILD: mkdir -p $BUILD_DIR/door1 $BUILD_DIR/door2 +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/door1/libfoo.dylib -install_name $RUN_DIR/door1/libfoo.dylib -DVALUE=1 +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/door2/libfoo.dylib -install_name $RUN_DIR/door2/libfoo.dylib -DVALUE=42 +// BUILD: $CC main.c -o $BUILD_DIR/main.exe $BUILD_DIR/door1/libfoo.dylib +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main.exe + +// RUN: ./main.exe +// RUN: DYLD_LIBRARY_PATH=$RUN_DIR/door2/ ./main.exe + +#include +#include + +extern int foo(); + +int main() +{ + int expected = (getenv("DYLD_LIBRARY_PATH") != NULL) ? 42 : 1; + + printf("[BEGIN] env-DYLD_LIBRARY_PATH, expect %d\n", expected); + + if ( foo() == expected ) + printf("[PASS] env-DYLD_LIBRARY_PATH\n"); + else + printf("[FAIL] env-DYLD_LIBRARY_PATH\n"); + + return 0; +} + diff --git a/testing/test-cases/flat-namespace.dtest/foo.c b/testing/test-cases/flat-namespace.dtest/foo.c new file mode 100644 index 0000000..20ae519 --- /dev/null +++ b/testing/test-cases/flat-namespace.dtest/foo.c @@ -0,0 +1,21 @@ + +#include +#include + +char buffer[100000]; +char* p = buffer; + +void* malloc(size_t size) +{ + // bump ptr allocate and fill second half with '#' + char* result = p; + p += size; + memset(result, '#', size); + p = (char*)(((long)p + 15) & (-16)); // 16-byte align next malloc + return result; +} + +void free(void* p) +{ +} + diff --git a/testing/test-cases/flat-namespace.dtest/main.c b/testing/test-cases/flat-namespace.dtest/main.c new file mode 100644 index 0000000..37c99fc --- /dev/null +++ b/testing/test-cases/flat-namespace.dtest/main.c @@ -0,0 +1,28 @@ +// BUILD_ONLY: MacOSX + +// 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/flat-namespace.exe -flat_namespace + +// RUN: ./flat-namespace.exe + + + +#include +#include +#include + +int main() +{ + printf("[BEGIN] flat-namespace\n"); + + // check that the malloc in libfoo.dylib was used by looking at the content the allocated buffer + // strncmp is tricky for flat namespace because it is re-exporte and renamed + char* p1 = malloc(10); + if ( strncmp(p1, "##########", 10) != 0 ) { + printf("[FAIL] malloc() from main executable not interposed\n"); + return 0; + } + + printf("[PASS] flat-namespace\n"); + return 0; +} diff --git a/testing/test-cases/interpose-malloc.dtest/foo.c b/testing/test-cases/interpose-malloc.dtest/foo.c new file mode 100644 index 0000000..d539c96 --- /dev/null +++ b/testing/test-cases/interpose-malloc.dtest/foo.c @@ -0,0 +1,15 @@ + +#include + +void* myalloc1(size_t sz) +{ + return malloc(sz); +} + +void* (*pMalloc)(size_t) = &malloc; + +void* myalloc2(size_t sz) +{ + return (*pMalloc)(sz); +} + diff --git a/testing/test-cases/interpose-malloc.dtest/interposer.c b/testing/test-cases/interpose-malloc.dtest/interposer.c new file mode 100644 index 0000000..ceac979 --- /dev/null +++ b/testing/test-cases/interpose-malloc.dtest/interposer.c @@ -0,0 +1,25 @@ +#include +#include +#include + + +char buffer[100000]; +char* p = buffer; + +void* mymalloc(size_t size) +{ + // bump ptr allocate twice the size and fill second half with '#' + char* result = p; + p += size; + memset(p, '#', size); + p += size; + p = (char*)(((long)p + 15) & (-16)); // 16-byte align next malloc + return result; +} + +void myfree(void* p) +{ +} + +DYLD_INTERPOSE(mymalloc, malloc) +DYLD_INTERPOSE(myfree, free) diff --git a/testing/test-cases/interpose-malloc.dtest/main.c b/testing/test-cases/interpose-malloc.dtest/main.c new file mode 100644 index 0000000..9ef1842 --- /dev/null +++ b/testing/test-cases/interpose-malloc.dtest/main.c @@ -0,0 +1,48 @@ +// 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-malloc.exe +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/interpose-malloc.exe +// BUILD: $CC interposer.c -dynamiclib -o $BUILD_DIR/libmyalloc.dylib -install_name libmyalloc.dylib + +// RUN: DYLD_INSERT_LIBRARIES=libmyalloc.dylib ./interpose-malloc.exe + + + +#include +#include +#include + +extern void* myalloc1(size_t); +extern void* myalloc2(size_t); + +int main() +{ + printf("[BEGIN] interpose-malloc\n"); + + char* p1 = malloc(10); + if ( strncmp(p1+10, "##########", 10) != 0 ) { + printf("[FAIL] interpose-malloc malloc() from main executable not interposed\n"); + return 0; + } + + void* p2 = myalloc1(6); + if ( strncmp(p2+6, "######", 6) != 0 ) { + printf("[FAIL] interpose-malloc myalloc1() from libfoo.dylib not interposed\n"); + return 0; + } + + void* p3 = myalloc2(10); + if ( strncmp(p3+10, "##########", 10) != 0 ) { + printf("[FAIL] interpose-malloc myalloc2() from libfoo.dylib not interposed\n"); + return 0; + } + + void* p4 = strdup("hello"); + if ( strncmp(p4+6, "#######", 6) != 0 ) { + printf("[FAIL] interpose-malloc malloc() from strdup not interposed\n"); + return 0; + } + + //printf("%p %p %p %p\n", p1, p2, p3, p4); + printf("[PASS] interpose-malloc\n"); + return 0; +} diff --git a/testing/test-cases/operator-new.dtest/main.cxx b/testing/test-cases/operator-new.dtest/main.cxx new file mode 100644 index 0000000..d985e8f --- /dev/null +++ b/testing/test-cases/operator-new.dtest/main.cxx @@ -0,0 +1,37 @@ + +// BUILD: $CXX main.cxx -o $BUILD_DIR/operator-new.exe + +// RUN: ./operator-new.exe + +#include +#include + + + +// +// This test case verifies that calling operator new[] in libstdc++.dylib +// will turn around and call operator new in this main exectuable +// + +static void* ptr; + +void* operator new(size_t s) throw (std::bad_alloc) +{ + ptr = malloc(s); + return ptr; +} + +int main() +{ + printf("[BEGIN] operator-new\n"); + + char* stuff = new char[24]; + if ( (void*)stuff == ptr ) + printf("[PASS] operator-new\n"); + else + printf("[FAIL] operator-new\n"); + + return 0; +} + + diff --git a/testing/test-cases/thread-local-cleanup.dtest/foo.c b/testing/test-cases/thread-local-cleanup.dtest/foo.c new file mode 100644 index 0000000..ac354a5 --- /dev/null +++ b/testing/test-cases/thread-local-cleanup.dtest/foo.c @@ -0,0 +1,6 @@ + +__thread int a; +__thread int b = 5; + + + diff --git a/testing/test-cases/thread-local-cleanup.dtest/main.c b/testing/test-cases/thread-local-cleanup.dtest/main.c new file mode 100644 index 0000000..e301792 --- /dev/null +++ b/testing/test-cases/thread-local-cleanup.dtest/main.c @@ -0,0 +1,36 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libtlv.dylib -o $BUILD_DIR/libtlv.dylib +// BUILD: $CC main.c -DRUN_DIR="$RUN_DIR" -o $BUILD_DIR/thread-local-cleanup.exe + +// RUN: ./thread-local-cleanup.exe + +#include +#include + + + + + +int main() +{ + printf("[BEGIN] thread-local-cleanup\n"); + + for (int i=0; i < 1000; ++i) { + void* handle = dlopen(RUN_DIR "/libtlv.dylib", RTLD_FIRST); + if ( handle == NULL ) { + printf("[FAIL] thread-local-cleanup: iteration %d %s\n", i, dlerror()); + return 0; + } + + int result = dlclose(handle); + if ( result != 0 ) { + printf("[FAIL] thread-local-cleanup: iteration %d %s\n", i, dlerror()); + return 0; + } + } + + printf("[PASS] thread-local-cleanup\n"); + + return 0; +} + 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 index 228935b..32ac531 100644 --- a/unit-tests/test-cases/dyld_shared_cache_iterate_text/main.c +++ b/unit-tests/test-cases/dyld_shared_cache_iterate_text/main.c @@ -36,18 +36,13 @@ 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); + _dyld_get_shared_cache_uuid(curUuid); 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); + //printf(" cur: 0x%09llX -> 0x%09llX off=0x%0llX %s\n", info->loadAddressUnslid, info->loadAddressUnslid + info->textSegmentSize, info->textSegmentOffset, info->path); }); if ( result != 0 ) { FAIL("dyld_shared_cache_iterate_text: dyld_shared_cache_iterate_text() failed");