From: Apple Date: Thu, 4 Oct 2018 22:11:26 +0000 (+0000) Subject: dyld-625.13.tar.gz X-Git-Tag: macos-1014^0 X-Git-Url: https://git.saurik.com/apple/dyld.git/commitdiff_plain/6cae9b637796eb548e85897ce8a2a058bb5799cf?ds=inline dyld-625.13.tar.gz --- diff --git a/bin/expand.pl b/bin/expand.pl deleted file mode 100755 index b21ef3f..0000000 --- a/bin/expand.pl +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/perl - -use strict; - - -my $sdk = $ENV{"SDKROOT"}; -my $availCmd = $sdk . "/usr/local/libexec/availability.pl"; - -sub expandVersions -{ - my $macroPrefix = shift; - my $availArg = shift; - - my $cmd = $availCmd . " " . $availArg; - my $versionList = `$cmd`; - my $tmp = $versionList; - while ($tmp =~ m/^\s*([\S]+)(.*)$/) { - my $vers = $1; - $tmp = $2; - - my $major = 0; - my $minor = 0; - my $revision = 0; - my $uvers; - - if ($vers =~ m/^(\d+)$/) { - $major = $1; - $uvers = sprintf("%d_0", $major); - } elsif ($vers =~ m/^(\d+).(\d+)$/) { - $major = $1; - $minor = $2; - $uvers = sprintf("%d_%d", $major, $minor); - } elsif ($vers =~ m/^(\d+).(\d+).(\d+)$/) { - $major = $1; - $minor = $2; - $revision = $3; - if ($revision == 0) { - $uvers = sprintf("%d_%d", $major, $minor); - } - else { - $uvers = sprintf("%d_%d_%d", $major, $minor, $revision); - } - } - printf "#define %s%-18s 0x00%02X%02X%02X\n", $macroPrefix, $uvers, $major, $minor, $revision; - } -} - - - - -while() -{ - if(m/^\/\/\@MAC_VERSION_DEFS\@$/) { - expandVersions("DYLD_MACOSX_VERSION_", "--macosx"); - } - elsif(m/^\/\/\@IOS_VERSION_DEFS\@$/) { - expandVersions("DYLD_IOS_VERSION_", "--ios"); - } - elsif(m/^\/\/\@WATCHOS_VERSION_DEFS\@$/) { - expandVersions("DYLD_WATCHOS_VERSION_", "--watchos"); - } - else { - print $_; - } -} - diff --git a/bin/expand.rb b/bin/expand.rb new file mode 100755 index 0000000..8eff336 --- /dev/null +++ b/bin/expand.rb @@ -0,0 +1,94 @@ +#!/usr/bin/env ruby + +require 'yaml' + +$availCmd = ENV["SDKROOT"] + "/usr/local/libexec/availability.pl"; + +def versionString(vers) + uvers = "" + if vers =~ /^(\d+)$/ + uvers = "#{$1}_0" + elsif vers =~ /^(\d+).(\d+)$/ + uvers = "#{$1}_#{$2}" + elsif vers =~ /^(\d+).(\d+).(\d+)$/ + if $3 == 0 + uvers = "#{$1}_#{$2}" + else + uvers = "#{$1}_#{$2}_#{$3}" + end + end + uvers +end + +def versionHex(vers) + major = 0; + minor = 0; + revision = 0; + + if vers =~ /^(\d+)$/ + major = $1.to_i; + elsif vers =~ /^(\d+).(\d+)$/ + major = $1.to_i; + minor = $2.to_i; + elsif vers =~ /^(\d+).(\d+).(\d+)$/ + major = $1.to_i; + minor = $2.to_i; + revision = $3.to_i; + end + "0x00#{major.to_s(16).rjust(2, '0')}#{minor.to_s(16).rjust(2, '0')}#{revision.to_s(16).rjust(2, '0')}" +end + +def expandVersions(prefix, arg) + versionList = `#{$availCmd} #{arg}`.gsub(/\s+/m, ' ').strip.split(" ") + versionList.each { |version| + puts "#define #{prefix}#{versionString(version)}".ljust(48, ' ') + versionHex(version) + } +end + +def expandPlatformVersions(prefix, platform, arg) + versionList = `#{$availCmd} #{arg}`.gsub(/\s+/m, ' ').strip.split(" ") + versionList.each { |version| + puts "static dyld_build_version_t dyld_platform_version_#{prefix}_#{versionString(version)}".ljust(72, ' ') + "= { .platform = #{platform}, .version = #{versionHex(version)} };" + } +end + +def versionSetsForOSes(versionSets, key, platform, target) + puts "#if #{target}" + versionSets.each { |k,v| + puts "#define dyld_#{k}_os_versions dyld_platform_version_#{platform}_#{versionString(v[key].to_s)}" + } + puts "#endif /* #{target} */" +end + +def expandVersionSets() + versionSets = YAML.load(`#{$availCmd} --sets`) + versionSetsForOSes(versionSets, "macos", "macOS", "TARGET_OS_OSX") + versionSetsForOSes(versionSets, "ios", "iOS", "TARGET_OS_IOS") + versionSetsForOSes(versionSets, "tvos", "tvOS", "TARGET_OS_TV") + versionSetsForOSes(versionSets, "watchos", "watchOS", "TARGET_OS_WATCH") + versionSetsForOSes(versionSets, "bridgeos", "bridgeOS", "TARGET_OS_BRIDGE") +end + +ARGF.each do |line| + if line =~ /^\/\/\@MAC_VERSION_DEFS\@$/ + expandVersions("DYLD_MACOSX_VERSION_", "--macosx") + elsif line =~ /^\/\/\@IOS_VERSION_DEFS\@$/ + expandVersions("DYLD_IOS_VERSION_", "--ios") + elsif line =~ /^\/\/\@WATCHOS_VERSION_DEFS\@$/ + expandVersions("DYLD_WATCHOS_VERSION_", "--watchos") + elsif line =~ /^\/\/\@MACOS_PLATFORM_VERSION_DEFS\@$/ + expandPlatformVersions("macOS", "PLATFORM_MACOS", "--macosx") + elsif line =~ /^\/\/\@IOS_PLATFORM_VERSION_DEFS\@$/ + expandPlatformVersions("iOS", "PLATFORM_IOS", "--ios") + elsif line =~ /^\/\/\@WATCHOS_PLATFORM_VERSION_DEFS\@$/ + expandPlatformVersions("watchOS", "PLATFORM_WATCHOS", "--watchos") + elsif line =~ /^\/\/\@TVOS_PLATFORM_VERSION_DEFS\@$/ + expandPlatformVersions("tvOS", "PLATFORM_TVOS", "--appletvos") + elsif line =~ /^\/\/\@BRIDGEOS_PLATFORM_VERSION_DEFS\@$/ + expandPlatformVersions("bridgeOS", "PLATFORM_BRIDGEOS", "--bridgeos") + elsif line =~ /^\/\/\@VERSION_SET_DEFS\@$/ + expandVersionSets() + else + puts line + end +end diff --git a/configs/dyld.xcconfig b/configs/dyld.xcconfig index d1ec099..bbae560 100644 --- a/configs/dyld.xcconfig +++ b/configs/dyld.xcconfig @@ -15,7 +15,7 @@ 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 +GCC_PREPROCESSOR_DEFINITIONS = 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 +GCC_PREPROCESSOR_DEFINITIONS = DYLD_VERSION=$(RC_ProjectSourceVersion) BUILDING_DYLD=1 diff --git a/configs/libdyld.xcconfig b/configs/libdyld.xcconfig index 8f06e5a..3e66a3c 100644 --- a/configs/libdyld.xcconfig +++ b/configs/libdyld.xcconfig @@ -1,14 +1,9 @@ -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 +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-lcompiler_rt +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-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-lcompiler_rt 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 +IS_ZIPPERED = YES diff --git a/configs/update_dyld_shared_cache.xcconfig b/configs/update_dyld_shared_cache.xcconfig index e45c714..8b13789 100644 --- a/configs/update_dyld_shared_cache.xcconfig +++ b/configs/update_dyld_shared_cache.xcconfig @@ -1,4 +1 @@ - -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 index e4f7bfa..1a04e7b 100644 --- a/configs/update_dyld_sim_shared_cache.xcconfig +++ b/configs/update_dyld_sim_shared_cache.xcconfig @@ -1,3 +1,9 @@ #include "/AppleInternal/XcodeConfig/PlatformSupportHost.xcconfig" + +//:configuration = Debug +GCC_PREPROCESSOR_DEFINITIONS = BUILDING_CACHE_BUILDER=1 DEBUG=1 + +//:configuration = Release +GCC_PREPROCESSOR_DEFINITIONS = BUILDING_CACHE_BUILDER=1 diff --git a/doc/tracing/dyld.codes b/doc/tracing/dyld.codes new file mode 100644 index 0000000..f69124c --- /dev/null +++ b/doc/tracing/dyld.codes @@ -0,0 +1,22 @@ +0x1f070000 dyld.static_intializer +0x1f070004 dyld.launch_executable +0x1f070008 dyld.map_file +0x1f07000c dyld.apply_fixups +0x1f070010 dyld.attach_codesignature +0x1f070014 dyld.build_closure +0x1f070018 dyld.add_image_callback +0x1f07001c dyld.remove_image_callback +0x1f070020 dyld.objc_image_init +0x1f070024 dyld.objc_images_map +0x1f070028 dyld.apply_interposing +0x1f07002c dyld.gdb_image_notifier +0x1f070030 dyld.remote_image_notifier +0x1f080000 dyld.dlopen +0x1f080004 dyld.dlopen_preflight +0x1f080008 dyld.dlclose +0x1f08000c dyld.dlsym +0x1f080010 dyld.dladdr +0x1f090000 dyld.DEBUG.vm_remap +0x1f090004 dyld.DEBUG.vm_dealloc +0x1f090008 dyld.DEBUG.map_loop +0x1f09000c dyld.DEBUG.marker diff --git a/doc/tracing/dyld.plist b/doc/tracing/dyld.plist new file mode 100644 index 0000000..225d316 --- /dev/null +++ b/doc/tracing/dyld.plist @@ -0,0 +1,127 @@ + + + + + + Name + dyld + Children + + + Name + dlopen + Type + Interval + EventsMatchedBy + Arg1 + KTraceCodeBegin + 0x1f070005 + KTraceCodeEnd + 0x1f070006 + ArgValueTypesBegin + + Arg2 + String + + ArgNamesBegin + + Arg2 + Path + + + + Name + dlopen_preflight + Type + Interval + EventsMatchedBy + Arg1 + KTraceCodeBegin + 0x1f070009 + KTraceCodeEnd + 0x1f07000a + ArgValueTypesBegin + + Arg2 + String + + ArgNamesBegin + + Arg2 + Path + + + + Name + map_file + Type + Interval + EventsMatchedBy + Arg1 + KTraceCodeBegin + 0x1f070005 + KTraceCodeEnd + 0x1f070006 + ArgValueTypesBegin + + Arg2 + String + + ArgNamesBegin + + Arg2 + Path + + + + Name + dlsym + Type + Interval + EventsMatchedBy + Arg1 + KTraceCodeBegin + 0x1f07000d + KTraceCodeEnd + 0x1f07000e + ArgValueTypesBegin + + Arg2 + String + + ArgNamesBegin + + Arg2 + Path + + + + Name + Static Initializer + Type + Interval + EventsMatchedBy + Arg1 + KTraceCodeBegin + 0x1f070001 + KTraceCodeEnd + 0x1f070002 + ArgValueTypesBegin + + Arg2 + Hex + Arg3 + Hex + + ArgNamesBegin + + Arg2 + Mach Header + Arg3 + Initializer Address + + + + + + diff --git a/dyld.xcodeproj/project.pbxproj b/dyld.xcodeproj/project.pbxproj index 121b7fa..eabdcd3 100644 --- a/dyld.xcodeproj/project.pbxproj +++ b/dyld.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ F94182D81E60F0BE00D8EF25 /* PBXTargetDependency */, F94182DA1E60F0C000D8EF25 /* PBXTargetDependency */, F94182DC1E60F16900D8EF25 /* PBXTargetDependency */, + C187B90C1FE067590042D3B7 /* PBXTargetDependency */, ); name = update_dyld_shared_cache; productName = update_dyld_shared_cache; @@ -70,7 +71,6 @@ 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 */; }; @@ -82,12 +82,6 @@ 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 */; }; @@ -97,13 +91,46 @@ 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 */; }; + 37918AC52058915E00F39A77 /* dyld.codes in install ktrace codes file */ = {isa = PBXBuildFile; fileRef = 37918AC42058913800F39A77 /* dyld.codes */; }; 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 */; }; + 37F597D52061ED0B00F9B6F9 /* dyld_usage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 37F597D42061ECFF00F9B6F9 /* dyld_usage.cpp */; }; + 37F597D72061ED3200F9B6F9 /* libktrace.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 37F597D62061ED3200F9B6F9 /* libktrace.tbd */; }; + C1436B2C203BE67D00028AF1 /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + C172C9DD20252CB500159311 /* ClosureFileSystemPhysical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.cpp */; }; + C17984D61FE9E9160057D002 /* mrm_shared_cache_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D2682E1FE08918009F115B /* mrm_shared_cache_builder.cpp */; }; + C187B90D1FE067C70042D3B7 /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + C187B90E1FE067CD0042D3B7 /* ClosureWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */; }; + C187B90F1FE067D30042D3B7 /* ClosureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */; }; + C187B9101FE067D90042D3B7 /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; + C187B9111FE067E10042D3B7 /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */; }; + C187B9121FE067E60042D3B7 /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */; }; + C187B9131FE067F10042D3B7 /* CacheBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921C1DC3F86C00CBEDE6 /* CacheBuilder.cpp */; }; + C187B9141FE067FA0042D3B7 /* OptimizerBranches.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692111DC3EF6C00CBEDE6 /* OptimizerBranches.cpp */; }; + C187B9151FE068000042D3B7 /* OptimizerObjC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692131DC3EF6C00CBEDE6 /* OptimizerObjC.cpp */; }; + C187B9161FE0680A0042D3B7 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; + C187B9171FE068180042D3B7 /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + C187B9181FE068260042D3B7 /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + C187B9191FE0682C0042D3B7 /* BuilderUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C5C2FB1E5CD154006B32C9 /* BuilderUtils.mm */; }; + C187B91B1FE0683F0042D3B7 /* OptimizerLinkedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692121DC3EF6C00CBEDE6 /* OptimizerLinkedit.cpp */; }; + C187B91E1FE0684C0042D3B7 /* AdjustDylibSegments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692091DC3EF6C00CBEDE6 /* AdjustDylibSegments.cpp */; }; + C1960ECF2090D9E5007E3E6B /* DyldSharedCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */; }; + C1960ED02090D9F0007E3E6B /* Diagnostics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */; }; + C1960ED12090D9F6007E3E6B /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */; }; + C1960ED22090D9FA007E3E6B /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */; }; + C1960ED32090D9FF007E3E6B /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; + C1960ED42090DA09007E3E6B /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + C1D268311FE0891C009F115B /* mrm_shared_cache_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D2682E1FE08918009F115B /* mrm_shared_cache_builder.cpp */; }; + C1D268351FE0A77B009F115B /* ClosureFileSystemPhysical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.cpp */; }; + C1D268371FE0BC5F009F115B /* ClosureFileSystemPhysical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.cpp */; }; + C1D268391FE0BC94009F115B /* ClosureFileSystemPhysical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.cpp */; }; + C1D2683A1FE0BCF3009F115B /* ClosureFileSystemPhysical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.cpp */; }; + C1D2683F1FE98D4F009F115B /* ClosureFileSystemPhysical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.cpp */; }; + C1D268401FE9B464009F115B /* ClosureFileSystemPhysical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.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 */; }; @@ -113,20 +140,43 @@ 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 */; }; + F92756811F68AF4D000820EE /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + F92756821F68AF4D000820EE /* ClosureWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */; }; + F92756831F68AF4D000820EE /* ClosureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */; }; + F92756841F68AF4D000820EE /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; + F92756851F68AF4D000820EE /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */; }; + F92756861F68AF4D000820EE /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */; }; F9280B7B1AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9280B791AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp */; }; + F936BF9720323F0F00568B23 /* FileUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */; }; + F93D733D1F82F03F007D9413 /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + F93D733E1F82F03F007D9413 /* ClosureWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */; }; + F93D733F1F82F03F007D9413 /* ClosureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */; }; + F93D73401F8404A2007D9413 /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; + F93D73411F8404FA007D9413 /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */; }; + F93D73421F8421CC007D9413 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; + F93D73431F842CBF007D9413 /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */; }; + F93D73441F8475C3007D9413 /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; + F93D73451F8475C3007D9413 /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */; }; + F93D73461F8475C3007D9413 /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */; }; + F93D73471F8C4E55007D9413 /* PathOverrides.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9F76FAE1E08CFF200828678 /* PathOverrides.cpp */; }; + F93D73481F8FF780007D9413 /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + F93D73491F8FF780007D9413 /* ClosureWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */; }; + F93D734A1F8FF780007D9413 /* ClosureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */; }; + F93D734B1F8FF79E007D9413 /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; + F93D734C1F8FF79E007D9413 /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */; }; + F93D734D1F8FF79E007D9413 /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */; }; + F93D734E1F8FF7C2007D9413 /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + F93D734F1F8FF7C2007D9413 /* ClosureWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */; }; + F93D73501F8FF7C2007D9413 /* ClosureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */; }; + F93D73511F8FF7C2007D9413 /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; + F93D73521F8FF7C2007D9413 /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */; }; + F93D73531F8FF7C2007D9413 /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */; }; + F93F46521FA420850060D9F9 /* execserver.defs in Sources */ = {isa = PBXBuildFile; fileRef = F93F46511FA420630060D9F9 /* execserver.defs */; settings = {ATTRIBUTES = (Server, ); }; }; 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"; }; }; @@ -139,35 +189,28 @@ 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 */; }; + F9653F8E1FAE51C9008B5D93 /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + F9653F8F1FAE51C9008B5D93 /* ClosureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */; }; + F9653F901FAE51C9008B5D93 /* ClosureWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */; }; + F9653F911FAE51C9008B5D93 /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; + F9653F921FAE51C9008B5D93 /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */; }; + F9653F941FAE51ED008B5D93 /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.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, ); }; }; @@ -177,30 +220,16 @@ 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 */; }; + F9A5E6171F5C967C0030C490 /* MachOLoaded.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6151F5C967C0030C490 /* MachOLoaded.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 */; }; + F9C2755A1DA71CE8007A5D8A /* Loading.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9C275581DA71A13007A5D8A /* Loading.cpp */; settings = {COMPILER_FLAGS = "-fvisibility=hidden"; }; }; 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 */; }; + F9CC10D71F5F1D480021BFE2 /* MachOAnalyzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */; }; + F9CC10D81F5F1D4E0021BFE2 /* MachOFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */; }; 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 */; }; @@ -209,32 +238,23 @@ 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 */; }; + F9DFEA6C1F50DD16003BF8A7 /* Closure.h in Headers */ = {isa = PBXBuildFile; fileRef = F9DFEA6B1F50DD16003BF8A7 /* Closure.h */; }; + F9DFEA701F50FDE5003BF8A7 /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + F9DFEA721F54BD83003BF8A7 /* ClosureWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = F9DFEA711F54BD83003BF8A7 /* ClosureWriter.h */; }; + F9DFEA741F54DB25003BF8A7 /* ClosureWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */; }; + F9DFEA761F54FAAB003BF8A7 /* ClosureBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = F9DFEA751F54FAAB003BF8A7 /* ClosureBuilder.h */; }; + F9DFEA781F54FACF003BF8A7 /* ClosureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */; }; + F9DFEA791F55DDC0003BF8A7 /* Closure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */; }; + F9DFEA7A1F55DDC4003BF8A7 /* ClosureWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */; }; + F9DFEA7B1F55DDC7003BF8A7 /* ClosureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */; }; + F9DFEA7D1F588506003BF8A7 /* ClosurePrinter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9DFEA7C1F588506003BF8A7 /* ClosurePrinter.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 */; }; @@ -242,7 +262,7 @@ F9ED4CDB0630A7F100DF4E74 /* dyldInitialization.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CCB0630A7F100DF4E74 /* dyldInitialization.cpp */; }; F9ED4CDE0630A7F100DF4E74 /* dyldNew.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CCE0630A7F100DF4E74 /* dyldNew.cpp */; }; F9ED4CDF0630A7F100DF4E74 /* dyldStartup.s in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CCF0630A7F100DF4E74 /* dyldStartup.s */; }; - F9ED4CE00630A7F100DF4E74 /* glue.c in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CD00630A7F100DF4E74 /* glue.c */; }; + F9ED4CE00630A7F100DF4E74 /* glue.c in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CD00630A7F100DF4E74 /* glue.c */; settings = {COMPILER_FLAGS = "-fno-builtin"; }; }; F9ED4CE10630A7F100DF4E74 /* ImageLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CD10630A7F100DF4E74 /* ImageLoader.cpp */; }; F9ED4CE30630A7F100DF4E74 /* ImageLoaderMachO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CD30630A7F100DF4E74 /* ImageLoaderMachO.cpp */; }; F9ED4CE50630A7F100DF4E74 /* stub_binding_helper.s in Sources */ = {isa = PBXBuildFile; fileRef = F9ED4CD50630A7F100DF4E74 /* stub_binding_helper.s */; }; @@ -305,6 +325,13 @@ remoteGlobalIDString = 37A0AD0A1C15FFF500731E50; remoteInfo = update_dyld_shared_cache; }; + C187B90B1FE067590042D3B7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C187B8FF1FE063A40042D3B7; + remoteInfo = libslc_builder; + }; D8668ACF1ECE335F005E7D31 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; @@ -319,20 +346,6 @@ remoteGlobalIDString = F9ED4C9E0630A76B00DF4E74; remoteInfo = libdyld.dylib; }; - F922C8111F33B62700D8F479 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; - proxyType = 1; - remoteGlobalIDString = F9AB02B71F329FAA00EE96C4; - remoteInfo = libclosured; - }; - F922C81D1F33B96300D8F479 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; - proxyType = 1; - remoteGlobalIDString = F922C8161F33B73800D8F479; - remoteInfo = "libclosured-stub"; - }; F94182D71E60F0BE00D8EF25 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F9ED4C8B0630A72300DF4E74 /* Project object */; @@ -417,6 +430,36 @@ ); runOnlyForDeploymentPostprocessing = 1; }; + 37918AC32058912100F39A77 /* install ktrace codes file */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/local/share/misc; + dstSubfolderSpec = 0; + files = ( + 37918AC52058915E00F39A77 /* dyld.codes in install ktrace codes file */, + ); + name = "install ktrace codes file"; + runOnlyForDeploymentPostprocessing = 1; + }; + 37F597CB2061EB4200F9B6F9 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; + C187B9041FE063A40042D3B7 /* usr|local|include|mach-o */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = "$(INSTALL_PATH_PREFIX)$(INSTALL_LOCATION)/usr/local/include"; + dstSubfolderSpec = 0; + files = ( + ); + name = "usr|local|include|mach-o"; + runOnlyForDeploymentPostprocessing = 1; + }; F908137211D3FB5000626CC1 /* usr|share|man|man1 */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; @@ -445,17 +488,6 @@ 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; @@ -524,11 +556,24 @@ 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 = ""; }; + 37918AC0205890D700F39A77 /* dyld.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = dyld.plist; sourceTree = ""; }; + 37918AC42058913800F39A77 /* dyld.codes */ = {isa = PBXFileReference; lastKnownFileType = text; path = dyld.codes; 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 = ""; }; + 37F597CD2061EB4200F9B6F9 /* dyld_usage */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dyld_usage; sourceTree = BUILT_PRODUCTS_DIR; }; + 37F597D42061ECFF00F9B6F9 /* dyld_usage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dyld_usage.cpp; path = src/dyld_usage.cpp; sourceTree = ""; }; + 37F597D62061ED3200F9B6F9 /* libktrace.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libktrace.tbd; path = usr/lib/libktrace.tbd; sourceTree = SDKROOT; }; 37F7A5961BB363820039043A /* Bom.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Bom.framework; path = System/Library/PrivateFrameworks/Bom.framework; sourceTree = SDKROOT; }; + C187B90A1FE063A40042D3B7 /* slc_builder.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = slc_builder.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + C18A75F5209940A500DC01BB /* JSONWriter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JSONWriter.h; path = dyld3/JSONWriter.h; sourceTree = ""; }; + C19D50142087E4BC00563DAF /* SupportedArchs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SupportedArchs.h; path = dyld3/SupportedArchs.h; sourceTree = ""; }; + C1D2682E1FE08918009F115B /* mrm_shared_cache_builder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mrm_shared_cache_builder.cpp; path = "dyld3/shared-cache/mrm_shared_cache_builder.cpp"; sourceTree = ""; }; + C1D2682F1FE08918009F115B /* mrm_shared_cache_builder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = mrm_shared_cache_builder.h; path = "dyld3/shared-cache/mrm_shared_cache_builder.h"; sourceTree = ""; }; + C1D268321FE09843009F115B /* ClosureFileSystem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ClosureFileSystem.h; path = dyld3/ClosureFileSystem.h; sourceTree = ""; }; + C1D268331FE0A21F009F115B /* ClosureFileSystemPhysical.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ClosureFileSystemPhysical.h; path = dyld3/ClosureFileSystemPhysical.h; sourceTree = ""; }; + C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ClosureFileSystemPhysical.cpp; path = dyld3/ClosureFileSystemPhysical.cpp; sourceTree = ""; }; EF799FE9070D27BB00F78484 /* dyld.1 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.man; name = dyld.1; path = doc/man/man1/dyld.1; sourceTree = SOURCE_ROOT; }; EF799FEB070D27BB00F78484 /* dladdr.3 */ = {isa = PBXFileReference; explicitFileType = text.man; fileEncoding = 30; name = dladdr.3; path = doc/man/man3/dladdr.3; sourceTree = SOURCE_ROOT; }; EF799FEC070D27BB00F78484 /* dlclose.3 */ = {isa = PBXFileReference; explicitFileType = text.man; fileEncoding = 30; name = dlclose.3; path = doc/man/man3/dlclose.3; sourceTree = SOURCE_ROOT; }; @@ -537,27 +582,25 @@ 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; }; 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 = ""; }; + F92756871F7098FB000820EE /* Array.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Array.h; path = dyld3/Array.h; sourceTree = ""; }; + F9280B791AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoaderMegaDylib.cpp; path = src/ImageLoaderMegaDylib.cpp; sourceTree = ""; usesTabs = 1; }; + F9280B7A1AB9DCA000B18AEC /* ImageLoaderMegaDylib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMegaDylib.h; path = src/ImageLoaderMegaDylib.h; sourceTree = ""; usesTabs = 1; }; F93937320A94FAF700070A07 /* update_dyld_shared_cache */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = update_dyld_shared_cache; sourceTree = BUILT_PRODUCTS_DIR; }; F939373E0A94FC4700070A07 /* Architectures.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Architectures.hpp; sourceTree = ""; }; F939373F0A94FC4700070A07 /* CacheFileAbstraction.hpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = CacheFileAbstraction.hpp; sourceTree = ""; }; 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 = ""; }; + F93F46511FA420630060D9F9 /* execserver.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; name = execserver.defs; path = testing/nocr/execserver.defs; 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 = ""; }; - F94DB9030F0A9B1700323715 /* ImageLoaderMachOCompressed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMachOCompressed.h; path = src/ImageLoaderMachOCompressed.h; sourceTree = ""; }; + F94DB9000F0A9B1700323715 /* ImageLoaderMachOClassic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoaderMachOClassic.cpp; path = src/ImageLoaderMachOClassic.cpp; sourceTree = ""; usesTabs = 1; }; + F94DB9010F0A9B1700323715 /* ImageLoaderMachOClassic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMachOClassic.h; path = src/ImageLoaderMachOClassic.h; sourceTree = ""; usesTabs = 1; }; + F94DB9020F0A9B1700323715 /* ImageLoaderMachOCompressed.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoaderMachOCompressed.cpp; path = src/ImageLoaderMachOCompressed.cpp; sourceTree = ""; usesTabs = 1; }; + F94DB9030F0A9B1700323715 /* ImageLoaderMachOCompressed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMachOCompressed.h; path = src/ImageLoaderMachOCompressed.h; sourceTree = ""; usesTabs = 1; }; 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 = ""; }; @@ -565,14 +608,10 @@ 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 = ""; }; + F96D19711D7F63EE007AF3CE /* expand.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; name = expand.rb; path = bin/expand.rb; 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 = ""; }; @@ -587,17 +626,10 @@ 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 = ""; }; 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 = ""; }; 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; }; @@ -614,24 +646,25 @@ 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 = ""; }; - 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 = ""; }; F99EE6AE06B48D4200BF1992 /* dlfcn.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = dlfcn.h; path = include/dlfcn.h; sourceTree = ""; }; F99EFC0D0EAD60E8001032B8 /* dyld_stub_binder.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = dyld_stub_binder.s; path = src/dyld_stub_binder.s; sourceTree = ""; }; F9A221E60F3A6D7C00D15F73 /* dyldLibSystemGlue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dyldLibSystemGlue.c; path = src/dyldLibSystemGlue.c; sourceTree = ""; }; + F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MachOLoaded.cpp; path = dyld3/MachOLoaded.cpp; sourceTree = ""; }; + F9A5E6161F5C967C0030C490 /* MachOLoaded.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MachOLoaded.h; path = dyld3/MachOLoaded.h; sourceTree = ""; }; + F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MachOAnalyzer.cpp; path = dyld3/MachOAnalyzer.cpp; sourceTree = ""; }; + F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MachOFile.cpp; path = dyld3/MachOFile.cpp; sourceTree = ""; }; + F9A5E61A1F5F1BFA0030C490 /* MachOFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MachOFile.h; path = dyld3/MachOFile.h; sourceTree = ""; }; + F9A5E61B1F5F1BFB0030C490 /* MachOAnalyzer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MachOAnalyzer.h; path = dyld3/MachOAnalyzer.h; 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; }; @@ -643,20 +676,21 @@ 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; }; + F9DFEA6B1F50DD16003BF8A7 /* Closure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Closure.h; path = dyld3/Closure.h; sourceTree = ""; }; + F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Closure.cpp; path = dyld3/Closure.cpp; sourceTree = ""; }; + F9DFEA711F54BD83003BF8A7 /* ClosureWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClosureWriter.h; path = dyld3/ClosureWriter.h; sourceTree = ""; }; + F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ClosureWriter.cpp; path = dyld3/ClosureWriter.cpp; sourceTree = ""; }; + F9DFEA751F54FAAB003BF8A7 /* ClosureBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ClosureBuilder.h; path = dyld3/ClosureBuilder.h; sourceTree = ""; }; + F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ClosureBuilder.cpp; path = dyld3/ClosureBuilder.cpp; sourceTree = ""; }; + F9DFEA7C1F588506003BF8A7 /* ClosurePrinter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ClosurePrinter.cpp; path = dyld3/ClosurePrinter.cpp; sourceTree = ""; }; + F9DFEA7E1F588558003BF8A7 /* ClosurePrinter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ClosurePrinter.h; path = dyld3/ClosurePrinter.h; sourceTree = ""; }; F9E572000A66EF41007D9BE9 /* dlopen_preflight.3 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = dlopen_preflight.3; 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; }; + F9ED4CC60630A7F100DF4E74 /* dyld_gdb.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = dyld_gdb.cpp; path = src/dyld_gdb.cpp; sourceTree = SOURCE_ROOT; usesTabs = 0; }; F9ED4CC70630A7F100DF4E74 /* dyld.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dyld.cpp; path = src/dyld.cpp; sourceTree = SOURCE_ROOT; usesTabs = 1; }; F9ED4CC80630A7F100DF4E74 /* dyld.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = dyld.h; path = src/dyld.h; sourceTree = SOURCE_ROOT; }; - F9ED4CC90630A7F100DF4E74 /* dyldAPIs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = dyldAPIs.cpp; path = src/dyldAPIs.cpp; sourceTree = SOURCE_ROOT; }; + F9ED4CC90630A7F100DF4E74 /* dyldAPIs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = dyldAPIs.cpp; path = src/dyldAPIs.cpp; sourceTree = SOURCE_ROOT; usesTabs = 1; }; F9ED4CCA0630A7F100DF4E74 /* dyldExceptions.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = dyldExceptions.c; path = src/dyldExceptions.c; sourceTree = SOURCE_ROOT; }; F9ED4CCB0630A7F100DF4E74 /* dyldInitialization.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = dyldInitialization.cpp; path = src/dyldInitialization.cpp; sourceTree = SOURCE_ROOT; }; F9ED4CCC0630A7F100DF4E74 /* dyldLock.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = dyldLock.cpp; path = src/dyldLock.cpp; sourceTree = SOURCE_ROOT; }; @@ -664,17 +698,15 @@ F9ED4CCE0630A7F100DF4E74 /* dyldNew.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = dyldNew.cpp; path = src/dyldNew.cpp; sourceTree = SOURCE_ROOT; }; F9ED4CCF0630A7F100DF4E74 /* dyldStartup.s */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.asm; name = dyldStartup.s; path = src/dyldStartup.s; sourceTree = SOURCE_ROOT; tabWidth = 8; usesTabs = 1; }; F9ED4CD00630A7F100DF4E74 /* glue.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = glue.c; path = src/glue.c; sourceTree = SOURCE_ROOT; }; - F9ED4CD10630A7F100DF4E74 /* ImageLoader.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoader.cpp; path = src/ImageLoader.cpp; sourceTree = SOURCE_ROOT; }; - F9ED4CD20630A7F100DF4E74 /* ImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = ImageLoader.h; path = src/ImageLoader.h; sourceTree = SOURCE_ROOT; }; - F9ED4CD30630A7F100DF4E74 /* ImageLoaderMachO.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoaderMachO.cpp; path = src/ImageLoaderMachO.cpp; sourceTree = SOURCE_ROOT; }; - F9ED4CD40630A7F100DF4E74 /* ImageLoaderMachO.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMachO.h; path = src/ImageLoaderMachO.h; sourceTree = SOURCE_ROOT; }; + F9ED4CD10630A7F100DF4E74 /* ImageLoader.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoader.cpp; path = src/ImageLoader.cpp; sourceTree = SOURCE_ROOT; usesTabs = 1; }; + F9ED4CD20630A7F100DF4E74 /* ImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = ImageLoader.h; path = src/ImageLoader.h; sourceTree = SOURCE_ROOT; usesTabs = 1; }; + F9ED4CD30630A7F100DF4E74 /* ImageLoaderMachO.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = ImageLoaderMachO.cpp; path = src/ImageLoaderMachO.cpp; sourceTree = SOURCE_ROOT; usesTabs = 1; }; + F9ED4CD40630A7F100DF4E74 /* ImageLoaderMachO.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = ImageLoaderMachO.h; path = src/ImageLoaderMachO.h; sourceTree = SOURCE_ROOT; usesTabs = 1; }; F9ED4CD50630A7F100DF4E74 /* stub_binding_helper.s */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.asm; name = stub_binding_helper.s; path = src/stub_binding_helper.s; sourceTree = SOURCE_ROOT; tabWidth = 8; usesTabs = 1; }; 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 = ""; }; @@ -702,7 +734,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F922C8141F33B73800D8F479 /* Frameworks */ = { + 37F597CA2061EB4200F9B6F9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 37F597D72061ED3200F9B6F9 /* libktrace.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C187B9031FE063A40042D3B7 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -746,13 +786,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F9AB02B51F329FAA00EE96C4 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; F9D1001014D8D0BA00099D91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -760,14 +793,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F9DDEDAF1E2878CA00A753DC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F90F7C081E9C6B8B00535722 /* libCrashReporterClient.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; F9F2A5570F7AEE9800B7C9EB /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -778,6 +803,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 37918ABF2058908000F39A77 /* tracing */ = { + isa = PBXGroup; + children = ( + 37918AC0205890D700F39A77 /* dyld.plist */, + 37918AC42058913800F39A77 /* dyld.codes */, + ); + name = tracing; + path = doc/tracing; + sourceTree = SOURCE_ROOT; + }; EF799FE7070D27BB00F78484 /* man */ = { isa = PBXGroup; children = ( @@ -836,7 +871,7 @@ F94C22231E513CA90079E5DD /* Frameworks */ = { isa = PBXGroup; children = ( - F90F7C071E9C6B8B00535722 /* libCrashReporterClient.a */, + 37F597D62061ED3200F9B6F9 /* libktrace.tbd */, 37F7A5961BB363820039043A /* Bom.framework */, 376ED1D71C46F2710051DD54 /* Metabom.framework */, F94C22241E513CA90079E5DD /* CoreFoundation.framework */, @@ -847,42 +882,47 @@ 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 */, + F96D19A71D9363D6007AF3CE /* AllImages.h */, + F96D19A61D9363D6007AF3CE /* AllImages.cpp */, + F96D19C11D95C6D6007AF3CE /* APIs.h */, + F96D19A51D9363D6007AF3CE /* APIs.cpp */, + F9C15A491E1F7D960006E570 /* APIs_macOS.cpp */, + F92756871F7098FB000820EE /* Array.h */, + F98692221DC4028B00CBEDE6 /* CodeSigningTypes.h */, + F9DFEA6B1F50DD16003BF8A7 /* Closure.h */, + F9DFEA6F1F50FDE5003BF8A7 /* Closure.cpp */, + F9DFEA751F54FAAB003BF8A7 /* ClosureBuilder.h */, + F9DFEA771F54FACF003BF8A7 /* ClosureBuilder.cpp */, + C1D268321FE09843009F115B /* ClosureFileSystem.h */, + C1D268331FE0A21F009F115B /* ClosureFileSystemPhysical.h */, + C1D268341FE0A52D009F115B /* ClosureFileSystemPhysical.cpp */, + F9DFEA7E1F588558003BF8A7 /* ClosurePrinter.h */, + F9DFEA7C1F588506003BF8A7 /* ClosurePrinter.cpp */, + F9DFEA711F54BD83003BF8A7 /* ClosureWriter.h */, + F9DFEA731F54DB25003BF8A7 /* ClosureWriter.cpp */, + F986921B1DC3F07C00CBEDE6 /* Diagnostics.cpp */, + F98692001DC3EF4800CBEDE6 /* Diagnostics.h */, + C18A75F5209940A500DC01BB /* JSONWriter.h */, + F97C619D1D96C5BE00A84CD7 /* libdyldEntryVector.h */, + F97C619E1D98292700A84CD7 /* libdyldEntryVector.cpp */, 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 */, + F9A5E6191F5F1BFA0030C490 /* MachOFile.cpp */, + F9A5E61A1F5F1BFA0030C490 /* MachOFile.h */, + F9A5E6151F5C967C0030C490 /* MachOLoaded.cpp */, + F9A5E6161F5C967C0030C490 /* MachOLoaded.h */, + F9A5E6181F5F1BFA0030C490 /* MachOAnalyzer.cpp */, + F9A5E61B1F5F1BFB0030C490 /* MachOAnalyzer.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 */, + F977DDC91E53BEA700609230 /* SharedCacheRuntime.cpp */, + F977DDCA1E53BEA700609230 /* SharedCacheRuntime.h */, + 37D7DAFE1E96F0ED00D52CEA /* Tracing.cpp */, + 37D7DAFF1E96F0ED00D52CEA /* Tracing.h */, + C19D50142087E4BC00563DAF /* SupportedArchs.h */, ); name = dyld3; sourceTree = ""; @@ -895,19 +935,10 @@ F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */, F971DD161A4A0E0700BBDD52 /* update_dyld_shared_cache.xcconfig */, F94182DE1E60FFDC00D8EF25 /* update_dyld_sim_shared_cache.xcconfig */, - F9EDC0A01F0481B400B030F4 /* closured.xcconfig */, ); path = configs; sourceTree = SOURCE_ROOT; }; - F97FF3571C23638F000ACDD2 /* nocr */ = { - isa = PBXGroup; - children = ( - F97FF3581C23638F000ACDD2 /* main.c */, - ); - path = nocr; - sourceTree = ""; - }; F98692161DC3EF7700CBEDE6 /* shared-cache */ = { isa = PBXGroup; children = ( @@ -923,8 +954,6 @@ F98692141DC3EF6C00CBEDE6 /* DyldSharedCache.cpp */, F986920E1DC3EF6C00CBEDE6 /* FileUtils.h */, F986920D1DC3EF6C00CBEDE6 /* FileUtils.cpp */, - F963546A1DD8D8D300895049 /* ImageProxy.h */, - F963546B1DD8F2A800895049 /* ImageProxy.cpp */, 37908A2C1E3A85A4009613FA /* Manifest.h */, 37908A281E3A853E009613FA /* Manifest.mm */, F986920F1DC3EF6C00CBEDE6 /* ObjC1Abstraction.hpp */, @@ -941,33 +970,20 @@ F963542E1DCD736000895049 /* update_dyld_sim_shared_cache.cpp */, F98692151DC3EF6C00CBEDE6 /* update_dyld_shared_cache.cpp */, F9EDC09E1F04767300B030F4 /* update_dyld_shared_cache_entitlements.plist */, + C1D2682E1FE08918009F115B /* mrm_shared_cache_builder.cpp */, + C1D2682F1FE08918009F115B /* mrm_shared_cache_builder.h */, ); 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 */, F9ED4CC30630A7BE00DF4E74 /* doc */, F9ED4CBE0630A7B100DF4E74 /* include */, - F97FF3571C23638F000ACDD2 /* nocr */, F9ED4C990630A76000DF4E74 /* Products */, F96D19A41D9363B7007AF3CE /* dyld3 */, F939373D0A94FC4700070A07 /* launch-cache */, @@ -992,9 +1008,8 @@ F97FF3561C23638F000ACDD2 /* nocr */, F97C61A71DBAD1A900A84CD7 /* dyld_closure_util */, F96354451DCD74A400895049 /* update_dyld_sim_shared_cache */, - F9DDEDB21E2878CA00A753DC /* closured */, - F9AB02B81F329FAA00EE96C4 /* libclosured.dylib */, - F922C8171F33B73800D8F479 /* libclosured.dylib */, + C187B90A1FE063A40042D3B7 /* slc_builder.dylib */, + 37F597CD2061EB4200F9B6F9 /* dyld_usage */, ); name = Products; sourceTree = ""; @@ -1002,7 +1017,9 @@ F9ED4CBB0630A7AA00DF4E74 /* src */ = { isa = PBXGroup; children = ( + F93F46511FA420630060D9F9 /* execserver.defs */, F97FF35F1C236402000ACDD2 /* nocr.c */, + 37F597D42061ECFF00F9B6F9 /* dyld_usage.cpp */, F9ED4CC60630A7F100DF4E74 /* dyld_gdb.cpp */, F9ED4CC70630A7F100DF4E74 /* dyld.cpp */, F9ED4CC80630A7F100DF4E74 /* dyld.h */, @@ -1048,7 +1065,7 @@ F9ED4CBE0630A7B100DF4E74 /* include */ = { isa = PBXGroup; children = ( - F96D19711D7F63EE007AF3CE /* expand.pl */, + F96D19711D7F63EE007AF3CE /* expand.rb */, F95090D01C5AB89A0031F81D /* dyld_process_info.h */, F98D274C0AA79D7400416316 /* dyld_images.h */, F918691408B16D2500E0F9DB /* dyld-interposing.h */, @@ -1064,6 +1081,7 @@ F9ED4CC30630A7BE00DF4E74 /* doc */ = { isa = PBXGroup; children = ( + 37918ABF2058908000F39A77 /* tracing */, EF799FE7070D27BB00F78484 /* man */, ); name = doc; @@ -1072,34 +1090,23 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - F922C8151F33B73800D8F479 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; F98F1FBB1E4029CA00EF868D /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + F9DFEA6C1F50DD16003BF8A7 /* Closure.h in Headers */, F99006DD1E411BA70013456D /* dyld_images.h in Headers */, F99006DE1E411BBC0013456D /* dyld.h in Headers */, F98F1FBD1E4029E400EF868D /* dyld_priv.h in Headers */, + F9DFEA761F54FAAB003BF8A7 /* ClosureBuilder.h in Headers */, F960A78A1E40569400840176 /* dyld-interposing.h in Headers */, + F9DFEA721F54BD83003BF8A7 /* ClosureWriter.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 */ @@ -1139,21 +1146,38 @@ productReference = 377686021AC4B27D00026E6C /* multi_dyld_shared_cache_builder */; productType = "com.apple.product-type.tool"; }; - F922C8161F33B73800D8F479 /* libclosured-stub */ = { + 37F597CC2061EB4200F9B6F9 /* dyld_usage */ = { + isa = PBXNativeTarget; + buildConfigurationList = 37F597D32061EB4200F9B6F9 /* Build configuration list for PBXNativeTarget "dyld_usage" */; + buildPhases = ( + 37F597C92061EB4200F9B6F9 /* Sources */, + 37F597CA2061EB4200F9B6F9 /* Frameworks */, + 37F597CB2061EB4200F9B6F9 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = dyld_usage; + productName = dyld_usage; + productReference = 37F597CD2061EB4200F9B6F9 /* dyld_usage */; + productType = "com.apple.product-type.tool"; + }; + C187B8FF1FE063A40042D3B7 /* libslc_builder.dylib */ = { isa = PBXNativeTarget; - buildConfigurationList = F922C8181F33B73800D8F479 /* Build configuration list for PBXNativeTarget "libclosured-stub" */; + buildConfigurationList = C187B9071FE063A40042D3B7 /* Build configuration list for PBXNativeTarget "libslc_builder.dylib" */; buildPhases = ( - F922C8131F33B73800D8F479 /* Sources */, - F922C8141F33B73800D8F479 /* Frameworks */, - F922C8151F33B73800D8F479 /* Headers */, + C187B9001FE063A40042D3B7 /* Sources */, + C187B9031FE063A40042D3B7 /* Frameworks */, + C187B9041FE063A40042D3B7 /* usr|local|include|mach-o */, ); buildRules = ( ); dependencies = ( ); - name = "libclosured-stub"; - productName = "libclosured-stub"; - productReference = F922C8171F33B73800D8F479 /* libclosured.dylib */; + name = libslc_builder.dylib; + productName = dsc; + productReference = C187B90A1FE063A40042D3B7 /* slc_builder.dylib */; productType = "com.apple.product-type.library.dynamic"; }; F93937310A94FAF700070A07 /* update_dyld_shared_cache_tool */ = { @@ -1243,23 +1267,6 @@ 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" */; @@ -1276,25 +1283,6 @@ 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" */; @@ -1305,6 +1293,7 @@ F907E2490FA6469000BFEDBD /* install iPhone file */, F9213B3F18BFC9CB001CB6E8 /* simulator entitlement */, F99B8EB60FEC236500701838 /* suppress macosx dyld_shared_cache_util */, + 371C117D208ADFC700FD9036 /* Suppress simulator dyld_usage */, ); buildRules = ( F921D318070376B0000D1056 /* PBXBuildRule */, @@ -1312,7 +1301,6 @@ F921D3160703769A000D1056 /* PBXBuildRule */, ); dependencies = ( - F922C8121F33B62700D8F479 /* PBXTargetDependency */, F99B8EB20FEC220C00701838 /* PBXTargetDependency */, F96543A11E343601003C5540 /* PBXTargetDependency */, ); @@ -1330,13 +1318,13 @@ F98F1FBB1E4029CA00EF868D /* Headers */, F960A78C1E405E2300840176 /* expand dyld_priv.h macros */, F99006DF1E411C500013456D /* install dlfcn.h */, + 37918AC32058912100F39A77 /* install ktrace codes file */, ); buildRules = ( F921D31E070376F1000D1056 /* PBXBuildRule */, F9574C4906C94DA700142BFA /* PBXBuildRule */, ); dependencies = ( - F922C81E1F33B96300D8F479 /* PBXTargetDependency */, ); name = libdyld.dylib; productName = libdyld; @@ -1366,13 +1354,14 @@ F9ED4C8B0630A72300DF4E74 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1000; TargetAttributes = { 37A0AD0A1C15FFF500731E50 = { CreatedOnToolsVersion = 8.0; }; - F922C8161F33B73800D8F479 = { - CreatedOnToolsVersion = 9.0; + 37F597CC2061EB4200F9B6F9 = { + CreatedOnToolsVersion = 10.0; + ProvisioningStyle = Automatic; }; F97C61A61DBAD1A900A84CD7 = { CreatedOnToolsVersion = 8.0; @@ -1382,14 +1371,6 @@ F97FF3551C23638F000ACDD2 = { CreatedOnToolsVersion = 8.0; }; - F9AB02B71F329FAA00EE96C4 = { - CreatedOnToolsVersion = 9.0; - }; - F9DDEDB11E2878CA00A753DC = { - CreatedOnToolsVersion = 8.2; - DevelopmentTeam = 59GAB85EFG; - ProvisioningStyle = Automatic; - }; F9F6F4271C1FB0A700BD8FED = { CreatedOnToolsVersion = 8.0; }; @@ -1414,7 +1395,6 @@ 37A0AD0A1C15FFF500731E50 /* update_dyld_shared_cache */, F908134211D3ED0B00626CC1 /* libdyld */, F9ED4C970630A76000DF4E74 /* dyld */, - F9DDEDB11E2878CA00A753DC /* closured */, F9ED4C9E0630A76B00DF4E74 /* libdyld.dylib */, F93937310A94FAF700070A07 /* update_dyld_shared_cache_tool */, 377685F21AC4B27D00026E6C /* multi_dyld_shared_cache_builder */, @@ -1424,10 +1404,10 @@ F97C61A61DBAD1A900A84CD7 /* dyld_closure_util */, F9F2A5580F7AEE9800B7C9EB /* libdsc */, F9D1001114D8D0BA00099D91 /* dsc_extractor */, + C187B8FF1FE063A40042D3B7 /* libslc_builder.dylib */, F9F6F4271C1FB0A700BD8FED /* dyld_tests */, F97FF3551C23638F000ACDD2 /* nocr */, - F9AB02B71F329FAA00EE96C4 /* libclosured */, - F922C8161F33B73800D8F479 /* libclosured-stub */, + 37F597CC2061EB4200F9B6F9 /* dyld_usage */, ); }; /* End PBXProject section */ @@ -1446,7 +1426,22 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; - shellScript = "echo \"\" > ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\nif [ -z \"${ARM_SDK}\" ]; then\n # if iOS SDK not available, use MacOSX SDK\n ARM_SDK=`xcodebuild -sdk macosx.internal -version Path`\nfi\n\nSHARED_REGION_FILE=\"${ARM_SDK}/usr/include/mach/shared_region.h\"\n\n\nif [ -r \"${SHARED_REGION_FILE}\" ]; then\n echo -n \"#define ARM_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\nelse\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${SHARED_REGION_FILE}'\"\n exit 1\nfi\n\nif [ -r \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\nif [ -r \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\n\n"; + 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[ \\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_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n grep SHARED_REGION_BASE_ARM64_32 \"${SHARED_REGION_FILE}\" > /dev/null 2>&1\n if [ \"$?\" -eq \"0\" ]; then\n echo -n \"#define ARM64_32_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64_32/ { 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_32_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64_32/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n fi\nelse\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${SHARED_REGION_FILE}'\"\n exit 1\nfi\n\nif [ -r \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\nif [ -r \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\n\n"; + showEnvVarsInLog = 0; + }; + 371C117D208ADFC700FD9036 /* Suppress simulator dyld_usage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + ); + name = "Suppress simulator dyld_usage"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = /bin/sh; + shellScript = "# dyld_usage requires libktrace which is not available in the simualtor\n# The target builds a dummy app that we delete\nif [ \"${PRODUCT_NAME}\" != \"dyld_sim\" ]\nthen\nxcodebuild install -target dyld_usage SDKROOT=\"${SDKROOT}\" MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} OBJROOT=\"${OBJROOT}\" SRCROOT=\"${SRCROOT}\" DSTROOT=\"${DSTROOT}\" SYMROOT=\"${SYMROOT}\" RC_ProjectSourceVersion=\"${RC_ProjectSourceVersion}\"\nfi"; showEnvVarsInLog = 0; }; 377685F31AC4B27D00026E6C /* make dyld_cache_config.h */ = { @@ -1462,7 +1457,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; - shellScript = "echo \"\" > ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\nif [ -z \"${ARM_SDK}\" ]; then\n # if iOS SDK not available, use MacOSX SDK\n ARM_SDK=`xcodebuild -sdk macosx.internal -version Path`\nfi\n\nSHARED_REGION_FILE=\"${ARM_SDK}/usr/include/mach/shared_region.h\"\n\n\nif [ -r \"${SHARED_REGION_FILE}\" ]; then\n echo -n \"#define ARM_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n echo -n \"#define ARM64_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\nelse\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${SHARED_REGION_FILE}'\"\n exit 1\nfi\n\nif [ -r \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\nif [ -r \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\n\n"; + 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[ \\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_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n grep SHARED_REGION_BASE_ARM64_32 \"${SHARED_REGION_FILE}\" > /dev/null 2>&1\n if [ \"$?\" -eq \"0\" ]; then\n echo -n \"#define ARM64_32_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64_32/ { 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_32_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64_32/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n fi\nelse\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${SHARED_REGION_FILE}'\"\n exit 1\nfi\n\nif [ -r \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/DirtyDataFiles/dirty-data-segments-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\nif [ -r \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" ]; then\n mkdir -p \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\n cp \"${ARM_SDK}/AppleInternal/OrderFiles/dylib-order.txt\" \"${DSTROOT}/${INSTALL_LOCATION}/usr/local/bin\"\nfi\n\n"; showEnvVarsInLog = 0; }; F907E2490FA6469000BFEDBD /* install iPhone file */ = { @@ -1493,24 +1488,7 @@ ); 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; - }; - 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"; + 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[ \\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_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n grep SHARED_REGION_BASE_ARM64_32 \"${SHARED_REGION_FILE}\" > /dev/null 2>&1\n if [ \"$?\" -eq \"0\" ]; then\n echo -n \"#define ARM64_32_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64_32/ { 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_32_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64_32/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n fi\nelse\n echo \"ERROR: File needed to configure update_dyld_shared_cache does not exist '${SHARED_REGION_FILE}'\"\n exit 1\nfi\n\n"; showEnvVarsInLog = 0; }; F9213B3F18BFC9CB001CB6E8 /* simulator entitlement */ = { @@ -1540,24 +1518,7 @@ ); 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"; + 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\tif [ \"${RC_BRIDGE}\" != \"YES\" ]\n\tthen\n\t\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}\"\n\tfi\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; }; F959621018849DF20003E4D4 /* add dyld symlink */ = { @@ -1572,7 +1533,7 @@ ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/bash; - shellScript = "if [[ \"${PLATFORM_NAME}\" == *simulator* ]]\nthen\n\tcd ${DSTROOT}/${INSTALL_PATH}\n\tln -s libdyld.dylib libdyld_sim.dylib\nfi\n"; + shellScript = "if [[ \"${PLATFORM_NAME}\" == *simulator* ]]\nthen\n\tcd ${DSTROOT}\n mkdir -p usr/lib/system/\n\tcd ${DSTROOT}/${INSTALL_PATH}\n\tln -s libdyld.dylib libdyld_sim.dylib\nfi\n"; showEnvVarsInLog = 0; }; F960A78C1E405E2300840176 /* expand dyld_priv.h macros */ = { @@ -1589,7 +1550,7 @@ ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "${SRCROOT}/bin/expand.pl < ${SRCROOT}/include/mach-o/dyld_priv.h > ${DSTROOT}/usr/local/include/mach-o/dyld_priv.h\n"; + shellScript = "${SRCROOT}/bin/expand.rb < ${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 */ = { @@ -1605,7 +1566,7 @@ ); 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"; + 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[ \\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_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64[ \\t]/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n\n grep SHARED_REGION_BASE_ARM64_32 \"${SHARED_REGION_FILE}\" > /dev/null 2>&1\n if [ \"$?\" -eq \"0\" ]; then\n echo -n \"#define ARM64_32_SHARED_REGION_START \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_BASE_ARM64_32/ { 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_32_SHARED_REGION_SIZE \" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n awk '/define SHARED_REGION_SIZE_ARM64_32/ { print $3;}' \"${SHARED_REGION_FILE}\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n echo \"\" >> ${DERIVED_FILE_DIR}/dyld_cache_config.h\n fi\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 */ = { @@ -1622,7 +1583,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "mkdir -p ${DERIVED_FILE_DIR}/mach-o\n${SRCROOT}/bin/expand.rb < ${SRCROOT}/include/mach-o/dyld_priv.h > ${DERIVED_FILE_DIR}/mach-o/dyld_priv.h\n"; showEnvVarsInLog = 0; }; F981C8C21F058F8200452F35 /* mkdir /var/db/dyld */ = { @@ -1682,7 +1643,7 @@ ); 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"; + 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\nrm -rf ${DSTROOT}/usr/local/bin/dyld_shared_cache_util\nrm -rf ${DSTROOT}/usr/local/bin/dyld_closure_util\nfi\n"; showEnvVarsInLog = 0; }; F9D050C811DD701A00FB0A29 /* configure archives */ = { @@ -1698,7 +1659,7 @@ ); 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"; + 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# link with crypto archive if it exists\nif [ -f ${SDKROOT}/usr/local/lib/libcorecrypto_static.a ]\nthen\n echo \\\"${SDKROOT}/usr/local/lib/libcorecrypto_static.a\\\" >> ${DERIVED_SOURCES_DIR}/archives.txt\nfi\n"; showEnvVarsInLog = 0; }; F9F6F42B1C1FB0AE00BD8FED /* build */ = { @@ -1723,22 +1684,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 37554F521E3F78EB00407388 /* ImageProxy.cpp in Sources */, + F93D734E1F8FF7C2007D9413 /* Closure.cpp in Sources */, + F93D734F1F8FF7C2007D9413 /* ClosureWriter.cpp in Sources */, + F93D73501F8FF7C2007D9413 /* ClosureBuilder.cpp in Sources */, + F93D73511F8FF7C2007D9413 /* MachOFile.cpp in Sources */, + C17984D61FE9E9160057D002 /* mrm_shared_cache_builder.cpp in Sources */, + F93D73521F8FF7C2007D9413 /* MachOLoaded.cpp in Sources */, + F93D73531F8FF7C2007D9413 /* MachOAnalyzer.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 */, + C1D268351FE0A77B009F115B /* ClosureFileSystemPhysical.cpp in Sources */, 37554F4B1E3F76E900407388 /* AdjustDylibSegments.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1747,31 +1711,57 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 37554F511E3F78EB00407388 /* ImageProxy.cpp in Sources */, + F93D734B1F8FF79E007D9413 /* MachOFile.cpp in Sources */, + F93D734C1F8FF79E007D9413 /* MachOLoaded.cpp in Sources */, + F93D734D1F8FF79E007D9413 /* MachOAnalyzer.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 */, + F93D73481F8FF780007D9413 /* Closure.cpp in Sources */, + F93D73491F8FF780007D9413 /* ClosureWriter.cpp in Sources */, + F93D734A1F8FF780007D9413 /* ClosureBuilder.cpp in Sources */, + C1D268401FE9B464009F115B /* ClosureFileSystemPhysical.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - F922C8131F33B73800D8F479 /* Sources */ = { + 37F597C92061EB4200F9B6F9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F922C81C1F33B88400D8F479 /* libclosured-stub.cpp in Sources */, + 37F597D52061ED0B00F9B6F9 /* dyld_usage.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C187B9001FE063A40042D3B7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C187B9181FE068260042D3B7 /* DyldSharedCache.cpp in Sources */, + C1436B2C203BE67D00028AF1 /* FileUtils.cpp in Sources */, + C187B91B1FE0683F0042D3B7 /* OptimizerLinkedit.cpp in Sources */, + C187B90E1FE067CD0042D3B7 /* ClosureWriter.cpp in Sources */, + C187B91E1FE0684C0042D3B7 /* AdjustDylibSegments.cpp in Sources */, + C187B9191FE0682C0042D3B7 /* BuilderUtils.mm in Sources */, + C187B90F1FE067D30042D3B7 /* ClosureBuilder.cpp in Sources */, + C187B9131FE067F10042D3B7 /* CacheBuilder.cpp in Sources */, + C187B9121FE067E60042D3B7 /* MachOAnalyzer.cpp in Sources */, + C187B9161FE0680A0042D3B7 /* PathOverrides.cpp in Sources */, + C187B9171FE068180042D3B7 /* Diagnostics.cpp in Sources */, + C187B9151FE068000042D3B7 /* OptimizerObjC.cpp in Sources */, + C187B9101FE067D90042D3B7 /* MachOFile.cpp in Sources */, + C187B9111FE067E10042D3B7 /* MachOLoaded.cpp in Sources */, + C187B90D1FE067C70042D3B7 /* Closure.cpp in Sources */, + C1D268311FE0891C009F115B /* mrm_shared_cache_builder.cpp in Sources */, + C187B9141FE067FA0042D3B7 /* OptimizerBranches.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1779,20 +1769,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F93D73471F8C4E55007D9413 /* PathOverrides.cpp in Sources */, + F92756811F68AF4D000820EE /* Closure.cpp in Sources */, + F92756821F68AF4D000820EE /* ClosureWriter.cpp in Sources */, + C1D2683F1FE98D4F009F115B /* ClosureFileSystemPhysical.cpp in Sources */, + F92756831F68AF4D000820EE /* ClosureBuilder.cpp in Sources */, + F92756841F68AF4D000820EE /* MachOFile.cpp in Sources */, + F92756851F68AF4D000820EE /* MachOLoaded.cpp in Sources */, + F92756861F68AF4D000820EE /* MachOAnalyzer.cpp 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; @@ -1801,21 +1793,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F9653F941FAE51ED008B5D93 /* MachOAnalyzer.cpp in Sources */, + F9653F8E1FAE51C9008B5D93 /* Closure.cpp in Sources */, + F9653F8F1FAE51C9008B5D93 /* ClosureBuilder.cpp in Sources */, + C172C9DD20252CB500159311 /* ClosureFileSystemPhysical.cpp in Sources */, + F9653F901FAE51C9008B5D93 /* ClosureWriter.cpp in Sources */, + F9653F911FAE51C9008B5D93 /* MachOFile.cpp in Sources */, + F9653F921FAE51C9008B5D93 /* MachOLoaded.cpp in Sources */, 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; @@ -1825,18 +1818,18 @@ 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 */, + C1D268371FE0BC5F009F115B /* ClosureFileSystemPhysical.cpp in Sources */, + F9DFEA791F55DDC0003BF8A7 /* Closure.cpp in Sources */, + F9DFEA7A1F55DDC4003BF8A7 /* ClosureWriter.cpp in Sources */, + F9DFEA7B1F55DDC7003BF8A7 /* ClosureBuilder.cpp in Sources */, + F9CC10D81F5F1D4E0021BFE2 /* MachOFile.cpp in Sources */, + F9A5E6171F5C967C0030C490 /* MachOLoaded.cpp in Sources */, + F9CC10D71F5F1D480021BFE2 /* MachOAnalyzer.cpp in Sources */, + F9DFEA7D1F588506003BF8A7 /* ClosurePrinter.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1844,8 +1837,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F93F46521FA420850060D9F9 /* execserver.defs in Sources */, F97FF3611C23640C000ACDD2 /* nocr.c in Sources */, - F988F0BB1E2FDF5B003AED79 /* execserver.defs in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1853,28 +1846,17 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1960ED02090D9F0007E3E6B /* Diagnostics.cpp in Sources */, + C1960ED42090DA09007E3E6B /* Closure.cpp in Sources */, + C1960ECF2090D9E5007E3E6B /* DyldSharedCache.cpp in Sources */, + C1960ED32090D9FF007E3E6B /* MachOFile.cpp in Sources */, + C1960ED22090D9FA007E3E6B /* MachOAnalyzer.cpp in Sources */, F99B8EA30FEC1C4200701838 /* dsc_iterator.cpp in Sources */, + C1960ED12090D9F6007E3E6B /* MachOLoaded.cpp in Sources */, F99B8E630FEC11B400701838 /* dyld_shared_cache_util.cpp in Sources */, ); 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; @@ -1884,36 +1866,18 @@ ); 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 */, + C1D2683A1FE0BCF3009F115B /* ClosureFileSystemPhysical.cpp in Sources */, F9ED4CD90630A7F100DF4E74 /* dyldAPIs.cpp in Sources */, F9ED4CDA0630A7F100DF4E74 /* dyldExceptions.c in Sources */, F9ED4CD60630A7F100DF4E74 /* dyld_gdb.cpp in Sources */, + 37D7DB001E96F0ED00D52CEA /* Tracing.cpp in Sources */, F9ED4CE00630A7F100DF4E74 /* glue.c in Sources */, F9280B7B1AB9DCA000B18AEC /* ImageLoaderMegaDylib.cpp in Sources */, F9ED4CE10630A7F100DF4E74 /* ImageLoader.cpp in Sources */, @@ -1923,13 +1887,17 @@ 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 */, + F936BF9720323F0F00568B23 /* FileUtils.cpp in Sources */, + F93D73411F8404FA007D9413 /* MachOLoaded.cpp in Sources */, + F93D73401F8404A2007D9413 /* MachOFile.cpp in Sources */, + F93D73431F842CBF007D9413 /* MachOAnalyzer.cpp in Sources */, + F93D733D1F82F03F007D9413 /* Closure.cpp in Sources */, + F93D733E1F82F03F007D9413 /* ClosureWriter.cpp in Sources */, + F93D733F1F82F03F007D9413 /* ClosureBuilder.cpp in Sources */, + F93D73421F8421CC007D9413 /* PathOverrides.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1939,16 +1907,16 @@ files = ( F9D49CCC1458B95200F86ADD /* start_glue.s in Sources */, 37D7DB011E96F3EB00D52CEA /* Tracing.cpp in Sources */, + F9DFEA701F50FDE5003BF8A7 /* Closure.cpp in Sources */, F9F256360639DBCC00A7427D /* dyldLock.cpp in Sources */, F9BA514B0ECE4F4200D1D62E /* dyld_stub_binder.s in Sources */, + C1D268391FE0BC94009F115B /* ClosureFileSystemPhysical.cpp in Sources */, F9A221E70F3A6D7C00D15F73 /* dyldLibSystemGlue.c in Sources */, F913FADA0630A8AE00B7AE9D /* dyldAPIsInLibSystem.cpp in Sources */, F9A6D6E4116F9DF20051CC16 /* threadLocalVariables.c in Sources */, F9A6D70C116FBBD10051CC16 /* threadLocalHelpers.s in Sources */, F95090E51C5AD1E80031F81D /* dyld_process_info.cpp in Sources */, F958D4771C7FCE6700A0B199 /* dyld_process_info_notify.cpp in Sources */, - 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 */, @@ -1956,10 +1924,12 @@ F9C15A4A1E1F7DAC0006E570 /* APIs_macOS.cpp in Sources */, F97C61A21D9CAE3500A84CD7 /* Logging.cpp in Sources */, F9C2755A1DA71CE8007A5D8A /* Loading.cpp in Sources */, + F93D73441F8475C3007D9413 /* MachOFile.cpp in Sources */, + F93D73451F8475C3007D9413 /* MachOLoaded.cpp in Sources */, + F93D73461F8475C3007D9413 /* MachOAnalyzer.cpp in Sources */, F90108611E2AD96000870568 /* PathOverrides.cpp in Sources */, - F9D862431DC90A4F000A199A /* LaunchCacheReader.cpp in Sources */, - F9E5E2C71EB00AAA0013A0BB /* ClosureBuffer.cpp in Sources */, - F9C86B651E2B16C600FD8669 /* closuredProtocol.defs in Sources */, + F9DFEA781F54FACF003BF8A7 /* ClosureBuilder.cpp in Sources */, + F9DFEA741F54DB25003BF8A7 /* ClosureWriter.cpp in Sources */, F97C619F1D9829AA00A84CD7 /* libdyldEntryVector.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1981,6 +1951,11 @@ target = 37A0AD0A1C15FFF500731E50 /* update_dyld_shared_cache */; targetProxy = 37A0AD0E1C16000F00731E50 /* PBXContainerItemProxy */; }; + C187B90C1FE067590042D3B7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C187B8FF1FE063A40042D3B7 /* libslc_builder.dylib */; + targetProxy = C187B90B1FE067590042D3B7 /* PBXContainerItemProxy */; + }; D8668AD01ECE335F005E7D31 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F97C61A61DBAD1A900A84CD7 /* dyld_closure_util */; @@ -1991,16 +1966,6 @@ target = F9ED4C9E0630A76B00DF4E74 /* libdyld.dylib */; targetProxy = F908134711D3ED1A00626CC1 /* PBXContainerItemProxy */; }; - F922C8121F33B62700D8F479 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F9AB02B71F329FAA00EE96C4 /* libclosured */; - targetProxy = F922C8111F33B62700D8F479 /* PBXContainerItemProxy */; - }; - F922C81E1F33B96300D8F479 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F922C8161F33B73800D8F479 /* libclosured-stub */; - targetProxy = F922C81D1F33B96300D8F479 /* PBXContainerItemProxy */; - }; F94182D81E60F0BE00D8EF25 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F99B8E550FEC10F600701838 /* dyld_shared_cache_util */; @@ -2053,7 +2018,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_ARC = YES; @@ -2096,7 +2060,10 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + OTHER_CFLAGS = ( + "-DBOM_SUPPORT=1", + "-DBUILDING_EMBEDDED_SHARED_CACHE_BUILDER=1", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; SUPPORTED_PLATFORMS = macosx; @@ -2110,7 +2077,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_ARC = YES; @@ -2147,7 +2113,10 @@ INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + OTHER_CFLAGS = ( + "-DBOM_SUPPORT=1", + "-DBUILDING_EMBEDDED_SHARED_CACHE_BUILDER=1", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; SUPPORTED_PLATFORMS = macosx; @@ -2162,7 +2131,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_ARC = YES; @@ -2204,7 +2172,10 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + OTHER_CFLAGS = ( + "-DBOM_SUPPORT=1", + "-DBUILDING_EMBEDDED_SHARED_CACHE_BUILDER=1", + ); PRODUCT_NAME = multi_dyld_shared_cache_builder; SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = NO; @@ -2217,7 +2188,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_ARC = YES; @@ -2253,7 +2223,10 @@ INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + OTHER_CFLAGS = ( + "-DBOM_SUPPORT=1", + "-DBUILDING_EMBEDDED_SHARED_CACHE_BUILDER=1", + ); PRODUCT_NAME = multi_dyld_shared_cache_builder; SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = NO; @@ -2277,29 +2250,7 @@ }; name = Release; }; - F908134311D3ED0C00626CC1 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - INSTALLHDRS_COPY_PHASE = YES; - PRODUCT_NAME = libdyld; - }; - name = Debug; - }; - F908134411D3ED0C00626CC1 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - INSTALLHDRS_COPY_PHASE = YES; - PRODUCT_NAME = libdyld; - ZERO_LINK = NO; - }; - name = Release; - }; - F922C8191F33B73800D8F479 /* Debug */ = { + 37F597D12061EB4200F9B6F9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -2309,22 +2260,18 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_COMMA = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; 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; @@ -2334,17 +2281,16 @@ ); 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; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos"; + VALID_ARCHS = "arm64 arm64e x86_64"; }; name = Debug; }; - F922C81A1F33B73800D8F479 /* Release */ = { + 37F597D22061EB4200F9B6F9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -2354,39 +2300,139 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_COMMA = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; 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; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos"; + VALID_ARCHS = "arm64 arm64e x86_64"; + }; + name = Release; + }; + C187B9081FE063A40042D3B7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LIBRARY = "libc++"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(APPLE_INTERNAL_LIBRARY_DIR)/Frameworks", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_CPP_EXCEPTIONS = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "BUILDING_CACHE_BUILDER=1", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + GCC_WARN_SIGN_COMPARE = YES; + INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/lib"; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + PRODUCT_NAME = slc_builder; + SDKROOT = macosx.internal; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + USER_HEADER_SEARCH_PATHS = "../launch-cache/"; + VALID_ARCHS = "x86_64 x86_64h"; + }; + name = Debug; + }; + C187B9091FE063A40042D3B7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LIBRARY = "libc++"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(SDKROOT)$(APPLE_INTERNAL_LIBRARY_DIR)/Frameworks", + ); + GCC_ENABLE_CPP_EXCEPTIONS = YES; + GCC_ENABLE_CPP_RTTI = YES; + GCC_ENABLE_OBJC_EXCEPTIONS = NO; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_MODEL_TUNING = G5; + GCC_PREPROCESSOR_DEFINITIONS = "BUILDING_CACHE_BUILDER=1"; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_PEDANTIC = NO; + GCC_WARN_SHADOW = NO; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALLHDRS_COPY_PHASE = YES; + INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/lib"; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CFLAGS = "-DBOM_SUPPORT=1"; + PRODUCT_NAME = slc_builder; + SDKROOT = macosx.internal; + STRIP_STYLE = "non-global"; + USER_HEADER_SEARCH_PATHS = "../launch-cache/"; + VALID_ARCHS = "x86_64 x86_64h"; + ZERO_LINK = NO; + }; + name = Release; + }; + F908134311D3ED0C00626CC1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD)"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + INSTALLHDRS_COPY_PHASE = YES; + PRODUCT_NAME = libdyld; + }; + name = Debug; + }; + F908134411D3ED0C00626CC1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD)"; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + INSTALLHDRS_COPY_PHASE = YES; + PRODUCT_NAME = libdyld; + ZERO_LINK = NO; }; name = Release; }; F93937350A94FB2900070A07 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = F971DD161A4A0E0700BBDD52 /* update_dyld_shared_cache.xcconfig */; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = NO; @@ -2399,6 +2445,11 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "BUILDING_CACHE_BUILDER=1", + "BUILDING_UPDATE_DYLD_CACHE_BUILDER=1", + "DEBUG=1", + ); GCC_THREADSAFE_STATICS = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; @@ -2417,6 +2468,7 @@ "$(SRCROOT)/dyld3/shared-cache", ); INSTALL_PATH = /usr/bin; + MACOSX_DEPLOYMENT_TARGET = 10.13; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = "-stdlib=libc++"; PRODUCT_NAME = update_dyld_shared_cache; @@ -2430,9 +2482,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD161A4A0E0700BBDD52 /* update_dyld_shared_cache.xcconfig */; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_ENTITLEMENTS = "dyld3/shared-cache/update_dyld_shared_cache_entitlements.plist"; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)"; @@ -2444,6 +2496,10 @@ ); GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "BUILDING_CACHE_BUILDER=1", + "BUILDING_UPDATE_DYLD_CACHE_BUILDER=1", + ); GCC_THREADSAFE_STATICS = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; @@ -2457,6 +2513,7 @@ "$(SRCROOT)/dyld3/shared-cache", ); INSTALL_PATH = /usr/bin; + MACOSX_DEPLOYMENT_TARGET = 10.13; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = "-stdlib=libc++"; PRODUCT_NAME = update_dyld_shared_cache; @@ -2471,8 +2528,8 @@ }; F96354431DCD74A400895049 /* Debug */ = { 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; @@ -2484,6 +2541,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = "BUILDING_CACHE_BUILDER=1"; GCC_THREADSAFE_STATICS = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; @@ -2509,7 +2567,7 @@ SDKROOT = macosx.internal; SIMULATOR_DIR_NAME = "$(PLATFORM_FAMILY_NAME_$(DEVICE_PLATFORM_NAME)) $(IPHONE_SDK_MARKETING_VERSION)"; USE_HEADERMAP = NO; - VALID_ARCHS = x86_64; + VALID_ARCHS = "x86_64 x86_64h"; }; name = Debug; }; @@ -2517,7 +2575,6 @@ 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; @@ -2529,6 +2586,7 @@ ); GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = "BUILDING_CACHE_BUILDER=1"; GCC_THREADSAFE_STATICS = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; @@ -2551,7 +2609,7 @@ STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; USE_HEADERMAP = NO; - VALID_ARCHS = x86_64; + VALID_ARCHS = "x86_64 x86_64h"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -2561,7 +2619,6 @@ 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; @@ -2587,10 +2644,12 @@ GCC_WARN_SHADOW = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos"; }; name = Debug; }; @@ -2599,7 +2658,6 @@ 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; @@ -2619,9 +2677,11 @@ GCC_WARN_SHADOW = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos"; }; name = Release; }; @@ -2629,7 +2689,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_ARC = YES; @@ -2676,7 +2735,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = NO; CLANG_ENABLE_OBJC_ARC = YES; @@ -2697,6 +2755,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ""; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -2719,7 +2778,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_MODEL_TUNING = G5; @@ -2733,7 +2791,7 @@ INSTALL_PATH = "$(INSTALL_LOCATION)/usr/local/bin"; PRODUCT_NAME = dyld_shared_cache_util; SDKROOT = macosx.internal; - SUPPORTED_PLATFORMS = macosx; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos"; }; name = Debug; }; @@ -2741,7 +2799,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_DYNAMIC_NO_PIC = NO; @@ -2755,103 +2812,7 @@ 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; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos"; }; name = Release; }; @@ -2912,7 +2873,6 @@ baseConfigurationReference = F971DD141A4A0E0700BBDD52 /* dyld.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_EMPTY_BODY = YES; CODE_SIGN_IDENTITY = "-"; @@ -2956,6 +2916,7 @@ "$(ENTRY)", ); STRIPFLAGS = "-S"; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos iphonesimulatornano iphonesimulator appletvsimulator"; UNSTRIPPED_PRODUCT = NO; VERSIONING_SYSTEM = "apple-generic"; WARNING_CFLAGS = ( @@ -2969,7 +2930,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD141A4A0E0700BBDD52 /* dyld.xcconfig */; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_EMPTY_BODY = YES; CODE_SIGN_IDENTITY = "-"; @@ -3011,9 +2971,10 @@ "$(ALIGNMENT)", "$(ENTRY)", "-Wl,-no_data_const", - "-Wl,-section_order,__DATA,__all_image_info:__nl_symbol_ptr:__got:__const:__crash_info:__data:__bss:__common", + "-Wl,-section_order,__DATA,__all_image_info:__nl_symbol_ptr:__got:__auth_ptr:__const:__crash_info:__data:__bss:__common", ); STRIPFLAGS = "-S"; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos iphonesimulatornano iphonesimulator appletvsimulator"; UNSTRIPPED_PRODUCT = NO; VERSIONING_SYSTEM = "apple-generic"; WARNING_CFLAGS = ( @@ -3028,7 +2989,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_WARN_EMPTY_BODY = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; @@ -3039,6 +2999,10 @@ GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "BUILDING_LIBDYLD=1", + "DEBUG=1", + ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES; @@ -3052,7 +3016,7 @@ OTHER_CFLAGS = ""; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", - "-Wglobal-constructors", + "-fno-exceptions", ); OTHER_LDFLAGS = ( "-Wl,-no_inits", @@ -3062,11 +3026,12 @@ 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 "; + OTHER_TAPI_FLAGS = "-extra-public-header ./include/dlfcn.h -extra-private-header ./dyld3/libdyldEntryVector.h -ObjC++ -std=c++11 -umbrella System"; PRIVATE_HEADERS_FOLDER_PATH = "/usr/local/include/mach-o"; PRODUCT_NAME = dyld; PUBLIC_HEADERS_FOLDER_PATH = "/usr//include/mach-o"; SKIP_INSTALL = NO; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos iphonesimulatornano iphonesimulator appletvsimulator"; SUPPORTS_TEXT_BASED_API = YES; TAPI_VERIFY_MODE = Pedantic; VERSIONING_SYSTEM = "apple-generic"; @@ -3083,7 +3048,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = F971DD151A4A0E0700BBDD52 /* libdyld.xcconfig */; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_WARN_EMPTY_BODY = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; @@ -3097,6 +3061,7 @@ GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_PREPROCESSOR_DEFINITIONS = "BUILDING_LIBDYLD=1"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES; @@ -3108,8 +3073,8 @@ INSTALLHDRS_SCRIPT_PHASE = YES; OTHER_CFLAGS = ""; OTHER_CPLUSPLUSFLAGS = ( - "-fno-exceptions", "$(OTHER_CFLAGS)", + "-fno-exceptions", ); OTHER_LDFLAGS = ( "-Wl,-no_inits", @@ -3137,13 +3102,14 @@ 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 "; + OTHER_TAPI_FLAGS = "-extra-public-header ./include/dlfcn.h -extra-private-header ./dyld3/libdyldEntryVector.h -ObjC++ -std=c++11 -umbrella System"; 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; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos iphonesimulatornano iphonesimulator appletvsimulator"; SUPPORTS_TEXT_BASED_API = YES; TAPI_VERIFY_MODE = Pedantic; VERSIONING_SYSTEM = "apple-generic"; @@ -3175,14 +3141,22 @@ baseConfigurationReference = F971DD131A4A0E0700BBDD52 /* base.xcconfig */; buildSettings = { CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "compiler-default"; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -3206,14 +3180,22 @@ baseConfigurationReference = F971DD131A4A0E0700BBDD52 /* base.xcconfig */; buildSettings = { CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "compiler-default"; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -3231,81 +3213,6 @@ }; 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; - }; F9F2A55A0F7AEE9900B7C9EB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3359,6 +3266,7 @@ isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos iphonesimulatornano iphonesimulator appletvsimulator"; }; name = Debug; }; @@ -3366,6 +3274,7 @@ isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "macosx iphoneos watchos appletvos bridgeos iphonesimulatornano iphonesimulator appletvsimulator"; }; name = Release; }; @@ -3399,20 +3308,29 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F908135211D3ED9000626CC1 /* Build configuration list for PBXAggregateTarget "libdyld" */ = { + 37F597D32061EB4200F9B6F9 /* Build configuration list for PBXNativeTarget "dyld_usage" */ = { isa = XCConfigurationList; buildConfigurations = ( - F908134311D3ED0C00626CC1 /* Debug */, - F908134411D3ED0C00626CC1 /* Release */, + 37F597D12061EB4200F9B6F9 /* Debug */, + 37F597D22061EB4200F9B6F9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F922C8181F33B73800D8F479 /* Build configuration list for PBXNativeTarget "libclosured-stub" */ = { + C187B9071FE063A40042D3B7 /* Build configuration list for PBXNativeTarget "libslc_builder.dylib" */ = { isa = XCConfigurationList; buildConfigurations = ( - F922C8191F33B73800D8F479 /* Debug */, - F922C81A1F33B73800D8F479 /* Release */, + C187B9081FE063A40042D3B7 /* Debug */, + C187B9091FE063A40042D3B7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F908135211D3ED9000626CC1 /* Build configuration list for PBXAggregateTarget "libdyld" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F908134311D3ED0C00626CC1 /* Debug */, + F908134411D3ED0C00626CC1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3462,15 +3380,6 @@ 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 = ( @@ -3516,15 +3425,6 @@ 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 index 360a1cd..111b8f6 100644 --- a/dyld3/APIs.cpp +++ b/dyld3/APIs.cpp @@ -24,7 +24,6 @@ #include #include -#include <_simple.h> #include #include #include @@ -33,27 +32,30 @@ #include #include #include +#include <_simple.h> +#include #include #include "dlfcn.h" +#include "dyld.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" - +#include "Closure.h" +#include "MachOLoaded.h" +#include "ClosureBuilder.h" +#include "ClosureFileSystemPhysical.h" - -extern "C" { - #include "closuredProtocol.h" -} +#if __has_feature(ptrauth_calls) +#include +#endif namespace dyld { @@ -64,6 +66,20 @@ namespace dyld { namespace dyld3 { +static const void *stripPointer(const void *ptr) { +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + +pthread_mutex_t RecursiveAutoLock::_sMutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; + +// forward declaration +static void dyld_get_image_versions_internal(const struct mach_header* mh, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version)); + + uint32_t _dyld_image_count(void) { log_apis("_dyld_image_count()\n"); @@ -74,27 +90,25 @@ uint32_t _dyld_image_count(void) 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; + return gAllImages.imageLoadAddressByIndex(imageIndex); } 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(); + const MachOLoaded* mf = (MachOLoaded*)mh; + if ( !mf->hasMachOMagic() ) + return 0; + + return mf->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); + const mach_header* mh = gAllImages.imageLoadAddressByIndex(imageIndex); if ( mh != nullptr ) return dyld3::_dyld_get_image_slide(mh); return 0; @@ -103,12 +117,7 @@ intptr_t _dyld_get_image_vmaddr_slide(uint32_t imageIndex) 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; + return gAllImages.imagePathByIndex(imageIndex); } @@ -154,8 +163,7 @@ 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) { + gAllImages.mainExecutable()->forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t compatVersion, uint32_t currentVersion, bool& stop) { if ( nameMatch(loadPath, libraryName) ) result = currentVersion; }); @@ -175,170 +183,239 @@ int32_t NSVersionOfLinkTimeLibrary(const char* libraryName) 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; - } + __block int32_t result = -1; + gAllImages.forEachImage(^(const dyld3::LoadedImage& loadedImage, bool &stop) { + const char* installName; + uint32_t currentVersion; + uint32_t compatVersion; + if ( loadedImage.loadedAddress()->getDylibInstallName(&installName, &compatVersion, ¤tVersion) && nameMatch(installName, libraryName) ) { + result = currentVersion; + stop = true; } - } - log_apis(" NSVersionOfRunTimeLibrary() => -1\n"); - return -1; + }); + log_apis(" NSVersionOfRunTimeLibrary() => 0x%08X\n", result); + return result; } -#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; + __block uint32_t retval = 0; + __block bool versionFound = false; + dyld3::dyld_get_image_versions_internal(gAllImages.mainExecutable(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; - MachOParser parser(gAllImages.mainExecutable()); - if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { - if ( platform == Platform::watchOS ) - return sdk; - } - return 0; + if (dyld_get_base_platform(platform) == PLATFORM_WATCHOS) { + versionFound = true; + retval = sdk_version; + } + }); + + return retval; } 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; + __block uint32_t retval = 0; + __block bool versionFound = false; + dyld3::dyld_get_image_versions_internal(gAllImages.mainExecutable(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; - 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; + if (dyld_get_base_platform(platform) == PLATFORM_WATCHOS) { + versionFound = true; + retval = min_version; + } + }); + + return retval; } -#endif +uint32_t dyld_get_program_sdk_bridge_os_version() +{ + log_apis("dyld_get_program_sdk_bridge_os_version()\n"); -#if TARGET_OS_BRIDGE + __block uint32_t retval = 0; + __block bool versionFound = false; + dyld3::dyld_get_image_versions_internal(gAllImages.mainExecutable(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; -static uint32_t bridgeVersToIOSVers(uint32_t vers) -{ - return vers + 0x00090000; + if (dyld_get_base_platform(platform) == PLATFORM_BRIDGEOS) { + versionFound = true; + retval = sdk_version; + } + }); + + return retval; } -uint32_t dyld_get_program_sdk_bridge_os_version() +uint32_t dyld_get_program_min_bridge_os_version() +{ + log_apis("dyld_get_program_min_bridge_os_version()\n"); + + __block uint32_t retval = 0; + __block bool versionFound = false; + dyld3::dyld_get_image_versions_internal(gAllImages.mainExecutable(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; + + if (dyld_get_base_platform(platform) == PLATFORM_BRIDGEOS) { + versionFound = true; + retval = min_version; + } + }); + + return retval; + } + +// +// 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_program_sdk_bridge_os_version()\n"); + log_apis("dyld_get_sdk_version(%p)\n", mh); + __block bool versionFound = false; + __block uint32_t retval = 0; + dyld3::dyld_get_image_versions(mh, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; + + if (platform == ::dyld_get_active_platform()) { + versionFound = true; + switch (dyld3::dyld_get_base_platform(platform)) { + case PLATFORM_BRIDGEOS: retval = sdk_version + 0x00090000; return; + case PLATFORM_WATCHOS: retval = sdk_version + 0x00070000; return; + default: retval = sdk_version; return; + } + } else if (platform == PLATFORM_IOSSIMULATOR && ::dyld_get_active_platform() == PLATFORM_IOSMAC) { + //FIXME bringup hack + versionFound = true; + retval = 0x000C0000; + } + }); - Platform platform; - uint32_t minOS; - uint32_t sdk; + return retval; +} - MachOParser parser(gAllImages.mainExecutable()); - if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { - if ( platform == Platform::bridgeOS ) - return sdk; +uint32_t dyld_get_program_sdk_version() +{ + log_apis("dyld_get_program_sdk_version()\n"); + static uint32_t sProgramSDKVersion = 0; + if (sProgramSDKVersion == 0) { + sProgramSDKVersion = dyld3::dyld_get_sdk_version(gAllImages.mainExecutable()); } - return 0; + return sProgramSDKVersion; } -uint32_t dyld_get_program_min_bridge_os_version() +uint32_t dyld_get_min_os_version(const mach_header* mh) { - log_apis("dyld_get_program_min_bridge_os_version()\n"); + log_apis("dyld_get_min_os_version(%p)\n", mh); + __block bool versionFound = false; + __block uint32_t retval = 0; + dyld3::dyld_get_image_versions(mh, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; + + if (platform == ::dyld_get_active_platform()) { + versionFound = true; + switch (dyld3::dyld_get_base_platform(platform)) { + case PLATFORM_BRIDGEOS: retval = min_version + 0x00090000; return; + case PLATFORM_WATCHOS: retval = min_version + 0x00070000; return; + default: retval = min_version; return; + } + } else if (platform == PLATFORM_IOSSIMULATOR && ::dyld_get_active_platform() == PLATFORM_IOSMAC) { + //FIXME bringup hack + versionFound = true; + retval = 0x000C0000; + } + }); - Platform platform; - uint32_t minOS; - uint32_t sdk; + return retval; +} - MachOParser parser(gAllImages.mainExecutable()); - if ( parser.getPlatformAndVersion(&platform, &minOS, &sdk) ) { - if ( platform == Platform::bridgeOS ) - return minOS; // return raw minOS (not mapped to iOS version) +dyld_platform_t dyld_get_active_platform(void) { + return gAllImages.platform(); +} + +dyld_platform_t dyld_get_base_platform(dyld_platform_t platform) { + switch (platform) { + case PLATFORM_IOSMAC: return PLATFORM_IOS; + case PLATFORM_IOSSIMULATOR: return PLATFORM_IOS; + case PLATFORM_WATCHOSSIMULATOR: return PLATFORM_WATCHOS; + case PLATFORM_TVOSSIMULATOR: return PLATFORM_TVOS; + default: return platform; } - return 0; } -#endif +bool dyld_is_simulator_platform(dyld_platform_t platform) { + switch(platform) { + case PLATFORM_IOSSIMULATOR: + case PLATFORM_WATCHOSSIMULATOR: + case PLATFORM_TVOSSIMULATOR: + return true; + default: + return false; + } +} +bool dyld_sdk_at_least(const struct mach_header* mh, dyld_build_version_t version) { + __block bool retval = false; + dyld3::dyld_get_image_versions(mh, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (dyld3::dyld_get_base_platform(platform) == version.platform && sdk_version >= version.version) { + retval = true; + } + }); + return retval; +} -#if !__WATCH_OS_VERSION_MIN_REQUIRED && !__TV_OS_VERSION_MIN_REQUIRED && !TARGET_OS_BRIDGE +bool dyld_minos_at_least(const struct mach_header* mh, dyld_build_version_t version) { + __block bool retval = false; + dyld3::dyld_get_image_versions(mh, ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (dyld3::dyld_get_base_platform(platform) == version.platform && min_version >= version.version) { + retval = true; + } + }); + return retval; +} -#define PACKED_VERSION(major, minor, tiny) ((((major) & 0xffff) << 16) | (((minor) & 0xff) << 8) | ((tiny) & 0xff)) +bool dyld_program_sdk_at_least(dyld_build_version_t version) { + return dyld3::dyld_sdk_at_least(gAllImages.mainExecutable(), version); +} -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; +bool dyld_program_minos_at_least(dyld_build_version_t version) { + return dyld3::dyld_minos_at_least(gAllImages.mainExecutable(), version); +} + +static +uint32_t linkedDylibVersion(const mach_header* mh, const char *installname) { + __block uint32_t retval = 0; + ((MachOLoaded*)mh)->forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t compatVersion, uint32_t currentVersion, bool& stop) { + if (strcmp(loadPath, installname) == 0) { + retval = currentVersion; + stop = true; + } }); + return retval; +} +#define PACKED_VERSION(major, minor, tiny) ((((major) & 0xffff) << 16) | (((minor) & 0xff) << 8) | ((tiny) & 0xff)) + +static uint32_t deriveVersionFromDylibs(const struct mach_header* mh) { + // This is a binary without a version load command, we need to infer things 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[] = { + uint32_t linkedVersion = 0; +#if TARGET_OS_OSX + linkedVersion = linkedDylibVersion(mh, "/usr/lib/libSystem.B.dylib"); + static const DylibToOSMapping versionMapping[] = { { PACKED_VERSION(88,1,3), 0x000A0400 }, { PACKED_VERSION(111,0,0), 0x000A0500 }, { PACKED_VERSION(123,0,0), 0x000A0600 }, @@ -349,159 +426,107 @@ static uint32_t deriveSDKVersFromDylibs(const mach_header* mh) // We don't need to expand this table because all recent // binaries have LC_VERSION_MIN_ load command. }; - - if ( libSystemVers != 0 ) { +#elif TARGET_OS_IOS + linkedVersion = linkedDylibVersion(mh, "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation"); + static const DylibToOSMapping versionMapping[] = { + { 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. + }; +#else + static const DylibToOSMapping versionMapping[] = {}; +#endif + if ( linkedVersion != 0 ) { uint32_t lastOsVersion = 0; - for (const DylibToOSMapping* p=libSystemMapping; ; ++p) { - if ( p->dylibVersion == 0 ) + for (const DylibToOSMapping* p=versionMapping; ; ++p) { + if ( p->dylibVersion == 0 ) { return p->osVersion; - if ( libSystemVers < p->dylibVersion ) + } + if ( linkedVersion < p->dylibVersion ) { return lastOsVersion; + } lastOsVersion = p->osVersion; } } - #endif - return 0; + 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) +// assumes mh has already been validated +static void dyld_get_image_versions_internal(const struct mach_header* mh, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version)) { - 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; + const MachOFile* mf = (MachOFile*)mh; + __block bool lcFound = false; + mf->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) { + lcFound = true; + // If SDK field is empty then derive the value from library linkages + if (sdk == 0) { + sdk = deriveVersionFromDylibs(mh); } - } + callback((const dyld_platform_t)platform, sdk, minOS); + }); -#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; + // No load command was found, so again, fallback to deriving it from library linkages + if (!lcFound) { +#if TARGET_OS_IOS +#if __x86_64__ || __x86__ + dyld_platform_t platform = PLATFORM_IOSSIMULATOR; #else - // MacOSX and iOS have old binaries without version load commmand. - return deriveSDKVersFromDylibs(mh); + dyld_platform_t platform = PLATFORM_IOS; #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; +#elif TARGET_OS_OSX + dyld_platform_t platform = PLATFORM_MACOS; #else - case Platform::macOS: - return minOS; + dyld_platform_t platform = 0; #endif - default: - // wrong binary for this platform - break; + uint32_t derivedVersion = deriveVersionFromDylibs(mh); + if ( platform != 0 && derivedVersion != 0 ) { + callback(platform, derivedVersion, 0); } } - return 0; } +void dyld_get_image_versions(const struct mach_header* mh, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version)) +{ + Diagnostics diag; + const MachOFile* mf = (MachOFile*)mh; + if ( mf->isMachO(diag, mh->sizeofcmds + sizeof(mach_header_64)) ) + dyld_get_image_versions_internal(mh, callback); +} 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()); + log_apis("dyld_get_program_min_os_version()\n"); + static uint32_t sProgramMinVersion = 0; + if (sProgramMinVersion == 0) { + sProgramMinVersion = dyld3::dyld_get_min_os_version(gAllImages.mainExecutable()); + } + return sProgramMinVersion; } - bool _dyld_get_image_uuid(const mach_header* mh, uuid_t uuid) { - log_apis("_dyld_get_image_uuid(%p, %p)\n", mh, uuid); + log_apis("_dyld_get_image_uuid(%p, %p)\n", mh, uuid); - if ( !MachOParser::wellFormedMachHeaderAndLoadCommands(mh) ) + const MachOFile* mf = (MachOFile*)mh; + if ( !mf->hasMachOMagic() ) return false; - MachOParser parser(mh); - return parser.getUuid(uuid); + + return mf->getUuid(uuid); } // @@ -512,18 +537,16 @@ bool _dyld_get_image_uuid(const mach_header* mh, uuid_t uuid) // 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; + log_apis("_NSGetExecutablePath(%p, %p)\n", buf, bufsize); + + const closure::Image* mainImage = gAllImages.mainExecutableImage(); + const char* path = gAllImages.imagePath(mainImage); + size_t pathSize = strlen(path) + 1; + if ( *bufsize >= pathSize ) { + strcpy(buf, path); + return 0; } + *bufsize = (uint32_t)pathSize; return -1; } @@ -555,10 +578,12 @@ 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; + addr = stripPointer(addr); + + const MachOLoaded* ml; + if ( gAllImages.infoForImageMappedAt(addr, &ml, nullptr, nullptr) ) + return ml; + return nullptr; } @@ -567,51 +592,18 @@ 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; + addr = stripPointer(addr); + const char* result = gAllImages.pathForImageMappedAt(addr); + + log_apis(" dyld_image_path_containing_address() => %s\n", result); + return result; } + + 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); + return gAllImages.immutableMemory(addr, length); } @@ -619,37 +611,49 @@ 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 ) { + // calling dladdr(xx,NULL) crashes + if ( info == NULL ) + return 0; // failure + + addr = stripPointer(addr); + + __block int result = 0; + const MachOLoaded* ml = nullptr; + const char* path = nullptr; + if ( gAllImages.infoForImageMappedAt(addr, &ml, nullptr, &path) ) { + info->dli_fname = path; + info->dli_fbase = (void*)ml; + + uint64_t symbolAddr; + if ( addr == info->dli_fbase ) { + // special case lookup of header + info->dli_sname = "__dso_handle"; + info->dli_saddr = info->dli_fbase; + } + else if ( ml->findClosestSymbol((long)addr, &(info->dli_sname), &symbolAddr) ) { + info->dli_saddr = (void*)(long)symbolAddr; + // 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; } - // 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; + result = 1; } - log_apis(" dladdr() => 1, { \"%s\", %p, \"%s\", %p }\n", info->dli_fname, info->dli_fbase, info->dli_sname, info->dli_saddr); - return 1; + + if ( result == 0 ) + log_apis(" dladdr() => 0\n"); + else + log_apis(" dladdr() => 1, { \"%s\", %p, \"%s\", %p }\n", info->dli_fname, info->dli_fbase, info->dli_sname, info->dli_saddr); + return result; } @@ -731,21 +735,6 @@ char* dlerror() #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); @@ -753,14 +742,15 @@ static void* makeDlHandle(const mach_header* mh, bool dontContinue) } VIS_HIDDEN -void parseDlHandle(void* h, const mach_header** mh, bool* dontContinue) +void parseDlHandle(void* h, const MachOLoaded** mh, bool* dontContinue) { *dontContinue = (((uintptr_t)h) & 1); - *mh = (const mach_header*)((((uintptr_t)h) & (-2)) << 5); + *mh = (const MachOLoaded*)((((uintptr_t)h) & (-2)) << 5); } int dlclose(void* handle) { + DYLD_LOAD_LOCK_THIS_BLOCK log_apis("dlclose(%p)\n", handle); // silently accept magic handles for main executable @@ -769,17 +759,22 @@ int dlclose(void* handle) if ( handle == RTLD_DEFAULT ) return 0; - // from here on, serialize all dlopen()s - RecursiveAutoLock dlopenSerializer; - - const mach_header* mh; + const MachOLoaded* 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); + + __block bool unloadable = false; + __block bool validHandle = false; + gAllImages.infoForImageMappedAt(mh, ^(const LoadedImage& foundImage, uint8_t permissions) { + validHandle = true; + if ( !foundImage.image()->neverUnload() ) + unloadable = true; + }); + if ( unloadable ) { + gAllImages.decRefCount(mh); // removes image if reference count went to zero + } + + if ( validHandle ) { clearErrorString(); return 0; } @@ -790,92 +785,9 @@ int dlclose(void* handle) } - -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) +void* dlopen_internal(const char* path, int mode, void* callerAddress) { + DYLD_LOAD_LOCK_THIS_BLOCK log_apis("dlopen(\"%s\", 0x%08X)\n", ((path==NULL) ? "NULL" : path), mode); clearErrorString(); @@ -889,293 +801,145 @@ void* dlopen(const char* path, int mode) 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 __IPHONE_OS_VERSION_MIN_REQUIRED + // dyld3: dlopen() not working with non-canonical paths + char canonicalPath[PATH_MAX]; + if ( leafName != path ) { + // make path canonical if it contains a // or ./ + if ( (strstr(path, "//") != NULL) || (strstr(path, "./") != NULL) ) { + const char* lastSlash = strrchr(path, '/'); + char dirPath[PATH_MAX]; + if ( strlcpy(dirPath, path, sizeof(dirPath)) < sizeof(dirPath) ) { + dirPath[lastSlash-path] = '\0'; + if ( realpath(dirPath, canonicalPath) ) { + strlcat(canonicalPath, "/", sizeof(canonicalPath)); + if ( strlcat(canonicalPath, lastSlash+1, sizeof(canonicalPath)) < sizeof(canonicalPath) ) { + // if all fit in buffer, use new canonical path + path = canonicalPath; + } + } + } } - }); - if ( alreadyLoadMH != nullptr) { - void* result = makeDlHandle(alreadyLoadMH, dontContinue); - log_apis(" dlopen(%s) => %p\n", leafName, result); - return result; } +#endif - // 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; - } - } + // RTLD_FIRST means when dlsym() is called with handle, only search the image and not those loaded after it + const bool firstOnly = (mode & RTLD_FIRST); - // 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; - }); - } + // RTLD_LOCAL means when flat searches of all images (e.g. RTLD_DEFAULT) is done, this image should be skipped. But dlsym(handle, xx) can find symbols + const bool rtldLocal = (mode & RTLD_LOCAL); - // 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_NODELETE means don't unmap image during dlclose(). Leave the memory mapped, but orphan (leak) it. + // Note: this is a weird state and it slightly different semantics that other OSs + const bool rtldNoDelete = (mode & RTLD_NODELETE); // RTLD_NOLOAD means do nothing if image not already loaded - if ( mode & RTLD_NOLOAD ) { - log_apis(" dlopen(%s) => NULL\n", leafName); + const bool rtldNoLoad = (mode & RTLD_NOLOAD); + + // try to load image from specified path + Diagnostics diag; + const mach_header* topLoadAddress = gAllImages.dlopen(diag, path, rtldNoLoad, rtldLocal, rtldNoDelete, false, callerAddress); + if ( diag.hasError() ) { + setErrorString("dlopen(%s, 0x%04X): %s", path, mode, diag.errorMessage()); + log_apis(" dlopen: closure creation error: %s\n", diag.errorMessage()); 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; + if ( topLoadAddress == nullptr ) { + log_apis(" dlopen(%s) => NULL\n", leafName); + return nullptr; } - for (int i=0; i < closuredErrorMessagesCount;++i) - free((void*)closuredErrorMessages[i]); - - log_apis(" dlopen(%s) => NULL\n", leafName); + void* result = makeDlHandle(topLoadAddress, firstOnly); + log_apis(" dlopen(%s) => %p\n", leafName, result); + return result; - return nullptr; } -bool dlopen_preflight(const char* path) +bool dlopen_preflight_internal(const char* path) { + DYLD_LOAD_LOCK_THIS_BLOCK log_apis("dlopen_preflight(%s)\n", path); - if ( gAllImages.alreadyLoaded(path, false) != nullptr ) + // check if path is in dyld shared cache + if ( gAllImages.dyldCacheHasPath(path) ) return true; - if ( gAllImages.findImageInKnownGroups(path) != nullptr ) + // check if file is loadable + Diagnostics diag; + closure::FileSystemPhysical fileSystem; + closure::LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, path, MachOFile::currentArchName(), MachOFile::currentPlatform()); + if ( loadedFileInfo.fileContent != nullptr ) { + fileSystem.unloadFile(loadedFileInfo); 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; + return false; } -static void* dlsym_search(const char* symName, const mach_header* startImageLoadAddress, const launch_cache::Image& startImage, bool searchStartImage, MachOParser::DependentFinder reExportFollower) +static void* dlsym_search(const char* symName, const LoadedImage& start, bool searchStartImage, MachOLoaded::DependentToMachOLoaded reExportHelper, + bool* resultPointsToInstructions) { - // 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); + MachOLoaded::DependentToMachOLoaded finder = ^(const MachOLoaded* mh, uint32_t depIndex) { + return gAllImages.findDependent(mh, depIndex); + }; + //fprintf(stderr, "dlsym_search: %s, start=%s\n", symName, start.image()->path()); + // walk all dependents of 'start' in order looking for symbol __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) ) + gAllImages.visitDependentsTopDown(start, ^(const LoadedImage& aLoadedImage, bool& stop) { + //fprintf(stderr, " search: %s\n", aLoadedImage.image()->path()); + if ( !searchStartImage && aLoadedImage.image() == start.image() ) return; - if ( loadAddress != nullptr ) { - MachOParser parser(loadAddress); - if ( parser.hasExportedSymbol(symName, reExportFollower, &result) ) { - stop = true; - } + if ( aLoadedImage.loadedAddress()->hasExportedSymbol(symName, finder, &result, resultPointsToInstructions) ) { + 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) + +void* dlsym_internal(void* handle, const char* symbolName, void* callerAddress) { log_apis("dlsym(%p, \"%s\")\n", handle, symbolName); clearErrorString(); + MachOLoaded::DependentToMachOLoaded finder = ^(const MachOLoaded* mh, uint32_t depIndex) { + return gAllImages.findDependent(mh, depIndex); + }; + // 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]; + BLOCK_ACCCESSIBLE_ARRAY(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); - }; - + __block void* result = nullptr; + __block bool resultPointsToInstructions = false; 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; - } + gAllImages.forEachImage(^(const LoadedImage& loadedImage, bool& stop) { + if ( loadedImage.hideFromFlatSearch() ) + return; + if ( loadedImage.loadedAddress()->hasExportedSymbol(underscoredName, finder, &result, &resultPointsToInstructions) ) { + stop = true; } + }); + if ( result != nullptr ) { +#if __has_feature(ptrauth_calls) + if (resultPointsToInstructions) + result = __builtin_ptrauth_sign_unauthenticated(result, ptrauth_key_asia, 0); +#endif + log_apis(" dlsym() => %p\n", result); + return result; } setErrorString("dlsym(RTLD_DEFAULT, %s): symbol not found", symbolName); log_apis(" dlsym() => NULL\n"); @@ -1183,63 +947,73 @@ void* dlsym(void* handle, const char* symbolName) } 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) ) { + if ( gAllImages.mainExecutable()->hasExportedSymbol(underscoredName, finder, &result, &resultPointsToInstructions) ) { log_apis(" dlsym() => %p\n", result); +#if __has_feature(ptrauth_calls) + if (resultPointsToInstructions) + result = __builtin_ptrauth_sign_unauthenticated(result, ptrauth_key_asia, 0); +#endif 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() ) { + __block bool foundCaller = false; + gAllImages.infoForImageMappedAt(callerAddress, ^(const LoadedImage& foundImage, uint8_t permissions) { + foundCaller = true; + result = dlsym_search(underscoredName, foundImage, false, finder, &resultPointsToInstructions); + }); + if ( !foundCaller ) { 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() ) { + __block bool foundCaller = false; + gAllImages.infoForImageMappedAt(callerAddress, ^(const LoadedImage& foundImage, uint8_t permissions) { + foundCaller = true; + result = dlsym_search(underscoredName, foundImage, true, finder, &resultPointsToInstructions); + }); + if ( !foundCaller ) { 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() ) { + const MachOLoaded* mh; + bool dontContinue; + parseDlHandle(handle, &mh, &dontContinue); + + __block bool foundCaller = false; + gAllImages.infoForImageWithLoadAddress(mh, ^(const LoadedImage& foundImage) { + foundCaller = true; + if ( dontContinue ) { + // RTLD_FIRST only searches one place + // we go through infoForImageWithLoadAddress() to validate the handle + mh->hasExportedSymbol(underscoredName, finder, &result, &resultPointsToInstructions); + } + else { + result = dlsym_search(underscoredName, foundImage, true, finder, &resultPointsToInstructions); + } + }); + if ( !foundCaller ) { 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 ) { +#if __has_feature(ptrauth_calls) + if (resultPointsToInstructions) + result = __builtin_ptrauth_sign_unauthenticated(result, ptrauth_key_asia, 0); +#endif log_apis(" dlsym() => %p\n", result); return result; } @@ -1286,44 +1060,73 @@ const void* _dyld_get_shared_cache_range(size_t* mappedSize) return NULL; } -bool _dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info) +void _dyld_images_for_addresses(unsigned count, const void* addresses[], dyld_image_uuid_offset infos[]) { - 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; + log_apis("_dyld_images_for_addresses(%u, %p, %p)\n", count, addresses, infos); + + // in stack crawls, common for contiguous fames to be in same image, so cache + // last lookup and check if next addresss in in there before doing full search + const MachOLoaded* ml = nullptr; + uint64_t textSize = 0; + const void* end = (void*)ml; + for (unsigned i=0; i < count; ++i) { + const void* addr = stripPointer(addresses[i]); + bzero(&infos[i], sizeof(dyld_image_uuid_offset)); + if ( (ml == nullptr) || (addr < (void*)ml) || (addr > end) ) { + if ( gAllImages.infoForImageMappedAt(addr, &ml, &textSize, nullptr) ) { + end = (void*)((uint8_t*)ml + textSize); } - else if ( strcmp(sectName, "__unwind_info") == 0 ) { - info->compact_unwind_section = content; - info->compact_unwind_section_length = sectSize; + else { + ml = nullptr; + textSize = 0; } } - }); + if ( ml != nullptr ) { + infos[i].image = ml; + infos[i].offsetInImage = (uintptr_t)addr - (uintptr_t)ml; + ml->getUuid(infos[i].uuid); + } + } +} + +void _dyld_register_for_image_loads(void (*func)(const mach_header* mh, const char* path, bool unloadable)) +{ + gAllImages.addLoadNotifier(func); +} - return true; +bool _dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info) +{ + log_apis("_dyld_find_unwind_sections(%p, %p)\n", addr, info); + addr = (void*)stripPointer(addr); + + const MachOLoaded* ml = nullptr; + if ( gAllImages.infoForImageMappedAt(addr, &ml, nullptr, nullptr) ) { + info->mh = ml; + info->dwarf_section = nullptr; + info->dwarf_section_length = 0; + info->compact_unwind_section = nullptr; + info->compact_unwind_section_length = 0; + + uint64_t size; + if ( const void* content = ml->findSectionContent("__TEXT", "__eh_frame", size) ) { + info->dwarf_section = content; + info->dwarf_section_length = (uintptr_t)size; + } + if ( const void* content = ml->findSectionContent("__TEXT", "__unwind_info", size) ) { + info->compact_unwind_section = content; + info->compact_unwind_section_length = (uintptr_t)size; + } + return true; + } + + return false; } bool dyld_process_is_restricted() { log_apis("dyld_process_is_restricted()\n"); - - launch_cache::Closure closure(gAllImages.mainClosure()); - return closure.isRestricted(); + return gAllImages.isRestricted(); } @@ -1443,7 +1246,7 @@ int dyld_shared_cache_find_iterate_text(const uuid_t cacheUuid, const char* extr }); // iterate all images - sharedCache->forEachImageTextSegment(^(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const uuid_t dylibUUID, const char* installName) { + sharedCache->forEachImageTextSegment(^(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const uuid_t dylibUUID, const char* installName, bool& stop) { dyld_shared_cache_dylib_text_info dylibTextInfo; dylibTextInfo.version = 2; dylibTextInfo.loadAddressUnslid = loadAddressUnslid; diff --git a/dyld3/APIs.h b/dyld3/APIs.h index 544f090..37e627e 100644 --- a/dyld3/APIs.h +++ b/dyld3/APIs.h @@ -28,6 +28,8 @@ #include #include +#include +#include #include "dlfcn.h" #include "dyld_priv.h" @@ -35,8 +37,40 @@ #define TEMP_HIDDEN __attribute__((visibility("hidden"))) +// +// The implementation of all dyld load/unload API's must hold a global lock +// so that the next load/unload does start until the current is complete. +// This lock is recursive so that initializers can call dlopen(). +// This is done using the macros DYLD_LOCK_THIS_BLOCK. +// Example: +// +// void dyld_load_api() { +// DYLD_LOAD_LOCK_THIS_BLOCK; +// // free to do stuff here +// // that accesses dyld internal data structures +// } +// +// + +#define DYLD_LOAD_LOCK_THIS_BLOCK RecursiveAutoLock _dyld_load_lock; + namespace dyld3 { +class __attribute__((visibility("hidden"))) RecursiveAutoLock +{ +public: + RecursiveAutoLock() { + pthread_mutex_lock(&_sMutex); + } + ~RecursiveAutoLock() { + pthread_mutex_unlock(&_sMutex); + } +private: + static pthread_mutex_t _sMutex; +}; + + + uint32_t _dyld_image_count() TEMP_HIDDEN; @@ -52,17 +86,11 @@ 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; @@ -73,6 +101,14 @@ uint32_t dyld_get_min_os_version(const mach_header* mh) TEMP_HIDDEN; uint32_t dyld_get_program_min_os_version() TEMP_HIDDEN; +dyld_platform_t dyld_get_active_platform(void) TEMP_HIDDEN; +dyld_platform_t dyld_get_base_platform(dyld_platform_t platform) TEMP_HIDDEN; +bool dyld_is_simulator_platform(dyld_platform_t platform) TEMP_HIDDEN; +bool dyld_sdk_at_least(const struct mach_header* mh, dyld_build_version_t version) TEMP_HIDDEN; +bool dyld_minos_at_least(const struct mach_header* mh, dyld_build_version_t version) TEMP_HIDDEN; +bool dyld_program_sdk_at_least(dyld_build_version_t version) TEMP_HIDDEN; +bool dyld_program_minos_at_least(dyld_build_version_t version) TEMP_HIDDEN; +void dyld_get_image_versions(const struct mach_header* mh, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version)) TEMP_HIDDEN; bool _dyld_get_image_uuid(const mach_header* mh, uuid_t uuid) TEMP_HIDDEN; @@ -103,11 +139,11 @@ char* dlerror() TEMP_HIDDEN; int dlclose(void* handle) TEMP_HIDDEN; -void* dlopen(const char* path, int mode) TEMP_HIDDEN; +void* dlopen_internal(const char* path, int mode, void* callerAddress) TEMP_HIDDEN; -bool dlopen_preflight(const char* path) TEMP_HIDDEN; +bool dlopen_preflight_internal(const char* path) TEMP_HIDDEN; -void* dlsym(void* handle, const char* symbolName) TEMP_HIDDEN; +void* dlsym_internal(void* handle, const char* symbolName, void* callerAddress) TEMP_HIDDEN; const struct dyld_all_image_infos* _dyld_get_all_image_infos() TEMP_HIDDEN; @@ -117,6 +153,10 @@ bool _dyld_get_shared_cache_uuid(uuid_t uuid) TEMP_HIDDEN; const void* _dyld_get_shared_cache_range(size_t* length) TEMP_HIDDEN; +void _dyld_images_for_addresses(unsigned count, const void* addresses[], struct dyld_image_uuid_offset infos[]) TEMP_HIDDEN; + +void _dyld_register_for_image_loads(void (*func)(const mach_header* mh, const char* path, bool unloadable)) TEMP_HIDDEN; + bool _dyld_find_unwind_sections(void* addr, dyld_unwind_sections* info) TEMP_HIDDEN; bool dyld_process_is_restricted() TEMP_HIDDEN; diff --git a/dyld3/APIs_macOS.cpp b/dyld3/APIs_macOS.cpp index 41fdff3..773a424 100644 --- a/dyld3/APIs_macOS.cpp +++ b/dyld3/APIs_macOS.cpp @@ -40,7 +40,6 @@ #include "dyld_priv.h" #include "AllImages.h" -#include "MachOParser.h" #include "Loading.h" #include "Logging.h" #include "Diagnostics.h" @@ -48,15 +47,10 @@ #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); +void parseDlHandle(void* h, const MachOLoaded** mh, bool* dontContinue); // only in macOS and deprecated @@ -79,15 +73,15 @@ NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* path, NS 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; + OFIInfo result; + result.path = strdup(path); + result.memSource = nullptr; + result.memLength = 0; + result.loadAddress = nullptr; + result.imageNum = 0; + *ofi = gAllImages.addNSObjectFileImage(result); - log_apis("NSCreateObjectFileImageFromFile() => %p\n", result); + log_apis("NSCreateObjectFileImageFromFile() => %p\n", *ofi); return NSObjectFileImageSuccess; } @@ -98,150 +92,164 @@ NSObjectFileImageReturnCode NSCreateObjectFileImageFromMemory(const void* memIma // 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; + + // check if it is current arch mach-o or fat with slice for current arch + bool usable = false; + const MachOFile* mf = (MachOFile*)memImage; + if ( mf->hasMachOMagic() && mf->isMachO(diag, memImageSize) ) { + if ( strcmp(mf->archName(), MachOFile::currentArchName()) == 0 ) + usable = true; +#if __x86_64__ + // support thin x86_64 on haswell machines + else if ( (strcmp(MachOFile::currentArchName(), "x86_64h") == 0) && (strcmp(mf->archName(), "x86_64") == 0) ) + usable = true; +#endif } - 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; + else if ( const FatFile* ff = FatFile::isFatFile(memImage) ) { + uint64_t sliceOffset; + uint64_t sliceLen; + bool missingSlice; + if ( ff->isFatFileWithSlice(diag, memImageSize, MachOFile::currentArchName(), sliceOffset, sliceLen, missingSlice) ) { + mf = (MachOFile*)((long)memImage+sliceOffset); + if ( mf->isMachO(diag, sliceLen) ) { + usable = true; } - }); + } + } + if ( usable ) { + if ( !mf->supportsPlatform(Platform::macOS) ) + usable = false; } - if ( foundMH == nullptr ) { + if ( !usable ) { 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); + if ( !mf->isBundle() ) { + log_apis("NSCreateObjectFileImageFromMemory() not a bundle\n"); 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; + OFIInfo result; + result.path = nullptr; + result.memSource = memImage; + result.memLength = memImageSize; + result.loadAddress = nullptr; + result.imageNum = 0; + *ofi = gAllImages.addNSObjectFileImage(result); - log_apis("NSCreateObjectFileImageFromMemory() => %p\n", result); + log_apis("NSCreateObjectFileImageFromMemory() => %p\n", *ofi); return NSObjectFileImageSuccess; } NSModule NSLinkModule(NSObjectFileImage ofi, const char* moduleName, uint32_t options) { + DYLD_LOAD_LOCK_THIS_BLOCK 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); + __block const char* path = nullptr; + bool foundImage = gAllImages.forNSObjectFileImage(ofi, ^(OFIInfo &image) { + // if this is memory based image, write to temp file, then use file based loading + if ( image.memSource != nullptr ) { + // make temp file with content of memory buffer + bool successfullyWritten = false; + image.path = ::tempnam(nullptr, "NSCreateObjectFileImageFromMemory-"); + if ( image.path != nullptr ) { + int fd = ::open(image.path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if ( fd != -1 ) { + ssize_t writtenSize = ::pwrite(fd, image.memSource, image.memLength, 0); + if ( writtenSize == image.memLength ) + successfullyWritten = true; + ::close(fd); + } } - } - if ( !successfullyWritten ) { - if ( ofi->path != nullptr ) { - free((void*)ofi->path); - ofi->path = nullptr; + if ( !successfullyWritten ) { + if ( image.path != nullptr ) { + free((void*)image.path); + image.path = nullptr; + } + log_apis("NSLinkModule() => NULL (could not save memory image to temp file)\n"); + return; } - 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 - } + path = image.path; + }); - // 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]); - } + if (!foundImage) { + // ofi is invalid if not in list + log_apis("NSLinkModule() => NULL (invalid NSObjectFileImage)\n"); + return nullptr; } - // 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); + if (!path) + return nullptr; + + // dlopen the binary outside of the read lock as we don't want to risk deadlock + Diagnostics diag; + void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue + const MachOLoaded* loadAddress = gAllImages.dlopen(diag, path, false, false, false, true, callerAddress); + if ( diag.hasError() ) { + log_apis(" NSLinkModule: failed: %s\n", diag.errorMessage()); + return nullptr; } - log_apis("NSLinkModule() => %p\n", ofi->loadAddress); - return (NSModule)ofi->loadAddress; + // Now update the load address of this object + gAllImages.forNSObjectFileImage(ofi, ^(OFIInfo &image) { + image.loadAddress = loadAddress; + + // if memory based load, delete temp file + if ( image.memSource != nullptr ) { + log_apis(" NSLinkModule: delete temp file: %s\n", image.path); + ::unlink(image.path); + } + }); + + log_apis("NSLinkModule() => %p\n", loadAddress); + return (NSModule)loadAddress; } // NSUnLinkModule unmaps the image, but does not release the NSObjectFileImage bool NSUnLinkModule(NSModule module, uint32_t options) { + DYLD_LOAD_LOCK_THIS_BLOCK 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; - } + __block const mach_header* mh = nullptr; + gAllImages.infoForImageMappedAt(module, ^(const LoadedImage& foundImage, uint8_t permissions) { + mh = foundImage.loadedAddress(); + }); - log_apis("NSUnLinkModule() => %d\n", result); + if ( mh != nullptr ) + gAllImages.decRefCount(mh); // removes image if reference count went to zero - return result; + log_apis("NSUnLinkModule() => %d\n", mh != nullptr); + + return mh != nullptr; } // NSDestroyObjectFileImage releases the NSObjectFileImage, but the mapped image may remain in use -bool NSDestroyObjectFileImage(NSObjectFileImage ofi) -{ - log_apis("NSDestroyObjectFileImage(%p)\n", ofi); +bool NSDestroyObjectFileImage(NSObjectFileImage imageHandle) +{ + log_apis("NSDestroyObjectFileImage(%p)\n", imageHandle); + + __block const void* memSource = nullptr; + __block size_t memLength = 0; + __block const char* path = nullptr; + bool foundImage = gAllImages.forNSObjectFileImage(imageHandle, ^(OFIInfo &image) { + // keep copy of info + memSource = image.memSource; + memLength = image.memLength; + path = image.path; + }); - // ofi is invalid if not in list - if ( !gAllImages.hasNSObjectFileImage(ofi) ) + if (!foundImage) 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); + gAllImages.removeNSObjectFileImage(imageHandle); // 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 @@ -277,79 +285,76 @@ const char* NSSymbolReferenceNameInObjectFileImage(NSObjectFileImage objectFileI halt("NSSymbolReferenceNameInObjectFileImage() is obsolete"); } -bool NSIsSymbolDefinedInObjectFileImage(NSObjectFileImage ofi, const char* symbolName) +bool NSIsSymbolDefinedInObjectFileImage(NSObjectFileImage imageHandle, const char* symbolName) { - log_apis("NSIsSymbolDefinedInObjectFileImage(%p, %s)\n", ofi, symbolName); + log_apis("NSIsSymbolDefinedInObjectFileImage(%p, %s)\n", imageHandle, symbolName); + + __block bool hasSymbol = false; + bool foundImage = gAllImages.forNSObjectFileImage(imageHandle, ^(OFIInfo &image) { + void* addr; + bool resultPointsToInstructions = false; + hasSymbol = image.loadAddress->hasExportedSymbol(symbolName, nullptr, &addr, &resultPointsToInstructions); + }); // ofi is invalid if not in list - if ( !gAllImages.hasNSObjectFileImage(ofi) ) + if (!foundImage) return false; - void* addr; - MachOParser parser(ofi->loadAddress); - return parser.hasExportedSymbol(symbolName, ^(uint32_t , const char*, void*, const mach_header**, void**) { - return false; - }, &addr); + return hasSymbol; } -void* NSGetSectionDataInObjectFileImage(NSObjectFileImage ofi, const char* segmentName, const char* sectionName, size_t* size) +void* NSGetSectionDataInObjectFileImage(NSObjectFileImage imageHandle, const char* segmentName, const char* sectionName, size_t* size) { + __block const void* result = nullptr; + bool foundImage = gAllImages.forNSObjectFileImage(imageHandle, ^(OFIInfo &image) { + uint64_t sz; + result = image.loadAddress->findSectionContent(segmentName, sectionName, sz); + *size = (size_t)sz; + }); + // ofi is invalid if not in list - if ( !gAllImages.hasNSObjectFileImage(ofi) ) + if (!foundImage) 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; + return (void*)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; + __block const char* result = nullptr; + gAllImages.infoForImageMappedAt(m, ^(const LoadedImage& foundImage, uint8_t permissions) { + result = gAllImages.imagePath(foundImage.image()); + }); + + return result; } 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; -} + __block const char* result = nullptr; + gAllImages.infoForImageMappedAt(m, ^(const LoadedImage& foundImage, uint8_t permissions) { + result = gAllImages.imagePath(foundImage.image()); + }); + return result; + } 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; - } + __block bool result = false; + gAllImages.forEachImage(^(const LoadedImage& loadedImage, bool& stop) { + bool resultPointsToInstructions = false; + if ( loadedImage.loadedAddress()->hasExportedSymbol(symbolName, nullptr, symbolAddress, &resultPointsToInstructions) ) { + *foundInImageAtLoadAddress = loadedImage.loadedAddress(); + stop = true; + result = true; } - } - return false; + }); + return result; } bool NSIsSymbolNameDefined(const char* symbolName) @@ -374,14 +379,9 @@ bool NSIsSymbolNameDefinedInImage(const struct mach_header* mh, const char* symb { 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); + void* addr; + bool resultPointsToInstructions = false; + return ((MachOLoaded*)mh)->hasExportedSymbol(symbolName, nullptr, &addr, &resultPointsToInstructions); } NSSymbol NSLookupAndBindSymbol(const char* symbolName) @@ -412,43 +412,30 @@ 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; - } + const MachOLoaded* mh = (const MachOLoaded*)module; + void* addr; + bool resultPointsToInstructions = false; + if ( mh->hasExportedSymbol(symbolName, nullptr, &addr, &resultPointsToInstructions) ) { + return (NSSymbol)addr; } return nullptr; } -NSSymbol NSLookupSymbolInImage(const struct mach_header* mh, const char* symbolName, uint32_t options) +NSSymbol NSLookupSymbolInImage(const 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; - } - + void* addr; + bool resultPointsToInstructions = false; + if ( ((MachOLoaded*)mh)->hasExportedSymbol(symbolName, nullptr, &addr, &resultPointsToInstructions) ) { + log_apis(" NSLookupSymbolInImage() => %p\n", addr); + return (NSSymbol)addr; + } if ( options & NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR ) { log_apis(" NSLookupSymbolInImage() => NULL\n"); return nullptr; } + // FIXME: abort(); return nullptr; } @@ -469,12 +456,12 @@ 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; + __block NSModule result = nullptr; + gAllImages.infoForImageMappedAt(symbol, ^(const LoadedImage& foundImage, uint8_t permissions) { + result = (NSModule)foundImage.loadedAddress(); + }); + + return result; } void NSLinkEditError(NSLinkEditErrors *c, int *errorNumber, const char** fileName, const char** errorString) @@ -490,14 +477,16 @@ bool NSAddLibrary(const char* pathName) { log_apis("NSAddLibrary(%s)\n", pathName); - return ( dlopen(pathName, 0) != nullptr); + void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue + return ( dlopen_internal(pathName, 0, callerAddress) != nullptr); } bool NSAddLibraryWithSearching(const char* pathName) { log_apis("NSAddLibraryWithSearching(%s)\n", pathName); - return ( dlopen(pathName, 0) != nullptr); + void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue + return ( dlopen_internal(pathName, 0, callerAddress) != nullptr); } const mach_header* NSAddImage(const char* imageName, uint32_t options) @@ -509,9 +498,10 @@ const mach_header* NSAddImage(const char* imageName, uint32_t options) if ( (options & NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED) != 0 ) dloptions |= RTLD_NOLOAD; - void* h = dlopen(imageName, dloptions); + void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue + void* h = dlopen_internal(imageName, dloptions, callerAddress); if ( h != nullptr ) { - const mach_header* mh; + const MachOLoaded* mh; bool dontContinue; parseDlHandle(h, &mh, &dontContinue); return mh; diff --git a/dyld3/AllImages.cpp b/dyld3/AllImages.cpp index a410456..5a696a7 100644 --- a/dyld3/AllImages.cpp +++ b/dyld3/AllImages.cpp @@ -23,25 +23,26 @@ #include +#include +#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" +#include "Closure.h" +#include "ClosureBuilder.h" +#include "ClosureFileSystemPhysical.h" extern const char** appleParams; @@ -57,564 +58,247 @@ 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()); -} +///////////////////// AllImages //////////////////////////// -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 +AllImages gAllImages; -///////////////////// ChunkedVector //////////////////////////// -template -ChunkedVector* ChunkedVector::make(uint32_t count) +void AllImages::init(const closure::LaunchClosure* closure, const DyldSharedCache* dyldCacheLoadAddress, const char* dyldCachePath, + const Array& initialImages) { - 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; - } -} + _mainClosure = closure; + _initialImages = &initialImages; + _dyldCacheAddress = dyldCacheLoadAddress; + _dyldCachePath = dyldCachePath; -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; + if ( _dyldCacheAddress ) { + const dyld_cache_mapping_info* const fileMappings = (dyld_cache_mapping_info*)((uint64_t)_dyldCacheAddress + _dyldCacheAddress->header.mappingOffset); + _dyldCacheSlide = (uint64_t)dyldCacheLoadAddress - fileMappings[0].address; + _imagesArrays.push_back(dyldCacheLoadAddress->cachedDylibsImageArray()); + if ( auto others = dyldCacheLoadAddress->otherOSImageArray() ) + _imagesArrays.push_back(others); } -} - -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; -} + _imagesArrays.push_back(_mainClosure->images()); -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); + // record first ImageNum to do use for dlopen() calls + _mainClosure->images()->forEachImage(^(const dyld3::closure::Image* image, bool& stop) { + closure::ImageNum num = image->imageNum(); + if ( num >= _nextImageNum ) + _nextImageNum = num+1; + }); + + // Make temporary old image array, so libSystem initializers can be debugged + STACK_ALLOC_ARRAY(dyld_image_info, oldDyldInfo, initialImages.count()); + for (const LoadedImage& li : initialImages) { + oldDyldInfo.push_back({li.loadedAddress(), li.image()->path(), 0}); } - _inUseCount--; -} - - -///////////////////// ReaderWriterChunkedVector //////////////////////////// - - - -template -void ReaderWriterChunkedVector::withReadLock(void (^work)()) const -{ - assert(pthread_rwlock_rdlock(&_lock) == 0); - work(); - assert(pthread_rwlock_unlock(&_lock) == 0); -} + _oldAllImageInfos->infoArray = &oldDyldInfo[0]; + _oldAllImageInfos->infoArrayCount = (uint32_t)oldDyldInfo.count(); + _oldAllImageInfos->notification(dyld_image_adding, _oldAllImageInfos->infoArrayCount, _oldAllImageInfos->infoArray); + _oldAllImageInfos->infoArray = nullptr; + _oldAllImageInfos->infoArrayCount = 0; -template -void ReaderWriterChunkedVector::withWriteLock(void (^work)()) const -{ - assert(pthread_rwlock_wrlock(&_lock) == 0); - work(); - assert(pthread_rwlock_unlock(&_lock) == 0); + _processDOFs = Loader::dtraceUserProbesEnabled(); } -template -void ReaderWriterChunkedVector::acquireWriteLock() +void AllImages::setProgramVars(ProgramVars* vars) { - assert(pthread_rwlock_wrlock(&_lock) == 0); + _programVars = vars; + const dyld3::MachOFile* mf = (dyld3::MachOFile*)_programVars->mh; + mf->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) { + _platform = (dyld_platform_t)platform; + //FIXME assert there is only one? + }); } -template -void ReaderWriterChunkedVector::releaseWriteLock() +void AllImages::setRestrictions(bool allowAtPaths, bool allowEnvPaths) { - assert(pthread_rwlock_unlock(&_lock) == 0); + _allowAtPaths = allowAtPaths; + _allowEnvPaths = allowEnvPaths; } -template -uint32_t ReaderWriterChunkedVector::count() const +void AllImages::applyInitialImages() { - __block uint32_t result = 0; - withReadLock(^() { - for (const ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { - result += chunk->count(); - } - }); - return result; + addImages(*_initialImages); + runImageNotifiers(*_initialImages); + _initialImages = nullptr; // this was stack allocated } -template -uint32_t ReaderWriterChunkedVector::countNoLock() const +void AllImages::withReadLock(void (^work)()) const { - uint32_t result = 0; - for (const ChunkedVector* chunk = &_firstChunk; chunk != nullptr; chunk = chunk->_next) { - result += chunk->count(); - } - return result; +#ifdef OS_UNFAIR_RECURSIVE_LOCK_INIT + os_unfair_recursive_lock_lock(&_loadImagesLock); + work(); + os_unfair_recursive_lock_unlock(&_loadImagesLock); +#else + pthread_mutex_lock(&_loadImagesLock); + work(); + pthread_mutex_unlock(&_loadImagesLock); +#endif } -template -T* ReaderWriterChunkedVector::addNoLock(uint32_t count, const T values[]) +void AllImages::withWriteLock(void (^work)()) { - 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; +#ifdef OS_UNFAIR_RECURSIVE_LOCK_INIT + os_unfair_recursive_lock_lock(&_loadImagesLock); + work(); + os_unfair_recursive_lock_unlock(&_loadImagesLock); +#else + pthread_mutex_lock(&_loadImagesLock); + work(); + pthread_mutex_unlock(&_loadImagesLock); +#endif } -template -T* ReaderWriterChunkedVector::add(uint32_t count, const T values[]) +void AllImages::withNotifiersLock(void (^work)()) const { - __block T* result = nullptr; - withWriteLock(^() { - result = addNoLock(count, values); - }); - return result; +#ifdef OS_UNFAIR_RECURSIVE_LOCK_INIT + os_unfair_recursive_lock_lock(&_notifiersLock); + work(); + os_unfair_recursive_lock_unlock(&_notifiersLock); +#else + pthread_mutex_lock(&_notifiersLock); + work(); + pthread_mutex_unlock(&_notifiersLock); +#endif } -template -void ReaderWriterChunkedVector::remove(const T& valueToRemove) +void AllImages::mirrorToOldAllImageInfos() { - __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; - } + withReadLock(^(){ + // set infoArray to NULL to denote it is in-use + _oldAllImageInfos->infoArray = nullptr; + + // if array not large enough, re-alloc it + uint32_t imageCount = (uint32_t)_loadedImages.count(); + 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; } - }); -} -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; + // fill out array to mirror current image list + int index = 0; + for (const LoadedImage& li : _loadedImages) { + _oldAllImageArray[index].imageLoadAddress = li.loadedAddress(); + _oldAllImageArray[index].imageFilePath = imagePath(li.image()); + _oldAllImageArray[index].imageFileModDate = 0; + ++index; } - }); -} -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; - } -} + // 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; -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; - - if ( _dyldCacheAddress ) { - const DyldSharedCache* cache = (DyldSharedCache*)_dyldCacheAddress; - const dyld_cache_mapping_info* const fileMappings = (dyld_cache_mapping_info*)((uint64_t)_dyldCacheAddress + cache->header.mappingOffset); - _dyldCacheSlide = (uint64_t)dyldCacheLoadAddress - fileMappings[0].address; - } - - // 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() +void AllImages::addImages(const Array& newImages) { - // 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); + // copy into _loadedImages + withWriteLock(^(){ + _loadedImages.append(newImages); + // if any image not in the shared cache added, recompute bounds + for (const LoadedImage& li : newImages) { + if ( !((MachOAnalyzer*)li.loadedAddress())->inDyldCache() ) { + recomputeBounds(); + break; + } } - _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) +void AllImages::runImageNotifiers(const Array& 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(); - }); - } + 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); + for (uint32_t i=0; i < count; ++i) { + oldDyldInfo[i].imageLoadAddress = newImages[i].loadedAddress(); + oldDyldInfo[i].imageFilePath = imagePath(newImages[i].image()); 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)); + for (const LoadedImage& li : newImages) { + log_loads("dyld: %s\n", imagePath(li.image())); } #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 ) { + for (const LoadedImage& li : newImages) { + const closure::Image* image = li.image(); + struct stat stat_buf; + fsid_t fsid = {{ 0, 0 }}; + fsobj_id_t fsobjid = { 0, 0 }; + if ( !image->inDyldCache() && (stat(imagePath(image), &stat_buf) == 0) ) { fsobjid = *(fsobj_id_t*)&stat_buf.st_ino; - fsid = {{ stat_buf.st_dev, 0 }}; + fsid = {{ stat_buf.st_dev, 0 }}; } - kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, img.uuid(), fsobjid, fsid, newImages[i].loadAddress); + uuid_t uuid; + image->getUuid(uuid); + kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, &uuid, fsobjid, fsid, li.loadedAddress()); } } #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) { - log_notifications("dyld: add notifier %p called with mh=%p\n", func, newImages[i].loadAddress); - if (newImages[i].justUsedFromDyldCache) { - func(newImages[i].loadAddress, _dyldCacheSlide); - } else { - MachOParser parser(newImages[i].loadAddress); - func(newImages[i].loadAddress, parser.getSlide()); + withNotifiersLock(^{ + for (NotifyFunc func : _loadNotifiers) { + for (const LoadedImage& li : newImages) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)li.loadedAddress(), (uint64_t)func, 0); + log_notifications("dyld: add notifier %p called with mh=%p\n", func, li.loadedAddress()); + if ( li.image()->inDyldCache() ) + func(li.loadedAddress(), (uintptr_t)_dyldCacheSlide); + else + func(li.loadedAddress(), li.loadedAddress()->getSlide()); } } - } + for (LoadNotifyFunc func : _loadNotifiers2) { + for (const LoadedImage& li : newImages) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)li.loadedAddress(), (uint64_t)func, 0); + log_notifications("dyld: add notifier %p called with mh=%p\n", func, li.loadedAddress()); + if ( li.image()->inDyldCache() ) + func(li.loadedAddress(), li.image()->path(), false); + else + func(li.loadedAddress(), li.image()->path(), !li.image()->neverUnload()); + } + } + }); // 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; + for (const LoadedImage& li : newImages) { + const closure::Image* image = li.image(); + if ( image->hasObjC() ) { + pathsBuffer[imagesWithObjC] = imagePath(image); + mhBuffer[imagesWithObjC] = li.loadedAddress(); ++imagesWithObjC; } } if ( imagesWithObjC != 0 ) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 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) { @@ -628,37 +312,28 @@ void AllImages::addImages(const launch_cache::DynArray& newIm notifyMonitorLoads(newImages); } -void AllImages::removeImages(const launch_cache::DynArray& unloadImages) +void AllImages::removeImages(const Array& 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()); + withNotifiersLock(^{ + for (NotifyFunc func : _unloadNotifiers) { + for (const LoadedImage& li : unloadImages) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_REMOVE_IMAGE, (uint64_t)li.loadedAddress(), (uint64_t)func, 0); + log_notifications("dyld: remove notifier %p called with mh=%p\n", func, li.loadedAddress()); + if ( li.image()->inDyldCache() ) + func(li.loadedAddress(), (uintptr_t)_dyldCacheSlide); + else + func(li.loadedAddress(), li.loadedAddress()->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)); + for (const LoadedImage& li : unloadImages) { + if ( li.image()->hasObjC() ) { + (*_objcNotifyUnmapped)(imagePath(li.image()), li.loadedAddress()); + log_notifications("dyld: objc-unmapped-notifier called with image %p %s\n", li.loadedAddress(), imagePath(li.image())); } } } @@ -666,185 +341,422 @@ void AllImages::removeImages(const launch_cache::DynArray& un #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 ) { + for (const LoadedImage& li : unloadImages) { + const closure::Image* image = li.image(); + struct stat stat_buf; + fsid_t fsid = {{ 0, 0 }}; + fsobj_id_t fsobjid = { 0, 0 }; + if ( stat(imagePath(image), &stat_buf) == 0 ) { fsobjid = *(fsobj_id_t*)&stat_buf.st_ino; - fsid = {{ stat_buf.st_dev, 0 }}; + fsid = {{ stat_buf.st_dev, 0 }}; } - kdebug_trace_dyld_image(DBG_DYLD_UUID_UNMAP_A, img.uuid(), fsobjid, fsid, unloadImages[i].loadAddress); + uuid_t uuid; + image->getUuid(uuid); + kdebug_trace_dyld_image(DBG_DYLD_UUID_UNMAP_A, &uuid, fsobjid, fsid, li.loadedAddress()); } } #endif - // remove each from sLoadedImages - for (uint32_t i=0; i < count; ++i) { - LoadedImage info(unloadImages[i].loadAddress, unloadImages[i].imageData); - sLoadedImages.remove(info); - } + // remove each from _loadedImages + withWriteLock(^(){ + for (const LoadedImage& uli : unloadImages) { + for (LoadedImage& li : _loadedImages) { + if ( uli.loadedAddress() == li.loadedAddress() ) { + _loadedImages.erase(li); + break; + } + } + } + recomputeBounds(); + }); // sync to old all image infos struct - sLoadedImages.withReadLock(^{ - mirrorToOldAllImageInfos(); - }); + 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)); + STACK_ALLOC_ARRAY(dyld_image_info, oldDyldInfo, unloadImages.count()); + for (const LoadedImage& li : unloadImages) { + oldDyldInfo.push_back({li.loadedAddress(), li.image()->path(), 0}); } + _oldAllImageInfos->notification(dyld_image_removing, (uint32_t)oldDyldInfo.count(), &oldDyldInfo[0]); // notify any processes tracking loads in this process notifyMonitorUnloads(unloadImages); + + // finally, unmap images + for (const LoadedImage& li : unloadImages) { + if ( li.leaveMapped() ) { + log_loads("dyld: unloaded but left mmapped %s\n", imagePath(li.image())); + } + else { + // unmapImage() modifies parameter, so use copy + LoadedImage copy = li; + Loader::unmapImage(copy); + log_loads("dyld: unloaded %s\n", imagePath(li.image())); + } + } +} + +// must be called with writeLock held +void AllImages::recomputeBounds() +{ + _lowestNonCached = UINTPTR_MAX; + _highestNonCached = 0; + for (const LoadedImage& li : _loadedImages) { + const MachOLoaded* ml = li.loadedAddress(); + uintptr_t start = (uintptr_t)ml; + if ( !((MachOAnalyzer*)ml)->inDyldCache() ) { + if ( start < _lowestNonCached ) + _lowestNonCached = start; + uintptr_t end = start + (uintptr_t)(li.image()->vmSizeToMap()); + if ( end > _highestNonCached ) + _highestNonCached = end; + } + } +} + +uint32_t AllImages::count() const +{ + return (uint32_t)_loadedImages.count(); +} + +bool AllImages::dyldCacheHasPath(const char* path) const +{ + uint32_t dyldCacheImageIndex; + if ( _dyldCacheAddress != nullptr ) + return _dyldCacheAddress->hasImagePath(path, dyldCacheImageIndex); + return false; +} + +const char* AllImages::imagePathByIndex(uint32_t index) const +{ + if ( index < _loadedImages.count() ) + return imagePath(_loadedImages[index].image()); + return nullptr; } -void AllImages::setNeverUnload(const loader::ImageInfo& existingImage) +const mach_header* AllImages::imageLoadAddressByIndex(uint32_t index) const { - sLoadedImages.forEachWithWriteLock(^(uint32_t index, dyld3::LoadedImage &value, bool &stop) { - if (value.image() == existingImage.imageData) { - value.setNeverUnload(); - stop = true; + if ( index < _loadedImages.count() ) + return _loadedImages[index].loadedAddress(); + return nullptr; +} + +bool AllImages::findImage(const mach_header* loadAddress, LoadedImage& foundImage) const +{ + __block bool result = false; + withReadLock(^(){ + for (const LoadedImage& li : _loadedImages) { + if ( li.loadedAddress() == loadAddress ) { + foundImage = li; + result = true; + break; + } } }); + return result; } -uint32_t AllImages::count() const +void AllImages::forEachImage(void (^handler)(const LoadedImage& loadedImage, bool& stop)) const { - return sLoadedImages.count(); + withReadLock(^{ + bool stop = false; + for (const LoadedImage& li : _loadedImages) { + handler(li, stop); + if ( stop ) + break; + } + }); } -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; +const char* AllImages::pathForImageMappedAt(const void* addr) const +{ + if ( _initialImages != nullptr ) { + // being called during libSystem initialization, so _loadedImages not allocated yet + for (const LoadedImage& li : *_initialImages) { + uint8_t permissions; + if ( li.image()->containsAddress(addr, li.loadedAddress(), &permissions) ) { + return li.image()->path(); + } + } + return nullptr; + } + + // if address is in cache, do fast search of TEXT segments in cache + __block const char* result = nullptr; + if ( (_dyldCacheAddress != nullptr) && (addr > _dyldCacheAddress) ) { + if ( addr < (void*)((uint8_t*)_dyldCacheAddress+_dyldCacheAddress->mappedSize()) ) { + uint64_t cacheSlide = (uint64_t)_dyldCacheAddress - _dyldCacheAddress->unslidLoadAddress(); + uint64_t unslidTargetAddr = (uint64_t)addr - cacheSlide; + _dyldCacheAddress->forEachImageTextSegment(^(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const unsigned char* dylibUUID, const char* installName, bool& stop) { + if ( (loadAddressUnslid <= unslidTargetAddr) && (unslidTargetAddr < loadAddressUnslid+textSegmentSize) ) { + result = installName; + stop = true; + } + }); + if ( result != nullptr ) + return result; + } + } + + // slow path - search image list + infoForImageMappedAt(addr, ^(const LoadedImage& foundImage, uint8_t permissions) { + result = foundImage.image()->path(); + }); + + return result; +} + +void AllImages::infoForImageMappedAt(const void* addr, void (^handler)(const LoadedImage& foundImage, uint8_t permissions)) const +{ + __block uint8_t permissions; + if ( _initialImages != nullptr ) { + // being called during libSystem initialization, so _loadedImages not allocated yet + for (const LoadedImage& li : *_initialImages) { + if ( li.image()->containsAddress(addr, li.loadedAddress(), &permissions) ) { + handler(li, permissions); + break; + } + } + return; + } + + withReadLock(^{ + for (const LoadedImage& li : _loadedImages) { + if ( li.image()->containsAddress(addr, li.loadedAddress(), &permissions) ) { + handler(li, permissions); + break; + } + } + }); +} + + +bool AllImages::infoForImageMappedAt(const void* addr, const MachOLoaded** ml, uint64_t* textSize, const char** path) const +{ + if ( _initialImages != nullptr ) { + // being called during libSystem initialization, so _loadedImages not allocated yet + for (const LoadedImage& li : *_initialImages) { + uint8_t permissions; + if ( li.image()->containsAddress(addr, li.loadedAddress(), &permissions) ) { + if ( ml != nullptr ) + *ml = li.loadedAddress(); + if ( path != nullptr ) + *path = li.image()->path(); + if ( textSize != nullptr ) { + *textSize = li.image()->textSize(); + } + return true; + } + } + return false; + } + + // if address is in cache, do fast search of TEXT segments in cache + __block bool result = false; + if ( (_dyldCacheAddress != nullptr) && (addr > _dyldCacheAddress) ) { + if ( addr < (void*)((uint8_t*)_dyldCacheAddress+_dyldCacheAddress->mappedSize()) ) { + uint64_t cacheSlide = (uint64_t)_dyldCacheAddress - _dyldCacheAddress->unslidLoadAddress(); + uint64_t unslidTargetAddr = (uint64_t)addr - cacheSlide; + _dyldCacheAddress->forEachImageTextSegment(^(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const unsigned char* dylibUUID, const char* installName, bool& stop) { + if ( (loadAddressUnslid <= unslidTargetAddr) && (unslidTargetAddr < loadAddressUnslid+textSegmentSize) ) { + if ( ml != nullptr ) + *ml = (MachOLoaded*)(loadAddressUnslid + cacheSlide); + if ( path != nullptr ) + *path = installName; + if ( textSize != nullptr ) + *textSize = textSegmentSize; + stop = true; + result = true; + } + }); + if ( result ) + return result; + } + } + + // slow path - search image list + infoForImageMappedAt(addr, ^(const LoadedImage& foundImage, uint8_t permissions) { + if ( ml != nullptr ) + *ml = foundImage.loadedAddress(); + if ( path != nullptr ) + *path = foundImage.image()->path(); + if ( textSize != nullptr ) + *textSize = foundImage.image()->textSize(); + result = true; + }); + + return result; +} + +// same as infoForImageMappedAt(), but only look at images not in the dyld cache +void AllImages::infoForNonCachedImageMappedAt(const void* addr, void (^handler)(const LoadedImage& foundImage, uint8_t permissions)) const +{ + __block uint8_t permissions; + if ( _initialImages != nullptr ) { + // being called during libSystem initialization, so _loadedImages not allocated yet + for (const LoadedImage& li : *_initialImages) { + if ( !((MachOAnalyzer*)li.loadedAddress())->inDyldCache() ) { + if ( li.image()->containsAddress(addr, li.loadedAddress(), &permissions) ) { + handler(li, permissions); + break; + } + } + } + return; + } + + withReadLock(^{ + for (const LoadedImage& li : _loadedImages) { + if ( !((MachOAnalyzer*)li.loadedAddress())->inDyldCache() ) { + if ( li.image()->containsAddress(addr, li.loadedAddress(), &permissions) ) { + handler(li, permissions); + break; + } + } } }); - return launch_cache::Image(foundImage); } -launch_cache::Image AllImages::findByLoadAddress(const mach_header* loadAddress) const +bool AllImages::immutableMemory(const void* addr, size_t length) 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; + // quick check to see if in shared cache + if ( _dyldCacheAddress != nullptr ) { + bool readOnly; + if ( _dyldCacheAddress->inCache(addr, length, readOnly) ) { + return readOnly; } - }); - 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; + withReadLock(^() { + // quick check to see if it is not any non-cached image loaded + if ( ((uintptr_t)addr < _lowestNonCached) || ((uintptr_t)addr+length > _highestNonCached) ) { + result = false; + return; + } + // slow walk through all images, only look at images not in dyld cache + for (const LoadedImage& li : _loadedImages) { + if ( !((MachOAnalyzer*)li.loadedAddress())->inDyldCache() ) { + uint8_t permissions; + if ( li.image()->containsAddress(addr, li.loadedAddress(), &permissions) ) { + result = ((permissions & VM_PROT_WRITE) == 0) && li.image()->neverUnload(); + break; + } + } } }); + return result; } -void AllImages::forEachImage(void (^handler)(uint32_t imageIndex, const mach_header* loadAddress, const launch_cache::Image image, bool& stop)) const +void AllImages::infoForImageWithLoadAddress(const MachOLoaded* mh, void (^handler)(const LoadedImage& foundImage)) const { - sLoadedImages.forEachWithReadLock(^(uint32_t imageIndex, const LoadedImage& loadedImage, bool& stop) { - handler(imageIndex, loadedImage.loadedAddress(), launch_cache::Image(loadedImage.image()), stop); + withReadLock(^{ + for (const LoadedImage& li : _loadedImages) { + if ( li.loadedAddress() == mh ) { + handler(li); + break; + } + } }); } -launch_cache::Image AllImages::findByOwnedAddress(const void* addr, const mach_header** loadAddress, uint8_t* permissions) const +bool AllImages::findImageNum(closure::ImageNum imageNum, LoadedImage& foundImage) 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; + if ( _initialImages != nullptr ) { + // being called during libSystem initialization, so _loadedImages not allocated yet + for (const LoadedImage& li : *_initialImages) { + if ( li.image()->representsImageNum(imageNum) ) { + foundImage = li; + return true; } } - return launch_cache::Image(nullptr); + return false; } - // 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; - } + bool result = false; + for (const LoadedImage& li : _loadedImages) { + if ( li.image()->representsImageNum(imageNum) ) { + foundImage = li; + result = true; + break; } } - __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 result; +} + +const MachOLoaded* AllImages::findDependent(const MachOLoaded* mh, uint32_t depIndex) +{ + __block const MachOLoaded* result = nullptr; + withReadLock(^{ + for (const LoadedImage& li : _loadedImages) { + if ( li.loadedAddress() == mh ) { + closure::ImageNum depImageNum = li.image()->dependentImageNum(depIndex); + LoadedImage depLi; + if ( findImageNum(depImageNum, depLi) ) + result = depLi.loadedAddress(); + break; + } } }); - return launch_cache::Image(foundImage); + return result; } -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; + +void AllImages::breadthFirstRecurseDependents(Array& visited, const LoadedImage& nodeLi, bool& stopped, void (^handler)(const LoadedImage& aLoadedImage, bool& stop)) const +{ + // call handler on all direct dependents (unless already visited) + STACK_ALLOC_ARRAY(LoadedImage, dependentsToRecurse, 256); + nodeLi.image()->forEachDependentImage(^(uint32_t depIndex, closure::Image::LinkKind kind, closure::ImageNum depImageNum, bool& depStop) { + if ( kind == closure::Image::LinkKind::upward ) + return; + if ( visited.contains(depImageNum) ) + return; + LoadedImage depLi; + if ( !findImageNum(depImageNum, depLi) ) + return; + handler(depLi, depStop); + visited.push_back(depImageNum); + if ( depStop ) { + stopped = true; + return; } + dependentsToRecurse.push_back(depLi); + }); + if ( stopped ) + return; + // recurse on all dependents just visited + for (LoadedImage& depLi : dependentsToRecurse) { + breadthFirstRecurseDependents(visited, depLi, stopped, handler); + } +} + +void AllImages::visitDependentsTopDown(const LoadedImage& start, void (^handler)(const LoadedImage& aLoadedImage, bool& stop)) const +{ + withReadLock(^{ + STACK_ALLOC_ARRAY(closure::ImageNum, visited, count()); + bool stop = false; + handler(start, stop); + if ( stop ) + return; + visited.push_back(start.image()->imageNum()); + breadthFirstRecurseDependents(visited, start, stop, handler); }); - return foundAddress; } -const mach_header* AllImages::mainExecutable() const +const MachOLoaded* AllImages::mainExecutable() const { assert(_programVars != nullptr); - return (const mach_header*)_programVars->mh; + return (const MachOLoaded*)_programVars->mh; } -launch_cache::Image AllImages::mainExecutableImage() const +const closure::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; + return _mainClosure->images()->imageForNum(_mainClosure->topImage()); } void AllImages::setMainPath(const char* path ) @@ -852,197 +764,94 @@ void AllImages::setMainPath(const char* path ) _mainExeOverridePath = path; } -const char* AllImages::imagePath(const BinaryImage* binImage) const +const char* AllImages::imagePath(const closure::Image* image) 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() ) + if ( image == mainExecutableImage() ) 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; - }); + return image->path(); } -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; +dyld_platform_t AllImages::platform() const { + return _platform; } void AllImages::incRefCount(const mach_header* loadAddress) { - __block bool found = false; - sDlopenRefCounts.forEachWithWriteLock(^(uint32_t index, DlopenCount& entry, bool& stop) { + for (DlopenCount& entry : _dlopenRefCounts) { if ( entry.loadAddress == loadAddress ) { - found = true; + // found existing DlopenCount entry, bump counter entry.refCount += 1; - stop = true; + return; } - }); - if ( !found ) { - DlopenCount newEnty = { loadAddress, 1 }; - sDlopenRefCounts.add(newEnty); } + + // no existing DlopenCount, add new one + _dlopenRefCounts.push_back({ loadAddress, 1 }); } void AllImages::decRefCount(const mach_header* loadAddress) { - __block bool refCountNowZero = false; - sDlopenRefCounts.forEachWithWriteLock(^(uint32_t index, DlopenCount& entry, bool& stop) { + bool doCollect = false; + for (DlopenCount& entry : _dlopenRefCounts) { if ( entry.loadAddress == loadAddress ) { + // found existing DlopenCount entry, bump counter entry.refCount -= 1; - stop = true; - if ( entry.refCount == 0 ) - refCountNowZero = true; + if ( entry.refCount == 0 ) { + _dlopenRefCounts.erase(entry); + doCollect = true; + break; + } + return; } - }); - if ( refCountNowZero ) { - DlopenCount delEnty = { loadAddress, 0 }; - sDlopenRefCounts.remove(delEnty); - garbageCollectImages(); } + if ( doCollect ) + garbageCollectImages(); } #if __MAC_OS_X_VERSION_MIN_REQUIRED -__NSObjectFileImage* AllImages::addNSObjectFileImage() +NSObjectFileImage AllImages::addNSObjectFileImage(const OFIInfo& image) { - // 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; - } + __block uint64_t imageNum = 0; + withWriteLock(^{ + imageNum = ++_nextObjectFileImageNum; + _objectFileImages.push_back(image); + _objectFileImages.back().imageNum = imageNum; }); - 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 (NSObjectFileImage)imageNum; +} + +bool AllImages::forNSObjectFileImage(NSObjectFileImage imageHandle, + void (^handler)(OFIInfo& image)) { + uint64_t imageNum = (uint64_t)imageHandle; + bool __block foundImage = false; + withReadLock(^{ + for (OFIInfo& ofi : _objectFileImages) { + if ( ofi.imageNum == imageNum ) { + handler(ofi); + foundImage = true; + return; + } } }); - return result; + + return foundImage; } -void AllImages::removeNSObjectFileImage(__NSObjectFileImage* ofi) +void AllImages::removeNSObjectFileImage(NSObjectFileImage imageHandle) { - 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; + uint64_t imageNum = (uint64_t)imageHandle; + withWriteLock(^{ + for (OFIInfo& ofi : _objectFileImages) { + if ( ofi.imageNum == imageNum ) { + _objectFileImages.erase(ofi); + return; + } } }); } @@ -1052,105 +861,82 @@ void AllImages::removeNSObjectFileImage(__NSObjectFileImage* ofi) class VIS_HIDDEN Reaper { public: - Reaper(uint32_t count, const LoadedImage** unloadables, bool* inUseArray); + struct ImageAndUse + { + const LoadedImage* li; + bool inUse; + }; + Reaper(Array& unloadables, AllImages*); 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; + Array& _unloadables; + AllImages* _allImages; 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) +Reaper::Reaper(Array& unloadables, AllImages* all) + : _unloadables(unloadables), _allImages(all), _deadCount(0) { - 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; + for (AllImages::DlopenCount& entry : _allImages->_dlopenRefCounts) { + if ( entry.refCount != 0 ) { + for (ImageAndUse& iu : _unloadables) { + if ( iu.li->loadedAddress() == entry.loadAddress ) { + iu.inUse = true; + break; + } } - } - }); + } + } } uint32_t Reaper::inUseCount() { uint32_t count = 0; - for (uint32_t i=0; i < _arrayCount; ++i) { - if ( _inUseArray[i] ) + for (ImageAndUse& iu : _unloadables) { + if ( iu.inUse ) ++count; } return count; } -void Reaper::markDependentsOf(const LoadedImage* entry) +void Reaper::markDependentsOf(const LoadedImage* li) { - 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; + li->image()->forEachDependentImage(^(uint32_t depIndex, closure::Image::LinkKind kind, closure::ImageNum depImageNum, bool& stop) { + for (ImageAndUse& iu : _unloadables) { + if ( !iu.inUse && iu.li->image()->representsImageNum(depImageNum) ) { + iu.inUse = true; + break; + } } }); } void Reaper::markDependentOfInUseImages() { - for (uint32_t i=0; i < _arrayCount; ++i) { - if ( _inUseArray[i] ) - markDependentsOf(_unloadablesArray[i]); + for (ImageAndUse& iu : _unloadables) { + if ( iu.inUse ) + markDependentsOf(iu.li); } } 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()); - } + //for (ImageAndUse& iu : _unloadables) { + // log(" in-used=%d %s\n", iu.inUse, iu.li->image()->path()); + //} } void Reaper::garbageCollect() @@ -1173,7 +959,7 @@ void Reaper::garbageCollect() lastCount = newCount; } while (countChanged); - _deadCount = _arrayCount - inUseCount(); + _deadCount = (uint32_t)_unloadables.count() - inUseCount(); } void Reaper::finalizeDeadImages() @@ -1183,13 +969,12 @@ void Reaper::finalizeDeadImages() __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] ) + for (ImageAndUse& iu : _unloadables) { + if ( iu.inUse ) 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) { + iu.li->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].addr = (char*)(iu.li->loadedAddress()) + vmOffset; rangesArray[rangesCount].length = (size_t)vmSize; ++rangesCount; } @@ -1210,7 +995,7 @@ void Reaper::finalizeDeadImages() // 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 +// Also note that this is done within the _loadedImages writer lock, so any dlopen/dlclose // on other threads are blocked while this garbage collections runs // void AllImages::garbageCollectImages() @@ -1221,68 +1006,48 @@ void AllImages::garbageCollectImages() 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; + STACK_ALLOC_ARRAY(Reaper::ImageAndUse, unloadables, _loadedImages.count()); + withReadLock(^{ + for (const LoadedImage& li : _loadedImages) { + if ( !li.image()->neverUnload() /*&& !li.neverUnload()*/ ) { + unloadables.push_back({&li, false}); + //fprintf(stderr, "unloadable[%lu] %p %s\n", unloadables.count(), li.loadedAddress(), li.image()->path()); } - }); - // make reaper object to do garbage collection and notifications - Reaper reaper(unloadableCount, unloadablesArray, unloadableInUseArray); - reaper.garbageCollect(); + } + }); + // make reaper object to do garbage collection and notifications + Reaper reaper(unloadables, this); + reaper.garbageCollect(); - // FIXME: we should sort dead images so higher level ones are terminated first + // FIXME: we should sort dead images so higher level ones are terminated first - // call cxa_finalize_ranges of dead images - reaper.finalizeDeadImages(); + // call cxa_finalize_ranges of dead images + reaper.finalizeDeadImages(); - // FIXME: call static terminators of dead images + // FIXME: call static terminators of dead images - // FIXME: DOF unregister - }); + // 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()); - //}); + //fprintf(stderr, "_loadedImages before GC removals:\n"); + //for (const LoadedImage& li : _loadedImages) { + // fprintf(stderr, " loadAddr=%p, path=%s\n", li.loadedAddress(), li.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; + // because unloadables[] points into LoadedImage we are shrinking + STACK_ALLOC_ARRAY(LoadedImage, unloadImages, _loadedImages.count()); + for (const Reaper::ImageAndUse& iu : unloadables) { + if ( !iu.inUse ) + unloadImages.push_back(*iu.li); } - 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 + // remove entries from _loadedImages + if ( !unloadImages.empty() ) { 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()); - //}); + //fprintf(stderr, "_loadedImages after GC removals:\n"); + //for (const LoadedImage& li : _loadedImages) { + // fprintf(stderr, " loadAddr=%p, path=%s\n", li.loadedAddress(), li.image()->path()); + //} } // if some other thread called GC during our work, redo GC on its behalf @@ -1293,123 +1058,51 @@ void AllImages::garbageCollectImages() -VIS_HIDDEN -const launch_cache::binary_format::Image* AllImages::messageClosured(const char* path, const char* apiName, const char* closuredErrorMessages[3], int& closuredErrorMessagesCount) +void AllImages::addLoadNotifier(NotifyFunc func) { - __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(); + // callback about already loaded images + withReadLock(^{ + for (const LoadedImage& li : _loadedImages) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)li.loadedAddress(), (uint64_t)func, 0); + log_notifications("dyld: add notifier %p called with mh=%p\n", func, li.loadedAddress()); + if ( li.image()->inDyldCache() ) + func(li.loadedAddress(), (uintptr_t)_dyldCacheSlide); + else + func(li.loadedAddress(), li.loadedAddress()->getSlide()); } - 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; - } + // add to list of functions to call about future loads + withNotifiersLock(^{ + _loadNotifiers.push_back(func); }); - return result; } -bool AllImages::imageUnloadable(const launch_cache::Image& image, const mach_header* loadAddress) const +void AllImages::addUnloadNotifier(NotifyFunc func) { - // 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; - } + // add to list of functions to call about future unloads + withNotifiersLock(^{ + _unloadNotifiers.push_back(func); }); - if ( foundAsNeverUnload ) - return false; - - return true; } -void AllImages::addLoadNotifier(NotifyFunc func) +void AllImages::addLoadNotifier(LoadNotifyFunc 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(); + withReadLock(^{ + for (const LoadedImage& li : _loadedImages) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)li.loadedAddress(), (uint64_t)func, 0); + log_notifications("dyld: add notifier %p called with mh=%p\n", func, li.loadedAddress()); + func(li.loadedAddress(), li.image()->path(), !li.image()->neverUnload()); + } }); - // 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); + withNotifiersLock(^{ + _loadNotifiers2.push_back(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) { @@ -1418,150 +1111,357 @@ void AllImages::setObjCNotifiers(_dyld_objc_notify_mapped map, _dyld_objc_notify _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]); + uint32_t maxCount = count(); + STACK_ALLOC_ARRAY(const mach_header*, mhs, maxCount); + STACK_ALLOC_ARRAY(const char*, paths, maxCount); + // don't need _mutex here because this is called when process is still single threaded + for (const LoadedImage& li : _loadedImages) { + if ( li.image()->hasObjC() ) { + paths.push_back(imagePath(li.image())); + mhs.push_back(li.loadedAddress()); + } + } + if ( !mhs.empty() ) { + (*map)((uint32_t)mhs.count(), &paths[0], &mhs[0]); + if ( log_notifications("dyld: objc-mapped-notifier called with %ld images:\n", mhs.count()) ) { + for (uintptr_t i=0; i < mhs.count(); ++i) { + log_notifications("dyld: objc-mapped: %p %s\n", mhs[i], paths[i]); } } } } -void AllImages::vmAccountingSetSuspended(bool suspend) +void AllImages::applyInterposingToDyldCache(const closure::Closure* closure) { -#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); + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0, 0, 0); + const uintptr_t cacheStart = (uintptr_t)_dyldCacheAddress; + __block closure::ImageNum lastCachedDylibImageNum = 0; + __block const closure::Image* lastCachedDylibImage = nullptr; + __block bool suspendedAccounting = false; + closure->forEachPatchEntry(^(const closure::Closure::PatchEntry& entry) { + if ( entry.overriddenDylibInCache != lastCachedDylibImageNum ) { + lastCachedDylibImage = closure::ImageArray::findImage(imagesArrays(), entry.overriddenDylibInCache); + assert(lastCachedDylibImage != nullptr); + lastCachedDylibImageNum = entry.overriddenDylibInCache; + } + if ( !suspendedAccounting ) { + Loader::vmAccountingSetSuspended(true, log_fixups); + suspendedAccounting = true; + } + uintptr_t newValue = 0; + LoadedImage foundImage; + switch ( entry.replacement.image.kind ) { + case closure::Image::ResolvedSymbolTarget::kindImage: + assert(findImageNum(entry.replacement.image.imageNum, foundImage)); + newValue = (uintptr_t)(foundImage.loadedAddress()) + (uintptr_t)entry.replacement.image.offset; + break; + case closure::Image::ResolvedSymbolTarget::kindSharedCache: + newValue = (uintptr_t)_dyldCacheAddress + (uintptr_t)entry.replacement.sharedCache.offset; + break; + case closure::Image::ResolvedSymbolTarget::kindAbsolute: + // this means the symbol was missing in the cache override dylib, so set any uses to NULL + newValue = (uintptr_t)entry.replacement.absolute.value; + break; + default: + assert(0 && "bad replacement kind"); + } + lastCachedDylibImage->forEachPatchableUseOfExport(entry.exportCacheOffset, ^(closure::Image::PatchableExport::PatchLocation patchLocation) { + uintptr_t* loc = (uintptr_t*)(cacheStart+patchLocation.cacheOffset); + #if __has_feature(ptrauth_calls) + if ( patchLocation.authenticated ) { + MachOLoaded::ChainedFixupPointerOnDisk fixupInfo; + fixupInfo.authRebase.auth = true; + fixupInfo.authRebase.addrDiv = patchLocation.usesAddressDiversity; + fixupInfo.authRebase.diversity = patchLocation.discriminator; + fixupInfo.authRebase.key = patchLocation.key; + *loc = fixupInfo.signPointer(loc, newValue + patchLocation.getAddend()); + log_fixups("dyld: cache fixup: *%p = %p (JOP: diversity 0x%04X, addr-div=%d, key=%s)\n", + loc, (void*)*loc, patchLocation.discriminator, patchLocation.usesAddressDiversity, patchLocation.keyName()); + return; + } #endif + log_fixups("dyld: cache fixup: *%p = 0x%0lX (dyld cache patch)\n", loc, newValue + (uintptr_t)patchLocation.getAddend()); + *loc = newValue + (uintptr_t)patchLocation.getAddend(); + }); + }); + if ( suspendedAccounting ) + Loader::vmAccountingSetSuspended(false, log_fixups); } -void AllImages::applyInterposingToDyldCache(const launch_cache::binary_format::Closure* closure, const dyld3::launch_cache::DynArray& initialImages) +void AllImages::runStartupInitialzers() { - 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; + __block bool mainExecutableInitializerNeedsToRun = true; + __block uint32_t imageIndex = 0; + while ( mainExecutableInitializerNeedsToRun ) { + __block const closure::Image* image = nullptr; + withReadLock(^{ + image = _loadedImages[imageIndex].image(); + if ( _loadedImages[imageIndex].loadedAddress()->isMainExecutable() ) + mainExecutableInitializerNeedsToRun = false; + }); + runInitialzersBottomUp(image); + ++imageIndex; + } +} + + +// Find image in _loadedImages which has ImageNum == num. +// Try indexHint first, if hint is wrong, updated it, so next use is faster. +LoadedImage AllImages::findImageNum(closure::ImageNum num, uint32_t& indexHint) +{ + __block LoadedImage copy; + withReadLock(^{ + if ( (indexHint >= _loadedImages.count()) || !_loadedImages[indexHint].image()->representsImageNum(num) ) { + indexHint = 0; + for (indexHint=0; indexHint < _loadedImages.count(); ++indexHint) { + if ( _loadedImages[indexHint].image()->representsImageNum(num) ) + break; + } + assert(indexHint < _loadedImages.count()); + } + copy = _loadedImages[indexHint]; + }); + return copy; +} + + +// Change the state of the LoadedImage in _loadedImages which has ImageNum == num. +// Only change state if current state is expectedCurrentState (atomic swap). +bool AllImages::swapImageState(closure::ImageNum num, uint32_t& indexHint, LoadedImage::State expectedCurrentState, LoadedImage::State newState) +{ + __block bool result = false; + withWriteLock(^{ + if ( (indexHint >= _loadedImages.count()) || !_loadedImages[indexHint].image()->representsImageNum(num) ) { + indexHint = 0; + for (indexHint=0; indexHint < _loadedImages.count(); ++indexHint) { + if ( _loadedImages[indexHint].image()->representsImageNum(num) ) + break; } + assert(indexHint < _loadedImages.count()); } - if ( !foundInImages ) { - launch_cache::Image img(imageData); - log_fixups("did not find loaded image to patch into cache: %s\n", img.path()); + if ( _loadedImages[indexHint].state() == expectedCurrentState ) { + _loadedImages[indexHint].setState(newState); + result = true; } }); - if ( suspendedAccounting ) - vmAccountingSetSuspended(false); + return result; +} + +// dyld3 pre-builds the order initializers need to be run (bottom up) in a list in the closure. +// This method uses that list to run all initializers. +// Because an initializer may call dlopen() and/or create threads, the _loadedImages array +// may move under us. So, never keep a pointer into it. Always reference images by ImageNum +// and use hint to make that faster in the case where the _loadedImages does not move. +void AllImages::runInitialzersBottomUp(const closure::Image* topImage) +{ + // walk closure specified initializer list, already ordered bottom up + topImage->forEachImageToInitBefore(^(closure::ImageNum imageToInit, bool& stop) { + // get copy of LoadedImage about imageToInit, but don't keep reference into _loadedImages, because it may move if initialzers call dlopen() + uint32_t indexHint = 0; + LoadedImage loadedImageCopy = findImageNum(imageToInit, indexHint); + // skip if the image is already inited, or in process of being inited (dependency cycle) + if ( (loadedImageCopy.state() == LoadedImage::State::fixedUp) && swapImageState(imageToInit, indexHint, LoadedImage::State::fixedUp, LoadedImage::State::beingInited) ) { + // tell objc to run any +load methods in image + if ( (_objcNotifyInit != nullptr) && loadedImageCopy.image()->mayHavePlusLoads() ) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)loadedImageCopy.loadedAddress(), 0, 0); + const char* path = imagePath(loadedImageCopy.image()); + log_notifications("dyld: objc-init-notifier called with mh=%p, path=%s\n", loadedImageCopy.loadedAddress(), path); + (*_objcNotifyInit)(path, loadedImageCopy.loadedAddress()); + } + + // run all initializers in image + runAllInitializersInImage(loadedImageCopy.image(), loadedImageCopy.loadedAddress()); + + // advance state to inited + swapImageState(imageToInit, indexHint, LoadedImage::State::beingInited, LoadedImage::State::inited); + } + }); +} + + +void AllImages::runLibSystemInitializer(const LoadedImage& libSystem) +{ + // run all initializers in libSystem.dylib + runAllInitializersInImage(libSystem.image(), libSystem.loadedAddress()); + + // Note: during libSystem's initialization, libdyld_initializer() is called which copies _initialImages to _loadedImages + + // mark libSystem.dylib as being inited, so later recursive-init would re-run it + for (LoadedImage& li : _loadedImages) { + if ( li.loadedAddress() == libSystem.loadedAddress() ) { + li.setState(LoadedImage::State::inited); + break; + } + } } -void AllImages::runLibSystemInitializer(const mach_header* libSystemAddress, const launch_cache::binary_format::Image* libSystemBinImage) +void AllImages::runAllInitializersInImage(const closure::Image* image, const MachOLoaded* ml) { - // run all initializers in image - launch_cache::Image libSystemImage(libSystemBinImage); - libSystemImage.forEachInitializer(libSystemAddress, ^(const void* func) { + image->forEachInitializer(ml, ^(const void* func) { Initializer initFunc = (Initializer)func; - dyld3::kdebug_trace_dyld_duration(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)func, 0, ^{ +#if __has_feature(ptrauth_calls) + initFunc = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)initFunc, 0, 0); +#endif + { + ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)ml, (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; } + log_initializers("dyld: called initialzer %p in %s\n", initFunc, image->path()); }); } -void AllImages::runInitialzersBottomUp(const mach_header* imageLoadAddress) +const MachOLoaded* AllImages::dlopen(Diagnostics& diag, const char* path, bool rtldNoLoad, bool rtldLocal, bool rtldNoDelete, bool fromOFI, const void* callerAddress) { - 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()); + // quick check if path is in shared cache and already loaded + if ( _dyldCacheAddress != nullptr ) { + uint32_t dyldCacheImageIndex; + if ( _dyldCacheAddress->hasImagePath(path, dyldCacheImageIndex) ) { + uint64_t mTime; + uint64_t inode; + const MachOLoaded* mh = (MachOLoaded*)_dyldCacheAddress->getIndexedImageEntry(dyldCacheImageIndex, mTime, inode); + // Note: we do not need readLock because this is within global dlopen lock + for (const LoadedImage& li : _loadedImages) { + if ( li.loadedAddress() == mh ) { + return mh; } - // 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); - }); + } + } + + __block closure::ImageNum callerImageNum = 0; + STACK_ALLOC_ARRAY(LoadedImage, loadedList, 1024); + for (const LoadedImage& li : _loadedImages) { + loadedList.push_back(li); + uint8_t permissions; + if ( (callerImageNum == 0) && li.image()->containsAddress(callerAddress, li.loadedAddress(), &permissions) ) { + callerImageNum = li.image()->imageNum(); + } + //fprintf(stderr, "mh=%p, image=%p, imageNum=0x%04X, path=%s\n", li.loadedAddress(), li.image(), li.image()->imageNum(), li.image()->path()); + } + uintptr_t alreadyLoadedCount = loadedList.count(); + + // make closure + closure::ImageNum topImageNum = 0; + const closure::DlopenClosure* newClosure; + + // First try with closures from the shared cache permitted. + // Then try again with forcing a new closure + for (bool canUseSharedCacheClosure : { true, false }) { + closure::FileSystemPhysical fileSystem; + closure::ClosureBuilder::AtPath atPathHanding = (_allowAtPaths ? closure::ClosureBuilder::AtPath::all : closure::ClosureBuilder::AtPath::onlyInRPaths); + closure::ClosureBuilder cb(_nextImageNum, fileSystem, _dyldCacheAddress, true, closure::gPathOverrides, atPathHanding); + newClosure = cb.makeDlopenClosure(path, _mainClosure, loadedList, callerImageNum, rtldNoLoad, canUseSharedCacheClosure, &topImageNum); + if ( newClosure == closure::ClosureBuilder::sRetryDlopenClosure ) { + log_apis(" dlopen: closure builder needs to retry: %s\n", path); + assert(canUseSharedCacheClosure); + continue; + } + if ( (newClosure == nullptr) && (topImageNum == 0) ) { + if ( cb.diagnostics().hasError()) + diag.error("%s", cb.diagnostics().errorMessage()); + else if ( !rtldNoLoad ) + diag.error("dlopen(): file not found: %s", path); + return nullptr; + } + // save off next available ImageNum for use by next call to dlopen() + _nextImageNum = cb.nextFreeImageNum(); + break; + } + + if ( newClosure != nullptr ) { + // if new closure contains an ImageArray, add it to list + if ( const closure::ImageArray* newArray = newClosure->images() ) { + appendToImagesArray(newArray); + } + log_apis(" dlopen: made closure: %p\n", newClosure); + } + + // if already loaded, just bump refCount and return + if ( (newClosure == nullptr) && (topImageNum != 0) ) { + for (LoadedImage& li : _loadedImages) { + if ( li.image()->imageNum() == topImageNum ) { + // is already loaded + const MachOLoaded* topLoadAddress = li.loadedAddress(); + if ( !li.image()->inDyldCache() ) + incRefCount(topLoadAddress); + log_apis(" dlopen: already loaded as '%s'\n", li.image()->path()); + // if previously opened with RTLD_LOCAL, but now opened with RTLD_GLOBAL, unhide it + if ( !rtldLocal && li.hideFromFlatSearch() ) + li.setHideFromFlatSearch(false); + // if called with RTLD_NODELETE, mark it as never-unload + if ( rtldNoDelete ) + li.markLeaveMapped(); + return topLoadAddress; + } + } + } + + // run loader to load all new images + Loader loader(loadedList, _dyldCacheAddress, imagesArrays(), &dyld3::log_loads, &dyld3::log_segments, &dyld3::log_fixups, &dyld3::log_dofs); + const closure::Image* topImage = closure::ImageArray::findImage(imagesArrays(), topImageNum); + if ( newClosure == nullptr ) { + if ( topImageNum < dyld3::closure::kLastDyldCacheImageNum ) + log_apis(" dlopen: using image in dyld shared cache %p\n", topImage); + else + log_apis(" dlopen: using pre-built dlopen closure %p\n", topImage); + } + uintptr_t topIndex = loadedList.count(); + LoadedImage topLoadedImage = LoadedImage::make(topImage); + if ( rtldLocal && !topImage->inDyldCache() ) + topLoadedImage.setHideFromFlatSearch(true); + if ( rtldNoDelete && !topImage->inDyldCache() ) + topLoadedImage.markLeaveMapped(); + loader.addImage(topLoadedImage); + + + // recursively load all dependents and fill in allImages array + loader.completeAllDependents(diag, topIndex); + if ( diag.hasError() ) + return nullptr; + loader.mapAndFixupAllImages(diag, _processDOFs, fromOFI, topIndex); + if ( diag.hasError() ) + return nullptr; + + const MachOLoaded* topLoadAddress = loadedList[topIndex].loadedAddress(); + + // bump dlopen refcount of image directly loaded + if ( !topImage->inDyldCache() ) + incRefCount(topLoadAddress); + + // tell gAllImages about new images + const uint32_t newImageCount = (uint32_t)(loadedList.count() - alreadyLoadedCount); + addImages(loadedList.subArray(alreadyLoadedCount, newImageCount)); + + // if closure adds images that override dyld cache, patch cache + if ( newClosure != nullptr ) + applyInterposingToDyldCache(newClosure); + + runImageNotifiers(loadedList.subArray(alreadyLoadedCount, newImageCount)); + + // run initializers + runInitialzersBottomUp(topImage); + + return topLoadAddress; +} + +void AllImages::appendToImagesArray(const closure::ImageArray* newArray) +{ + _imagesArrays.push_back(newArray); } +const Array& AllImages::imagesArrays() +{ + return _imagesArrays.array(); +} + +bool AllImages::isRestricted() const +{ + return !_allowEnvPaths; +} + + + } // namespace dyld3 diff --git a/dyld3/AllImages.h b/dyld3/AllImages.h index 47d0655..9880773 100644 --- a/dyld3/AllImages.h +++ b/dyld3/AllImages.h @@ -25,25 +25,27 @@ #ifndef __ALL_IMAGES_H__ #define __ALL_IMAGES_H__ -#include #include +#include +#include #include "dyld_priv.h" -#include "LaunchCache.h" +#include "Closure.h" #include "Loading.h" - +#include "MachOLoaded.h" +#include "DyldSharedCache.h" #if __MAC_OS_X_VERSION_MIN_REQUIRED // only in macOS and deprecated -struct VIS_HIDDEN __NSObjectFileImage +struct VIS_HIDDEN OFIInfo { - 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; + const char* path; // = nullptr; + const void* memSource; // = nullptr; + size_t memLength; // = 0; + const dyld3::MachOLoaded* loadAddress; // = nullptr; + uint64_t imageNum; // = 0; }; #endif @@ -52,53 +54,48 @@ 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); + typedef void (*NotifyFunc)(const mach_header* mh, intptr_t slide); + typedef void (*LoadNotifyFunc)(const mach_header* mh, const char* path, bool unloadable); + + void init(const closure::LaunchClosure* closure, const DyldSharedCache* dyldCacheLoadAddress, const char* dyldCachePath, + const Array& initialImages); + void setRestrictions(bool allowAtPaths, bool allowEnvPaths); 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(); + void addImages(const Array& newImages); + void removeImages(const Array& unloadImages); + void runImageNotifiers(const Array& newImages); + void applyInterposingToDyldCache(const closure::Closure* closure); + void runStartupInitialzers(); + void runInitialzersBottomUp(const closure::Image* topImage); + void runLibSystemInitializer(const LoadedImage& libSystem); 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; + + void forEachImage(void (^handler)(const LoadedImage& loadedImage, bool& stop)) const; + const MachOLoaded* findDependent(const MachOLoaded* mh, uint32_t depIndex); + void visitDependentsTopDown(const LoadedImage& start, void (^handler)(const LoadedImage& aLoadedImage, bool& stop)) const; + void infoForImageMappedAt(const void* addr, void (^handler)(const LoadedImage& foundImage, uint8_t permissions)) const; + bool infoForImageMappedAt(const void* addr, const MachOLoaded** ml, uint64_t* textSize, const char** path) const; + void infoForNonCachedImageMappedAt(const void* addr, void (^handler)(const LoadedImage& foundImage, uint8_t permissions)) const; + void infoForImageWithLoadAddress(const MachOLoaded*, void (^handler)(const LoadedImage& foundImage)) const; + const char* pathForImageMappedAt(const void* addr) const; + const char* imagePathByIndex(uint32_t index) const; + const mach_header* imageLoadAddressByIndex(uint32_t index) const; + bool immutableMemory(const void* addr, size_t length) const; + + bool isRestricted() const; + const MachOLoaded* mainExecutable() const; + const closure::Image* mainExecutableImage() const; const void* cacheLoadAddress() const { return _dyldCacheAddress; } const char* dyldCachePath() const { return _dyldCachePath; } - const char* imagePath(const BinaryImage*) const; + bool dyldCacheHasPath(const char* path) const; + const char* imagePath(const closure::Image*) const; + dyld_platform_t platform() 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); + const Array& imagesArrays(); - bool imageUnloadable(const launch_cache::Image& image, const mach_header* loadAddress) const; void incRefCount(const mach_header* loadAddress); void decRefCount(const mach_header* loadAddress); @@ -106,22 +103,24 @@ public: 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 addLoadNotifier(LoadNotifyFunc); - 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); + void notifyMonitorLoads(const Array& newImages); + void notifyMonitorUnloads(const Array& unloadingImages); #if __MAC_OS_X_VERSION_MIN_REQUIRED - __NSObjectFileImage* addNSObjectFileImage(); - bool hasNSObjectFileImage(__NSObjectFileImage*); - void removeNSObjectFileImage(__NSObjectFileImage*); + NSObjectFileImage addNSObjectFileImage(const OFIInfo&); + void removeNSObjectFileImage(NSObjectFileImage); + bool forNSObjectFileImage(NSObjectFileImage imageHandle, + void (^handler)(OFIInfo& image)); #endif + const MachOLoaded* dlopen(Diagnostics& diag, const char* path, bool rtldNoLoad, bool rtldLocal, bool rtldNoDelete, bool fromOFI, const void* callerAddress); + struct ProgramVars { const void* mh; @@ -133,18 +132,42 @@ public: void setProgramVars(ProgramVars* vars); private: + friend class Reaper; + + struct DlopenCount { + const mach_header* loadAddress; + uintptr_t refCount; + }; + typedef void (*Initializer)(int argc, const char* argv[], char* envp[], const char* apple[], const ProgramVars* vars); - typedef const launch_cache::DynArray StartImageArray; + typedef const Array StartImageArray; - void runInitialzersInImage(const mach_header* imageLoadAddress, const launch_cache::binary_format::Image* binImage); + void runInitialzersInImage(const mach_header* imageLoadAddress, const closure::Image* image); void mirrorToOldAllImageInfos(); void garbageCollectImages(); - void vmAccountingSetSuspended(bool suspend); - void copyCurrentGroupsNoLock(ImageGroupList& groups) const; - - const BinaryClosure* _mainClosure = nullptr; - const void* _dyldCacheAddress = nullptr; + void breadthFirstRecurseDependents(Array& visited, const LoadedImage& nodeLi, bool& stop, void (^handler)(const LoadedImage& aLoadedImage, bool& stop)) const; + void appendToImagesArray(const closure::ImageArray* newArray); + void withReadLock(void (^work)()) const; + void withWriteLock(void (^work)()); + void withNotifiersLock(void (^work)()) const; + bool findImage(const mach_header* loadAddress, LoadedImage& foundImage) const; + bool findImageNum(closure::ImageNum imageNum, LoadedImage& foundImage) const; + LoadedImage findImageNum(closure::ImageNum num, uint32_t& indexHint); + bool swapImageState(closure::ImageNum num, uint32_t& indexHint, LoadedImage::State expectedCurrentState, LoadedImage::State newState); + void runAllInitializersInImage(const closure::Image* image, const MachOLoaded* ml); + void recomputeBounds(); + + void constructMachPorts(int slot); + void teardownMachPorts(int slot); + void forEachPortSlot(void (^callback)(int slot)); + void sendMachMessage(int slot, mach_msg_id_t msg_id, mach_msg_header_t* msg_buffer, mach_msg_size_t msg_size); + void notifyMonitoringDyld(bool unloading, const Array& images); + + typedef closure::ImageArray ImageArray; + + const closure::LaunchClosure* _mainClosure = nullptr; + const DyldSharedCache* _dyldCacheAddress = nullptr; const char* _dyldCachePath = nullptr; uint64_t _dyldCacheSlide = 0; StartImageArray* _initialImages = nullptr; @@ -155,10 +178,32 @@ private: ProgramVars* _programVars = nullptr; dyld_all_image_infos* _oldAllImageInfos = nullptr; dyld_image_info* _oldAllImageArray = nullptr; + dyld_platform_t _platform = 0; uint32_t _oldArrayAllocCount = 0; - pthread_mutex_t _initializerLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; - pthread_cond_t _initializerCondition= PTHREAD_COND_INITIALIZER; + closure::ImageNum _nextImageNum = 0; int32_t _gcCount = 0; + bool _processDOFs = false; + bool _allowAtPaths = false; + bool _allowEnvPaths = false; + uintptr_t _lowestNonCached = 0; + uintptr_t _highestNonCached = UINTPTR_MAX; +#ifdef OS_UNFAIR_RECURSIVE_LOCK_INIT + mutable os_unfair_recursive_lock _loadImagesLock = OS_UNFAIR_RECURSIVE_LOCK_INIT; + mutable os_unfair_recursive_lock _notifiersLock = OS_UNFAIR_RECURSIVE_LOCK_INIT; +#else + mutable pthread_mutex_t _loadImagesLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; + mutable pthread_mutex_t _notifiersLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; +#endif + GrowableArray _imagesArrays; + GrowableArray _loadNotifiers; + GrowableArray _unloadNotifiers; + GrowableArray _loadNotifiers2; + GrowableArray _dlopenRefCounts; + GrowableArray _loadedImages; +#if __MAC_OS_X_VERSION_MIN_REQUIRED + uint64_t _nextObjectFileImageNum = 0; + GrowableArray _objectFileImages; +#endif }; extern AllImages gAllImages; diff --git a/dyld3/Array.h b/dyld3/Array.h new file mode 100644 index 0000000..5ff120f --- /dev/null +++ b/dyld3/Array.h @@ -0,0 +1,264 @@ +/* + * 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 Array_h +#define Array_h + +#include +#include +#include +#include + +#define VIS_HIDDEN __attribute__((visibility("hidden"))) + +namespace dyld3 { + + +// +// Similar to std::vector<> but storage is pre-allocated and cannot be re-allocated. +// Storage is normally stack allocated. +// +// Use push_back() to add elements and range based for loops to iterate and [] to access by index. +// +template +class VIS_HIDDEN Array +{ +public: + Array() : _elements(nullptr), _allocCount(0), _usedCount(0) {} + Array(T* storage, uintptr_t allocCount, uintptr_t usedCount=0) : _elements(storage), _allocCount(allocCount), _usedCount(usedCount) {} + void setInitialStorage(T* storage, uintptr_t allocCount) { assert(_usedCount == 0); _elements=storage; _allocCount=allocCount; } + + T& operator[](size_t idx) { assert(idx < _usedCount); return _elements[idx]; } + const T& operator[](size_t idx) const { assert(idx < _usedCount); return _elements[idx]; } + T& back() { assert(_usedCount > 0); return _elements[_usedCount-1]; } + uintptr_t count() const { return _usedCount; } + uintptr_t maxCount() const { return _allocCount; } + uintptr_t freeCount() const { return _allocCount - _usedCount; } + bool empty() const { return (_usedCount == 0); } + uintptr_t index(const T& element) { return &element - _elements; } + void push_back(const T& t) { assert(_usedCount < _allocCount); _elements[_usedCount++] = t; } + void pop_back() { assert(_usedCount > 0); _usedCount--; } + T* begin() { return &_elements[0]; } + T* end() { return &_elements[_usedCount]; } + const T* begin() const { return &_elements[0]; } + const T* end() const { return &_elements[_usedCount]; } + const Array subArray(uintptr_t start, uintptr_t size) const { assert(start+size <= _usedCount); + return Array(&_elements[start], size, size); } + bool contains(const T& targ) const { for (const T& a : *this) { if ( a == targ ) return true; } return false; } + void remove(size_t idx) { assert(idx < _usedCount); ::memmove(&_elements[idx], &_elements[idx+1], sizeof(T)*(_usedCount-idx-1)); } + +protected: + T* _elements; + uintptr_t _allocCount; + uintptr_t _usedCount; +}; + + +// If an Array<>.setInitialStorage() is used, the array may out live the stack space of the storage. +// To allow cleanup to be done to array elements when the stack goes away, you can make a local +// variable of ArrayFinalizer<>. +template +class VIS_HIDDEN ArrayFinalizer +{ +public: + typedef void (^CleanUp)(T& element); + ArrayFinalizer(Array& array, CleanUp handler) : _array(array), _handler(handler) { } + ~ArrayFinalizer() { for(T& element : _array) _handler(element); } +private: + Array& _array; + CleanUp _handler; +}; + + + +// +// Similar to Array<> but if the array overflows, it is re-allocated using vm_allocate(). +// When the variable goes out of scope, any vm_allocate()ed storage is released. +// if MAXCOUNT is specified, then only one one vm_allocate() to that size is done. +// +template +class VIS_HIDDEN OverflowSafeArray : public Array +{ +public: + OverflowSafeArray() : Array(nullptr, 0) {} + OverflowSafeArray(T* stackStorage, uintptr_t stackAllocCount) : Array(stackStorage, stackAllocCount) {} + ~OverflowSafeArray(); + + void push_back(const T& t) { verifySpace(1); this->_elements[this->_usedCount++] = t; } + void clear() { this->_usedCount = 0; } + void reserve(uintptr_t n) { if (this->_allocCount < n) growTo(n); } + +protected: + void growTo(uintptr_t n); + void verifySpace(uintptr_t n) { if (this->_usedCount+n > this->_allocCount) growTo(this->_usedCount + n); } + +private: + vm_address_t _overflowBuffer = 0; + vm_size_t _overflowBufferSize = 0; +}; + + +template +inline void OverflowSafeArray::growTo(uintptr_t n) +{ + vm_address_t oldBuffer = _overflowBuffer; + vm_size_t oldBufferSize = _overflowBufferSize; + if ( MAXCOUNT != 0xFFFFFFFF ) { + assert(oldBufferSize == 0); // only re-alloc once + // MAXCOUNT is specified, so immediately jump to that size + _overflowBufferSize = round_page(MAXCOUNT * sizeof(T)); + } + else { + // MAXCOUNT is not specified, keep doubling size + _overflowBufferSize = round_page(std::max(this->_allocCount * 2, n) * sizeof(T)); + } + assert(::vm_allocate(mach_task_self(), &_overflowBuffer, _overflowBufferSize, VM_FLAGS_ANYWHERE) == KERN_SUCCESS); + ::memcpy((void*)_overflowBuffer, this->_elements, this->_usedCount*sizeof(T)); + this->_elements = (T*)_overflowBuffer; + this->_allocCount = _overflowBufferSize / sizeof(T); + + if ( oldBuffer != 0 ) + ::vm_deallocate(mach_task_self(), oldBuffer, oldBufferSize); +} + +template +inline OverflowSafeArray::~OverflowSafeArray() +{ + if ( _overflowBuffer != 0 ) + ::vm_deallocate(mach_task_self(), _overflowBuffer, _overflowBufferSize); +} + + + + +#if BUILDING_LIBDYLD +// +// Similar to std::vector<> but storage is initially allocated in the object. But if it needs to +// grow beyond, it will use malloc. The QUANT template arg is the "quantum" size for allocations. +// When the allocation needs to be grown, it is re-allocated at the required size rounded up to +// the next quantum. +// +// Use push_back() to add elements and range based for loops to iterate and [] to access by index. +// +// Note: this should be a subclass of Array but doing so disables the compiler from optimizing away static constructors +// +template +class VIS_HIDDEN GrowableArray +{ +public: + + T& operator[](size_t idx) { assert(idx < _usedCount); return _elements[idx]; } + const T& operator[](size_t idx) const { assert(idx < _usedCount); return _elements[idx]; } + T& back() { assert(_usedCount > 0); return _elements[_usedCount-1]; } + uintptr_t count() const { return _usedCount; } + uintptr_t maxCount() const { return _allocCount; } + bool empty() const { return (_usedCount == 0); } + uintptr_t index(const T& element) { return &element - _elements; } + void push_back(const T& t) { verifySpace(1); _elements[_usedCount++] = t; } + void append(const Array& a); + void pop_back() { assert(_usedCount > 0); _usedCount--; } + T* begin() { return &_elements[0]; } + T* end() { return &_elements[_usedCount]; } + const T* begin() const { return &_elements[0]; } + const T* end() const { return &_elements[_usedCount]; } + const Array subArray(uintptr_t start, uintptr_t size) const { assert(start+size <= _usedCount); + return Array(&_elements[start], size, size); } + const Array& array() const { return *((Array*)this); } + bool contains(const T& targ) const { for (const T& a : *this) { if ( a == targ ) return true; } return false; } + void erase(T& targ); + +protected: + void growTo(uintptr_t n); + void verifySpace(uintptr_t n) { if (this->_usedCount+n > this->_allocCount) growTo(this->_usedCount + n); } + +private: + T* _elements = _initialAlloc; + uintptr_t _allocCount = INIT; + uintptr_t _usedCount = 0; + T _initialAlloc[INIT] = { }; +}; + + +template +inline void GrowableArray::growTo(uintptr_t n) +{ + uintptr_t newCount = (n + QUANT - 1) & (-QUANT); + T* newArray = (T*)::malloc(sizeof(T)*newCount); + T* oldArray = this->_elements; + if ( this->_usedCount != 0 ) + ::memcpy(newArray, oldArray, sizeof(T)*this->_usedCount); + this->_elements = newArray; + this->_allocCount = newCount; + if ( oldArray != this->_initialAlloc ) + ::free(oldArray); +} + +template +inline void GrowableArray::append(const Array& a) +{ + verifySpace(a.count()); + ::memcpy(&_elements[_usedCount], a.begin(), a.count()*sizeof(T)); + _usedCount += a.count(); +} + +template +inline void GrowableArray::erase(T& targ) +{ + intptr_t index = &targ - _elements; + assert(index >= 0); + assert(index < (intptr_t)_usedCount); + intptr_t moveCount = _usedCount-index-1; + if ( moveCount > 0 ) + ::memcpy(&_elements[index], &_elements[index+1], moveCount*sizeof(T)); + _usedCount -= 1; +} + +#endif // BUILDING_LIBDYLD + + + +// STACK_ALLOC_ARRAY(foo, myarray, 10); +// myarray is of type Array +#define STACK_ALLOC_ARRAY(_type, _name, _count) \ + uintptr_t __##_name##_array_alloc[1 + ((sizeof(_type)*(_count))/sizeof(uintptr_t))]; \ + __block dyld3::Array<_type> _name((_type*)__##_name##_array_alloc, _count); + + +// STACK_ALLOC_OVERFLOW_SAFE_ARRAY(foo, myarray, 10); +// myarray is of type OverflowSafeArray +#define STACK_ALLOC_OVERFLOW_SAFE_ARRAY(_type, _name, _count) \ + uintptr_t __##_name##_array_alloc[1 + ((sizeof(_type)*(_count))/sizeof(uintptr_t))]; \ + __block dyld3::OverflowSafeArray<_type> _name((_type*)__##_name##_array_alloc, _count); + + +// work around compiler bug where: +// __block type name[count]; +// is not accessible in a block +#define BLOCK_ACCCESSIBLE_ARRAY(_type, _name, _count) \ + _type __##_name##_array_alloc[_count]; \ + _type* _name = __##_name##_array_alloc; + + +} // namespace dyld3 + +#endif /* Array_h */ diff --git a/dyld3/Closure.cpp b/dyld3/Closure.cpp new file mode 100644 index 0000000..7a28ae1 --- /dev/null +++ b/dyld3/Closure.cpp @@ -0,0 +1,963 @@ +/* + * 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 "Closure.h" +#include "MachOFile.h" +#include "MachOLoaded.h" + + +namespace dyld { + extern void log(const char* format, ...) __attribute__((format(printf, 1, 2))); +} + +namespace dyld3 { +namespace closure { + + +//////////////////////////// TypedBytes //////////////////////////////////////// + +const void* TypedBytes::payload() const +{ + return (uint8_t*)this + sizeof(TypedBytes); +} + +void* TypedBytes::payload() +{ + return (uint8_t*)this + sizeof(TypedBytes); +} + + +//////////////////////////// ContainerTypedBytes //////////////////////////////////////// + +const TypedBytes* ContainerTypedBytes::first() const +{ + return (TypedBytes*)payload(); +} + +const TypedBytes* ContainerTypedBytes::next(const TypedBytes* p) const +{ + assert((p->payloadLength & 0x3) == 0); + return (TypedBytes*)((uint8_t*)(p->payload()) + p->payloadLength); +} + +void ContainerTypedBytes::forEachAttribute(void (^handler)(const TypedBytes* typedBytes, bool& stop)) const +{ + assert(((long)this & 0x3) == 0); + const TypedBytes* end = next(this); + bool stop = false; + for (const TypedBytes* p = first(); p < end && !stop; p = next(p)) { + handler(p, stop); + } +} + +void ContainerTypedBytes::forEachAttributePayload(Type requestedType, void (^handler)(const void* payload, uint32_t size, bool& stop)) const +{ + forEachAttribute(^(const TypedBytes* typedBytes, bool& stop) { + if ( (Type)(typedBytes->type) != requestedType ) + return; + handler(typedBytes->payload(), typedBytes->payloadLength, stop); + }); +} + +const void* ContainerTypedBytes::findAttributePayload(Type requestedType, uint32_t* payloadSize) const +{ + assert(((long)this & 0x3) == 0); + if ( payloadSize != nullptr ) + *payloadSize = 0; + const TypedBytes* end = next(this); + bool stop = false; + for (const TypedBytes* p = first(); p < end && !stop; p = next(p)) { + if ( (Type)(p->type) == requestedType ) { + if ( payloadSize != nullptr ) + *payloadSize = p->payloadLength; + return p->payload(); + } + } + return nullptr; +} + + +//////////////////////////// Image //////////////////////////////////////// + +const Image::Flags& Image::getFlags() const +{ + return *(Flags*)((uint8_t*)this + 2*sizeof(TypedBytes)); +} + +bool Image::isInvalid() const +{ + return getFlags().isInvalid; +} + +size_t Image::size() const +{ + return sizeof(TypedBytes) + this->payloadLength; +} + +ImageNum Image::imageNum() const +{ + return getFlags().imageNum; +} + +// returns true iff 'num' is this image's ImageNum, or this image overrides that imageNum (in dyld cache) +bool Image::representsImageNum(ImageNum num) const +{ + const Flags& flags = getFlags(); + if ( flags.imageNum == num ) + return true; + if ( !flags.isDylib ) + return false; + if ( flags.inDyldCache ) + return false; + ImageNum cacheImageNum; + if ( isOverrideOfDyldCacheImage(cacheImageNum) ) + return (cacheImageNum == num); + return false; +} + +uint32_t Image::maxLoadCount() const +{ + return getFlags().maxLoadCount; +} + +bool Image::isBundle() const +{ + return getFlags().isBundle; +} + +bool Image::isDylib() const +{ + return getFlags().isDylib; +} + +bool Image::isExecutable() const +{ + return getFlags().isExecutable; +} + +bool Image::hasObjC() const +{ + return getFlags().hasObjC; +} + +bool Image::is64() const +{ + return getFlags().is64; +} + +bool Image::hasWeakDefs() const +{ + return getFlags().hasWeakDefs; +} + +bool Image::mayHavePlusLoads() const +{ + return getFlags().mayHavePlusLoads; +} + +bool Image::neverUnload() const +{ + return getFlags().neverUnload; +} + +bool Image::overridableDylib() const +{ + return getFlags().overridableDylib; +} + +bool Image::inDyldCache() const +{ + return getFlags().inDyldCache; +} + +const char* Image::path() const +{ + // might be multiple pathWithHash enties, first is canonical name + const PathAndHash* result = (PathAndHash*)findAttributePayload(Type::pathWithHash); + assert(result && "Image missing pathWithHash"); + return result->path; +} + +const char* Image::leafName() const +{ + uint32_t size; + // might be multiple pathWithHash enties, first is canonical name + const PathAndHash* result = (PathAndHash*)findAttributePayload(Type::pathWithHash, &size); + assert(result && "Image missing pathWithHash"); + for (const char* p=(char*)result + size; p > result->path; --p) { + if ( *p == '/' ) + return p+1; + } + return result->path; +} + +bool Image::hasFileModTimeAndInode(uint64_t& inode, uint64_t& mTime) const +{ + uint32_t size; + const FileInfo* info = (FileInfo*)(findAttributePayload(Type::fileInodeAndTime, &size)); + if ( info != nullptr ) { + assert(size == sizeof(FileInfo)); + inode = info->inode; + mTime = info->modTime; + return true; + } + return false; +} + +bool Image::hasCdHash(uint8_t cdHash[20]) const +{ + uint32_t size; + const uint8_t* bytes = (uint8_t*)(findAttributePayload(Type::cdHash, &size)); + if ( bytes != nullptr ) { + assert(size == 20); + memcpy(cdHash, bytes, 20); + return true; + } + return false; +} + +bool Image::getUuid(uuid_t uuid) const +{ + uint32_t size; + const uint8_t* bytes = (uint8_t*)(findAttributePayload(Type::uuid, &size)); + if ( bytes == nullptr ) + return false; + assert(size == 16); + memcpy(uuid, bytes, 16); + return true; +} + +bool Image::hasCodeSignature(uint32_t& sigFileOffset, uint32_t& sigSize) const +{ + uint32_t sz; + const Image::CodeSignatureLocation* sigInfo = (Image::CodeSignatureLocation*)(findAttributePayload(Type::codeSignLoc, &sz)); + if ( sigInfo != nullptr ) { + assert(sz == sizeof(Image::CodeSignatureLocation)); + sigFileOffset = sigInfo->fileOffset; + sigSize = sigInfo->fileSize; + return true; + } + return false; +} + +bool Image::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const +{ + uint32_t sz; + const Image::FairPlayRange* fpInfo = (Image::FairPlayRange*)(findAttributePayload(Type::fairPlayLoc, &sz)); + if ( fpInfo != nullptr ) { + assert(sz == sizeof(Image::FairPlayRange)); + textOffset = fpInfo->textStartPage * pageSize(); + size = fpInfo->textPageCount * pageSize(); + return true; + } + return false; +} + +const Array Image::dependentsArray() const +{ + uint32_t size; + LinkedImage* dependents = (LinkedImage*)findAttributePayload(Type::dependents, &size); + assert((size % sizeof(LinkedImage)) == 0); + uintptr_t count = size / sizeof(LinkedImage); + return Array(dependents, count, count); +} + +void Image::forEachDependentImage(void (^handler)(uint32_t dependentIndex, LinkKind kind, ImageNum imageNum, bool& stop)) const +{ + uint32_t size; + const LinkedImage* dependents = (LinkedImage*)findAttributePayload(Type::dependents, &size); + assert((size % sizeof(LinkedImage)) == 0); + const uint32_t count = size / sizeof(LinkedImage); + bool stop = false; + for (uint32_t i=0; (i < count) && !stop; ++i) { + LinkKind kind = dependents[i].kind(); + ImageNum imageNum = dependents[i].imageNum(); + // ignore missing weak links + if ( (imageNum == kMissingWeakLinkedImage) && (kind == LinkKind::weak) ) + continue; + handler(i, kind, imageNum, stop); + } +} + +ImageNum Image::dependentImageNum(uint32_t depIndex) const +{ + uint32_t size; + const LinkedImage* dependents = (LinkedImage*)findAttributePayload(Type::dependents, &size); + assert((size % sizeof(LinkedImage)) == 0); + const uint32_t count = size / sizeof(LinkedImage); + assert(depIndex < count); + return dependents[depIndex].imageNum(); +} + + +uint32_t Image::hashFunction(const char* str) +{ + uint32_t h = 0; + for (const char* s=str; *s != '\0'; ++s) + h = h*5 + *s; + return h; +} + +void Image::forEachAlias(void (^handler)(const char* aliasPath, bool& stop)) const +{ + __block bool foundFirst = false; + forEachAttribute(^(const TypedBytes* typedBytes, bool& stopLoop) { + if ( (Type)(typedBytes->type) != Type::pathWithHash ) + return; + if ( foundFirst ) { + const PathAndHash* aliasInfo = (PathAndHash*)typedBytes->payload(); + handler(aliasInfo->path, stopLoop); + } + else { + foundFirst = true; + } + }); +} + +bool Image::hasPathWithHash(const char* path, uint32_t hash) const +{ + __block bool found = false; + forEachAttribute(^(const TypedBytes* typedBytes, bool& stop) { + if ( (Type)(typedBytes->type) != Type::pathWithHash ) + return; + const PathAndHash* pathInfo = (PathAndHash*)typedBytes->payload(); + if ( (pathInfo->hash == hash) && (strcmp(path, pathInfo->path) == 0) ) { + stop = true; + found = true; + } + }); + return found; +} + +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 +{ + uint32_t size; + const DiskSegment* segments = (DiskSegment*)findAttributePayload(Type::diskSegment, &size); + assert(segments != nullptr); + assert((size % sizeof(DiskSegment)) == 0); + const uint32_t count = size / sizeof(DiskSegment); + const uint32_t pageSz = pageSize(); + 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 < count; ++i) { + const DiskSegment* seg = &segments[i]; + if ( seg->filePageCount != 0 ) { + break; + } + vmOffset -= (uint64_t)seg->vmPageCount * pageSz; + } + // walk each segment and call handler + bool stop = false; + for (uint32_t i=0; i < count && !stop; ++i) { + const DiskSegment* seg = &segments[i]; + uint64_t vmSize = (uint64_t)seg->vmPageCount * pageSz; + uint32_t fileSize = seg->filePageCount * pageSz; + if ( !seg->paddingNotSeg ) { + handler(segIndex, ( fileSize == 0) ? 0 : fileOffset, fileSize, vmOffset, vmSize, seg->permissions, stop); + ++segIndex; + } + vmOffset += vmSize; + fileOffset += fileSize; + } +} + +uint32_t Image::pageSize() const +{ + if ( getFlags().has16KBpages ) + return 0x4000; + else + return 0x1000; +} + +uint32_t Image::cacheOffset() const +{ + uint32_t size; + const DyldCacheSegment* segments = (DyldCacheSegment*)findAttributePayload(Type::cacheSegment, &size); + assert(segments != nullptr); + assert((size % sizeof(DyldCacheSegment)) == 0); + return segments[0].cacheOffset; +} + +void Image::forEachCacheSegment(void (^handler)(uint32_t segIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop)) const +{ + uint32_t size; + const DyldCacheSegment* segments = (DyldCacheSegment*)findAttributePayload(Type::cacheSegment, &size); + assert(segments != nullptr); + assert((size % sizeof(DyldCacheSegment)) == 0); + const uint32_t count = size / sizeof(DyldCacheSegment); + bool stop = false; + for (uint32_t i=0; i < count; ++i) { + uint64_t vmOffset = segments[i].cacheOffset - segments[0].cacheOffset; + uint64_t vmSize = segments[i].size; + uint8_t permissions = segments[i].permissions; + handler(i, vmOffset, vmSize, permissions, stop); + if ( stop ) + break; + } +} + +uint64_t Image::textSize() const +{ + __block uint64_t result = 0; + if ( inDyldCache() ) { + forEachCacheSegment(^(uint32_t segIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop) { + result = vmSize; + stop = true; + }); + } + else { + forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop) { + if ( permissions != 0) { + result = vmSize; + stop = true; + } + }); + } + return result; +} + +bool Image::containsAddress(const void* addr, const void* imageLoadAddress, uint8_t* permsResult) const +{ + __block bool result = false; + uint64_t targetAddr = (uint64_t)addr; + uint64_t imageStart = (uint64_t)imageLoadAddress; + if ( inDyldCache() ) { + forEachCacheSegment(^(uint32_t segIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop) { + if ( (targetAddr >= imageStart+vmOffset) && (targetAddr < imageStart+vmOffset+vmSize) ) { + result = true; + if ( permsResult ) + *permsResult = permissions; + stop = true; + } + }); + } + else { + forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop) { + if ( (targetAddr >= imageStart+vmOffset) && (targetAddr < imageStart+vmOffset+vmSize) ) { + result = true; + if ( permsResult ) + *permsResult = permissions; + stop = true; + } + }); + } + return result; +} + +uint64_t Image::vmSizeToMap() const +{ + uint32_t size; + const Image::MappingInfo* info = (Image::MappingInfo*)(findAttributePayload(Type::mappingInfo, &size)); + assert(info != nullptr); + assert(size == sizeof(Image::MappingInfo)); + return info->totalVmPages * pageSize(); +} + +uint64_t Image::sliceOffsetInFile() const +{ + uint32_t size; + const Image::MappingInfo* info = (Image::MappingInfo*)(findAttributePayload(Type::mappingInfo, &size)); + assert(info != nullptr); + assert(size == sizeof(Image::MappingInfo)); + return info->sliceOffsetIn4K * 0x1000; +} + +void Image::forEachInitializer(const void* imageLoadAddress, void (^handler)(const void* initializer)) const +{ + uint32_t size; + const uint32_t* inits = (uint32_t*)findAttributePayload(Type::initOffsets, &size); + if ( inits != nullptr ) { + assert((size % sizeof(uint32_t)) == 0); + const uint32_t count = size / sizeof(uint32_t); + for (uint32_t i=0; i < count; ++i) { + uint32_t offset = inits[i]; + const void* init = (void*)((uint8_t*)imageLoadAddress + offset); + handler(init); + } + } +} + +bool Image::hasInitializers() const +{ + uint32_t size; + return ( findAttributePayload(Type::initOffsets, &size) != nullptr ); +} + +void Image::forEachDOF(const void* imageLoadAddress, void (^handler)(const void* dofSection)) const +{ + uint32_t size; + const uint32_t* dofs = (uint32_t*)findAttributePayload(Type::dofOffsets, &size); + if ( dofs != nullptr ) { + assert((size % sizeof(uint32_t)) == 0); + const uint32_t count = size / sizeof(uint32_t); + for (uint32_t i=0; i < count; ++i) { + uint32_t offset = dofs[i]; + const void* sect = (void*)((uint8_t*)imageLoadAddress + offset); + handler(sect); + } + } +} + +void Image::forEachPatchableExport(void (^handler)(uint32_t cacheOffsetOfImpl, const char* exportName)) const +{ + forEachAttributePayload(Type::cachePatchInfo, ^(const void* payload, uint32_t size, bool& stop) { + const Image::PatchableExport* pe = (Image::PatchableExport*)payload; + assert(size > (sizeof(Image::PatchableExport) + pe->patchLocationsCount*sizeof(PatchableExport::PatchLocation))); + handler(pe->cacheOffsetOfImpl, (char*)(&pe->patchLocations[pe->patchLocationsCount])); + }); +} + +void Image::forEachPatchableUseOfExport(uint32_t cacheOffsetOfImpl, void (^handler)(PatchableExport::PatchLocation patchLocation)) const +{ + forEachAttributePayload(Type::cachePatchInfo, ^(const void* payload, uint32_t size, bool& stop) { + const Image::PatchableExport* pe = (Image::PatchableExport*)payload; + assert(size > (sizeof(Image::PatchableExport) + pe->patchLocationsCount*sizeof(PatchableExport::PatchLocation))); + if ( pe->cacheOffsetOfImpl != cacheOffsetOfImpl ) + return; + const PatchableExport::PatchLocation* start = pe->patchLocations; + const PatchableExport::PatchLocation* end = &start[pe->patchLocationsCount]; + for (const PatchableExport::PatchLocation* p=start; p < end; ++p) + handler(*p); + }); +} + +uint32_t Image::patchableExportCount() const +{ + __block uint32_t count = 0; + forEachAttributePayload(Type::cachePatchInfo, ^(const void* payload, uint32_t size, bool& stop) { + ++count; + }); + return count; +} + +void Image::forEachFixup(void (^rebase)(uint64_t imageOffsetToRebase, bool& stop), + void (^bind)(uint64_t imageOffsetToBind, ResolvedSymbolTarget bindTarget, bool& stop), + void (^chainedFixupsStart)(uint64_t imageOffsetStart, const Array& targets, bool& stop)) const +{ + const uint32_t pointerSize = is64() ? 8 : 4; + uint64_t curRebaseOffset = 0; + bool stop = false; + for (const Image::RebasePattern& rebasePat : rebaseFixups()) { + //fprintf(stderr, " repeat=0x%04X, contig=%d, skip=%d\n", rebasePat.repeatCount, rebasePat.contigCount, rebasePat.skipCount); + if ( rebasePat.contigCount == 0 ) { + // note: contigCount==0 means this just advances location + if ( (rebasePat.repeatCount == 0) && (rebasePat.skipCount == 0) ) { + // all zeros is special pattern that means reset to rebase offset to zero + curRebaseOffset = 0; + } + else { + curRebaseOffset += rebasePat.repeatCount * rebasePat.skipCount; + } + } + else { + for (int r=0; r < rebasePat.repeatCount && !stop; ++r) { + for (int i=0; i < rebasePat.contigCount && !stop; ++i) { + //fprintf(stderr, " 0x%08llX\n", curRebaseOffset); + rebase(curRebaseOffset, stop); + curRebaseOffset += pointerSize; + } + curRebaseOffset += pointerSize * rebasePat.skipCount; + } + } + if ( stop ) + break; + } + if ( stop ) + return; + + for (const Image::BindPattern& bindPat : bindFixups()) { + uint64_t curBindOffset = bindPat.startVmOffset; + for (uint16_t i=0; i < bindPat.repeatCount; ++i) { + bind(curBindOffset, bindPat.target, stop); + curBindOffset += (pointerSize * (1 + bindPat.skipCount)); + if ( stop ) + break; + } + if ( stop ) + break; + } + + const Array targetsArray = chainedTargets(); + for (uint64_t start : chainedStarts()) { + chainedFixupsStart(start, targetsArray, stop); + if ( stop ) + break; + } +} + +void Image::forEachChainedFixup(void* imageLoadAddress, uint64_t imageOffsetChainStart, void (^callback)(uint64_t* fixUpLoc, ChainedFixupPointerOnDisk fixupInfo, bool& stop)) +{ + bool stop = false; + uint64_t* fixupLoc = (uint64_t*)((uint8_t*)imageLoadAddress + imageOffsetChainStart); + do { + // save off current entry as it will be overwritten in callback + ChainedFixupPointerOnDisk info = *((ChainedFixupPointerOnDisk*)fixupLoc); + callback(fixupLoc, info, stop); + if ( info.plainRebase.next != 0 ) + fixupLoc += info.plainRebase.next; + else + stop = true; + } while (!stop); +} + +void Image::forEachTextReloc(void (^rebase)(uint32_t imageOffsetToRebase, bool& stop), + void (^bind)(uint32_t imageOffsetToBind, ResolvedSymbolTarget bindTarget, bool& stop)) const +{ + bool stop = false; + const Array f = textFixups(); + for (const Image::TextFixupPattern& pat : f) { + uint32_t curOffset = pat.startVmOffset; + for (uint16_t i=0; i < pat.repeatCount; ++i) { + if ( pat.target.raw == 0 ) + rebase(curOffset, stop); + else + bind(curOffset, pat.target, stop); + curOffset += pat.skipCount; + } + } +} + +const Array Image::rebaseFixups() const +{ + uint32_t rebaseFixupsSize; + Image::RebasePattern* rebaseFixupsContent = (RebasePattern*)findAttributePayload(Type::rebaseFixups, &rebaseFixupsSize); + uint32_t rebaseCount = rebaseFixupsSize/sizeof(RebasePattern); + return Array(rebaseFixupsContent, rebaseCount, rebaseCount); +} + +const Array Image::bindFixups() const +{ + uint32_t bindFixupsSize; + BindPattern* bindFixupsContent = (BindPattern*)findAttributePayload(Type::bindFixups, &bindFixupsSize); + uint32_t bindCount = bindFixupsSize/sizeof(BindPattern); + return Array(bindFixupsContent, bindCount, bindCount); +} + +const Array Image::chainedStarts() const +{ + uint32_t startsSize; + uint64_t* starts = (uint64_t*)findAttributePayload(Type::chainedFixupsStarts, &startsSize); + uint32_t count = startsSize/sizeof(uint64_t); + return Array(starts, count, count); +} + +const Array Image::chainedTargets() const +{ + uint32_t size; + ResolvedSymbolTarget* targetsContent = (ResolvedSymbolTarget*)findAttributePayload(Type::chainedFixupsTargets, &size); + uint32_t count = size/sizeof(ResolvedSymbolTarget); + return Array(targetsContent, count, count); +} + +const Array Image::textFixups() const +{ + uint32_t fixupsSize; + TextFixupPattern* fixupsContent = (TextFixupPattern*)findAttributePayload(Type::textFixups, &fixupsSize); + uint32_t count = fixupsSize/sizeof(TextFixupPattern); + return Array(fixupsContent, count, count); +} + +bool Image::isOverrideOfDyldCacheImage(ImageNum& imageNum) const +{ + uint32_t size; + const uint32_t* content = (uint32_t*)findAttributePayload(Type::imageOverride, &size); + if ( content != nullptr ) { + assert(size == sizeof(uint32_t)); + imageNum = *content; + return true; + } + return false; +} + +void Image::forEachImageToInitBefore(void (^handler)(ImageNum imageToInit, bool& stop)) const +{ + uint32_t size; + const ImageNum* initBefores = (ImageNum*)findAttributePayload(Type::initBefores, &size); + if ( initBefores != nullptr ) { + assert((size % sizeof(ImageNum)) == 0); + const uint32_t count = size / sizeof(ImageNum); + bool stop = false; + for (uint32_t i=0; (i < count) && !stop; ++i) { + handler(initBefores[i], stop); + } + } +} + +const char* Image::PatchableExport::PatchLocation::keyName() const +{ + return MachOLoaded::ChainedFixupPointerOnDisk::keyName(this->key); +} + +Image::PatchableExport::PatchLocation::PatchLocation(size_t cacheOff, uint64_t ad) + : cacheOffset(cacheOff), addend(ad), authenticated(0), usesAddressDiversity(0), key(0), discriminator(0) +{ + int64_t signedAddend = (int64_t)ad; + assert(((signedAddend << 52) >> 52) == signedAddend); +} + +Image::PatchableExport::PatchLocation::PatchLocation(size_t cacheOff, uint64_t ad, dyld3::MachOLoaded::ChainedFixupPointerOnDisk authInfo) + : cacheOffset(cacheOff), addend(ad), authenticated(authInfo.authBind.auth), usesAddressDiversity(authInfo.authBind.addrDiv), key(authInfo.authBind.key), discriminator(authInfo.authBind.diversity) +{ + int64_t signedAddend = (int64_t)ad; + assert(((signedAddend << 52) >> 52) == signedAddend); +} + +//////////////////////////// ImageArray //////////////////////////////////////// + +size_t ImageArray::size() const +{ + return sizeof(TypedBytes) + this->payloadLength; +} + +size_t ImageArray::startImageNum() const +{ + return firstImageNum; +} + +uint32_t ImageArray::imageCount() const +{ + return count; +} + +void ImageArray::forEachImage(void (^callback)(const Image* image, bool& stop)) const +{ + bool stop = false; + for (uint32_t i=0; i < count && !stop; ++i) { + const Image* image = (Image*)((uint8_t*)payload() + offsets[i]); + callback(image, stop); + if (stop) + break; + } +} + +bool ImageArray::hasPath(const char* path, ImageNum& num) const +{ + const uint32_t hash = Image::hashFunction(path); + __block bool found = false; + forEachImage(^(const Image* image, bool& stop) { + if ( image->hasPathWithHash(path, hash) ) { + num = image->imageNum(); + found = true; + stop = true; + } + }); + return found; +} + +const Image* ImageArray::imageForNum(ImageNum num) const +{ + if ( num < firstImageNum ) + return nullptr; + + uint32_t index = num - firstImageNum; + if ( index >= count ) + return nullptr; + + return (Image*)((uint8_t*)payload() + offsets[index]); +} + +const Image* ImageArray::findImage(const Array imagesArrays, ImageNum imageNum) +{ + for (const ImageArray* ia : imagesArrays) { + if ( const Image* result = ia->imageForNum(imageNum) ) + return result; + } + return nullptr; +} + +//////////////////////////// Closure //////////////////////////////////////// + +size_t Closure::size() const +{ + return sizeof(TypedBytes) + this->payloadLength; +} + +const ImageArray* Closure::images() const +{ + __block const TypedBytes* result = nullptr; + forEachAttribute(^(const TypedBytes* typedBytes, bool& stop) { + if ( (Type)(typedBytes->type) == Type::imageArray ) { + result = typedBytes; + stop = true; + } + }); + + return (ImageArray*)result; +} + +ImageNum Closure::topImage() const +{ + uint32_t size; + const ImageNum* top = (ImageNum*)findAttributePayload(Type::topImage, &size); + assert(top != nullptr); + assert(size == sizeof(ImageNum)); + return *top; +} + +void Closure::forEachPatchEntry(void (^handler)(const PatchEntry& entry)) const +{ + forEachAttributePayload(Type::cacheOverrides, ^(const void* payload, uint32_t size, bool& stop) { + assert((size % sizeof(Closure::PatchEntry)) == 0); + const PatchEntry* patches = (PatchEntry*)payload; + const PatchEntry* patchesEnd = (PatchEntry*)((char*)payload + size); + for (const PatchEntry* p=patches; p < patchesEnd; ++p) + handler(*p); + }); +} + +void Closure::deallocate() const +{ + ::vm_deallocate(mach_task_self(), (long)this, size()); +} + +//////////////////////////// LaunchClosure //////////////////////////////////////// + +void LaunchClosure::forEachMustBeMissingFile(void (^handler)(const char* path, bool& stop)) const +{ + uint32_t size; + const char* paths = (const char*)findAttributePayload(Type::missingFiles, &size); + bool stop = false; + for (const char* s=paths; s < &paths[size]; ++s) { + if ( *s != '\0' ) + handler(s, stop); + if ( stop ) + break; + s += strlen(s); + } +} + +bool LaunchClosure::builtAgainstDyldCache(uuid_t cacheUUID) const +{ + uint32_t size; + const uint8_t* uuidBytes = (uint8_t*)findAttributePayload(Type::dyldCacheUUID, &size); + if ( uuidBytes == nullptr ) + return false; + assert(size == sizeof(uuid_t)); + memcpy(cacheUUID, uuidBytes, sizeof(uuid_t)); + return true; +} + +const char* LaunchClosure::bootUUID() const +{ + uint32_t size; + return (char*)findAttributePayload(Type::bootUUID, &size); +} + +void LaunchClosure::forEachEnvVar(void (^handler)(const char* keyEqualValue, bool& stop)) const +{ + forEachAttributePayload(Type::envVar, ^(const void* payload, uint32_t size, bool& stop) { + handler((char*)payload, stop); + }); +} + +ImageNum LaunchClosure::libSystemImageNum() const +{ + uint32_t size; + const ImageNum* num = (ImageNum*)findAttributePayload(Type::libSystemNum, &size); + assert(num != nullptr); + assert(size == sizeof(ImageNum)); + return *num; +} + +void LaunchClosure::libDyldEntry(Image::ResolvedSymbolTarget& loc) const +{ + uint32_t size; + const Image::ResolvedSymbolTarget* data = (Image::ResolvedSymbolTarget*)findAttributePayload(Type::libDyldEntry, &size); + assert(data != nullptr); + assert(size == sizeof(Image::ResolvedSymbolTarget)); + loc = *data; +} + +bool LaunchClosure::mainEntry(Image::ResolvedSymbolTarget& mainLoc) const +{ + uint32_t size; + const Image::ResolvedSymbolTarget* data = (Image::ResolvedSymbolTarget*)findAttributePayload(Type::mainEntry, &size); + if ( data == nullptr ) + return false; + assert(size == sizeof(Image::ResolvedSymbolTarget)); + mainLoc = *data; + return true; +} + +bool LaunchClosure::startEntry(Image::ResolvedSymbolTarget& startLoc) const +{ + uint32_t size; + const Image::ResolvedSymbolTarget* data = (Image::ResolvedSymbolTarget*)findAttributePayload(Type::startEntry, &size); + if ( data == nullptr ) + return false; + assert(size == sizeof(Image::ResolvedSymbolTarget)); + startLoc = *data; + return true; +} + +const LaunchClosure::Flags& LaunchClosure::getFlags() const +{ + uint32_t size; + const Flags* flags = (Flags*)findAttributePayload(Type::closureFlags, &size); + assert(flags != nullptr && "Closure missing Flags"); + return *flags; +} + +uint32_t LaunchClosure::initialLoadCount() const +{ + return getFlags().initImageCount; +} + +bool LaunchClosure::usedAtPaths() const +{ + return getFlags().usedAtPaths; +} + +bool LaunchClosure::usedFallbackPaths() const +{ + return getFlags().usedFallbackPaths; +} + +void LaunchClosure::forEachInterposingTuple(void (^handler)(const InterposingTuple& tuple, bool& stop)) const +{ + forEachAttributePayload(Type::interposeTuples, ^(const void* payload, uint32_t size, bool& stop) { + assert((size % sizeof(InterposingTuple)) == 0); + uintptr_t count = size / sizeof(InterposingTuple); + const InterposingTuple* tuples = (InterposingTuple*)payload; + for (uint32_t i=0; i < count && !stop; ++i) { + handler(tuples[i], stop); + } + }); +} + + + +} // namespace closure +} // namespace dyld3 + + + diff --git a/dyld3/Closure.h b/dyld3/Closure.h new file mode 100644 index 0000000..40ab625 --- /dev/null +++ b/dyld3/Closure.h @@ -0,0 +1,570 @@ +/* + * 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 Closures_h +#define Closures_h + + +#include +#include +#include +#include +#include +#include + +#include "Diagnostics.h" +#include "Array.h" +#include "MachOLoaded.h" +#include "SupportedArchs.h" + +namespace dyld3 { +namespace closure { + + + +// bump this number each time binary format changes +enum { kFormatVersion = 10 }; + + +typedef uint32_t ImageNum; + +const ImageNum kFirstDyldCacheImageNum = 0x00000001; +const ImageNum kLastDyldCacheImageNum = 0x00000FFF; +const ImageNum kFirstOtherOSImageNum = 0x00001001; +const ImageNum kLastOtherOSImageNum = 0x00001FFF; +const ImageNum kFirstLaunchClosureImageNum = 0x00002000; +const ImageNum kMissingWeakLinkedImage = 0x0FFFFFFF; + + +// +// Generic typed range of bytes (similar to load commands) +// Must be 4-byte aligned +// +struct VIS_HIDDEN TypedBytes +{ + uint32_t type : 8, + payloadLength : 24; + + enum class Type { + // containers which have an overall length and TypedBytes inside their content + launchClosure = 1, // contains TypedBytes of closure attributes including imageArray + imageArray = 2, // sizeof(ImageArray) + sizeof(uint32_t)*count + size of all images + image = 3, // contains TypedBytes of image attributes + dlopenClosure = 4, // contains TypedBytes of closure attributes including imageArray + + // attributes for Images + imageFlags = 7, // sizeof(Image::Flags) + pathWithHash = 8, // len = uint32_t + length path + 1, use multiple entries for aliases + fileInodeAndTime = 9, // sizeof(FileInfo) + cdHash = 10, // 20 + uuid = 11, // 16 + mappingInfo = 12, // sizeof(MappingInfo) + diskSegment = 13, // sizeof(DiskSegment) * count + cacheSegment = 14, // sizeof(DyldCacheSegment) * count + dependents = 15, // sizeof(LinkedImage) * count + initOffsets = 16, // sizeof(uint32_t) * count + dofOffsets = 17, // sizeof(uint32_t) * count + codeSignLoc = 18, // sizeof(CodeSignatureLocation) + fairPlayLoc = 19, // sizeof(FairPlayRange) + rebaseFixups = 20, // sizeof(RebasePattern) * count + bindFixups = 21, // sizeof(BindPattern) * count + cachePatchInfo = 22, // sizeof(PatchableExport) + count*sizeof(PatchLocation) + strlen(name) // only in dyld cache Images + textFixups = 23, // sizeof(TextFixupPattern) * count + imageOverride = 24, // sizeof(ImageNum) + initBefores = 25, // sizeof(ImageNum) * count + chainedFixupsStarts = 26, // sizeof(uint64_t) * count + chainedFixupsTargets = 27, // sizeof(ResolvedSymbolTarget) * count + + // attributes for Closures (launch or dlopen) + closureFlags = 32, // sizeof(Closure::Flags) + dyldCacheUUID = 33, // 16 + missingFiles = 34, + envVar = 35, // "DYLD_BLAH=stuff" + topImage = 36, // sizeof(ImageNum) + libDyldEntry = 37, // sizeof(ResolvedSymbolTarget) + libSystemNum = 38, // sizeof(ImageNum) + bootUUID = 39, // c-string 40 + mainEntry = 40, // sizeof(ResolvedSymbolTarget) + startEntry = 41, // sizeof(ResolvedSymbolTarget) // used by programs built with crt1.o + cacheOverrides = 42, // sizeof(PatchEntry) * count // used if process uses interposing or roots (cached dylib overrides) + interposeTuples = 43, // sizeof(InterposingTuple) * count + }; + + const void* payload() const; + void* payload(); +}; + + +// +// A TypedBytes which is a bag of other TypedBytes +// +struct VIS_HIDDEN ContainerTypedBytes : TypedBytes +{ + void forEachAttribute(void (^callback)(const TypedBytes* typedBytes, bool& stop)) const; + void forEachAttributePayload(Type requestedType, void (^handler)(const void* payload, uint32_t size, bool& stop)) const; + const void* findAttributePayload(Type requestedType, uint32_t* payloadSize=nullptr) const; +private: + const TypedBytes* first() const; + const TypedBytes* next(const TypedBytes*) const; +}; + + +// +// Information about a mach-o file +// +struct VIS_HIDDEN Image : ContainerTypedBytes +{ + enum class LinkKind { regular=0, weak=1, upward=2, reExport=3 }; + + size_t size() const; + ImageNum imageNum() const; + bool representsImageNum(ImageNum num) const; // imageNum() or isOverrideOfDyldCacheImage() + uint32_t maxLoadCount() const; + const char* path() const; + const char* leafName() const; + bool getUuid(uuid_t) const; + bool isInvalid() const; + bool inDyldCache() const; + bool hasObjC() const; + bool hasInitializers() const; + bool isBundle() const; + bool isDylib() const; + bool isExecutable() const; + bool hasWeakDefs() const; + bool mayHavePlusLoads() const; + bool is64() const; + bool neverUnload() const; + bool cwdMustBeThisDir() const; + bool isPlatformBinary() const; + bool overridableDylib() const; + bool hasFileModTimeAndInode(uint64_t& inode, uint64_t& mTime) const; + bool hasCdHash(uint8_t cdHash[20]) const; + void forEachAlias(void (^handler)(const char* aliasPath, bool& stop)) const; + void forEachDependentImage(void (^handler)(uint32_t dependentIndex, LinkKind kind, ImageNum imageNum, bool& stop)) const; + ImageNum dependentImageNum(uint32_t depIndex) const; + bool containsAddress(const void* addr, const void* imageLoadAddress, uint8_t* permissions=nullptr) const; + void forEachInitializer(const void* imageLoadAddress, void (^handler)(const void* initializer)) const; + void forEachImageToInitBefore(void (^handler)(ImageNum imageToInit, bool& stop)) const; + void forEachDOF(const void* imageLoadAddress, void (^handler)(const void* initializer)) const; + bool hasPathWithHash(const char* path, uint32_t hash) const; + bool isOverrideOfDyldCacheImage(ImageNum& cacheImageNum) const; + uint64_t textSize() const; + + union ResolvedSymbolTarget + { + enum Kinds { kindRebase, kindSharedCache, kindImage, kindAbsolute }; + + struct Rebase { + uint64_t kind : 2, // kindRebase + unused : 62; // all zeros + }; + struct SharedCache { + uint64_t kind : 2, // kindSharedCache + offset : 62; + }; + struct Image { + uint64_t kind : 2, // kindImage + imageNum : 22, // ImageNum + offset : 40; + }; + struct Absolute { + uint64_t kind : 2, // kindAbsolute + value : 62; // sign extended + }; + Rebase rebase; + SharedCache sharedCache; + Image image; + Absolute absolute; + uint64_t raw; + + bool operator==(const ResolvedSymbolTarget& rhs) const { + return (raw == rhs.raw); + } + bool operator!=(const ResolvedSymbolTarget& rhs) const { + return (raw != rhs.raw); + } + }; + + typedef MachOLoaded::ChainedFixupPointerOnDisk ChainedFixupPointerOnDisk; + + // the following are only valid if inDyldCache() returns true + uint32_t cacheOffset() const; + uint32_t patchStartIndex() const; + uint32_t patchCount() const; + void forEachCacheSegment(void (^handler)(uint32_t segIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool& stop)) const; + + + // the following are only valid if inDyldCache() returns false + uint64_t vmSizeToMap() const; + uint64_t sliceOffsetInFile() const; + bool hasCodeSignature(uint32_t& fileOffset, uint32_t& size) const; + bool isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) 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 forEachFixup(void (^rebase)(uint64_t imageOffsetToRebase, bool& stop), + void (^bind)(uint64_t imageOffsetToBind, ResolvedSymbolTarget bindTarget, bool& stop), + void (^chainedFixupStart)(uint64_t imageOffsetStart, const Array& targets, bool& stop)) const; + void forEachTextReloc(void (^rebase)(uint32_t imageOffsetToRebase, bool& stop), + void (^bind)(uint32_t imageOffsetToBind, ResolvedSymbolTarget bindTarget, bool& stop)) const; + static void forEachChainedFixup(void* imageLoadAddress, uint64_t imageOffsetChainStart, + void (^chainedFixupStart)(uint64_t* fixupLoc, ChainedFixupPointerOnDisk fixupInfo, bool& stop)); + + static_assert(sizeof(ResolvedSymbolTarget) == 8, "Overflow in size of SymbolTargetLocation"); + + static uint32_t hashFunction(const char*); + + + // only in Image for cached dylibs + struct PatchableExport + { + struct PatchLocation + { + uint64_t cacheOffset : 32, + addend : 12, // +/- 2048 + authenticated : 1, + usesAddressDiversity : 1, + key : 2, + discriminator : 16; + + PatchLocation(size_t cacheOffset, uint64_t addend); + PatchLocation(size_t cacheOffset, uint64_t addend, dyld3::MachOLoaded::ChainedFixupPointerOnDisk authInfo); + + uint64_t getAddend() const { + uint64_t unsingedAddend = addend; + int64_t signedAddend = (int64_t)unsingedAddend; + signedAddend = (signedAddend << 52) >> 52; + return (uint64_t)signedAddend; + } + + const char* keyName() const; + bool operator==(const PatchLocation& other) const { + return this->cacheOffset == other.cacheOffset; + } + }; + + uint32_t cacheOffsetOfImpl; + uint32_t patchLocationsCount; + PatchLocation patchLocations[]; + // export name + }; + uint32_t patchableExportCount() const; + void forEachPatchableExport(void (^handler)(uint32_t cacheOffsetOfImpl, const char* exportName)) const; + void forEachPatchableUseOfExport(uint32_t cacheOffsetOfImpl, void (^handler)(PatchableExport::PatchLocation patchLocation)) const; + +private: + friend struct Closure; + friend class ImageWriter; + friend class ClosureBuilder; + friend class LaunchClosureWriter; + + uint32_t pageSize() const; + + struct Flags + { + uint64_t imageNum : 16, + maxLoadCount : 12, + isInvalid : 1, // an error occurred creating the info for this image + has16KBpages : 1, + is64 : 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, + isDylib : 1, + isExecutable : 1, + overridableDylib : 1, // only applicable to cached dylibs + inDyldCache : 1, + padding : 21; + }; + + const Flags& getFlags() const; + + struct PathAndHash + { + uint32_t hash; + char path[]; + }; + + // 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; + }; + + struct CodeSignatureLocation + { + uint32_t fileOffset; + uint32_t fileSize; + }; + + struct FileInfo + { + uint64_t inode; + uint64_t modTime; + }; + + struct FairPlayRange + { + uint32_t textPageCount : 28, + textStartPage : 4; + }; + + struct MappingInfo + { + uint32_t totalVmPages; + uint32_t sliceOffsetIn4K; + }; + + struct LinkedImage { + + LinkedImage() : imgNum(0), linkKind(0) { + } + LinkedImage(LinkKind k, ImageNum num) : imgNum(num), linkKind((uint32_t)k) { + assert((num & 0xC0000000) == 0); + } + + LinkKind kind() const { return (LinkKind)linkKind; } + ImageNum imageNum() const { return imgNum; } + void clearKind() { linkKind = 0; } + + bool operator==(const LinkedImage& rhs) const { + return (linkKind == rhs.linkKind) && (imgNum == rhs.imgNum); + } + bool operator!=(const LinkedImage& rhs) const { + return (linkKind != rhs.linkKind) || (imgNum != rhs.imgNum); + } + private: + uint32_t imgNum : 30, + linkKind : 2; // LinkKind + }; + + const Array dependentsArray() const; + + struct RebasePattern + { + uint32_t repeatCount : 20, + contigCount : 8, // how many contiguous pointers neeed rebasing + skipCount : 4; // how many pointers to skip between contig groups + // If contigCount == 0, then there are no rebases for this entry, + // instead it advances the rebase location by repeatCount*skipCount. + // If all fields are zero, then the rebase position is reset to the start. + // This is to support old binaries with some non-monotonically-increasing rebases. + }; + const Array rebaseFixups() const; + + struct BindPattern + { + Image::ResolvedSymbolTarget target; + uint64_t startVmOffset : 40, // max 1TB offset + skipCount : 8, + repeatCount : 16; + }; + const Array bindFixups() const; + + struct TextFixupPattern + { + Image::ResolvedSymbolTarget target; + uint32_t startVmOffset; + uint16_t repeatCount; + uint16_t skipCount; + }; + const Array textFixups() const; + + // for use with chained fixups + const Array chainedStarts() const; + const Array chainedTargets() const; + +}; + +/* + Dyld cache patching notes: + + The dyld cache needs to be patched to support interposing and dylib "roots". + + For cached dylibs overrides: + Closure build time: + 1) LoadedImages will contain the new dylib, so all symbol look ups + will naturally find new impl. Only dyld cache needs special help. + 2) LoadedImages entry will have flag for images that override cache. + 3) When setting Closure attributes, if flag is set, builder will + iterate PatchableExport entries in Image* from cache and create + a PatchEntry for each. + Runtime: + 1) [lib]dyld will iterate PatchEntry in closure and patch cache + + For interposing: + Closure build time: + 1) After Images built, if __interpose section(s) exist, builder will + build InterposingTuple entries for closure + 2) For being-built closure and launch closure, apply any InterposingTuple + to modify Image fixups before Image finalized. + 3) Builder will find PatchableExport entry that matchs stock Impl + and add PatchEntry to closure for it. + Runtime: + 1) When closure is loaded (launch or dlopen) PatchEntries are + applied (exactly once) to whole cache. + 2) For each DlopenClosure loaded, any InterposeTuples in *launch* closure + are applied to all new images in new DlopenClosure. + + For weak-def coalesing: + Closure build time: + 1) weak_bind entries are turned into -3 ordinal lookup which search through images + in load order for first def (like flat). This fixups up all images not in cache. + 2) When processing -3 ordinals, it continues past first found and if any images + past it are in dyld cache and export that same symbol, a PatchEntry is added to + closure to fix up all cached uses of that symbol. + 3) If a weak_bind has strong bit set (no fixup, just def), all images from the dyld + cache are checked to see if the export that symbol, if so, a PatchEntry is added + to the closure. + Runtime: + 1) When closure is loaded (launch or dlopen) PatchEntries are + applied (exactly once) to whole cache. + +*/ + + +// +// An array (accessible by index) list of Images +// +struct VIS_HIDDEN ImageArray : public TypedBytes +{ + size_t size() const; + size_t startImageNum() const; + uint32_t imageCount() const; + void forEachImage(void (^callback)(const Image* image, bool& stop)) const; + bool hasPath(const char* path, ImageNum& num) const; + const Image* imageForNum(ImageNum) const; + + static const Image* findImage(const Array imagesArrays, ImageNum imageNum); + +private: + friend class ImageArrayWriter; + + uint32_t firstImageNum; + uint32_t count; + uint32_t offsets[]; + // Image data +}; + + +struct InterposingTuple +{ + Image::ResolvedSymbolTarget stockImplementation; + Image::ResolvedSymbolTarget newImplementation; +}; + + +// +// Describes how dyld should load a set of mach-o files +// +struct VIS_HIDDEN Closure : public ContainerTypedBytes +{ + size_t size() const; + const ImageArray* images() const; + ImageNum topImage() const; + void deallocate() const; + + friend class ClosureWriter; + + struct PatchEntry + { + ImageNum overriddenDylibInCache; + uint32_t exportCacheOffset; + Image::ResolvedSymbolTarget replacement; + }; + void forEachPatchEntry(void (^handler)(const PatchEntry& entry)) const; +}; + + +// +// Describes how dyld should launch a main executable +// +struct VIS_HIDDEN LaunchClosure : public Closure +{ + bool builtAgainstDyldCache(uuid_t cacheUUID) const; + const char* bootUUID() const; + void forEachMustBeMissingFile(void (^handler)(const char* path, bool& stop)) const; + void forEachEnvVar(void (^handler)(const char* keyEqualValue, bool& stop)) const; + ImageNum libSystemImageNum() const; + void libDyldEntry(Image::ResolvedSymbolTarget& loc) const; + bool mainEntry(Image::ResolvedSymbolTarget& mainLoc) const; + bool startEntry(Image::ResolvedSymbolTarget& startLoc) const; + uint32_t initialLoadCount() const; + void forEachInterposingTuple(void (^handler)(const InterposingTuple& tuple, bool& stop)) const; + bool usedAtPaths() const; + bool usedFallbackPaths() const; + + +private: + friend class LaunchClosureWriter; + + struct Flags + { + uint32_t usedAtPaths : 1, + usedFallbackPaths : 1, + initImageCount : 16, + padding : 14; + }; + const Flags& getFlags() const; +}; + + + +// +// Describes how dyld should dlopen() a mach-o file +// +struct VIS_HIDDEN DlopenClosure : public Closure +{ + +}; + + + +} // namespace closure +} // namespace dyld3 + + +#endif // Closures_h + + diff --git a/dyld3/ClosureBuffer.cpp b/dyld3/ClosureBuffer.cpp deleted file mode 100644 index 0231827..0000000 --- a/dyld3/ClosureBuffer.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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 deleted file mode 100644 index c1ab2c3..0000000 --- a/dyld3/ClosureBuffer.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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/ClosureBuilder.cpp b/dyld3/ClosureBuilder.cpp new file mode 100644 index 0000000..e16760e --- /dev/null +++ b/dyld3/ClosureBuilder.cpp @@ -0,0 +1,2278 @@ +/* + * 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 "mach-o/dyld_priv.h" + +#include "ClosureWriter.h" +#include "ClosureBuilder.h" +#include "MachOAnalyzer.h" +#include "libdyldEntryVector.h" +#include "Tracing.h" + +namespace dyld3 { +namespace closure { + +const DlopenClosure* ClosureBuilder::sRetryDlopenClosure = (const DlopenClosure*)(-1); + +ClosureBuilder::ClosureBuilder(uint32_t startImageNum, const FileSystem& fileSystem, const DyldSharedCache* dyldCache, bool dyldCacheIsLive, + const PathOverrides& pathOverrides, AtPath atPathHandling, LaunchErrorInfo* errorInfo, + const char* archName, Platform platform, + const CacheDylibsBindingHandlers* handlers) + : _fileSystem(fileSystem), _dyldCache(dyldCache), _pathOverrides(pathOverrides), _archName(archName), _platform(platform), _startImageNum(startImageNum), + _handlers(handlers), _atPathHandling(atPathHandling), _launchErrorInfo(errorInfo), _dyldCacheIsLive(dyldCacheIsLive) +{ + if ( dyldCache != nullptr ) { + _dyldImageArray = dyldCache->cachedDylibsImageArray(); + if ( (dyldCache->header.otherImageArrayAddr != 0) && (dyldCache->header.progClosuresSize == 0) ) + _makingClosuresInCache = true; + } +} + + +ClosureBuilder::~ClosureBuilder() { + if ( _tempPaths != nullptr ) + PathPool::deallocate(_tempPaths); + if ( _mustBeMissingPaths != nullptr ) + PathPool::deallocate(_mustBeMissingPaths); +} + +bool ClosureBuilder::findImage(const char* loadPath, const LoadedImageChain& forImageChain, BuilderLoadedImage*& foundImage, bool staticLinkage, bool allowOther) +{ + __block bool result = false; + + _pathOverrides.forEachPathVariant(loadPath, ^(const char* possiblePath, bool isFallbackPath, bool& stop) { + bool unmapWhenDone = false; + bool contentRebased = false; + bool hasInits = false; + bool fileFound = false; + bool markNeverUnload = staticLinkage ? forImageChain.image.markNeverUnload : false; + ImageNum overrideImageNum = 0; + ImageNum foundImageNum = 0; + const MachOAnalyzer* mh = nullptr; + const char* filePath = nullptr; + LoadedFileInfo loadedFileInfo; + + // This check is within forEachPathVariant() to let DYLD_LIBRARY_PATH override LC_RPATH + bool isRPath = (strncmp(possiblePath, "@rpath/", 7) == 0); + + // passing a leaf name to dlopen() allows rpath searching for it + bool implictRPath = !staticLinkage && (loadPath[0] != '/') && (loadPath == possiblePath) && (_atPathHandling != AtPath::none); + + // expand @ paths + const char* prePathVarExpansion = possiblePath; + possiblePath = resolvePathVar(possiblePath, forImageChain, implictRPath); + if ( prePathVarExpansion != possiblePath ) + _atPathUsed = true; + + // look at already loaded images + const char* leafName = strrchr(possiblePath, '/'); + for (BuilderLoadedImage& li: _loadedImages) { + if ( strcmp(li.path(), possiblePath) == 0 ) { + foundImage = &li; + result = true; + stop = true; + return; + } + else if ( isRPath ) { + // Special case @rpath/ because name in li.fileInfo.path is full path. + // Getting installName is expensive, so first see if an already loaded image + // has same leaf name and if so see if its installName matches request @rpath + if (const char* aLeaf = strrchr(li.path(), '/')) { + if ( strcmp(aLeaf, leafName) == 0 ) { + if ( li.loadAddress()->isDylib() && (strcmp(loadPath, li.loadAddress()->installName()) == 0) ) { + foundImage = &li; + result = true; + stop = true; + return; + } + } + } + } + } + + // look to see if image already loaded via a different symlink + if ( _fileSystem.fileExists(possiblePath, &loadedFileInfo.inode, &loadedFileInfo.mtime) ) { + fileFound = true; + for (BuilderLoadedImage& li: _loadedImages) { + if ( (li.loadedFileInfo.inode == loadedFileInfo.inode) && (li.loadedFileInfo.mtime == loadedFileInfo.mtime) ) { + foundImage = &li; + result = true; + stop = true; + return; + } + } + } + + // look in dyld cache + filePath = possiblePath; + char realPath[MAXPATHLEN]; + if ( _dyldImageArray != nullptr && (_dyldCache->header.formatVersion == dyld3::closure::kFormatVersion) ) { + uint32_t dyldCacheImageIndex; + bool foundInCache = _dyldCache->hasImagePath(possiblePath, dyldCacheImageIndex); + if ( !foundInCache && fileFound ) { + // see if this is an OS dylib/bundle with a pre-built dlopen closure + if ( allowOther ) { + if (const dyld3::closure::Image* otherImage = _dyldCache->findDlopenOtherImage(possiblePath) ) { + uint64_t expectedInode; + uint64_t expectedModTime; + if ( !otherImage->isInvalid() ) { + bool hasInodeInfo = otherImage->hasFileModTimeAndInode(expectedInode, expectedModTime); + // use pre-built Image if it does not have mtime/inode or it does and it has matches current file info + if ( !hasInodeInfo || ((expectedInode == loadedFileInfo.inode) && (expectedModTime == loadedFileInfo.mtime)) ) { + loadedFileInfo = MachOAnalyzer::load(_diag, _fileSystem, possiblePath, _archName, _platform); + if ( _diag.noError() ) { + mh = (const MachOAnalyzer*)loadedFileInfo.fileContent; + foundImageNum = otherImage->imageNum(); + unmapWhenDone = true; + contentRebased = false; + hasInits = otherImage->hasInitializers() || otherImage->mayHavePlusLoads(); + } + } + } + } + } + // if not found in cache, may be a symlink to something in cache + if ( mh == nullptr ) { + if ( _fileSystem.getRealPath(possiblePath, realPath) ) { + foundInCache = _dyldCache->hasImagePath(realPath, dyldCacheImageIndex); + if ( foundInCache ) { + filePath = realPath; + #if BUILDING_LIBDYLD + // handle case where OS dylib was updated after this process launched + if ( foundInCache ) { + for (BuilderLoadedImage& li: _loadedImages) { + if ( strcmp(li.path(), realPath) == 0 ) { + foundImage = &li; + result = true; + stop = true; + return; + } + } + } + #endif + } + } + } + } + + // if using a cached dylib, look to see if there is an override + if ( foundInCache ) { + ImageNum dyldCacheImageNum = dyldCacheImageIndex + 1; + bool useCache = true; + markNeverUnload = true; // dylibs in cache, or dylibs that override cache should not be unloaded at runtime + const Image* image = _dyldImageArray->imageForNum(dyldCacheImageNum); + if ( image->overridableDylib() ) { + if ( fileFound && (_platform == MachOFile::currentPlatform()) ) { + uint64_t expectedInode; + uint64_t expectedModTime; + if ( image->hasFileModTimeAndInode(expectedInode, expectedModTime) ) { + // macOS where dylibs remain on disk. only use cache if mtime and inode have not changed + useCache = ( (loadedFileInfo.inode == expectedInode) && (loadedFileInfo.mtime == expectedModTime) ); + } + else if ( _makingClosuresInCache ) { + // during iOS cache build, don't look at files on disk, use ones in cache + useCache = true; + } + else { + // iOS internal build. Any disk on cache overrides cache + useCache = false; + } + } + if ( !useCache ) + overrideImageNum = dyldCacheImageNum; + } + if ( useCache ) { + foundImageNum = dyldCacheImageNum; + mh = (MachOAnalyzer*)_dyldCache->getIndexedImageEntry(foundImageNum-1, loadedFileInfo.mtime, loadedFileInfo.inode); + unmapWhenDone = false; + // if we are building ImageArray in dyld cache, content is not rebased + contentRebased = !_makingDyldCacheImages && _dyldCacheIsLive; + hasInits = image->hasInitializers() || image->mayHavePlusLoads(); + } + } + } + + // If we are building the cache, and don't find an image, then it might be weak so just return + if (_makingDyldCacheImages) { + addMustBeMissingPath(possiblePath); + return; + } + + // if not found yet, mmap file + if ( mh == nullptr ) { + loadedFileInfo = MachOAnalyzer::load(_diag, _fileSystem, filePath, _archName, _platform); + mh = (const MachOAnalyzer*)loadedFileInfo.fileContent; + if ( mh == nullptr ) { + // Don't add must be missing paths for dlopen as we don't cache dlopen closures + if (_isLaunchClosure) { + addMustBeMissingPath(possiblePath); + } + return; + } + if ( staticLinkage ) { + // LC_LOAD_DYLIB can only link with dylibs + if ( !mh->isDylib() ) { + _diag.error("not a dylib"); + return; + } + } + else if ( mh->isMainExecutable() ) { + // when dlopen()ing a main executable, it must be dynamic Position Independent Executable + if ( !mh->isPIE() || !mh->isDynamicExecutable() ) { + _diag.error("not PIE"); + return; + } + } + foundImageNum = _startImageNum + _nextIndex++; + unmapWhenDone = true; + } else { + loadedFileInfo.fileContent = mh; + } + + // if path is not original path + if ( filePath != loadPath ) { + // possiblePath may be a temporary (stack) string, since we found file at that path, make it permanent + filePath = strdup_temp(filePath); + // check if this overrides what would have been found in cache + if ( overrideImageNum == 0 ) { + if ( _dyldImageArray != nullptr ) { + uint32_t dyldCacheImageIndex; + if ( _dyldCache->hasImagePath(loadPath, dyldCacheImageIndex) ) { + ImageNum possibleOverrideNum = dyldCacheImageIndex+1; + if ( possibleOverrideNum != foundImageNum ) + overrideImageNum = possibleOverrideNum; + } + } + } + } + + if ( !markNeverUnload ) { + // If the parent didn't force us to be never unload, other conditions still may + if ( mh->hasThreadLocalVariables() ) { + markNeverUnload = true; + } else if ( mh->hasObjC() && mh->isDylib() ) { + markNeverUnload = true; + } else { + // record if image has DOF sections + __block bool hasDOFs = false; + mh->forEachDOFSection(_diag, ^(uint32_t offset) { + hasDOFs = true; + }); + if ( hasDOFs ) + markNeverUnload = true; + } + } + + // Set the path again just in case it was strdup'ed. + loadedFileInfo.path = filePath; + + // add new entry + BuilderLoadedImage entry; + entry.loadedFileInfo = loadedFileInfo; + entry.imageNum = foundImageNum; + entry.unmapWhenDone = unmapWhenDone; + entry.contentRebased = contentRebased; + entry.hasInits = hasInits; + entry.markNeverUnload = markNeverUnload; + entry.rtldLocal = false; + entry.isBadImage = false; + entry.overrideImageNum = overrideImageNum; + _loadedImages.push_back(entry); + foundImage = &_loadedImages.back(); + if ( isFallbackPath ) + _fallbackPathUsed = true; + stop = true; + result = true; + }, _platform); + + return result; +} + +bool ClosureBuilder::expandAtLoaderPath(const char* loadPath, bool fromLCRPATH, const BuilderLoadedImage& loadedImage, char fixedPath[]) +{ + switch ( _atPathHandling ) { + case AtPath::none: + return false; + case AtPath::onlyInRPaths: + if ( !fromLCRPATH ) { + // allow @loader_path in LC_LOAD_DYLIB during dlopen() + if ( _isLaunchClosure ) + return false; + } + break; + case AtPath::all: + break; + } + if ( strncmp(loadPath, "@loader_path/", 13) != 0 ) + return false; + + strlcpy(fixedPath, loadedImage.path(), PATH_MAX); + char* lastSlash = strrchr(fixedPath, '/'); + if ( lastSlash != nullptr ) { + strcpy(lastSlash+1, &loadPath[13]); + return true; + } + return false; +} + +bool ClosureBuilder::expandAtExecutablePath(const char* loadPath, bool fromLCRPATH, char fixedPath[]) +{ + switch ( _atPathHandling ) { + case AtPath::none: + return false; + case AtPath::onlyInRPaths: + if ( !fromLCRPATH ) + return false; + break; + case AtPath::all: + break; + } + if ( strncmp(loadPath, "@executable_path/", 17) != 0 ) + return false; + + if ( _atPathHandling != AtPath::all ) + return false; + + strlcpy(fixedPath, _loadedImages[_mainProgLoadIndex].path(), PATH_MAX); + char* lastSlash = strrchr(fixedPath, '/'); + if ( lastSlash != nullptr ) { + strcpy(lastSlash+1, &loadPath[17]); + return true; + } + return false; +} + +const char* ClosureBuilder::resolvePathVar(const char* loadPath, const LoadedImageChain& forImageChain, bool implictRPath) +{ + // don't expand @ path if disallowed + if ( (_atPathHandling == AtPath::none) && (loadPath[0] == '@') ) + return loadPath; + + // quick out if not @ path or not implicit rpath + if ( !implictRPath && (loadPath[0] != '@') ) + return loadPath; + + // expand @loader_path + BLOCK_ACCCESSIBLE_ARRAY(char, tempPath, PATH_MAX); // read as: char tempPath[PATH_MAX]; + if ( expandAtLoaderPath(loadPath, false, forImageChain.image, tempPath) ) + return strdup_temp(tempPath); + + // expand @executable_path + if ( expandAtExecutablePath(loadPath, false, tempPath) ) + return strdup_temp(tempPath); + + // expand @rpath + const char* rpathTail = nullptr; + char implicitRpathBuffer[PATH_MAX]; + if ( strncmp(loadPath, "@rpath/", 7) == 0 ) { + // note: rpathTail starts with '/' + rpathTail = &loadPath[6]; + } + else if ( implictRPath ) { + // make rpathTail starts with '/' + strlcpy(implicitRpathBuffer, "/", PATH_MAX); + strlcat(implicitRpathBuffer, loadPath, PATH_MAX); + rpathTail = implicitRpathBuffer; + } + if ( rpathTail != nullptr ) { + // rpath is expansion is technically a stack of rpath dirs built starting with main executable and pushing + // LC_RPATHS from each dylib as they are recursively loaded. Our imageChain represents that stack. + __block const char* result = nullptr; + for (const LoadedImageChain* link = &forImageChain; (link != nullptr) && (result == nullptr); link = link->previous) { + link->image.loadAddress()->forEachRPath(^(const char* rPath, bool& stop) { + // fprintf(stderr, "LC_RPATH %s from %s\n", rPath, link->image.fileInfo.path); + if ( expandAtLoaderPath(rPath, true, link->image, tempPath) || expandAtExecutablePath(rPath, true, tempPath) ) { + strlcat(tempPath, rpathTail, PATH_MAX); + } + else { + strlcpy(tempPath, rPath, PATH_MAX); + strlcat(tempPath, rpathTail, PATH_MAX); + } + if ( _fileSystem.fileExists(tempPath) ) { + stop = true; + result = strdup_temp(tempPath); + } + else { + // Don't add must be missing paths for dlopen as we don't cache dlopen closures + if (_isLaunchClosure) { + addMustBeMissingPath(tempPath); + } + } + }); + } + if ( result != nullptr ) + return result; + } + + return loadPath; +} + +const char* ClosureBuilder::strdup_temp(const char* path) +{ + if ( _tempPaths == nullptr ) + _tempPaths = PathPool::allocate(); + return _tempPaths->add(path); +} + +void ClosureBuilder::addMustBeMissingPath(const char* path) +{ + //fprintf(stderr, "must be missing: %s\n", path); + if ( _mustBeMissingPaths == nullptr ) + _mustBeMissingPaths = PathPool::allocate(); + _mustBeMissingPaths->add(path); +} + +ClosureBuilder::BuilderLoadedImage& ClosureBuilder::findLoadedImage(ImageNum imageNum) +{ + for (BuilderLoadedImage& li : _loadedImages) { + if ( li.imageNum == imageNum ) { + return li; + } + } + for (BuilderLoadedImage& li : _loadedImages) { + if ( li.overrideImageNum == imageNum ) { + return li; + } + } + assert(0 && "LoadedImage not found"); +} + +ClosureBuilder::BuilderLoadedImage& ClosureBuilder::findLoadedImage(const MachOAnalyzer* mh) +{ + for (BuilderLoadedImage& li : _loadedImages) { + if ( li.loadAddress() == mh ) { + return li; + } + } + assert(0 && "LoadedImage not found"); +} + +const MachOAnalyzer* ClosureBuilder::machOForImageNum(ImageNum imageNum) +{ + return findLoadedImage(imageNum).loadAddress(); +} + +const MachOAnalyzer* ClosureBuilder::findDependent(const MachOLoaded* mh, uint32_t depIndex) +{ + for (const BuilderLoadedImage& li : _loadedImages) { + if ( li.loadAddress() == mh ) { + if (li.isBadImage) { + // Bad image duting building group 1 closures, so the dependents array + // is potentially incomplete. + return nullptr; + } + ImageNum childNum = li.dependents[depIndex].imageNum(); + return machOForImageNum(childNum); + } + } + return nullptr; +} + +ImageNum ClosureBuilder::imageNumForMachO(const MachOAnalyzer* mh) +{ + for (const BuilderLoadedImage& li : _loadedImages) { + if ( li.loadAddress() == mh ) { + return li.imageNum; + } + } + assert(0 && "unknown mach-o"); + return 0; +} + +void ClosureBuilder::recursiveLoadDependents(LoadedImageChain& forImageChain) +{ + // if dependents is set, then we have already loaded this + if ( forImageChain.image.dependents.begin() != nullptr ) + return; + + uintptr_t startDepIndex = _dependencies.count(); + // add dependents + __block uint32_t depIndex = 0; + forImageChain.image.loadAddress()->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) { + Image::LinkKind kind = Image::LinkKind::regular; + if ( isWeak ) + kind = Image::LinkKind::weak; + else if ( isReExport ) + kind = Image::LinkKind::reExport; + else if ( isUpward ) + kind = Image::LinkKind::upward; + BuilderLoadedImage* foundImage; + if ( findImage(loadPath, forImageChain, foundImage, true, false) ) { + // verify this is compatable dylib version + if ( foundImage->loadAddress()->filetype != MH_DYLIB ) { + _diag.error("found '%s' which is not a dylib. Needed by '%s'", foundImage->path(), forImageChain.image.path()); + } + else { + const char* installName; + uint32_t foundCompatVers; + uint32_t foundCurrentVers; + foundImage->loadAddress()->getDylibInstallName(&installName, &foundCompatVers, &foundCurrentVers); + if ( (foundCompatVers < compatVersion) && foundImage->loadAddress()->enforceCompatVersion() ) { + char foundStr[32]; + char requiredStr[32]; + MachOFile::packedVersionToString(foundCompatVers, foundStr); + MachOFile::packedVersionToString(compatVersion, requiredStr); + _diag.error("found '%s' which has compat version (%s) which is less than required (%s). Needed by '%s'", + foundImage->path(), foundStr, requiredStr, forImageChain.image.path()); + } + } + if ( _diag.noError() ) + _dependencies.push_back(Image::LinkedImage(kind, foundImage->imageNum)); + } + else if ( isWeak ) { + _dependencies.push_back(Image::LinkedImage(Image::LinkKind::weak, kMissingWeakLinkedImage)); + } + else { + BLOCK_ACCCESSIBLE_ARRAY(char, extra, 4096); + extra[0] = '\0'; + const char* targetLeaf = strrchr(loadPath, '/'); + if ( targetLeaf == nullptr ) + targetLeaf = loadPath; + if ( _mustBeMissingPaths != nullptr ) { + strcpy(extra, ", tried: "); + _mustBeMissingPaths->forEachPath(^(const char* aPath) { + const char* aLeaf = strrchr(aPath, '/'); + if ( aLeaf == nullptr ) + aLeaf = aPath; + if ( strcmp(targetLeaf, aLeaf) == 0 ) { + strlcat(extra, "'", 4096); + strlcat(extra, aPath, 4096); + strlcat(extra, "' ", 4096); + } + }); + } + if ( _diag.hasError() ) { + #if BUILDING_CACHE_BUILDER + std::string errorMessageBuffer = _diag.errorMessage(); + const char* msg = errorMessageBuffer.c_str(); + #else + const char* msg = _diag.errorMessage(); + #endif + char msgCopy[strlen(msg)+4]; + strcpy(msgCopy, msg); + _diag.error("dependent dylib '%s' not found for '%s'. %s", loadPath, forImageChain.image.path(), msgCopy); + } + else { + _diag.error("dependent dylib '%s' not found for '%s'%s", loadPath, forImageChain.image.path(), extra); + } + if ( _launchErrorInfo != nullptr ) { + _launchErrorInfo->kind = DYLD_EXIT_REASON_DYLIB_MISSING; + _launchErrorInfo->clientOfDylibPath = forImageChain.image.path(); + _launchErrorInfo->targetDylibPath = loadPath; + _launchErrorInfo->symbol = nullptr; + } + } + ++depIndex; + if ( _diag.hasError() ) + stop = true; + }); + if ( _diag.hasError() ) + return; + forImageChain.image.dependents = _dependencies.subArray(startDepIndex, depIndex); + + // breadth first recurse + for (Image::LinkedImage dep : forImageChain.image.dependents) { + // don't recurse upwards + if ( dep.kind() == Image::LinkKind::upward ) + continue; + // don't recurse down missing weak links + if ( (dep.kind() == Image::LinkKind::weak) && (dep.imageNum() == kMissingWeakLinkedImage) ) + continue; + BuilderLoadedImage& depLoadedImage = findLoadedImage(dep.imageNum()); + LoadedImageChain chain = { &forImageChain, depLoadedImage }; + recursiveLoadDependents(chain); + if ( _diag.hasError() ) + break; + } +} + +void ClosureBuilder::loadDanglingUpwardLinks() +{ + bool danglingFixed; + do { + danglingFixed = false; + for (BuilderLoadedImage& li : _loadedImages) { + if ( li.dependents.begin() == nullptr ) { + // this image has not have dependents set (probably a dangling upward link or referenced by upward link) + LoadedImageChain chain = { nullptr, li }; + recursiveLoadDependents(chain); + danglingFixed = true; + break; + } + } + } while (danglingFixed && _diag.noError()); +} + +bool ClosureBuilder::overridableDylib(const BuilderLoadedImage& forImage) +{ + // only set on dylibs in the dyld shared cache + if ( !_makingDyldCacheImages ) + return false; + + // on macOS dylibs always override cache + if ( _platform == Platform::macOS ) + return true; + + // on embedded platforms with Internal cache, allow overrides + if ( !_makingCustomerCache ) + return true; + + // embedded platform customer caches, no overrides + return false; // FIXME, allow libdispatch.dylib to be overridden +} + +void ClosureBuilder::buildImage(ImageWriter& writer, BuilderLoadedImage& forImage) +{ + const MachOAnalyzer* macho = forImage.loadAddress(); + // set ImageNum + writer.setImageNum(forImage.imageNum); + + // set flags + writer.setHasWeakDefs(macho->hasWeakDefs()); + writer.setIsBundle(macho->isBundle()); + writer.setIsDylib(macho->isDylib()); + writer.setIs64(macho->is64()); + writer.setIsExecutable(macho->isMainExecutable()); + writer.setUses16KPages(macho->uses16KPages()); + writer.setOverridableDylib(overridableDylib(forImage)); + writer.setInDyldCache(macho->inDyldCache()); + if ( macho->hasObjC() ) { + writer.setHasObjC(true); + bool hasPlusLoads = macho->hasPlusLoadMethod(_diag); + writer.setHasPlusLoads(hasPlusLoads); + if ( hasPlusLoads ) + forImage.hasInits = true; + } + else { + writer.setHasObjC(false); + writer.setHasPlusLoads(false); + } + + if ( forImage.markNeverUnload ) { + writer.setNeverUnload(true); + } + +#if BUILDING_DYLD || BUILDING_LIBDYLD + // shared cache not built by dyld or libdyld.dylib, so must be real file + writer.setFileInfo(forImage.loadedFileInfo.inode, forImage.loadedFileInfo.mtime); +#else + if ( _platform == Platform::macOS ) { + if ( macho->inDyldCache() && !_dyldCache->header.dylibsExpectedOnDisk ) { + // don't add file info for shared cache files mastered out of final file system + } + else { + // file is either not in cache or is in cache but not mastered out + writer.setFileInfo(forImage.loadedFileInfo.inode, forImage.loadedFileInfo.mtime); + } + } + else { + // all other platforms, cache is built off-device, so inodes are not known + } +#endif + + // add info on how to load image + if ( !macho->inDyldCache() ) { + writer.setMappingInfo(forImage.loadedFileInfo.sliceOffset, macho->mappedSize()); + // add code signature, if signed + uint32_t codeSigFileOffset; + uint32_t codeSigSize; + if ( macho->hasCodeSignature(codeSigFileOffset, codeSigSize) ) { + writer.setCodeSignatureLocation(codeSigFileOffset, codeSigSize); + uint8_t cdHash[20]; + if ( macho->getCDHash(cdHash) ) + writer.setCDHash(cdHash); + } + // add FairPlay encryption range if encrypted + uint32_t fairPlayFileOffset; + uint32_t fairPlaySize; + if ( macho->isFairPlayEncrypted(fairPlayFileOffset, fairPlaySize) ) { + writer.setFairPlayEncryptionRange(fairPlayFileOffset, fairPlaySize); + } + } + + // set path + writer.addPath(forImage.path()); + if ( _aliases != nullptr ) { + for (const CachedDylibAlias& alias : *_aliases) { + if ( strcmp(alias.realPath, forImage.path()) == 0 ) + writer.addPath(alias.aliasPath); + } + } + + // set uuid, if has one + uuid_t uuid; + if ( macho->getUuid(uuid) ) + writer.setUUID(uuid); + + // set dependents + writer.setDependents(forImage.dependents); + + // set segments + addSegments(writer, macho); + + // record if this dylib overrides something in the cache + if ( forImage.overrideImageNum != 0 ) { + writer.setAsOverrideOf(forImage.overrideImageNum); + const char* overridePath = _dyldImageArray->imageForNum(forImage.overrideImageNum)->path(); + writer.addPath(overridePath); + if ( strcmp(overridePath, "/usr/lib/system/libdyld.dylib") == 0 ) + _libDyldImageNum = forImage.imageNum; + else if ( strcmp(overridePath, "/usr/lib/libSystem.B.dylib") == 0 ) + _libSystemImageNum = forImage.imageNum; + } + + + // do fix up info for non-cached, and cached if building cache + if ( !macho->inDyldCache() || _makingDyldCacheImages ) { + if ( macho->hasChainedFixups() ) { + addChainedFixupInfo(writer, forImage); + } + else { + if ( _handlers != nullptr ) { + reportRebasesAndBinds(writer, forImage); + } + else { + addRebaseInfo(writer, macho); + if ( _diag.noError() ) + addBindInfo(writer, forImage); + } + } + } + if ( _diag.hasError() ) { + writer.setInvalid(); + return; + } + + // add initializers + bool contentRebased = forImage.contentRebased; + __block unsigned initCount = 0; + macho->forEachInitializer(_diag, contentRebased, ^(uint32_t offset) { + ++initCount; + }, _dyldCache); + if ( initCount != 0 ) { + BLOCK_ACCCESSIBLE_ARRAY(uint32_t, initOffsets, initCount); + __block unsigned index = 0; + macho->forEachInitializer(_diag, contentRebased, ^(uint32_t offset) { + initOffsets[index++] = offset; + }, _dyldCache); + writer.setInitOffsets(initOffsets, initCount); + forImage.hasInits = true; + } + + // record if image has DOF sections + STACK_ALLOC_ARRAY(uint32_t, dofSectionOffsets, 256); + macho->forEachDOFSection(_diag, ^(uint32_t offset) { + dofSectionOffsets.push_back(offset); + }); + if ( !dofSectionOffsets.empty() ) { + writer.setDofOffsets(dofSectionOffsets); + } + +} + +void ClosureBuilder::addSegments(ImageWriter& writer, const MachOAnalyzer* mh) +{ + const uint32_t segCount = mh->segmentCount(); + if ( mh->inDyldCache() ) { + uint64_t cacheUnslideBaseAddress = _dyldCache->unslidLoadAddress(); + BLOCK_ACCCESSIBLE_ARRAY(Image::DyldCacheSegment, segs, segCount); + mh->forEachSegment(^(const MachOAnalyzer::SegmentInfo& info, bool& stop) { + segs[info.segIndex] = { (uint32_t)(info.vmAddr-cacheUnslideBaseAddress), (uint32_t)info.vmSize, info.protections }; + }); + writer.setCachedSegments(segs, segCount); + } + else { + const uint32_t pageSize = (mh->uses16KPages() ? 0x4000 : 0x1000); + __block uint32_t diskSegIndex = 0; + __block uint32_t totalPageCount = 0; + __block uint32_t lastFileOffsetEnd = 0; + __block uint64_t lastVmAddrEnd = 0; + BLOCK_ACCCESSIBLE_ARRAY(Image::DiskSegment, dsegs, segCount*3); // room for padding + mh->forEachSegment(^(const MachOAnalyzer::SegmentInfo& info, bool& stop) { + if ( (info.fileOffset != 0) && (info.fileOffset != lastFileOffsetEnd) ) { + Image::DiskSegment filePadding; + filePadding.filePageCount = (info.fileOffset - lastFileOffsetEnd)/pageSize; + filePadding.vmPageCount = 0; + filePadding.permissions = 0; + filePadding.paddingNotSeg = 1; + dsegs[diskSegIndex++] = filePadding; + } + if ( (lastVmAddrEnd != 0) && (info.vmAddr != lastVmAddrEnd) ) { + Image::DiskSegment vmPadding; + vmPadding.filePageCount = 0; + vmPadding.vmPageCount = (info.vmAddr - lastVmAddrEnd)/pageSize; + vmPadding.permissions = 0; + vmPadding.paddingNotSeg = 1; + dsegs[diskSegIndex++] = vmPadding; + totalPageCount += vmPadding.vmPageCount; + } + { + Image::DiskSegment segInfo; + segInfo.filePageCount = (info.fileSize+pageSize-1)/pageSize; + segInfo.vmPageCount = (info.vmSize+pageSize-1)/pageSize; + segInfo.permissions = info.protections & 7; + segInfo.paddingNotSeg = 0; + dsegs[diskSegIndex++] = segInfo; + totalPageCount += segInfo.vmPageCount; + if ( info.fileSize != 0 ) + lastFileOffsetEnd = (uint32_t)(info.fileOffset + info.fileSize); + if ( info.vmSize != 0 ) + lastVmAddrEnd = info.vmAddr + info.vmSize; + } + }); + writer.setDiskSegments(dsegs, diskSegIndex); + } +} + +void ClosureBuilder::addInterposingTuples(LaunchClosureWriter& writer, const Image* image, const MachOAnalyzer* mh) +{ + const unsigned pointerSize = mh->pointerSize(); + mh->forEachInterposingSection(_diag, ^(uint64_t sectVmOffset, uint64_t sectVmSize, bool &stop) { + const uint32_t entrySize = 2*pointerSize; + const uint32_t tupleCount = (uint32_t)(sectVmSize/entrySize); + BLOCK_ACCCESSIBLE_ARRAY(InterposingTuple, resolvedTuples, tupleCount); + for (uint32_t i=0; i < tupleCount; ++i) { + resolvedTuples[i].stockImplementation.absolute.kind = Image::ResolvedSymbolTarget::kindAbsolute; + resolvedTuples[i].stockImplementation.absolute.value = 0; + resolvedTuples[i].newImplementation.absolute.kind = Image::ResolvedSymbolTarget::kindAbsolute; + resolvedTuples[i].newImplementation.absolute.value = 0; + } + image->forEachFixup(^(uint64_t imageOffsetToRebase, bool &rebaseStop) { + if ( imageOffsetToRebase < sectVmOffset ) + return; + if ( imageOffsetToRebase > sectVmOffset+sectVmSize ) + return; + uint64_t offsetIntoSection = imageOffsetToRebase - sectVmOffset; + uint64_t rebaseIndex = offsetIntoSection/entrySize; + if ( rebaseIndex*entrySize != offsetIntoSection ) + return; + const void* content = (uint8_t*)mh + imageOffsetToRebase; + uint64_t unslidTargetAddress = mh->is64() ? *(uint64_t*)content : *(uint32_t*)content; + resolvedTuples[rebaseIndex].newImplementation.image.kind = Image::ResolvedSymbolTarget::kindImage; + resolvedTuples[rebaseIndex].newImplementation.image.imageNum = image->imageNum(); + resolvedTuples[rebaseIndex].newImplementation.image.offset = unslidTargetAddress - mh->preferredLoadAddress(); + }, ^(uint64_t imageOffsetToBind, Image::ResolvedSymbolTarget bindTarget, bool &bindStop) { + if ( imageOffsetToBind < sectVmOffset ) + return; + if ( imageOffsetToBind > sectVmOffset+sectVmSize ) + return; + uint64_t offsetIntoSection = imageOffsetToBind - sectVmOffset; + uint64_t bindIndex = offsetIntoSection/entrySize; + if ( bindIndex*entrySize + pointerSize != offsetIntoSection ) + return; + resolvedTuples[bindIndex].stockImplementation = bindTarget; + }, ^(uint64_t imageOffsetStart, const Array& targets, bool& chainStop) { + // walk each fixup in the chain + image->forEachChainedFixup((void*)mh, imageOffsetStart, ^(uint64_t* fixupLoc, MachOLoaded::ChainedFixupPointerOnDisk fixupInfo, bool& stopChain) { + uint64_t imageOffsetToFixup = (uint64_t)fixupLoc - (uint64_t)mh; + if ( fixupInfo.authRebase.auth ) { +#if SUPPORT_ARCH_arm64e + if ( fixupInfo.authBind.bind ) { + closure::Image::ResolvedSymbolTarget bindTarget = targets[fixupInfo.authBind.ordinal]; + if ( imageOffsetToFixup < sectVmOffset ) + return; + if ( imageOffsetToFixup > sectVmOffset+sectVmSize ) + return; + uint64_t offsetIntoSection = imageOffsetToFixup - sectVmOffset; + uint64_t bindIndex = offsetIntoSection/entrySize; + if ( bindIndex*entrySize + pointerSize != offsetIntoSection ) + return; + resolvedTuples[bindIndex].stockImplementation = bindTarget; + } + else { + if ( imageOffsetToFixup < sectVmOffset ) + return; + if ( imageOffsetToFixup > sectVmOffset+sectVmSize ) + return; + uint64_t offsetIntoSection = imageOffsetToFixup - sectVmOffset; + uint64_t rebaseIndex = offsetIntoSection/entrySize; + if ( rebaseIndex*entrySize != offsetIntoSection ) + return; + uint64_t unslidTargetAddress = (uint64_t)mh->preferredLoadAddress() + fixupInfo.authRebase.target; + resolvedTuples[rebaseIndex].newImplementation.image.kind = Image::ResolvedSymbolTarget::kindImage; + resolvedTuples[rebaseIndex].newImplementation.image.imageNum = image->imageNum(); + resolvedTuples[rebaseIndex].newImplementation.image.offset = unslidTargetAddress - mh->preferredLoadAddress(); + } +#else + _diag.error("malformed chained pointer"); + stop = true; + stopChain = true; +#endif + } + else { + if ( fixupInfo.plainRebase.bind ) { + closure::Image::ResolvedSymbolTarget bindTarget = targets[fixupInfo.plainBind.ordinal]; + if ( imageOffsetToFixup < sectVmOffset ) + return; + if ( imageOffsetToFixup > sectVmOffset+sectVmSize ) + return; + uint64_t offsetIntoSection = imageOffsetToFixup - sectVmOffset; + uint64_t bindIndex = offsetIntoSection/entrySize; + if ( bindIndex*entrySize + pointerSize != offsetIntoSection ) + return; + resolvedTuples[bindIndex].stockImplementation = bindTarget; + } + else { + if ( imageOffsetToFixup < sectVmOffset ) + return; + if ( imageOffsetToFixup > sectVmOffset+sectVmSize ) + return; + uint64_t offsetIntoSection = imageOffsetToFixup - sectVmOffset; + uint64_t rebaseIndex = offsetIntoSection/entrySize; + if ( rebaseIndex*entrySize != offsetIntoSection ) + return; + uint64_t unslidTargetAddress = fixupInfo.plainRebase.signExtendedTarget(); + resolvedTuples[rebaseIndex].newImplementation.image.kind = Image::ResolvedSymbolTarget::kindImage; + resolvedTuples[rebaseIndex].newImplementation.image.imageNum = image->imageNum(); + resolvedTuples[rebaseIndex].newImplementation.image.offset = unslidTargetAddress - mh->preferredLoadAddress(); + } + } + }); + }); + + // remove any tuples in which both sides are not set (or target is weak-import NULL) + STACK_ALLOC_ARRAY(InterposingTuple, goodTuples, tupleCount); + for (uint32_t i=0; i < tupleCount; ++i) { + if ( (resolvedTuples[i].stockImplementation.image.kind != Image::ResolvedSymbolTarget::kindAbsolute) + && (resolvedTuples[i].newImplementation.image.kind != Image::ResolvedSymbolTarget::kindAbsolute) ) + goodTuples.push_back(resolvedTuples[i]); + } + writer.addInterposingTuples(goodTuples); + + // if the target of the interposing is in the dyld shared cache, add a PatchEntry so the cache is fixed up at launch + STACK_ALLOC_ARRAY(Closure::PatchEntry, patches, goodTuples.count()); + for (const InterposingTuple& aTuple : goodTuples) { + if ( aTuple.stockImplementation.sharedCache.kind == Image::ResolvedSymbolTarget::kindSharedCache ) { + uint32_t imageIndex; + assert(_dyldCache->addressInText((uint32_t)aTuple.stockImplementation.sharedCache.offset, &imageIndex)); + ImageNum imageInCache = imageIndex+1; + Closure::PatchEntry patch; + patch.exportCacheOffset = (uint32_t)aTuple.stockImplementation.sharedCache.offset; + patch.overriddenDylibInCache = imageInCache; + patch.replacement = aTuple.newImplementation; + patches.push_back(patch); + } + } + writer.addCachePatches(patches); + }); +} + +void ClosureBuilder::addRebaseInfo(ImageWriter& writer, const MachOAnalyzer* mh) +{ + const uint64_t ptrSize = mh->pointerSize(); + Image::RebasePattern maxLeapPattern = { 0xFFFFF, 0, 0xF }; + const uint64_t maxLeapCount = maxLeapPattern.repeatCount * maxLeapPattern.skipCount; + STACK_ALLOC_OVERFLOW_SAFE_ARRAY(Image::RebasePattern, rebaseEntries, 1024); + __block uint64_t lastLocation = -ptrSize; + mh->forEachRebase(_diag, true, ^(uint64_t runtimeOffset, bool& stop) { + const uint64_t delta = runtimeOffset - lastLocation; + const bool aligned = ((delta % ptrSize) == 0); + if ( delta == ptrSize ) { + // this rebase location is contiguous to previous + if ( rebaseEntries.back().contigCount < 255 ) { + // just bump previous's contigCount + rebaseEntries.back().contigCount++; + } + else { + // previous contiguous run already has max 255, so start a new run + rebaseEntries.push_back({ 1, 1, 0 }); + } + } + else if ( aligned && (delta <= (ptrSize*15)) ) { + // this rebase is within skip distance of last rebase + rebaseEntries.back().skipCount = (uint8_t)((delta-ptrSize)/ptrSize); + int lastIndex = (int)(rebaseEntries.count() - 1); + if ( lastIndex > 1 ) { + if ( (rebaseEntries[lastIndex].contigCount == rebaseEntries[lastIndex-1].contigCount) + && (rebaseEntries[lastIndex].skipCount == rebaseEntries[lastIndex-1].skipCount) ) { + // this entry as same contig and skip as prev, so remove it and bump repeat count of previous + rebaseEntries.pop_back(); + rebaseEntries.back().repeatCount += 1; + } + } + rebaseEntries.push_back({ 1, 1, 0 }); + } + else { + uint64_t advanceCount = (delta-ptrSize); + if ( (runtimeOffset < lastLocation) && (lastLocation != -ptrSize) ) { + // out of rebases! handle this be resting rebase offset to zero + rebaseEntries.push_back({ 0, 0, 0 }); + advanceCount = runtimeOffset; + } + // if next rebase is too far to reach with one pattern, use series + while ( advanceCount > maxLeapCount ) { + rebaseEntries.push_back(maxLeapPattern); + advanceCount -= maxLeapCount; + } + // if next rebase is not reachable with skipCount==1 or skipCount==15, add intermediate + while ( advanceCount > maxLeapPattern.repeatCount ) { + uint64_t count = advanceCount / maxLeapPattern.skipCount; + rebaseEntries.push_back({ (uint32_t)count, 0, maxLeapPattern.skipCount }); + advanceCount -= (count*maxLeapPattern.skipCount); + } + if ( advanceCount != 0 ) + rebaseEntries.push_back({ (uint32_t)advanceCount, 0, 1 }); + rebaseEntries.push_back({ 1, 1, 0 }); + } + lastLocation = runtimeOffset; + }); + writer.setRebaseInfo(rebaseEntries); + + // i386 programs also use text relocs to rebase stubs + if ( mh->cputype == CPU_TYPE_I386 ) { + STACK_ALLOC_OVERFLOW_SAFE_ARRAY(Image::TextFixupPattern, textRebases, 512); + __block uint64_t lastOffset = -4; + mh->forEachTextRebase(_diag, ^(uint64_t runtimeOffset, bool& stop) { + if ( textRebases.freeCount() < 2 ) { + _diag.error("too many text rebase locations (%ld) in %s", textRebases.maxCount(), writer.currentImage()->path()); + stop = true; + } + bool mergedIntoPrevious = false; + if ( (runtimeOffset > lastOffset) && !textRebases.empty() ) { + uint32_t skipAmount = (uint32_t)(runtimeOffset - lastOffset); + if ( (textRebases.back().repeatCount == 1) && (textRebases.back().skipCount == 0) ) { + textRebases.back().repeatCount = 2; + textRebases.back().skipCount = skipAmount; + mergedIntoPrevious = true; + } + else if ( textRebases.back().skipCount == skipAmount ) { + textRebases.back().repeatCount += 1; + mergedIntoPrevious = true; + } + } + if ( !mergedIntoPrevious ) { + Image::TextFixupPattern pattern; + pattern.target.raw = 0; + pattern.startVmOffset = (uint32_t)runtimeOffset; + pattern.repeatCount = 1; + pattern.skipCount = 0; + textRebases.push_back(pattern); + } + lastOffset = runtimeOffset; + }); + writer.setTextRebaseInfo(textRebases); + } +} + + +void ClosureBuilder::forEachBind(BuilderLoadedImage& forImage, void (^handler)(uint64_t runtimeOffset, Image::ResolvedSymbolTarget target, const ResolvedTargetInfo& targetInfo, bool& stop), + void (^strongHandler)(const char* strongSymbolName)) +{ + __block int lastLibOrdinal = 256; + __block const char* lastSymbolName = nullptr; + __block uint64_t lastAddend = 0; + __block Image::ResolvedSymbolTarget target; + __block ResolvedTargetInfo targetInfo; + forImage.loadAddress()->forEachBind(_diag, ^(uint64_t runtimeOffset, int libOrdinal, const char* symbolName, bool weakImport, uint64_t addend, bool& stop) { + if ( (symbolName == lastSymbolName) && (libOrdinal == lastLibOrdinal) && (addend == lastAddend) ) { + // same symbol lookup as last location + handler(runtimeOffset, target, targetInfo, stop); + } + else if ( findSymbol(forImage, libOrdinal, symbolName, weakImport, addend, target, targetInfo) ) { + handler(runtimeOffset, target, targetInfo, stop); + lastSymbolName = symbolName; + lastLibOrdinal = libOrdinal; + lastAddend = addend; + } + else { + stop = true; + } + }, ^(const char* symbolName) { + strongHandler(symbolName); + }); +} + +void ClosureBuilder::addBindInfo(ImageWriter& writer, BuilderLoadedImage& forImage) +{ + const uint32_t ptrSize = forImage.loadAddress()->pointerSize(); + STACK_ALLOC_OVERFLOW_SAFE_ARRAY(Image::BindPattern, binds, 512); + __block uint64_t lastOffset = -ptrSize; + __block Image::ResolvedSymbolTarget lastTarget = { {0, 0} }; + forEachBind(forImage, ^(uint64_t runtimeOffset, Image::ResolvedSymbolTarget target, const ResolvedTargetInfo& targetInfo, bool& stop) { + if ( targetInfo.weakBindCoalese ) { + // may be previous bind to this location + // if so, update that rather create new BindPattern + for (Image::BindPattern& aBind : binds) { + if ( (aBind.startVmOffset == runtimeOffset) && (aBind.repeatCount == 1) && (aBind.skipCount == 0) ) { + aBind.target = target; + return; + } + } + } + bool mergedIntoPrevious = false; + if ( !mergedIntoPrevious && (target == lastTarget) && (runtimeOffset > lastOffset) && !binds.empty() ) { + uint64_t skipAmount = (runtimeOffset - lastOffset - ptrSize)/ptrSize; + if ( skipAmount*ptrSize != (runtimeOffset - lastOffset - ptrSize) ) { + // misaligned pointer means we cannot optimize + } + else { + if ( (binds.back().repeatCount == 1) && (binds.back().skipCount == 0) && (skipAmount <= 255) ) { + binds.back().repeatCount = 2; + binds.back().skipCount = skipAmount; + assert(binds.back().skipCount == skipAmount); // check overflow + mergedIntoPrevious = true; + } + else if ( (binds.back().skipCount == skipAmount) && (binds.back().repeatCount < 0xfff) ) { + uint32_t prevRepeatCount = binds.back().repeatCount; + binds.back().repeatCount += 1; + assert(binds.back().repeatCount > prevRepeatCount); // check overflow + mergedIntoPrevious = true; + } + } + } + if ( (target == lastTarget) && (runtimeOffset == lastOffset) && !binds.empty() ) { + // duplicate bind for same location, ignore this one + mergedIntoPrevious = true; + } + if ( !mergedIntoPrevious ) { + Image::BindPattern pattern; + pattern.target = target; + pattern.startVmOffset = runtimeOffset; + pattern.repeatCount = 1; + pattern.skipCount = 0; + assert(pattern.startVmOffset == runtimeOffset); + binds.push_back(pattern); + } + lastTarget = target; + lastOffset = runtimeOffset; + }, ^(const char* strongSymbolName) { + if ( !_makingDyldCacheImages ) { + // something has a strong symbol definition that may override a weak impl in the dyld cache + Image::ResolvedSymbolTarget strongOverride; + ResolvedTargetInfo strongTargetInfo; + if ( findSymbolInImage(forImage.loadAddress(), strongSymbolName, 0, false, strongOverride, strongTargetInfo) ) { + for (const BuilderLoadedImage& li : _loadedImages) { + if ( li.loadAddress()->inDyldCache() && li.loadAddress()->hasWeakDefs() ) { + Image::ResolvedSymbolTarget implInCache; + ResolvedTargetInfo implInCacheInfo; + if ( findSymbolInImage(li.loadAddress(), strongSymbolName, 0, false, implInCache, implInCacheInfo) ) { + // found another instance in some dylib in dyld cache, will need to patch it + Closure::PatchEntry patch; + patch.exportCacheOffset = (uint32_t)implInCache.sharedCache.offset; + patch.overriddenDylibInCache = li.imageNum; + patch.replacement = strongOverride; + _weakDefCacheOverrides.push_back(patch); + } + } + } + } + } + }); + writer.setBindInfo(binds); +} + +void ClosureBuilder::reportRebasesAndBinds(ImageWriter& writer, BuilderLoadedImage& forImage) +{ + // report all rebases + forImage.loadAddress()->forEachRebase(_diag, true, ^(uint64_t runtimeOffset, bool& stop) { + _handlers->rebase(forImage.imageNum, forImage.loadAddress(), (uint32_t)runtimeOffset); + }); + + // report all binds + forEachBind(forImage, ^(uint64_t runtimeOffset, Image::ResolvedSymbolTarget target, const ResolvedTargetInfo& targetInfo, bool& stop) { + _handlers->bind(forImage.imageNum, forImage.loadAddress(), (uint32_t)runtimeOffset, target, targetInfo); + }, + ^(const char* strongSymbolName) {}); + + // i386 programs also use text relocs to rebase stubs + if ( forImage.loadAddress()->cputype == CPU_TYPE_I386 ) { + // FIX ME + } +} + +// These are mangled symbols for all the variants of operator new and delete +// which a main executable can define (non-weak) and override the +// weak-def implementation in the OS. +static const char* sTreatAsWeak[] = { + "__Znwm", "__ZnwmRKSt9nothrow_t", + "__Znam", "__ZnamRKSt9nothrow_t", + "__ZdlPv", "__ZdlPvRKSt9nothrow_t", "__ZdlPvm", + "__ZdaPv", "__ZdaPvRKSt9nothrow_t", "__ZdaPvm", + "__ZnwmSt11align_val_t", "__ZnwmSt11align_val_tRKSt9nothrow_t", + "__ZnamSt11align_val_t", "__ZnamSt11align_val_tRKSt9nothrow_t", + "__ZdlPvSt11align_val_t", "__ZdlPvSt11align_val_tRKSt9nothrow_t", "__ZdlPvmSt11align_val_t", + "__ZdaPvSt11align_val_t", "__ZdaPvSt11align_val_tRKSt9nothrow_t", "__ZdaPvmSt11align_val_t" +}; + + +void ClosureBuilder::addChainedFixupInfo(ImageWriter& writer, const BuilderLoadedImage& forImage) +{ + // calculate max page starts + __block uint32_t dataPageCount = 1; + forImage.loadAddress()->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) { + if ( info.protections & VM_PROT_WRITE ) { + dataPageCount += ((info.fileSize+4095) / 4096); + } + }); + + // build array of starts + STACK_ALLOC_ARRAY(uint64_t, starts, dataPageCount); + forImage.loadAddress()->forEachChainedFixupStart(_diag, ^(uint64_t runtimeOffset, bool& stop) { + starts.push_back(runtimeOffset); + }); + + // build array of targets + STACK_ALLOC_OVERFLOW_SAFE_ARRAY(Image::ResolvedSymbolTarget, targets, 1024); + STACK_ALLOC_OVERFLOW_SAFE_ARRAY(ResolvedTargetInfo, targetInfos, 1024); + forImage.loadAddress()->forEachChainedFixupTarget(_diag, ^(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop) { + Image::ResolvedSymbolTarget target; + ResolvedTargetInfo targetInfo; + if ( !findSymbol(forImage, libOrdinal, symbolName, weakImport, addend, target, targetInfo) ) { + const char* expectedInPath = forImage.loadAddress()->dependentDylibLoadPath(libOrdinal-1); + _diag.error("symbol '%s' not found, expected in '%s', needed by '%s'", symbolName, expectedInPath, forImage.path()); + stop = true; + return; + } + if ( libOrdinal == BIND_SPECIAL_DYLIB_WEAK_DEF_COALESCE ) { + // add if not already in array + bool alreadyInArray = false; + for (const char* sym : _weakDefsFromChainedBinds) { + if ( strcmp(sym, symbolName) == 0 ) { + alreadyInArray = true; + break; + } + } + if ( !alreadyInArray ) + _weakDefsFromChainedBinds.push_back(symbolName); + } + targets.push_back(target); + targetInfos.push_back(targetInfo); + }); + if ( _diag.hasError() ) + return; + + if ( _handlers != nullptr ) + _handlers->chainedBind(forImage.imageNum, forImage.loadAddress(), starts, targets, targetInfos); + else + writer.setChainedFixups(starts, targets); // store results in Image object + + // with chained fixups, main executable may define symbol that overrides weak-defs but has no fixup + if ( _isLaunchClosure && forImage.loadAddress()->hasWeakDefs() && forImage.loadAddress()->isMainExecutable() ) { + for (const char* weakSymbolName : sTreatAsWeak) { + Diagnostics exportDiag; + dyld3::MachOAnalyzer::FoundSymbol foundInfo; + if ( forImage.loadAddress()->findExportedSymbol(exportDiag, weakSymbolName, foundInfo, nullptr) ) { + _weakDefsFromChainedBinds.push_back(weakSymbolName); + } + } + } +} + + +bool ClosureBuilder::findSymbolInImage(const MachOAnalyzer* macho, const char* symbolName, uint64_t addend, bool followReExports, + Image::ResolvedSymbolTarget& target, ResolvedTargetInfo& targetInfo) +{ + targetInfo.foundInDylib = nullptr; + targetInfo.requestedSymbolName = symbolName; + targetInfo.addend = addend; + targetInfo.isWeakDef = false; + MachOLoaded::DependentToMachOLoaded reexportFinder = ^(const MachOLoaded* mh, uint32_t depIndex) { + return (const MachOLoaded*)findDependent(mh, depIndex); + }; + MachOAnalyzer::DependentToMachOLoaded finder = nullptr; + if ( followReExports ) + finder = reexportFinder; + + dyld3::MachOAnalyzer::FoundSymbol foundInfo; + if ( macho->findExportedSymbol(_diag, symbolName, foundInfo, finder) ) { + const MachOAnalyzer* impDylib = (const MachOAnalyzer*)foundInfo.foundInDylib; + targetInfo.foundInDylib = foundInfo.foundInDylib; + targetInfo.foundSymbolName = foundInfo.foundSymbolName; + if ( foundInfo.isWeakDef ) + targetInfo.isWeakDef = true; + if ( foundInfo.kind == MachOAnalyzer::FoundSymbol::Kind::absolute ) { + target.absolute.kind = Image::ResolvedSymbolTarget::kindAbsolute; + target.absolute.value = foundInfo.value + addend; + } + else if ( impDylib->inDyldCache() ) { + target.sharedCache.kind = Image::ResolvedSymbolTarget::kindSharedCache; + target.sharedCache.offset = (uint8_t*)impDylib - (uint8_t*)_dyldCache + foundInfo.value + addend; + } + else { + target.image.kind = Image::ResolvedSymbolTarget::kindImage; + target.image.imageNum = findLoadedImage(impDylib).imageNum; + target.image.offset = foundInfo.value + addend; + } + return true; + } + return false; +} + +bool ClosureBuilder::findSymbol(const BuilderLoadedImage& fromImage, int libOrdinal, const char* symbolName, bool weakImport, uint64_t addend, + Image::ResolvedSymbolTarget& target, ResolvedTargetInfo& targetInfo) +{ + targetInfo.weakBindCoalese = false; + targetInfo.weakBindSameImage = false; + targetInfo.requestedSymbolName = symbolName; + targetInfo.libOrdinal = libOrdinal; + if ( libOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP ) { + for (const BuilderLoadedImage& li : _loadedImages) { + if ( !li.rtldLocal && findSymbolInImage(li.loadAddress(), symbolName, addend, true, target, targetInfo) ) + return true; + } + if ( weakImport ) { + target.absolute.kind = Image::ResolvedSymbolTarget::kindAbsolute; + target.absolute.value = 0; + return true; + } + _diag.error("symbol '%s' not found, expected in flat namespace by '%s'", symbolName, fromImage.path()); + } + else if ( libOrdinal == BIND_SPECIAL_DYLIB_WEAK_DEF_COALESCE ) { + // to resolve weakDef coalesing, we need to search all images in order and use first definition + // but, if first found is a weakDef, a later non-weak def overrides that + bool foundWeakDefImpl = false; + bool foundStrongDefImpl = false; + bool foundImpl = false; + Image::ResolvedSymbolTarget aTarget; + ResolvedTargetInfo aTargetInfo; + STACK_ALLOC_ARRAY(const BuilderLoadedImage*, cachedDylibsUsingSymbol, 1024); + for (const BuilderLoadedImage& li : _loadedImages) { + // only search images with weak-defs that were not loaded with RTLD_LOCAL + if ( li.loadAddress()->hasWeakDefs() && !li.rtldLocal ) { + if ( findSymbolInImage(li.loadAddress(), symbolName, addend, false, aTarget, aTargetInfo) ) { + foundImpl = true; + // with non-chained images, weak-defs first have a rebase to their local impl, and a weak-bind which allows earlier impls to override + if ( !li.loadAddress()->hasChainedFixups() && (aTargetInfo.foundInDylib == fromImage.loadAddress()) ) + targetInfo.weakBindSameImage = true; + if ( aTargetInfo.isWeakDef ) { + // found a weakDef impl, if this is first found, set target to this + if ( !foundWeakDefImpl && !foundStrongDefImpl ) { + target = aTarget; + targetInfo = aTargetInfo; + } + foundWeakDefImpl = true; + } + else { + // found a non-weak impl, use this (unless early strong found) + if ( !foundStrongDefImpl ) { + target = aTarget; + targetInfo = aTargetInfo; + } + foundStrongDefImpl = true; + } + } + if ( foundImpl && !_makingDyldCacheImages && li.loadAddress()->inDyldCache() ) + cachedDylibsUsingSymbol.push_back(&li); + } + } + // now that final target found, if any dylib in dyld cache uses that symbol name, redirect it to new target + if ( !cachedDylibsUsingSymbol.empty() ) { + for (const BuilderLoadedImage* li : cachedDylibsUsingSymbol) { + Image::ResolvedSymbolTarget implInCache; + ResolvedTargetInfo implInCacheInfo; + if ( findSymbolInImage(li->loadAddress(), symbolName, addend, false, implInCache, implInCacheInfo) ) { + if ( implInCache != target ) { + // found another instance in some dylib in dyld cache, will need to patch it + Closure::PatchEntry patch; + patch.exportCacheOffset = (uint32_t)implInCache.sharedCache.offset; + patch.overriddenDylibInCache = li->imageNum; + patch.replacement = target; + _weakDefCacheOverrides.push_back(patch); + } + } + } + } + targetInfo.weakBindCoalese = true; + + if ( foundImpl ) + return true; + _diag.error("symbol '%s' not found, expected to be weak-def coalesced", symbolName); + } + else { + const BuilderLoadedImage* targetLoadedImage = nullptr; + if ( (libOrdinal > 0) && (libOrdinal <= (int)fromImage.dependents.count()) ) { + ImageNum childNum = fromImage.dependents[libOrdinal - 1].imageNum(); + if ( childNum != kMissingWeakLinkedImage ) { + targetLoadedImage = &findLoadedImage(childNum); + } + } + else if ( libOrdinal == BIND_SPECIAL_DYLIB_SELF ) { + targetLoadedImage = &fromImage; + } + else if ( libOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) { + targetLoadedImage = &_loadedImages[_mainProgLoadIndex]; + } + else { + _diag.error("unknown special ordinal %d in %s", libOrdinal, fromImage.path()); + return false; + } + + if ( targetLoadedImage != nullptr ) { + if ( findSymbolInImage(targetLoadedImage->loadAddress(), symbolName, addend, true, target, targetInfo) ) + return true; + } + + if ( weakImport ) { + target.absolute.kind = Image::ResolvedSymbolTarget::kindAbsolute; + target.absolute.value = 0; + return true; + } + const char* expectedInPath = targetLoadedImage ? targetLoadedImage->path() : "unknown"; + _diag.error("symbol '%s' not found, expected in '%s', needed by '%s'", symbolName, expectedInPath, fromImage.path()); + if ( _launchErrorInfo != nullptr ) { + _launchErrorInfo->kind = DYLD_EXIT_REASON_SYMBOL_MISSING; + _launchErrorInfo->clientOfDylibPath = fromImage.path(); + _launchErrorInfo->targetDylibPath = expectedInPath; + _launchErrorInfo->symbol = symbolName; + } + } + return false; +} + + +void ClosureBuilder::depthFirstRecurseSetInitInfo(uint32_t loadIndex, InitInfo initInfos[], uint32_t& initOrder, bool& hasError) +{ + if ( initInfos[loadIndex].visited ) + return; + initInfos[loadIndex].visited = true; + initInfos[loadIndex].danglingUpward = false; + + if (_loadedImages[loadIndex].isBadImage) { + hasError = true; + return; + } + + for (const Image::LinkedImage& dep : _loadedImages[loadIndex].dependents) { + if ( dep.imageNum() == kMissingWeakLinkedImage ) + continue; + ClosureBuilder::BuilderLoadedImage& depLi = findLoadedImage(dep.imageNum()); + uint32_t depLoadIndex = (uint32_t)_loadedImages.index(depLi); + if ( dep.kind() == Image::LinkKind::upward ) { + if ( !initInfos[depLoadIndex].visited ) + initInfos[depLoadIndex].danglingUpward = true; + } + else { + depthFirstRecurseSetInitInfo(depLoadIndex, initInfos, initOrder, hasError); + if (hasError) + return; + } + } + initInfos[loadIndex].initOrder = initOrder++; +} + +void ClosureBuilder::computeInitOrder(ImageWriter& imageWriter, uint32_t loadIndex) +{ + // allocate array to track initializers + InitInfo initInfos[_loadedImages.count()]; + bzero(initInfos, sizeof(initInfos)); + + // recurse all images and build initializer list from bottom up + uint32_t initOrder = 1; + bool hasMissingDependent = false; + depthFirstRecurseSetInitInfo(loadIndex, initInfos, initOrder, hasMissingDependent); + if (hasMissingDependent) { + imageWriter.setInvalid(); + return; + } + + // any images not visited yet are are danging, force add them to end of init list + for (uint32_t i=0; i < (uint32_t)_loadedImages.count(); ++i) { + if ( !initInfos[i].visited && initInfos[i].danglingUpward ) { + depthFirstRecurseSetInitInfo(i, initInfos, initOrder, hasMissingDependent); + } + } + + if (hasMissingDependent) { + imageWriter.setInvalid(); + return; + } + + // build array of just images with initializer + STACK_ALLOC_ARRAY(uint32_t, indexOfImagesWithInits, _loadedImages.count()); + uint32_t index = 0; + for (const BuilderLoadedImage& li : _loadedImages) { + if ( initInfos[index].visited && li.hasInits ) { + indexOfImagesWithInits.push_back(index); + } + ++index; + } + + // bubble sort (FIXME) + if ( indexOfImagesWithInits.count() > 1 ) { + for (uint32_t i=0; i < indexOfImagesWithInits.count()-1; ++i) { + for (uint32_t j=0; j < indexOfImagesWithInits.count()-i-1; ++j) { + if ( initInfos[indexOfImagesWithInits[j]].initOrder > initInfos[indexOfImagesWithInits[j+1]].initOrder ) { + uint32_t temp = indexOfImagesWithInits[j]; + indexOfImagesWithInits[j] = indexOfImagesWithInits[j+1]; + indexOfImagesWithInits[j+1] = temp; + } + } + } + } + + // copy ImageNum of each image with initializers into array + ImageNum initNums[indexOfImagesWithInits.count()]; + for (uint32_t i=0; i < indexOfImagesWithInits.count(); ++i) { + initNums[i] = _loadedImages[indexOfImagesWithInits[i]].imageNum; + } + + // add to closure info + imageWriter.setInitsOrder(initNums, (uint32_t)indexOfImagesWithInits.count()); +} + +void ClosureBuilder::addCachePatchInfo(ImageWriter& imageWriter, const BuilderLoadedImage& forImage) +{ + assert(_handlers != nullptr); + _handlers->forEachExportsPatch(forImage.imageNum, ^(const CacheDylibsBindingHandlers::PatchInfo& info) { + assert(info.usesCount != 0); + imageWriter.addExportPatchInfo(info.exportCacheOffset, info.exportSymbolName, info.usesCount, info.usesArray); + }); +} + +void ClosureBuilder::addClosureInfo(LaunchClosureWriter& closureWriter) +{ + // record which is libSystem + assert(_libSystemImageNum != 0); + closureWriter.setLibSystemImageNum(_libSystemImageNum); + + // record which is libdyld + assert(_libDyldImageNum != 0); + Image::ResolvedSymbolTarget entryLocation; + ResolvedTargetInfo entryInfo; + if ( findSymbolInImage(findLoadedImage(_libDyldImageNum).loadAddress(), "__ZN5dyld318entryVectorForDyldE", 0, false, entryLocation, entryInfo) ) { + const dyld3::LibDyldEntryVector* libDyldEntry = nullptr; + switch ( entryLocation.image.kind ) { + case Image::ResolvedSymbolTarget::kindSharedCache: + libDyldEntry = (dyld3::LibDyldEntryVector*)((uint8_t*)_dyldCache + entryLocation.sharedCache.offset); + break; + case Image::ResolvedSymbolTarget::kindImage: + libDyldEntry = (dyld3::LibDyldEntryVector*)((uint8_t*)findLoadedImage(entryLocation.image.imageNum).loadAddress() + entryLocation.image.offset); + break; + } + if ( (libDyldEntry != nullptr) && (libDyldEntry->binaryFormatVersion == dyld3::closure::kFormatVersion) ) + closureWriter.setLibDyldEntry(entryLocation); + else + _diag.error("libdyld.dylib entry vector is incompatible"); + } + else { + _diag.error("libdyld.dylib is missing entry vector"); + } + + // record which is main executable + ImageNum mainProgImageNum = _loadedImages[_mainProgLoadIndex].imageNum; + closureWriter.setTopImageNum(mainProgImageNum); + + // add entry + uint32_t entryOffset; + bool usesCRT; + if ( _loadedImages[_mainProgLoadIndex].loadAddress()->getEntry(entryOffset, usesCRT) ) { + Image::ResolvedSymbolTarget location; + location.image.kind = Image::ResolvedSymbolTarget::kindImage; + location.image.imageNum = mainProgImageNum; + location.image.offset = entryOffset; + if ( usesCRT ) + closureWriter.setStartEntry(location); + else + closureWriter.setMainEntry(location); + } + + // add env vars that must match at launch time + _pathOverrides.forEachEnvVar(^(const char* envVar) { + closureWriter.addEnvVar(envVar); + }); + + // add list of files which must be missing + STACK_ALLOC_ARRAY(const char*, paths, 8192); + if ( _mustBeMissingPaths != nullptr ) { + _mustBeMissingPaths->forEachPath(^(const char* aPath) { + paths.push_back(aPath); + }); + } + closureWriter.setMustBeMissingFiles(paths); +} + + +// used at launch by dyld when kernel has already mapped main executable +const LaunchClosure* ClosureBuilder::makeLaunchClosure(const LoadedFileInfo& fileInfo, bool allowInsertFailures) +{ + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_BUILD_CLOSURE, 0, 0, 0); + const mach_header* mainMH = (const mach_header*)fileInfo.fileContent; + // set up stack based storage for all arrays + BuilderLoadedImage loadImagesStorage[512]; + Image::LinkedImage dependenciesStorage[512*8]; + InterposingTuple tuplesStorage[64]; + Closure::PatchEntry cachePatchStorage[64]; + const char* weakDefNameStorage[64]; + _loadedImages.setInitialStorage(loadImagesStorage, 512); + _dependencies.setInitialStorage(dependenciesStorage, 512*8); + _interposingTuples.setInitialStorage(tuplesStorage, 64); + _weakDefCacheOverrides.setInitialStorage(cachePatchStorage, 64); + _weakDefsFromChainedBinds.setInitialStorage(weakDefNameStorage, 64); + ArrayFinalizer scopedCleanup(_loadedImages, ^(BuilderLoadedImage& li) { if (li.unmapWhenDone) {_fileSystem.unloadFile(li.loadedFileInfo); li.unmapWhenDone=false;} }); + + const MachOAnalyzer* mainExecutable = MachOAnalyzer::validMainExecutable(_diag, mainMH, fileInfo.path, fileInfo.sliceLen, _archName, _platform); + if ( mainExecutable == nullptr ) + return nullptr; + if ( !mainExecutable->isDynamicExecutable() ) { + _diag.error("not a main executable"); + return nullptr; + } + _isLaunchClosure = true; + + // add any DYLD_INSERT_LIBRARIES + _nextIndex = 0; + _pathOverrides.forEachInsertedDylib(^(const char* dylibPath) { + BuilderLoadedImage insertEntry; + insertEntry.loadedFileInfo.path = strdup_temp(dylibPath); + insertEntry.imageNum = _startImageNum + _nextIndex++; + insertEntry.unmapWhenDone = true; + insertEntry.contentRebased = false; + insertEntry.hasInits = false; + insertEntry.markNeverUnload = true; + insertEntry.rtldLocal = false; + insertEntry.isBadImage = false; + insertEntry.overrideImageNum = 0; + _loadedImages.push_back(insertEntry); + }); + _mainProgLoadIndex = (uint32_t)_loadedImages.count(); + + // add main executable + BuilderLoadedImage mainEntry; + mainEntry.loadedFileInfo = fileInfo; + mainEntry.imageNum = _startImageNum + _nextIndex++; + mainEntry.unmapWhenDone = false; + mainEntry.contentRebased = false; + mainEntry.hasInits = false; + mainEntry.markNeverUnload = true; + mainEntry.rtldLocal = false; + mainEntry.isBadImage = false; + mainEntry.overrideImageNum = 0; + _loadedImages.push_back(mainEntry); + + // get mach_headers for all images needed to launch this main executable + LoadedImageChain chainStart = { nullptr, _loadedImages[_mainProgLoadIndex] }; + recursiveLoadDependents(chainStart); + if ( _diag.hasError() ) + return nullptr; + for (uint32_t i=0; i < _mainProgLoadIndex; ++i) { + closure::LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(_diag, _fileSystem, _loadedImages[i].loadedFileInfo.path, _archName, _platform); + const char* originalLoadPath = _loadedImages[i].loadedFileInfo.path; + _loadedImages[i].loadedFileInfo = loadedFileInfo; + if ( _loadedImages[i].loadAddress() != nullptr ) { + LoadedImageChain insertChainStart = { nullptr, _loadedImages[i] }; + recursiveLoadDependents(insertChainStart); + } + if ( _diag.hasError() || (_loadedImages[i].loadAddress() == nullptr) ) { + if ( !allowInsertFailures ) { + if ( _diag.noError() ) + _diag.error("could not load inserted dylib %s", originalLoadPath); + return nullptr; + } + _diag.clearError(); // FIXME add way to plumb back warning + // remove slot for inserted image that could not loaded + _loadedImages.remove(i); + i -= 1; + _mainProgLoadIndex -= 1; + _nextIndex -= 1; + // renumber images in this closure + for (uint32_t j=i+1; j < _loadedImages.count(); ++j) { + if ( (_loadedImages[j].imageNum >= _startImageNum) && (_loadedImages[j].imageNum <= _startImageNum+_nextIndex) ) + _loadedImages[j].imageNum -= 1; + } + } + } + loadDanglingUpwardLinks(); + + // only some images need to go into closure (ones from dyld cache do not) + STACK_ALLOC_ARRAY(ImageWriter, writers, _loadedImages.count()); + for (BuilderLoadedImage& li : _loadedImages) { + if ( li.imageNum >= _startImageNum ) { + writers.push_back(ImageWriter()); + buildImage(writers.back(), li); + if ( _diag.hasError() ) + return nullptr; + } + if ( li.loadAddress()->isDylib() && (strcmp(li.loadAddress()->installName(), "/usr/lib/system/libdyld.dylib") == 0) ) + _libDyldImageNum = li.imageNum; + else if ( strcmp(li.path(), "/usr/lib/libSystem.B.dylib") == 0 ) + _libSystemImageNum = li.imageNum; + } + + // add initializer order into top level Images (may be inserted dylibs before main executable) + for (uint32_t i=0; i <= _mainProgLoadIndex; ++i) + computeInitOrder(writers[i], i); + + // combine all Image objects into one ImageArray + ImageArrayWriter imageArrayWriter(_startImageNum, (uint32_t)writers.count()); + for (ImageWriter& writer : writers) { + imageArrayWriter.appendImage(writer.finalize()); + writer.deallocate(); + } + const ImageArray* imageArray = imageArrayWriter.finalize(); + + // merge ImageArray object into LaunchClosure object + __block LaunchClosureWriter closureWriter(imageArray); + + // record shared cache info + if ( _dyldCache != nullptr ) { + // record cache UUID + uuid_t cacheUUID; + _dyldCache->getUUID(cacheUUID); + closureWriter.setDyldCacheUUID(cacheUUID); + + // record any cache patching needed because of dylib overriding cache + for (const BuilderLoadedImage& li : _loadedImages) { + if ( li.overrideImageNum != 0 ) { + const Image* cacheImage = _dyldImageArray->imageForNum(li.overrideImageNum); + STACK_ALLOC_ARRAY(Closure::PatchEntry, patches, cacheImage->patchableExportCount()); + //fprintf(stderr, "'%s' overrides '%s'\n", li.loadedFileInfo.path, cacheImage->path()); + cacheImage->forEachPatchableExport(^(uint32_t cacheOffsetOfImpl, const char* symbolName) { + dyld3::MachOAnalyzer::FoundSymbol foundInfo; + Diagnostics patchDiag; + Closure::PatchEntry patch; + patch.overriddenDylibInCache = li.overrideImageNum; + patch.exportCacheOffset = cacheOffsetOfImpl; + if ( li.loadAddress()->findExportedSymbol(patchDiag, symbolName, foundInfo, nullptr) ) { + patch.replacement.image.kind = Image::ResolvedSymbolTarget::kindImage; + patch.replacement.image.imageNum = li.imageNum; + patch.replacement.image.offset = foundInfo.value; + } + else { + // this means the symbol is missing in the cache override dylib, so set any uses to NULL + patch.replacement.absolute.kind = Image::ResolvedSymbolTarget::kindAbsolute; + patch.replacement.absolute.value = 0; + } + patches.push_back(patch); + }); + closureWriter.addCachePatches(patches); + } + } + + // handle any extra weak-def coalescing needed by chained fixups + if ( !_weakDefsFromChainedBinds.empty() ) { + for (const char* symbolName : _weakDefsFromChainedBinds) { + Image::ResolvedSymbolTarget cacheOverrideTarget; + bool haveCacheOverride = false; + bool foundCachOverrideIsWeakDef = false; + for (const BuilderLoadedImage& li : _loadedImages) { + if ( !li.loadAddress()->hasWeakDefs() ) + continue; + Image::ResolvedSymbolTarget target; + ResolvedTargetInfo targetInfo; + if ( findSymbolInImage(li.loadAddress(), symbolName, 0, false, target, targetInfo) ) { + if ( li.loadAddress()->inDyldCache() ) { + if ( haveCacheOverride ) { + Closure::PatchEntry patch; + patch.exportCacheOffset = (uint32_t)target.sharedCache.offset; + patch.overriddenDylibInCache = li.imageNum; + patch.replacement = cacheOverrideTarget; + _weakDefCacheOverrides.push_back(patch); + } + else { + // found first in cached dylib, so no need to patch cache for this symbol + break; + } + } + else { + // found image that exports this symbol and is not in cache + if ( !haveCacheOverride || (foundCachOverrideIsWeakDef && !targetInfo.isWeakDef) ) { + // update cache to use this symbol if it if first found or it is first non-weak found + cacheOverrideTarget = target; + foundCachOverrideIsWeakDef = targetInfo.isWeakDef; + haveCacheOverride = true; + } + } + } + } + } + } + + // record any cache patching needed because weak-def C++ symbols override dyld cache + if ( !_weakDefCacheOverrides.empty() ) + closureWriter.addCachePatches(_weakDefCacheOverrides); + + } + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + // if closure is built on-device for iOS, then record boot UUID + char bootSessionUUID[256] = { 0 }; + size_t bootSize = sizeof(bootSessionUUID); + if ( sysctlbyname("kern.bootsessionuuid", bootSessionUUID, &bootSize, NULL, 0) == 0 ) + closureWriter.setBootUUID(bootSessionUUID); +#endif + + // record any interposing info + imageArray->forEachImage(^(const Image* image, bool &stop) { + if ( !image->inDyldCache() ) + addInterposingTuples(closureWriter, image, findLoadedImage(image->imageNum()).loadAddress()); + }); + + // modify fixups in contained Images by applying interposing tuples + closureWriter.applyInterposing(); + + // set flags + closureWriter.setUsedAtPaths(_atPathUsed); + closureWriter.setUsedFallbackPaths(_fallbackPathUsed); + closureWriter.setInitImageCount((uint32_t)_loadedImages.count()); + + // add other closure attributes + addClosureInfo(closureWriter); + + // make result + const LaunchClosure* result = closureWriter.finalize(); + imageArrayWriter.deallocate(); + + return result; +} + +// used by libdyld for dlopen() +const DlopenClosure* ClosureBuilder::makeDlopenClosure(const char* path, const LaunchClosure* mainClosure, const Array& alreadyLoadedList, + closure::ImageNum callerImageNum, bool noLoad, bool canUseSharedCacheClosure, closure::ImageNum* topImageNum) +{ + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_BUILD_CLOSURE, 0, 0, 0); + // set up stack based storage for all arrays + BuilderLoadedImage loadImagesStorage[512]; + Image::LinkedImage dependenciesStorage[512*8]; + Closure::PatchEntry cachePatchStorage[64]; + _loadedImages.setInitialStorage(loadImagesStorage, 512); + _dependencies.setInitialStorage(dependenciesStorage, 512*8); + _weakDefCacheOverrides.setInitialStorage(cachePatchStorage, 64); + ArrayFinalizer scopedCleanup(_loadedImages, ^(BuilderLoadedImage& li) { if (li.unmapWhenDone) {_fileSystem.unloadFile(li.loadedFileInfo); li.unmapWhenDone=false;} }); + + // fill in builder array from already loaded images + bool cachedDylibsExpectedOnDisk = _dyldCache ? _dyldCache->header.dylibsExpectedOnDisk : true; + uintptr_t callerImageIndex = UINTPTR_MAX; + for (const LoadedImage& ali : alreadyLoadedList) { + const Image* image = ali.image(); + const MachOAnalyzer* ma = (MachOAnalyzer*)(ali.loadedAddress()); + bool inDyldCache = ma->inDyldCache(); + BuilderLoadedImage entry; + ImageNum overrideImageNum; + entry.loadedFileInfo.path = image->path(); + entry.loadedFileInfo.fileContent = ma; + entry.loadedFileInfo.sliceOffset = 0; + entry.loadedFileInfo.inode = 0; + entry.loadedFileInfo.mtime = 0; + entry.imageNum = image->imageNum(); + entry.dependents = image->dependentsArray(); + entry.unmapWhenDone = false; + entry.contentRebased = inDyldCache; + entry.hasInits = false; + entry.markNeverUnload = image->neverUnload(); + entry.rtldLocal = ali.hideFromFlatSearch(); + entry.isBadImage = false; + entry.overrideImageNum = 0; + if ( !inDyldCache && image->isOverrideOfDyldCacheImage(overrideImageNum) ) { + entry.overrideImageNum = overrideImageNum; + canUseSharedCacheClosure = false; + } + if ( !inDyldCache || cachedDylibsExpectedOnDisk ) + image->hasFileModTimeAndInode(entry.loadedFileInfo.inode, entry.loadedFileInfo.mtime); + if ( entry.imageNum == callerImageNum ) + callerImageIndex = _loadedImages.count(); + _loadedImages.push_back(entry); + } + _alreadyInitedIndex = (uint32_t)_loadedImages.count(); + + // find main executable (may be needed for @executable_path) + _isLaunchClosure = false; + for (uint32_t i=0; i < alreadyLoadedList.count(); ++i) { + if ( _loadedImages[i].loadAddress()->isMainExecutable() ) { + _mainProgLoadIndex = i; + break; + } + } + + // add top level dylib being dlopen()ed + BuilderLoadedImage* foundTopImage; + _nextIndex = 0; + // @rpath has caller's LC_PRATH, then main executable's LC_RPATH + BuilderLoadedImage& callerImage = (callerImageIndex != UINTPTR_MAX) ? _loadedImages[callerImageIndex] : _loadedImages[_mainProgLoadIndex]; + LoadedImageChain chainCaller = { nullptr, callerImage }; + LoadedImageChain chainMain = { &chainCaller, _loadedImages[_mainProgLoadIndex] }; + if ( !findImage(path, chainMain, foundTopImage, false, canUseSharedCacheClosure) ) { + // If we didn't find the image, but its a shared cache path, then try again with realpath. + if ( (strncmp(path, "/usr/lib/", 9) == 0) || (strncmp(path, "/System/Library/", 16) == 0) ) { + char resolvedPath[PATH_MAX]; + if ( _fileSystem.getRealPath(path, resolvedPath) ) { + if ( !findImage(resolvedPath, chainMain, foundTopImage, false, canUseSharedCacheClosure) ) { + return nullptr; + } + } else { + // We didn't find a new path from realpath + return nullptr; + } + } else { + // Not in /usr/lib/ or /System/Library/ + return nullptr; + } + } + + // exit early in RTLD_NOLOAD mode + if ( noLoad ) { + // if no new images added to _loadedImages, then requested path was already loaded + if ( (uint32_t)_loadedImages.count() == _alreadyInitedIndex ) + *topImageNum = foundTopImage->imageNum; + else + *topImageNum = 0; + return nullptr; + } + + // fast path if roots are not allowed and target is in dyld cache or is other + if ( (_dyldCache != nullptr) && (_dyldCache->header.cacheType == kDyldSharedCacheTypeProduction) ) { + if ( foundTopImage->imageNum < closure::kFirstLaunchClosureImageNum ) { + *topImageNum = foundTopImage->imageNum; + return nullptr; + } + } + + // recursive load dependents + // @rpath for stuff top dylib depends on uses LC_RPATH from caller, main exe, and dylib being dlopen()ed + LoadedImageChain chainTopDylib = { &chainMain, *foundTopImage }; + recursiveLoadDependents(chainTopDylib); + if ( _diag.hasError() ) + return nullptr; + loadDanglingUpwardLinks(); + + // only some images need to go into closure (ones from dyld cache do not) + STACK_ALLOC_ARRAY(ImageWriter, writers, _loadedImages.count()); + for (BuilderLoadedImage& li : _loadedImages) { + if ( li.imageNum >= _startImageNum ) { + writers.push_back(ImageWriter()); + buildImage(writers.back(), li); + } + } + + // check if top image loaded is in shared cache along with everything it depends on + *topImageNum = foundTopImage->imageNum; + if ( writers.count() == 0 ) { + return nullptr; + } else if ( canUseSharedCacheClosure && ( foundTopImage->imageNum < closure::kFirstLaunchClosureImageNum ) ) { + // We used a shared cache built closure, but now discovered roots. We need to try again + topImageNum = 0; + return sRetryDlopenClosure; + } + + // add initializer order into top level Image + computeInitOrder(writers[0], (uint32_t)alreadyLoadedList.count()); + + // combine all Image objects into one ImageArray + ImageArrayWriter imageArrayWriter(_startImageNum, (uint32_t)writers.count()); + for (ImageWriter& writer : writers) { + imageArrayWriter.appendImage(writer.finalize()); + writer.deallocate(); + } + const ImageArray* imageArray = imageArrayWriter.finalize(); + + // merge ImageArray object into LaunchClosure object + DlopenClosureWriter closureWriter(imageArray); + + // add other closure attributes + closureWriter.setTopImageNum(foundTopImage->imageNum); + + // record any cache patching needed because of dylib overriding cache + if ( _dyldCache != nullptr ) { + for (const BuilderLoadedImage& li : _loadedImages) { + if ( (li.overrideImageNum != 0) && (li.imageNum >= _startImageNum) ) { + const Image* cacheImage = _dyldImageArray->imageForNum(li.overrideImageNum); + STACK_ALLOC_ARRAY(Closure::PatchEntry, patches, cacheImage->patchableExportCount()); + //fprintf(stderr, "'%s' overrides '%s'\n", li.loadedFileInfo.path, cacheImage->path()); + cacheImage->forEachPatchableExport(^(uint32_t cacheOffsetOfImpl, const char* symbolName) { + dyld3::MachOAnalyzer::FoundSymbol foundInfo; + Diagnostics patchDiag; + Closure::PatchEntry patch; + patch.overriddenDylibInCache = li.overrideImageNum; + patch.exportCacheOffset = cacheOffsetOfImpl; + if ( li.loadAddress()->findExportedSymbol(patchDiag, symbolName, foundInfo, nullptr) ) { + patch.replacement.image.kind = Image::ResolvedSymbolTarget::kindImage; + patch.replacement.image.imageNum = li.imageNum; + patch.replacement.image.offset = foundInfo.value; + } + else { + patch.replacement.absolute.kind = Image::ResolvedSymbolTarget::kindAbsolute; + patch.replacement.absolute.value = 0; + } + patches.push_back(patch); + }); + closureWriter.addCachePatches(patches); + } + } + } + + // Dlopen's should never keep track of missing paths as we don't cache these closures. + assert(_mustBeMissingPaths == nullptr); + + // make final DlopenClosure object + const DlopenClosure* result = closureWriter.finalize(); + imageArrayWriter.deallocate(); + return result; +} + + +// used by dyld_closure_util +const LaunchClosure* ClosureBuilder::makeLaunchClosure(const char* mainPath, bool allowInsertFailures) +{ + closure::LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(_diag, _fileSystem, mainPath, _archName, _platform); + const MachOAnalyzer* mh = (const MachOAnalyzer*)loadedFileInfo.fileContent; + loadedFileInfo.path = mainPath; + if (_diag.hasError()) + return nullptr; + if (mh == nullptr) { + _diag.error("could not load file"); + return nullptr; + } + if (!mh->isDynamicExecutable()) { + _diag.error("file is not an executable"); + return nullptr; + } + const_cast(&_pathOverrides)->setMainExecutable(mh, mainPath); + const LaunchClosure* launchClosure = makeLaunchClosure(loadedFileInfo, allowInsertFailures); + loadedFileInfo.unload(loadedFileInfo); + return launchClosure; +} + + +// used by dyld shared cache builder +const ImageArray* ClosureBuilder::makeDyldCacheImageArray(bool customerCache, const Array& dylibs, const Array& aliases) +{ + // because this is run in cache builder using dispatch_apply() there is minimal stack space + // so set up storage for all arrays to be vm_allocated + uintptr_t maxImageCount = dylibs.count() + 16; + _loadedImages.reserve(maxImageCount); + _dependencies.reserve(maxImageCount*16); + + _makingDyldCacheImages = true; + _makingCustomerCache = customerCache; + _aliases = &aliases; + + // build _loadedImages[] with every dylib in cache + __block ImageNum imageNum = _startImageNum; + for (const CachedDylibInfo& aDylibInfo : dylibs) { + BuilderLoadedImage entry; + entry.loadedFileInfo = aDylibInfo.fileInfo; + entry.imageNum = imageNum++; + entry.unmapWhenDone = false; + entry.contentRebased = false; + entry.hasInits = false; + entry.markNeverUnload = true; + entry.rtldLocal = false; + entry.isBadImage = false; + entry.overrideImageNum = 0; + _loadedImages.push_back(entry); + } + + // wire up dependencies between cached dylibs + for (BuilderLoadedImage& li : _loadedImages) { + LoadedImageChain chainStart = { nullptr, li }; + recursiveLoadDependents(chainStart); + if ( _diag.hasError() ) + break; + } + assert(_loadedImages.count() == dylibs.count()); + + // create an ImageWriter for each cached dylib + STACK_ALLOC_ARRAY(ImageWriter, writers, _loadedImages.count()); + for (BuilderLoadedImage& li : _loadedImages) { + writers.push_back(ImageWriter()); + buildImage(writers.back(), li); + } + + // add initializer order into each dylib + for (const BuilderLoadedImage& li : _loadedImages) { + uint32_t index = li.imageNum - _startImageNum; + computeInitOrder(writers[index], index); + } + + // add exports patch info for each dylib + for (const BuilderLoadedImage& li : _loadedImages) { + uint32_t index = li.imageNum - _startImageNum; + addCachePatchInfo(writers[index], li); + } + + // combine all Image objects into one ImageArray + ImageArrayWriter imageArrayWriter(_startImageNum, (uint32_t)writers.count()); + for (ImageWriter& writer : writers) { + imageArrayWriter.appendImage(writer.finalize()); + writer.deallocate(); + } + const ImageArray* imageArray = imageArrayWriter.finalize(); + + return imageArray; +} + + +#if BUILDING_CACHE_BUILDER +const ImageArray* ClosureBuilder::makeOtherDylibsImageArray(const Array& otherDylibs, uint32_t cachedDylibsCount) +{ + // because this is run in cache builder using dispatch_apply() there is minimal stack space + // so set up storage for all arrays to be vm_allocated + uintptr_t maxImageCount = otherDylibs.count() + cachedDylibsCount + 128; + _loadedImages.reserve(maxImageCount); + _dependencies.reserve(maxImageCount*16); + + // build _loadedImages[] with every dylib in cache, followed by others + _nextIndex = 0; + for (const LoadedFileInfo& aDylibInfo : otherDylibs) { + BuilderLoadedImage entry; + entry.loadedFileInfo = aDylibInfo; + entry.imageNum = _startImageNum + _nextIndex++; + entry.unmapWhenDone = false; + entry.contentRebased = false; + entry.hasInits = false; + entry.markNeverUnload = false; + entry.rtldLocal = false; + entry.isBadImage = false; + entry.overrideImageNum = 0; + _loadedImages.push_back(entry); + } + + // wire up dependencies between cached dylibs + // Note, _loadedImages can grow when we call recursiveLoadDependents so we need + // to check the count on each iteration. + for (uint64_t index = 0; index != _loadedImages.count(); ++index) { + BuilderLoadedImage& li = _loadedImages[index]; + LoadedImageChain chainStart = { nullptr, li }; + recursiveLoadDependents(chainStart); + if ( _diag.hasError() ) { + _diag.warning("while building dlopen closure for %s: %s", li.loadedFileInfo.path, _diag.errorMessage().c_str()); + //fprintf(stderr, "while building dlopen closure for %s: %s\n", li.loadedFileInfo.path, _diag.errorMessage().c_str()); + _diag.clearError(); + li.isBadImage = true; // mark bad + } + } + + auto invalidateBadImages = [&]() { + // Invalidate images with bad dependencies + while (true) { + bool madeChange = false; + for (BuilderLoadedImage& li : _loadedImages) { + if (li.isBadImage) { + // Already invalidated + continue; + } + for (Image::LinkedImage depIndex : li.dependents) { + if ( depIndex.imageNum() == kMissingWeakLinkedImage ) + continue; + if ( depIndex.imageNum() < dyld3::closure::kLastDyldCacheImageNum ) + continue; + BuilderLoadedImage& depImage = findLoadedImage(depIndex.imageNum()); + if (depImage.isBadImage) { + _diag.warning("while building dlopen closure for %s: dependent dylib had error", li.loadedFileInfo.path); + li.isBadImage = true; // mark bad + madeChange = true; + } + } + } + if (!madeChange) + break; + } + }; + + invalidateBadImages(); + + // create an ImageWriter for each cached dylib + STACK_ALLOC_ARRAY(ImageWriter, writers, _loadedImages.count()); + for (BuilderLoadedImage& li : _loadedImages) { + if ( li.imageNum == 0 ) { + writers.push_back(ImageWriter()); + writers.back().setInvalid(); + continue; + } + if ( li.imageNum < dyld3::closure::kLastDyldCacheImageNum ) + continue; + writers.push_back(ImageWriter()); + buildImage(writers.back(), li); + if ( _diag.hasError() ) { + _diag.warning("while building dlopen closure for %s: %s", li.loadedFileInfo.path, _diag.errorMessage().c_str()); + //fprintf(stderr, "while building dlopen closure for %s: %s\n", li.loadedFileInfo.path, _diag.errorMessage().c_str()); + _diag.clearError(); + li.isBadImage = true; // mark bad + writers.back().setInvalid(); + } + } + + invalidateBadImages(); + + // add initializer order into each dylib + for (const BuilderLoadedImage& li : _loadedImages) { + if ( li.imageNum < dyld3::closure::kLastDyldCacheImageNum ) + continue; + if (li.isBadImage) + continue; + uint32_t index = li.imageNum - _startImageNum; + computeInitOrder(writers[index], index); + } + + // combine all Image objects into one ImageArray + ImageArrayWriter imageArrayWriter(_startImageNum, (uint32_t)writers.count()); + for (ImageWriter& writer : writers) { + imageArrayWriter.appendImage(writer.finalize()); + writer.deallocate(); + } + const ImageArray* imageArray = imageArrayWriter.finalize(); + + return imageArray; +} +#endif + + +bool ClosureBuilder::inLoadedImageArray(const Array& loadedList, ImageNum imageNum) +{ + for (const LoadedImage& ali : loadedList) { + if ( ali.image()->representsImageNum(imageNum) ) + return true; + } + return false; +} + +void ClosureBuilder::buildLoadOrderRecurse(Array& loadedList, const Array& imagesArrays, const Image* image) +{ + // breadth first load + STACK_ALLOC_ARRAY(const Image*, needToRecurse, 256); + image->forEachDependentImage(^(uint32_t dependentIndex, dyld3::closure::Image::LinkKind kind, ImageNum depImageNum, bool &stop) { + if ( !inLoadedImageArray(loadedList, depImageNum) ) { + const Image* depImage = ImageArray::findImage(imagesArrays, depImageNum); + loadedList.push_back(LoadedImage::make(depImage)); + needToRecurse.push_back(depImage); + } + }); + + // recurse load + for (const Image* img : needToRecurse) { + buildLoadOrderRecurse(loadedList, imagesArrays, img); + } +} + +void ClosureBuilder::buildLoadOrder(Array& loadedList, const Array& imagesArrays, const Closure* toAdd) +{ + const dyld3::closure::Image* topImage = ImageArray::findImage(imagesArrays, toAdd->topImage()); + loadedList.push_back(LoadedImage::make(topImage)); + buildLoadOrderRecurse(loadedList, imagesArrays, topImage); +} + + + +} // namespace closure +} // namespace dyld3 diff --git a/dyld3/ClosureBuilder.h b/dyld3/ClosureBuilder.h new file mode 100644 index 0000000..dfb9b2b --- /dev/null +++ b/dyld3/ClosureBuilder.h @@ -0,0 +1,250 @@ +/* + * 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 ClosureBuilder_h +#define ClosureBuilder_h + + +#include "Closure.h" +#include "ClosureFileSystem.h" +#include "ClosureWriter.h" +#include "PathOverrides.h" +#include "DyldSharedCache.h" +#include "MachOAnalyzer.h" +#include "Loading.h" + + + +namespace dyld3 { + + +namespace closure { + + + +class VIS_HIDDEN ClosureBuilder +{ +public: + + struct LaunchErrorInfo + { + uintptr_t kind; + const char* clientOfDylibPath; + const char* targetDylibPath; + const char* symbol; + }; + + struct ResolvedTargetInfo + { + const MachOLoaded* foundInDylib; + const char* requestedSymbolName; + const char* foundSymbolName; + uint64_t addend; + bool weakBindCoalese; + bool weakBindSameImage; + bool isWeakDef; + int libOrdinal; + }; + + typedef Image::PatchableExport::PatchLocation PatchLocation; + + struct CacheDylibsBindingHandlers + { + struct PatchInfo + { + const char* exportSymbolName; + uint32_t exportCacheOffset; + uint32_t usesCount; + const PatchLocation* usesArray; + }; + + void (^rebase)(ImageNum, const MachOLoaded* imageToFix, uint32_t runtimeOffset); + void (^bind)(ImageNum, const MachOLoaded* imageToFix, uint32_t runtimeOffset, Image::ResolvedSymbolTarget target, const ResolvedTargetInfo& targetInfo); + void (^chainedBind)(ImageNum, const MachOLoaded*, const Array& starts, const Array& targets, const Array& targetInfos); + void (^forEachExportsPatch)(ImageNum, void (^handler)(const PatchInfo&)); + }; + + enum class AtPath { none, all, onlyInRPaths }; + + ClosureBuilder(uint32_t startImageNum, const FileSystem& fileSystem, const DyldSharedCache* dyldCache, bool dyldCacheIsLive, + const PathOverrides& pathOverrides, AtPath atPathHandling=AtPath::all, + LaunchErrorInfo* errorInfo=nullptr, + const char* archName=MachOFile::currentArchName(), Platform platform=MachOFile::currentPlatform(), + const CacheDylibsBindingHandlers* handlers=nullptr); + ~ClosureBuilder(); + Diagnostics& diagnostics() { return _diag; } + + const LaunchClosure* makeLaunchClosure(const LoadedFileInfo& fileInfo, bool allowInsertFailures); + + const LaunchClosure* makeLaunchClosure(const char* mainPath,bool allowInsertFailures); + + + static const DlopenClosure* sRetryDlopenClosure; + const DlopenClosure* makeDlopenClosure(const char* dylibPath, const LaunchClosure* mainClosure, const Array& loadedList, + closure::ImageNum callerImageNum, bool noLoad, bool canUseSharedCacheClosure, + closure::ImageNum* topImageNum); + + ImageNum nextFreeImageNum() const { return _startImageNum + _nextIndex; } + + + struct PatchableExport + { + uint32_t cacheOffsetOfImpl; + uint32_t cacheOffsetOfName; + uint32_t patchLocationsCount; + const PatchLocation* patchLocations; + }; + + struct CachedDylibInfo + { + LoadedFileInfo fileInfo; + }; + + struct CachedDylibAlias + { + const char* realPath; + const char* aliasPath; + }; + + const ImageArray* makeDyldCacheImageArray(bool customerCache, const Array& dylibs, const Array& aliases); + + const ImageArray* makeOtherDylibsImageArray(const Array& otherDylibs, uint32_t cachedDylibsCount); + + static void buildLoadOrder(Array& loadedList, const Array& imagesArrays, const Closure* toAdd); + +private: + + + struct InitInfo + { + uint32_t initOrder; + bool danglingUpward; + bool visited; + }; + + struct BuilderLoadedImage + { + Array dependents; + ImageNum imageNum; + uint32_t unmapWhenDone : 1, + contentRebased : 1, + hasInits : 1, + markNeverUnload : 1, + rtldLocal : 1, + isBadImage : 1, + padding : 14, + overrideImageNum : 12; + LoadedFileInfo loadedFileInfo; + + // Convenience method to get the information from the loadedFileInfo + const MachOAnalyzer* loadAddress() const { return (const MachOAnalyzer*)loadedFileInfo.fileContent; } + const char* path() const { return loadedFileInfo.path; } + }; + + struct LoadedImageChain + { + LoadedImageChain* previous; + BuilderLoadedImage& image; + }; + + + void recursiveLoadDependents(LoadedImageChain& forImageChain); + void loadDanglingUpwardLinks(); + const char* resolvePathVar(const char* loadPath, const LoadedImageChain& forImageChain, bool implictRPath); + bool findImage(const char* loadPath, const LoadedImageChain& forImageChain, BuilderLoadedImage*& foundImage, bool mustBeDylib, bool allowOther=true); + void buildImage(ImageWriter& writer, BuilderLoadedImage& forImage); + void addSegments(ImageWriter& writer, const MachOAnalyzer* mh); + void addRebaseInfo(ImageWriter& writer, const MachOAnalyzer* mh); + void addSynthesizedRebaseInfo(ImageWriter& writer, const MachOAnalyzer* mh); + void addSynthesizedBindInfo(ImageWriter& writer, const MachOAnalyzer* mh); + void addBindInfo(ImageWriter& writer, BuilderLoadedImage& forImage); + void reportRebasesAndBinds(ImageWriter& writer, BuilderLoadedImage& forImage); + void addChainedFixupInfo(ImageWriter& writer, const BuilderLoadedImage& forImage); + void addInterposingTuples(LaunchClosureWriter& writer, const Image* image, const MachOAnalyzer* mh); + void computeInitOrder(ImageWriter& writer, uint32_t loadIndex); + void addCachePatchInfo(ImageWriter& writer, const BuilderLoadedImage& forImage); + void addClosureInfo(LaunchClosureWriter& closureWriter); + void depthFirstRecurseSetInitInfo(uint32_t loadIndex, InitInfo initInfos[], uint32_t& initOrder, bool& hasError); + bool findSymbol(const BuilderLoadedImage& fromImage, int libraryOrdinal, const char* symbolName, bool weakImport, uint64_t addend, + Image::ResolvedSymbolTarget& target, ResolvedTargetInfo& targetInfo); + bool findSymbolInImage(const MachOAnalyzer* macho, const char* symbolName, uint64_t addend, bool followReExports, Image::ResolvedSymbolTarget& target, ResolvedTargetInfo& targetInfo); + const MachOAnalyzer* machOForImageNum(ImageNum imageNum); + ImageNum imageNumForMachO(const MachOAnalyzer* mh); + const MachOAnalyzer* findDependent(const MachOLoaded* mh, uint32_t depIndex); + BuilderLoadedImage& findLoadedImage(ImageNum imageNum); + BuilderLoadedImage& findLoadedImage(const MachOAnalyzer* mh); + uint32_t index(const BuilderLoadedImage&); + bool expandAtLoaderPath(const char* loadPath, bool fromLCRPATH, const BuilderLoadedImage& loadedImage, char fixedPath[]); + bool expandAtExecutablePath(const char* loadPath, bool fromLCRPATH, char fixedPath[]); + void addMustBeMissingPath(const char* path); + const char* strdup_temp(const char* path); + bool overridableDylib(const BuilderLoadedImage& forImage); + void forEachBind(BuilderLoadedImage& forImage, void (^handler)(uint64_t runtimeOffset, Image::ResolvedSymbolTarget target, const ResolvedTargetInfo& targetInfo, bool& stop), + void (^strongHandler)(const char* strongSymbolName)); + + static bool inLoadedImageArray(const Array& loadedList, ImageNum imageNum); + static void buildLoadOrderRecurse(Array& loadedList, const Array& imagesArrays, const Image* toAdd); + + const FileSystem& _fileSystem; + const DyldSharedCache* const _dyldCache; + const PathOverrides& _pathOverrides; + const char* const _archName; + Platform const _platform; + uint32_t const _startImageNum; + const ImageArray* _dyldImageArray = nullptr; + const CacheDylibsBindingHandlers* _handlers = nullptr; + const Array* _aliases = nullptr; + const AtPath _atPathHandling = AtPath::none; + uint32_t _mainProgLoadIndex = 0; + Diagnostics _diag; + LaunchErrorInfo* _launchErrorInfo = nullptr; + PathPool* _tempPaths = nullptr; + PathPool* _mustBeMissingPaths = nullptr; + uint32_t _nextIndex = 0; + OverflowSafeArray _loadedImages; + OverflowSafeArray _dependencies; // all dylibs in cache need ~20,000 edges + OverflowSafeArray _interposingTuples; + OverflowSafeArray _weakDefCacheOverrides; + OverflowSafeArray _weakDefsFromChainedBinds; + uint32_t _alreadyInitedIndex = 0; + bool _isLaunchClosure = false; + bool _makingDyldCacheImages = false; + bool _dyldCacheIsLive = false; // means kernel is rebasing dyld cache content being viewed + bool _makingClosuresInCache = false; + bool _makingCustomerCache = false; + bool _atPathUsed = false; + bool _fallbackPathUsed = false; + ImageNum _libDyldImageNum = 0; + ImageNum _libSystemImageNum = 0; +}; + + + + + +} // namespace closure +} // namespace dyld3 + + +#endif /* ClosureBuilder_h */ diff --git a/dyld3/ClosureFileSystem.h b/dyld3/ClosureFileSystem.h new file mode 100644 index 0000000..bb9fe8c --- /dev/null +++ b/dyld3/ClosureFileSystem.h @@ -0,0 +1,80 @@ +/* + * 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 ClosureFileSystem_h +#define ClosureFileSystem_h + +// For MAXPATHLEN +#include +// For va_list +#include +// For uint64_t +#include + +namespace dyld3 { +namespace closure { + +struct LoadedFileInfo { + const void* fileContent = nullptr; + uint64_t fileContentLen = 0; + uint64_t sliceOffset = 0; + uint64_t sliceLen = 0; + uint64_t inode = 0; + uint64_t mtime = 0; + void (*unload)(const LoadedFileInfo&) = nullptr; + const char* path = nullptr; +}; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +class FileSystem { +protected: + FileSystem() { } + +public: + + // Get the real path for a given path, if it exists. + // Returns true if the real path was found and updates the given buffer iff that is the case + virtual bool getRealPath(const char possiblePath[MAXPATHLEN], char realPath[MAXPATHLEN]) const = 0; + + // Returns true on success. If an error occurs the given callback will be called with the reason. + // On success, info is filled with info about the loaded file. If the path supplied includes a symlink, + // the supplier realerPath is filled in with the real path of the file, otherwise it is set to the empty string. + virtual bool loadFile(const char* path, LoadedFileInfo& info, char realerPath[MAXPATHLEN], void (^error)(const char* format, ...)) const = 0; + + // Frees the buffer allocated by loadFile() + virtual void unloadFile(const LoadedFileInfo& info) const = 0; + + // Frees all but the requested range and adjusts info to new buffer location + // Remaining buffer can be freed later with unloadFile() + virtual void unloadPartialFile(LoadedFileInfo& info, uint64_t keepStartOffset, uint64_t keepLength) const = 0; + + // If a file exists at path, returns true and sets inode and mtime + virtual bool fileExists(const char* path, uint64_t* inode=nullptr, uint64_t* mtime=nullptr, bool* issetuid=nullptr) const = 0; +}; +#pragma clang diagnostic pop + +} // namespace closure +} // namespace dyld3 + +#endif /* ClosureFileSystem_h */ diff --git a/dyld3/ClosureFileSystemPhysical.cpp b/dyld3/ClosureFileSystemPhysical.cpp new file mode 100644 index 0000000..d399bdf --- /dev/null +++ b/dyld3/ClosureFileSystemPhysical.cpp @@ -0,0 +1,224 @@ +/* + * 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 "ClosureFileSystemPhysical.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using dyld3::closure::FileSystemPhysical; + +bool FileSystemPhysical::getRealPath(const char possiblePath[MAXPATHLEN], char realPath[MAXPATHLEN]) const { + bool success = false; + int fd = ::open(possiblePath, O_RDONLY); + if ( fd != -1 ) { + success = fcntl(fd, F_GETPATH, realPath) == 0; + ::close(fd); + } + if (success) + return success; + realpath(possiblePath, realPath); + int realpathErrno = errno; + // If realpath() resolves to a path which does not exist on disk, errno is set to ENOENT + return (realpathErrno == ENOENT) || (realpathErrno == 0); +} + +static bool sandboxBlocked(const char* path, const char* kind) +{ +#if TARGET_IPHONE_SIMULATOR + // sandbox calls not yet supported in dyld_sim + return false; +#else + sandbox_filter_type filter = (sandbox_filter_type)(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT); + return ( sandbox_check(getpid(), kind, filter, path) > 0 ); +#endif +} + +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"); +} + +// Returns true on success. If an error occurs the given callback will be called with the reason. +// On success, info is filled with info about the loaded file. If the path supplied includes a symlink, +// the supplier realerPath is filled in with the real path of the file, otherwise it is set to the empty string. +bool FileSystemPhysical::loadFile(const char* path, LoadedFileInfo& info, char realerPath[MAXPATHLEN], void (^error)(const char* format, ...)) const { + // open file + const char* originalPath = path; + char altPath[PATH_MAX]; + int fd = -1; + if ( _fileSystemPrefix != nullptr ) { + strlcpy(altPath, _fileSystemPrefix, PATH_MAX); + strlcat(altPath, path, PATH_MAX); + fd = ::open(altPath, O_RDONLY, 0); + if ( fd != -1 ) + path = altPath; + } + if ( fd == -1 ) { + fd = ::open(path, O_RDONLY, 0); + if ( fd == -1 ) { + int openErrno = errno; + if ( (openErrno == EPERM) && sandboxBlockedOpen(path) ) + error("file system sandbox blocked open(\"%s\", O_RDONLY)", path); + else if ( (openErrno != ENOENT) && (openErrno != ENOTDIR) ) + error("open(\"%s\", O_RDONLY) failed with errno=%d", path, openErrno); + return false; + } + } + + // Get the realpath of the file if it is a symlink + if ( fcntl(fd, F_GETPATH, realerPath) == 0 ) { + // Don't set the realpath if it is just the same as the regular path + if ( strcmp(originalPath, realerPath) == 0 ) + realerPath[0] = '\0'; + } else { + error("Could not get real path for \"%s\"\n", path); + ::close(fd); + return false; + } + + // get file info + struct stat statBuf; +#if TARGET_IPHONE_SIMULATOR + if ( ::stat(path, &statBuf) != 0 ) { +#else + if ( ::fstat(fd, &statBuf) != 0 ) { +#endif + int statErr = errno; + if ( (statErr == EPERM) && sandboxBlockedStat(path) ) + error("file system sandbox blocked stat(\"%s\")", path); + else + error("stat(\"%s\") failed with errno=%d", path, errno); + ::close(fd); + return false; + } + + // only regular files can be loaded + if ( !S_ISREG(statBuf.st_mode) ) { + error("not a file for %s", path); + ::close(fd); + return false; + } + + // mach-o files must be at list one page in size + if ( statBuf.st_size < 4096 ) { + error("file too short %s", path); + ::close(fd); + return false; + } + + info.fileContent = nullptr; + info.fileContentLen = statBuf.st_size; + info.sliceOffset = 0; + info.sliceLen = statBuf.st_size; + info.inode = statBuf.st_ino; + info.mtime = statBuf.st_mtime; + info.path = originalPath; + + // mmap() whole file + void* wholeFile = ::mmap(nullptr, (size_t)statBuf.st_size, PROT_READ, MAP_PRIVATE|MAP_RESILIENT_CODESIGN, fd, 0); + if ( wholeFile == MAP_FAILED ) { + int mmapErr = errno; + if ( mmapErr == EPERM ) { + if ( sandboxBlockedMmap(path) ) + error("file system sandbox blocked mmap() of '%s'", path); + else + error("code signing blocked mmap() of '%s'", path); + } + else { + error("mmap() failed with errno=%d for %s", errno, path); + } + ::close(fd); + return false; + } + info.fileContent = wholeFile; + + // Set unmap as the unload method. + info.unload = [](const LoadedFileInfo& info) { + ::munmap((void*)info.fileContent, (size_t)info.fileContentLen); + }; + + ::close(fd); + return true; +} + +void FileSystemPhysical::unloadFile(const LoadedFileInfo& info) const { + if (info.unload) + info.unload(info); +} + +void FileSystemPhysical::unloadPartialFile(LoadedFileInfo& info, uint64_t keepStartOffset, uint64_t keepLength) const { + // Unmap from 0..keepStartOffset and (keepStartOffset+keepLength)..info.fileContentLen + if (keepStartOffset) + ::munmap((void*)info.fileContent, (size_t)keepStartOffset); + if ((keepStartOffset + keepLength) != info.fileContentLen) { + // Round up to page alignment + keepLength = (keepLength + PAGE_SIZE - 1) & (-PAGE_SIZE); + ::munmap((void*)((char*)info.fileContent + keepStartOffset + keepLength), (size_t)(info.fileContentLen - (keepStartOffset + keepLength))); + } + info.fileContent = (const void*)((char*)info.fileContent + keepStartOffset); + info.fileContentLen = keepLength; +} + +bool FileSystemPhysical::fileExists(const char* path, uint64_t* inode, uint64_t* mtime, bool* issetuid) const { + struct stat statBuf; + if ( _fileSystemPrefix != nullptr ) { + char altPath[PATH_MAX]; + strlcpy(altPath, _fileSystemPrefix, PATH_MAX); + strlcat(altPath, path, PATH_MAX); + if ( ::stat(altPath, &statBuf) == 0 ) { + if (inode) + *inode = statBuf.st_ino; + if (mtime) + *mtime = statBuf.st_mtime; + if (issetuid) + *issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID)); + return true; + } + } + if ( ::stat(path, &statBuf) != 0 ) + return false; + if (inode) + *inode = statBuf.st_ino; + if (mtime) + *mtime = statBuf.st_mtime; + if (issetuid) + *issetuid = (statBuf.st_mode & (S_ISUID|S_ISGID)); + return true; +} diff --git a/dyld3/ClosureFileSystemPhysical.h b/dyld3/ClosureFileSystemPhysical.h new file mode 100644 index 0000000..ecb0fbf --- /dev/null +++ b/dyld3/ClosureFileSystemPhysical.h @@ -0,0 +1,56 @@ +/* + * 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 ClosureFileSystemPhysical_h +#define ClosureFileSystemPhysical_h + +#include "ClosureFileSystem.h" + +namespace dyld3 { +namespace closure { + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +class __attribute__((visibility("hidden"))) FileSystemPhysical : public FileSystem { +public: + FileSystemPhysical(const char* fileSystemPrefix = nullptr) : FileSystem(), _fileSystemPrefix(fileSystemPrefix) { } + + bool getRealPath(const char possiblePath[MAXPATHLEN], char realPath[MAXPATHLEN]) const override; + + bool loadFile(const char* path, LoadedFileInfo& info, char realerPath[MAXPATHLEN], void (^error)(const char* format, ...)) const override; + + void unloadFile(const LoadedFileInfo& info) const override; + + void unloadPartialFile(LoadedFileInfo& info, uint64_t keepStartOffset, uint64_t keepLength) const override; + + bool fileExists(const char* path, uint64_t* inode=nullptr, uint64_t* mtime=nullptr, bool* issetuid=nullptr) const override; + +private: + const char* _fileSystemPrefix; +}; +#pragma clang diagnostic pop + +} // namespace closure +} // namespace dyld3 + +#endif /* ClosureFileSystemPhysical_h */ diff --git a/dyld3/ClosurePrinter.cpp b/dyld3/ClosurePrinter.cpp new file mode 100644 index 0000000..98b27df --- /dev/null +++ b/dyld3/ClosurePrinter.cpp @@ -0,0 +1,442 @@ +/* + * 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 "ClosurePrinter.h" +#include "JSONWriter.h" + +using namespace dyld3::json; + +namespace dyld3 { +namespace closure { + +static std::string printTarget(const Array& imagesArrays, Image::ResolvedSymbolTarget target) +{ + const Image* targetImage; + uint64_t value; + switch ( target.image.kind ) { + case Image::ResolvedSymbolTarget::kindImage: + targetImage = ImageArray::findImage(imagesArrays, target.image.imageNum); + if ( target.image.offset & 0x8000000000ULL ) { + uint64_t signExtend = target.image.offset | 0xFFFFFF0000000000ULL; + return std::string("bind to ") + targetImage->leafName() + " - " + hex8(-signExtend); + } + else + return std::string("bind to ") + targetImage->leafName() + " + " + hex8(target.image.offset); + break; + case Image::ResolvedSymbolTarget::kindSharedCache: + return std::string("bind to dyld cache + ") + hex8(target.sharedCache.offset); + break; + case Image::ResolvedSymbolTarget::kindAbsolute: + value = target.absolute.value; + if ( value & 0x2000000000000000LL ) + value |= 0xC000000000000000LL; + return std::string("bind to absolute ") + hex(value); + break; + } + return "???"; +} + + +static Node buildImageNode(const Image* image, const Array& imagesArrays, bool printFixups, bool printDependentsDetails, const uint8_t* cacheStart=nullptr) +{ + __block Node imageNode; + + if ( image->isInvalid() ) + return imageNode; + + imageNode.map["image-num"].value = hex4(image->imageNum()); + imageNode.map["path"].value = image->path(); + __block Node imageAliases; + image->forEachAlias(^(const char* aliasPath, bool& stop) { + Node anAlias; + anAlias.value = aliasPath; + imageAliases.array.push_back(anAlias); + }); + if ( !imageAliases.array.empty() ) + imageNode.map["aliases"] = imageAliases; + uuid_t uuid; + if ( image->getUuid(uuid) ) { + uuid_string_t uuidStr; + uuid_unparse(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["has-plus-loads"].value = (image->mayHavePlusLoads() ? "true" : "false"); + imageNode.map["never-unload"].value = (image->neverUnload() ? "true" : "false"); +// imageNode.map["platform-binary"].value = (image->isPlatformBinary() ? "true" : "false"); +// if ( image->cwdMustBeThisDir() ) +// imageNode.map["cwd-must-be-this-dir"].value = "true"; + if ( !image->inDyldCache() ) { + 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); +// } + uint64_t inode; + uint64_t mTime; + if ( image->hasFileModTimeAndInode(inode, mTime) ) { + imageNode.map["file-mod-time"].value = hex(inode); + imageNode.map["file-inode"].value = hex(mTime); + } + uint8_t cdHash[20]; + if ( image->hasCdHash(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" ) + imageNode.map["cd-hash"].value = cdHashStr; + } + else { + #if 0 + 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; + #endif + } + 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->forEachFixup(^(uint64_t imageOffsetToRebase, bool &stop) { + // rebase + imageNode.map["fixups"].map[hex8(imageOffsetToRebase)].value = "rebase"; + }, ^(uint64_t imageOffsetToBind, Image::ResolvedSymbolTarget target, bool &stop) { + // bind + imageNode.map["fixups"].map[hex8(imageOffsetToBind)].value = printTarget(imagesArrays, target); + }, ^(uint64_t imageOffsetStart, const Array& targets, bool& stop) { + // chain + imageNode.map["fixups"].map[hex8(imageOffsetStart)].value = "chain-start"; + for (const Image::ResolvedSymbolTarget& target: targets) { + Node targetNode; + targetNode.value = printTarget(imagesArrays, target); + imageNode.map["fixups-targets"].array.push_back(targetNode); + } + }); + image->forEachTextReloc(^(uint32_t imageOffsetToRebase, bool &stop) { + // rebase + imageNode.map["fixups"].map[hex8(imageOffsetToRebase)].value = "text rebase"; + }, ^(uint32_t imageOffsetToBind, Image::ResolvedSymbolTarget target, bool &stop) { + imageNode.map["fixups"].map[hex8(imageOffsetToBind)].value = "text " + printTarget(imagesArrays, target); + }); + } + } + else { + if ( printFixups ) { + image->forEachPatchableExport(^(uint32_t cacheOffsetOfImpl, const char* name) { + __block Node implNode; + implNode.map["name"].value = name; + implNode.map["impl-cache-offset"].value = hex8(cacheOffsetOfImpl); + image->forEachPatchableUseOfExport(cacheOffsetOfImpl, ^(Image::PatchableExport::PatchLocation patchLocation) { + Node siteNode; + siteNode.map["cache-offset"].value = hex8(patchLocation.cacheOffset); + if ( patchLocation.addend != 0 ) + siteNode.map["addend"].value = hex(patchLocation.addend); + if ( patchLocation.authenticated != 0 ) { + siteNode.map["key"].value = patchLocation.keyName(); + siteNode.map["address-diversity"].value = patchLocation.usesAddressDiversity ? "true" : "false"; + siteNode.map["discriminator"].value = hex4(patchLocation.discriminator); + } + implNode.map["usage-sites"].array.push_back(siteNode); + }); + imageNode.map["patches"].array.push_back(implNode); + }); + } + } + + // add dependents + image->forEachDependentImage(^(uint32_t depIndex, Image::LinkKind kind, ImageNum imageNum, bool& stop) { + Node depMapNode; + const Image* depImage = ImageArray::findImage(imagesArrays, imageNum); + depMapNode.map["image-num"].value = hex4(imageNum); + if ( depImage != nullptr ) + depMapNode.map["path"].value = depImage->path(); + 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 initializers + image->forEachInitializer(nullptr, ^(const void* initializer) { + Node initNode; + initNode.value = hex((long)initializer); + imageNode.map["initializer-offsets"].array.push_back(initNode); + }); + + __block Node initBeforeNode; + image->forEachImageToInitBefore(^(ImageNum imageToInit, bool& stop) { + Node beforeNode; + const Image* initImage = ImageArray::findImage(imagesArrays, imageToInit); + assert(initImage != nullptr); + beforeNode.value = initImage->path(); + imageNode.map["initializer-order"].array.push_back(beforeNode); + }); + + ImageNum cacheImageNum; + if ( image->isOverrideOfDyldCacheImage(cacheImageNum) ) { + imageNode.map["override-of-dyld-cache-image"].value = ImageArray::findImage(imagesArrays, cacheImageNum)->path(); + } + + +#if 0 + // 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 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); + }); +#endif + + return imageNode; +} + + +static Node buildImageArrayNode(const ImageArray* imageArray, const Array& imagesArrays, bool printFixups, bool printDependentsDetails, const uint8_t* cacheStart=nullptr) +{ + __block Node images; + imageArray->forEachImage(^(const Image* image, bool& stop) { + images.array.push_back(buildImageNode(image, imagesArrays, printFixups, printDependentsDetails, cacheStart)); + }); + return images; +} + + +static Node buildClosureNode(const DlopenClosure* closure, const Array& imagesArrays, bool printFixups, bool printDependentsDetails) +{ + __block Node root; + root.map["images"] = buildImageArrayNode(closure->images(), imagesArrays, printFixups, printDependentsDetails); + + closure->forEachPatchEntry(^(const Closure::PatchEntry& patchEntry) { + Node patchNode; + patchNode.map["func-dyld-cache-offset"].value = hex8(patchEntry.exportCacheOffset); + patchNode.map["func-image-num"].value = hex8(patchEntry.overriddenDylibInCache); + patchNode.map["replacement"].value = printTarget(imagesArrays, patchEntry.replacement); + root.map["dyld-cache-fixups"].array.push_back(patchNode); + }); + + return root; +} + +static Node buildClosureNode(const LaunchClosure* closure, const Array& imagesArrays, bool printFixups, bool printDependentsDetails) +{ + __block Node root; + root.map["images"] = buildImageArrayNode(closure->images(), imagesArrays, printFixups, printDependentsDetails); + + Image::ResolvedSymbolTarget entry; + if ( closure->mainEntry(entry) ) + root.map["main"].value = printTarget(imagesArrays, entry); + else if ( closure->startEntry(entry) ) + root.map["start"].value = printTarget(imagesArrays, entry); + + Image::ResolvedSymbolTarget libdyldEntry; + closure->libDyldEntry(libdyldEntry); + root.map["libdyld-entry"].value = printTarget(imagesArrays, libdyldEntry); + + root.map["uses-@paths"].value = (closure->usedAtPaths() ? "true" : "false"); + root.map["uses-fallback-paths"].value = (closure->usedFallbackPaths() ? "true" : "false"); + + // 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); + }); + + // add interposing info, if any + closure->forEachInterposingTuple(^(const InterposingTuple& tuple, bool& stop) { + Node tupleNode; + tupleNode.map["stock"].value = printTarget(imagesArrays, tuple.stockImplementation); + tupleNode.map["replace"].value = printTarget(imagesArrays, tuple.newImplementation); + root.map["interposing-tuples"].array.push_back(tupleNode); + }); + + closure->forEachPatchEntry(^(const Closure::PatchEntry& patchEntry) { + Node patchNode; + patchNode.map["func-dyld-cache-offset"].value = hex8(patchEntry.exportCacheOffset); + patchNode.map["func-image-num"].value = hex8(patchEntry.overriddenDylibInCache); + patchNode.map["replacement"].value = printTarget(imagesArrays, patchEntry.replacement); + root.map["dyld-cache-fixups"].array.push_back(patchNode); + }); + + root.map["initial-image-count"].value = decimal(closure->initialLoadCount()); + +#if 0 + + + // 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 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["group-num"].value = decimal(closure.group().groupNum()); + + + __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; +#endif + return root; +} + +void printImageAsJSON(const Image* image, const Array& imagesArrays, bool printFixups, FILE* out) +{ + Node root = buildImageNode(image, imagesArrays, printFixups, false); + printJSON(root, 0, out); +} + +void printDyldCacheImagesAsJSON(const DyldSharedCache* dyldCache, bool printFixups, FILE* out) +{ + const dyld3::closure::ImageArray* dylibs = dyldCache->cachedDylibsImageArray(); + STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 2); + imagesArrays.push_back(dylibs); + + Node root = buildImageArrayNode(dylibs, imagesArrays, printFixups, false, (uint8_t*)dyldCache); + printJSON(root, 0, out); +} + +void printClosureAsJSON(const LaunchClosure* cls, const Array& imagesArrays, bool printFixups, FILE* out) +{ + Node root = buildClosureNode(cls, imagesArrays, printFixups, false); + printJSON(root, 0, out); +} + +void printClosureAsJSON(const DlopenClosure* cls, const Array& imagesArrays, bool printFixups, FILE* out) +{ + Node root = buildClosureNode(cls, imagesArrays, printFixups, false); + printJSON(root, 0, out); +} + + +} // namespace closure +} // namespace dyld3 diff --git a/dyld3/ClosurePrinter.h b/dyld3/ClosurePrinter.h new file mode 100644 index 0000000..56f63d8 --- /dev/null +++ b/dyld3/ClosurePrinter.h @@ -0,0 +1,49 @@ +/* + * 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 ClosurePrinter_h +#define ClosurePrinter_h + +#include + +#include "Closure.h" +#include "DyldSharedCache.h" + + +namespace dyld3 { +namespace closure { + + +void printClosureAsJSON( const LaunchClosure* cls, const Array& imagesArrays, bool printFixups=false, FILE* out=stdout); +void printClosureAsJSON( const DlopenClosure* cls, const Array& imagesArrays, bool printFixups=false, FILE* out=stdout); +void printImageAsJSON( const Image* image, const Array& imagesArrays, bool printFixups=false, FILE* out=stdout); + +void printDyldCacheImagesAsJSON(const DyldSharedCache* dyldCache, bool printFixups=false, FILE* out=stdout); + + +} // namespace closure +} // namespace dyld3 + + +#endif /* ClosurePrinter_h */ diff --git a/dyld3/ClosureWriter.cpp b/dyld3/ClosureWriter.cpp new file mode 100644 index 0000000..17f4c91 --- /dev/null +++ b/dyld3/ClosureWriter.cpp @@ -0,0 +1,516 @@ +/* + * 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 "ClosureWriter.h" +#include "MachOFile.h" + + +namespace dyld3 { +namespace closure { + + +//////////////////////////// ContainerTypedBytesWriter //////////////////////////////////////// + + +void ContainerTypedBytesWriter::setContainerType(TypedBytes::Type containerType) +{ + assert(_vmAllocationStart == 0); + _vmAllocationSize = 1024 * 1024; + vm_address_t allocationAddr; + ::vm_allocate(mach_task_self(), &allocationAddr, _vmAllocationSize, VM_FLAGS_ANYWHERE); + assert(allocationAddr != 0); + _vmAllocationStart = (void*)allocationAddr; + _containerTypedBytes = (TypedBytes*)_vmAllocationStart; + _containerTypedBytes->type = (uint32_t)containerType; + _containerTypedBytes->payloadLength = 0; + _end = (uint8_t*)_vmAllocationStart + sizeof(TypedBytes); +} + +void* ContainerTypedBytesWriter::append(TypedBytes::Type t, const void* payload, uint32_t payloadSize) +{ + assert((payloadSize & 0x3) == 0); + if ( (uint8_t*)_end + payloadSize >= (uint8_t*)_vmAllocationStart + _vmAllocationSize ) { + // if current buffer too small, grow it + size_t growth = _vmAllocationSize; + if ( growth < payloadSize ) + growth = _vmAllocationSize*((payloadSize/_vmAllocationSize)+1); + vm_address_t newAllocationAddr; + size_t newAllocationSize = _vmAllocationSize+growth; + ::vm_allocate(mach_task_self(), &newAllocationAddr, newAllocationSize, VM_FLAGS_ANYWHERE); + assert(newAllocationAddr != 0); + size_t currentInUse = (char*)_end - (char*)_vmAllocationStart; + memcpy((void*)newAllocationAddr, _vmAllocationStart, currentInUse); + ::vm_deallocate(mach_task_self(), (vm_address_t)_vmAllocationStart, _vmAllocationSize); + _end = (void*)(newAllocationAddr + currentInUse); + _vmAllocationStart = (void*)newAllocationAddr; + _containerTypedBytes = (TypedBytes*)_vmAllocationStart; + _vmAllocationSize = newAllocationSize; + } + assert( (uint8_t*)_end + payloadSize < (uint8_t*)_vmAllocationStart + _vmAllocationSize); + TypedBytes* tb = (TypedBytes*)_end; + tb->type = (uint32_t)t; + tb->payloadLength = payloadSize; + if ( payload != nullptr ) + ::memcpy(tb->payload(), payload, payloadSize); + _end = (uint8_t*)_end + sizeof(TypedBytes) + payloadSize; + _containerTypedBytes->payloadLength += sizeof(TypedBytes) + payloadSize; + return tb->payload(); +} + +const void* ContainerTypedBytesWriter::finalizeContainer() +{ + // trim vm allocation down to just what is needed + uintptr_t bufferStart = (uintptr_t)_vmAllocationStart; + uintptr_t used = round_page((uintptr_t)_end - bufferStart); + if ( used < _vmAllocationSize ) { + uintptr_t deallocStart = bufferStart + used; + ::vm_deallocate(mach_task_self(), deallocStart, _vmAllocationSize - used); + _end = nullptr; + _vmAllocationSize = used; + } + // mark vm region read-only + ::vm_protect(mach_task_self(), bufferStart, used, false, VM_PROT_READ); + return (void*)_vmAllocationStart; +} + +const void* ContainerTypedBytesWriter::currentTypedBytes() +{ + return (void*)_vmAllocationStart; +} + +void ContainerTypedBytesWriter::deallocate() +{ + ::vm_deallocate(mach_task_self(), (long)_vmAllocationStart, _vmAllocationSize); +} + +//////////////////////////// ImageWriter //////////////////////////////////////// + + +const Image* ImageWriter::finalize() +{ + return (Image*)finalizeContainer(); +} + +const Image* ImageWriter::currentImage() +{ + return (Image*)currentTypedBytes(); +} + +void ImageWriter::addPath(const char* path) +{ + uint32_t roundedPathLen = ((uint32_t)strlen(path) + 1 + 3) & (-4); + Image::PathAndHash* ph = (Image::PathAndHash*)append(TypedBytes::Type::pathWithHash, nullptr, sizeof(Image::PathAndHash)+roundedPathLen); + ph->hash = Image::hashFunction(path); + strcpy(ph->path, path); +} + +Image::Flags& ImageWriter::getFlags() +{ + if ( _flagsOffset == -1 ) { + setContainerType(TypedBytes::Type::image); + Image::Flags flags; + ::bzero(&flags, sizeof(flags)); + uint8_t* p = (uint8_t*)append(TypedBytes::Type::imageFlags, &flags, sizeof(flags)); + _flagsOffset = (int)(p - (uint8_t*)currentTypedBytes()); + } + return *((Image::Flags*)((uint8_t*)currentTypedBytes() + _flagsOffset)); +} + +void ImageWriter::setImageNum(ImageNum num) +{ + getFlags().imageNum = num; +} + +void ImageWriter::setHasObjC(bool value) +{ + getFlags().hasObjC = value; +} + +void ImageWriter::setIs64(bool value) +{ + getFlags().is64 = value; +} + +void ImageWriter::setHasPlusLoads(bool value) +{ + getFlags().mayHavePlusLoads = value; +} + +void ImageWriter::setIsBundle(bool value) +{ + getFlags().isBundle = value; +} + +void ImageWriter::setIsDylib(bool value) +{ + getFlags().isDylib = value; +} + +void ImageWriter::setIsExecutable(bool value) +{ + getFlags().isExecutable = value; +} + +void ImageWriter::setHasWeakDefs(bool value) +{ + getFlags().hasWeakDefs = value; +} + +void ImageWriter::setUses16KPages(bool value) +{ + getFlags().has16KBpages = value; +} + +void ImageWriter::setOverridableDylib(bool value) +{ + getFlags().overridableDylib = value; +} + +void ImageWriter::setInvalid() +{ + getFlags().isInvalid = true; +} + +void ImageWriter::setInDyldCache(bool value) +{ + getFlags().inDyldCache = value; +} + +void ImageWriter::setNeverUnload(bool value) +{ + getFlags().neverUnload = value; +} + +void ImageWriter::setUUID(const uuid_t uuid) +{ + append(TypedBytes::Type::uuid, uuid, sizeof(uuid_t)); +} + +void ImageWriter::setCDHash(const uint8_t cdHash[20]) +{ + append(TypedBytes::Type::cdHash, cdHash, 20); +} + +void ImageWriter::setDependents(const Array& deps) +{ + append(TypedBytes::Type::dependents, deps.begin(), (uint32_t)deps.count()*sizeof(Image::LinkedImage)); +} + +void ImageWriter::setDofOffsets(const Array& dofSectionOffsets) +{ + append(TypedBytes::Type::dofOffsets, &dofSectionOffsets[0], (uint32_t)dofSectionOffsets.count()*sizeof(uint32_t)); +} + +void ImageWriter::setInitOffsets(const uint32_t initOffsets[], uint32_t count) +{ + append(TypedBytes::Type::initOffsets, initOffsets, count*sizeof(uint32_t)); +} + +void ImageWriter::setDiskSegments(const Image::DiskSegment segs[], uint32_t count) +{ + append(TypedBytes::Type::diskSegment, segs, count*sizeof(Image::DiskSegment)); +} + +void ImageWriter::setCachedSegments(const Image::DyldCacheSegment segs[], uint32_t count) +{ + append(TypedBytes::Type::cacheSegment, segs, count*sizeof(Image::DyldCacheSegment)); +} + +void ImageWriter::setCodeSignatureLocation(uint32_t fileOffset, uint32_t size) +{ + Image::CodeSignatureLocation loc; + loc.fileOffset = fileOffset; + loc.fileSize = size; + append(TypedBytes::Type::codeSignLoc, &loc, sizeof(loc)); +} + +void ImageWriter::setFairPlayEncryptionRange(uint32_t fileOffset, uint32_t size) +{ + const uint32_t pageSize = getFlags().has16KBpages ? 0x4000 : 0x1000; + assert((fileOffset % pageSize) == 0); + assert((size % pageSize) == 0); + Image::FairPlayRange loc; + loc.textStartPage = fileOffset/pageSize; + loc.textPageCount = size/pageSize; + append(TypedBytes::Type::fairPlayLoc, &loc, sizeof(loc)); +} + +void ImageWriter::setMappingInfo(uint64_t sliceOffset, uint64_t vmSize) +{ + const uint32_t pageSize = getFlags().has16KBpages ? 0x4000 : 0x1000; + Image::MappingInfo info; + info.sliceOffsetIn4K = (uint32_t)(sliceOffset / 0x1000); + info.totalVmPages = (uint32_t)(vmSize / pageSize); + append(TypedBytes::Type::mappingInfo, &info, sizeof(info)); +} + +void ImageWriter::setFileInfo(uint64_t inode, uint64_t mTime) +{ + Image::FileInfo info = { inode, mTime }; + append(TypedBytes::Type::fileInodeAndTime, &info, sizeof(info)); +} + +void ImageWriter::setRebaseInfo(const Array& fixups) +{ + append(TypedBytes::Type::rebaseFixups, fixups.begin(), (uint32_t)fixups.count()*sizeof(Image::RebasePattern)); +} + +void ImageWriter::setTextRebaseInfo(const Array& fixups) +{ + append(TypedBytes::Type::textFixups, fixups.begin(), (uint32_t)fixups.count()*sizeof(Image::TextFixupPattern)); +} + +void ImageWriter::setBindInfo(const Array& fixups) +{ + append(TypedBytes::Type::bindFixups, fixups.begin(), (uint32_t)fixups.count()*sizeof(Image::BindPattern)); +} + +void ImageWriter::setChainedFixups(const Array& starts, const Array& targets) +{ + append(TypedBytes::Type::chainedFixupsStarts, starts.begin(), (uint32_t)starts.count()*sizeof(uint64_t)); + append(TypedBytes::Type::chainedFixupsTargets, targets.begin(), (uint32_t)targets.count()*sizeof(Image::ResolvedSymbolTarget)); +} + +void ImageWriter::addExportPatchInfo(uint32_t implCacheOff, const char* name, uint32_t locCount, const Image::PatchableExport::PatchLocation* locs) +{ + uint32_t roundedNameLen = ((uint32_t)strlen(name) + 1 + 3) & (-4); + uint32_t payloadSize = sizeof(Image::PatchableExport) + locCount*sizeof(Image::PatchableExport::PatchLocation) + roundedNameLen; + Image::PatchableExport* buffer = (Image::PatchableExport*)append(TypedBytes::Type::cachePatchInfo, nullptr, payloadSize); + buffer->cacheOffsetOfImpl = implCacheOff; + buffer->patchLocationsCount = locCount; + memcpy(&buffer->patchLocations[0], locs, locCount*sizeof(Image::PatchableExport::PatchLocation)); + strcpy((char*)(&buffer->patchLocations[locCount]), name); +} + +void ImageWriter::setAsOverrideOf(ImageNum imageNum) +{ + uint32_t temp = imageNum; + append(TypedBytes::Type::imageOverride, &temp, sizeof(temp)); +} + +void ImageWriter::setInitsOrder(const ImageNum images[], uint32_t count) +{ + append(TypedBytes::Type::initBefores, images, count*sizeof(ImageNum)); +} + + +//////////////////////////// ImageArrayWriter //////////////////////////////////////// + + +ImageArrayWriter::ImageArrayWriter(ImageNum startImageNum, unsigned count) : _index(0) +{ + setContainerType(TypedBytes::Type::imageArray); + _end = (void*)((uint8_t*)_end + sizeof(ImageArray) - sizeof(TypedBytes) + sizeof(uint32_t)*count); + _containerTypedBytes->payloadLength = sizeof(ImageArray) - sizeof(TypedBytes) + sizeof(uint32_t)*count; + ImageArray* ia = (ImageArray*)_containerTypedBytes; + ia->firstImageNum = startImageNum; + ia->count = count; +} + +void ImageArrayWriter::appendImage(const Image* image) +{ + ImageArray* ia = (ImageArray*)_containerTypedBytes; + ia->offsets[_index++] = _containerTypedBytes->payloadLength; + append(TypedBytes::Type::image, image->payload(), image->payloadLength); +} + +const ImageArray* ImageArrayWriter::finalize() +{ + return (ImageArray*)finalizeContainer(); +} + + +//////////////////////////// ClosureWriter //////////////////////////////////////// + +void ClosureWriter::setTopImageNum(ImageNum imageNum) +{ + append(TypedBytes::Type::topImage, &imageNum, sizeof(ImageNum)); +} + +void ClosureWriter::addCachePatches(const Array& patches) +{ + append(TypedBytes::Type::cacheOverrides, patches.begin(), (uint32_t)patches.count()*sizeof(Closure::PatchEntry)); +} + + +//////////////////////////// LaunchClosureWriter //////////////////////////////////////// + +LaunchClosureWriter::LaunchClosureWriter(const ImageArray* images) +{ + setContainerType(TypedBytes::Type::launchClosure); + append(TypedBytes::Type::imageArray, images->payload(), images->payloadLength); +} + +const LaunchClosure* LaunchClosureWriter::finalize() +{ + return (LaunchClosure*)finalizeContainer(); +} + +void LaunchClosureWriter::setLibSystemImageNum(ImageNum imageNum) +{ + append(TypedBytes::Type::libSystemNum, &imageNum, sizeof(ImageNum)); +} + +void LaunchClosureWriter::setLibDyldEntry(Image::ResolvedSymbolTarget entry) +{ + append(TypedBytes::Type::libDyldEntry, &entry, sizeof(entry)); +} + +void LaunchClosureWriter::setMainEntry(Image::ResolvedSymbolTarget main) +{ + append(TypedBytes::Type::mainEntry, &main, sizeof(main)); +} + +void LaunchClosureWriter::setStartEntry(Image::ResolvedSymbolTarget start) +{ + append(TypedBytes::Type::startEntry, &start, sizeof(start)); +} + +void LaunchClosureWriter::setUsedFallbackPaths(bool value) +{ + getFlags().usedFallbackPaths = value; +} + +void LaunchClosureWriter::setUsedAtPaths(bool value) +{ + getFlags().usedAtPaths = value; +} + +void LaunchClosureWriter::setInitImageCount(uint32_t count) +{ + getFlags().initImageCount = count; +} + +LaunchClosure::Flags& LaunchClosureWriter::getFlags() +{ + if ( _flagsOffset == -1 ) { + LaunchClosure::Flags flags; + ::bzero(&flags, sizeof(flags)); + uint8_t* p = (uint8_t*)append(TypedBytes::Type::closureFlags, &flags, sizeof(flags)); + _flagsOffset = (int)(p - (uint8_t*)currentTypedBytes()); + } + return *((LaunchClosure::Flags*)((uint8_t*)currentTypedBytes() + _flagsOffset)); +} + +void LaunchClosureWriter::setMustBeMissingFiles(const Array& paths) +{ + uint32_t totalSize = 0; + for (const char* s : paths) + totalSize += (strlen(s) +1); + totalSize = (totalSize + 3) & (-4); // align + + char* buffer = (char*)append(TypedBytes::Type::missingFiles, nullptr, totalSize); + char* t = buffer; + for (const char* path : paths) { + for (const char* s=path; *s != '\0'; ++s) + *t++ = *s; + *t++ = '\0'; + } + while (t < &buffer[totalSize]) + *t++ = '\0'; +} + +void LaunchClosureWriter::addEnvVar(const char* envVar) +{ + unsigned len = (unsigned)strlen(envVar); + char temp[len+8]; + strcpy(temp, envVar); + unsigned paddedSize = len+1; + while ( (paddedSize % 4) != 0 ) + temp[paddedSize++] = '\0'; + append(TypedBytes::Type::envVar, temp, paddedSize); +} + +void LaunchClosureWriter::addInterposingTuples(const Array& tuples) +{ + append(TypedBytes::Type::interposeTuples, tuples.begin(), (uint32_t)tuples.count()*sizeof(InterposingTuple)); +} + +void LaunchClosureWriter::setDyldCacheUUID(const uuid_t uuid) +{ + append(TypedBytes::Type::dyldCacheUUID, uuid, sizeof(uuid_t)); +} + +void LaunchClosureWriter::setBootUUID(const char* uuid) +{ + unsigned len = (unsigned)strlen(uuid); + char temp[len+8]; + strcpy(temp, uuid); + unsigned paddedSize = len+1; + while ( (paddedSize % 4) != 0 ) + temp[paddedSize++] = '\0'; + append(TypedBytes::Type::bootUUID, temp, paddedSize); +} + +void LaunchClosureWriter::applyInterposing() +{ + const LaunchClosure* currentClosure = (LaunchClosure*)currentTypedBytes(); + const ImageArray* images = currentClosure->images(); + currentClosure->forEachInterposingTuple(^(const InterposingTuple& tuple, bool&) { + images->forEachImage(^(const dyld3::closure::Image* image, bool&) { + for (const Image::BindPattern& bindPat : image->bindFixups()) { + if ( (bindPat.target == tuple.stockImplementation) && (tuple.newImplementation.image.imageNum != image->imageNum()) ) { + Image::BindPattern* writePat = const_cast(&bindPat); + writePat->target = tuple.newImplementation; + } + } + + // Chained fixups may also be interposed. We can't change elements in the chain, but we can change + // the target list. + for (const Image::ResolvedSymbolTarget& symbolTarget : image->chainedTargets()) { + if ( (symbolTarget == tuple.stockImplementation) && (tuple.newImplementation.image.imageNum != image->imageNum()) ) { + Image::ResolvedSymbolTarget* writeTarget = const_cast(&symbolTarget); + *writeTarget = tuple.newImplementation; + } + } + }); + }); +} + +//////////////////////////// DlopenClosureWriter //////////////////////////////////////// + +DlopenClosureWriter::DlopenClosureWriter(const ImageArray* images) +{ + setContainerType(TypedBytes::Type::dlopenClosure); + append(TypedBytes::Type::imageArray, images->payload(), images->payloadLength); +} + +const DlopenClosure* DlopenClosureWriter::finalize() +{ + return (DlopenClosure*)finalizeContainer(); +} + + +} // namespace closure +} // namespace dyld3 + + + diff --git a/dyld3/ClosureWriter.h b/dyld3/ClosureWriter.h new file mode 100644 index 0000000..e147709 --- /dev/null +++ b/dyld3/ClosureWriter.h @@ -0,0 +1,174 @@ +/* + * 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 ClosureWriter_h +#define ClosureWriter_h + + +#include +#include +#include +#include +#include +#include +#include + +#include "Closure.h" + + + +namespace dyld3 { + +namespace closure { + + +class VIS_HIDDEN ContainerTypedBytesWriter +{ +public: + void deallocate(); + +protected: + void setContainerType(TypedBytes::Type containerType); + void* append(TypedBytes::Type t, const void* payload, uint32_t payloadSize); + + const void* currentTypedBytes(); + const void* finalizeContainer(); + + void* _vmAllocationStart = nullptr; + size_t _vmAllocationSize = 0; + TypedBytes* _containerTypedBytes = nullptr; + void* _end = nullptr; +}; + + +class VIS_HIDDEN ImageWriter : public ContainerTypedBytesWriter +{ +public: + + void setImageNum(ImageNum num); + void addPath(const char* path); // first is canonical, others are aliases + void setInvalid(); + void setInDyldCache(bool); + void setIs64(bool); + void setHasObjC(bool); + void setHasPlusLoads(bool); + void setIsBundle(bool); + void setIsDylib(bool); + void setIsExecutable(bool); + void setIsRestricted(bool); + void setHasWeakDefs(bool); + void setUses16KPages(bool); + void setOverridableDylib(bool); + void setNeverUnload(bool); + void setUUID(const uuid_t uuid); + void setCDHash(const uint8_t cdHash[20]); + void setDependents(const Array& deps); + void setDofOffsets(const Array& dofSectionOffsets); + void setInitOffsets(const uint32_t initOffsets[], uint32_t count); + void setDiskSegments(const Image::DiskSegment segs[], uint32_t count); + void setCachedSegments(const Image::DyldCacheSegment segs[], uint32_t count); + void setCodeSignatureLocation(uint32_t fileOffset, uint32_t size); + void setFairPlayEncryptionRange(uint32_t fileOffset, uint32_t size); + void setMappingInfo(uint64_t sliceOffset, uint64_t vmSize); + void setFileInfo(uint64_t inode, uint64_t modTime); + void setRebaseInfo(const Array&); + void setTextRebaseInfo(const Array&); + void setBindInfo(const Array&); + void setAsOverrideOf(ImageNum); + void addExportPatchInfo(uint32_t implOff, const char* name, uint32_t locCount, const Image::PatchableExport::PatchLocation* locs); + void setInitsOrder(const ImageNum images[], uint32_t count); + void setChainedFixups(const Array& starts, const Array& targets); + + const Image* currentImage(); + + const Image* finalize(); + +private: + Image::Flags& getFlags(); + + int _flagsOffset = -1; +}; + + +class VIS_HIDDEN ImageArrayWriter : public ContainerTypedBytesWriter +{ +public: + ImageArrayWriter(ImageNum startImageNum, unsigned count); + + void appendImage(const Image*); + const ImageArray* finalize(); +private: + unsigned _index; +}; + +class VIS_HIDDEN ClosureWriter : public ContainerTypedBytesWriter +{ +public: + void setTopImageNum(ImageNum imageNum); + void addCachePatches(const Array&); +}; + +class VIS_HIDDEN LaunchClosureWriter : public ClosureWriter +{ +public: + LaunchClosureWriter(const ImageArray* images); + + const LaunchClosure* finalize(); + void setLibSystemImageNum(ImageNum imageNum); + void setInitImageCount(uint32_t count); + void setLibDyldEntry(Image::ResolvedSymbolTarget dyldEntry); + void setMainEntry(Image::ResolvedSymbolTarget main); + void setStartEntry(Image::ResolvedSymbolTarget start); + void setUsedFallbackPaths(bool); + void setUsedAtPaths(bool); + void setMustBeMissingFiles(const Array& paths); + void addInterposingTuples(const Array& tuples); + void setDyldCacheUUID(const uuid_t); + void setBootUUID(const char* uuid); + void applyInterposing(); + void addEnvVar(const char* envVar); + +private: + LaunchClosure::Flags& getFlags(); + + int _flagsOffset = -1; +}; + + +class VIS_HIDDEN DlopenClosureWriter : public ClosureWriter +{ +public: + DlopenClosureWriter(const ImageArray* images); + + const DlopenClosure* finalize(); + +}; + + +} // namespace closure +} // namespace dyld3 + + +#endif // ClosureWriter_h + diff --git a/dyld3/CodeSigningTypes.h b/dyld3/CodeSigningTypes.h index c84a708..22af412 100644 --- a/dyld3/CodeSigningTypes.h +++ b/dyld3/CodeSigningTypes.h @@ -47,6 +47,7 @@ enum { CS_HASHTYPE_SHA1 = 1, CS_HASHTYPE_SHA256 = 2, CS_HASHTYPE_SHA256_TRUNCATED = 3, + CS_HASHTYPE_SHA384 = 4, CS_HASH_SIZE_SHA1 = 20, CS_HASH_SIZE_SHA256 = 32, diff --git a/dyld3/Diagnostics.cpp b/dyld3/Diagnostics.cpp index a8e4bca..06a0401 100644 --- a/dyld3/Diagnostics.cpp +++ b/dyld3/Diagnostics.cpp @@ -55,14 +55,14 @@ #endif Diagnostics::Diagnostics(bool verbose) -#if !DYLD_IN_PROCESS +#if BUILDING_CACHE_BUILDER : _verbose(verbose) , _prefix("") #endif { } -#if !DYLD_IN_PROCESS +#if BUILDING_CACHE_BUILDER Diagnostics::Diagnostics(const std::string& prefix, bool verbose) : _verbose(verbose) , _prefix(prefix) @@ -77,20 +77,23 @@ Diagnostics::~Diagnostics() void Diagnostics::error(const char* format, ...) { - _buffer = _simple_salloc(); va_list list; va_start(list, format); - _simple_vsprintf(_buffer, format, list); + error(format, list); va_end(list); +} + +void Diagnostics::error(const char* format, va_list list) +{ + _buffer = _simple_salloc(); + _simple_vsprintf(_buffer, format, list); -#if !DYLD_IN_PROCESS +#if BUILDING_CACHE_BUILDER 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); @@ -123,7 +126,7 @@ void Diagnostics::assertNoError() const abort_report_np("%s", _simple_string(_buffer)); } -#if DYLD_IN_PROCESS +#if !BUILDING_CACHE_BUILDER const char* Diagnostics::errorMessage() const { return _simple_string(_buffer); diff --git a/dyld3/Diagnostics.h b/dyld3/Diagnostics.h index 81fcf82..21cdf41 100644 --- a/dyld3/Diagnostics.h +++ b/dyld3/Diagnostics.h @@ -27,7 +27,7 @@ #include -#if !DYLD_IN_PROCESS +#if BUILDING_CACHE_BUILDER #include #include #include @@ -44,7 +44,8 @@ public: ~Diagnostics(); void error(const char* format, ...) __attribute__((format(printf, 2, 3))); -#if !DYLD_IN_PROCESS + void error(const char* format, va_list list); +#if BUILDING_CACHE_BUILDER Diagnostics(const std::string& prefix, bool verbose=false); void warning(const char* format, ...) __attribute__((format(printf, 2, 3))); @@ -57,7 +58,7 @@ public: void clearError(); void assertNoError() const; -#if DYLD_IN_PROCESS +#if !BUILDING_CACHE_BUILDER const char* errorMessage() const; #else const std::string prefix() const; @@ -68,7 +69,7 @@ public: private: void* _buffer = nullptr; -#if !DYLD_IN_PROCESS +#if BUILDING_CACHE_BUILDER std::string _prefix; std::set _warnings; bool _verbose = false; diff --git a/dyld3/DyldCacheParser.cpp b/dyld3/DyldCacheParser.cpp deleted file mode 100644 index 4547027..0000000 --- a/dyld3/DyldCacheParser.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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 deleted file mode 100644 index 34deee4..0000000 --- a/dyld3/DyldCacheParser.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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/JSONWriter.h b/dyld3/JSONWriter.h new file mode 100644 index 0000000..e25cb2e --- /dev/null +++ b/dyld3/JSONWriter.h @@ -0,0 +1,112 @@ +/* + * 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 + +namespace dyld3 { +namespace json { + +struct Node +{ + std::string value; + std::map map; + std::vector array; +}; + +static inline std::string hex(uint64_t value) { + char buff[64]; + sprintf(buff, "0x%llX", value); + return buff; +} + +static inline std::string hex4(uint64_t value) { + char buff[64]; + sprintf(buff, "0x%04llX", value); + return buff; +} + +static inline std::string hex8(uint64_t value) { + char buff[64]; + sprintf(buff, "0x%08llX", value); + return buff; +} + +static inline std::string decimal(uint64_t value) { + char buff[64]; + sprintf(buff, "%llu", value); + return buff; +} + +static inline void indentBy(uint32_t spaces, FILE* out) { + for (int i=0; i < spaces; ++i) { + fprintf(out, " "); + } +} + +static inline 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"); +} + + +} // namespace json +} // namespace dyld3 diff --git a/dyld3/LaunchCache.h b/dyld3/LaunchCache.h deleted file mode 100644 index 447a2c7..0000000 --- a/dyld3/LaunchCache.h +++ /dev/null @@ -1,382 +0,0 @@ -/* - * 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 deleted file mode 100644 index bea67ad..0000000 --- a/dyld3/LaunchCacheFormat.h +++ /dev/null @@ -1,346 +0,0 @@ -/* - * 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 deleted file mode 100644 index f5efd16..0000000 --- a/dyld3/LaunchCachePrinter.cpp +++ /dev/null @@ -1,457 +0,0 @@ -/* - * 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 deleted file mode 100644 index 5b99e75..0000000 --- a/dyld3/LaunchCacheReader.cpp +++ /dev/null @@ -1,1503 +0,0 @@ -/* - * 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); - }) ) { - switch (foundInfo.kind) { - case MachOParser::FoundSymbol::Kind::headerOffset: - case MachOParser::FoundSymbol::Kind::resolverOffset: - result = ((uintptr_t)(foundInfo.foundInDylib) + (uintptr_t)foundInfo.value); - break; - case MachOParser::FoundSymbol::Kind::absolute: - result = (uintptr_t)foundInfo.value; - break; - } - 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) ) { - switch (foundInfo.kind) { - case MachOParser::FoundSymbol::Kind::headerOffset: - case MachOParser::FoundSymbol::Kind::resolverOffset: - result = ((uintptr_t)(foundInfo.foundInDylib) + (uintptr_t)foundInfo.value); - break; - case MachOParser::FoundSymbol::Kind::absolute: - result = (uintptr_t)foundInfo.value; - break; - } - 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) ) { - switch (foundInfo.kind) { - case MachOParser::FoundSymbol::Kind::headerOffset: - case MachOParser::FoundSymbol::Kind::resolverOffset: - result = ((uintptr_t)(foundInfo.foundInDylib) + (uintptr_t)foundInfo.value); - break; - case MachOParser::FoundSymbol::Kind::absolute: - result = (uintptr_t)foundInfo.value; - break; - } - 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) ) { - switch (foundInfo.kind) { - case MachOParser::FoundSymbol::Kind::headerOffset: - case MachOParser::FoundSymbol::Kind::resolverOffset: - result = ((uintptr_t)(foundInfo.foundInDylib) + (uintptr_t)foundInfo.value); - break; - case MachOParser::FoundSymbol::Kind::absolute: - result = (uintptr_t)foundInfo.value; - break; - } - 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 deleted file mode 100644 index e51fbdf..0000000 --- a/dyld3/LaunchCacheWriter.cpp +++ /dev/null @@ -1,1285 +0,0 @@ -/* - * 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 deleted file mode 100644 index a00ea93..0000000 --- a/dyld3/LaunchCacheWriter.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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 index d34431a..2ac09de 100644 --- a/dyld3/Loading.cpp +++ b/dyld3/Loading.cpp @@ -22,6 +22,7 @@ */ +#include #include #include @@ -43,128 +44,361 @@ #include #include #include +#include -#include "LaunchCache.h" -#include "LaunchCacheFormat.h" +#include "MachOFile.h" +#include "MachOLoaded.h" #include "Logging.h" #include "Loading.h" -#include "MachOParser.h" +#include "Tracing.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 +namespace { -static bool sandboxBlocked(const char* path, const char* kind) +// utility to track a set of ImageNum's in use +class VIS_HIDDEN ImageNumSet { -#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 +public: + void add(dyld3::closure::ImageNum num); + bool contains(dyld3::closure::ImageNum num) const; + +private: + std::bitset<5120> _bitmap; + dyld3::OverflowSafeArray _overflowArray; +}; + +void ImageNumSet::add(dyld3::closure::ImageNum num) +{ + if ( num < 5120 ) + _bitmap.set(num); + else + _overflowArray.push_back(num); +} + +bool ImageNumSet::contains(dyld3::closure::ImageNum num) const +{ + if ( num < 5120 ) + return _bitmap.test(num); + + for (dyld3::closure::ImageNum existingNum : _overflowArray) { + if ( existingNum == num ) + return true; + } return false; -#endif } +} // namespace anonymous + -static bool sandboxBlockedMmap(const char* path) +namespace dyld3 { + +Loader::Loader(Array& storage, const void* cacheAddress, const Array& imagesArrays, + LogFunc logLoads, LogFunc logSegments, LogFunc logFixups, LogFunc logDofs) + : _allImages(storage), _imagesArrays(imagesArrays), _dyldCacheAddress(cacheAddress), + _logLoads(logLoads), _logSegments(logSegments), _logFixups(logFixups), _logDofs(logDofs) { - return sandboxBlocked(path, "file-map-executable"); } -static bool sandboxBlockedOpen(const char* path) +void Loader::addImage(const LoadedImage& li) { - return sandboxBlocked(path, "file-read-data"); + _allImages.push_back(li); } -static bool sandboxBlockedStat(const char* path) +LoadedImage* Loader::findImage(closure::ImageNum targetImageNum) { - return sandboxBlocked(path, "file-read-metadata"); + for (LoadedImage& info : _allImages) { + if ( info.image()->representsImageNum(targetImageNum) ) + return &info; + } + return nullptr; } -#if TARGET_OS_WATCH || TARGET_OS_BRIDGE -static uint64_t pageAlign(uint64_t value) +uintptr_t Loader::resolveTarget(closure::Image::ResolvedSymbolTarget target) { - #if __arm64__ - return (value + 0x3FFF) & (-0x4000); - #else - return (value + 0xFFF) & (-0x1000); - #endif + const LoadedImage* info; + switch ( target.sharedCache.kind ) { + case closure::Image::ResolvedSymbolTarget::kindSharedCache: + assert(_dyldCacheAddress != nullptr); + return (uintptr_t)_dyldCacheAddress + (uintptr_t)target.sharedCache.offset; + + case closure::Image::ResolvedSymbolTarget::kindImage: + info = findImage(target.image.imageNum); + assert(info != nullptr); + return (uintptr_t)(info->loadedAddress()) + (uintptr_t)target.image.offset; + + case closure::Image::ResolvedSymbolTarget::kindAbsolute: + if ( target.absolute.value & (1ULL << 62) ) + return (uintptr_t)(target.absolute.value | 0xC000000000000000ULL); + else + return (uintptr_t)target.absolute.value; + } + assert(0 && "malformed ResolvedSymbolTarget"); + return 0; } -#endif -static void updateSliceOffset(uint64_t& sliceOffset, uint64_t codeSignEndOffset, size_t fileLen) + +void Loader::completeAllDependents(Diagnostics& diag, uintptr_t topIndex) { -#if TARGET_OS_WATCH || TARGET_OS_BRIDGE - if ( sliceOffset != 0 ) { - if ( pageAlign(codeSignEndOffset) == pageAlign(fileLen) ) { - // cache builder saw fat file, but file is now thin - sliceOffset = 0; - return; + // accumulate all image overrides + STACK_ALLOC_ARRAY(ImageOverride, overrides, _allImages.maxCount()); + for (const auto anArray : _imagesArrays) { + // ignore prebuilt Image* in dyld cache + if ( anArray->startImageNum() < dyld3::closure::kFirstLaunchClosureImageNum ) + continue; + anArray->forEachImage(^(const dyld3::closure::Image* image, bool& stop) { + ImageOverride overrideEntry; + if ( image->isOverrideOfDyldCacheImage(overrideEntry.inCache) ) { + overrideEntry.replacement = image->imageNum(); + overrides.push_back(overrideEntry); + } + }); + } + + // make cache for fast lookup of already loaded images + __block ImageNumSet alreadyLoaded; + for (int i=0; i <= topIndex; ++i) { + alreadyLoaded.add(_allImages[i].image()->imageNum()); + } + + // for each image in _allImages, starting at topIndex, make sure its depenents are in _allImages + uintptr_t index = topIndex; + while ( (index < _allImages.count()) && diag.noError() ) { + const closure::Image* image = _allImages[index].image(); + //fprintf(stderr, "completeAllDependents(): looking at dependents of %s\n", image->path()); + image->forEachDependentImage(^(uint32_t depIndex, closure::Image::LinkKind kind, closure::ImageNum depImageNum, bool& stop) { + // check if imageNum needs to be changed to an override + for (const ImageOverride& entry : overrides) { + if ( entry.inCache == depImageNum ) { + depImageNum = entry.replacement; + break; + } + } + // check if this dependent is already loaded + if ( !alreadyLoaded.contains(depImageNum) ) { + // if not, look in imagesArrays + const closure::Image* depImage = closure::ImageArray::findImage(_imagesArrays, depImageNum); + if ( depImage != nullptr ) { + //dyld::log(" load imageNum=0x%05X, image path=%s\n", depImageNum, depImage->path()); + if ( _allImages.freeCount() == 0 ) { + diag.error("too many initial images"); + stop = true; + } + else { + _allImages.push_back(LoadedImage::make(depImage)); + } + alreadyLoaded.add(depImageNum); + } + else { + diag.error("unable to locate imageNum=0x%04X, depIndex=%d of %s", depImageNum, depIndex, image->path()); + stop = true; + } + } + }); + ++index; + } +} + +void Loader::mapAndFixupAllImages(Diagnostics& diag, bool processDOFs, bool fromOFI, uintptr_t topIndex) +{ + // scan array and map images not already loaded + for (uintptr_t i=topIndex; i < _allImages.count(); ++i) { + LoadedImage& info = _allImages[i]; + if ( info.loadedAddress() != nullptr ) { + // log main executable's segments + if ( (info.loadedAddress()->filetype == MH_EXECUTE) && (info.state() == LoadedImage::State::mapped) ) { + if ( _logSegments("dyld: mapped by kernel %s\n", info.image()->path()) ) { + info.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.loadedAddress() + vmOffset; + uint64_t end = start+vmSize-1; + if ( (segIndex == 0) && (permissions == 0) ) { + start = 0; + } + _logSegments("%14s (%c%c%c) 0x%012llX->0x%012llX \n", info.loadedAddress()->segmentName(segIndex), + (permissions & PROT_READ) ? 'r' : '.', (permissions & PROT_WRITE) ? 'w' : '.', (permissions & PROT_EXEC) ? 'x' : '.' , + start, end); + }); + } + } + // skip over ones already loaded + continue; + } + if ( info.image()->inDyldCache() ) { + if ( info.image()->overridableDylib() ) { + struct stat statBuf; + if ( stat(info.image()->path(), &statBuf) == 0 ) { + // verify file has not changed since closure was built + uint64_t inode; + uint64_t mtime; + if ( info.image()->hasFileModTimeAndInode(inode, mtime) ) { + if ( (statBuf.st_mtime != mtime) || (statBuf.st_ino != inode) ) { + diag.error("dylib file mtime/inode changed since closure was built for '%s'", info.image()->path()); + } + } + else { + diag.error("dylib file not expected on disk, must be a root '%s'", info.image()->path()); + } + } + else if ( (_dyldCacheAddress != nullptr) && ((dyld_cache_header*)_dyldCacheAddress)->dylibsExpectedOnDisk ) { + diag.error("dylib file missing, was in dyld shared cache '%s'", info.image()->path()); + } + } + if ( diag.noError() ) { + info.setLoadedAddress((MachOLoaded*)((uintptr_t)_dyldCacheAddress + info.image()->cacheOffset())); + info.setState(LoadedImage::State::fixedUp); + if ( _logSegments("dyld: Using from dyld cache %s\n", info.image()->path()) ) { + info.image()->forEachCacheSegment(^(uint32_t segIndex, uint64_t vmOffset, uint64_t vmSize, uint8_t permissions, bool &stop) { + _logSegments("%14s (%c%c%c) 0x%012lX->0x%012lX \n", info.loadedAddress()->segmentName(segIndex), + (permissions & PROT_READ) ? 'r' : '.', (permissions & PROT_WRITE) ? 'w' : '.', (permissions & PROT_EXEC) ? 'x' : '.' , + (long)info.loadedAddress()+(long)vmOffset, (long)info.loadedAddress()+(long)vmOffset+(long)vmSize-1); + }); + } + } + } + else { + mapImage(diag, info, fromOFI); + if ( diag.hasError() ) + break; // out of for loop + } + + } + if ( diag.hasError() ) { + // bummer, need to clean up by unmapping any images just mapped + for (LoadedImage& info : _allImages) { + if ( (info.state() == LoadedImage::State::mapped) && !info.image()->inDyldCache() && !info.leaveMapped() ) { + _logSegments("dyld: unmapping %s\n", info.image()->path()); + unmapImage(info); + } + } + return; + } + + // apply fixups + for (uintptr_t i=topIndex; i < _allImages.count(); ++i) { + LoadedImage& info = _allImages[i]; + // images in shared cache do not need fixups applied + if ( info.image()->inDyldCache() ) + continue; + // previously loaded images were previously fixed up + if ( info.state() < LoadedImage::State::fixedUp ) { + applyFixupsToImage(diag, info); + if ( diag.hasError() ) + break; + info.setState(LoadedImage::State::fixedUp); } } + + // find and register dtrace DOFs + if ( processDOFs ) { + STACK_ALLOC_OVERFLOW_SAFE_ARRAY(DOFInfo, dofImages, _allImages.count()); + for (uintptr_t i=topIndex; i < _allImages.count(); ++i) { + LoadedImage& info = _allImages[i]; + info.image()->forEachDOF(info.loadedAddress(), ^(const void* section) { + DOFInfo dofInfo; + dofInfo.dof = section; + dofInfo.imageHeader = info.loadedAddress(); + dofInfo.imageShortName = info.image()->leafName(); + dofImages.push_back(dofInfo); + }); + } + registerDOFs(dofImages); + } +} + +bool Loader::sandboxBlocked(const char* path, const char* kind) +{ +#if TARGET_IPHONE_SIMULATOR + // sandbox calls not yet supported in dyld_sim + return false; +#else + sandbox_filter_type filter = (sandbox_filter_type)(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT); + return ( sandbox_check(getpid(), kind, filter, path) > 0 ); #endif } -static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagnostics& diag, LogFunc log_loads, LogFunc log_segments) +bool Loader::sandboxBlockedMmap(const char* path) { - 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; + return sandboxBlocked(path, "file-map-executable"); +} + +bool Loader::sandboxBlockedOpen(const char* path) +{ + return sandboxBlocked(path, "file-read-data"); +} + +bool Loader::sandboxBlockedStat(const char* path) +{ + return sandboxBlocked(path, "file-read-metadata"); +} + +void Loader::mapImage(Diagnostics& diag, LoadedImage& info, bool fromOFI) +{ + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_MAP_IMAGE, info.image()->path(), 0, 0); + + const closure::Image* image = info.image(); + uint64_t sliceOffset = image->sliceOffsetInFile(); + const uint64_t totalVMSize = image->vmSizeToMap(); + uint32_t codeSignFileOffset; + uint32_t codeSignFileSize; + bool isCodeSigned = image->hasCodeSignature(codeSignFileOffset, codeSignFileSize); // open file - int fd = ::open(image.path(), O_RDONLY, 0); + int fd = ::open(info.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()); + 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; + diag.error("open(\"%s\", O_RDONLY) failed with errno=%d", image->path(), openErr); + return; } // get file info struct stat statBuf; #if TARGET_IPHONE_SIMULATOR - if ( stat(image.path(), &statBuf) != 0 ) { + 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()); + 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); + diag.error("stat(\"%s\") failed with errno=%d", image->path(), statErr); close(fd); - return nullptr; + return; } // 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()); + uint64_t inode; + uint64_t mtime; + if ( image->hasFileModTimeAndInode(inode, mtime) ) { + if ( (statBuf.st_mtime != mtime) || (statBuf.st_ino != inode) ) { + diag.error("file mtime/inode changed since closure was built for '%s'", image->path()); close(fd); - return nullptr; + return; } } - // handle OS dylibs being thinned after closure was built - if ( image.group().groupNum() == 1 ) - updateSliceOffset(sliceOffset, codeSignFileOffset+codeSignFileSize, (size_t)statBuf.st_size); + // handle case on iOS where sliceOffset in closure is wrong because file was thinned after cache was built + if ( (_dyldCacheAddress != nullptr) && !(((dyld_cache_header*)_dyldCacheAddress)->dylibsExpectedOnDisk) ) { + if ( sliceOffset != 0 ) { + if ( round_page_kernel(codeSignFileOffset+codeSignFileSize) == round_page_kernel(statBuf.st_size) ) { + // file is now thin + sliceOffset = 0; + } + } + } // register code signature uint64_t coveredCodeLength = UINT64_MAX; - if ( codeSignFileOffset != 0 ) { + if ( isCodeSigned ) { + auto sigTimer = ScopedTimer(DBG_DYLD_TIMING_ATTACH_CODESIGNATURE, 0, 0, 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 @@ -174,22 +408,25 @@ static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagn 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()); + 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()); + errnoCopy, sliceOffset, codeSignFileOffset, codeSignFileSize, image->path()); } close(fd); - return nullptr; + return; } coveredCodeLength = siginfo.fs_file_start; - if ( coveredCodeLength < image.asDiskImage()->codeSignFileOffset ) { + if ( coveredCodeLength < codeSignFileOffset ) { diag.error("code signature does not cover entire file up to signature"); close(fd); - return nullptr; + return; } + } + // dyld should use F_CHECK_LV even on unsigned binaries + { // always call F_CHECK_LV to preflight fchecklv checkInfo; char messageBuffer[512]; @@ -199,9 +436,9 @@ static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagn 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); + diag.error("code signature in (%s) not valid for use in process: %s", image->path(), messageBuffer); close(fd); - return nullptr; + return; } } @@ -211,20 +448,20 @@ static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagn if ( r != KERN_SUCCESS ) { diag.error("vm_allocate(size=0x%0llX) failed with result=%d", totalVMSize, r); close(fd); - return nullptr; + return; } if ( sliceOffset != 0 ) - log_segments("dyld: Mapping %s (slice offset=%llu)\n", image.path(), sliceOffset); + _logSegments("dyld: Mapping %s (slice offset=%llu)\n", image->path(), sliceOffset); else - log_segments("dyld: Mapping %s\n", image.path()); + _logSegments("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) { + 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; @@ -232,13 +469,13 @@ static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagn 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()); + 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()); + 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()); + 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; @@ -250,30 +487,32 @@ static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagn // sanity check first segment is mach-o header if ( (segAddress != MAP_FAILED) && !mappedFirstSegment ) { mappedFirstSegment = true; - if ( !MachOParser::isMachO(diag, segAddress, fileSize) ) { + const MachOFile* mf = (MachOFile*)segAddress; + if ( !mf->isMachO(diag, 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), + const MachOLoaded* lmo = (MachOLoaded*)loadAddress; + _logSegments("%14s (%c%c%c) 0x%012lX->0x%012lX \n", lmo->segmentName(segIndex), (permissions & PROT_READ) ? 'r' : '.', (permissions & PROT_WRITE) ? 'w' : '.', (permissions & PROT_EXEC) ? 'x' : '.' , - (long)segAddress, (long)segAddress+vmSize-1); + (long)segAddress, (long)segAddress+(long)vmSize-1); } }); if ( mmapFailure ) { - vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize); - close(fd); - return nullptr; + ::vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize); + ::close(fd); + return; } // close file close(fd); - #if BUILDING_LIBDYLD +#if BUILDING_LIBDYLD // verify file has not changed since closure was built by checking code signature has not changed - if ( image.validateUsingCdHash() ) { + uint8_t cdHashExpected[20]; + if ( image->hasCdHash(cdHashExpected) ) { if ( codeSignatureStartAddress == nullptr ) { diag.error("code signature missing"); } @@ -281,18 +520,19 @@ static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagn 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 ) + uint8_t cdHashFound[20]; + const MachOLoaded* lmo = (MachOLoaded*)loadAddress; + if ( lmo->cdHashOfCodeSignature(codeSignatureStartAddress, codeSignFileSize, cdHashFound) ) { + if ( ::memcmp(cdHashFound, cdHashExpected, 20) != 0 ) diag.error("code signature changed since closure was built"); } - else{ + else { diag.error("code signature format invalid"); } } if ( diag.hasError() ) { - vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize); - return nullptr; + ::vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize); + return; } } #endif @@ -301,377 +541,223 @@ static const mach_header* mapImage(const dyld3::launch_cache::Image image, Diagn // tell kernel about fairplay encrypted regions uint32_t fpTextOffset; uint32_t fpSize; - if ( image.isFairPlayEncrypted(fpTextOffset, 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; + int result = ::mremap_encrypted(((uint8_t*)mh) + fpTextOffset, fpSize, 1, mh->cputype, mh->cpusubtype); + if ( result != 0 ) { + diag.error("could not register fairplay decryption, mremap_encrypted() => %d", result); + ::vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize); + return; + } } #endif - log_loads("dyld: load %s\n", image.path()); + _logLoads("dyld: load %s\n", image->path()); - return (mach_header*)loadAddress; + timer.setData4((uint64_t)loadAddress); + info.setLoadedAddress((MachOLoaded*)loadAddress); + info.setState(LoadedImage::State::mapped); } - -void unmapImage(const launch_cache::binary_format::Image* binImage, const mach_header* loadAddress) +void Loader::unmapImage(LoadedImage& info) { - assert(loadAddress != nullptr); - launch_cache::Image image(binImage); - vm_deallocate(mach_task_self(), (vm_address_t)loadAddress, (vm_size_t)(image.vmSizeToMap())); + assert(info.loadedAddress() != nullptr); + ::vm_deallocate(mach_task_self(), (vm_address_t)info.loadedAddress(), (vm_size_t)(info.image()->vmSizeToMap())); + info.setLoadedAddress(nullptr); } - -static void applyFixupsToImage(Diagnostics& diag, const mach_header* imageMH, const launch_cache::binary_format::Image* imageData, - launch_cache::TargetSymbolValue::LoadedImages& imageResolver, LogFunc log_fixups) +void Loader::registerDOFs(const Array& dofs) { - 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; + if ( dofs.empty() ) + return; + + int fd = open("/dev/" DTRACEMNR_HELPER, O_RDWR); + if ( fd < 0 ) { + _logDofs("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) + dofs.count()*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 = dofs.count(); + for (unsigned int i=0; i < dofs.count(); ++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); } - #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; + + // 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 < dofs.count(); ++i) { + _logDofs("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)); } } - #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; + else { + _logDofs("dyld: ioctl to register dtrace DOF section failed\n"); } - }); - return result; + close(fd); + } } -void CurrentLoadImages::forEachImage(void (^handler)(uint32_t anIndex, const launch_cache::binary_format::Image*, const mach_header*, bool& stop)) +bool Loader::dtraceUserProbesEnabled() { - 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; +#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_IPHONE_SIMULATOR + int dof_mode; + size_t dof_mode_size = sizeof(dof_mode); + if ( sysctlbyname("kern.dtrace.dof_mode", &dof_mode, &dof_mode_size, nullptr, 0) == 0 ) { + return ( dof_mode != 0 ); } + return false; +#else + // dtrace is always available for macOS and simulators + return true; +#endif } -struct DOFInfo { - const void* dof; - const mach_header* imageHeader; - const char* imageShortName; -}; -static void registerDOFs(const DOFInfo* dofs, uint32_t dofSectionCount, LogFunc log_dofs) +void Loader::vmAccountingSetSuspended(bool suspend, LogFunc logger) { - 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); - } - } +#if __arm__ || __arm64__ + // dyld should tell the kernel when it is doing fix-ups caused by roots + logger("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 mapAndFixupImages(Diagnostics& diag, launch_cache::DynArray& images, const uint8_t* cacheLoadAddress, - LogFunc log_loads, LogFunc log_segments, LogFunc log_fixups, LogFunc log_dofs) +void Loader::applyFixupsToImage(Diagnostics& diag, LoadedImage& info) { - // 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); - }); + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_FIXUPS, (uint64_t)info.loadedAddress(), 0, 0); + closure::ImageNum cacheImageNum; + const char* leafName = info.image()->leafName(); + const closure::Image* image = info.image(); + const uint8_t* imageLoadAddress = (uint8_t*)info.loadedAddress(); + uintptr_t slide = info.loadedAddress()->getSlide(); + bool overrideOfCache = info.image()->isOverrideOfDyldCacheImage(cacheImageNum); + if ( overrideOfCache ) + vmAccountingSetSuspended(true, _logFixups); + image->forEachFixup(^(uint64_t imageOffsetToRebase, bool &stop) { + uintptr_t* fixUpLoc = (uintptr_t*)(imageLoadAddress + imageOffsetToRebase); + *fixUpLoc += slide; + _logFixups("dyld: fixup: %s:%p += %p\n", leafName, fixUpLoc, (void*)slide); + }, + ^(uint64_t imageOffsetToBind, closure::Image::ResolvedSymbolTarget bindTarget, bool &stop) { + uintptr_t* fixUpLoc = (uintptr_t*)(imageLoadAddress + imageOffsetToBind); + uintptr_t value = resolveTarget(bindTarget); + _logFixups("dyld: fixup: %s:%p = %p\n", leafName, fixUpLoc, (void*)value); + *fixUpLoc = value; + }, + ^(uint64_t imageOffsetStart, const Array& targets, bool& stop) { + // walk each fixup in the chain + image->forEachChainedFixup((void*)imageLoadAddress, imageOffsetStart, ^(uint64_t* fixupLoc, MachOLoaded::ChainedFixupPointerOnDisk fixupInfo, bool& stopChain) { + if ( fixupInfo.authRebase.auth ) { + #if __has_feature(ptrauth_calls) + if ( fixupInfo.authBind.bind ) { + closure::Image::ResolvedSymbolTarget bindTarget = targets[fixupInfo.authBind.ordinal]; + uint64_t targetAddr = resolveTarget(bindTarget); + // Don't sign missing weak imports. + if (targetAddr != 0) + targetAddr = fixupInfo.signPointer(fixupLoc, targetAddr); + _logFixups("dyld: fixup: *%p = %p (JOP: diversity 0x%04X, addr-div=%d, key=%s)\n", + fixupLoc, (void*)targetAddr, fixupInfo.authBind.diversity, fixupInfo.authBind.addrDiv, fixupInfo.authBind.keyName()); + *fixupLoc = targetAddr; } + else { + uint64_t targetAddr = (uint64_t)imageLoadAddress + fixupInfo.authRebase.target; + targetAddr = fixupInfo.signPointer(fixupLoc, targetAddr); + _logFixups("dyld: fixup: *%p = %p (JOP: diversity 0x%04X, addr-div=%d, key=%s)\n", + fixupLoc, (void*)targetAddr, fixupInfo.authRebase.diversity, fixupInfo.authRebase.addrDiv, fixupInfo.authRebase.keyName()); + *fixupLoc = targetAddr; + } + #else + diag.error("malformed chained pointer"); + stop = true; + stopChain = true; + #endif } - // 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 ( fixupInfo.plainRebase.bind ) { + closure::Image::ResolvedSymbolTarget bindTarget = targets[fixupInfo.plainBind.ordinal]; + uint64_t targetAddr = resolveTarget(bindTarget) + fixupInfo.plainBind.signExtendedAddend(); + _logFixups("dyld: fixup: %s:%p = %p\n", leafName, fixupLoc, (void*)targetAddr); + *fixupLoc = targetAddr; } 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 - } + uint64_t targetAddr = fixupInfo.plainRebase.signExtendedTarget() + slide; + _logFixups("dyld: fixup: %s:%p += %p\n", leafName, fixupLoc, (void*)slide); + *fixupLoc = targetAddr; } } - 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; - } +#if __i386__ + __block bool segmentsMadeWritable = false; + image->forEachTextReloc(^(uint32_t imageOffsetToRebase, bool& stop) { + if ( !segmentsMadeWritable ) + setSegmentProtects(info, true); + uintptr_t* fixUpLoc = (uintptr_t*)(imageLoadAddress + imageOffsetToRebase); + *fixUpLoc += slide; + _logFixups("dyld: fixup: %s:%p += %p\n", leafName, fixUpLoc, (void*)slide); + }, + ^(uint32_t imageOffsetToBind, closure::Image::ResolvedSymbolTarget bindTarget, bool& stop) { + // FIXME + }); + if ( segmentsMadeWritable ) + setSegmentProtects(info, false); +#endif - // 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; - }); - } + if ( overrideOfCache ) + vmAccountingSetSuspended(false, _logFixups); +} - // 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; - }); +#if __i386__ +void Loader::setSegmentProtects(const LoadedImage& info, bool write) +{ + info.image()->forEachDiskSegment(^(uint32_t segIndex, uint32_t fileOffset, uint32_t fileSize, int64_t vmOffset, uint64_t vmSize, uint8_t protections, bool& segStop) { + if ( protections & VM_PROT_WRITE ) + return; + uint32_t regionProt = protections; + if ( write ) + regionProt = VM_PROT_WRITE | VM_PROT_READ; + kern_return_t r = vm_protect(mach_task_self(), ((uintptr_t)info.loadedAddress())+(uintptr_t)vmOffset, (uintptr_t)vmSize, false, regionProt); + assert( r == KERN_SUCCESS ); + }); +} +#endif + +#if BUILDING_DYLD +void forEachLineInFile(const char* buffer, size_t bufferLen, void (^lineHandler)(const char* line, bool& stop)) +{ + bool stop = false; + const char* const eof = &buffer[bufferLen]; + for (const char* s = buffer; s < eof; ++s) { + char lineBuffer[MAXPATHLEN]; + char* t = lineBuffer; + char* tEnd = &lineBuffer[MAXPATHLEN]; + while ( (s < eof) && (t != tEnd) ) { + if ( *s == '\n' ) + break; + *t++ = *s++; } - registerDOFs(dofImages, dofCount, log_dofs); + *t = '\0'; + lineHandler(lineBuffer, stop); + if ( stop ) + break; } } -#if BUILDING_DYLD void forEachLineInFile(const char* path, void (^lineHandler)(const char* line, bool& stop)) { int fd = dyld::my_open(path, O_RDONLY, 0); @@ -680,22 +766,7 @@ void forEachLineInFile(const char* path, void (^lineHandler)(const char* line, b 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; - } + forEachLineInFile(lines, (size_t)statBuf.st_size, lineHandler); munmap((void*)lines, (size_t)statBuf.st_size); } } @@ -796,10 +867,6 @@ void __cxa_pure_virtual() } #endif - -#endif // DYLD_IN_PROCESS - -} // namespace loader } // namespace dyld3 diff --git a/dyld3/Loading.h b/dyld3/Loading.h index 177543b..e5f22a6 100644 --- a/dyld3/Loading.h +++ b/dyld3/Loading.h @@ -30,50 +30,119 @@ #include #include #include <_simple.h> -#include "LaunchCache.h" -#include "LaunchCacheFormat.h" -#include "MachOParser.h" -#include "ClosureBuffer.h" - +#include "Closure.h" +#include "MachOLoaded.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; +// +// Tuple of info about a loaded image. Contains the loaded address, Image*, and state. +// +class VIS_HIDDEN LoadedImage { +public: + enum class State { unmapped=0, mapped=1, fixedUp=2, beingInited=3, inited=4 }; + + static LoadedImage make(const closure::Image* img) { LoadedImage result; result._image = img; return result; } + static LoadedImage make(const closure::Image* img, const MachOLoaded* mh) + { LoadedImage result; result._image = img; result.setLoadedAddress(mh); return result; } + + const closure::Image* image() const { return _image; } + const MachOLoaded* loadedAddress() const { return (MachOLoaded*)(_loadAddr & (-4096)); } + void setLoadedAddress(const MachOLoaded* a) { _loadAddr |= ((uintptr_t)a & (-4096)); } + State state() const { return (State)(asBits().state); } + void setState(State s) { asBits().state = (int)s; } + bool hideFromFlatSearch() const { return asBits().hide; } + void setHideFromFlatSearch(bool h) { asBits().hide = h; } + bool leaveMapped() const { return asBits().leaveMapped; } + void markLeaveMapped() { asBits().leaveMapped = true; } + +private: + // since loaded MachO files are always page aligned, that means at least low 12-bits are always zero + // so we don't need to record the low 12 bits, instead those bits hold various flags in the _loadeAddr field + struct AddrBits { + uintptr_t state : 3, + hide : 1, + leaveMapped : 1, + extra : 7, + #if __LP64__ + addr : 52; + #else + addr : 20; + #endif + }; + AddrBits& asBits() { return *((AddrBits*)&_loadAddr); } + const AddrBits& asBits() const { return *((AddrBits*)&_loadAddr); } + + // Note: this must be statically initializable so as to not cause static initializers + const closure::Image* _image = nullptr; + uintptr_t _loadAddr = 0; // really AddrBits }; -#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; +// +// Utility class to recursively load dependents +// +class VIS_HIDDEN Loader { +public: + typedef bool (*LogFunc)(const char*, ...) __attribute__((format(printf, 1, 2))); + + Loader(Array& storage, const void* cacheAddress, const Array& imagesArrays, + LogFunc log_loads, LogFunc log_segments, LogFunc log_fixups, LogFunc log_dofs); + + void addImage(const LoadedImage&); + void completeAllDependents(Diagnostics& diag, uintptr_t topIndex=0); + void mapAndFixupAllImages(Diagnostics& diag, bool processDOFs, bool fromOFI=false, uintptr_t topIndex=0); + uintptr_t resolveTarget(closure::Image::ResolvedSymbolTarget target); + LoadedImage* findImage(closure::ImageNum targetImageNum); + + static void unmapImage(LoadedImage& info); + static bool dtraceUserProbesEnabled(); + static void vmAccountingSetSuspended(bool suspend, LogFunc); + +private: + + struct ImageOverride + { + closure::ImageNum inCache; + closure::ImageNum replacement; + }; + + struct DOFInfo { + const void* dof; + const mach_header* imageHeader; + const char* imageShortName; + }; + + void mapImage(Diagnostics& diag, LoadedImage& info, bool fromOFI); + void applyFixupsToImage(Diagnostics& diag, LoadedImage& info); + void registerDOFs(const Array& dofs); + void setSegmentProtects(const LoadedImage& info, bool write); + bool sandboxBlockedMmap(const char* path); + bool sandboxBlockedOpen(const char* path); + bool sandboxBlockedStat(const char* path); + bool sandboxBlocked(const char* path, const char* kind); + + Array& _allImages; + const Array& _imagesArrays; + const void* _dyldCacheAddress; + LogFunc _logLoads; + LogFunc _logSegments; + LogFunc _logFixups; + LogFunc _logDofs; +}; -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)); +void forEachLineInFile(const char* buffer, size_t bufferLen, void (^lineHandler)(const char* line, bool& stop)); #endif -#endif -} // namespace loader + } // namespace dyld3 diff --git a/dyld3/MachOAnalyzer.cpp b/dyld3/MachOAnalyzer.cpp new file mode 100644 index 0000000..8b3b72b --- /dev/null +++ b/dyld3/MachOAnalyzer.cpp @@ -0,0 +1,2541 @@ +/* + * 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 "MachOAnalyzer.h" +#include "CodeSigningTypes.h" +#include "Array.h" + +#include + + +#ifndef BIND_OPCODE_THREADED + #define BIND_OPCODE_THREADED 0xD0 +#endif + +#ifndef BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB + #define BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB 0x00 +#endif + +#ifndef BIND_SUBOPCODE_THREADED_APPLY + #define BIND_SUBOPCODE_THREADED_APPLY 0x01 +#endif + + +namespace dyld3 { + + +const MachOAnalyzer* MachOAnalyzer::validMainExecutable(Diagnostics& diag, const mach_header* mh, const char* path, uint64_t sliceLength, const char* reqArchName, Platform reqPlatform) +{ + const MachOAnalyzer* result = (const MachOAnalyzer*)mh; + if ( !result->validMachOForArchAndPlatform(diag, (size_t)sliceLength, path, reqArchName, reqPlatform) ) + return nullptr; + if ( !result->isDynamicExecutable() ) + return nullptr; + + return result; +} + + +closure::LoadedFileInfo MachOAnalyzer::load(Diagnostics& diag, const closure::FileSystem& fileSystem, const char* path, const char* reqArchName, Platform reqPlatform) +{ + closure::LoadedFileInfo info; + char realerPath[MAXPATHLEN]; + if (!fileSystem.loadFile(path, info, realerPath, ^(const char *format, ...) { + va_list list; + va_start(list, format); + diag.error(format, list); + va_end(list); + })) { + return closure::LoadedFileInfo(); + } + + // if fat, remap just slice needed + bool fatButMissingSlice; + const FatFile* fh = (FatFile*)info.fileContent; + uint64_t sliceOffset = info.sliceOffset; + uint64_t sliceLen = info.sliceLen; + if ( fh->isFatFileWithSlice(diag, info.fileContentLen, reqArchName, sliceOffset, sliceLen, fatButMissingSlice) ) { + if ( (sliceOffset & 0xFFF) != 0 ) { + // slice not page aligned + if ( strncmp((char*)info.fileContent + sliceOffset, "!", 7) == 0 ) + diag.error("file is static library"); + else + diag.error("slice is not page aligned"); + fileSystem.unloadFile(info); + return closure::LoadedFileInfo(); + } + else { + // unmap anything before slice + fileSystem.unloadPartialFile(info, sliceOffset, sliceLen); + // Update the info to keep track of the new slice offset. + info.sliceOffset = sliceOffset; + info.sliceLen = sliceLen; + } + } + else if ( fatButMissingSlice ) { + diag.error("missing required arch %s in %s", reqArchName, path); + fileSystem.unloadFile(info); + return closure::LoadedFileInfo(); + } + + const MachOAnalyzer* mh = (MachOAnalyzer*)info.fileContent; + + // validate is mach-o of requested arch and platform + if ( !mh->validMachOForArchAndPlatform(diag, (size_t)info.sliceLen, path, reqArchName, reqPlatform) ) { + fileSystem.unloadFile(info); + return closure::LoadedFileInfo(); + } + + // if has zero-fill expansion, re-map + mh = mh->remapIfZeroFill(diag, fileSystem, info); + + // on error, remove mappings and return nullptr + if ( diag.hasError() ) { + fileSystem.unloadFile(info); + return closure::LoadedFileInfo(); + } + + // now that LINKEDIT is at expected offset, finish validation + mh->validLinkedit(diag, path); + + // on error, remove mappings and return nullptr + if ( diag.hasError() ) { + fileSystem.unloadFile(info); + return closure::LoadedFileInfo(); + } + + return info; +} + +#if DEBUG +// only used in debug builds of cache builder to verify segment moves are valid +void MachOAnalyzer::validateDyldCacheDylib(Diagnostics& diag, const char* path) const +{ + validLinkedit(diag, path); + validSegments(diag, path, 0xffffffff); +} +#endif + +uint64_t MachOAnalyzer::mappedSize() const +{ + const uint32_t pageSize = uses16KPages() ? 0x4000 : 0x1000; + __block uint64_t textSegVmAddr = 0; + __block uint64_t vmSpaceRequired = 0; + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__TEXT") == 0 ) { + textSegVmAddr = info.vmAddr; + } + else if ( strcmp(info.segName, "__LINKEDIT") == 0 ) { + vmSpaceRequired = info.vmAddr + ((info.vmSize + (pageSize-1)) & (-pageSize)) - textSegVmAddr; + stop = true; + } + }); + + return vmSpaceRequired; +} + +bool MachOAnalyzer::validMachOForArchAndPlatform(Diagnostics& diag, size_t sliceLength, const char* path, const char* reqArchName, Platform reqPlatform) const +{ + // must start with mach-o magic value + if ( (this->magic != MH_MAGIC) && (this->magic != MH_MAGIC_64) ) { + diag.error("could not use '%s' because it is not a mach-o file, 0x%08X", path, this->magic); + return false; + } + + // must match requested architecture, if specified + if ( reqArchName != nullptr ) { + if ( !this->isArch(reqArchName)) { + // except when looking for x86_64h, fallback to x86_64 + if ( (strcmp(reqArchName, "x86_64h") != 0) || !this->isArch("x86_64") ) { +#if SUPPORT_ARCH_arm64e + // except when looking for arm64e, fallback to arm64 + if ( (strcmp(reqArchName, "arm64e") != 0) || !this->isArch("arm64") ) { +#endif + diag.error("could not use '%s' because it does not contain required architecture %s", path, reqArchName); + return false; +#if SUPPORT_ARCH_arm64e + } +#endif + } + } + } + + // must be a filetype dyld can load + switch ( this->filetype ) { + case MH_EXECUTE: + case MH_DYLIB: + case MH_BUNDLE: + break; + default: + diag.error("could not use '%s' because it is not a dylib, bundle, or executable", path); + return false; + } + + // validate load commands structure + if ( !this->validLoadCommands(diag, path, sliceLength) ) { + return false; + } + + // filter out static executables + if ( (this->filetype == MH_EXECUTE) && !isDynamicExecutable() ) { + diag.error("could not use '%s' because it is a static executable", path); + return false; + } + + // must match requested platform (do this after load commands are validated) + if ( !this->supportsPlatform(reqPlatform) ) { + diag.error("could not use '%s' because it was built for a different platform", path); + return false; + } + + // validate dylib loads + if ( !validEmbeddedPaths(diag, path) ) + return false; + + // validate segments + if ( !validSegments(diag, path, sliceLength) ) + return false; + + // validate entry + if ( this->filetype == MH_EXECUTE ) { + if ( !validMain(diag, path) ) + return false; + } + + // further validations done in validLinkedit() + + return true; +} + +bool MachOAnalyzer::validLinkedit(Diagnostics& diag, const char* path) const +{ + // validate LINKEDIT layout + if ( !validLinkeditLayout(diag, path) ) + return false; + + if ( hasChainedFixups() ) { + if ( !validChainedFixupsInfo(diag, path) ) + return false; + } + else { + // validate rebasing info + if ( !validRebaseInfo(diag, path) ) + return false; + + // validate binding info + if ( !validBindInfo(diag, path) ) + return false; + } + + return true; +} + +bool MachOAnalyzer::validLoadCommands(Diagnostics& diag, const char* path, size_t fileLen) const +{ + // check load command don't exceed file length + if ( this->sizeofcmds + sizeof(mach_header_64) > fileLen ) { + diag.error("in '%s' load commands exceed length of file", path); + return false; + } + + // walk all load commands and sanity check them + Diagnostics walkDiag; + forEachLoadCommand(walkDiag, ^(const load_command* cmd, bool& stop) {}); + if ( walkDiag.hasError() ) { +#if BUILDING_CACHE_BUILDER + diag.error("in '%s' %s", path, walkDiag.errorMessage().c_str()); +#else + diag.error("in '%s' %s", path, walkDiag.errorMessage()); +#endif + return false; + } + + // check load commands fit in TEXT segment + __block bool foundTEXT = false; + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__TEXT") == 0 ) { + foundTEXT = true; + if ( this->sizeofcmds + sizeof(mach_header_64) > info.fileSize ) { + diag.error("in '%s' load commands exceed length of __TEXT segment", path); + } + if ( info.fileOffset != 0 ) { + diag.error("in '%s' __TEXT segment not start of mach-o", path); + } + stop = true; + } + }); + if ( !diag.noError() && !foundTEXT ) { + diag.error("in '%s' __TEXT segment not found", path); + return false; + } + + return true; +} + +const MachOAnalyzer* MachOAnalyzer::remapIfZeroFill(Diagnostics& diag, const closure::FileSystem& fileSystem, closure::LoadedFileInfo& info) const +{ + uint64_t vmSpaceRequired; + auto hasZeroFill = [this, &vmSpaceRequired]() { + __block bool hasZeroFill = false; + __block uint64_t textSegVmAddr = 0; + forEachSegment(^(const SegmentInfo& segmentInfo, bool& stop) { + if ( strcmp(segmentInfo.segName, "__TEXT") == 0 ) { + textSegVmAddr = segmentInfo.vmAddr; + } + else if ( strcmp(segmentInfo.segName, "__LINKEDIT") == 0 ) { + uint64_t vmOffset = segmentInfo.vmAddr - textSegVmAddr; + // A zero fill page in the __DATA segment means the file offset of __LINKEDIT is less than its vm offset + if ( segmentInfo.fileOffset != vmOffset ) + hasZeroFill = true; + vmSpaceRequired = segmentInfo.vmAddr + segmentInfo.vmSize - textSegVmAddr; + stop = true; + } + }); + return hasZeroFill; + }; + + if (hasZeroFill()) { + vm_address_t newMappedAddr; + if ( ::vm_allocate(mach_task_self(), &newMappedAddr, (size_t)vmSpaceRequired, VM_FLAGS_ANYWHERE) != 0 ) { + diag.error("vm_allocate failure"); + return nullptr; + } + // mmap() each segment read-only with standard layout + __block uint64_t textSegVmAddr; + forEachSegment(^(const SegmentInfo& segmentInfo, bool& stop) { + if ( strcmp(segmentInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segmentInfo.vmAddr; + if ( segmentInfo.fileSize != 0 ) { + kern_return_t r = vm_copy(mach_task_self(), (vm_address_t)((long)info.fileContent+segmentInfo.fileOffset), (vm_size_t)segmentInfo.fileSize, (vm_address_t)(newMappedAddr+segmentInfo.vmAddr-textSegVmAddr)); + if ( r != KERN_SUCCESS ) { + diag.error("vm_copy() failure"); + stop = true; + } + } + }); + if ( diag.noError() ) { + // remove original mapping and return new mapping + fileSystem.unloadFile(info); + + // Set vm_deallocate as the unload method. + info.unload = [](const closure::LoadedFileInfo& info) { + ::vm_deallocate(mach_task_self(), (vm_address_t)info.fileContent, (size_t)info.fileContentLen); + }; + + // And update the file content to the new location + info.fileContent = (const void*)newMappedAddr; + info.fileContentLen = vmSpaceRequired; + return (const MachOAnalyzer*)info.fileContent; + } + else { + // new mapping failed, return old mapping with an error in diag + ::vm_deallocate(mach_task_self(), newMappedAddr, (size_t)vmSpaceRequired); + return nullptr; + } + } + + return this; +} + +bool MachOAnalyzer::enforceFormat(Malformed kind) const +{ +#if TARGET_OS_OSX + __block bool result = false; + forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) { + if ( platform == Platform::macOS ) { + switch (kind) { + case Malformed::linkeditOrder: + case Malformed::linkeditAlignment: + case Malformed::dyldInfoAndlocalRelocs: + // enforce these checks on new binaries only + result = (sdk >= 0x000A0E00); // macOS 10.14 + } + } + }); + // if binary is so old, there is no platform info, don't enforce malformed errors + return result; +#else + return true; +#endif +} + +bool MachOAnalyzer::validEmbeddedPaths(Diagnostics& diag, const char* path) const +{ + __block int index = 1; + __block bool allGood = true; + __block bool foundInstallName = false; + __block int dependentsCount = 0; + 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.error("in '%s' load command #%d name offset (%u) outside its size (%u)", path, 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.error("in '%s' load command #%d string extends beyond end of load command", path, index); + stop = true; + allGood = false; + } + } + if ( cmd->cmd != LC_ID_DYLIB ) + ++dependentsCount; + break; + case LC_RPATH: + rpathCmd = (rpath_command*)cmd; + if ( rpathCmd->path.offset > cmd->cmdsize ) { + diag.error("in '%s' load command #%d path offset (%u) outside its size (%u)", path, 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.error("in '%s' load command #%d string extends beyond end of load command", path, index); + stop = true; + allGood = false; + } + } + break; + } + ++index; + }); + if ( !allGood ) + return false; + + if ( this->filetype == MH_DYLIB ) { + if ( !foundInstallName ) { + diag.error("in '%s' MH_DYLIB is missing LC_ID_DYLIB", path); + return false; + } + } + else { + if ( foundInstallName ) { + diag.error("in '%s' LC_ID_DYLIB found in non-MH_DYLIB", path); + return false; + } + } + + if ( (dependentsCount == 0) && (this->filetype == MH_EXECUTE) ) { + diag.error("in '%s' missing LC_LOAD_DYLIB (must link with at least libSystem.dylib)", path); + return false; + } + + return true; +} + +bool MachOAnalyzer::validSegments(Diagnostics& diag, const char* path, size_t fileLen) const +{ + // 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.error("in '%s' load command size too small for LC_SEGMENT_64", path); + badSegmentLoadCommand = true; + stop = true; + } + else if ( (sectionsSpace % sizeof(section_64)) != 0 ) { + diag.error("in '%s' segment load command size 0x%X will not fit whole number of sections", path, cmd->cmdsize); + badSegmentLoadCommand = true; + stop = true; + } + else if ( sectionsSpace != (seg->nsects * sizeof(section_64)) ) { + diag.error("in '%s' load command size 0x%X does not match nsects %d", path, cmd->cmdsize, seg->nsects); + badSegmentLoadCommand = true; + stop = true; + } + else if ( greaterThanAddOrOverflow(seg->fileoff, seg->filesize, fileLen) ) { + diag.error("in '%s' segment load command content extends beyond end of file", path); + 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.error("in '%s' segment filesize exceeds vmsize", path); + 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.error("in '%s' load command size too small for LC_SEGMENT", path); + badSegmentLoadCommand = true; + stop = true; + } + else if ( (sectionsSpace % sizeof(section)) != 0 ) { + diag.error("in '%s' segment load command size 0x%X will not fit whole number of sections", path, cmd->cmdsize); + badSegmentLoadCommand = true; + stop = true; + } + else if ( sectionsSpace != (seg->nsects * sizeof(section)) ) { + diag.error("in '%s' load command size 0x%X does not match nsects %d", path, 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.error("in '%s' segment filesize exceeds vmsize", path); + 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 SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__TEXT") == 0 ) { + if ( info.protections != (VM_PROT_READ|VM_PROT_EXECUTE) ) { + diag.error("in '%s' __TEXT segment permissions is not 'r-x'", path); + badPermissions = true; + stop = true; + } + hasTEXT = true; + } + else if ( strcmp(info.segName, "__LINKEDIT") == 0 ) { + if ( info.protections != VM_PROT_READ ) { + diag.error("in '%s' __LINKEDIT segment permissions is not 'r--'", path); + badPermissions = true; + stop = true; + } + hasLINKEDIT = true; + } + else if ( (info.protections & 0xFFFFFFF8) != 0 ) { + diag.error("in '%s' %s segment permissions has invalid bits set", path, info.segName); + badPermissions = true; + stop = true; + } + if ( greaterThanAddOrOverflow(info.fileOffset, info.fileSize, fileLen) ) { + diag.error("in '%s' %s segment content extends beyond end of file", path, info.segName); + badSize = true; + stop = true; + } + if ( is64() ) { + if ( info.vmAddr+info.vmSize < info.vmAddr ) { + diag.error("in '%s' %s segment vm range wraps", path, info.segName); + badSize = true; + stop = true; + } + } + else { + if ( (uint32_t)(info.vmAddr+info.vmSize) < (uint32_t)(info.vmAddr) ) { + diag.error("in '%s' %s segment vm range wraps", path, info.segName); + badSize = true; + stop = true; + } + } + }); + if ( badPermissions || badSize ) + return false; + if ( !hasTEXT ) { + diag.error("in '%s' missing __TEXT segment", path); + return false; + } + if ( !hasLINKEDIT ) { + diag.error("in '%s' missing __LINKEDIT segment", path); + return false; + } + + // check for overlapping segments + __block bool badSegments = false; + forEachSegment(^(const SegmentInfo& info1, bool& stop1) { + uint64_t seg1vmEnd = info1.vmAddr + info1.vmSize; + uint64_t seg1FileEnd = info1.fileOffset + info1.fileSize; + forEachSegment(^(const SegmentInfo& info2, bool& stop2) { + if ( info1.segIndex == info2.segIndex ) + return; + uint64_t seg2vmEnd = info2.vmAddr + info2.vmSize; + uint64_t seg2FileEnd = info2.fileOffset + info2.fileSize; + if ( ((info2.vmAddr <= info1.vmAddr) && (seg2vmEnd > info1.vmAddr) && (seg1vmEnd > info1.vmAddr )) || ((info2.vmAddr >= info1.vmAddr ) && (info2.vmAddr < seg1vmEnd) && (seg2vmEnd > info2.vmAddr)) ) { + diag.error("in '%s' segment %s vm range overlaps segment %s", path, info1.segName, info2.segName); + badSegments = true; + stop1 = true; + stop2 = true; + } + if ( ((info2.fileOffset <= info1.fileOffset) && (seg2FileEnd > info1.fileOffset) && (seg1FileEnd > info1.fileOffset)) || ((info2.fileOffset >= info1.fileOffset) && (info2.fileOffset < seg1FileEnd) && (seg2FileEnd > info2.fileOffset )) ) { + diag.error("in '%s' segment %s file content overlaps segment %s", path, info1.segName, info2.segName); + badSegments = true; + stop1 = true; + stop2 = true; + } + if ( (info1.segIndex < info2.segIndex) && !stop1 ) { + if ( (info1.vmAddr > info2.vmAddr) || ((info1.fileOffset > info2.fileOffset ) && (info1.fileOffset != 0) && (info2.fileOffset != 0)) ){ + if ( !inDyldCache() ) { + // dyld cache __DATA_* segments are moved around + diag.error("in '%s' segment load commands out of order with respect to layout for %s and %s", path, info1.segName, info2.segName); + 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.error("in '%s' section %s size too large 0x%llX", path, sect->sectname, sect->size); + badSections = true; + } + else if ( sect->addr < seg->vmaddr ) { + diag.error("in '%s' section %s start address 0x%llX is before containing segment's address 0x%0llX", path, sect->sectname, sect->addr, seg->vmaddr); + badSections = true; + } + else if ( sect->addr+sect->size > seg->vmaddr+seg->vmsize ) { + diag.error("in '%s' section %s end address 0x%llX is beyond containing segment's end address 0x%0llX", path, 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.error("in '%s' section %s size too large 0x%X", path, sect->sectname, sect->size); + badSections = true; + } + else if ( sect->addr < seg->vmaddr ) { + diag.error("in '%s' section %s start address 0x%X is before containing segment's address 0x%0X", path, sect->sectname, sect->addr, seg->vmaddr); + badSections = true; + } + else if ( sect->addr+sect->size > seg->vmaddr+seg->vmsize ) { + diag.error("in '%s' section %s end address 0x%X is beyond containing segment's end address 0x%0X", path, sect->sectname, sect->addr+sect->size, seg->vmaddr+seg->vmsize); + badSections = true; + } + } + } + }); + + return !badSections; +} + + +bool MachOAnalyzer::validMain(Diagnostics& diag, const char* path) const +{ + __block uint64_t textSegStartAddr = 0; + __block uint64_t textSegStartSize = 0; + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__TEXT") == 0 ) { + textSegStartAddr = info.vmAddr; + textSegStartSize = info.vmSize; + stop = true; + } + }); + + __block int mainCount = 0; + __block int threadCount = 0; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + entry_point_command* mainCmd; + uint64_t startAddress; + switch (cmd->cmd) { + case LC_MAIN: + ++mainCount; + mainCmd = (entry_point_command*)cmd; + if ( mainCmd->entryoff > textSegStartSize ) { + diag.error("LC_MAIN points outside of __TEXT segment"); + stop = true; + } + break; + case LC_UNIXTHREAD: + ++threadCount; + startAddress = entryAddrFromThreadCmd((thread_command*)cmd); + if ( startAddress == 0 ) { + diag.error("LC_UNIXTHREAD not valid for arch %s", archName()); + stop = true; + } + else if ( (startAddress < textSegStartAddr) || (startAddress > textSegStartAddr+textSegStartSize) ) { + diag.error("LC_UNIXTHREAD entry not in __TEXT segment"); + stop = true; + } + break; + } + }); + if ( diag.hasError() ) + return false; + if ( diag.noError() && (mainCount+threadCount == 1) ) + return true; + + if ( mainCount + threadCount == 0 ) + diag.error("missing LC_MAIN or LC_UNIXTHREAD"); + else + diag.error("only one LC_MAIN or LC_UNIXTHREAD is allowed"); + return false; +} + + +namespace { + struct LinkEditContentChunk + { + const char* name; + uint32_t stdOrder; + uint32_t fileOffsetStart; + uint32_t size; + + static int compareByFileOffset(const void* l, const void* r) { + if ( ((LinkEditContentChunk*)l)->fileOffsetStart < ((LinkEditContentChunk*)r)->fileOffsetStart ) + return -1; + else + return 1; + } + static int compareByStandardOrder(const void* l, const void* r) { + if ( ((LinkEditContentChunk*)l)->stdOrder < ((LinkEditContentChunk*)r)->stdOrder ) + return -1; + else + return 1; + } + }; +} // anonymous namespace + + + +bool MachOAnalyzer::validLinkeditLayout(Diagnostics& diag, const char* path) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return false; + const uint32_t ptrSize = pointerSize(); + + // build vector of all blobs in LINKEDIT + LinkEditContentChunk blobs[32]; + LinkEditContentChunk* bp = blobs; + if ( leInfo.dyldInfo != nullptr ) { + if ( leInfo.dyldInfo->rebase_size != 0 ) + *bp++ = {"rebase opcodes", 1, leInfo.dyldInfo->rebase_off, leInfo.dyldInfo->rebase_size}; + if ( leInfo.dyldInfo->bind_size != 0 ) + *bp++ = {"bind opcodes", 2, leInfo.dyldInfo->bind_off, leInfo.dyldInfo->bind_size}; + if ( leInfo.dyldInfo->weak_bind_size != 0 ) + *bp++ = {"weak bind opcodes", 3, leInfo.dyldInfo->weak_bind_off, leInfo.dyldInfo->weak_bind_size}; + if ( leInfo.dyldInfo->lazy_bind_size != 0 ) + *bp++ = {"lazy bind opcodes", 4, leInfo.dyldInfo->lazy_bind_off, leInfo.dyldInfo->lazy_bind_size}; + if ( leInfo.dyldInfo->export_size!= 0 ) + *bp++ = {"exports trie", 5, leInfo.dyldInfo->export_off, leInfo.dyldInfo->export_size}; + } + if ( leInfo.dynSymTab != nullptr ) { + if ( leInfo.dynSymTab->nlocrel != 0 ) + *bp++ = {"local relocations", 6, leInfo.dynSymTab->locreloff, static_cast(leInfo.dynSymTab->nlocrel*sizeof(relocation_info))}; + if ( leInfo.dynSymTab->nextrel != 0 ) + *bp++ = {"external relocations", 11, leInfo.dynSymTab->extreloff, static_cast(leInfo.dynSymTab->nextrel*sizeof(relocation_info))}; + if ( leInfo.dynSymTab->nindirectsyms != 0 ) + *bp++ = {"indirect symbol table", 12, leInfo.dynSymTab->indirectsymoff, leInfo.dynSymTab->nindirectsyms*4}; + } + if ( leInfo.splitSegInfo != nullptr ) { + if ( leInfo.splitSegInfo->datasize != 0 ) + *bp++ = {"shared cache info", 6, leInfo.splitSegInfo->dataoff, leInfo.splitSegInfo->datasize}; + } + if ( leInfo.functionStarts != nullptr ) { + if ( leInfo.functionStarts->datasize != 0 ) + *bp++ = {"function starts", 7, leInfo.functionStarts->dataoff, leInfo.functionStarts->datasize}; + } + if ( leInfo.dataInCode != nullptr ) { + if ( leInfo.dataInCode->datasize != 0 ) + *bp++ = {"data in code", 8, leInfo.dataInCode->dataoff, leInfo.dataInCode->datasize}; + } + if ( leInfo.symTab != nullptr ) { + if ( leInfo.symTab->nsyms != 0 ) + *bp++ = {"symbol table", 10, leInfo.symTab->symoff, static_cast(leInfo.symTab->nsyms*(ptrSize == 8 ? sizeof(nlist_64) : sizeof(struct nlist)))}; + if ( leInfo.symTab->strsize != 0 ) + *bp++ = {"symbol table strings", 20, leInfo.symTab->stroff, leInfo.symTab->strsize}; + } + if ( leInfo.codeSig != nullptr ) { + if ( leInfo.codeSig->datasize != 0 ) + *bp++ = {"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) && enforceFormat(Malformed::dyldInfoAndlocalRelocs) ) { + diag.error("in '%s' malformed mach-o contains LC_DYLD_INFO_ONLY and local relocations", path); + return false; + } + if ( leInfo.dynSymTab->nextrel != 0 ) { + diag.error("in '%s' malformed mach-o contains LC_DYLD_INFO_ONLY and external relocations", path); + return false; + } + } + if ( (leInfo.dyldInfo == nullptr) && (leInfo.dynSymTab == nullptr) ) { + diag.error("in '%s' malformed mach-o misssing LC_DYLD_INFO and LC_DYSYMTAB", path); + return false; + } + const unsigned long blobCount = bp - blobs; + if ( blobCount == 0 ) { + diag.error("in '%s' malformed mach-o misssing LINKEDIT", path); + return false; + } + + uint32_t linkeditFileEnd = leInfo.layout.linkeditFileOffset + leInfo.layout.linkeditFileSize; + + + // sort blobs by file-offset and error on overlaps + ::qsort(blobs, blobCount, sizeof(LinkEditContentChunk), &LinkEditContentChunk::compareByFileOffset); + uint32_t prevEnd = leInfo.layout.linkeditFileOffset; + const char* prevName = "start of LINKEDIT"; + for (unsigned long i=0; i < blobCount; ++i) { + const LinkEditContentChunk& blob = blobs[i]; + if ( blob.fileOffsetStart < prevEnd ) { + diag.error("in '%s' LINKEDIT overlap of %s and %s", path, prevName, blob.name); + return false; + } + if (greaterThanAddOrOverflow(blob.fileOffsetStart, blob.size, linkeditFileEnd)) { + diag.error("in '%s' LINKEDIT content '%s' extends beyond end of segment", path, blob.name); + return false; + } + prevEnd = blob.fileOffsetStart + blob.size; + prevName = blob.name; + } + + // sort vector by order and warn on non standard order or mis-alignment + ::qsort(blobs, blobCount, sizeof(LinkEditContentChunk), &LinkEditContentChunk::compareByStandardOrder); + prevEnd = leInfo.layout.linkeditFileOffset; + for (unsigned long i=0; i < blobCount; ++i) { + const LinkEditContentChunk& blob = blobs[i]; + if ( ((blob.fileOffsetStart & (ptrSize-1)) != 0) && (blob.stdOrder != 20) && enforceFormat(Malformed::linkeditAlignment) ) // ok for "symbol table strings" to be mis-aligned + diag.error("in '%s' mis-aligned LINKEDIT content '%s'", path, blob.name); + if ( (blob.fileOffsetStart < prevEnd) && enforceFormat(Malformed::linkeditOrder) ) { + diag.error("in '%s' LINKEDIT out of order %s", path, blob.name); + } + prevEnd = blob.fileOffsetStart; + } + + // Check for invalid symbol table sizes + if ( leInfo.symTab != nullptr ) { + if ( leInfo.symTab->nsyms > 0x10000000 ) { + diag.error("in '%s' malformed mach-o image: symbol table too large", path); + return false; + } + if ( leInfo.dynSymTab != nullptr ) { + // validate indirect symbol table + if ( leInfo.dynSymTab->nindirectsyms != 0 ) { + if ( leInfo.dynSymTab->nindirectsyms > 0x10000000 ) { + diag.error("in '%s' malformed mach-o image: indirect symbol table too large", path); + return false; + } + } + if ( (leInfo.dynSymTab->nlocalsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->ilocalsym > leInfo.symTab->nsyms) ) { + diag.error("in '%s' malformed mach-o image: indirect symbol table local symbol count exceeds total symbols", path); + return false; + } + if ( leInfo.dynSymTab->ilocalsym + leInfo.dynSymTab->nlocalsym < leInfo.dynSymTab->ilocalsym ) { + diag.error("in '%s' malformed mach-o image: indirect symbol table local symbol count wraps", path); + return false; + } + if ( (leInfo.dynSymTab->nextdefsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->iextdefsym > leInfo.symTab->nsyms) ) { + diag.error("in '%s' malformed mach-o image: indirect symbol table extern symbol count exceeds total symbols", path); + return false; + } + if ( leInfo.dynSymTab->iextdefsym + leInfo.dynSymTab->nextdefsym < leInfo.dynSymTab->iextdefsym ) { + diag.error("in '%s' malformed mach-o image: indirect symbol table extern symbol count wraps", path); + return false; + } + if ( (leInfo.dynSymTab->nundefsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->iundefsym > leInfo.symTab->nsyms) ) { + diag.error("in '%s' malformed mach-o image: indirect symbol table undefined symbol count exceeds total symbols", path); + return false; + } + if ( leInfo.dynSymTab->iundefsym + leInfo.dynSymTab->nundefsym < leInfo.dynSymTab->iundefsym ) { + diag.error("in '%s' malformed mach-o image: indirect symbol table undefined symbol count wraps", path); + return false; + } + } + } + + return true; +} + + + +bool MachOAnalyzer::invalidRebaseState(Diagnostics& diag, const char* opcodeName, const char* path, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, uint32_t ptrSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type) const +{ + if ( !segIndexSet ) { + diag.error("in '%s' %s missing preceding REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", path, opcodeName); + return true; + } + if ( segmentIndex >= leInfo.layout.linkeditSegIndex ) { + diag.error("in '%s' %s segment index %d too large", path, opcodeName, segmentIndex); + return true; + } + if ( segmentOffset > (segments[segmentIndex].vmSize-ptrSize) ) { + diag.error("in '%s' %s current segment offset 0x%08llX beyond segment size (0x%08llX)", path, opcodeName, segmentOffset, segments[segmentIndex].vmSize); + return true; + } + switch ( type ) { + case REBASE_TYPE_POINTER: + if ( !segments[segmentIndex].writable() ) { + diag.error("in '%s' %s pointer rebase is in non-writable segment", path, opcodeName); + return true; + } + if ( segments[segmentIndex].executable() ) { + diag.error("in '%s' %s pointer rebase is in executable segment", path, opcodeName); + return true; + } + break; + case REBASE_TYPE_TEXT_ABSOLUTE32: + case REBASE_TYPE_TEXT_PCREL32: + if ( !segments[segmentIndex].textRelocs ) { + diag.error("in '%s' %s text rebase is in segment that does not support text relocations", path, opcodeName); + return true; + } + if ( segments[segmentIndex].writable() ) { + diag.error("in '%s' %s text rebase is in writable segment", path, opcodeName); + return true; + } + if ( !segments[segmentIndex].executable() ) { + diag.error("in '%s' %s pointer rebase is in non-executable segment", path, opcodeName); + return true; + } + break; + default: + diag.error("in '%s' %s unknown rebase type %d", path, opcodeName, type); + return true; + } + return false; +} + + +void MachOAnalyzer::getAllSegmentsInfos(Diagnostics& diag, SegmentInfo segments[]) const +{ + forEachSegment(^(const SegmentInfo& info, bool& stop) { + segments[info.segIndex] = info; + }); +} + + +bool MachOAnalyzer::validRebaseInfo(Diagnostics& diag, const char* path) const +{ + forEachRebase(diag, ^(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, uint32_t ptrSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, bool& stop) { + if ( invalidRebaseState(diag, opcodeName, path, leInfo, segments, segIndexSet, ptrSize, segmentIndex, segmentOffset, type) ) + stop = true; + }); + return diag.noError(); +} + + +void MachOAnalyzer::forEachTextRebase(Diagnostics& diag, void (^handler)(uint64_t runtimeOffset, bool& stop)) const +{ + __block bool startVmAddrSet = false; + __block uint64_t startVmAddr = 0; + forEachRebase(diag, ^(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, uint32_t ptrSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, bool& stop) { + if ( type != REBASE_TYPE_TEXT_ABSOLUTE32 ) + return; + if ( !startVmAddrSet ) { + for (int i=0; i <= segmentIndex; ++i) { + if ( strcmp(segments[i].segName, "__TEXT") == 0 ) { + startVmAddr = segments[i].vmAddr; + startVmAddrSet = true; + break; + } + } + } + uint64_t rebaseVmAddr = segments[segmentIndex].vmAddr + segmentOffset; + uint64_t runtimeOffset = rebaseVmAddr - startVmAddr; + handler(runtimeOffset, stop); + }); +} + + +void MachOAnalyzer::forEachRebase(Diagnostics& diag, bool ignoreLazyPointers, void (^handler)(uint64_t runtimeOffset, bool& stop)) const +{ + __block bool startVmAddrSet = false; + __block uint64_t startVmAddr = 0; + __block uint64_t lpVmAddr = 0; + __block uint64_t lpEndVmAddr = 0; + __block uint64_t shVmAddr = 0; + __block uint64_t shEndVmAddr = 0; + if ( ignoreLazyPointers ) { + forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& info, bool malformedSectionRange, bool &stop) { + if ( (info.sectFlags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS ) { + lpVmAddr = info.sectAddr; + lpEndVmAddr = info.sectAddr + info.sectSize; + } + else if ( (info.sectFlags & S_ATTR_PURE_INSTRUCTIONS) && (strcmp(info.sectName, "__stub_helper") == 0) ) { + shVmAddr = info.sectAddr; + shEndVmAddr = info.sectAddr + info.sectSize; + } + }); + } + forEachRebase(diag, ^(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, uint32_t ptrSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, bool& stop) { + if ( type != REBASE_TYPE_POINTER ) + return; + if ( !startVmAddrSet ) { + for (int i=0; i < segmentIndex; ++i) { + if ( strcmp(segments[i].segName, "__TEXT") == 0 ) { + startVmAddr = segments[i].vmAddr; + startVmAddrSet = true; + break; + } + } + } + uint64_t rebaseVmAddr = segments[segmentIndex].vmAddr + segmentOffset; + bool skipRebase = false; + if ( (rebaseVmAddr >= lpVmAddr) && (rebaseVmAddr < lpEndVmAddr) ) { + // rebase is in lazy pointer section + uint64_t lpValue = 0; + if ( ptrSize == 8 ) + lpValue = *((uint64_t*)(rebaseVmAddr-startVmAddr+(uint8_t*)this)); + else + lpValue = *((uint32_t*)(rebaseVmAddr-startVmAddr+(uint8_t*)this)); + if ( (lpValue >= shVmAddr) && (lpValue < shEndVmAddr) ) { + // content is into stub_helper section + uint64_t lpTargetImageOffset = lpValue - startVmAddr; + const uint8_t* helperContent = (uint8_t*)this + lpTargetImageOffset; + bool isLazyStub = contentIsRegularStub(helperContent); + // ignore rebases for normal lazy pointers, but leave rebase for resolver helper stub + if ( isLazyStub ) + skipRebase = true; + } + else { + // if lazy pointer does not point into stub_helper, then it points to weak-def symbol and we need rebase + } + } + if ( !skipRebase ) { + uint64_t runtimeOffset = rebaseVmAddr - startVmAddr; + handler(runtimeOffset, stop); + } + }); +} + + +bool MachOAnalyzer::contentIsRegularStub(const uint8_t* helperContent) const +{ + switch (this->cputype) { + case CPU_TYPE_X86_64: + return ( (helperContent[0] == 0x68) && (helperContent[5] == 0xE9) ); // push $xxx / JMP pcRel + case CPU_TYPE_I386: + return ( (helperContent[0] == 0x68) && (helperContent[5] == 0xFF) && (helperContent[2] == 0x26) ); // push $xxx / JMP *pcRel + case CPU_TYPE_ARM: + return ( (helperContent[0] == 0x00) && (helperContent[1] == 0xC0) && (helperContent[2] == 0x9F) && (helperContent[3] == 0xE5) ); // ldr ip, [pc, #0] + case CPU_TYPE_ARM64: + return ( (helperContent[0] == 0x50) && (helperContent[1] == 0x00) && (helperContent[2] == 0x00) && (helperContent[3] == 0x18) ); // ldr w16, L0 + + } + return false; +} + +static int uint32Sorter(const void* l, const void* r) { + if ( *((uint32_t*)l) < *((uint32_t*)r) ) + return -1; + else + return 1; +} + + +void MachOAnalyzer::forEachRebase(Diagnostics& diag, + void (^handler)(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, uint32_t ptrSize, uint8_t segmentIndex, uint64_t segmentOffset, + uint8_t type, bool& stop)) const +{ + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + BLOCK_ACCCESSIBLE_ARRAY(SegmentInfo, segmentsInfo, leInfo.layout.linkeditSegIndex+1); + getAllSegmentsInfos(diag, segmentsInfo); + if ( diag.hasError() ) + return; + + if ( leInfo.dyldInfo != nullptr ) { + const uint8_t* p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->rebase_off); + const uint8_t* end = p + leInfo.dyldInfo->rebase_size; + const uint32_t ptrSize = pointerSize(); + 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*ptrSize; + break; + case REBASE_OPCODE_DO_REBASE_IMM_TIMES: + for (int i=0; i < immediate; ++i) { + handler("REBASE_OPCODE_DO_REBASE_IMM_TIMES", leInfo, segmentsInfo, segIndexSet, ptrSize, segIndex, segOffset, type, stop); + segOffset += ptrSize; + if ( stop ) + break; + } + break; + case REBASE_OPCODE_DO_REBASE_ULEB_TIMES: + count = read_uleb128(diag, p, end); + for (uint32_t i=0; i < count; ++i) { + handler("REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", leInfo, segmentsInfo, segIndexSet, ptrSize, segIndex, segOffset, type, stop); + segOffset += ptrSize; + if ( stop ) + break; + } + break; + case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: + handler("REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", leInfo, segmentsInfo, segIndexSet, ptrSize, segIndex, segOffset, type, stop); + segOffset += read_uleb128(diag, p, end) + ptrSize; + 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) { + handler("REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB", leInfo, segmentsInfo, segIndexSet, ptrSize, segIndex, segOffset, type, stop); + segOffset += skip + ptrSize; + if ( stop ) + break; + } + break; + default: + diag.error("unknown rebase opcode 0x%02X", opcode); + } + } + } + else { + // old binary, walk relocations + const uint64_t relocsStartAddress = relocBaseAddress(segmentsInfo, leInfo.layout.linkeditSegIndex); + 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); + const uint8_t ptrSize = pointerSize(); + STACK_ALLOC_OVERFLOW_SAFE_ARRAY(uint32_t, relocAddrs, 2048); + 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; + } + relocAddrs.push_back(reloc->r_address); + } + if ( !relocAddrs.empty() ) { + ::qsort(&relocAddrs[0], relocAddrs.count(), sizeof(uint32_t), &uint32Sorter); + for (uint32_t addrOff : relocAddrs) { + uint32_t segIndex = 0; + uint64_t segOffset = 0; + if ( segIndexAndOffsetForAddress(relocsStartAddress+addrOff, segmentsInfo, leInfo.layout.linkeditSegIndex, segIndex, segOffset) ) { + uint8_t type = REBASE_TYPE_POINTER; + if ( this->cputype == CPU_TYPE_I386 ) { + if ( segmentsInfo[segIndex].executable() ) + type = REBASE_TYPE_TEXT_ABSOLUTE32; + } + handler("local relocation", leInfo, segmentsInfo, true, ptrSize, segIndex, segOffset, type , stop); + } + else { + diag.error("local relocation has out of range r_address"); + break; + } + } + } + // then process indirect symbols + forEachIndirectPointer(diag, ^(uint64_t address, bool bind, int bindLibOrdinal, + const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& indStop) { + if ( bind ) + return; + uint32_t segIndex = 0; + uint64_t segOffset = 0; + if ( segIndexAndOffsetForAddress(address, segmentsInfo, leInfo.layout.linkeditSegIndex, segIndex, segOffset) ) { + handler("local relocation", leInfo, segmentsInfo, true, ptrSize, segIndex, segOffset, REBASE_TYPE_POINTER, indStop); + } + else { + diag.error("local relocation has out of range r_address"); + indStop = true; + } + }); + } +} + +bool MachOAnalyzer::segIndexAndOffsetForAddress(uint64_t addr, const SegmentInfo segmentsInfos[], uint32_t segCount, uint32_t& segIndex, uint64_t& segOffset) const +{ + for (uint32_t i=0; i < segCount; ++i) { + if ( (segmentsInfos[i].vmAddr <= addr) && (addr < segmentsInfos[i].vmAddr+segmentsInfos[i].vmSize) ) { + segIndex = i; + segOffset = addr - segmentsInfos[i].vmAddr; + return true; + } + } + return false; +} + +uint64_t MachOAnalyzer::relocBaseAddress(const SegmentInfo segmentsInfos[], uint32_t segCount) const +{ + if ( is64() ) { + // x86_64 reloc base address is first writable segment + for (uint32_t i=0; i < segCount; ++i) { + if ( segmentsInfos[i].writable() ) + return segmentsInfos[i].vmAddr; + } + } + return segmentsInfos[0].vmAddr; +} + + + +void MachOAnalyzer::forEachIndirectPointer(Diagnostics& diag, void (^handler)(uint64_t pointerAddress, 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 ptrSize = pointerSize(); + 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 dyld3::MachOAnalyzer::SectionInfo& sectInfo, bool malformedSectionRange, bool& sectionStop) { + uint8_t sectionType = (sectInfo.sectFlags & SECTION_TYPE); + bool selfModifyingStub = (sectionType == S_SYMBOL_STUBS) && (sectInfo.sectFlags & S_ATTR_SELF_MODIFYING_CODE) && (sectInfo.reserved2 == 5) && (this->cputype == CPU_TYPE_I386); + if ( (sectionType != S_LAZY_SYMBOL_POINTERS) && (sectionType != S_NON_LAZY_SYMBOL_POINTERS) && !selfModifyingStub ) + return; + 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 ? sectInfo.reserved2 : ptrSize; + uint32_t elementCount = (uint32_t)(sectInfo.sectSize/elementSize); + if ( greaterThanAddOrOverflow(sectInfo.reserved1, elementCount, indirectSymbolTableCount) ) { + diag.error("section %s overflows indirect symbol table", sectInfo.sectName); + sectionStop = true; + return; + } + + for (uint32_t i=0; (i < elementCount) && !stop; ++i) { + uint32_t symNum = indirectSymbolTable[sectInfo.reserved1 + i]; + if ( symNum == INDIRECT_SYMBOL_ABS ) + continue; + if ( symNum == INDIRECT_SYMBOL_LOCAL ) { + handler(sectInfo.sectAddr+i*elementSize, false, 0, "", false, false, false, stop); + continue; + } + if ( symNum > symCount ) { + diag.error("indirect symbol[%d] = %d which is invalid symbol index", sectInfo.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", sectInfo.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(sectInfo.sectAddr+i*elementSize, true, libOrdinal, symbolName, weakImport, lazy, selfModifyingStub, stop); + } + sectionStop = stop; + }); +} + +int MachOAnalyzer::libOrdinalFromDesc(uint16_t n_desc) const +{ + // -flat_namespace is always flat lookup + if ( (this->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 MachOAnalyzer::validBindInfo(Diagnostics& diag, const char* path) const +{ + forEachBind(diag, ^(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, + uint32_t ptrSize, uint8_t segmentIndex, uint64_t segmentOffset, + uint8_t type, const char* symbolName, bool weakImport, uint64_t addend, bool& stop) { + if ( invalidBindState(diag, opcodeName, path, leInfo, segments, segIndexSet, libraryOrdinalSet, dylibCount, + libOrdinal, ptrSize, segmentIndex, segmentOffset, type, symbolName) ) { + stop = true; + } + }, ^(const char* symbolName) { + }); + return diag.noError(); +} + +bool MachOAnalyzer::invalidBindState(Diagnostics& diag, const char* opcodeName, const char* path, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, uint32_t ptrSize, + uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, const char* symbolName) const +{ + if ( !segIndexSet ) { + diag.error("in '%s' %s missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", path, opcodeName); + return true; + } + if ( segmentIndex >= leInfo.layout.linkeditSegIndex ) { + diag.error("in '%s' %s segment index %d too large", path, opcodeName, segmentIndex); + return true; + } + if ( segmentOffset > (segments[segmentIndex].vmSize-ptrSize) ) { + diag.error("in '%s' %s current segment offset 0x%08llX beyond segment size (0x%08llX)", path, opcodeName, segmentOffset, segments[segmentIndex].vmSize); + return true; + } + if ( symbolName == NULL ) { + diag.error("in '%s' %s missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM", path, opcodeName); + return true; + } + if ( !libraryOrdinalSet ) { + diag.error("in '%s' %s missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL", path, opcodeName); + return true; + } + if ( libOrdinal > (int)dylibCount ) { + diag.error("in '%s' %s has library ordinal too large (%d) max (%d)", path, opcodeName, libOrdinal, dylibCount); + return true; + } + if ( libOrdinal < BIND_SPECIAL_DYLIB_WEAK_DEF_COALESCE ) { + diag.error("in '%s' %s has unknown library special ordinal (%d)", path, opcodeName, libOrdinal); + return true; + } + switch ( type ) { + case BIND_TYPE_POINTER: + if ( !segments[segmentIndex].writable() ) { + diag.error("in '%s' %s pointer bind is in non-writable segment", path, opcodeName); + return true; + } + if ( segments[segmentIndex].executable() ) { + diag.error("in '%s' %s pointer bind is in executable segment", path, opcodeName); + return true; + } + break; + case BIND_TYPE_TEXT_ABSOLUTE32: + case BIND_TYPE_TEXT_PCREL32: + if ( !segments[segmentIndex].textRelocs ) { + diag.error("in '%s' %s text bind is in segment that does not support text relocations", path, opcodeName); + return true; + } + if ( segments[segmentIndex].writable() ) { + diag.error("in '%s' %s text bind is in writable segment", path, opcodeName); + return true; + } + if ( !segments[segmentIndex].executable() ) { + diag.error("in '%s' %s pointer bind is in non-executable segment", path, opcodeName); + return true; + } + break; + default: + diag.error("in '%s' %s unknown bind type %d", path, opcodeName, type); + return true; + } + return false; +} + +void MachOAnalyzer::forEachBind(Diagnostics& diag, void (^handler)(uint64_t runtimeOffset, int libOrdinal, const char* symbolName, + bool weakImport, uint64_t addend, bool& stop), + void (^strongHandler)(const char* symbolName)) const +{ + __block bool startVmAddrSet = false; + __block uint64_t startVmAddr = 0; + forEachBind(diag, ^(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, + uint32_t ptrSize, uint8_t segmentIndex, uint64_t segmentOffset, + uint8_t type, const char* symbolName, bool weakImport, uint64_t addend, bool& stop) { + if ( !startVmAddrSet ) { + for (int i=0; i <= segmentIndex; ++i) { + if ( strcmp(segments[i].segName, "__TEXT") == 0 ) { + startVmAddr = segments[i].vmAddr; + startVmAddrSet = true; + break; + } + } + } + uint64_t bindVmOffset = segments[segmentIndex].vmAddr + segmentOffset; + uint64_t runtimeOffset = bindVmOffset - startVmAddr; + handler(runtimeOffset, libOrdinal, symbolName, weakImport, addend, stop); + }, ^(const char* symbolName) { + strongHandler(symbolName); + }); +} + +void MachOAnalyzer::forEachBind(Diagnostics& diag, + void (^handler)(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, + uint32_t ptrSize, uint8_t segmentIndex, uint64_t segmentOffset, + uint8_t type, const char* symbolName, bool weakImport, uint64_t addend, bool& stop), + void (^strongHandler)(const char* symbolName)) const +{ + const uint32_t ptrSize = this->pointerSize(); + bool stop = false; + + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + BLOCK_ACCCESSIBLE_ARRAY(SegmentInfo, segmentsInfo, leInfo.layout.linkeditSegIndex+1); + getAllSegmentsInfos(diag, segmentsInfo); + 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; + 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; + 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: + stop = 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: + handler("BIND_OPCODE_DO_BIND", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += ptrSize; + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + handler("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += read_uleb128(diag, p, end) + ptrSize; + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + handler("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += immediate*ptrSize + ptrSize; + 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) { + handler("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += skip + ptrSize; + if ( stop ) + break; + } + break; + default: + diag.error("bad bind opcode 0x%02X", *p); + } + } + if ( diag.hasError() ) + 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: + handler("BIND_OPCODE_DO_BIND", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += ptrSize; + 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; + } + } + } + if ( diag.hasError() ) + return; + + // process weak bind info + if ( leInfo.dyldInfo->weak_bind_size != 0 ) { + p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->weak_bind_off); + end = p + leInfo.dyldInfo->weak_bind_size; + type = BIND_TYPE_POINTER; + segmentOffset = 0; + segmentIndex = 0; + symbolName = NULL; + libraryOrdinal = BIND_SPECIAL_DYLIB_WEAK_DEF_COALESCE; + segIndexSet = false; + libraryOrdinalSet= true; + 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: + stop = 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_bind"); + 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; + if ( immediate & BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION ) { + strongHandler(symbolName); + } + 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: + handler("BIND_OPCODE_DO_BIND", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += ptrSize; + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + handler("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += read_uleb128(diag, p, end) + ptrSize; + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + handler("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += immediate*ptrSize + ptrSize; + 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) { + handler("BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", leInfo, segmentsInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, + ptrSize, segmentIndex, segmentOffset, type, symbolName, weakImport, addend, stop); + segmentOffset += skip + ptrSize; + if ( stop ) + break; + } + break; + default: + diag.error("bad bind opcode 0x%02X", *p); + } + } + } + } + else { + // old binary, process external relocations + const uint64_t relocsStartAddress = relocBaseAddress(segmentsInfo, leInfo.layout.linkeditSegIndex); + const relocation_info* const relocsStart = (relocation_info*)getLinkEditContent(leInfo.layout, leInfo.dynSymTab->extreloff); + const relocation_info* const relocsEnd = &relocsStart[leInfo.dynSymTab->nextrel]; + bool is64Bit = is64() ; + const uint8_t relocSize = (is64Bit ? 3 : 2); + 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; + 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; + } + uint32_t segIndex = 0; + uint64_t segOffset = 0; + if ( segIndexAndOffsetForAddress(relocsStartAddress+reloc->r_address, segmentsInfo, leInfo.layout.linkeditSegIndex, segIndex, segOffset) ) { + uint32_t symbolIndex = reloc->r_symbolnum; + if ( symbolIndex > symCount ) { + diag.error("external relocation has out of range r_symbolnum"); + break; + } + else { + uint32_t strOffset = is64Bit ? symbols64[symbolIndex].n_un.n_strx : symbols32[symbolIndex].n_un.n_strx; + uint16_t n_desc = is64Bit ? symbols64[symbolIndex].n_desc : symbols32[symbolIndex].n_desc; + uint32_t libOrdinal = libOrdinalFromDesc(n_desc); + if ( strOffset >= poolSize ) { + diag.error("external relocation has r_symbolnum=%d which has out of range n_strx", symbolIndex); + break; + } + else { + const char* symbolName = stringPool + strOffset; + bool weakImport = (n_desc & N_WEAK_REF); + const uint8_t* content = (uint8_t*)this + segmentsInfo[segIndex].vmAddr - leInfo.layout.textUnslidVMAddr + segOffset; + uint64_t addend = is64Bit ? *((uint64_t*)content) : *((uint32_t*)content); + handler("external relocation", leInfo, segmentsInfo, true, true, dylibCount, libOrdinal, + ptrSize, segIndex, segOffset, BIND_TYPE_POINTER, symbolName, weakImport, addend, stop); + } + } + } + else { + diag.error("local relocation has out of range r_address"); + break; + } + } + // then process indirect symbols + forEachIndirectPointer(diag, ^(uint64_t address, bool bind, int bindLibOrdinal, + const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& indStop) { + if ( !bind ) + return; + uint32_t segIndex = 0; + uint64_t segOffset = 0; + if ( segIndexAndOffsetForAddress(address, segmentsInfo, leInfo.layout.linkeditSegIndex, segIndex, segOffset) ) { + handler("indirect symbol", leInfo, segmentsInfo, true, true, dylibCount, bindLibOrdinal, + ptrSize, segIndex, segOffset, BIND_TYPE_POINTER, bindSymbolName, bindWeakImport, 0, indStop); + } + else { + diag.error("indirect symbol has out of range address"); + indStop = true; + } + }); + } + +} + + +bool MachOAnalyzer::validChainedFixupsInfo(Diagnostics& diag, const char* path) const +{ + __block uint32_t maxTargetCount = 0; + __block uint32_t currentTargetCount = 0; + forEachChainedFixup(diag, + ^(uint32_t totalTargets, bool& stop) { + maxTargetCount = totalTargets; + }, + ^(const LinkEditInfo& leInfo, const SegmentInfo segments[], bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, uint8_t type, const char* symbolName, uint64_t addend, bool weakImport, bool& stop) { + if ( symbolName == NULL ) { + diag.error("in '%s' missing BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM", path); + } + else if ( !libraryOrdinalSet ) { + diag.error("in '%s' missing BIND_OPCODE_SET_DYLIB_ORDINAL", path); + } + else if ( libOrdinal > (int)dylibCount ) { + diag.error("in '%s' has library ordinal too large (%d) max (%d)", path, libOrdinal, dylibCount); + } + else if ( libOrdinal < BIND_SPECIAL_DYLIB_WEAK_DEF_COALESCE ) { + diag.error("in '%s' has unknown library special ordinal (%d)", path, libOrdinal); + } + else if ( type != BIND_TYPE_POINTER ) { + diag.error("in '%s' unknown bind type %d", path, type); + } + else if ( currentTargetCount > maxTargetCount ) { + diag.error("in '%s' chained target counts exceeds BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB", path); + } + ++currentTargetCount; + if ( diag.hasError() ) + stop = true; + }, + ^(const LinkEditInfo& leInfo, const SegmentInfo segments[], uint8_t segmentIndex, bool segIndexSet, uint64_t segmentOffset, bool& stop) { + if ( !segIndexSet ) { + diag.error("in '%s' missing BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", path); + } + else if ( segmentIndex >= leInfo.layout.linkeditSegIndex ) { + diag.error("in '%s' segment index %d too large", path, segmentIndex); + } + else if ( segmentOffset > (segments[segmentIndex].vmSize-8) ) { + diag.error("in '%s' current segment offset 0x%08llX beyond segment size (0x%08llX)", path, segmentOffset, segments[segmentIndex].vmSize); + } + else if ( !segments[segmentIndex].writable() ) { + diag.error("in '%s' pointer bind is in non-writable segment", path); + } + else if ( segments[segmentIndex].executable() ) { + diag.error("in '%s' pointer bind is in executable segment", path); + } + if ( diag.hasError() ) + stop = true; + } + ); + + return diag.noError(); +} + + +void MachOAnalyzer::forEachChainedFixup(Diagnostics& diag, void (^targetCount)(uint32_t totalTargets, bool& stop), + void (^addTarget)(const LinkEditInfo& leInfo, const SegmentInfo segments[], bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, uint8_t type, const char* symbolName, uint64_t addend, bool weakImport, bool& stop), + void (^addChainStart)(const LinkEditInfo& leInfo, const SegmentInfo segments[], uint8_t segmentIndex, bool segIndexSet, uint64_t segmentOffset, bool& stop)) const +{ + bool stop = false; + + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return; + + BLOCK_ACCCESSIBLE_ARRAY(SegmentInfo, segmentsInfo, leInfo.layout.linkeditSegIndex+1); + getAllSegmentsInfos(diag, segmentsInfo); + 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; + 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; + uint64_t targetTableCount; + uint64_t addend = 0; + bool weakImport = 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: + stop = 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_SEGMENT_AND_OFFSET_ULEB: + segmentIndex = immediate; + segmentOffset = read_uleb128(diag, p, end); + segIndexSet = true; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + addend = read_sleb128(diag, p, end); + break; + case BIND_OPCODE_DO_BIND: + if ( addTarget ) + addTarget(leInfo, segmentsInfo, libraryOrdinalSet, dylibCount, libraryOrdinal, type, symbolName, addend, weakImport, stop); + break; + case BIND_OPCODE_THREADED: + switch (immediate) { + case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: + targetTableCount = read_uleb128(diag, p, end); + if ( targetTableCount > 65535 ) { + diag.error("BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB size too large"); + stop = true; + } + else { + if ( targetCount ) + targetCount((uint32_t)targetTableCount, stop); + } + break; + case BIND_SUBOPCODE_THREADED_APPLY: + if ( addChainStart ) + addChainStart(leInfo, segmentsInfo, segmentIndex, segIndexSet, segmentOffset, stop); + break; + default: + diag.error("bad BIND_OPCODE_THREADED sub-opcode 0x%02X", immediate); + } + break; + default: + diag.error("bad bind opcode 0x%02X", immediate); + } + } + if ( diag.hasError() ) + return; + } +} + +void MachOAnalyzer::forEachChainedFixupStart(Diagnostics& diag, void (^callback)(uint64_t runtimeOffset, bool& stop)) const +{ + __block bool startVmAddrSet = false; + __block uint64_t startVmAddr = 0; + forEachChainedFixup(diag, nullptr, nullptr, ^(const LinkEditInfo& leInfo, const SegmentInfo segments[], uint8_t segmentIndex, bool segIndexSet, uint64_t segmentOffset, bool& stop) { + if ( !startVmAddrSet ) { + for (int i=0; i <= segmentIndex; ++i) { + if ( strcmp(segments[i].segName, "__TEXT") == 0 ) { + startVmAddr = segments[i].vmAddr; + startVmAddrSet = true; + break; + } + } + } + uint64_t startVmOffset = segments[segmentIndex].vmAddr + segmentOffset; + uint64_t runtimeOffset = startVmOffset - startVmAddr; + callback((uint32_t)runtimeOffset, stop); + }); +} + +void MachOAnalyzer::forEachChainedFixupTarget(Diagnostics& diag, void (^callback)(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop)) const +{ + forEachChainedFixup(diag, nullptr, ^(const LinkEditInfo& leInfo, const SegmentInfo segments[], bool libraryOrdinalSet, uint32_t dylibCount, + int libOrdinal, uint8_t type, const char* symbolName, uint64_t addend, bool weakImport, bool& stop){ + callback(libOrdinal, symbolName, addend, weakImport, stop); + }, nullptr); +} + +uint32_t MachOAnalyzer::segmentCount() const +{ + __block uint32_t count = 0; + forEachSegment(^(const SegmentInfo& info, bool& stop) { + ++count; + }); + return count; +} + +bool MachOAnalyzer::hasCodeSignature(uint32_t& fileOffset, uint32_t& size) const +{ + fileOffset = 0; + size = 0; + + 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 + + // early exist if no LC_CODE_SIGNATURE + if ( fileOffset == 0 ) + return false; + + // ignore code signatures in macOS binaries built with pre-10.9 tools + __block bool goodSignature = true; + if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) ) { + forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) { + if ( (platform == Platform::macOS) && (sdk < 0x000A0900) ) + goodSignature = false; + }); + } + + return goodSignature; +} + +bool MachOAnalyzer::hasInitializer(Diagnostics& diag, bool contentRebased, const void* dyldCache) const +{ + __block bool result = false; + forEachInitializer(diag, contentRebased, ^(uint32_t offset) { + result = true; + }, dyldCache); + return result; +} + +void MachOAnalyzer::forEachInitializer(Diagnostics& diag, bool contentRebased, void (^callback)(uint32_t offset), const void* dyldCache) const +{ + __block uint64_t prefTextSegAddrStart = 0; + __block uint64_t prefTextSegAddrEnd = 0; + + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__TEXT") == 0 ) { + prefTextSegAddrStart = info.vmAddr; + prefTextSegAddrEnd = info.vmAddr + info.vmSize; + stop = true; + } + }); + if ( prefTextSegAddrStart == prefTextSegAddrEnd ) { + diag.error("no __TEXT segment"); + return; + } + uint64_t slide = (long)this - prefTextSegAddrStart; + + // 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 ( (prefTextSegAddrStart < dashInit) && (dashInit < prefTextSegAddrEnd) ) + callback((uint32_t)(dashInit - prefTextSegAddrStart)); + 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 ( (prefTextSegAddrStart < dashInit) && (dashInit < prefTextSegAddrEnd) ) + callback((uint32_t)(dashInit - prefTextSegAddrStart)); + else + diag.error("-init does not point within __TEXT segment"); + } + }); + + // next any function pointers in mod-init section + unsigned ptrSize = pointerSize(); + forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) { + if ( (info.sectFlags & SECTION_TYPE) == S_MOD_INIT_FUNC_POINTERS ) { + const uint8_t* content; + content = (uint8_t*)(info.sectAddr + slide); + if ( (info.sectSize % ptrSize) != 0 ) { + diag.error("initializer section %s/%s has bad size", info.segInfo.segName, info.sectName); + stop = true; + return; + } + if ( malformedSectionRange ) { + diag.error("initializer section %s/%s extends beyond its segment", info.segInfo.segName, info.sectName); + stop = true; + return; + } + if ( ((long)content % ptrSize) != 0 ) { + diag.error("initializer section %s/%s is not pointer aligned", info.segInfo.segName, info.sectName); + stop = true; + return; + } + if ( ptrSize == 8 ) { + const uint64_t* initsStart = (uint64_t*)content; + const uint64_t* initsEnd = (uint64_t*)((uint8_t*)content + info.sectSize); + for (const uint64_t* p=initsStart; p < initsEnd; ++p) { + uint64_t anInit = *p; + if ( contentRebased ) + anInit -= slide; + if ( hasChainedFixups() ) { + ChainedFixupPointerOnDisk* aChainedInit = (ChainedFixupPointerOnDisk*)p; + if ( aChainedInit->authBind.bind ) + diag.error("initializer uses bind"); + if ( aChainedInit->authRebase.auth ) { + anInit = aChainedInit->authRebase.target; + } + else { + anInit = aChainedInit->plainRebase.signExtendedTarget(); + } + } + if ( (anInit <= prefTextSegAddrStart) || (anInit > prefTextSegAddrEnd) ) { + diag.error("initializer 0x%0llX does not point within __TEXT segment", anInit); + stop = true; + break; + } + callback((uint32_t)(anInit - prefTextSegAddrStart)); + } + } + else { + const uint32_t* initsStart = (uint32_t*)content; + const uint32_t* initsEnd = (uint32_t*)((uint8_t*)content + info.sectSize); + for (const uint32_t* p=initsStart; p < initsEnd; ++p) { + uint32_t anInit = *p; + if ( contentRebased ) + anInit -= slide; + if ( (anInit <= prefTextSegAddrStart) || (anInit > prefTextSegAddrEnd) ) { + diag.error("initializer 0x%0X does not point within __TEXT segment", anInit); + stop = true; + break; + } + callback(anInit - (uint32_t)prefTextSegAddrStart); + } + } + } + }); +} + + +void MachOAnalyzer::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 +} + + +bool MachOAnalyzer::hasObjC() const +{ + __block bool result = false; + forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) { + if ( (strcmp(info.sectName, "__objc_imageinfo") == 0) && (strncmp(info.segInfo.segName, "__DATA", 6) == 0) ) { + result = true; + stop = true; + } + if ( (this->cputype == CPU_TYPE_I386) && (strcmp(info.sectName, "__image_info") == 0) && (strcmp(info.segInfo.segName, "__OBJC") == 0) ) { + result = true; + stop = true; + } + }); + return result; +} + +bool MachOAnalyzer::hasPlusLoadMethod(Diagnostics& diag) const +{ + __block bool result = false; + if ( (this->cputype == CPU_TYPE_I386) && supportsPlatform(Platform::macOS) ) { + // old objc runtime has no special section for +load methods, scan for string + int64_t slide = getSlide(); + forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) { + if ( ( (info.sectFlags & SECTION_TYPE) == S_CSTRING_LITERALS ) ) { + if ( malformedSectionRange ) { + diag.error("cstring section %s/%s extends beyond the end of the segment", info.segInfo.segName, info.sectName); + stop = true; + return; + } + const uint8_t* content = (uint8_t*)(info.sectAddr + slide); + const char* s = (char*)content; + const char* end = s + info.sectSize; + while ( s < end ) { + if ( strcmp(s, "load") == 0 ) { + result = true; + stop = true; + return; + } + while (*s != '\0' ) + ++s; + ++s; + } + } + }); + } + else { + // in new objc runtime compiler puts classes/categories with +load method in specical section + forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool& stop) { + if ( strncmp(info.segInfo.segName, "__DATA", 6) != 0 ) + return; + if ( (strcmp(info.sectName, "__objc_nlclslist") == 0) || (strcmp(info.sectName, "__objc_nlcatlist") == 0)) { + result = true; + stop = true; + } + }); + } + return result; +} + +const void* MachOAnalyzer::getRebaseOpcodes(uint32_t& size) const +{ + Diagnostics diag; + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() || (leInfo.dyldInfo == nullptr) ) + return nullptr; + + size = leInfo.dyldInfo->rebase_size; + return getLinkEditContent(leInfo.layout, leInfo.dyldInfo->rebase_off); +} + +const void* MachOAnalyzer::getBindOpcodes(uint32_t& size) const +{ + Diagnostics diag; + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() || (leInfo.dyldInfo == nullptr) ) + return nullptr; + + size = leInfo.dyldInfo->bind_size; + return getLinkEditContent(leInfo.layout, leInfo.dyldInfo->bind_off); +} + +const void* MachOAnalyzer::getLazyBindOpcodes(uint32_t& size) const +{ + Diagnostics diag; + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() || (leInfo.dyldInfo == nullptr) ) + return nullptr; + + size = leInfo.dyldInfo->lazy_bind_size; + return getLinkEditContent(leInfo.layout, leInfo.dyldInfo->lazy_bind_off); +} + + +uint64_t MachOAnalyzer::segAndOffsetToRuntimeOffset(uint8_t targetSegIndex, uint64_t targetSegOffset) const +{ + __block uint64_t textVmAddr = 0; + __block uint64_t result = 0; + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__TEXT") == 0 ) + textVmAddr = info.vmAddr; + if ( info.segIndex == targetSegIndex ) { + result = (info.vmAddr - textVmAddr) + targetSegOffset; + } + }); + return result; +} + +bool MachOAnalyzer::hasLazyPointers(uint32_t& runtimeOffset, uint32_t& size) const +{ + size = 0; + forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& info, bool malformedSectionRange, bool &stop) { + if ( (info.sectFlags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS ) { + runtimeOffset = (uint32_t)(info.sectAddr - preferredLoadAddress()); + size = (uint32_t)info.sectSize; + stop = true; + } + }); + return (size != 0); +} + +uint64_t MachOAnalyzer::preferredLoadAddress() const +{ + __block uint64_t textVmAddr = 0; + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__TEXT") == 0 ) { + textVmAddr = info.vmAddr; + stop = true; + } + }); + return textVmAddr; +} + + +bool MachOAnalyzer::getEntry(uint32_t& offset, bool& usesCRT) const +{ + 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; + uint64_t startAddress = entryAddrFromThreadCmd((thread_command*)cmd); + offset = (uint32_t)(startAddress - preferredLoadAddress()); + } + }); + return (offset != 0); +} + +uint64_t MachOAnalyzer::entryAddrFromThreadCmd(const thread_command* cmd) const +{ + assert(cmd->cmd == LC_UNIXTHREAD); + const uint32_t* regs32 = (uint32_t*)(((char*)cmd) + 16); + const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16); + uint64_t startAddress = 0; + switch ( this->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; + } + return startAddress; +} + + +void MachOAnalyzer::forEachInterposingSection(Diagnostics& diag, void (^handler)(uint64_t vmOffset, uint64_t vmSize, bool& stop)) const +{ + const unsigned ptrSize = pointerSize(); + const unsigned entrySize = 2 * ptrSize; + forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& info, bool malformedSectionRange, bool &stop) { + if ( ((info.sectFlags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(info.sectName, "__interpose") == 0) && (strcmp(info.segInfo.segName, "__DATA") == 0)) ) { + if ( info.sectSize % entrySize != 0 ) { + diag.error("interposing section %s/%s has bad size", info.segInfo.segName, info.sectName); + stop = true; + return; + } + if ( malformedSectionRange ) { + diag.error("interposing section %s/%s extends beyond the end of the segment", info.segInfo.segName, info.sectName); + stop = true; + return; + } + if ( (info.sectAddr % ptrSize) != 0 ) { + diag.error("interposing section %s/%s is not pointer aligned", info.segInfo.segName, info.sectName); + stop = true; + return; + } + handler(info.sectAddr - preferredLoadAddress(), info.sectSize, stop); + } + }); +} + +void MachOAnalyzer::forEachDOFSection(Diagnostics& diag, void (^callback)(uint32_t offset)) const +{ + forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& info, bool malformedSectionRange, bool &stop) { + if ( ( (info.sectFlags & SECTION_TYPE) == S_DTRACE_DOF ) && !malformedSectionRange ) { + callback((uint32_t)(info.sectAddr - info.segInfo.vmAddr)); + } + }); +} + +bool MachOAnalyzer::getCDHash(uint8_t cdHash[20]) const +{ + 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 MachOAnalyzer::isRestricted() const +{ + __block bool result = false; + forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& info, bool malformedSectionRange, bool &stop) { + if ( (strcmp(info.segInfo.segName, "__RESTRICT") == 0) && (strcmp(info.sectName, "__restrict") == 0) ) { + result = true; + stop = true; + } + }); + return result; +} + +bool MachOAnalyzer::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 MachOAnalyzer::canHavePrecomputedDlopenClosure(const char* path, void (^failureReason)(const char*)) const +{ + __block bool retval = true; + + // only dylibs can go in cache + if ( (this->filetype != MH_DYLIB) && (this->filetype != MH_BUNDLE) ) { + retval = false; + failureReason("not MH_DYLIB or MH_BUNDLE"); + } + + // flat namespace files cannot go in cache + if ( (this->flags & MH_TWOLEVEL) == 0 ) { + retval = false; + failureReason("not built with two level namespaces"); + } + + // can only depend on other dylibs with absolute paths + __block bool allDepPathsAreGood = true; + forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if ( loadPath[0] != '/' ) { + allDepPathsAreGood = false; + stop = true; + } + }); + if ( !allDepPathsAreGood ) { + retval = false; + failureReason("depends on dylibs that are not absolute paths"); + } + + // dylibs with interposing info cannot have dlopen closure pre-computed + __block bool hasInterposing = false; + forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool &stop) { + if ( ((info.sectFlags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(info.sectName, "__interpose") == 0) && (strcmp(info.segInfo.segName, "__DATA") == 0)) ) + hasInterposing = true; + }); + if ( hasInterposing ) { + retval = false; + failureReason("has interposing tuples"); + } + + // images that use dynamic_lookup, bundle_loader, or have weak-defs cannot have dlopen closure pre-computed + Diagnostics diag; + auto checkBind = ^(int libOrdinal, bool& stop) { + switch (libOrdinal) { + case BIND_SPECIAL_DYLIB_WEAK_DEF_COALESCE: + failureReason("has weak externals"); + retval = false; + stop = true; + break; + case BIND_SPECIAL_DYLIB_FLAT_LOOKUP: + failureReason("has dynamic_lookup binds"); + retval = false; + stop = true; + break; + case BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE: + failureReason("has reference to main executable (bundle loader)"); + retval = false; + stop = true; + break; + } + }; + + if (hasChainedFixups()) { + forEachChainedFixupTarget(diag, ^(int libOrdinal, const char *symbolName, uint64_t addend, bool weakImport, bool &stop) { + checkBind(libOrdinal, stop); + }); + } else { + forEachBind(diag, ^(uint64_t runtimeOffset, int libOrdinal, const char* symbolName, bool weakImport, uint64_t addend, bool& stop) { + checkBind(libOrdinal, stop); + }, + ^(const char* symbolName) { + }); + } + + // special system dylib overrides cannot have closure pre-computed + if ( strncmp(path, "/usr/lib/system/introspection/", 30) == 0 ) { + retval = false; + failureReason("override of OS dylib"); + } + + return retval; +} + +bool MachOAnalyzer::canBePlacedInDyldCache(const char* path, void (^failureReason)(const char*)) const +{ + if (!MachOFile::canBePlacedInDyldCache(path, failureReason)) + return false; + if ( !(isArch("x86_64") || isArch("x86_64h")) ) + return true; + + // Kick dylibs out of the x86_64 cache if they are using TBI. + __block bool rebasesOk = true; + Diagnostics diag; + uint64_t startVMAddr = preferredLoadAddress(); + uint64_t endVMAddr = startVMAddr + mappedSize(); + forEachRebase(diag, false, ^(uint64_t runtimeOffset, bool &stop) { + uint64_t value = *(uint64_t*)((uint8_t*)this + runtimeOffset); + if ( (value < startVMAddr) || (value >= endVMAddr) ) { + failureReason("rebase value out of range of dylib"); + rebasesOk = false; + stop = true; + } + }); + return rebasesOk; +} + +} // dyld3 + + diff --git a/dyld3/MachOAnalyzer.h b/dyld3/MachOAnalyzer.h new file mode 100644 index 0000000..85255f3 --- /dev/null +++ b/dyld3/MachOAnalyzer.h @@ -0,0 +1,162 @@ +/* + * 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 MachOAnalyzer_h +#define MachOAnalyzer_h + + +#define BIND_SPECIAL_DYLIB_WEAK_DEF_COALESCE (-3) + +#include "MachOLoaded.h" +#include "ClosureFileSystem.h" + +namespace dyld3 { + +// Extra functionality on loaded mach-o files only used during closure building +struct VIS_HIDDEN MachOAnalyzer : public MachOLoaded +{ + static closure::LoadedFileInfo load(Diagnostics& diag, const closure::FileSystem& fileSystem, const char* logicalPath, const char* reqArchName, Platform reqPlatform); + static const MachOAnalyzer* validMainExecutable(Diagnostics& diag, const mach_header* mh, const char* path, uint64_t sliceLength, const char* reqArchName, Platform reqPlatform); + + bool validMachOForArchAndPlatform(Diagnostics& diag, size_t mappedSize, const char* path, const char* reqArchName, Platform reqPlatform) const; + uint64_t mappedSize() const; + bool hasObjC() const; + bool hasPlusLoadMethod(Diagnostics& diag) const; + uint64_t preferredLoadAddress() 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; + + bool isEncrypted() const; + bool getCDHash(uint8_t cdHash[20]) const; + bool hasCodeSignature(uint32_t& fileOffset, uint32_t& size) const; + bool usesLibraryValidation() const; + bool isRestricted() const; + bool getEntry(uint32_t& offset, bool& usesCRT) const; + bool isSlideable() const; + bool hasInitializer(Diagnostics& diag, bool contentRebased, const void* dyldCache=nullptr) const; + void forEachInitializer(Diagnostics& diag, bool contentRebased, void (^callback)(uint32_t offset), const void* dyldCache=nullptr) 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 forEachRebase(Diagnostics& diag, void (^callback)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, 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)(uint64_t pointerAddress, bool bind, int bindLibOrdinal, + const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& stop)) const; + void forEachInterposingSection(Diagnostics& diag, void (^handler)(uint64_t vmOffset, uint64_t vmSize, bool& stop)) const; + const void* content(uint64_t vmOffset); + void forEachLocalReloc(void (^handler)(uint64_t runtimeOffset, bool& stop)) const; + void forEachExternalReloc(void (^handler)(uint64_t runtimeOffset, int libOrdinal, const char* symbolName, bool& stop)) const; + + const void* getRebaseOpcodes(uint32_t& size) const; + const void* getBindOpcodes(uint32_t& size) const; + const void* getLazyBindOpcodes(uint32_t& size) const; + uint64_t segAndOffsetToRuntimeOffset(uint8_t segIndex, uint64_t segOffset) const; + bool hasLazyPointers(uint32_t& runtimeOffset, uint32_t& size) const; + void forEachRebase(Diagnostics& diag, bool ignoreLazyPointer, void (^callback)(uint64_t runtimeOffset, bool& stop)) const; + void forEachTextRebase(Diagnostics& diag, void (^callback)(uint64_t runtimeOffset, bool& stop)) const; + void forEachBind(Diagnostics& diag, void (^callback)(uint64_t runtimeOffset, int libOrdinal, const char* symbolName, + bool weakImport, uint64_t addend, bool& stop), + void (^strongHandler)(const char* symbolName)) const; + void forEachChainedFixupTarget(Diagnostics& diag, void (^callback)(int libOrdinal, const char* symbolName, uint64_t addend, bool weakImport, bool& stop)) const; + void forEachChainedFixupStart(Diagnostics& diag, void (^callback)(uint64_t runtimeOffset, bool& stop)) const; + bool canHavePrecomputedDlopenClosure(const char* path, void (^failureReason)(const char*)) const; + bool canBePlacedInDyldCache(const char* path, void (^failureReason)(const char*)) const; + +#if DEBUG + void validateDyldCacheDylib(Diagnostics& diag, const char* path) const; +#endif + + const MachOAnalyzer* remapIfZeroFill(Diagnostics& diag, const closure::FileSystem& fileSystem, closure::LoadedFileInfo& info) const; + + // protected members of subclass promoted to public here + using MachOLoaded::SegmentInfo; + using MachOLoaded::SectionInfo; + using MachOLoaded::forEachSegment; + using MachOLoaded::forEachSection; + using MachOLoaded::forEachDependentDylib; + using MachOLoaded::getDylibInstallName; + using MachOLoaded::FoundSymbol; + using MachOLoaded::findExportedSymbol; + +private: + + struct SegmentStuff + { + uint64_t fileOffset; + uint64_t fileSize; + uint64_t writable : 1, + executable : 1, + textRelocsAllowed : 1, // segment supports text relocs (i386 only) + segSize : 61; + }; + + enum class Malformed { linkeditOrder, linkeditAlignment, dyldInfoAndlocalRelocs }; + bool enforceFormat(Malformed) const; + + const uint8_t* getContentForVMAddr(const LayoutInfo& info, uint64_t vmAddr) const; + + bool validLoadCommands(Diagnostics& diag, const char* path, size_t fileLen) const; + bool validEmbeddedPaths(Diagnostics& diag, const char* path) const; + bool validSegments(Diagnostics& diag, const char* path, size_t fileLen) const; + bool validLinkedit(Diagnostics& diag, const char* path) const; + bool validLinkeditLayout(Diagnostics& diag, const char* path) const; + bool validRebaseInfo(Diagnostics& diag, const char* path) const; + bool validBindInfo(Diagnostics& diag, const char* path) const; + bool validMain(Diagnostics& diag, const char* path) const; + bool validChainedFixupsInfo(Diagnostics& diag, const char* path) const; + + bool invalidRebaseState(Diagnostics& diag, const char* opcodeName, const char* path, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type) const; + bool invalidBindState(Diagnostics& diag, const char* opcodeName, const char* path, const LinkEditInfo& leInfo, const SegmentInfo segments[], + 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 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; + + void getAllSegmentsInfos(Diagnostics& diag, SegmentInfo segments[]) const; + bool segmentHasTextRelocs(uint32_t segIndex) const; + uint64_t relocBaseAddress(const SegmentInfo segmentsInfos[], uint32_t segCount) const; + void forEachRebase(Diagnostics& diag, void (^handler)(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, bool& stop)) const; + bool segIndexAndOffsetForAddress(uint64_t addr, const SegmentInfo segmentsInfos[], uint32_t segCount, uint32_t& segIndex, uint64_t& segOffset) const; + void forEachBind(Diagnostics& diag, void (^handler)(const char* opcodeName, const LinkEditInfo& leInfo, const SegmentInfo segments[], + bool segIndexSet, bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, + uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, + uint8_t type, const char* symbolName, bool weakImport, uint64_t addend, bool& stop), + void (^strongHandler)(const char* symbolName)) const; + void forEachChainedFixup(Diagnostics& diag, void (^targetCount)(uint32_t totalTargets, bool& stop), + void (^addTarget)(const LinkEditInfo& leInfo, const SegmentInfo segments[], bool libraryOrdinalSet, uint32_t dylibCount, int libOrdinal, uint8_t type, const char* symbolName, uint64_t addend, bool weakImport, bool& stop), + void (^addChainStart)(const LinkEditInfo& leInfo, const SegmentInfo segments[], uint8_t segmentIndex, bool segIndexSet, uint64_t segmentOffset, bool& stop)) const; + bool contentIsRegularStub(const uint8_t* helperContent) const; + uint64_t entryAddrFromThreadCmd(const thread_command* cmd) const; + +}; + +} // namespace dyld3 + +#endif /* MachOAnalyzer_h */ diff --git a/dyld3/MachOFile.cpp b/dyld3/MachOFile.cpp new file mode 100644 index 0000000..bf8724b --- /dev/null +++ b/dyld3/MachOFile.cpp @@ -0,0 +1,984 @@ +/* + * 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 "MachOFile.h" +#include "SupportedArchs.h" + +#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 CPU_SUBTYPE_ARM64_32_V8 + #define CPU_SUBTYPE_ARM64_32_V8 1 +#endif + +#ifndef CPU_TYPE_ARM64_32 + #ifndef CPU_ARCH_ABI64_32 + #define CPU_ARCH_ABI64_32 0x02000000 + #endif + #define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32) +#endif + +namespace dyld3 { + +//////////////////////////// FatFile //////////////////////////////////////// + +const FatFile* FatFile::isFatFile(const void* fileStart) +{ + const FatFile* fileStartAsFat = (FatFile*)fileStart; + if ( (fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC)) || (fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC_64)) ) + return fileStartAsFat; + else + return nullptr; +} + +void FatFile::forEachSlice(Diagnostics& diag, uint64_t fileLen, void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop)) const +{ + if ( this->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) { + if ( OSSwapBigToHostInt32(nfat_arch) > ((4096 - sizeof(fat_header)) / sizeof(fat_arch)) ) { + diag.error("fat header too large: %u entries", OSSwapBigToHostInt32(nfat_arch)); + return; + } + bool stop = false; + const fat_arch* const archs = (fat_arch*)(((char*)this)+sizeof(fat_header)); + for (uint32_t i=0; i < OSSwapBigToHostInt32(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*)this+offset, len, stop); + if ( stop ) + break; + } + } + else if ( this->magic == OSSwapBigToHostInt32(FAT_MAGIC_64) ) { + if ( OSSwapBigToHostInt32(nfat_arch) > ((4096 - sizeof(fat_header)) / sizeof(fat_arch)) ) { + diag.error("fat header too large: %u entries", OSSwapBigToHostInt32(nfat_arch)); + return; + } + bool stop = false; + const fat_arch_64* const archs = (fat_arch_64*)(((char*)this)+sizeof(fat_header)); + for (uint32_t i=0; i < OSSwapBigToHostInt32(nfat_arch); ++i) { + uint32_t cpuType = OSSwapBigToHostInt32(archs[i].cputype); + uint32_t cpuSubType = OSSwapBigToHostInt32(archs[i].cpusubtype); + uint64_t offset = OSSwapBigToHostInt64(archs[i].offset); + uint64_t len = OSSwapBigToHostInt64(archs[i].size); + if ( greaterThanAddOrOverflow(offset, len, fileLen) ) { + diag.error("slice %d extends beyond end of file", i); + return; + } + callback(cpuType, cpuSubType, (uint8_t*)this+offset, len, stop); + if ( stop ) + break; + } + } + else { + diag.error("not a fat file"); + } +} + +bool FatFile::isFatFileWithSlice(Diagnostics& diag, uint64_t fileLen, const char* archName, uint64_t& sliceOffset, uint64_t& sliceLen, bool& missingSlice) const +{ + missingSlice = false; + if ( (this->magic != OSSwapBigToHostInt32(FAT_MAGIC)) && (this->magic != OSSwapBigToHostInt32(FAT_MAGIC_64)) ) + return false; + + __block bool found = false; + forEachSlice(diag, fileLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) { + const char* sliceArchName = MachOFile::archName(sliceCpuType, sliceCpuSubType); + if ( strcmp(sliceArchName, archName) == 0 ) { + sliceOffset = (char*)sliceStart - (char*)this; + 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 && (strcmp(archName, "x86_64h") == 0) ) + return isFatFileWithSlice(diag, fileLen, "x86_64", sliceOffset, sliceLen, missingSlice); + + return found; +} + + +//////////////////////////// MachOFile //////////////////////////////////////// + + +const MachOFile::ArchInfo MachOFile::_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 }, +#if SUPPORT_ARCH_arm64e + { "arm64e", CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_E }, +#endif +#if SUPPORT_ARCH_arm64_32 + { "arm64_32", CPU_TYPE_ARM64_32, CPU_SUBTYPE_ARM64_32_V8 }, +#endif + { "armv7k", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K }, + { "armv7s", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S }, + { "armv7", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7 } +}; + +const MachOFile::PlatformInfo MachOFile::_s_platformInfos[] = { + { "macOS", Platform::macOS, LC_VERSION_MIN_MACOSX }, + { "iOS", Platform::iOS, LC_VERSION_MIN_IPHONEOS }, + { "tvOS", Platform::tvOS, LC_VERSION_MIN_TVOS }, + { "watchOS", Platform::watchOS, LC_VERSION_MIN_WATCHOS }, + { "bridgeOS", Platform::bridgeOS, LC_BUILD_VERSION }, + { "iOSMac", Platform::iOSMac, LC_BUILD_VERSION }, + { "iOS-sim", Platform::iOS_simulator, LC_BUILD_VERSION }, + { "tvOS-sim", Platform::tvOS_simulator, LC_BUILD_VERSION }, + { "watchOS-sim", Platform::watchOS_simulator, LC_BUILD_VERSION }, +}; + + +bool MachOFile::is64() const +{ + return (this->magic == MH_MAGIC_64); +} + +uint32_t MachOFile::pointerSize() const +{ + if (this->magic == MH_MAGIC_64) + return 8; + else + return 4; +} + +bool MachOFile::uses16KPages() const +{ + switch (this->cputype) { + case CPU_TYPE_ARM64: + case CPU_TYPE_ARM: + case CPU_TYPE_ARM64_32: + return true; + default: + return false; + } +} + +bool MachOFile::isArch(const char* aName) const +{ + return (strcmp(aName, archName(this->cputype, this->cpusubtype)) == 0); +} + +const char* MachOFile::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 MachOFile::cpuTypeFromArchName(const char* archName) +{ + for (const ArchInfo& info : _s_archInfos) { + if ( strcmp(archName, info.name) == 0 ) { + return info.cputype; + } + } + return 0; +} + +uint32_t MachOFile::cpuSubtypeFromArchName(const char* archName) +{ + for (const ArchInfo& info : _s_archInfos) { + if ( strcmp(archName, info.name) == 0 ) { + return info.cpusubtype; + } + } + return 0; +} + +const char* MachOFile::archName() const +{ + return archName(this->cputype, this->cpusubtype); +} + +static void appendDigit(char*& s, unsigned& num, unsigned place, bool& startedPrinting) +{ + if ( num >= place ) { + unsigned dig = (num/place); + *s++ = '0' + dig; + num -= (dig*place); + startedPrinting = true; + } + else if ( startedPrinting ) { + *s++ = '0'; + } +} + +static void appendNumber(char*& s, unsigned num) +{ + assert(num < 99999); + bool startedPrinting = false; + appendDigit(s, num, 10000, startedPrinting); + appendDigit(s, num, 1000, startedPrinting); + appendDigit(s, num, 100, startedPrinting); + appendDigit(s, num, 10, startedPrinting); + appendDigit(s, num, 1, startedPrinting); + if ( !startedPrinting ) + *s++ = '0'; +} + +void MachOFile::packedVersionToString(uint32_t packedVersion, char versionString[32]) +{ + // sprintf(versionString, "%d.%d.%d", (packedVersion >> 16), ((packedVersion >> 8) & 0xFF), (packedVersion & 0xFF)); + char* s = versionString; + appendNumber(s, (packedVersion >> 16)); + *s++ = '.'; + appendNumber(s, (packedVersion >> 8) & 0xFF); + *s++ = '.'; + appendNumber(s, (packedVersion & 0xFF)); + *s++ = '\0'; +} + +bool MachOFile::supportsPlatform(Platform reqPlatform) const +{ + __block bool foundRequestedPlatform = false; + __block bool foundOtherPlatform = false; + forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) { + if ( platform == reqPlatform ) + foundRequestedPlatform = true; + else + foundOtherPlatform = true; + }); + if ( foundRequestedPlatform ) + return true; + + // we did find some platform info, but not requested, so return false + if ( foundOtherPlatform ) + return false; + + // binary has no explict load command to mark platform + // could be an old macOS binary, look at arch + if ( reqPlatform == Platform::macOS ) { + if ( this->cputype == CPU_TYPE_X86_64 ) + return true; + if ( this->cputype == CPU_TYPE_I386 ) + return true; + } + + return false; +} + +Platform MachOFile::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 +} + +#if __x86_64__ +static bool isHaswell() +{ + // FIXME: figure out a commpage way to check this + static bool sAlreadyDetermined = false; + static bool sHaswell = false; + if ( !sAlreadyDetermined ) { + 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); + sHaswell = (result == KERN_SUCCESS) && (info.cpu_subtype == CPU_SUBTYPE_X86_64_H); + sAlreadyDetermined = true; + } + return sHaswell; +} +#endif + +const char* MachOFile::currentArchName() +{ +#if __ARM_ARCH_7K__ + return "armv7k"; +#elif __ARM_ARCH_7A__ + return "armv7"; +#elif __ARM_ARCH_7S__ + return "armv7s"; +#elif __arm64e__ + return "arm64e"; +#elif __arm64__ +#if __LP64__ + return "arm64"; +#else + return "arm64_32"; +#endif +#elif __x86_64__ + return isHaswell() ? "x86_64h" : "x86_64"; +#elif __i386__ + return "i386"; +#else + #error unknown arch +#endif +} + + +bool MachOFile::isDylib() const +{ + return (this->filetype == MH_DYLIB); +} + +bool MachOFile::isBundle() const +{ + return (this->filetype == MH_BUNDLE); +} + +bool MachOFile::isMainExecutable() const +{ + return (this->filetype == MH_EXECUTE); +} + +bool MachOFile::isDynamicExecutable() const +{ + if ( this->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 MachOFile::isPIE() const +{ + return (this->flags & MH_PIE); +} + +const char* MachOFile::platformName(Platform reqPlatform) +{ + for (const PlatformInfo& info : _s_platformInfos) { + if ( info.platform == reqPlatform ) + return info.name; + } + return "unknown platform"; +} + +void MachOFile::forEachSupportedPlatform(void (^handler)(Platform platform, uint32_t minOS, uint32_t sdk)) const +{ + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + const build_version_command* buildCmd = (build_version_command *)cmd; + const version_min_command* versCmd = (version_min_command*)cmd; + switch ( cmd->cmd ) { + case LC_BUILD_VERSION: + handler((Platform)(buildCmd->platform), buildCmd->minos, buildCmd->sdk); + break; + case LC_VERSION_MIN_MACOSX: + handler(Platform::macOS, versCmd->version, versCmd->sdk); + break; + case LC_VERSION_MIN_IPHONEOS: + if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) ) + handler(Platform::iOS_simulator, versCmd->version, versCmd->sdk); // old sim binary + else + handler(Platform::iOS, versCmd->version, versCmd->sdk); + break; + case LC_VERSION_MIN_TVOS: + if ( this->cputype == CPU_TYPE_X86_64 ) + handler(Platform::tvOS_simulator, versCmd->version, versCmd->sdk); // old sim binary + else + handler(Platform::tvOS, versCmd->version, versCmd->sdk); + break; + case LC_VERSION_MIN_WATCHOS: + if ( (this->cputype == CPU_TYPE_X86_64) || (this->cputype == CPU_TYPE_I386) ) + handler(Platform::watchOS_simulator, versCmd->version, versCmd->sdk); // old sim binary + else + handler(Platform::watchOS, versCmd->version, versCmd->sdk); + break; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + + +bool MachOFile::isMachO(Diagnostics& diag, uint64_t fileSize) const +{ + if ( !hasMachOMagic() ) { + diag.error("file does not start with MH_MAGIC[_64]"); + return false; + } + if ( this->sizeofcmds + sizeof(mach_header_64) > fileSize ) { + diag.error("load commands exceed length of first segment"); + return false; + } + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { }); + return diag.noError(); +} + +bool MachOFile::hasMachOMagic() const +{ + return ( (this->magic == MH_MAGIC) || (this->magic == MH_MAGIC_64) ); +} + +void MachOFile::forEachLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& stop)) const +{ + bool stop = false; + const load_command* startCmds = nullptr; + if ( this->magic == MH_MAGIC_64 ) + startCmds = (load_command*)((char *)this + sizeof(mach_header_64)); + else if ( this->magic == MH_MAGIC ) + startCmds = (load_command*)((char *)this + sizeof(mach_header)); + else { + diag.error("file does not start with MH_MAGIC[_64]"); + return; // not a mach-o file + } + const load_command* const cmdsEnd = (load_command*)((char*)startCmds + this->sizeofcmds); + const load_command* cmd = startCmds; + for (uint32_t i = 0; i < this->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; + } +} + +const char* MachOFile::installName() const +{ + const char* name; + uint32_t compatVersion; + uint32_t currentVersion; + if ( getDylibInstallName(&name, &compatVersion, ¤tVersion) ) + return name; + return nullptr; +} + +bool MachOFile::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; +} + +bool MachOFile::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; +} + +void MachOFile::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; + 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 MachOFile::forDyldEnv(void (^callback)(const char* envVar, bool& stop)) const +{ + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_DYLD_ENVIRONMENT ) { + const dylinker_command* envCmd = (dylinker_command*)cmd; + const char* keyEqualsValue = (char*)envCmd + envCmd->name.offset; + // only process variables that start with DYLD_ and end in _PATH + if ( (strncmp(keyEqualsValue, "DYLD_", 5) == 0) ) { + const char* equals = strchr(keyEqualsValue, '='); + if ( equals != NULL ) { + if ( strncmp(&equals[-5], "_PATH", 5) == 0 ) { + callback(keyEqualsValue, stop); + } + } + } + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + +bool MachOFile::enforceCompatVersion() const +{ + __block bool result = true; + forEachSupportedPlatform(^(Platform platform, uint32_t minOS, uint32_t sdk) { + switch ( platform ) { + case Platform::macOS: + if ( minOS >= 0x000A0E00 ) // macOS 10.14 + result = false; + break; + case Platform::iOS: + case Platform::tvOS: + case Platform::iOS_simulator: + case Platform::tvOS_simulator: + if ( minOS >= 0x000C0000 ) // iOS 12.0 + result = false; + break; + case Platform::watchOS: + case Platform::watchOS_simulator: + if ( minOS >= 0x00050000 ) // watchOS 5.0 + result = false; + break; + case Platform::bridgeOS: + if ( minOS >= 0x00030000 ) // bridgeOS 3.0 + result = false; + break; + case Platform::iOSMac: + result = false; + break; + case Platform::unknown: + break; + } + }); + return result; +} + + +void MachOFile::forEachSegment(void (^callback)(const SegmentInfo& info, bool& stop)) const +{ + Diagnostics diag; + const bool intel32 = (this->cputype == CPU_TYPE_I386); + __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; + } + SegmentInfo info; + info.fileOffset = segCmd->fileoff; + info.fileSize = segCmd->filesize; + info.vmAddr = segCmd->vmaddr; + info.vmSize = segCmd->vmsize; + info.sizeOfSections = sizeOfSections; + info.segName = segCmd->segname; + info.protections = segCmd->initprot; + info.textRelocs = false; + info.p2align = p2align; + info.segIndex = segIndex; + callback(info, stop); + ++segIndex; + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + uint64_t sizeOfSections = segCmd->vmsize; + uint8_t p2align = 0; + bool hasTextRelocs = false; + 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; + if ( sect->flags & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC) ) + hasTextRelocs = true; + } + SegmentInfo info; + info.fileOffset = segCmd->fileoff; + info.fileSize = segCmd->filesize; + info.vmAddr = segCmd->vmaddr; + info.vmSize = segCmd->vmsize; + info.sizeOfSections = sizeOfSections; + info.segName = segCmd->segname; + info.protections = segCmd->initprot; + info.textRelocs = intel32 && !info.writable() && hasTextRelocs; + info.p2align = p2align; + info.segIndex = segIndex; + callback(info, stop); + ++segIndex; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + +void MachOFile::forEachSection(void (^callback)(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop)) const +{ + Diagnostics diag; + const bool intel32 = (this->cputype == CPU_TYPE_I386); + __block uint32_t segIndex = 0; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + SectionInfo sectInfo; + 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; + } + sectInfo.segInfo.fileOffset = segCmd->fileoff; + sectInfo.segInfo.fileSize = segCmd->filesize; + sectInfo.segInfo.vmAddr = segCmd->vmaddr; + sectInfo.segInfo.vmSize = segCmd->vmsize; + sectInfo.segInfo.sizeOfSections = sizeOfSections; + sectInfo.segInfo.segName = segCmd->segname; + sectInfo.segInfo.protections = segCmd->initprot; + sectInfo.segInfo.textRelocs = false; + sectInfo.segInfo.p2align = p2align; + sectInfo.segInfo.segIndex = segIndex; + 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 malformedSectionRange = (sect->addr < segCmd->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, segCmd->vmaddr + segCmd->filesize); + sectInfo.sectName = sectName; + sectInfo.sectFileOffset = sect->offset; + sectInfo.sectFlags = sect->flags; + sectInfo.sectAddr = sect->addr; + sectInfo.sectSize = sect->size; + sectInfo.sectAlignP2 = sect->align; + sectInfo.reserved1 = sect->reserved1; + sectInfo.reserved2 = sect->reserved2; + callback(sectInfo, malformedSectionRange, stop); + } + ++segIndex; + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + uint64_t sizeOfSections = segCmd->vmsize; + uint8_t p2align = 0; + bool hasTextRelocs = false; + 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; + if ( sect->flags & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC) ) + hasTextRelocs = true; + } + sectInfo.segInfo.fileOffset = segCmd->fileoff; + sectInfo.segInfo.fileSize = segCmd->filesize; + sectInfo.segInfo.vmAddr = segCmd->vmaddr; + sectInfo.segInfo.vmSize = segCmd->vmsize; + sectInfo.segInfo.sizeOfSections = sizeOfSections; + sectInfo.segInfo.segName = segCmd->segname; + sectInfo.segInfo.protections = segCmd->initprot; + sectInfo.segInfo.textRelocs = intel32 && !sectInfo.segInfo.writable() && hasTextRelocs; + sectInfo.segInfo.p2align = p2align; + sectInfo.segInfo.segIndex = segIndex; + 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 malformedSectionRange = (sect->addr < segCmd->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, segCmd->vmaddr + segCmd->filesize); + sectInfo.sectName = sectName; + sectInfo.sectFileOffset = sect->offset; + sectInfo.sectFlags = sect->flags; + sectInfo.sectAddr = sect->addr; + sectInfo.sectSize = sect->size; + sectInfo.sectAlignP2 = sect->align; + sectInfo.reserved1 = sect->reserved1; + sectInfo.reserved2 = sect->reserved2; + callback(sectInfo, malformedSectionRange, stop); + } + ++segIndex; + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call +} + +bool MachOFile::hasWeakDefs() const +{ + return (this->flags & MH_WEAK_DEFINES); +} + +bool MachOFile::hasThreadLocalVariables() const +{ + return (this->flags & MH_HAS_TLV_DESCRIPTORS); +} + +static bool endsWith(const char* str, const char* suffix) +{ + size_t strLen = strlen(str); + size_t suffixLen = strlen(suffix); + if ( strLen < suffixLen ) + return false; + return (strcmp(&str[strLen-suffixLen], suffix) == 0); +} + +bool MachOFile::canBePlacedInDyldCache(const char* path, void (^failureReason)(const char*)) const +{ + // only dylibs can go in cache + if ( this->filetype != MH_DYLIB ) { + failureReason("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 + bool retval = true; + const char* dylibName = installName(); + if ( dylibName[0] != '/' ) { + retval = false; + failureReason("install name not an absolute path"); + } + else if ( (strncmp(dylibName, "/usr/lib/", 9) != 0) && (strncmp(dylibName, "/System/Library/", 16) != 0) ) { + retval = false; + failureReason("Not in '/usr/lib/' or '/System/Library/'"); + } + + // flat namespace files cannot go in cache + if ( (this->flags & MH_TWOLEVEL) == 0 ) { + retval = false; + failureReason("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; + failureReason("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; + failureReason("Missing split seg info"); + } + if ( !hasDyldInfo ) { + retval = false; + failureReason("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; + failureReason("Depends on dylibs ineligable for dyld cache"); + } + + // dylibs with interposing info cannot be in cache + __block bool hasInterposing = false; + forEachSection(^(const SectionInfo& info, bool malformedSectionRange, bool &stop) { + if ( ((info.sectFlags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(info.sectName, "__interpose") == 0) && (strcmp(info.segInfo.segName, "__DATA") == 0)) ) + hasInterposing = true; + }); + if ( hasInterposing ) { + retval = false; + failureReason("Has interposing tuples"); + } + + return retval; +} + + +bool MachOFile::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const +{ + if ( const encryption_info_command* encCmd = findFairPlayEncryptionLoadCommand() ) { + if ( encCmd->cryptid == 1 ) { + // Note: cryptid is 0 in just-built apps. The AppStore sets cryptid to 1 + textOffset = encCmd->cryptoff; + size = encCmd->cryptsize; + return true; + } + } + textOffset = 0; + size = 0; + return false; +} + +bool MachOFile::canBeFairPlayEncrypted() const +{ + return (findFairPlayEncryptionLoadCommand() != nullptr); +} + +const encryption_info_command* MachOFile::findFairPlayEncryptionLoadCommand() const +{ + __block const encryption_info_command* result = nullptr; + Diagnostics diag; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) { + result = (encryption_info_command*)cmd; + stop = true; + } + }); + if ( diag.noError() ) + return result; + else + return nullptr; +} + + +bool MachOFile::hasChainedFixups() const +{ +#if SUPPORT_ARCH_arm64e + // for now only arm64e uses chained fixups + return ( strcmp(archName(), "arm64e") == 0 ); +#else + return false; +#endif +} + +uint64_t MachOFile::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 MachOFile::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 |= (~0ULL) << bit; + return result; +} + + +} // namespace dyld3 + + + + + diff --git a/dyld3/MachOFile.h b/dyld3/MachOFile.h new file mode 100644 index 0000000..4680e44 --- /dev/null +++ b/dyld3/MachOFile.h @@ -0,0 +1,193 @@ +/* + * 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 MachOFile_h +#define MachOFile_h + +#include +#include +#include + +#include "Diagnostics.h" + +#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE + #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 +#endif + + +#ifndef PLATFORM_IOSSIMULATOR + #define PLATFORM_IOSSIMULATOR (7) +#endif + +#ifndef PLATFORM_TVOSSIMULATOR + #define PLATFORM_TVOSSIMULATOR (8) +#endif + +#ifndef PLATFORM_WATCHOSSIMULATOR + #define PLATFORM_WATCHOSSIMULATOR (9) +#endif + + +namespace dyld3 { + + + +/// Returns true if (addLHS + addRHS) > b, or if the add overflowed +template +inline 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 +inline bool greaterThanAddOrOverflow(uint64_t addLHS, uint64_t addRHS, T b) { + return (addLHS > b) || (addRHS > (b-addLHS)); +} + + +// Note, this should match PLATFORM_* values in +enum class Platform { + unknown = 0, + macOS = 1, // PLATFORM_MACOS + iOS = 2, // PLATFORM_IOS + tvOS = 3, // PLATFORM_TVOS + watchOS = 4, // PLATFORM_WATCHOS + bridgeOS = 5, // PLATFORM_BRIDGEOS + iOSMac = 6, // PLATFORM_IOSMAC + iOS_simulator = 7, // PLATFORM_IOSSIMULATOR + tvOS_simulator = 8, // PLATFORM_TVOSSIMULATOR + watchOS_simulator = 9 // PLATFORM_WATCHOSSIMULATOR +}; + +// A file read/mapped into memory +struct VIS_HIDDEN FatFile : fat_header +{ + static const FatFile* isFatFile(const void* fileContent); + void forEachSlice(Diagnostics& diag, uint64_t fileLen, void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop)) const; + bool isFatFileWithSlice(Diagnostics& diag, uint64_t fileLen, const char* archName, uint64_t& sliceOffset, uint64_t& sliceLen, bool& missingSlice) const; +}; + + +// A mach-o file read/mapped into memory +// Only info from mach_header or load commands is accessible (no LINKEDIT info) +struct VIS_HIDDEN MachOFile : mach_header +{ + static const char* archName(uint32_t cputype, uint32_t cpusubtype); + static const char* platformName(Platform platform); + static uint32_t cpuTypeFromArchName(const char* archName); + static uint32_t cpuSubtypeFromArchName(const char* archName); + static void packedVersionToString(uint32_t packedVersion, char versionString[32]); + static const char* currentArchName(); + static Platform currentPlatform(); + 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); + + + bool hasMachOMagic() const; + bool isMachO(Diagnostics& diag, uint64_t fileSize) const; + bool isDylib() const; + bool isBundle() const; + bool isMainExecutable() const; + bool isDynamicExecutable() const; + bool isPIE() const; + bool isArch(const char* archName) const; + const char* archName() const; + bool is64() const; + uint32_t pointerSize() const; + bool uses16KPages() const; + bool supportsPlatform(Platform) const; + bool isSimulatorBinary() const; + bool getUuid(uuid_t uuid) const; + bool hasWeakDefs() const; + bool hasThreadLocalVariables() const; + bool getDylibInstallName(const char** installName, uint32_t* compatVersion, uint32_t* currentVersion) const; + void forEachSupportedPlatform(void (^callback)(Platform platform, uint32_t minOS, uint32_t sdk)) const; + const char* installName() const; // returns nullptr is no install name + void forEachDependentDylib(void (^callback)(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop)) const; + bool canBePlacedInDyldCache(const char* path, void (^failureReason)(const char*)) const; + bool canBeFairPlayEncrypted() const; + bool isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const; + bool hasChainedFixups() const; + void forDyldEnv(void (^callback)(const char* envVar, bool& stop)) const; + bool enforceCompatVersion() const; + + struct SegmentInfo + { + uint64_t fileOffset; + uint64_t fileSize; + uint64_t vmAddr; + uint64_t vmSize; + uint64_t sizeOfSections; + const char* segName; + uint32_t protections; + uint32_t textRelocs : 1, // segment has text relocs (i386 only) + segIndex : 15, + p2align : 16; + bool readable() const { return protections & VM_PROT_READ; } + bool writable() const { return protections & VM_PROT_WRITE; } + bool executable() const { return protections & VM_PROT_EXECUTE; } + }; + + struct SectionInfo + { + SegmentInfo segInfo; + uint64_t sectAddr; + uint64_t sectSize; + const char* sectName; + uint32_t sectFileOffset; + uint32_t sectFlags; + uint32_t sectAlignP2; + uint32_t reserved1; + uint32_t reserved2; + }; + + void forEachSegment(void (^callback)(const SegmentInfo& info, bool& stop)) const; + void forEachSection(void (^callback)(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop)) const; + +protected: + void forEachLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& stop)) const; + + const encryption_info_command* findFairPlayEncryptionLoadCommand() const; + + struct ArchInfo + { + const char* name; + uint32_t cputype; + uint32_t cpusubtype; + }; + static const ArchInfo _s_archInfos[]; + + struct PlatformInfo + { + const char* name; + Platform platform; + uint32_t loadCommand; + }; + static const PlatformInfo _s_platformInfos[]; +}; + + +} // namespace dyld3 + +#endif /* MachOFile_h */ diff --git a/dyld3/MachOLoaded.cpp b/dyld3/MachOLoaded.cpp new file mode 100644 index 0000000..4e54752 --- /dev/null +++ b/dyld3/MachOLoaded.cpp @@ -0,0 +1,977 @@ +/* + * 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 "MachOLoaded.h" +#include "MachOFile.h" +#include "MachOFile.h" +#include "CodeSigningTypes.h" + + +#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 { + + +void MachOLoaded::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 hasMinVersion = 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 ( hasMinVersion ) + diag.error("multiple LC_VERSION_MIN_* load commands"); + hasMinVersion = 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 ( hasMinVersion ) + diag.error("LC_BUILD_VERSION cannot coexist LC_VERSION_MIN_* with load commands"); + 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 MachOLoaded::getLinkEditPointers(Diagnostics& diag, LinkEditInfo& result) const +{ + getLinkEditLoadCommands(diag, result); + if ( diag.noError() ) + getLayoutInfo(result.layout); +} + +void MachOLoaded::getLayoutInfo(LayoutInfo& result) const +{ + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__TEXT") == 0 ) { + result.textUnslidVMAddr = (uintptr_t)info.vmAddr; + result.slide = (uintptr_t)(((uint64_t)this) - info.vmAddr); + } + else if ( strcmp(info.segName, "__LINKEDIT") == 0 ) { + result.linkeditUnslidVMAddr = (uintptr_t)info.vmAddr; + result.linkeditFileOffset = (uint32_t)info.fileOffset; + result.linkeditFileSize = (uint32_t)info.fileSize; + result.linkeditSegIndex = info.segIndex; + } + }); +} + +bool MachOLoaded::hasExportTrie(uint32_t& runtimeOffset, uint32_t& size) const +{ + runtimeOffset = 0; + size = 0; + Diagnostics diag; + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + if ( diag.hasError() ) + return false; + if ( leInfo.dyldInfo != nullptr ) { + uint32_t offsetInLinkEdit = leInfo.dyldInfo->export_off - leInfo.layout.linkeditFileOffset; + runtimeOffset = offsetInLinkEdit + (uint32_t)(leInfo.layout.linkeditUnslidVMAddr - leInfo.layout.textUnslidVMAddr); + size = leInfo.dyldInfo->export_size; + return true; + } + return false; +} + + +#if BUILDING_LIBDYLD +// this is only used by dlsym() at runtime. All other binding is done when the closure is built. +bool MachOLoaded::hasExportedSymbol(const char* symbolName, DependentToMachOLoaded finder, void** result, + bool* resultPointsToInstructions) const +{ + typedef void* (*ResolverFunc)(void); + ResolverFunc resolver; + Diagnostics diag; + FoundSymbol foundInfo; + if ( findExportedSymbol(diag, symbolName, foundInfo, finder) ) { + switch ( foundInfo.kind ) { + case FoundSymbol::Kind::headerOffset: { + *result = (uint8_t*)foundInfo.foundInDylib + foundInfo.value; + *resultPointsToInstructions = false; + int64_t slide = foundInfo.foundInDylib->getSlide(); + foundInfo.foundInDylib->forEachSection(^(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) { + uint64_t sectStartAddr = sectInfo.sectAddr + slide; + uint64_t sectEndAddr = sectStartAddr + sectInfo.sectSize; + if ( ((uint64_t)*result >= sectStartAddr) && ((uint64_t)*result < sectEndAddr) ) { + *resultPointsToInstructions = (sectInfo.sectFlags & S_ATTR_PURE_INSTRUCTIONS) || (sectInfo.sectFlags & S_ATTR_SOME_INSTRUCTIONS); + stop = true; + } + }); + break; + } + case FoundSymbol::Kind::absolute: + *result = (void*)(long)foundInfo.value; + *resultPointsToInstructions = false; + 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)(); + // FIXME: Set this properly + *resultPointsToInstructions = true; + break; + } + return true; + } + return false; +} +#endif // BUILDING_LIBDYLD + +bool MachOLoaded::findExportedSymbol(Diagnostics& diag, const char* symbolName, FoundSymbol& foundInfo, DependentToMachOLoaded 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 ) { + if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) { + if ( depMH->findExportedSymbol(diag, symbolName, foundInfo, findDependent) ) { + stop = true; + foundInReExportedDylib = true; + } + } + } + ++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; + if ( (ordinal == 0) || (ordinal > dependentDylibCount()) ) { + diag.error("re-export ordinal %lld out of range for %s", ordinal, symbolName); + return false; + } + uint32_t depIndex = (uint32_t)(ordinal-1); + if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) { + return depMH->findExportedSymbol(diag, importedName, 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.isWeakDef = false; + foundInfo.foundInDylib = this; + 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; + } + if ( flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION ) + foundInfo.isWeakDef = true; + 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; + 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 = this; + foundInfo.value = n_value - leInfo.layout.textUnslidVMAddr; + foundInfo.resolverFuncOffset = 0; + foundInfo.foundSymbolName = symbolName; + stop = true; + } + }); + if ( foundInfo.foundInDylib == nullptr ) { + // symbol not exported from this image. Search any re-exported dylibs + __block unsigned depIndex = 0; + forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if ( isReExport && findDependent ) { + if ( const MachOLoaded* depMH = findDependent(this, depIndex) ) { + if ( depMH->findExportedSymbol(diag, symbolName, foundInfo, findDependent) ) { + stop = true; + } + } + } + ++depIndex; + }); + } + return (foundInfo.foundInDylib != nullptr); + } +} + +intptr_t MachOLoaded::getSlide() const +{ + Diagnostics diag; + __block intptr_t slide = 0; + forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* seg = (segment_command_64*)cmd; + if ( strcmp(seg->segname, "__TEXT") == 0 ) { + slide = (uintptr_t)(((uint64_t)this) - seg->vmaddr); + stop = true; + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* seg = (segment_command*)cmd; + if ( strcmp(seg->segname, "__TEXT") == 0 ) { + slide = (uintptr_t)(((uint64_t)this) - seg->vmaddr); + stop = true; + } + } + }); + diag.assertNoError(); // any malformations in the file should have been caught by earlier validate() call + return slide; +} + +const uint8_t* MachOLoaded::getLinkEditContent(const LayoutInfo& info, uint32_t fileOffset) const +{ + uint32_t offsetInLinkedit = fileOffset - info.linkeditFileOffset; + uintptr_t linkeditStartAddr = info.linkeditUnslidVMAddr + info.slide; + return (uint8_t*)(linkeditStartAddr + offsetInLinkedit); +} + + +void MachOLoaded::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*)symbols; + 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 MachOLoaded::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); + } + } + } +} + +uint32_t MachOLoaded::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* MachOLoaded::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; +} + +const char* MachOLoaded::segmentName(uint32_t targetSegIndex) const +{ + __block const char* result = nullptr; + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( targetSegIndex == info.segIndex ) { + result = info.segName; + stop = true; + } + }); + return result; +} + +bool MachOLoaded::findClosestFunctionStart(uint64_t address, uint64_t* functionStartAddress) const +{ + Diagnostics diag; + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return false; + if ( leInfo.functionStarts == nullptr ) + return false; + + const uint8_t* starts = getLinkEditContent(leInfo.layout, leInfo.functionStarts->dataoff); + const uint8_t* startsEnd = starts + leInfo.functionStarts->datasize; + + uint64_t lastAddr = (uint64_t)(long)this; + uint64_t runningAddr = lastAddr; + while (diag.noError()) { + uint64_t value = read_uleb128(diag, starts, startsEnd); + if ( value == 0 ) + break; + lastAddr = runningAddr; + runningAddr += value; + //fprintf(stderr, " addr=0x%08llX\n", runningAddr); + if ( runningAddr > address ) { + *functionStartAddress = lastAddr; + return true; + } + }; + + return false; +} + +bool MachOLoaded::findClosestSymbol(uint64_t address, const char** symbolName, uint64_t* symbolAddr) const +{ + Diagnostics diag; + LinkEditInfo leInfo; + getLinkEditPointers(diag, leInfo); + if ( diag.hasError() ) + return false; + if ( (leInfo.symTab == nullptr) || (leInfo.dynSymTab == nullptr) ) + return false; + uint64_t targetUnslidAddress = address - leInfo.layout.slide; + + 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)); + if ( is64() ) { + const struct nlist_64* symbols64 = (struct nlist_64*)symbols; + const struct nlist_64* bestSymbol = nullptr; + // first walk all global symbols + const struct nlist_64* const globalsStart = &symbols64[leInfo.dynSymTab->iextdefsym]; + const struct nlist_64* const globalsEnd = &globalsStart[leInfo.dynSymTab->nextdefsym]; + for (const struct nlist_64* s = globalsStart; s < globalsEnd; ++s) { + if ( (s->n_type & N_TYPE) == N_SECT ) { + if ( bestSymbol == nullptr ) { + if ( s->n_value <= targetUnslidAddress ) + bestSymbol = s; + } + else if ( (s->n_value <= targetUnslidAddress) && (bestSymbol->n_value < s->n_value) ) { + bestSymbol = s; + } + } + } + // next walk all local symbols + const struct nlist_64* const localsStart = &symbols64[leInfo.dynSymTab->ilocalsym]; + const struct nlist_64* const localsEnd = &localsStart[leInfo.dynSymTab->nlocalsym]; + for (const struct nlist_64* s = localsStart; s < localsEnd; ++s) { + if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) { + if ( bestSymbol == nullptr ) { + if ( s->n_value <= targetUnslidAddress ) + bestSymbol = s; + } + else if ( (s->n_value <= targetUnslidAddress) && (bestSymbol->n_value < s->n_value) ) { + bestSymbol = s; + } + } + } + if ( bestSymbol != NULL ) { + *symbolAddr = bestSymbol->n_value + leInfo.layout.slide; + if ( bestSymbol->n_un.n_strx < maxStringOffset ) + *symbolName = &stringPool[bestSymbol->n_un.n_strx]; + return true; + } + } + else { + const struct nlist* bestSymbol = nullptr; + // first walk all global symbols + const struct nlist* const globalsStart = &symbols[leInfo.dynSymTab->iextdefsym]; + const struct nlist* const globalsEnd = &globalsStart[leInfo.dynSymTab->nextdefsym]; + for (const struct nlist* s = globalsStart; s < globalsEnd; ++s) { + if ( (s->n_type & N_TYPE) == N_SECT ) { + if ( bestSymbol == nullptr ) { + if ( s->n_value <= targetUnslidAddress ) + bestSymbol = s; + } + else if ( (s->n_value <= targetUnslidAddress) && (bestSymbol->n_value < s->n_value) ) { + bestSymbol = s; + } + } + } + // next walk all local symbols + const struct nlist* const localsStart = &symbols[leInfo.dynSymTab->ilocalsym]; + const struct nlist* const localsEnd = &localsStart[leInfo.dynSymTab->nlocalsym]; + for (const struct nlist* s = localsStart; s < localsEnd; ++s) { + if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) { + if ( bestSymbol == nullptr ) { + if ( s->n_value <= targetUnslidAddress ) + bestSymbol = s; + } + else if ( (s->n_value <= targetUnslidAddress) && (bestSymbol->n_value < s->n_value) ) { + bestSymbol = s; + } + } + } + if ( bestSymbol != nullptr ) { +#if __arm__ + if ( bestSymbol->n_desc & N_ARM_THUMB_DEF ) + *symbolAddr = (bestSymbol->n_value | 1) + leInfo.layout.slide; + else + *symbolAddr = bestSymbol->n_value + leInfo.layout.slide; +#else + *symbolAddr = bestSymbol->n_value + leInfo.layout.slide; +#endif + if ( bestSymbol->n_un.n_strx < maxStringOffset ) + *symbolName = &stringPool[bestSymbol->n_un.n_strx]; + return true; + } + } + + return false; +} + +const void* MachOLoaded::findSectionContent(const char* segName, const char* sectName, uint64_t& size) const +{ + __block const void* result = nullptr; + forEachSection(^(const SectionInfo& sectInfo, bool malformedSectionRange, bool& stop) { + if ( (strcmp(sectInfo.sectName, sectName) == 0) && (strcmp(sectInfo.segInfo.segName, segName) == 0) ) { + size = sectInfo.sectSize; + result = (void*)(sectInfo.sectAddr + getSlide()); + } + }); + return result; +} + + +bool MachOLoaded::intersectsRange(uintptr_t start, uintptr_t length) const +{ + __block bool result = false; + uintptr_t slide = getSlide(); + forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( (info.vmAddr+info.vmSize+slide >= start) && (info.vmAddr+slide < start+length) ) + result = true; + }); + return result; +} + +const uint8_t* MachOLoaded::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 > (uint64_t)(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; +} + +bool MachOLoaded::cdHashOfCodeSignature(const void* codeSigStart, size_t codeSignLen, uint8_t cdHash[20]) const +{ + 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_SHA384 ) { + uint8_t digest[CC_SHA384_DIGEST_LENGTH]; + CC_SHA384(cd, cdLength, digest); + // cd-hash of sigs that use SHA384 is the first 20 bytes of the SHA384 of the code digest + memcpy(cdHash, digest, 20); + return true; + } + else if ( (cd->hashType == CS_HASHTYPE_SHA256) || (cd->hashType == CS_HASHTYPE_SHA256_TRUNCATED) ) { + 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; +} + + +// Note, this has to match the kernel +static const uint32_t hashPriorities[] = { + CS_HASHTYPE_SHA1, + CS_HASHTYPE_SHA256_TRUNCATED, + CS_HASHTYPE_SHA256, + CS_HASHTYPE_SHA384, +}; + +static unsigned int hash_rank(const CS_CodeDirectory *cd) +{ + uint32_t type = cd->hashType; + for (uint32_t n = 0; n < sizeof(hashPriorities) / sizeof(hashPriorities[0]); ++n) { + if (hashPriorities[n] == type) + return n + 1; + } + + /* not supported */ + return 0; +} + + +// Note, this has to match the kernel +static const uint32_t hashPriorities_watchOS[] = { + CS_HASHTYPE_SHA1 +}; + +static unsigned int hash_rank_watchOS(const CS_CodeDirectory *cd) +{ + uint32_t type = cd->hashType; + for (uint32_t n = 0; n < sizeof(hashPriorities_watchOS) / sizeof(hashPriorities_watchOS[0]); ++n) { + if (hashPriorities_watchOS[n] == type) + return n + 1; + } + + /* not supported */ + return 0; +} + +const void* MachOLoaded::findCodeDirectoryBlob(const void* codeSigStart, size_t codeSignLen) const +{ + // 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; + + // Note: The kernel currently always uses sha1 for watchOS, even if other hashes are available. + const bool isWatchOS = this->supportsPlatform(Platform::watchOS); + auto hashRankFn = isWatchOS ? &hash_rank_watchOS : &hash_rank; + + // walk each sub blob, looking at ones with type CSSLOT_CODEDIRECTORY + const CS_CodeDirectory* bestCd = nullptr; + for (uint32_t i=0; i < subBlobCount; ++i) { + if ( codeSuperBlob->index[i].type == htonl(CSSLOT_CODEDIRECTORY) ) { + // Ok, this is the regular code directory + } else if ( codeSuperBlob->index[i].type >= htonl(CSSLOT_ALTERNATE_CODEDIRECTORIES) && codeSuperBlob->index[i].type <= htonl(CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT)) { + // Ok, this is the alternative code directory + } else { + continue; + } + uint32_t cdOffset = htonl(codeSuperBlob->index[i].offset); + // verify offset is not out of range + if ( cdOffset > (codeSignLen - sizeof(CS_CodeDirectory)) ) + continue; + 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) ) + continue; + if ( cd->magic == htonl(CSMAGIC_CODEDIRECTORY) ) { + if ( !bestCd || (hashRankFn(cd) > hashRankFn(bestCd)) ) + bestCd = cd; + } + } + return bestCd; +} + + +// Regular pointer which needs to fit in 51-bits of value. +// C++ RTTI uses the top bit, so we'll allow the whole top-byte +// and the signed-extended bottom 43-bits to be fit in to 51-bits. +uint64_t MachOLoaded::ChainedFixupPointerOnDisk::signExtend51(uint64_t value51) +{ + uint64_t top8Bits = value51 & 0x007F80000000000ULL; + uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFFULL; + uint64_t newValue = (top8Bits << 13) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); + return newValue; +} + +uint64_t MachOLoaded::ChainedFixupPointerOnDisk::PlainRebase::signExtendedTarget() const +{ + return signExtend51(this->target); +} + +uint64_t MachOLoaded::ChainedFixupPointerOnDisk::PlainBind::signExtendedAddend() const +{ + uint64_t addend19 = this->addend; + if ( addend19 & 0x40000 ) + return addend19 | 0xFFFFFFFFFFFC0000ULL; + else + return addend19; +} + +const char* MachOLoaded::ChainedFixupPointerOnDisk::keyName(uint8_t keyBits) +{ + static const char* names[] = { + "IA", "IB", "DA", "DB" + }; + assert(keyBits < 4); + return names[keyBits]; +} + +const char* MachOLoaded::ChainedFixupPointerOnDisk::AuthRebase::keyName() const +{ + return ChainedFixupPointerOnDisk::keyName(this->key); +} + +const char* MachOLoaded::ChainedFixupPointerOnDisk::AuthBind::keyName() const +{ + return ChainedFixupPointerOnDisk::keyName(this->key); +} + + +uint64_t MachOLoaded::ChainedFixupPointerOnDisk::signPointer(void* loc, uint64_t target) const +{ +#if __has_feature(ptrauth_calls) + uint64_t discriminator = authBind.diversity; + if ( authBind.addrDiv ) + discriminator = __builtin_ptrauth_blend_discriminator(loc, discriminator); + switch ( authBind.key ) { + case 0: // IA + return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)target, 0, discriminator); + case 1: // IB + return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)target, 1, discriminator); + case 2: // DA + return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)target, 2, discriminator); + case 3: // DB + return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)target, 3, discriminator); + } +#endif + return target; +} + + + +} // namespace dyld3 + diff --git a/dyld3/MachOLoaded.h b/dyld3/MachOLoaded.h new file mode 100644 index 0000000..3895a70 --- /dev/null +++ b/dyld3/MachOLoaded.h @@ -0,0 +1,186 @@ +/* + * 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 MachOLoaded_h +#define MachOLoaded_h + +#include + +#include "MachOFile.h" + + +class CacheBuilder; + +namespace dyld3 { + + +// A mach-o mapped into memory with zero-fill expansion +// Can be used in dyld at runtime or during closure building +struct VIS_HIDDEN MachOLoaded : public MachOFile +{ + typedef const MachOLoaded* (^DependentToMachOLoaded)(const MachOLoaded* image, uint32_t depIndex); + + // for dlsym() + bool hasExportedSymbol(const char* symbolName, DependentToMachOLoaded finder, void** result, + bool* resultPointsToInstructions) const; + + // for DYLD_PRINT_SEGMENTS + const char* segmentName(uint32_t segIndex) const; + + // used to see if main executable overlaps shared region + bool intersectsRange(uintptr_t start, uintptr_t length) const; + + // for _dyld_get_image_slide() + intptr_t getSlide() const; + + // quick check if image has been incorporated into the dyld cache + bool inDyldCache() const { return (this->flags & 0x80000000); } + + // for dladdr() + bool findClosestSymbol(uint64_t unSlidAddr, const char** symbolName, uint64_t* symbolUnslidAddr) const; + + // for _dyld_find_unwind_sections() + const void* findSectionContent(const char* segName, const char* sectName, uint64_t& size) const; + + // used at runtime to validate loaded image matches closure + bool cdHashOfCodeSignature(const void* codeSigStart, size_t codeSignLen, uint8_t cdHash[20]) const; + + // used by DyldSharedCache to find closure + static const uint8_t* trieWalk(Diagnostics& diag, const uint8_t* start, const uint8_t* end, const char* symbol); + + // used by cache builder during error handling in chain bind processing + const char* dependentDylibLoadPath(uint32_t depIndex) const; + + // used by closure builder to find the offset and size of the trie. + bool hasExportTrie(uint32_t& runtimeOffset, uint32_t& size) const; + + + // For use with new rebase/bind scheme were each fixup location on disk contains info on what + // fix up it needs plus the offset to the next fixup. + union ChainedFixupPointerOnDisk + { + struct PlainRebase + { + uint64_t target : 51, + next : 11, + bind : 1, // 0 + auth : 1; // 0 + uint64_t signExtendedTarget() const; + }; + struct PlainBind + { + uint64_t ordinal : 16, + zero : 16, + addend : 19, + next : 11, + bind : 1, // 1 + auth : 1; // 0 + uint64_t signExtendedAddend() const; + }; + struct AuthRebase + { + uint64_t target : 32, + diversity : 16, + addrDiv : 1, + key : 2, + next : 11, + bind : 1, // 0 + auth : 1; // 1 + const char* keyName() const; + }; + struct AuthBind + { + uint64_t ordinal : 16, + zero : 16, + diversity : 16, + addrDiv : 1, + key : 2, + next : 11, + bind : 1, // 1 + auth : 1; // 1 + const char* keyName() const; + }; + + uint64_t raw; + AuthRebase authRebase; + AuthBind authBind; + PlainRebase plainRebase; + PlainBind plainBind; + + static const char* keyName(uint8_t keyBits); + static uint64_t signExtend51(uint64_t); + uint64_t signPointer(void* loc, uint64_t target) const; + }; + +protected: + friend CacheBuilder; + + struct FoundSymbol { + enum class Kind { headerOffset, absolute, resolverOffset }; + Kind kind; + bool isThreadLocal; + bool isWeakDef; + const MachOLoaded* foundInDylib; + uint64_t value; + uint32_t resolverFuncOffset; + const char* foundSymbolName; + }; + + struct LayoutInfo { + uintptr_t slide; + uintptr_t textUnslidVMAddr; + uintptr_t linkeditUnslidVMAddr; + uint32_t linkeditFileOffset; + uint32_t linkeditFileSize; + uint32_t linkeditSegIndex; + }; + + 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; + }; + + bool findExportedSymbol(Diagnostics& diag, const char* symbolName, FoundSymbol& foundInfo, DependentToMachOLoaded finder) const; + 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; + 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; + uint32_t dependentDylibCount() const; + bool findClosestFunctionStart(uint64_t address, uint64_t* functionStartAddress) const; + + const void* findCodeDirectoryBlob(const void* codeSigStart, size_t codeSignLen) const; + +}; + +} // namespace dyld3 + +#endif /* MachOLoaded_h */ diff --git a/dyld3/MachOParser.cpp b/dyld3/MachOParser.cpp deleted file mode 100644 index 92366f0..0000000 --- a/dyld3/MachOParser.cpp +++ /dev/null @@ -1,3507 +0,0 @@ -/* - * 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; - } - // 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 deleted file mode 100644 index 73b5ead..0000000 --- a/dyld3/MachOParser.h +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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 index 2516175..7e3b1f8 100644 --- a/dyld3/PathOverrides.cpp +++ b/dyld3/PathOverrides.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,7 @@ namespace dyld3 { +namespace closure { #if BUILDING_LIBDYLD PathOverrides gPathOverrides; @@ -55,79 +57,39 @@ static const char* strrstr(const char* str, const char* sub) return NULL; } + +void PathOverrides::setFallbackPathHandling(FallbackPathMode mode) +{ + _fallbackPathMode = mode; +} -#if DYLD_IN_PROCESS -void PathOverrides::setEnvVars(const char* envp[]) +void PathOverrides::setEnvVars(const char* envp[], const MachOFile* mainExe, const char* mainExePath) { for (const char** p = envp; *p != NULL; p++) { addEnvVar(*p); } + if ( mainExe != nullptr ) + setMainExecutable(mainExe, mainExePath); } -#else -PathOverrides::PathOverrides(const std::vector& env) +void PathOverrides::setMainExecutable(const dyld3::MachOFile* mainExe, const char* mainExePath) { - for (const std::string& envVar : env) { - addEnvVar(envVar.c_str()); - } + assert(mainExe != nullptr); + assert(mainExe->isMainExecutable()); + // process any LC_DYLD_ENVIRONMENT load commands in main executable + mainExe->forDyldEnv(^(const char* envVar, bool& stop) { + addEnvVar(envVar); + }); } -#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; @@ -150,112 +112,150 @@ uint32_t PathOverrides::envVarCount() const void PathOverrides::forEachInsertedDylib(void (^handler)(const char* dylibPath)) const { - if ( _insertedDylibs == nullptr ) + if ( _insertedDylibs != nullptr ) { + forEachInColonList(_insertedDylibs, ^(const char* path, bool &stop) { + handler(path); + }); + } +} + +void PathOverrides::handleEnvVar(const char* key, const char* value, void (^handler)(const char* envVar)) const +{ + if ( value == nullptr ) return; - for (const char** lp=_insertedDylibs; *lp != nullptr; ++lp) - handler(*lp); + 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::forEachEnvVar(void (^handler)(const char* envVar)) const +{ + handleEnvVar("DYLD_LIBRARY_PATH", _dylibPathOverrides, handler); + handleEnvVar("DYLD_FRAMEWORK_PATH", _frameworkPathOverrides, handler); + handleEnvVar("DYLD_FALLBACK_FRAMEWORK_PATH", _frameworkPathFallbacks, handler); + handleEnvVar("DYLD_FALLBACK_LIBRARY_PATH", _dylibPathFallbacks, handler); + handleEnvVar("DYLD_INSERT_LIBRARIES", _insertedDylibs, handler); + handleEnvVar("DYLD_IMAGE_SUFFIX", _imageSuffix, handler); + handleEnvVar("DYLD_ROOT_PATH", _rootPath, handler); +} + +const char* PathOverrides::addString(const char* str) +{ + if ( _pathPool == nullptr ) + _pathPool = PathPool::allocate(); + return _pathPool->add(str); +} + +void PathOverrides::setString(const char*& var, const char* value) +{ + if ( var == nullptr ) { + var = addString(value); + return; + } + // string already in use, build new appended string + char tmp[strlen(var)+strlen(value)+2]; + strcpy(tmp, var); + strcat(tmp, ":"); + strcat(tmp, value); + var = addString(tmp); } void PathOverrides::addEnvVar(const char* keyEqualsValue) { + // We have to make a copy of the env vars because the dyld + // semantics is that the env vars are only looked at once + // at launch (using setenv() at runtime does not change dyld behavior). 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); + if ( strncmp(keyEqualsValue, "DYLD_LIBRARY_PATH", 17) == 0 ) { + setString(_dylibPathOverrides, &keyEqualsValue[18]); } - else if ( strcmp(key, "DYLD_FRAMEWORK_PATH") == 0 ) { - _frameworkPathOverrides = parseColonListIntoArray(value); + else if ( strncmp(keyEqualsValue, "DYLD_FRAMEWORK_PATH", 19) == 0 ) { + setString(_frameworkPathOverrides, &keyEqualsValue[20]); } - else if ( strcmp(key, "DYLD_FALLBACK_FRAMEWORK_PATH") == 0 ) { - _frameworkPathFallbacks = parseColonListIntoArray(value); + else if ( strncmp(keyEqualsValue, "DYLD_FALLBACK_FRAMEWORK_PATH", 28) == 0 ) { + setString(_frameworkPathFallbacks, &keyEqualsValue[29]); } - else if ( strcmp(key, "DYLD_FALLBACK_LIBRARY_PATH") == 0 ) { - _dylibPathFallbacks = parseColonListIntoArray(value); + else if ( strncmp(keyEqualsValue, "DYLD_FALLBACK_LIBRARY_PATH", 26) == 0 ) { + setString(_dylibPathFallbacks, &keyEqualsValue[27]); } - else if ( strcmp(key, "DYLD_INSERT_LIBRARIES") == 0 ) { - _insertedDylibs = parseColonListIntoArray(value); + else if ( strncmp(keyEqualsValue, "DYLD_INSERT_LIBRARIES", 21) == 0 ) { + setString(_insertedDylibs, &keyEqualsValue[22]); } - else if ( strcmp(key, "DYLD_IMAGE_SUFFIX") == 0 ) { - _imageSuffix = value; + else if ( strncmp(keyEqualsValue, "DYLD_IMAGE_SUFFIX", 17) == 0 ) { + setString(_imageSuffix, &keyEqualsValue[18]); } - else if ( strcmp(key, "DYLD_ROOT_PATH") == 0 ) { - _rootPath = value; + else if ( strncmp(keyEqualsValue, "DYLD_ROOT_PATH", 14) == 0 ) { + setString(_rootPath, &keyEqualsValue[15]); } } } -void PathOverrides::forEachInColonList(const char* list, void (^handler)(const char* path)) +void PathOverrides::forEachInColonList(const char* list, void (^handler)(const char* path, bool& stop)) { char buffer[strlen(list)+1]; const char* t = list; + bool stop = false; 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); + handler(buffer, stop); + if ( stop ) + return; 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); + handler(t, stop); } void PathOverrides::forEachDylibFallback(Platform platform, void (^handler)(const char* fallbackDir, bool& stop)) const { - bool stop = false; + __block bool stop = false; if ( _dylibPathFallbacks != nullptr ) { - for (const char** fp=_dylibPathFallbacks; *fp != nullptr; ++fp) { - handler(*fp, stop); - if ( stop ) - return; - } + forEachInColonList(_dylibPathFallbacks, ^(const char* pth, bool& innerStop) { + handler(pth, innerStop); + if ( innerStop ) + stop = true; + }); } else { switch ( platform ) { case Platform::macOS: - // "$HOME/lib" - handler("/usr/local/lib", stop); // FIXME: not for restricted processes - if ( !stop ) - handler("/usr/lib", stop); + switch ( _fallbackPathMode ) { + case FallbackPathMode::classic: + // "$HOME/lib" + handler("/usr/local/lib", stop); + if ( stop ) + break; + // fall thru + case FallbackPathMode::restricted: + handler("/usr/lib", stop); + break; + case FallbackPathMode::none: + break; + } break; case Platform::iOS: case Platform::watchOS: case Platform::tvOS: case Platform::bridgeOS: case Platform::unknown: - handler("/usr/local/lib", stop); - if ( !stop ) + if ( _fallbackPathMode != FallbackPathMode::none ) { + handler("/usr/local/lib", stop); + if ( stop ) + break; + } + // fall into /usr/lib case + case Platform::iOSMac: + case Platform::iOS_simulator: + case Platform::watchOS_simulator: + case Platform::tvOS_simulator: + if ( _fallbackPathMode != FallbackPathMode::none ) handler("/usr/lib", stop); break; } @@ -264,43 +264,100 @@ void PathOverrides::forEachDylibFallback(Platform platform, void (^handler)(cons void PathOverrides::forEachFrameworkFallback(Platform platform, void (^handler)(const char* fallbackDir, bool& stop)) const { - bool stop = false; + __block bool stop = false; if ( _frameworkPathFallbacks != nullptr ) { - for (const char** fp=_frameworkPathFallbacks; *fp != nullptr; ++fp) { - handler(*fp, stop); - if ( stop ) - return; - } + forEachInColonList(_frameworkPathFallbacks, ^(const char* pth, bool& innerStop) { + handler(pth, innerStop); + if ( innerStop ) + stop = true; + }); } 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); + switch ( _fallbackPathMode ) { + case FallbackPathMode::classic: + // "$HOME/Library/Frameworks" + handler("/Library/Frameworks", stop); + if ( stop ) + break; + // "/Network/Library/Frameworks" + // fall thru + case FallbackPathMode::restricted: + handler("/System/Library/Frameworks", stop); + break; + case FallbackPathMode::none: + break; + } break; case Platform::iOS: case Platform::watchOS: case Platform::tvOS: case Platform::bridgeOS: + case Platform::iOSMac: + case Platform::iOS_simulator: + case Platform::watchOS_simulator: + case Platform::tvOS_simulator: case Platform::unknown: - handler("/System/Library/Frameworks", stop); + if ( _fallbackPathMode != FallbackPathMode::none ) + 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 + +// +// copy path and add suffix to result +// +// /path/foo.dylib _debug => /path/foo_debug.dylib +// foo.dylib _debug => foo_debug.dylib +// foo _debug => foo_debug +// /path/bar _debug => /path/bar_debug +// /path/bar.A.dylib _debug => /path/bar.A_debug.dylib +// +void PathOverrides::addSuffix(const char* path, const char* suffix, char* result) const +{ + strcpy(result, path); + + // find last slash + char* start = strrchr(result, '/'); + if ( start != NULL ) + start++; + else + start = result; + + // find last dot after last slash + char* dot = strrchr(start, '.'); + if ( dot != NULL ) { + strcpy(dot, suffix); + strcat(&dot[strlen(suffix)], &path[dot-result]); + } + else { + strcat(result, suffix); + } +} + +void PathOverrides::forEachImageSuffix(const char* path, bool isFallbackPath, bool& stop, void (^handler)(const char* possiblePath, bool isFallbackPath, bool& stop)) const +{ + if ( _imageSuffix == nullptr ) { + handler(path, isFallbackPath, stop); + } + else { + forEachInColonList(_imageSuffix, ^(const char* suffix, bool& innerStop) { + char npath[strlen(path)+strlen(suffix)+8]; + addSuffix(path, suffix, npath); + handler(npath, isFallbackPath, innerStop); + if ( innerStop ) + stop = true; + }); + if ( !stop ) + handler(path, isFallbackPath, stop); + } +} + +void PathOverrides::forEachPathVariant(const char* initialPath, void (^handler)(const char* possiblePath, bool isFallbackPath, bool& stop), Platform platform) const { -#if DYLD_IN_PROCESS - Platform platform = MachOParser::currentPlatform(); -#endif __block bool stop = false; // check for overrides @@ -309,15 +366,15 @@ void PathOverrides::forEachPathVariant(const char* initialPath, 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); + forEachInColonList(_frameworkPathOverrides, ^(const char* frDir, bool &innerStop) { + char npath[strlen(frDir)+frameworkPartialPathLen+8]; + strcpy(npath, frDir); strcat(npath, "/"); strcat(npath, frameworkPartialPath); - handler(npath, stop); - if ( stop ) - return; - } + forEachImageSuffix(npath, false, innerStop, handler); + if ( innerStop ) + stop = true; + }); } } else { @@ -325,20 +382,22 @@ void PathOverrides::forEachPathVariant(const char* 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; - } + forEachInColonList(_dylibPathOverrides, ^(const char* libDir, bool &innerStop) { + char npath[strlen(libDir)+libraryLeafNameLen+8]; + strcpy(npath, libDir); + strcat(npath, "/"); + strcat(npath, libraryLeafName); + forEachImageSuffix(npath, false, innerStop, handler); + if ( innerStop ) + stop = true; + }); } } + if ( stop ) + return; // try original path - handler(initialPath, stop); + forEachImageSuffix(initialPath, false, stop, handler); if ( stop ) return; @@ -346,14 +405,15 @@ void PathOverrides::forEachPathVariant(const char* initialPath, if ( frameworkPartialPath != nullptr ) { const size_t frameworkPartialPathLen = strlen(frameworkPartialPath); // look at each DYLD_FALLBACK_FRAMEWORK_PATH directory + bool usesDefaultFallbackPaths = (_frameworkPathFallbacks == nullptr); forEachFrameworkFallback(platform, ^(const char* dir, bool& innerStop) { char npath[strlen(dir)+frameworkPartialPathLen+8]; strcpy(npath, dir); strcat(npath, "/"); strcat(npath, frameworkPartialPath); - handler(npath, innerStop); + forEachImageSuffix(npath, usesDefaultFallbackPaths, innerStop, handler); if ( innerStop ) - stop = innerStop; + stop = true; }); } @@ -361,14 +421,15 @@ void PathOverrides::forEachPathVariant(const char* initialPath, const char* libraryLeafName = getLibraryLeafName(initialPath); const size_t libraryLeafNameLen = strlen(libraryLeafName); // look at each DYLD_FALLBACK_LIBRARY_PATH directory + bool usesDefaultFallbackPaths = (_dylibPathFallbacks == nullptr); forEachDylibFallback(platform, ^(const char* dir, bool& innerStop) { char libpath[strlen(dir)+libraryLeafNameLen+8]; strcpy(libpath, dir); strcat(libpath, "/"); strcat(libpath, libraryLeafName); - handler(libpath, innerStop); + forEachImageSuffix(libpath, usesDefaultFallbackPaths, innerStop, handler); if ( innerStop ) - stop = innerStop; + stop = true; }); } } @@ -428,6 +489,59 @@ const char* PathOverrides::getLibraryLeafName(const char* path) return path; } + + +//////////////////////////// PathPool //////////////////////////////////////// + + +PathPool* PathPool::allocate() +{ + vm_address_t addr; + ::vm_allocate(mach_task_self(), &addr, kAllocationSize, VM_FLAGS_ANYWHERE); + PathPool* p = (PathPool*)addr; + p->_next = nullptr; + p->_current = &(p->_buffer[0]); + p->_bytesFree = kAllocationSize - sizeof(PathPool); + return p; +} + +void PathPool::deallocate(PathPool* pool) { + do { + PathPool* next = pool->_next; + ::vm_deallocate(mach_task_self(), (vm_address_t)pool, kAllocationSize); + pool = next; + } while (pool); +} + +const char* PathPool::add(const char* path) +{ + size_t len = strlen(path) + 1; + if ( len < _bytesFree ) { + char* result = _current; + strcpy(_current, path); + _current += len; + _bytesFree -= len; + return result; + } + if ( _next == nullptr ) + _next = allocate(); + return _next->add(path); +} + +void PathPool::forEachPath(void (^handler)(const char* path)) +{ + for (const char* s = _buffer; s < _current; ++s) { + handler(s); + s += strlen(s); + } + + if ( _next != nullptr ) + _next->forEachPath(handler); +} + + + +} // namespace closure } // namespace dyld3 diff --git a/dyld3/PathOverrides.h b/dyld3/PathOverrides.h index 4ae7407..8b0fbff 100644 --- a/dyld3/PathOverrides.h +++ b/dyld3/PathOverrides.h @@ -28,16 +28,31 @@ #include -#if !DYLD_IN_PROCESS -#include -#include -#endif - #include "Logging.h" -#include "MachOParser.h" +#include "MachOFile.h" namespace dyld3 { +namespace closure { + + +class VIS_HIDDEN PathPool +{ +public: + static PathPool* allocate(); + static void deallocate(PathPool* pool); + const char* add(const char* path); + void forEachPath(void (^handler)(const char* path)); + +private: + enum { kAllocationSize = 32*1024 }; + + PathPool* _next; + char* _current; + size_t _bytesFree; + char _buffer[]; +}; + class VIS_HIDDEN PathOverrides { @@ -46,23 +61,22 @@ public: // libdyld is never unloaded ~PathOverrides(); #endif + enum class FallbackPathMode { classic, restricted, none }; -#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 + void setFallbackPathHandling(FallbackPathMode mode); + void setEnvVars(const char* envp[], const dyld3::MachOFile* mainExe, const char* mainExePath); + void setMainExecutable(const dyld3::MachOFile* mainExe, const char* mainExePath); + void forEachPathVariant(const char* requestedPath, void (^handler)(const char* possiblePath, bool isFallbackPath, bool& stop), + Platform plat=MachOFile::currentPlatform()) const; 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 setString(const char*& var, const char* value); + const char* addString(const char* str); + static void forEachInColonList(const char* list, void (^callback)(const char* path, bool& stop)); void addEnvVar(const char* keyEqualsValue); const char* getFrameworkPartialPath(const char* path) const; static const char* getLibraryLeafName(const char* path); @@ -70,14 +84,18 @@ private: 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; + void forEachImageSuffix(const char* path, bool isFallbackPath, bool& stop, void (^handler)(const char* possiblePath, bool isFallbackPath, bool& stop)) const; + void addSuffix(const char* path, const char* suffix, char* result) const; + + PathPool* _pathPool = nullptr; + 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 + FallbackPathMode _fallbackPathMode = FallbackPathMode::classic; }; #if BUILDING_LIBDYLD @@ -85,6 +103,7 @@ extern PathOverrides gPathOverrides; #endif +} // namespace closure } // namespace dyld3 #endif // __DYLD_PATH_OVERRIDES_H__ diff --git a/dyld3/SharedCacheRuntime.cpp b/dyld3/SharedCacheRuntime.cpp index 993ed54..c54c07f 100644 --- a/dyld3/SharedCacheRuntime.cpp +++ b/dyld3/SharedCacheRuntime.cpp @@ -45,8 +45,6 @@ #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 @@ -102,13 +100,18 @@ struct CacheInfo #define ARCH_NAME "arm64e" #define ARCH_CACHE_MAGIC "dyld_v1 arm64e" #elif __arm64__ - #define ARCH_NAME "arm64" - #define ARCH_CACHE_MAGIC "dyld_v1 arm64" + #if __LP64__ + #define ARCH_NAME "arm64" + #define ARCH_CACHE_MAGIC "dyld_v1 arm64" + #else + #define ARCH_NAME "arm64_32" + #define ARCH_CACHE_MAGIC "dyld_v1arm64_32" + #endif #endif -static void rebaseChain(uint8_t* pageContent, uint16_t startOffset, uintptr_t slideAmount, const dyld_cache_slide_info2* slideInfo) +static void rebaseChainV2(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; @@ -132,6 +135,38 @@ static void rebaseChain(uint8_t* pageContent, uint16_t startOffset, uintptr_t sl } } +#if !__LP64__ +static void rebaseChainV4(uint8_t* pageContent, uint16_t startOffset, uintptr_t slideAmount, const dyld_cache_slide_info4* 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 & 0xFFFF8000) == 0 ) { + // small positive non-pointer, use as-is + } + else if ( (value & 0x3FFF8000) == 0x3FFF8000 ) { + // small negative non-pointer + value |= 0xC0000000; + } + else { + 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; + } +} +#endif static void getCachePath(const SharedCacheOptions& options, size_t pathBufferSize, char pathBuffer[]) { @@ -168,10 +203,11 @@ static void getCachePath(const SharedCacheOptions& options, size_t pathBufferSiz struct stat enableStatBuf; struct stat devCacheStatBuf; struct stat optCacheStatBuf; + bool developmentDevice = dyld3::internalInstall(); 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 ) + if ( developmentDevice && ((enableFileExists && (enableStatBuf.st_size < ENABLE_DYLIBS_TO_OVERRIDE_CACHE_SIZE) && devCacheExists) || !optCacheExists) ) strlcat(pathBuffer, DYLD_SHARED_CACHE_DEVELOPMENT_EXT, pathBufferSize); #endif @@ -205,7 +241,7 @@ static bool validPlatform(const SharedCacheOptions& options, const DyldSharedCac if ( cache->header.mappingOffset < 0xE0 ) return true; - if ( cache->header.platform != (uint32_t)MachOParser::currentPlatform() ) + if ( cache->header.platform != (uint32_t)MachOFile::currentPlatform() ) return false; #if TARGET_IPHONE_SIMULATOR @@ -240,6 +276,7 @@ static bool preflightCacheFile(const SharedCacheOptions& options, SharedCacheLoa 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"; @@ -266,7 +303,7 @@ static bool preflightCacheFile(const SharedCacheOptions& options, SharedCacheLoa ::close(fd); return false; } - if ( (cache->header.mappingCount != 3) || (cache->header.mappingOffset > 0x120) ) { + if ( (cache->header.mappingCount != 3) || (cache->header.mappingOffset > 0x138) ) { results->errorMessage = "shared cache file mappings are invalid"; ::close(fd); return false; @@ -327,7 +364,7 @@ static bool preflightCacheFile(const SharedCacheOptions& options, SharedCacheLoa return false; } if ( memcmp(mappedData, firstPage, sizeof(firstPage)) != 0 ) { - results->errorMessage = "first page of shared cache not mmap()able"; + results->errorMessage = "first page of mmap()ed shared cache not valid"; ::close(fd); return false; } @@ -363,7 +400,120 @@ static bool preflightCacheFile(const SharedCacheOptions& options, SharedCacheLoa return true; } + #if !TARGET_IPHONE_SIMULATOR + +// update all __DATA pages with slide info +static bool rebaseDataPages(bool isVerbose, CacheInfo& info, SharedCacheLoadInfo* results) +{ + uint64_t dataPagesStart = info.mappings[1].sfm_address; + const dyld_cache_slide_info* slideInfo = nullptr; + if ( info.slideInfoSize != 0 ) { + slideInfo = (dyld_cache_slide_info*)(info.slideInfoAddressUnslid + results->slide); + } + const dyld_cache_slide_info* slideInfoHeader = (dyld_cache_slide_info*)slideInfo; + if ( slideInfoHeader != nullptr ) { + 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); + 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); + rebaseChainV2(page, pageStartOffset, results->slide, slideHeader); + done = (pInfo & DYLD_CACHE_SLIDE_PAGE_ATTR_END); + ++chainIndex; + } + } + else { + uint32_t pageOffset = pageEntry * 4; + //dyld::log(" start pageOffset=0x%03X\n", pageOffset); + rebaseChainV2(page, pageOffset, results->slide, slideHeader); + } + } + } +#if __LP64__ + else if ( slideInfoHeader->version == 3 ) { + const dyld_cache_slide_info3* slideHeader = (dyld_cache_slide_info3*)slideInfo; + const uint32_t pageSize = slideHeader->page_size; + for (int i=0; i < slideHeader->page_starts_count; ++i) { + uint8_t* page = (uint8_t*)(dataPagesStart + (pageSize*i)); + uint64_t delta = slideHeader->page_starts[i]; + if ( delta == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE ) + continue; + delta = delta/sizeof(uint64_t); // initial offset is byte based + dyld_cache_slide_pointer3* loc = (dyld_cache_slide_pointer3*)page; + do { + loc += delta; + delta = loc->plain.offsetToNextPointer; + if ( loc->auth.authenticated ) { +#if __has_feature(ptrauth_calls) + uint64_t target = info.sharedRegionStart + loc->auth.offsetFromSharedCacheBase + results->slide; + MachOLoaded::ChainedFixupPointerOnDisk ptr; + ptr.raw = *((uint64_t*)loc); + loc->raw = ptr.signPointer(loc, target); +#else + results->errorMessage = "invalid pointer kind in cache file"; + return false; +#endif + } + else { + loc->raw = MachOLoaded::ChainedFixupPointerOnDisk::signExtend51(loc->plain.pointerValue) + results->slide; + } + } while (delta != 0); + } + } +#else + else if ( slideInfoHeader->version == 4 ) { + const dyld_cache_slide_info4* slideHeader = (dyld_cache_slide_info4*)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); + 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_SLIDE4_PAGE_NO_REBASE ) + continue; + if ( pageEntry & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA ) { + uint16_t chainIndex = (pageEntry & DYLD_CACHE_SLIDE4_PAGE_INDEX); + bool done = false; + while ( !done ) { + uint16_t pInfo = page_extras[chainIndex]; + uint16_t pageStartOffset = (pInfo & DYLD_CACHE_SLIDE4_PAGE_INDEX)*4; + //dyld::log(" chain[%d] pageOffset=0x%03X\n", chainIndex, pageStartOffset); + rebaseChainV4(page, pageStartOffset, results->slide, slideHeader); + done = (pInfo & DYLD_CACHE_SLIDE4_PAGE_EXTRA_END); + ++chainIndex; + } + } + else { + uint32_t pageOffset = pageEntry * 4; + //dyld::log(" start pageOffset=0x%03X\n", pageOffset); + rebaseChainV4(page, pageOffset, results->slide, slideHeader); + } + } + } +#endif // LP64 + else { + results->errorMessage = "invalid slide info in cache file"; + return false; + } + } + return true; +} + static bool reuseExistingCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results) { uint64_t cacheBaseAddress; @@ -377,10 +527,6 @@ static bool reuseExistingCache(const SharedCacheOptions& options, SharedCacheLoa 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 ) { @@ -413,7 +559,7 @@ static long pickCacheASLR(CacheInfo& info) #endif // respect -disable_aslr boot-arg - if ( dyld3::loader::bootArgsContains("-disable_aslr") ) + if ( dyld3::bootArgsContains("-disable_aslr") ) slide = 0; // update mappings @@ -435,10 +581,6 @@ static bool mapCacheSystemWide(const SharedCacheOptions& options, SharedCacheLoa 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); @@ -450,7 +592,8 @@ static bool mapCacheSystemWide(const SharedCacheOptions& options, SharedCacheLoa 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"; + if ( results->errorMessage == nullptr ) + results->errorMessage = "syscall to map cache into shared region failed"; return false; } @@ -460,7 +603,7 @@ static bool mapCacheSystemWide(const SharedCacheOptions& options, SharedCacheLoa } return true; } -#endif +#endif // TARGET_IPHONE_SIMULATOR static bool mapCachePrivate(const SharedCacheOptions& options, SharedCacheLoadInfo* results) { @@ -471,18 +614,12 @@ static bool mapCachePrivate(const SharedCacheOptions& options, SharedCacheLoadIn // 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); @@ -506,55 +643,22 @@ static bool mapCachePrivate(const SharedCacheOptions& options, SharedCacheLoadIn 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 TARGET_IPHONE_SIMULATOR // simulator caches do not support sliding + return true; +#else + bool success = rebaseDataPages(options.verbose, info, results); if ( options.verbose ) { dyld::log("mapped dyld cache file private to process (%s):\n", results->path); verboseSharedCacheMappings(info.mappings); } - return true; + return success; +#endif } @@ -563,7 +667,6 @@ bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* resul { results->loadAddress = 0; results->slide = 0; - results->cachedDylibsGroup = nullptr; results->errorMessage = nullptr; #if TARGET_IPHONE_SIMULATOR @@ -576,11 +679,14 @@ bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* resul } 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); + bool hasError = false; + if ( reuseExistingCache(options, results) ) { + hasError = (results->errorMessage != nullptr); + } else { + // slow path: this is first process to load cache + hasError = mapCacheSystemWide(options, results); + } + return hasError; } #endif } @@ -591,56 +697,84 @@ bool findInSharedCacheImage(const SharedCacheLoadInfo& loadInfo, const char* dyl 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) ) { + if ( loadInfo.loadAddress->header.formatVersion != dyld3::closure::kFormatVersion ) { + // support for older cache with a different Image* format +#if __IPHONE_OS_VERSION_MIN_REQUIRED + uint64_t hash = 0; + for (const char* s=dylibPathToFind; *s != '\0'; ++s) + hash += hash*4 + *s; +#endif 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) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED + // on iOS, inode is used to hold hash of path + if ( (p->modTime == 0) && (p->inode != hash) ) + continue; +#endif 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; + results->image = 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); + const dyld3::closure::ImageArray* images = loadInfo.loadAddress->cachedDylibsImageArray(); + results->image = nullptr; + uint32_t imageIndex; + if ( loadInfo.loadAddress->hasImagePath(dylibPathToFind, imageIndex) ) { + results->image = images->imageForNum(imageIndex+1); + } + #if __MAC_OS_X_VERSION_MIN_REQUIRED + else { + // handle symlink to cached dylib + if ( loadInfo.loadAddress->header.dylibsExpectedOnDisk ) { + struct stat statBuf; + if ( dyld::my_stat(dylibPathToFind, &statBuf) == 0 ) { + // on macOS we store the inode and mtime of each dylib in the cache in the dyld_cache_image_info array + 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) { + if ( (p->inode == statBuf.st_ino) && (p->modTime == statBuf.st_mtime) ) { + imageIndex = (uint32_t)(p - start); + results->image = images->imageForNum(imageIndex+1); + break; + } + } + } + } + else { + char resolvedPath[PATH_MAX]; + if ( realpath(dylibPathToFind, resolvedPath) != nullptr ) { + if ( loadInfo.loadAddress->hasImagePath(resolvedPath, imageIndex) ) { + results->image = images->imageForNum(imageIndex+1); + } + } + } } #endif - if ( imageData == nullptr ) + if ( results->image == nullptr ) return false; - launch_cache::Image image(imageData); - results->mhInCache = (const mach_header*)((uintptr_t)loadInfo.loadAddress + image.cacheOffset()); - results->pathInCache = image.path(); + results->mhInCache = (const mach_header*)((uintptr_t)loadInfo.loadAddress + results->image->cacheOffset()); + results->pathInCache = results->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) ) + if ( (loadInfo.loadAddress == nullptr) || (loadInfo.loadAddress->header.formatVersion != closure::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); + uint32_t imageIndex; + return loadInfo.loadAddress->hasImagePath(dylibPathToFind, imageIndex); } diff --git a/dyld3/SharedCacheRuntime.h b/dyld3/SharedCacheRuntime.h index dbd4595..402ce54 100644 --- a/dyld3/SharedCacheRuntime.h +++ b/dyld3/SharedCacheRuntime.h @@ -41,23 +41,22 @@ struct SharedCacheOptions { }; struct SharedCacheLoadInfo { - const DyldSharedCache* loadAddress; - long slide; - const launch_cache::binary_format::ImageGroup* cachedDylibsGroup; - const char* errorMessage; - char path[256]; + const DyldSharedCache* loadAddress; + long slide; + 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; + const mach_header* mhInCache; + const char* pathInCache; + long slideInCache; + const closure::Image* image; }; - bool findInSharedCacheImage(const SharedCacheLoadInfo& loadInfo, const char* dylibPathToFind, SharedCacheFindDylibResults* results); bool pathIsInSharedCacheImage(const SharedCacheLoadInfo& loadInfo, const char* dylibPathToFind); diff --git a/dyld3/StartGlue.h b/dyld3/StartGlue.h new file mode 100644 index 0000000..6ef4a27 --- /dev/null +++ b/dyld3/StartGlue.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013 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 __START_GLUE_H__ +#define __START_GLUE_H__ + +// Implemented in start_glue.s +// Declare 'start' as a character, so that we can index into it. +// Avoid arithmetic on function pointers. +extern "C" char start; + + +// need 'start' to be one atom, but entry is in interior + +#if __x86_64__ || __i386__ + #define address_of_start (void*)((uintptr_t)&start + 1) +#elif __arm64__ + #define address_of_start (void*)((uintptr_t)&start + 4) +#elif __arm__ + #define address_of_start (void*)((uintptr_t)&start + 2) +#endif + + + +#endif // __START_GLUE_H__ diff --git a/dyld3/SupportedArchs.h b/dyld3/SupportedArchs.h new file mode 100644 index 0000000..425c23f --- /dev/null +++ b/dyld3/SupportedArchs.h @@ -0,0 +1,30 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _DYLD_SUPPORTED_ARCHS_H_ +#define _DYLD_SUPPORTED_ARCHS_H_ + + + +#endif // _DYLD_SUPPORTED_ARCHS_H_ diff --git a/dyld3/Tracing.cpp b/dyld3/Tracing.cpp index ff15311..430e3aa 100644 --- a/dyld3/Tracing.cpp +++ b/dyld3/Tracing.cpp @@ -69,38 +69,71 @@ void kdebug_trace_dyld_image(const uint32_t code, #endif /* __LP64__ */ } +// FIXME +// We get distinct copies of this in libdyld and dyld. Eventually we can fix it, +// for now we will just offset the values. + +#if BUILDING_DYLD +static std::atomic trace_pair_id(0); +#else +static std::atomic trace_pair_id(1LL<<63); +#endif + 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); - } +bool kdebug_trace_dyld_enabled(uint32_t code) { + return kdebug_is_enabled(code); } -static std::atomic trace_pair_id(0); +VIS_HIDDEN +void kdebug_trace_dyld_marker(uint32_t code, kt_arg data1, kt_arg data2, kt_arg data3, kt_arg data4) { + if (kdebug_is_enabled(code)) { + data1.prepare(code); + data2.prepare(code); + data3.prepare(code); + data4.prepare(code); + kdebug_trace(code, data1.value(), data2.value(), data3.value(), data4.value()); + data4.destroy(code); + data3.destroy(code); + data2.destroy(code); + data1.destroy(code); + } +} 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(); +uint64_t kdebug_trace_dyld_duration_start(uint32_t code, kt_arg data1, kt_arg data2, kt_arg data3) { + uint64_t result = 0; + if (kdebug_is_enabled(code)) { + result = ++trace_pair_id; + data1.prepare(code); + data2.prepare(code); + data3.prepare(code); + kdebug_trace(code | DBG_FUNC_START, result, data1.value(), data2.value(), data3.value()); + data3.destroy(code); + data2.destroy(code); + data1.destroy(code); } + return result; } -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); +VIS_HIDDEN +void kdebug_trace_dyld_duration_end(uint64_t trace_id, uint32_t code, kt_arg data1, kt_arg data2, kt_arg data3) { + if (trace_id != 0 && kdebug_is_enabled(code)) { + data1.prepare(code); + data2.prepare(code); + data3.prepare(code); + kdebug_trace(code | DBG_FUNC_END, trace_id, data1.value(), data2.value(), data3.value()); + data3.destroy(code); + data2.destroy(code); + data1.destroy(code); } } + +void ScopedTimer::startTimer() { + current_trace_id = kdebug_trace_dyld_duration_start(code, data1, data2, data3); +} + +void ScopedTimer::endTimer() { + kdebug_trace_dyld_duration_end(current_trace_id, code, data4, data5, data6); +} + }; diff --git a/dyld3/Tracing.h b/dyld3/Tracing.h index b127aa9..cf315ca 100644 --- a/dyld3/Tracing.h +++ b/dyld3/Tracing.h @@ -34,42 +34,99 @@ #include #include -#ifndef DBG_DYLD_SIGNPOST - #define DBG_DYLD_SIGNPOST (6) -#endif +#define DBG_DYLD_INTERNAL_SUBCLASS (7) +#define DBG_DYLD_API_SUBCLASS (8) +#define DBG_DYLD_DEBUGGING_SUBCLASS (9) + +#define DBG_DYLD_TIMING_STATIC_INITIALIZER (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 0)) +#define DBG_DYLD_TIMING_LAUNCH_EXECUTABLE (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 1)) +#define DBG_DYLD_TIMING_MAP_IMAGE (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 2)) +#define DBG_DYLD_TIMING_APPLY_FIXUPS (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 3)) +#define DBG_DYLD_TIMING_ATTACH_CODESIGNATURE (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 4)) +#define DBG_DYLD_TIMING_BUILD_CLOSURE (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 5)) +#define DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 6)) +#define DBG_DYLD_TIMING_FUNC_FOR_REMOVE_IMAGE (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 7)) +#define DBG_DYLD_TIMING_OBJC_INIT (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 8)) +#define DBG_DYLD_TIMING_OBJC_MAP (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 9)) +#define DBG_DYLD_TIMING_APPLY_INTERPOSING (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 10)) +#define DBG_DYLD_GDB_IMAGE_NOTIFIER (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 11)) +#define DBG_DYLD_REMOTE_IMAGE_NOTIFIER (KDBG_CODE(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 12)) + +#define DBG_DYLD_TIMING_DLOPEN (KDBG_CODE(DBG_DYLD, DBG_DYLD_API_SUBCLASS, 0)) +#define DBG_DYLD_TIMING_DLOPEN_PREFLIGHT (KDBG_CODE(DBG_DYLD, DBG_DYLD_API_SUBCLASS, 1)) +#define DBG_DYLD_TIMING_DLCLOSE (KDBG_CODE(DBG_DYLD, DBG_DYLD_API_SUBCLASS, 2)) +#define DBG_DYLD_TIMING_DLSYM (KDBG_CODE(DBG_DYLD, DBG_DYLD_API_SUBCLASS, 3)) +#define DBG_DYLD_TIMING_DLADDR (KDBG_CODE(DBG_DYLD, DBG_DYLD_API_SUBCLASS, 4)) + +#define DBG_DYLD_DEBUGGING_VM_REMAP (KDBG_CODE(DBG_DYLD, DBG_DYLD_DEBUGGING_SUBCLASS, 0)) +#define DBG_DYLD_DEBUGGING_VM_UNMAP (KDBG_CODE(DBG_DYLD, DBG_DYLD_DEBUGGING_SUBCLASS, 1)) +#define DBG_DYLD_DEBUGGING_MAP_LOOP (KDBG_CODE(DBG_DYLD, DBG_DYLD_DEBUGGING_SUBCLASS, 2)) +#define DBG_DYLD_DEBUGGING_MARK (KDBG_CODE(DBG_DYLD, DBG_DYLD_DEBUGGING_SUBCLASS, 3)) -#ifndef DBG_DYLD_TIMING - #define DBG_DYLD_TIMING (7) -#endif -#ifndef DBG_DYLD_PRINT - #define DBG_DYLD_PRINT (8) -#endif +#define VIS_HIDDEN __attribute__((visibility("hidden"))) -#ifndef DBG_DYLD_SIGNPOST_START_DYLD - #define DBG_DYLD_SIGNPOST_START_DYLD (0) -#endif +namespace dyld3 { -#ifndef DBG_DYLD_SIGNPOST_START_MAIN - #define DBG_DYLD_SIGNPOST_START_MAIN (1) +struct VIS_HIDDEN kt_arg { + kt_arg(int value) : _value(value), _str(nullptr) {} + kt_arg(uint64_t value) : _value(value), _str(nullptr) {} + kt_arg(const char *value) : _value(0), _str(value) {} + kt_arg(void *value) : _value((uint64_t)value), _str(nullptr) {} + uint64_t value() const { return _value; } +private: + void prepare(uint32_t code) { + if (_str) { + _value = kdebug_trace_string(code, 0, _str); + if (_value == (uint64_t)-1) _value = 0; + } + } + void destroy(uint32_t code) { + if (_str && _value) { + kdebug_trace_string(code, _value, nullptr); + } + } + friend class ScopedTimer; + friend uint64_t kdebug_trace_dyld_duration_start(uint32_t code, kt_arg data1, kt_arg data2, kt_arg data3); + friend void kdebug_trace_dyld_duration_end(uint64_t pair_id, uint32_t code, kt_arg data4, kt_arg data5, kt_arg data6); + friend void kdebug_trace_dyld_marker(uint32_t code, kt_arg data1, kt_arg data2, kt_arg data3, kt_arg data4); + uint64_t _value; + const char* _str; +}; + +class VIS_HIDDEN ScopedTimer { +public: + ScopedTimer(uint32_t code, kt_arg data1, kt_arg data2, kt_arg data3) + : code(code), data1(data1), data2(data2), data3(data3), data4(0), data5(0), data6(0) { +#if BUILDING_LIBDYLD || BUILDING_DYLD + startTimer(); #endif + } -#ifndef DBG_DYLD_SIGNPOST_START_MAIN_DYLD2 - #define DBG_DYLD_SIGNPOST_START_MAIN_DYLD2 (2) + ~ScopedTimer() { +#if BUILDING_LIBDYLD || BUILDING_DYLD + endTimer(); #endif - -#ifndef DBG_DYLD_TIMING_STATIC_INITIALIZER - #define DBG_DYLD_TIMING_STATIC_INITIALIZER (0) + } + + void setData4(kt_arg data) { data4 = data; } + void setData5(kt_arg data) { data5 = data; } + void setData6(kt_arg data) { data6 = data; } +private: +#if BUILDING_LIBDYLD || BUILDING_DYLD + void startTimer(); + void endTimer(); #endif -#ifndef DBG_DYLD_PRINT_GENERIC - #define DBG_DYLD_PRINT_GENERIC (0) -#endif - - -#define VIS_HIDDEN __attribute__((visibility("hidden"))) - -namespace dyld3 { + uint32_t code; + kt_arg data1; + kt_arg data2; + kt_arg data3; + kt_arg data4; + kt_arg data5; + kt_arg data6; + uint64_t current_trace_id = 0; +}; VIS_HIDDEN void kdebug_trace_dyld_image(const uint32_t code, @@ -79,13 +136,16 @@ void kdebug_trace_dyld_image(const uint32_t code, const mach_header* load_addr); VIS_HIDDEN -void kdebug_trace_dyld_signpost(const uint32_t code, uint64_t data1, uint64_t data2); +bool kdebug_trace_dyld_enabled(uint32_t code); + +VIS_HIDDEN +void kdebug_trace_dyld_marker(uint32_t code, kt_arg data1, kt_arg data2, kt_arg data3, kt_arg data4); VIS_HIDDEN -void kdebug_trace_dyld_duration(const uint32_t code, uint64_t data1, uint64_t data2, void (^block)()); +uint64_t kdebug_trace_dyld_duration_start(uint32_t code, kt_arg data1, kt_arg data2, kt_arg data3); VIS_HIDDEN -void kdebug_trace_print(const uint32_t code, const char *string); -} +void kdebug_trace_dyld_duration_end(uint64_t trace_id, uint32_t code, kt_arg data4, kt_arg data5, kt_arg data6); +}; #endif /* Tracing_h */ diff --git a/dyld3/closured/closured.cpp b/dyld3/closured/closured.cpp deleted file mode 100644 index 1e239e8..0000000 --- a/dyld3/closured/closured.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/* - * 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 deleted file mode 100644 index 565c8bc..0000000 --- a/dyld3/closured/closuredProtocol.defs +++ /dev/null @@ -1,29 +0,0 @@ - - -#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 deleted file mode 100644 index 5a352d6..0000000 --- a/dyld3/closured/closured_entitlements.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - seatbelt-profiles - - closured - - platform-application - - - diff --git a/dyld3/closured/closuredtypes.h b/dyld3/closured/closuredtypes.h deleted file mode 100644 index 6acc727..0000000 --- a/dyld3/closured/closuredtypes.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 deleted file mode 100644 index e19d134..0000000 --- a/dyld3/closured/com.apple.dyld.closured.plist +++ /dev/null @@ -1,25 +0,0 @@ - - - - - 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 deleted file mode 100644 index bd89f9c..0000000 --- a/dyld3/closured/com.apple.dyld.closured.sb +++ /dev/null @@ -1,22 +0,0 @@ -;;; 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 deleted file mode 100644 index 998a298..0000000 --- a/dyld3/dyld-potential-framework-overrides +++ /dev/null @@ -1,7 +0,0 @@ -/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 deleted file mode 100644 index f7e654c..0000000 --- a/dyld3/libclosured-stub.cpp +++ /dev/null @@ -1,12 +0,0 @@ - -namespace dyld3 { - -struct ClosureBuffer { }; - -ClosureBuffer closured_CreateImageGroup(const ClosureBuffer& input) -{ - return ClosureBuffer(); -} - - -} // namespace dyld3 diff --git a/dyld3/libdyldEntryVector.cpp b/dyld3/libdyldEntryVector.cpp index 42cedaa..630d01c 100644 --- a/dyld3/libdyldEntryVector.cpp +++ b/dyld3/libdyldEntryVector.cpp @@ -26,13 +26,14 @@ #include "dyld_priv.h" #include "libdyldEntryVector.h" #include "AllImages.h" +#include "Array.h" +#include "Loading.h" #include "Logging.h" #include "PathOverrides.h" -#include "LaunchCacheFormat.h" -#include "start_glue.h" - -extern "C" void start(); +#include "StartGlue.h" +#include "dyld_process_info_internal.h" +extern "C" char start; VIS_HIDDEN const char** appleParams; @@ -90,32 +91,38 @@ 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) +static void entry_setNotifyMonitoringDyldMain(void (*notifyMonitoringDyldMain)()) { + setNotifyMonitoringDyldMain(notifyMonitoringDyldMain); +} + +static void entry_setNotifyMonitoringDyld(void (*notifyMonitoringDyld)(bool unloading,unsigned imageCount, + const struct mach_header* loadAddresses[], + const char* imagePaths[])) { + setNotifyMonitoringDyld(notifyMonitoringDyld); +} + +static void entry_setInitialImageList(const closure::LaunchClosure* closure, + const DyldSharedCache* dyldCacheLoadAddress, const char* dyldCachePath, + const Array& initialImages, const LoadedImage& libSystem) { gAllImages.init(closure, dyldCacheLoadAddress, dyldCachePath, initialImages); - gAllImages.applyInterposingToDyldCache(closure, initialImages); + gAllImages.applyInterposingToDyldCache(closure); 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); + gAllImages.runLibSystemInitializer(libSystem); // now that malloc is available, parse DYLD_ env vars - gPathOverrides.setEnvVars((const char**)environ); + closure::gPathOverrides.setEnvVars((const char**)environ, gAllImages.mainExecutable(), gAllImages.mainExecutableImage()->path()); } static void entry_runInitialzersBottomUp(const mach_header* mainExecutableImageLoadAddress) { - gAllImages.runInitialzersBottomUp(mainExecutableImageLoadAddress); + gAllImages.runStartupInitialzers(); gAllImages.notifyMonitorMain(); } @@ -124,19 +131,25 @@ static void entry_setChildForkFunction(void (*func)() ) sChildForkFunction = func; } -typedef void (*StartFunc)(); +static void entry_setRestrictions(bool allowAtPaths, bool allowEnvPaths) +{ + gAllImages.setRestrictions(allowAtPaths, allowEnvPaths); +} const LibDyldEntryVector entryVectorForDyld = { LibDyldEntryVector::kCurrentVectorVersion, - launch_cache::binary_format::kFormatVersion, + closure::kFormatVersion, &entry_setVars, &entry_setHaltFunction, &entry_setOldAllImageInfo, &entry_setInitialImageList, &entry_runInitialzersBottomUp, - (StartFunc)address_of_start, + (__typeof(LibDyldEntryVector::startFunc))address_of_start, &entry_setChildForkFunction, &entry_setLogFunction, + &entry_setRestrictions, + &entry_setNotifyMonitoringDyldMain, + &entry_setNotifyMonitoringDyld }; VIS_HIDDEN void _dyld_fork_child() diff --git a/dyld3/libdyldEntryVector.h b/dyld3/libdyldEntryVector.h index 3c61df2..d7b832d 100644 --- a/dyld3/libdyldEntryVector.h +++ b/dyld3/libdyldEntryVector.h @@ -28,32 +28,39 @@ #include -#include "LaunchCache.h" #include "Loading.h" struct dyld_all_image_infos; +class DyldSharedCache; namespace dyld3 { + struct LibDyldEntryVector { - enum { kCurrentVectorVersion = 4 }; + enum { kCurrentVectorVersion = 6 }; uint32_t vectorVersion; // should be kCurrentVectorVersion - uint32_t binaryFormatVersion; // should be launch_cache::binary_format::kFormatVersion + uint32_t binaryFormatVersion; // should be dyld3::closure::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 (*setInitialImageList)(const closure::LaunchClosure* closure, + const DyldSharedCache* dyldCacheLoadAddress, const char* dyldCachePath, + const Array& initialImages, const LoadedImage& libSystem); 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)); + // added in version 5 + void (*setRestrictions)(bool allowAtPaths, bool allowEnvVars); + // added in version 6 + void (*setNotifyMonitoringDyldMain)(void (*notifyMonitoringDyldMain)()); + void (*setNotifyMonitoringDyld)(void (*notifyMonitoringDyldMain)(bool unloading, unsigned imageCount, + const struct mach_header* loadAddresses[], + const char* imagePaths[])); }; extern const LibDyldEntryVector entryVectorForDyld; diff --git a/dyld3/shared-cache/AdjustDylibSegments.cpp b/dyld3/shared-cache/AdjustDylibSegments.cpp index 9ab3bb8..f26d9ae 100644 --- a/dyld3/shared-cache/AdjustDylibSegments.cpp +++ b/dyld3/shared-cache/AdjustDylibSegments.cpp @@ -41,6 +41,8 @@ #include "DyldSharedCache.h" #include "Trie.hpp" #include "MachOFileAbstraction.hpp" +#include "MachOLoaded.h" +#include "MachOAnalyzer.h" #ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE @@ -53,20 +55,22 @@ template class Adjustor { public: Adjustor(DyldSharedCache* cacheBuffer, macho_header

* mh, const std::vector& mappingInfo, Diagnostics& diag); - void adjustImageForNewSegmentLocations(std::vector& pointersForASLR); - + void adjustImageForNewSegmentLocations(CacheBuilder::ASLR_Tracker& aslrTracker, + CacheBuilder::LOH_Tracker& lohTracker); + private: - void adjustReferencesUsingInfoV2(std::vector& pointersForASLR); + void adjustReferencesUsingInfoV2(CacheBuilder::ASLR_Tracker& aslrTracker, CacheBuilder::LOH_Tracker& lohTracker); 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); + uint64_t imageStartAddress, uint64_t imageEndAddress, + CacheBuilder::ASLR_Tracker& aslrTracker, CacheBuilder::LOH_Tracker* lohTracker, + uint32_t*& lastMappedAddr32, uint32_t& lastKind, uint64_t& lastToNewAddress); + void adjustDataPointers(CacheBuilder::ASLR_Tracker& aslrTracker); + void slidePointer(int segIndex, uint64_t segOffset, uint8_t type, CacheBuilder::ASLR_Tracker& aslrTracker); void adjustSymbolTable(); void adjustExportsTrie(std::vector& newTrieBytes); void rebuildLinkEdit(); void adjustCode(); - void adjustInstruction(uint8_t kind, uint64_t cacheOffset, uint64_t codeToDataDelta); + void adjustInstruction(uint8_t kind, uint8_t* textLoc, uint64_t codeToDataDelta); void rebuildLinkEditAndLoadCommands(); uint64_t slideForOrigAddress(uint64_t addr); @@ -77,7 +81,6 @@ private: macho_header

* _mh; Diagnostics& _diagnostics; const uint8_t* _linkeditBias = nullptr; - int64_t _linkeditAdjust = 0; unsigned _linkeditSegIndex = 0; bool _maskPointers = false; bool _splitSegInfoV2 = false; @@ -132,10 +135,9 @@ Adjustor

::Adjustor(DyldSharedCache* cacheBuffer, macho_header

* mh, const s segCmd = (macho_segment_command

*)cmd; _segCmds.push_back(segCmd); _segOrigStartAddresses.push_back(segCmd->vmaddr()); - _segSlides.push_back(_mappingInfo[segIndex].dstCacheAddress - segCmd->vmaddr()); + _segSlides.push_back(_mappingInfo[segIndex].dstCacheUnslidAddress - segCmd->vmaddr()); if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { - _linkeditAdjust = _mappingInfo[segIndex].dstCacheOffset - segCmd->fileoff(); - _linkeditBias = (uint8_t*)cacheBuffer + _linkeditAdjust; + _linkeditBias = (uint8_t*)_mappingInfo[segIndex].dstSegment - segCmd->fileoff(); _linkeditSegIndex = segIndex; } ++segIndex; @@ -143,7 +145,7 @@ Adjustor

::Adjustor(DyldSharedCache* cacheBuffer, macho_header

* mh, const s } cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); } - _maskPointers = (P::E::get32(mh->cputype()) == CPU_TYPE_ARM64); + _maskPointers = (P::E::get32(mh->cputype()) == CPU_TYPE_ARM64) || (P::E::get32(mh->cputype()) == CPU_TYPE_ARM64_32); if ( _splitSegInfoCmd != NULL ) { const uint8_t* infoStart = &_linkeditBias[_splitSegInfoCmd->dataoff()]; _splitSegInfoV2 = (*infoStart == DYLD_CACHE_ADJ_V2_FORMAT); @@ -154,15 +156,16 @@ Adjustor

::Adjustor(DyldSharedCache* cacheBuffer, macho_header

* mh, const s } template -void Adjustor

::adjustImageForNewSegmentLocations(std::vector& pointersForASLR) +void Adjustor

::adjustImageForNewSegmentLocations(CacheBuilder::ASLR_Tracker& aslrTracker, + CacheBuilder::LOH_Tracker& lohTracker) { if ( _diagnostics.hasError() ) return; if ( _splitSegInfoV2 ) { - adjustReferencesUsingInfoV2(pointersForASLR); + adjustReferencesUsingInfoV2(aslrTracker, lohTracker); } else { - adjustDataPointers(pointersForASLR); + adjustDataPointers(aslrTracker); adjustCode(); } if ( _diagnostics.hasError() ) @@ -171,6 +174,14 @@ void Adjustor

::adjustImageForNewSegmentLocations(std::vector& pointers if ( _diagnostics.hasError() ) return; rebuildLinkEditAndLoadCommands(); + +#if DEBUG + Diagnostics diag; + ((dyld3::MachOAnalyzer*)_mh)->validateDyldCacheDylib(diag, _installName); + if ( diag.hasError() ) { + fprintf(stderr, "%s\n", diag.errorMessage().c_str()); + } +#endif } template @@ -198,11 +209,11 @@ void Adjustor

::rebuildLinkEditAndLoadCommands() // 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 weakBindOffset = bindOffset + bindSize; uint32_t weakBindSize = _dyldInfo->weak_bind_size(); - uint32_t exportOffset = weakBindOffset + weakBindSize; + uint32_t lazyBindOffset = weakBindOffset + weakBindSize; + uint32_t lazyBindSize = _dyldInfo->lazy_bind_size(); + uint32_t exportOffset = lazyBindOffset + lazyBindSize; uint32_t exportSize = (uint32_t)newTrieBytes.size(); uint32_t splitSegInfoOffset = exportOffset + exportSize; uint32_t splitSegInfosSize = (_splitSegInfoCmd ? _splitSegInfoCmd->datasize() : 0); @@ -224,7 +235,6 @@ void Adjustor

::rebuildLinkEditAndLoadCommands() 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); @@ -247,9 +257,10 @@ void Adjustor

::rebuildLinkEditAndLoadCommands() if ( symbolStringsSize ) memcpy(&newLinkeditBufer[symbolStringsOffset], &_linkeditBias[_symTabCmd->stroff()], symbolStringsSize); - memcpy((uint8_t*)_cacheBuffer+linkeditStartOffset, newLinkeditBufer, newLinkEditSize); - ::bzero((uint8_t*)_cacheBuffer+linkeditStartOffset+newLinkEditSize, linkeditBufferSize-newLinkEditSize); + memcpy(_mappingInfo[_linkeditSegIndex].dstSegment, newLinkeditBufer, newLinkEditSize); + ::bzero(((uint8_t*)_mappingInfo[_linkeditSegIndex].dstSegment)+newLinkEditSize, linkeditBufferSize-newLinkEditSize); ::free(newLinkeditBufer); + uint32_t linkeditStartOffset = (uint32_t)_mappingInfo[_linkeditSegIndex].dstCacheFileOffset; // updates load commands and removed ones no longer needed macho_load_command

* const cmds = (macho_load_command

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

)); @@ -299,7 +310,7 @@ void Adjustor

::rebuildLinkEditAndLoadCommands() dyldInfo->set_lazy_bind_size(lazyBindSize); dyldInfo->set_export_off(exportSize ? linkeditStartOffset+exportOffset : 0); dyldInfo->set_export_size(exportSize); - break; + break; case LC_FUNCTION_STARTS: functionStartsCmd = (macho_linkedit_data_command

*)cmd; functionStartsCmd->set_dataoff(linkeditStartOffset+funcStartsOffset); @@ -314,10 +325,10 @@ void Adjustor

::rebuildLinkEditAndLoadCommands() 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); + segFileOffsetDelta = (int32_t)(_mappingInfo[segIndex].dstCacheFileOffset - segCmd->fileoff()); + segCmd->set_vmaddr(_mappingInfo[segIndex].dstCacheUnslidAddress); segCmd->set_vmsize(_mappingInfo[segIndex].dstCacheSegmentSize); - segCmd->set_fileoff(_mappingInfo[segIndex].dstCacheOffset); + segCmd->set_fileoff(_mappingInfo[segIndex].dstCacheFileOffset); segCmd->set_filesize(_mappingInfo[segIndex].copySegmentSize); if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) segCmd->set_vmsize(linkeditBufferSize); @@ -387,9 +398,9 @@ void Adjustor

::adjustSymbolTable() } template -void Adjustor

::slidePointer(int segIndex, uint64_t segOffset, uint8_t type, std::vector& pointersForASLR) +void Adjustor

::slidePointer(int segIndex, uint64_t segOffset, uint8_t type, CacheBuilder::ASLR_Tracker& aslrTracker) { - pint_t* mappedAddrP = (pint_t*)((uint8_t*)_cacheBuffer + _mappingInfo[segIndex].dstCacheOffset + segOffset); + pint_t* mappedAddrP = (pint_t*)((uint8_t*)_mappingInfo[segIndex].dstSegment + segOffset); uint32_t* mappedAddr32 = (uint32_t*)mappedAddrP; pint_t valueP; uint32_t value32; @@ -397,7 +408,7 @@ void Adjustor

::slidePointer(int segIndex, uint64_t segOffset, uint8_t type, s case REBASE_TYPE_POINTER: valueP = (pint_t)P::getP(*mappedAddrP); P::setP(*mappedAddrP, valueP + slideForOrigAddress(valueP)); - pointersForASLR.push_back(mappedAddrP); + aslrTracker.add(mappedAddrP); break; case REBASE_TYPE_TEXT_ABSOLUTE32: @@ -466,13 +477,15 @@ static uint32_t setArmWord(uint32_t instruction, uint16_t word) { 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) + CacheBuilder::ASLR_Tracker& aslrTracker, CacheBuilder::LOH_Tracker* lohTracker, + 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; + dyld3::MachOLoaded::ChainedFixupPointerOnDisk chainPtr; int64_t offsetAdjust; int64_t delta; switch ( kind ) { @@ -489,12 +502,12 @@ void Adjustor

::adjustReference(uint32_t kind, uint8_t* mappedAddr, uint64_t f break; case DYLD_CACHE_ADJ_V2_POINTER_32: mappedAddr32 = (uint32_t*)mappedAddr; - if ( toNewAddress != (E::get32(*mappedAddr32) + targetSlide) ) { + if ( toNewAddress != (uint64_t)(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); + aslrTracker.add(mappedAddr32); break; case DYLD_CACHE_ADJ_V2_POINTER_64: mappedAddr64 = (uint64_t*)mappedAddr; @@ -503,9 +516,26 @@ void Adjustor

::adjustReference(uint32_t kind, uint8_t* mappedAddr, uint64_t f return; } E::set64(*mappedAddr64, toNewAddress); - pointersForASLR.push_back(mappedAddr); + aslrTracker.add(mappedAddr64); break; - case DYLD_CACHE_ADJ_V2_DELTA_64: + case DYLD_CACHE_ADJ_V2_THREADED_POINTER_64: + mappedAddr64 = (uint64_t*)mappedAddr; + chainPtr.raw = E::get64(*mappedAddr64); + // ignore binds, fix up rebases to have new targets + if ( chainPtr.authRebase.bind == 0 ) { + if ( chainPtr.authRebase.auth ) { + // auth pointer target is offset in dyld cache + chainPtr.authRebase.target += (((dyld3::MachOAnalyzer*)_mh)->preferredLoadAddress() + targetSlide - _cacheBuffer->header.sharedRegionStart); + } + else { + // plain pointer target is unslid address of target + chainPtr.plainRebase.target += targetSlide; + } + // Note, the pointer remains a chain with just the target of the rebase adjusted to the new target location + E::set64(*mappedAddr64, chainPtr.raw); + } + break; + case DYLD_CACHE_ADJ_V2_DELTA_64: mappedAddr64 = (uint64_t*)mappedAddr; value64 = P::E::get64(*mappedAddr64); E::set64(*mappedAddr64, value64 + adjust); @@ -524,6 +554,8 @@ void Adjustor

::adjustReference(uint32_t kind, uint8_t* mappedAddr, uint64_t f break; case DYLD_CACHE_ADJ_V2_ARM64_ADRP: mappedAddr32 = (uint32_t*)mappedAddr; + if (lohTracker) + (*lohTracker)[toNewAddress].insert(mappedAddr); instruction = P::E::get32(*mappedAddr32); if ( (instruction & 0x9F000000) == 0x90000000 ) { int64_t pageDistance = ((toNewAddress & ~0xFFF) - (fromNewAddress & ~0xFFF)); @@ -541,6 +573,8 @@ void Adjustor

::adjustReference(uint32_t kind, uint8_t* mappedAddr, uint64_t f break; case DYLD_CACHE_ADJ_V2_ARM64_OFF12: mappedAddr32 = (uint32_t*)mappedAddr; + if (lohTracker) + (*lohTracker)[toNewAddress].insert(mappedAddr); instruction = P::E::get32(*mappedAddr32); offsetAdjust = (adjust & 0xFFF); if ( offsetAdjust == 0 ) @@ -713,7 +747,8 @@ void Adjustor

::adjustReference(uint32_t kind, uint8_t* mappedAddr, uint64_t f } template -void Adjustor

::adjustReferencesUsingInfoV2(std::vector& pointersForASLR) +void Adjustor

::adjustReferencesUsingInfoV2(CacheBuilder::ASLR_Tracker& aslrTracker, + CacheBuilder::LOH_Tracker& lohTracker) { static const bool log = false; @@ -731,24 +766,27 @@ void Adjustor

::adjustReferencesUsingInfoV2(std::vector& pointersForASL sectionNewAddress.reserve(16); sectionMappedAddress.reserve(16); // section index 0 refers to mach_header - sectionMappedAddress.push_back((uint8_t*)_cacheBuffer + _mappingInfo[0].dstCacheOffset); + sectionMappedAddress.push_back((uint8_t*)_mappingInfo[0].dstSegment); sectionSlides.push_back(_segSlides[0]); - sectionNewAddress.push_back(_mappingInfo[0].dstCacheAddress); + sectionNewAddress.push_back(_mappingInfo[0].dstCacheUnslidAddress); // section 1 and later refer to real sections unsigned sectionIndex = 0; + unsigned objcSelRefsSectionIndex = ~0U; 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()); + sectionMappedAddress.push_back((uint8_t*)_mappingInfo[segmentIndex].dstSegment + sect->addr() - segCmd->vmaddr()); sectionSlides.push_back(_segSlides[segmentIndex]); - sectionNewAddress.push_back(_mappingInfo[segmentIndex].dstCacheAddress + sect->addr() - segCmd->vmaddr()); + sectionNewAddress.push_back(_mappingInfo[segmentIndex].dstCacheUnslidAddress + sect->addr() - segCmd->vmaddr()); if (log) { fprintf(stderr, " %s/%s, sectIndex=%d, mapped at=%p\n", sect->segname(), sect->sectname(), sectionIndex, sectionMappedAddress.back()); } ++sectionIndex; + if (!strcmp(sect->segname(), "__DATA") && !strcmp(sect->sectname(), "__objc_selrefs")) + objcSelRefsSectionIndex = sectionIndex; } } @@ -770,6 +808,7 @@ void Adjustor

::adjustReferencesUsingInfoV2(std::vector& pointersForASL uint8_t* fromSectionMappedAddress = sectionMappedAddress[fromSectionIndex]; uint64_t toSectionSlide = sectionSlides[toSectionIndex]; uint64_t toSectionNewAddress = sectionNewAddress[toSectionIndex]; + CacheBuilder::LOH_Tracker* lohTrackerPtr = (toSectionIndex == objcSelRefsSectionIndex) ? &lohTracker : nullptr; 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) { @@ -778,8 +817,8 @@ void Adjustor

::adjustReferencesUsingInfoV2(std::vector& pointersForASL 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); + if ( kind > 13 ) { + _diagnostics.error("unknown split seg info v2 kind value (%llu) in %s", kind, _installName); return; } uint64_t fromSectDeltaCount = read_uleb128(p, infoEnd); @@ -794,8 +833,9 @@ void Adjustor

::adjustReferencesUsingInfoV2(std::vector& pointersForASL 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 ( toSectionIndex != 255 ) { + adjustReference((uint32_t)kind, fromMappedAddr, fromNewAddress, toNewAddress, deltaAdjust, toSectionSlide, imageStartAddress, imageEndAddress, aslrTracker, lohTrackerPtr, lastMappedAddr32, lastKind, lastToNewAddress); + } if ( _diagnostics.hasError() ) return; } @@ -806,7 +846,7 @@ void Adjustor

::adjustReferencesUsingInfoV2(std::vector& pointersForASL } template -void Adjustor

::adjustDataPointers(std::vector& pointersForASLR) +void Adjustor

::adjustDataPointers(CacheBuilder::ASLR_Tracker& aslrTracker) { const uint8_t* p = &_linkeditBias[_dyldInfo->rebase_off()]; const uint8_t* end = &p[_dyldInfo->rebase_size()]; @@ -840,26 +880,26 @@ void Adjustor

::adjustDataPointers(std::vector& pointersForASLR) break; case REBASE_OPCODE_DO_REBASE_IMM_TIMES: for (int i=0; i < immediate; ++i) { - slidePointer(segIndex, segOffset, type, pointersForASLR); + slidePointer(segIndex, segOffset, type, aslrTracker); 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); + slidePointer(segIndex, segOffset, type, aslrTracker); segOffset += sizeof(pint_t); } break; case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: - slidePointer(segIndex, segOffset, type, pointersForASLR); + slidePointer(segIndex, segOffset, type, aslrTracker); 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); + slidePointer(segIndex, segOffset, type, aslrTracker); segOffset += skip + sizeof(pint_t); } break; @@ -873,11 +913,10 @@ void Adjustor

::adjustDataPointers(std::vector& pointersForASLR) template -void Adjustor

::adjustInstruction(uint8_t kind, uint64_t cacheOffset, uint64_t codeToDataDelta) +void Adjustor

::adjustInstruction(uint8_t kind, uint8_t* textLoc, uint64_t codeToDataDelta) { - uint8_t* fixupLoc = (uint8_t*)_cacheBuffer + cacheOffset; - uint32_t* fixupLoc32 = (uint32_t*)fixupLoc; - uint64_t* fixupLoc64 = (uint64_t*)fixupLoc; + uint32_t* fixupLoc32 = (uint32_t*)textLoc; + uint64_t* fixupLoc64 = (uint64_t*)textLoc; uint32_t instruction; uint32_t value32; uint64_t value64; @@ -1010,10 +1049,10 @@ void Adjustor

::adjustCode() // 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; + uint8_t* textLoc = (uint8_t*)_mappingInfo[0].dstSegment; while (uint64_t delta = read_uleb128(p, infoEnd)) { - cacheOffset += delta; - adjustInstruction(kind, cacheOffset, codeToDataDelta); + textLoc += delta; + adjustInstruction(kind, textLoc, codeToDataDelta); } } } @@ -1065,16 +1104,16 @@ void Adjustor

::adjustExportsTrie(std::vector& newTrieBytes) } // anonymous namespace - -void adjustDylibSegments(DyldSharedCache* cache, bool is64, mach_header* mhInCache, const std::vector& mappingInfo, std::vector& pointersForASLR, Diagnostics& diag) +void CacheBuilder::adjustDylibSegments(const DylibInfo& dylib, Diagnostics& diag) const { - if ( is64 ) { - Adjustor> adjustor64(cache, (macho_header>*)mhInCache, mappingInfo, diag); - adjustor64.adjustImageForNewSegmentLocations(pointersForASLR); + DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + if ( _archLayout->is64 ) { + Adjustor> adjustor64(cache, (macho_header>*)dylib.cacheLocation[0].dstSegment, dylib.cacheLocation, diag); + adjustor64.adjustImageForNewSegmentLocations(_aslrTracker, _lohTracker); } else { - Adjustor> adjustor32(cache, (macho_header>*)mhInCache, mappingInfo, diag); - adjustor32.adjustImageForNewSegmentLocations(pointersForASLR); + Adjustor> adjustor32(cache, (macho_header>*)dylib.cacheLocation[0].dstSegment, dylib.cacheLocation, diag); + adjustor32.adjustImageForNewSegmentLocations(_aslrTracker, _lohTracker); } } @@ -1082,3 +1121,4 @@ void adjustDylibSegments(DyldSharedCache* cache, bool is64, mach_header* mhInCac + diff --git a/dyld3/shared-cache/BuilderUtils.h b/dyld3/shared-cache/BuilderUtils.h index 713d0bd..dc2c5e3 100644 --- a/dyld3/shared-cache/BuilderUtils.h +++ b/dyld3/shared-cache/BuilderUtils.h @@ -27,6 +27,7 @@ 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); +bool build(Diagnostics& diags, dyld3::Manifest& manifest, const std::string& masterDstRoot, bool dedupe, bool verbose, bool skipWrites, bool agileChooseSHA256CdHash, + bool emitDevCaches, bool isLocallyBuiltCache); #endif /* BuilderUtils_h */ diff --git a/dyld3/shared-cache/BuilderUtils.mm b/dyld3/shared-cache/BuilderUtils.mm index 7704301..212673d 100644 --- a/dyld3/shared-cache/BuilderUtils.mm +++ b/dyld3/shared-cache/BuilderUtils.mm @@ -23,6 +23,7 @@ #include +#include #include #include #include // std::setfill, std::setw @@ -140,7 +141,7 @@ 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) + bool skipWrites, bool agileChooseSHA256CdHash, bool emitDevCaches, bool isLocallyBuiltCache) { 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); @@ -207,7 +208,8 @@ bool build(Diagnostics& diags, dyld3::Manifest& manifest, const std::string& mas } } - manifest.configuration(*cacheSet.begin()).forEachArchitecture([&masterDstRoot, &dedupe, &fileName, &setName, &manifest, &buildQueue, &cacheSet, verbose](const std::string& arch) { + manifest.configuration(*cacheSet.begin()).forEachArchitecture([&masterDstRoot, &dedupe, &fileName, &setName, &manifest, + &buildQueue, &cacheSet, skipWrites, verbose, emitDevCaches, isLocallyBuiltCache](const std::string& arch) { std::string configPath; std::string runtimePath = "/System/Library/Caches/com.apple.dyld/"; if (manifest.platform() == dyld3::Platform::macOS) { @@ -219,11 +221,16 @@ bool build(Diagnostics& diags, dyld3::Manifest& manifest, const std::string& mas configPath = masterDstRoot + runtimePath; } + mkpath_np(configPath.c_str(), 0755); if (manifest.platform() == dyld3::Platform::macOS) { - buildQueue.push_back(manifest.makeQueueEntry(configPath + "dyld_shared_cache_" + arch, cacheSet, arch, false, setName + "/" + arch, verbose)); + buildQueue.push_back(manifest.makeQueueEntry(configPath + "dyld_shared_cache_" + arch, cacheSet, arch, false, setName + "/" + arch, + isLocallyBuiltCache, skipWrites, 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)); + if (emitDevCaches) + buildQueue.push_back(manifest.makeQueueEntry(configPath + "dyld_shared_cache_" + arch + ".development", cacheSet, arch, false, setName + "/" + arch, + isLocallyBuiltCache, skipWrites, verbose)); + buildQueue.push_back(manifest.makeQueueEntry(configPath + "dyld_shared_cache_" + arch, cacheSet, arch, true, setName + "/" + arch, + isLocallyBuiltCache, skipWrites, verbose)); } }); } @@ -234,7 +241,7 @@ bool build(Diagnostics& diags, dyld3::Manifest& manifest, const std::string& mas dispatch_sync(warningQueue, ^{ auto manifestWarnings = diags.warnings(); - warnings.insert(manifestWarnings.begin(), manifestWarnings.end()); + //warnings.insert(manifestWarnings.begin(), manifestWarnings.end()); }); dispatch_apply(buildQueue.size(), queue, ^(size_t index) { @@ -252,39 +259,21 @@ bool build(Diagnostics& diags, dyld3::Manifest& manifest, const std::string& mas for (const auto& configName : queueEntry.configNames) { auto& configResults = manifest.configuration(configName).architecture(queueEntry.options.archName).results; for (const auto& mh : results.evictions) { - auto parser = dyld3::MachOParser(mh); - configResults.exclude(&parser, "VM overflow, evicting"); + configResults.exclude(mh, "VM overflow, evicting"); } configResults.warnings = results.warnings; if (queueEntry.options.optimizeStubs) { - configResults.developmentCache.cdHash = chooseSecondCdHash ? results.cdHashSecond : results.cdHashFirst; + configResults.productionCache.cdHash = chooseSecondCdHash ? results.cdHashSecond : results.cdHashFirst; } else { - configResults.productionCache.cdHash = chooseSecondCdHash ? results.cdHashSecond : results.cdHashFirst; + configResults.developmentCache.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 { + cacheBuildFailure = true; + } else if (skipWrites) { 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); } }); diff --git a/dyld3/shared-cache/CacheBuilder.cpp b/dyld3/shared-cache/CacheBuilder.cpp index 89cd418..37df074 100644 --- a/dyld3/shared-cache/CacheBuilder.cpp +++ b/dyld3/shared-cache/CacheBuilder.cpp @@ -28,8 +28,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -44,34 +47,46 @@ #include #include -#include "MachOParser.h" +#include "MachOFileAbstraction.hpp" #include "CodeSigningTypes.h" #include "DyldSharedCache.h" #include "CacheBuilder.h" #include "FileAbstraction.hpp" -#include "LaunchCacheWriter.h" #include "Trie.hpp" +#include "FileUtils.h" #include "Diagnostics.h" -#include "ImageProxy.h" +#include "ClosureBuilder.h" +#include "Closure.h" +#include "StringUtils.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 + #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 + +#ifndef ARM64_32_SHARED_REGION_START + #define ARM64_32_SHARED_REGION_START 0x1A000000ULL + #define ARM64_32_SHARED_REGION_SIZE 0x26000000ULL #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 } + { 0x7FFF20000000ULL, 0xEFE00000ULL, 0x40000000, 0xFFFF000000000000, "x86_64", 0, 0, 0, 12, 2, true, true }, + { 0x7FFF20000000ULL, 0xEFE00000ULL, 0x40000000, 0xFFFF000000000000, "x86_64h", 0, 0, 0, 12, 2, true, true }, + { SHARED_REGION_BASE_I386, SHARED_REGION_SIZE_I386, 0x00200000, 0x0, "i386", 0, 0, 0, 12, 0, false, false }, + { ARM64_SHARED_REGION_START, ARM64_SHARED_REGION_SIZE, 0x02000000, 0x00FFFF0000000000, "arm64", 0x0000C000, 0x00100000, 0x07F00000, 14, 2, false, true }, +#if SUPPORT_ARCH_arm64e + { ARM64_SHARED_REGION_START, ARM64_SHARED_REGION_SIZE, 0x02000000, 0x00FFFF0000000000, "arm64e", 0x0000C000, 0x00100000, 0x07F00000, 14, 2, false, true }, +#endif +#if SUPPORT_ARCH_arm64_32 + { ARM64_32_SHARED_REGION_START, ARM64_32_SHARED_REGION_SIZE,0x02000000, 0xC0000000, "arm64_32",0x0000C000, 0x00100000, 0x07F00000, 14, 6, false, false }, +#endif + { ARM_SHARED_REGION_START, ARM_SHARED_REGION_SIZE, 0x02000000, 0xE0000000, "armv7s", 0, 0, 0, 14, 4, false, false }, + { ARM_SHARED_REGION_START, ARM_SHARED_REGION_SIZE, 0x00400000, 0xE0000000, "armv7k", 0, 0, 0, 14, 4, false, false }, + { 0x40000000, 0x40000000, 0x02000000, 0x0, "sim-x86", 0, 0, 0, 14, 0, false, false } }; @@ -82,17 +97,16 @@ const char* const CacheBuilder::_s_neverStubEliminate[] = { }; -CacheBuilder::CacheBuilder(const DyldSharedCache::CreateOptions& options) +CacheBuilder::CacheBuilder(const DyldSharedCache::CreateOptions& options, const dyld3::closure::FileSystem& fileSystem) : _options(options) - , _buffer(nullptr) + , _fileSystem(fileSystem) + , _fullAllocatedBuffer(0) , _diagnostics(options.loggingPrefix, options.verbose) , _archLayout(nullptr) , _aliasCount(0) , _slideInfoFileOffset(0) , _slideInfoBufferSizeAllocated(0) , _allocatedBufferSize(0) - , _currentFileSize(0) - , _vmSize(0) , _branchPoolsLinkEditStartAddr(0) { @@ -106,6 +120,10 @@ CacheBuilder::CacheBuilder(const DyldSharedCache::CreateOptions& options) break; } } + + if (!_archLayout) { + _diagnostics.error("Tool was built without support for: '%s'", targetArch.c_str()); + } } @@ -119,26 +137,28 @@ const std::set CacheBuilder::warnings() return _diagnostics.warnings(); } -const std::set CacheBuilder::evictions() +const std::set CacheBuilder::evictions() { return _evictions; } void CacheBuilder::deleteBuffer() { - vm_deallocate(mach_task_self(), (vm_address_t)_buffer, _allocatedBufferSize); - _buffer = nullptr; + vm_deallocate(mach_task_self(), _fullAllocatedBuffer, _archLayout->sharedMemorySize); + _fullAllocatedBuffer = 0; _allocatedBufferSize = 0; } -std::vector -CacheBuilder::makeSortedDylibs(const std::vector& dylibs, const std::unordered_map sortOrder) + +void CacheBuilder::makeSortedDylibs(const std::vector& dylibs, const std::unordered_map sortOrder) { - std::vector sortedDylibs = dylibs; + for (const LoadedMachO& dylib : dylibs) { + _sortedDylibs.push_back({ &dylib, dylib.mappedFile.runtimePath, {} }); + } - 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); + std::sort(_sortedDylibs.begin(), _sortedDylibs.end(), [&](const DylibInfo& a, const DylibInfo& b) { + const auto& orderA = sortOrder.find(a.input->mappedFile.runtimePath); + const auto& orderB = sortOrder.find(b.input->mappedFile.runtimePath); bool foundA = (orderA != sortOrder.end()); bool foundB = (orderB != sortOrder.end()); @@ -152,10 +172,8 @@ CacheBuilder::makeSortedDylibs(const std::vector& else if ( foundB ) return false; else - return a.runtimePath < b.runtimePath; + return a.input->mappedFile.runtimePath < b.input->mappedFile.runtimePath; }); - - return sortedDylibs; } @@ -166,301 +184,712 @@ inline uint32_t absolutetime_to_milliseconds(uint64_t abstime) struct DylibAndSize { - const char* installName; - uint64_t size; + const CacheBuilder::LoadedMachO* input; + const char* installName; + uint64_t size; }; -bool CacheBuilder::cacheOverflow(const dyld_cache_mapping_info regions[3]) +uint64_t CacheBuilder::cacheOverflowAmount() { 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) ); + if ( _readExecuteRegion.sizeInUse > 0x60000000 ) + return (_readExecuteRegion.sizeInUse - 0x60000000); + + if ( _readWriteRegion.sizeInUse > 0x40000000 ) + return (_readWriteRegion.sizeInUse - 0x40000000); + + if ( _readOnlyRegion.sizeInUse > 0x3FE00000 ) + return (_readOnlyRegion.sizeInUse - 0x3FE00000); } else { - return (_vmSize > _archLayout->sharedMemorySize); + bool alreadyOptimized = (_readOnlyRegion.sizeInUse != _readOnlyRegion.bufferSize); + uint64_t vmSize = _readOnlyRegion.unslidLoadAddress - _readExecuteRegion.unslidLoadAddress; + if ( alreadyOptimized ) + vmSize += _readOnlyRegion.sizeInUse; + else if ( _options.excludeLocalSymbols ) + vmSize += (_readOnlyRegion.sizeInUse * 37/100); // assume locals removal and LINKEDIT optimzation reduces LINKEDITs %25 of original size + else + vmSize += (_readOnlyRegion.sizeInUse * 80/100); // assume LINKEDIT optimzation reduces LINKEDITs to %80 of original size + if ( vmSize > _archLayout->sharedMemorySize ) + return vmSize - _archLayout->sharedMemorySize; } + // fits in shared region + return 0; } -void CacheBuilder::build(const std::vector& dylibs, - const std::vector& otherOsDylibsInput, - const std::vector& osExecutables) +size_t CacheBuilder::evictLeafDylibs(uint64_t reductionTarget, std::vector& overflowDylibs) { - // 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; + // build count of how many references there are to each dylib + __block std::map referenceCount; + for (const DylibInfo& dylib : _sortedDylibs) { + dylib.input->mappedFile.mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) { + referenceCount[loadPath] += 1; + }); } - uint64_t t1 = mach_absolute_time(); + // find all dylibs not referenced + std::vector unreferencedDylibs; + for (const DylibInfo& dylib : _sortedDylibs) { + const char* installName = dylib.input->mappedFile.mh->installName(); + if ( referenceCount.count(installName) == 0 ) { + // conservative: sum up all segments except LINKEDIT + __block uint64_t segsSize = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) { + if ( strcmp(info.segName, "__LINKEDIT") != 0 ) + segsSize += info.vmSize; + }); + unreferencedDylibs.push_back({ dylib.input, installName, segsSize }); + } + } + // sort leaf dylibs by size + std::sort(unreferencedDylibs.begin(), unreferencedDylibs.end(), [&](const DylibAndSize& a, const DylibAndSize& b) { + return ( a.size > b.size ); + }); - // make copy of dylib list and sort - std::vector sortedDylibs = makeSortedDylibs(dylibs, _options.dylibOrdering); - std::vector otherOsDylibs = otherOsDylibsInput; + // build set of dylibs that if removed will allow cache to build + for (DylibAndSize& dylib : unreferencedDylibs) { + if ( _options.verbose ) + _diagnostics.warning("to prevent cache overflow, not caching %s", dylib.installName); + _evictions.insert(dylib.input->mappedFile.mh); + // Track the evicted dylibs so we can try build "other" dlopen closures for them. + overflowDylibs.push_back(dylib.input); + if ( dylib.size > reductionTarget ) + break; + reductionTarget -= dylib.size; + } - // assign addresses for each segment of each dylib in new cache - dyld_cache_mapping_info regions[3]; - SegmentMapping segmentMapping = assignSegmentAddresses(sortedDylibs, regions); - while ( cacheOverflow(regions) ) { - if ( !_options.evictLeafDylibsOnOverflow ) { - _diagnostics.error("cache overflow: %lluMB (max %lluMB)", _vmSize / 1024 / 1024, (_archLayout->sharedMemorySize) / 1024 / 1024); - return; + // prune _sortedDylibs + _sortedDylibs.erase(std::remove_if(_sortedDylibs.begin(), _sortedDylibs.end(), [&](const DylibInfo& dylib) { + return (_evictions.count(dylib.input->mappedFile.mh) != 0); + }),_sortedDylibs.end()); + + return _evictions.size(); +} + +// Handles building a list of input files to the CacheBuilder itself. +class CacheInputBuilder { +public: + CacheInputBuilder(const dyld3::closure::FileSystem& fileSystem, + std::string reqArchitecture, dyld3::Platform reqPlatform) + : fileSystem(fileSystem), reqArchitecture(reqArchitecture), reqPlatform(reqPlatform) { } + + // Loads and maps any MachOs in the given list of files. + void loadMachOs(std::vector& inputFiles, + std::vector& dylibsToCache, + std::vector& otherDylibs, + std::vector& executables, + std::vector& couldNotLoadFiles) { + + std::map dylibInstallNameMap; + for (CacheBuilder::InputFile& inputFile : inputFiles) { + dyld3::closure::LoadedFileInfo loadedFileInfo = dyld3::MachOAnalyzer::load(inputFile.diag, fileSystem, inputFile.path, reqArchitecture.c_str(), reqPlatform); + const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)loadedFileInfo.fileContent; + if (ma == nullptr) { + couldNotLoadFiles.emplace_back((CacheBuilder::LoadedMachO){ DyldSharedCache::MappedMachO(), loadedFileInfo, &inputFile }); + continue; + } + + DyldSharedCache::MappedMachO mappedFile(inputFile.path, ma, loadedFileInfo.sliceLen, false, false, + loadedFileInfo.sliceOffset, loadedFileInfo.mtime, loadedFileInfo.inode); + + // The file can be loaded with the given slice, but we may still want to exlude it from the cache. + if (ma->isDylib()) { + std::string installName = ma->installName(); + + // Let the platform exclude the file before we do anything else. + if (platformExcludesInstallName(installName)) { + inputFile.diag.verbose("Platform excluded file\n"); + fileSystem.unloadFile(loadedFileInfo); + continue; + } + + if (!ma->canBePlacedInDyldCache(inputFile.path, ^(const char* msg) { + inputFile.diag.warning("Dylib located at '%s' cannot be placed in cache because: %s", inputFile.path, msg); + })) { + // TODO: Add exclusion lists here? + // Probably not as we already applied the dylib exclusion list. + otherDylibs.emplace_back((CacheBuilder::LoadedMachO){ mappedFile, loadedFileInfo, &inputFile }); + continue; + } + + // Otherwise see if we have another file with this install name + auto iteratorAndInserted = dylibInstallNameMap.insert(std::make_pair(installName, dylibsToCache.size())); + if (iteratorAndInserted.second) { + // We inserted the dylib so we haven't seen another with this name. + if (installName[0] != '@' && installName != inputFile.path) { + inputFile.diag.warning("Dylib located at '%s' has installname '%s'", inputFile.path, installName.c_str()); + } + + dylibsToCache.emplace_back((CacheBuilder::LoadedMachO){ mappedFile, loadedFileInfo, &inputFile }); + } else { + // We didn't insert this one so we've seen it before. + CacheBuilder::LoadedMachO& previousLoadedMachO = dylibsToCache[iteratorAndInserted.first->second]; + inputFile.diag.warning("Multiple dylibs claim installname '%s' ('%s' and '%s')", installName.c_str(), inputFile.path, previousLoadedMachO.mappedFile.runtimePath.c_str()); + + // This is the "Good" one, overwrite + if (inputFile.path == installName) { + // Unload the old one + fileSystem.unloadFile(previousLoadedMachO.loadedFileInfo); + + // And replace with this one. + previousLoadedMachO.mappedFile = mappedFile; + previousLoadedMachO.loadedFileInfo = loadedFileInfo; + } + } + } else if (ma->isBundle()) { + // TODO: Add exclusion lists here? + otherDylibs.emplace_back((CacheBuilder::LoadedMachO){ mappedFile, loadedFileInfo, &inputFile }); + } else if (ma->isDynamicExecutable()) { + if (platformExcludesExecutablePath_macOS(inputFile.path)) { + inputFile.diag.verbose("Platform excluded file\n"); + fileSystem.unloadFile(loadedFileInfo); + continue; + } + executables.emplace_back((CacheBuilder::LoadedMachO){ mappedFile, loadedFileInfo, &inputFile }); + } else { + inputFile.diag.verbose("Unsupported mach file type\n"); + fileSystem.unloadFile(loadedFileInfo); + } } - // 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; + } + +private: + + + + static bool platformExcludesInstallName_macOS(const std::string& installName) { + return false; + } + + static bool platformExcludesInstallName_iOS(const std::string& installName) { + if ( installName == "/System/Library/Caches/com.apple.xpc/sdk.dylib" ) + return true; + if ( installName == "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib" ) + return true; + return false; + } + + static bool platformExcludesInstallName_tvOS(const std::string& installName) { + return platformExcludesInstallName_iOS(installName); + } + + static bool platformExcludesInstallName_watchOS(const std::string& installName) { + return platformExcludesInstallName_iOS(installName); + } + + static bool platformExcludesInstallName_bridgeOS(const std::string& installName) { + return platformExcludesInstallName_iOS(installName); + } + + // Returns true if the current platform requires that this install name be excluded from the shared cache + // Note that this overrides any exclusion from anywhere else. + bool platformExcludesInstallName(const std::string& installName) { + switch (reqPlatform) { + case dyld3::Platform::unknown: + return false; + case dyld3::Platform::macOS: + return platformExcludesInstallName_macOS(installName); + case dyld3::Platform::iOS: + return platformExcludesInstallName_iOS(installName); + case dyld3::Platform::tvOS: + return platformExcludesInstallName_tvOS(installName); + case dyld3::Platform::watchOS: + return platformExcludesInstallName_watchOS(installName); + case dyld3::Platform::bridgeOS: + return platformExcludesInstallName_bridgeOS(installName); + case dyld3::Platform::iOSMac: + return false; + case dyld3::Platform::iOS_simulator: + return false; + case dyld3::Platform::tvOS_simulator: + return false; + case dyld3::Platform::watchOS_simulator: + return false; + } + } + + + + + static bool platformExcludesExecutablePath_macOS(const std::string& path) { + return false; + } + + static bool platformExcludesExecutablePath_iOS(const std::string& path) { + //HACK exclude all launchd and installd variants until we can do something about xpcd_cache.dylib and friends + if (path == "/sbin/launchd" + || path == "/usr/local/sbin/launchd.debug" + || path == "/usr/local/sbin/launchd.development" + || path == "/usr/libexec/installd") { + return true; + } + return false; + } + + static bool platformExcludesExecutablePath_tvOS(const std::string& path) { + return platformExcludesExecutablePath_iOS(path); + } + + static bool platformExcludesExecutablePath_watchOS(const std::string& path) { + return platformExcludesExecutablePath_iOS(path); + } + + static bool platformExcludesExecutablePath_bridgeOS(const std::string& path) { + return platformExcludesExecutablePath_iOS(path); + } + + // Returns true if the current platform requires that this path be excluded from the shared cache + // Note that this overrides any exclusion from anywhere else. + bool platformExcludesExecutablePath(const std::string& path) { + switch (reqPlatform) { + case dyld3::Platform::unknown: + return false; + case dyld3::Platform::macOS: + return platformExcludesExecutablePath_macOS(path); + case dyld3::Platform::iOS: + return platformExcludesExecutablePath_iOS(path); + case dyld3::Platform::tvOS: + return platformExcludesExecutablePath_tvOS(path); + case dyld3::Platform::watchOS: + return platformExcludesExecutablePath_watchOS(path); + case dyld3::Platform::bridgeOS: + return platformExcludesExecutablePath_bridgeOS(path); + case dyld3::Platform::iOSMac: + return false; + case dyld3::Platform::iOS_simulator: + return false; + case dyld3::Platform::tvOS_simulator: + return false; + case dyld3::Platform::watchOS_simulator: + return false; + } + } + + const dyld3::closure::FileSystem& fileSystem; + std::string reqArchitecture; + dyld3::Platform reqPlatform; +}; + +static void verifySelfContained(std::vector& dylibsToCache, + std::vector& otherDylibs, + std::vector& couldNotLoadFiles) +{ + // build map of dylibs + __block std::map knownDylibs; + __block std::map allDylibs; + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + knownDylibs.insert({ dylib.mappedFile.runtimePath, &dylib }); + allDylibs.insert({ dylib.mappedFile.runtimePath, &dylib }); + if (const char* installName = dylib.mappedFile.mh->installName()) { + knownDylibs.insert({ installName, &dylib }); + allDylibs.insert({ installName, &dylib }); + } + } + + for (const CacheBuilder::LoadedMachO& dylib : otherDylibs) { + allDylibs.insert({ dylib.mappedFile.runtimePath, &dylib }); + if (const char* installName = dylib.mappedFile.mh->installName()) + allDylibs.insert({ installName, &dylib }); + } + + for (const CacheBuilder::LoadedMachO& dylib : couldNotLoadFiles) { + allDylibs.insert({ dylib.inputFile->path, &dylib }); + } + + // check all dependencies to assure every dylib in cache only depends on other dylibs in cache + __block std::map> badDylibs; + __block bool doAgain = true; + while ( doAgain ) { + doAgain = false; + // scan dylib list making sure all dependents are in dylib list + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + if ( badDylibs.count(dylib.mappedFile.runtimePath) != 0 ) + continue; + dylib.mappedFile.mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if (isWeak) + return; + if ( knownDylibs.count(loadPath) == 0 ) { + badDylibs[dylib.mappedFile.runtimePath].insert(std::string("Could not find dependency '") + loadPath + "'"); + knownDylibs.erase(dylib.mappedFile.runtimePath); + knownDylibs.erase(dylib.mappedFile.mh->installName()); + doAgain = true; + } + }); + } + } + + // Now walk the dylibs which depend on missing dylibs and see if any of them are required binaries. + for (auto badDylibsIterator : badDylibs) { + const std::string& dylibRuntimePath = badDylibsIterator.first; + auto requiredDylibIterator = allDylibs.find(dylibRuntimePath); + if (requiredDylibIterator == allDylibs.end()) + continue; + if (!requiredDylibIterator->second->inputFile->mustBeIncluded()) + continue; + // This dylib is required so mark all dependencies as requried too + __block std::vector worklist; + worklist.push_back(requiredDylibIterator->second); + while (!worklist.empty()) { + const CacheBuilder::LoadedMachO* dylib = worklist.back(); + worklist.pop_back(); + if (!dylib->mappedFile.mh) + continue; + dylib->mappedFile.mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + if (isWeak) + return; + auto dylibIterator = allDylibs.find(loadPath); + if (dylibIterator != allDylibs.end()) { + if (dylibIterator->second->inputFile->state == CacheBuilder::InputFile::Unset) { + dylibIterator->second->inputFile->state = CacheBuilder::InputFile::MustBeIncludedForDependent; + worklist.push_back(dylibIterator->second); + } + } }); } + } - // 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; + // FIXME: Make this an option we can pass in + const bool evictLeafDylibs = true; + if (evictLeafDylibs) { + doAgain = true; + while ( doAgain ) { + doAgain = false; + + // build count of how many references there are to each dylib + __block std::set referencedDylibs; + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + if ( badDylibs.count(dylib.mappedFile.runtimePath) != 0 ) + continue; + dylib.mappedFile.mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool &stop) { + referencedDylibs.insert(loadPath); }); - unreferencedDylibs.push_back({installName, segsSize}); + } + + // find all dylibs not referenced + std::vector unreferencedDylibs; + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + if ( badDylibs.count(dylib.mappedFile.runtimePath) != 0 ) + continue; + const char* installName = dylib.mappedFile.mh->installName(); + if ( (referencedDylibs.count(installName) == 0) && (dylib.inputFile->state == CacheBuilder::InputFile::MustBeExcludedIfUnused) ) { + badDylibs[dylib.mappedFile.runtimePath].insert(std::string("It has been explicitly excluded as it is unused")); + doAgain = true; + } } } - // 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; + // Move bad dylibs from dylibs to cache to other dylibs. + for (const CacheBuilder::LoadedMachO& dylib : dylibsToCache) { + auto i = badDylibs.find(dylib.mappedFile.runtimePath); + if ( i != badDylibs.end()) { + otherDylibs.push_back(dylib); + for (const std::string& reason : i->second ) + otherDylibs.back().inputFile->diag.warning("Dylib located at '%s' not placed in shared cache because: %s", dylib.mappedFile.runtimePath.c_str(), reason.c_str()); + } + } + + const auto& badDylibsLambdaRef = badDylibs; + dylibsToCache.erase(std::remove_if(dylibsToCache.begin(), dylibsToCache.end(), [&](const CacheBuilder::LoadedMachO& dylib) { + if (badDylibsLambdaRef.find(dylib.mappedFile.runtimePath) != badDylibsLambdaRef.end()) + return true; + return false; + }), dylibsToCache.end()); +} + +// This is the new build API which takes the raw files (which could be FAT) and tries to build a cache from them. +// We should remove the other build() method, or make it private so that this can wrap it. +void CacheBuilder::build(std::vector& inputFiles, + std::vector& aliases) { + // First filter down to files which are actually MachO's + CacheInputBuilder cacheInputBuilder(_fileSystem, _archLayout->archName, _options.platform); + + std::vector dylibsToCache; + std::vector otherDylibs; + std::vector executables; + std::vector couldNotLoadFiles; + cacheInputBuilder.loadMachOs(inputFiles, dylibsToCache, otherDylibs, executables, couldNotLoadFiles); + + verifySelfContained(dylibsToCache, otherDylibs, couldNotLoadFiles); + + // Check for required binaries before we try to build the cache + if (!_diagnostics.hasError()) { + // If we succeeded in building, then now see if there was a missing required file, and if so why its missing. + std::string errorString; + for (const LoadedMachO& dylib : otherDylibs) { + if (dylib.inputFile->mustBeIncluded()) { + // An error loading a required file must be propagated up to the top level diagnostic handler. + bool gotWarning = false; + for (const std::string& warning : dylib.inputFile->diag.warnings()) { + gotWarning = true; + std::string message = warning; + if (message.back() == '\n') + message.pop_back(); + if (!errorString.empty()) + errorString += "ERROR: "; + errorString += "Required binary was not included in the shared cache '" + std::string(dylib.inputFile->path) + "' because: " + message + "\n"; + } + if (!gotWarning) { + if (!errorString.empty()) + errorString += "ERROR: "; + errorString += "Required binary was not included in the shared cache '" + std::string(dylib.inputFile->path) + "' because: 'unknown error. Please report to dyld'\n"; + } + } } - // 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() ) { - _evictions.insert(parser.header()); - otherOsDylibs.push_back(*it); - sortedDylibs.erase(it); - break; + for (const LoadedMachO& dylib : couldNotLoadFiles) { + if (dylib.inputFile->mustBeIncluded()) { + if (dylib.inputFile->diag.hasError()) { + if (!errorString.empty()) + errorString += "ERROR: "; + errorString += "Required binary was not included in the shared cache '" + std::string(dylib.inputFile->path) + "' because: " + dylib.inputFile->diag.errorMessage() + "\n"; + } else { + if (!errorString.empty()) + errorString += "ERROR: "; + errorString += "Required binary was not included in the shared cache '" + std::string(dylib.inputFile->path) + "' because: 'unknown error. Please report to dyld'\n"; + } } } - // re-layout cache - segmentMapping = assignSegmentAddresses(sortedDylibs, regions); - if ( unreferencedDylibs.size() == 0 && 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; + if (!errorString.empty()) { + _diagnostics.error("%s", errorString.c_str()); } } - // 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 ) { + if (!_diagnostics.hasError()) + build(dylibsToCache, otherDylibs, executables, aliases); + + if (!_diagnostics.hasError()) { + // If we succeeded in building, then now see if there was a missing required file, and if so why its missing. + std::string errorString; + for (CacheBuilder::InputFile& inputFile : inputFiles) { + if (inputFile.mustBeIncluded() && inputFile.diag.hasError()) { + // An error loading a required file must be propagated up to the top level diagnostic handler. + std::string message = inputFile.diag.errorMessage(); + if (message.back() == '\n') + message.pop_back(); + errorString += "Required binary was not included in the shared cache '" + std::string(inputFile.path) + "' because: " + message + "\n"; + } + } + if (!errorString.empty()) { + _diagnostics.error("%s", errorString.c_str()); + } + } + + // Add all the warnings from the input files to the top level warnings on the main diagnostics object. + for (CacheBuilder::InputFile& inputFile : inputFiles) { + for (const std::string& warning : inputFile.diag.warnings()) + _diagnostics.warning("%s", warning.c_str()); + } + + // Clean up the loaded files + for (LoadedMachO& loadedMachO : dylibsToCache) + _fileSystem.unloadFile(loadedMachO.loadedFileInfo); + for (LoadedMachO& loadedMachO : otherDylibs) + _fileSystem.unloadFile(loadedMachO.loadedFileInfo); + for (LoadedMachO& loadedMachO : executables) + _fileSystem.unloadFile(loadedMachO.loadedFileInfo); +} + +void CacheBuilder::build(const std::vector& dylibs, + const std::vector& otherOsDylibsInput, + const std::vector& osExecutables, + std::vector& aliases) { + + std::vector dylibsToCache; + std::vector otherDylibs; + std::vector executables; + + for (const DyldSharedCache::MappedMachO& mappedMachO : dylibs) { + dyld3::closure::LoadedFileInfo loadedFileInfo; + loadedFileInfo.fileContent = mappedMachO.mh; + loadedFileInfo.fileContentLen = mappedMachO.length; + loadedFileInfo.sliceOffset = mappedMachO.sliceFileOffset; + loadedFileInfo.sliceLen = mappedMachO.length; + loadedFileInfo.inode = mappedMachO.inode; + loadedFileInfo.mtime = mappedMachO.modTime; + loadedFileInfo.path = mappedMachO.runtimePath.c_str(); + dylibsToCache.emplace_back((LoadedMachO){ mappedMachO, loadedFileInfo, nullptr }); + } + + for (const DyldSharedCache::MappedMachO& mappedMachO : otherOsDylibsInput) { + dyld3::closure::LoadedFileInfo loadedFileInfo; + loadedFileInfo.fileContent = mappedMachO.mh; + loadedFileInfo.fileContentLen = mappedMachO.length; + loadedFileInfo.sliceOffset = mappedMachO.sliceFileOffset; + loadedFileInfo.sliceLen = mappedMachO.length; + loadedFileInfo.inode = mappedMachO.inode; + loadedFileInfo.mtime = mappedMachO.modTime; + loadedFileInfo.path = mappedMachO.runtimePath.c_str(); + otherDylibs.emplace_back((LoadedMachO){ mappedMachO, loadedFileInfo, nullptr }); + } + + for (const DyldSharedCache::MappedMachO& mappedMachO : osExecutables) { + dyld3::closure::LoadedFileInfo loadedFileInfo; + loadedFileInfo.fileContent = mappedMachO.mh; + loadedFileInfo.fileContentLen = mappedMachO.length; + loadedFileInfo.sliceOffset = mappedMachO.sliceFileOffset; + loadedFileInfo.sliceLen = mappedMachO.length; + loadedFileInfo.inode = mappedMachO.inode; + loadedFileInfo.mtime = mappedMachO.modTime; + loadedFileInfo.path = mappedMachO.runtimePath.c_str(); + executables.emplace_back((LoadedMachO){ mappedMachO, loadedFileInfo, nullptr }); + } + + build(dylibsToCache, otherDylibs, executables, aliases); +} + +void CacheBuilder::build(const std::vector& dylibs, + const std::vector& otherOsDylibsInput, + const std::vector& osExecutables, + std::vector& aliases) +{ + // 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; + } + uint64_t t1 = mach_absolute_time(); + + // make copy of dylib list and sort + makeSortedDylibs(dylibs, _options.dylibOrdering); + + // allocate space used by largest possible cache plus room for LINKEDITS before optimization + _allocatedBufferSize = _archLayout->sharedMemorySize * 1.50; + if ( vm_allocate(mach_task_self(), &_fullAllocatedBuffer, _allocatedBufferSize, VM_FLAGS_ANYWHERE) != 0 ) { _diagnostics.error("could not allocate buffer"); return; } - _currentFileSize = _allocatedBufferSize; - // write unoptimized cache - writeCacheHeader(regions, sortedDylibs, segmentMapping); - copyRawSegments(sortedDylibs, segmentMapping); - adjustAllImagesForNewSegmentLocations(sortedDylibs, segmentMapping); + // assign addresses for each segment of each dylib in new cache + assignSegmentAddresses(); + std::vector overflowDylibs; + while ( cacheOverflowAmount() != 0 ) { + if ( !_options.evictLeafDylibsOnOverflow ) { + _diagnostics.error("cache overflow by %lluMB", cacheOverflowAmount() / 1024 / 1024); + return; + } + size_t evictionCount = evictLeafDylibs(cacheOverflowAmount(), overflowDylibs); + // re-layout cache + for (DylibInfo& dylib : _sortedDylibs) + dylib.cacheLocation.clear(); + assignSegmentAddresses(); + + _diagnostics.verbose("cache overflow, evicted %lu leaf dylibs\n", evictionCount); + } + markPaddingInaccessible(); + + // copy all segments into cache + uint64_t t2 = mach_absolute_time(); + writeCacheHeader(); + copyRawSegments(); + + // rebase all dylibs for new location in cache + uint64_t t3 = mach_absolute_time(); + _aslrTracker.setDataRegion(_readWriteRegion.buffer, _readWriteRegion.sizeInUse); + adjustAllImagesForNewSegmentLocations(); if ( _diagnostics.hasError() ) return; - bindAllImagesInCacheFile(regions); + // build ImageArray for dyld3, which has side effect of binding all cached dylibs + uint64_t t4 = mach_absolute_time(); + buildImageArray(aliases); if ( _diagnostics.hasError() ) return; // optimize ObjC + uint64_t t5 = mach_absolute_time(); + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; if ( _options.optimizeObjC ) - optimizeObjC(_buffer, _archLayout->is64, _options.optimizeStubs, _pointersForASLR, _diagnostics); + optimizeObjC(); if ( _diagnostics.hasError() ) return; + // optimize away stubs + uint64_t t6 = mach_absolute_time(); 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) { + const uint64_t* p = (uint64_t*)((uint8_t*)dyldCache + dyldCache->header.branchPoolsOffset); + for (uint32_t i=0; i < dyldCache->header.branchPoolsCount; ++i) { uint64_t poolAddr = p[i]; branchPoolStartAddrs.push_back(poolAddr); branchPoolOffsets.push_back(poolAddr - cacheStartAddress); } - bypassStubs(_buffer, branchPoolStartAddrs, _s_neverStubEliminate, _diagnostics); + optimizeAwayStubs(branchPoolStartAddrs, _branchPoolsLinkEditStartAddr); } - 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) + + // FIPS seal corecrypto, This must be done after stub elimination (so that __TEXT,__text is not changed after sealing) 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(); + uint64_t t7 = mach_absolute_time(); + optimizeLinkedit(branchPoolOffsets); - // 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); + // copy ImageArray to end of read-only region + addImageArray(); if ( _diagnostics.hasError() ) return; - addCachedDylibsImageGroup(dylibGroup); - if ( _diagnostics.hasError() ) - return; - - 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); + // compute and add dlopen closures for all other dylibs + addOtherImageArray(otherOsDylibsInput, overflowDylibs); if ( _diagnostics.hasError() ) return; - addCachedOtherDylibsImageGroup(otherGroup); - if ( _diagnostics.hasError() ) - return; - - 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); + // compute and add launch closures to end of read-only region + uint64_t t8 = mach_absolute_time(); + addClosures(osExecutables); if ( _diagnostics.hasError() ) return; - 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; + // update final readOnly region size + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(_readExecuteRegion.buffer + dyldCache->header.mappingOffset); + mappings[2].size = _readOnlyRegion.sizeInUse; + if ( _options.excludeLocalSymbols ) + dyldCache->header.localSymbolsOffset = _readOnlyRegion.cacheFileOffset + _readOnlyRegion.sizeInUse; - // record cache bounds - _buffer->header.sharedRegionStart = _archLayout->sharedMemoryStart; - _buffer->header.sharedRegionSize = _archLayout->sharedMemorySize; + // record max slide now that final size is established 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); + uint64_t maxSlide0 = 0x60000000 - _readExecuteRegion.sizeInUse; // TEXT region has 1.5GB region + uint64_t maxSlide1 = 0x40000000 - _readWriteRegion.sizeInUse; + uint64_t maxSlide2 = 0x3FE00000 - _readOnlyRegion.sizeInUse; + dyldCache->header.maxSlide = std::min(std::min(maxSlide0, maxSlide1), maxSlide2); } else { - _buffer->header.maxSlide = (_archLayout->sharedMemoryStart + _archLayout->sharedMemorySize) - (mappings[2].address + mappings[2].size); + dyldCache->header.maxSlide = (_archLayout->sharedMemoryStart + _archLayout->sharedMemorySize) - (_readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse); } - // 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); + uint64_t t9 = 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 SUPPORT_ARCH_arm64e + if ( strcmp(_archLayout->archName, "arm64e") == 0 ) + writeSlideInfoV3(_aslrTracker.bitmap(), _aslrTracker.dataPageCount()); + else +#endif + if ( _archLayout->is64 ) + writeSlideInfoV2>(_aslrTracker.bitmap(), _aslrTracker.dataPageCount()); + else +#if SUPPORT_ARCH_arm64_32 + if ( strcmp(_archLayout->archName, "arm64_32") == 0 ) + writeSlideInfoV4>(_aslrTracker.bitmap(), _aslrTracker.dataPageCount()); + else +#endif + writeSlideInfoV2>(_aslrTracker.bitmap(), _aslrTracker.dataPageCount()); } - 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; + uint64_t t10 = mach_absolute_time(); // 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); + if ( cacheOverflowAmount() != 0 ) { + _diagnostics.error("cache overflow after optimizations 0x%llX -> 0x%llX", _readExecuteRegion.unslidLoadAddress, _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse); return; } @@ -469,31 +898,26 @@ void CacheBuilder::build(const std::vector& dylibs if ( _diagnostics.hasError() ) return; - uint64_t t8 = mach_absolute_time(); + uint64_t t11 = 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; + fprintf(stderr, "time to layout cache: %ums\n", absolutetime_to_milliseconds(t2-t1)); + fprintf(stderr, "time to copy cached dylibs into buffer: %ums\n", absolutetime_to_milliseconds(t3-t2)); + fprintf(stderr, "time to adjust segments for new split locations: %ums\n", absolutetime_to_milliseconds(t4-t3)); + fprintf(stderr, "time to bind all images: %ums\n", absolutetime_to_milliseconds(t5-t4)); + fprintf(stderr, "time to optimize Objective-C: %ums\n", absolutetime_to_milliseconds(t6-t5)); + fprintf(stderr, "time to do stub elimination: %ums\n", absolutetime_to_milliseconds(t7-t6)); + fprintf(stderr, "time to optimize LINKEDITs: %ums\n", absolutetime_to_milliseconds(t8-t7)); + fprintf(stderr, "time to build %lu closures: %ums\n", osExecutables.size(), absolutetime_to_milliseconds(t9-t8)); + fprintf(stderr, "time to compute slide info: %ums\n", absolutetime_to_milliseconds(t10-t9)); + fprintf(stderr, "time to compute UUID and codesign cache file: %ums\n", absolutetime_to_milliseconds(t11-t10)); } return; } -void CacheBuilder::writeCacheHeader(const dyld_cache_mapping_info regions[3], const std::vector& dylibs, const SegmentMapping& segmentMappings) +void CacheBuilder::writeCacheHeader() { // "dyld_v1" + spaces + archName(), with enough spaces to pad to 15 bytes std::string magic = "dyld_v1"; @@ -502,60 +926,83 @@ void CacheBuilder::writeCacheHeader(const dyld_cache_mapping_info regions[3], co 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]; + dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)_readExecuteRegion.buffer; + memcpy(dyldCacheHeader->magic, magic.c_str(), 16); + dyldCacheHeader->mappingOffset = sizeof(dyld_cache_header); + dyldCacheHeader->mappingCount = 3; + dyldCacheHeader->imagesOffset = (uint32_t)(dyldCacheHeader->mappingOffset + 3*sizeof(dyld_cache_mapping_info) + sizeof(uint64_t)*_branchPoolStarts.size()); + dyldCacheHeader->imagesCount = (uint32_t)_sortedDylibs.size() + _aliasCount; + dyldCacheHeader->dyldBaseAddress = 0; + dyldCacheHeader->codeSignatureOffset = 0; + dyldCacheHeader->codeSignatureSize = 0; + dyldCacheHeader->slideInfoOffset = _slideInfoFileOffset; + dyldCacheHeader->slideInfoSize = _slideInfoBufferSizeAllocated; + dyldCacheHeader->localSymbolsOffset = 0; + dyldCacheHeader->localSymbolsSize = 0; + dyldCacheHeader->cacheType = _options.optimizeStubs ? kDyldSharedCacheTypeProduction : kDyldSharedCacheTypeDevelopment; + dyldCacheHeader->accelerateInfoAddr = 0; + dyldCacheHeader->accelerateInfoSize = 0; + bzero(dyldCacheHeader->uuid, 16);// overwritten later by recomputeCacheUUID() + dyldCacheHeader->branchPoolsOffset = dyldCacheHeader->mappingOffset + 3*sizeof(dyld_cache_mapping_info); + dyldCacheHeader->branchPoolsCount = (uint32_t)_branchPoolStarts.size(); + dyldCacheHeader->imagesTextOffset = dyldCacheHeader->imagesOffset + sizeof(dyld_cache_image_info)*dyldCacheHeader->imagesCount; + dyldCacheHeader->imagesTextCount = _sortedDylibs.size(); + dyldCacheHeader->dylibsImageGroupAddr = 0; + dyldCacheHeader->dylibsImageGroupSize = 0; + dyldCacheHeader->otherImageGroupAddr = 0; + dyldCacheHeader->otherImageGroupSize = 0; + dyldCacheHeader->progClosuresAddr = 0; + dyldCacheHeader->progClosuresSize = 0; + dyldCacheHeader->progClosuresTrieAddr = 0; + dyldCacheHeader->progClosuresTrieSize = 0; + dyldCacheHeader->platform = (uint8_t)_options.platform; + dyldCacheHeader->formatVersion = dyld3::closure::kFormatVersion; + dyldCacheHeader->dylibsExpectedOnDisk = !_options.dylibsRemovedDuringMastering; + dyldCacheHeader->simulator = _options.forSimulator; + dyldCacheHeader->locallyBuiltCache = _options.isLocallyBuiltCache; + dyldCacheHeader->formatVersion = dyld3::closure::kFormatVersion; + dyldCacheHeader->sharedRegionStart = _archLayout->sharedMemoryStart; + dyldCacheHeader->sharedRegionSize = _archLayout->sharedMemorySize; + + // fill in mappings + dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(_readExecuteRegion.buffer + dyldCacheHeader->mappingOffset); + mappings[0].address = _readExecuteRegion.unslidLoadAddress; + mappings[0].fileOffset = 0; + mappings[0].size = _readExecuteRegion.sizeInUse; + mappings[0].maxProt = VM_PROT_READ | VM_PROT_EXECUTE; + mappings[0].initProt = VM_PROT_READ | VM_PROT_EXECUTE; + mappings[1].address = _readWriteRegion.unslidLoadAddress; + mappings[1].fileOffset = _readExecuteRegion.sizeInUse; + mappings[1].size = _readWriteRegion.sizeInUse; + mappings[1].maxProt = VM_PROT_READ | VM_PROT_WRITE; + mappings[1].initProt = VM_PROT_READ | VM_PROT_WRITE; + mappings[2].address = _readOnlyRegion.unslidLoadAddress; + mappings[2].fileOffset = _readExecuteRegion.sizeInUse + _readWriteRegion.sizeInUse; + mappings[2].size = _readOnlyRegion.sizeInUse; + mappings[2].maxProt = VM_PROT_READ; + mappings[2].initProt = VM_PROT_READ; // fill in branch pool addresses - uint64_t* p = (uint64_t*)((char*)_buffer + _buffer->header.branchPoolsOffset); + uint64_t* p = (uint64_t*)(_readExecuteRegion.buffer + dyldCacheHeader->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; + dyld_cache_image_info* images = (dyld_cache_image_info*)(_readExecuteRegion.buffer + dyldCacheHeader->imagesOffset); + for (const DylibInfo& dylib : _sortedDylibs) { + const char* installName = dylib.input->mappedFile.mh->installName(); + images->address = dylib.cacheLocation[0].dstCacheUnslidAddress; if ( _options.dylibsRemovedDuringMastering ) { images->modTime = 0; images->inode = pathHash(installName); } else { - images->modTime = dylib.modTime; - images->inode = dylib.inode; + images->modTime = dylib.input->mappedFile.modTime; + images->inode = dylib.input->mappedFile.inode; } - uint32_t installNameOffsetInTEXT = (uint32_t)(installName - (char*)dylib.mh); - images->pathFileOffset = (uint32_t)segs[0].dstCacheOffset + installNameOffsetInTEXT; + uint32_t installNameOffsetInTEXT = (uint32_t)(installName - (char*)dylib.input->mappedFile.mh); + images->pathFileOffset = (uint32_t)dylib.cacheLocation[0].dstCacheFileOffset + installNameOffsetInTEXT; ++images; } // append aliases image records and strings @@ -582,547 +1029,603 @@ void CacheBuilder::writeCacheHeader(const dyld_cache_mapping_info regions[3], co } */ // 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()); + dyld_cache_image_text_info* textImages = (dyld_cache_image_text_info*)(_readExecuteRegion.buffer + dyldCacheHeader->imagesTextOffset); + uint32_t stringOffset = (uint32_t)(dyldCacheHeader->imagesTextOffset + sizeof(dyld_cache_image_text_info) * _sortedDylibs.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; + for (const DylibInfo& dylib : _sortedDylibs) { + dylib.input->mappedFile.mh->getUuid(textImages->uuid); + textImages->loadAddress = dylib.cacheLocation[0].dstCacheUnslidAddress; + textImages->textSegmentSize = (uint32_t)dylib.cacheLocation[0].dstCacheSegmentSize; textImages->pathOffset = stringOffset; - const char* installName = parser.installName(); - ::strcpy((char*)_buffer + stringOffset, installName); + const char* installName = dylib.input->mappedFile.mh->installName(); + ::strcpy((char*)_readExecuteRegion.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); + const dyld_cache_image_info* firstImage = (dyld_cache_image_info*)(_readExecuteRegion.buffer + dyldCacheHeader->imagesOffset); assert(stringOffset <= (firstImage->address - mappings[0].address)); } - -void CacheBuilder::copyRawSegments(const std::vector& dylibs, const SegmentMapping& mapping) +void CacheBuilder::copyRawSegments() { - 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); + const bool log = false; + dispatch_apply(_sortedDylibs.size(), DISPATCH_APPLY_AUTO, ^(size_t index) { + const DylibInfo& dylib = _sortedDylibs[index]; + for (const SegmentMappingInfo& info : dylib.cacheLocation) { + if (log) 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, info.dstSegment, info.dstCacheUnslidAddress, dylib.input->mappedFile.runtimePath.c_str()); + ::memcpy(info.dstSegment, info.srcSegment, info.copySegmentSize); + if (uint64_t paddingSize = info.dstCacheSegmentSize - info.copySegmentSize) { + ::memset((char*)info.dstSegment + info.copySegmentSize, 0, paddingSize); + } } - } -} - -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]) +void CacheBuilder::adjustAllImagesForNewSegmentLocations() { - 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 std::vector diags; + diags.resize(_sortedDylibs.size()); - __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; + if (_options.platform == dyld3::Platform::macOS) { + dispatch_apply(_sortedDylibs.size(), DISPATCH_APPLY_AUTO, ^(size_t index) { + const DylibInfo& dylib = _sortedDylibs[index]; + adjustDylibSegments(dylib, diags[index]); }); - 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()); + } else { + // Note this has to be done in serial because the LOH Tracker isn't thread safe + for (size_t index = 0; index != _sortedDylibs.size(); ++index) { + const DylibInfo& dylib = _sortedDylibs[index]; + adjustDylibSegments(dylib, diags[index]); } - 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); + for (const Diagnostics& diag : diags) { + if ( diag.hasError() ) { + _diagnostics.error("%s", diag.errorMessage().c_str()); + break; } - 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]) +void CacheBuilder::assignSegmentAddresses() { // 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 ) + 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); + startOffset += sizeof(dyld_cache_image_info) * _sortedDylibs.size(); + startOffset += sizeof(dyld_cache_image_text_info) * _sortedDylibs.size(); + for (const DylibInfo& dylib : _sortedDylibs) { + startOffset += (strlen(dylib.input->mappedFile.mh->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 - + _readExecuteRegion.buffer = (uint8_t*)_fullAllocatedBuffer; + _readExecuteRegion.bufferSize = 0; + _readExecuteRegion.sizeInUse = 0; + _readExecuteRegion.unslidLoadAddress = _archLayout->sharedMemoryStart; + _readExecuteRegion.cacheFileOffset = 0; + __block uint64_t addr = _readExecuteRegion.unslidLoadAddress + 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) ) + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.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) ) { + if ( (_archLayout->branchPoolTextSize != 0) && ((addr + segInfo.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; + addr = align(addr, std::max((int)segInfo.p2align, (int)12)); + uint64_t offsetInRegion = addr - _readExecuteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readExecuteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)offsetInRegion; + loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12); + loc.copySegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12); + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; }); } // align TEXT region end uint64_t endTextAddress = align(addr, _archLayout->sharedRegionAlignP2); - regions[0].size = endTextAddress - regions[0].address; + _readExecuteRegion.bufferSize = endTextAddress - _readExecuteRegion.unslidLoadAddress; + _readExecuteRegion.sizeInUse = _readExecuteRegion.bufferSize; // 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; + _readWriteRegion.buffer = (uint8_t*)_fullAllocatedBuffer + addr - _archLayout->sharedMemoryStart; + _readWriteRegion.bufferSize = 0; + _readWriteRegion.sizeInUse = 0; + _readWriteRegion.unslidLoadAddress = addr; + _readWriteRegion.cacheFileOffset = _readExecuteRegion.sizeInUse; // 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) ) + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != (VM_PROT_READ | VM_PROT_WRITE) ) return; - if ( strcmp(segName, "__DATA_CONST") != 0 ) + if ( strcmp(segInfo.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; + addr = align(addr, segInfo.p2align); + size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections); + uint64_t offsetInRegion = addr - _readWriteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readWriteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readWriteRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.copySegmentSize = (uint32_t)copySize; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.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) ) + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != (VM_PROT_READ | VM_PROT_WRITE) ) return; - if ( strcmp(segName, "__DATA_CONST") == 0 ) + if ( strcmp(segInfo.segName, "__DATA_CONST") == 0 ) return; - if ( strcmp(segName, "__DATA_DIRTY") == 0 ) + if ( strcmp(segInfo.segName, "__DATA_DIRTY") == 0 ) return; if ( dataConstSegmentCount > 10 ) { // Pack __DATA segments only if we also have __DATA_CONST segments - addr = align(addr, p2align); + addr = align(addr, segInfo.p2align); } else { // Keep __DATA segments 4K or more aligned - addr = align(addr, std::max(p2align, (uint8_t)12)); + addr = align(addr, std::max((int)segInfo.p2align, (int)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; + size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections); + uint64_t offsetInRegion = addr - _readWriteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readWriteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readWriteRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.copySegmentSize = (uint32_t)copySize; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; }); } - // layout all __DATA_DIRTY segments, sorted + // layout all __DATA_DIRTY segments, sorted (FIXME) + const size_t dylibCount = _sortedDylibs.size(); + uint32_t dirtyDataSortIndexes[dylibCount]; + for (size_t i=0; i < dylibCount; ++i) + dirtyDataSortIndexes[i] = (uint32_t)i; + std::sort(&dirtyDataSortIndexes[0], &dirtyDataSortIndexes[dylibCount], [&](const uint32_t& a, const uint32_t& b) { + const auto& orderA = _options.dirtyDataSegmentOrdering.find(_sortedDylibs[a].input->mappedFile.runtimePath); + const auto& orderB = _options.dirtyDataSegmentOrdering.find(_sortedDylibs[b].input->mappedFile.runtimePath); + bool foundA = (orderA != _options.dirtyDataSegmentOrdering.end()); + bool foundB = (orderB != _options.dirtyDataSegmentOrdering.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 _sortedDylibs[a].input->mappedFile.runtimePath < _sortedDylibs[b].input->mappedFile.runtimePath; + }); 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) ) + for (size_t i=0; i < dylibCount; ++i) { + DylibInfo& dylib = _sortedDylibs[dirtyDataSortIndexes[i]]; + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != (VM_PROT_READ | VM_PROT_WRITE) ) return; - if ( strcmp(segName, "__DATA_DIRTY") != 0 ) + if ( strcmp(segInfo.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; + addr = align(addr, segInfo.p2align); + size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections); + uint64_t offsetInRegion = addr - _readWriteRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readWriteRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readWriteRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.copySegmentSize = (uint32_t)copySize; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.dstCacheSegmentSize; }); } // align DATA region end uint64_t endDataAddress = align(addr, _archLayout->sharedRegionAlignP2); - regions[1].size = endDataAddress - regions[1].address; + _readWriteRegion.bufferSize = endDataAddress - _readWriteRegion.unslidLoadAddress; + _readWriteRegion.sizeInUse = _readWriteRegion.bufferSize; // 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; + _readOnlyRegion.buffer = (uint8_t*)_fullAllocatedBuffer + addr - _archLayout->sharedMemoryStart; + _readOnlyRegion.bufferSize = 0; + _readOnlyRegion.sizeInUse = 0; + _readOnlyRegion.unslidLoadAddress = addr; + _readOnlyRegion.cacheFileOffset = _readWriteRegion.cacheFileOffset + _readWriteRegion.sizeInUse; // 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; + size_t slideInfoSize = sizeof(dyld_cache_slide_info); + slideInfoSize = std::max(slideInfoSize, sizeof(dyld_cache_slide_info2)); + slideInfoSize = std::max(slideInfoSize, sizeof(dyld_cache_slide_info3)); + slideInfoSize = std::max(slideInfoSize, sizeof(dyld_cache_slide_info4)); + _slideInfoBufferSizeAllocated = align(slideInfoSize + (_readWriteRegion.sizeInUse/4096) * _archLayout->slideInfoBytesPerPage, _archLayout->sharedRegionAlignP2); + _slideInfoFileOffset = _readOnlyRegion.cacheFileOffset; 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 ) + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != VM_PROT_READ ) return; - if ( strcmp(segName, "__LINKEDIT") == 0 ) + if ( strcmp(segInfo.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; + addr = align(addr, std::max((int)segInfo.p2align, (int)12)); + uint64_t offsetInRegion = addr - _readOnlyRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readOnlyRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readOnlyRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12); + loc.copySegmentSize = (uint32_t)segInfo.sizeOfSections; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.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 ) + // layout all LINKEDIT segments (after other read-only segments), aligned to 16KB + addr = align(addr, 14); + _nonLinkEditReadOnlySize = addr - _readOnlyRegion.unslidLoadAddress; + for (DylibInfo& dylib : _sortedDylibs) { + __block uint64_t textSegVmAddr = 0; + dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) + textSegVmAddr = segInfo.vmAddr; + if ( segInfo.protections != VM_PROT_READ ) return; - if ( strcmp(segName, "__LINKEDIT") != 0 ) + if ( strcmp(segInfo.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; + addr = align(addr, std::max((int)segInfo.p2align, (int)12)); + size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections); + uint64_t offsetInRegion = addr - _readOnlyRegion.unslidLoadAddress; + SegmentMappingInfo loc; + loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr; + loc.segName = segInfo.segName; + loc.dstSegment = _readOnlyRegion.buffer + offsetInRegion; + loc.dstCacheUnslidAddress = addr; + loc.dstCacheFileOffset = (uint32_t)(_readOnlyRegion.cacheFileOffset + offsetInRegion); + loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12); + loc.copySegmentSize = (uint32_t)copySize; + loc.srcSegmentIndex = segInfo.segIndex; + dylib.cacheLocation.push_back(loc); + addr += loc.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; + // align r/o region end + uint64_t endReadOnlyAddress = align(addr, _archLayout->sharedRegionAlignP2); + _readOnlyRegion.bufferSize = endReadOnlyAddress - _readOnlyRegion.unslidLoadAddress; + _readOnlyRegion.sizeInUse = _readOnlyRegion.bufferSize; + + //fprintf(stderr, "RX region=%p -> %p, logical addr=0x%llX\n", _readExecuteRegion.buffer, _readExecuteRegion.buffer+_readExecuteRegion.bufferSize, _readExecuteRegion.unslidLoadAddress); + //fprintf(stderr, "RW region=%p -> %p, logical addr=0x%llX\n", _readWriteRegion.buffer, _readWriteRegion.buffer+_readWriteRegion.bufferSize, _readWriteRegion.unslidLoadAddress); + //fprintf(stderr, "RO region=%p -> %p, logical addr=0x%llX\n", _readOnlyRegion.buffer, _readOnlyRegion.buffer+_readOnlyRegion.bufferSize, _readOnlyRegion.unslidLoadAddress); + + // sort SegmentMappingInfo for each image to be in the same order as original segments + for (DylibInfo& dylib : _sortedDylibs) { + std::sort(dylib.cacheLocation.begin(), dylib.cacheLocation.end(), [&](const SegmentMappingInfo& a, const SegmentMappingInfo& b) { + return a.srcSegmentIndex < b.srcSegmentIndex; + }); + } +} + +void CacheBuilder::markPaddingInaccessible() +{ + // region between RX and RW + uint8_t* startPad1 = _readExecuteRegion.buffer+_readExecuteRegion.sizeInUse; + uint8_t* endPad1 = _readWriteRegion.buffer; + ::vm_protect(mach_task_self(), (vm_address_t)startPad1, endPad1-startPad1, false, 0); + + // region between RW and RO + uint8_t* startPad2 = _readWriteRegion.buffer+_readWriteRegion.sizeInUse; + uint8_t* endPad2 = _readOnlyRegion.buffer; + ::vm_protect(mach_task_self(), (vm_address_t)startPad2, endPad2-startPad2, false, 0); +} + + +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 = "???"; + uint64_t unslidVmAddr = ((uint8_t*)contentPtr - _readExecuteRegion.buffer) + _readExecuteRegion.unslidLoadAddress; + const DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + cache->forEachImage(^(const mach_header* mh, const char* installName) { + ((dyld3::MachOLoaded*)mh)->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool &stop) { + if ( (unslidVmAddr >= info.vmAddr) && (unslidVmAddr < (info.vmAddr+info.vmSize)) ) { + foundDylibName = installName; + foundSegName = info.segName; + stop = true; + } + }); + }); +} + + +template +bool CacheBuilder::makeRebaseChainV2(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; + } + //fprintf(stderr, " too big delta = %d, lastOffset=0x%03X, offset=0x%03X\n", 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 (unsigned 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::addPageStartsV2(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(uint32_t 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 ( !makeRebaseChainV2

(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 v2 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(const bool bitmap[], unsigned dataPageCount) +{ + typedef typename P::uint_t pint_t; + typedef typename P::E E; + const uint32_t pageSize = 4096; - // 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; - } + // fill in fixed info + assert(_slideInfoFileOffset != 0); + dyld_cache_slide_info2* info = (dyld_cache_slide_info2*)_readOnlyRegion.buffer; + 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 - // 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; - }); + // set page starts and extras for each page + std::vector pageStarts; + std::vector pageExtras; + pageStarts.reserve(dataPageCount); + uint8_t* pageContent = _readWriteRegion.buffer; + const bool* bitmapForPage = bitmap; + for (unsigned i=0; i < dataPageCount; ++i) { + //warning("page[%d]", i); + addPageStartsV2

(pageContent, bitmapForPage, info, pageStarts, pageExtras); + if ( _diagnostics.hasError() ) { + return; + } + pageContent += pageSize; + bitmapForPage += (sizeof(bool)*(pageSize/4)); } - return result; + // 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 + uint64_t slideInfoSize = align(info->page_extras_offset + pageExtras.size()*sizeof(uint16_t), _archLayout->sharedRegionAlignP2); + if ( slideInfoSize > _slideInfoBufferSizeAllocated ) { + _diagnostics.error("kernel slide info overflow buffer"); + } + ((dyld_cache_header*)_readExecuteRegion.buffer)->slideInfoSize = slideInfoSize; + //fprintf(stderr, "pageCount=%u, page_starts_count=%lu, page_extras_count=%lu\n", dataPageCount, pageStarts.size(), pageExtras.size()); } -uint64_t CacheBuilder::pathHash(const char* path) +// fits in to int16_t +static bool smallValue(uint64_t value) { - uint64_t sum = 0; - for (const char* s=path; *s != '\0'; ++s) - sum += sum*4 + *s; - return sum; + uint32_t high = (value & 0xFFFF8000); + return (high == 0) || (high == 0xFFFF8000); } - -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) +bool CacheBuilder::makeRebaseChainV4(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t offset, const dyld_cache_slide_info4* info) { typedef typename P::uint_t pint_t; @@ -1152,7 +1655,7 @@ bool CacheBuilder::makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOf P::setP(*lastLoc, newLastValue); return true; } - //warning(" too big delta = %d, lastOffset=0x%03X, offset=0x%03X", offset - lastLocationOffset, lastLocationOffset, offset); + //fprintf(stderr, " too big delta = %d, lastOffset=0x%03X, offset=0x%03X\n", offset - lastLocationOffset, lastLocationOffset, offset); // distance between rebase locations is too far // see if we can make a chain from non-rebase locations @@ -1162,7 +1665,7 @@ bool CacheBuilder::makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOf 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 ) { + if ( smallValue(value) ) { // Steal values of 0 to be used in the rebase chain nonRebaseLocationOffsets[nrIndex] = i+j; break; @@ -1171,7 +1674,8 @@ bool CacheBuilder::makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOf 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); + //fprintf(stderr, " no way to make non-rebase delta chain, terminate off=0x%03X, old value=0x%08lX, new value=0x%08lX\n", + // lastLocationOffset, (long)lastValue, (long)newValue); P::setP(*lastLoc, newValue); return false; } @@ -1182,15 +1686,15 @@ bool CacheBuilder::makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOf // 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) { + for (unsigned 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); + if ( smallValue(value) ) + newValue = (value & valueMask) | (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); @@ -1201,8 +1705,8 @@ bool CacheBuilder::makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOf uint32_t delta3 = offset - prevOffset; pint_t value = (pint_t)P::getP(*prevLoc); pint_t newValue; - if ( value == 0 ) - newValue = (delta3 << deltaShift); + if ( smallValue(value) ) + newValue = (value & valueMask) | (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); @@ -1213,7 +1717,7 @@ bool CacheBuilder::makeRebaseChain(uint8_t* pageContent, uint16_t lastLocationOf template -void CacheBuilder::addPageStarts(uint8_t* pageContent, const bool bitmap[], const dyld_cache_slide_info2* info, +void CacheBuilder::addPageStartsV4(uint8_t* pageContent, const bool bitmap[], const dyld_cache_slide_info4* info, std::vector& pageStarts, std::vector& pageExtras) { typedef typename P::uint_t pint_t; @@ -1223,26 +1727,26 @@ void CacheBuilder::addPageStarts(uint8_t* pageContent, const bool bitmap[], cons 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 startValue = DYLD_CACHE_SLIDE4_PAGE_NO_REBASE; uint16_t lastLocationOffset = 0xFFFF; - for(int i=0; i < pageSize/4; ++i) { + for(uint32_t i=0; i < pageSize/4; ++i) { unsigned offset = i*4; if ( bitmap[i] ) { - if ( startValue == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE ) { + if ( startValue == DYLD_CACHE_SLIDE4_PAGE_NO_REBASE ) { // found first rebase location in page startValue = i; } - else if ( !makeRebaseChain

(pageContent, lastLocationOffset, offset, info) ) { + else if ( !makeRebaseChainV4

(pageContent, lastLocationOffset, offset, info) ) { // can't record all rebasings in one chain - if ( (startValue & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) == 0 ) { + if ( (startValue & DYLD_CACHE_SLIDE4_PAGE_USE_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"); + if ( indexInExtras >= DYLD_CACHE_SLIDE4_PAGE_INDEX ) { + _diagnostics.error("rebase overflow in v4 page extras"); return; } pageExtras.push_back(startValue); - startValue = indexInExtras | DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA; + startValue = indexInExtras | DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA; } pageExtras.push_back(i); } @@ -1256,55 +1760,26 @@ void CacheBuilder::addPageStarts(uint8_t* pageContent, const bool bitmap[], cons pint_t newValue = ((lastValue - valueAdd) & valueMask); P::setP(*lastLoc, newValue); } - if ( startValue & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA ) { + if ( startValue & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA ) { // add end bit to extras - pageExtras.back() |= DYLD_CACHE_SLIDE_PAGE_ATTR_END; + pageExtras.back() |= DYLD_CACHE_SLIDE4_PAGE_EXTRA_END; } pageStarts.push_back(startValue); } + + template -void CacheBuilder::writeSlideInfoV2() +void CacheBuilder::writeSlideInfoV4(const bool bitmap[], unsigned dataPageCount) { 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; + dyld_cache_slide_info4* info = (dyld_cache_slide_info4*)_readOnlyRegion.buffer; + info->version = 4; 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 @@ -1312,25 +1787,21 @@ void CacheBuilder::writeSlideInfoV2() // set page starts and extras for each page std::vector pageStarts; std::vector pageExtras; - pageStarts.reserve(pageCount); - uint8_t* pageContent = dataStart;; + pageStarts.reserve(dataPageCount); + uint8_t* pageContent = _readWriteRegion.buffer; const bool* bitmapForPage = bitmap; - for (unsigned i=0; i < pageCount; ++i) { - //warning("page[%d]", i); - addPageStarts

(pageContent, bitmapForPage, info, pageStarts, pageExtras); + for (unsigned i=0; i < dataPageCount; ++i) { + addPageStartsV4

(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_offset = sizeof(dyld_cache_slide_info4); 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_offset = (unsigned)(sizeof(dyld_cache_slide_info4)+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); @@ -1339,11 +1810,12 @@ void CacheBuilder::writeSlideInfoV2() 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"); + uint64_t slideInfoSize = align(info->page_extras_offset + pageExtras.size()*sizeof(uint16_t), _archLayout->sharedRegionAlignP2); + if ( slideInfoSize > _slideInfoBufferSizeAllocated ) { + _diagnostics.error("kernel slide info v4 overflow buffer"); } - //warning("pageCount=%u, page_starts_count=%lu, page_extras_count=%lu", pageCount, pageStarts.size(), pageExtras.size()); + ((dyld_cache_header*)_readExecuteRegion.buffer)->slideInfoSize = slideInfoSize; + //fprintf(stderr, "pageCount=%u, page_starts_count=%lu, page_extras_count=%lu\n", dataPageCount, pageStarts.size(), pageExtras.size()); } @@ -1405,50 +1877,100 @@ void CacheBuilder::writeSlideInfoV1() */ -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; +uint16_t CacheBuilder::pageStartV3(uint8_t* pageContent, uint32_t pageSize, const bool bitmap[]) +{ + const int maxPerPage = pageSize / 4; + uint16_t result = DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE; + dyld3::MachOLoaded::ChainedFixupPointerOnDisk* lastLoc = nullptr; + for (int i=0; i < maxPerPage; ++i) { + if ( bitmap[i] ) { + if ( result == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE ) { + // found first rebase location in page + result = i * 4; } - - if (illegalSectionSize) { - _diagnostics.error("FIPS section %s/%s extends beyond the end of the segment", segName, sectionName); - return; + dyld3::MachOLoaded::ChainedFixupPointerOnDisk* loc = (dyld3::MachOLoaded::ChainedFixupPointerOnDisk*)(pageContent + i*4);; + if ( lastLoc != nullptr ) { + // update chain (original chain may be wrong because of segment packing) + lastLoc->plainRebase.next = loc - lastLoc; } + lastLoc = loc; + } + } + if ( lastLoc != nullptr ) { + // mark last one as end of chain + lastLoc->plainRebase.next = 0; + } + return result; +} - //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; - }); + +void CacheBuilder::writeSlideInfoV3(const bool bitmap[], unsigned dataPageCount) +{ + const uint32_t pageSize = 4096; + + // fill in fixed info + assert(_slideInfoFileOffset != 0); + dyld_cache_slide_info3* info = (dyld_cache_slide_info3*)_readOnlyRegion.buffer; + info->version = 3; + info->page_size = pageSize; + info->page_starts_count = dataPageCount; + info->auth_value_add = _archLayout->sharedMemoryStart; + + // fill in per-page starts + uint8_t* pageContent = _readWriteRegion.buffer; + const bool* bitmapForPage = bitmap; + for (unsigned i=0; i < dataPageCount; ++i) { + info->page_starts[i] = pageStartV3(pageContent, pageSize, bitmapForPage); + pageContent += pageSize; + bitmapForPage += (sizeof(bool)*(pageSize/4)); + } + + // update header with final size + dyld_cache_header* dyldCacheHeader = (dyld_cache_header*)_readExecuteRegion.buffer; + dyldCacheHeader->slideInfoSize = align(__offsetof(dyld_cache_slide_info3, page_starts[dataPageCount]), _archLayout->sharedRegionAlignP2); + if ( dyldCacheHeader->slideInfoSize > _slideInfoBufferSizeAllocated ) { + _diagnostics.error("kernel slide info overflow buffer"); + } +} + + +void CacheBuilder::fipsSign() +{ + // find libcorecrypto.dylib in cache being built + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + __block const dyld3::MachOLoaded* ml = nullptr; + dyldCache->forEachImage(^(const mach_header* mh, const char* installName) { + if ( strcmp(installName, "/usr/lib/system/libcorecrypto.dylib") == 0 ) + ml = (dyld3::MachOLoaded*)mh; }); + if ( ml == nullptr ) { + _diagnostics.warning("Could not find libcorecrypto.dylib, skipping FIPS sealing"); + return; + } + + // find location in libcorecrypto.dylib to store hash of __text section + uint64_t hashStoreSize; + const void* hashStoreLocation = ml->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize); + if ( hashStoreLocation == nullptr ) { + _diagnostics.warning("Could not find __TEXT/__fips_hmacs section in libcorecrypto.dylib, skipping FIPS sealing"); + return; + } + if ( hashStoreSize != 32 ) { + _diagnostics.warning("__TEXT/__fips_hmacs section in libcorecrypto.dylib is not 32 bytes in size, skipping FIPS sealing"); + return; + } - if (!found) { - _diagnostics.warning("Could not find /usr/lib/system/libcorecrypto.dylib, skipping FIPS sealing"); + // compute hmac hash of __text section + uint64_t textSize; + const void* textLocation = ml->findSectionContent("__TEXT", "__text", textSize); + if ( textLocation == nullptr ) { + _diagnostics.warning("Could not find __TEXT/__text section in libcorecrypto.dylib, skipping FIPS sealing"); + return; } + unsigned char hmac_key = 0; + CCHmac(kCCHmacAlgSHA256, &hmac_key, 1, textLocation, textSize, (void*)hashStoreLocation); // store hash directly into hashStoreLocation } void CacheBuilder::codeSign() @@ -1487,9 +2009,7 @@ void CacheBuilder::codeSign() 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; + size_t inBbufferSize = _readExecuteRegion.sizeInUse+_readWriteRegion.sizeInUse+_readOnlyRegion.sizeInUse+_localSymbolsRegion.sizeInUse; // layout code signature contents uint32_t blobCount = agile ? 4 : 3; @@ -1510,14 +2030,18 @@ void CacheBuilder::codeSign() 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); + // allocate space for blob + vm_address_t codeSigAlloc; + if ( vm_allocate(mach_task_self(), &codeSigAlloc, sigSize, VM_FLAGS_ANYWHERE) != 0 ) { + _diagnostics.error("could not allocate code signature buffer"); return; } + _codeSignatureRegion.buffer = (uint8_t*)codeSigAlloc; + _codeSignatureRegion.bufferSize = sigSize; + _codeSignatureRegion.sizeInUse = sigSize; // create overall code signature which is a superblob - CS_SuperBlob* sb = reinterpret_cast(csBuffer); + CS_SuperBlob* sb = reinterpret_cast(_codeSignatureRegion.buffer); sb->magic = htonl(CSMAGIC_EMBEDDED_SIGNATURE); sb->length = htonl(sbSize); sb->count = htonl(blobCount); @@ -1560,10 +2084,9 @@ void CacheBuilder::codeSign() 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 + cd->execSegBase = htonll(_readExecuteRegion.cacheFileOffset); // base of TEXT segment + cd->execSegLimit = htonll(_readExecuteRegion.sizeInUse); // size of TEXT segment + cd->execSegFlags = 0; // not a main binary // initialize dynamic fields of Code Directory strcpy((char*)cd + idOffset, cacheIdentifier.c_str()); @@ -1624,22 +2147,54 @@ void CacheBuilder::codeSign() 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; + dyld_cache_header* cache = (dyld_cache_header*)_readExecuteRegion.buffer; + cache->codeSignatureOffset = inBbufferSize; + cache->codeSignatureSize = sigSize; + + const uint32_t rwSlotStart = (uint32_t)(_readExecuteRegion.sizeInUse / CS_PAGE_SIZE); + const uint32_t roSlotStart = (uint32_t)(rwSlotStart + _readWriteRegion.sizeInUse / CS_PAGE_SIZE); + const uint32_t localsSlotStart = (uint32_t)(roSlotStart + _readOnlyRegion.sizeInUse / CS_PAGE_SIZE); + auto codeSignPage = ^(size_t i) { + const uint8_t* code = nullptr; + // move to correct region + if ( i < rwSlotStart ) + code = _readExecuteRegion.buffer + (i * CS_PAGE_SIZE); + else if ( i >= rwSlotStart && i < roSlotStart ) + code = _readWriteRegion.buffer + ((i - rwSlotStart) * CS_PAGE_SIZE); + else if ( i >= roSlotStart && i < localsSlotStart ) + code = _readOnlyRegion.buffer + ((i - roSlotStart) * CS_PAGE_SIZE); + else + code = _localSymbolsRegion.buffer + ((i - localsSlotStart) * CS_PAGE_SIZE); - // compute hashes - const uint8_t* code = inBuffer; - for (uint32_t i=0; i < slotCount; ++i) { - CCDigest(dscDigestFormat, code, CS_PAGE_SIZE, hashSlot); - hashSlot += dscHashSize; + CCDigest(dscDigestFormat, code, CS_PAGE_SIZE, hashSlot + (i * dscHashSize)); if ( agile ) { - CCDigest(kCCDigestSHA256, code, CS_PAGE_SIZE, hash256Slot); - hash256Slot += CS_HASH_SIZE_SHA256; + CCDigest(kCCDigestSHA256, code, CS_PAGE_SIZE, hash256Slot + (i * CS_HASH_SIZE_SHA256)); } - code += CS_PAGE_SIZE; + }; + + // compute hashes + dispatch_apply(slotCount, DISPATCH_APPLY_AUTO, ^(size_t i) { + codeSignPage(i); + }); + + // Now that we have a code signature, compute a UUID from it. + + // Clear existing UUID, then MD5 whole cache buffer. + { + uint8_t* uuidLoc = cache->uuid; + assert(uuid_is_null(uuidLoc)); + static_assert(offsetof(dyld_cache_header, uuid) / CS_PAGE_SIZE == 0, "uuid is expected in the first page of the cache"); + CC_MD5((const void*)cd, (unsigned)cdSize, 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; + + // Now codesign page 0 again + codeSignPage(0); } // hash of entire code directory (cdHash) uses same hash as each page @@ -1656,9 +2211,6 @@ void CacheBuilder::codeSign() else { memset(_cdHashSecond, 0, 20); } - - // increase file size to include newly append code signature - _currentFileSize += sigSize; } const bool CacheBuilder::agileSignature() @@ -1684,92 +2236,345 @@ const std::string CacheBuilder::cdHashSecond() return cdHash(_cdHashSecond); } -void CacheBuilder::addCachedDylibsImageGroup(dyld3::ImageProxyGroup* dylibGroup) + +void CacheBuilder::buildImageArray(std::vector& aliases) { - const dyld3::launch_cache::binary_format::ImageGroup* groupBinary = dylibGroup->makeImageGroupBinary(_diagnostics, _s_neverStubEliminate); - if (!groupBinary) + typedef dyld3::closure::ClosureBuilder::CachedDylibInfo CachedDylibInfo; + typedef dyld3::closure::Image::PatchableExport::PatchLocation PatchLocation; + typedef uint64_t CacheOffset; + + // convert STL data structures to simple arrays to passe to makeDyldCacheImageArray() + __block std::vector dylibInfos; + __block std::unordered_map imageNumToML; + DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + cache->forEachImage(^(const mach_header* mh, const char* installName) { + uint64_t mtime; + uint64_t inode; + cache->getIndexedImageEntry((uint32_t)dylibInfos.size(), mtime, inode); + CachedDylibInfo entry; + entry.fileInfo.fileContent = mh; + entry.fileInfo.path = installName; + entry.fileInfo.sliceOffset = 0; + entry.fileInfo.inode = inode; + entry.fileInfo.mtime = mtime; + dylibInfos.push_back(entry); + imageNumToML[(dyld3::closure::ImageNum)(dylibInfos.size())] = (dyld3::MachOLoaded*)mh; + }); + + // Convert symlinks from STL to simple char pointers. + std::vector dylibAliases; + dylibAliases.reserve(aliases.size()); + for (const auto& alias : aliases) + dylibAliases.push_back({ alias.realPath.c_str(), alias.aliasPath.c_str() }); + + + __block std::unordered_map> dylibToItsExports; + __block std::unordered_map> exportsToUses; + __block std::unordered_map exportsToName; + + dyld3::closure::ClosureBuilder::CacheDylibsBindingHandlers handlers; + + handlers.chainedBind = ^(dyld3::closure::ImageNum, const dyld3::MachOLoaded* imageLoadAddress, + const dyld3::Array& starts, + const dyld3::Array& targets, + const dyld3::Array& targetInfos) { + for (uint64_t start : starts) { + dyld3::closure::Image::forEachChainedFixup((void*)imageLoadAddress, start, ^(uint64_t* fixupLoc, dyld3::MachOLoaded::ChainedFixupPointerOnDisk fixupInfo, bool& stop) { + // record location in aslr tracker so kernel can slide this on page-in + _aslrTracker.add(fixupLoc); + + // if bind, record info for patch table and convert to rebase + if ( fixupInfo.plainBind.bind ) { + dyld3::closure::Image::ResolvedSymbolTarget target = targets[fixupInfo.plainBind.ordinal]; + const dyld3::closure::ClosureBuilder::ResolvedTargetInfo& targetInfo = targetInfos[fixupInfo.plainBind.ordinal]; + dyld3::MachOLoaded::ChainedFixupPointerOnDisk* loc; + uint64_t offsetInCache; + switch ( target.sharedCache.kind ) { + case dyld3::closure::Image::ResolvedSymbolTarget::kindSharedCache: + loc = (dyld3::MachOLoaded::ChainedFixupPointerOnDisk*)fixupLoc; + offsetInCache = target.sharedCache.offset - targetInfo.addend; + dylibToItsExports[targetInfo.foundInDylib].insert(offsetInCache); + exportsToName[offsetInCache] = targetInfo.foundSymbolName; + if ( fixupInfo.authBind.auth ) { + // turn this auth bind into an auth rebase into the cache + loc->authRebase.bind = 0; + loc->authRebase.target = target.sharedCache.offset; + exportsToUses[offsetInCache].push_back(PatchLocation((uint8_t*)fixupLoc - _readExecuteRegion.buffer, targetInfo.addend, *loc)); + } + else { + // turn this plain bind into an plain rebase into the cache + loc->plainRebase.bind = 0; + loc->plainRebase.target = _archLayout->sharedMemoryStart + target.sharedCache.offset; + exportsToUses[offsetInCache].push_back(PatchLocation((uint8_t*)fixupLoc - _readExecuteRegion.buffer, targetInfo.addend)); + } + break; + case dyld3::closure::Image::ResolvedSymbolTarget::kindAbsolute: + if ( _archLayout->is64 ) + *((uint64_t*)fixupLoc) = target.absolute.value; + else + *((uint32_t*)fixupLoc) = (uint32_t)(target.absolute.value); + // don't record absolute targets for ASLR + break; + default: + assert(0 && "unsupported ResolvedSymbolTarget kind in dyld cache"); + } + } + }); + } + }; + + handlers.rebase = ^(dyld3::closure::ImageNum imageNum, const dyld3::MachOLoaded* imageToFix, uint32_t runtimeOffset) { + // record location in aslr tracker so kernel can slide this on page-in + uint8_t* fixupLoc = (uint8_t*)imageToFix+runtimeOffset; + _aslrTracker.add(fixupLoc); + }; + + handlers.bind = ^(dyld3::closure::ImageNum imageNum, const dyld3::MachOLoaded* mh, + uint32_t runtimeOffset, dyld3::closure::Image::ResolvedSymbolTarget target, + const dyld3::closure::ClosureBuilder::ResolvedTargetInfo& targetInfo) { + uint8_t* fixupLoc = (uint8_t*)mh+runtimeOffset; + + // binder is called a second time for weak_bind info, which we ignore when building cache + const bool weakDefUseAlreadySet = targetInfo.weakBindCoalese && _aslrTracker.has(fixupLoc); + + // do actual bind that sets pointer in image to unslid target address + uint64_t offsetInCache; + switch ( target.sharedCache.kind ) { + case dyld3::closure::Image::ResolvedSymbolTarget::kindSharedCache: + offsetInCache = target.sharedCache.offset - targetInfo.addend; + dylibToItsExports[targetInfo.foundInDylib].insert(offsetInCache); + exportsToUses[offsetInCache].push_back(PatchLocation(fixupLoc - _readExecuteRegion.buffer, targetInfo.addend)); + exportsToName[offsetInCache] = targetInfo.foundSymbolName; + if ( !weakDefUseAlreadySet ) { + if ( _archLayout->is64 ) + *((uint64_t*)fixupLoc) = _archLayout->sharedMemoryStart + target.sharedCache.offset; + else + *((uint32_t*)fixupLoc) = (uint32_t)(_archLayout->sharedMemoryStart + target.sharedCache.offset); + // record location in aslr tracker so kernel can slide this on page-in + _aslrTracker.add(fixupLoc); + } + break; + case dyld3::closure::Image::ResolvedSymbolTarget::kindAbsolute: + if ( _archLayout->is64 ) + *((uint64_t*)fixupLoc) = target.absolute.value; + else + *((uint32_t*)fixupLoc) = (uint32_t)(target.absolute.value); + // don't record absolute targets for ASLR + // HACK: Split seg may have added a target. Remove it + _aslrTracker.remove(fixupLoc); + if ( (targetInfo.libOrdinal > 0) && (targetInfo.libOrdinal <= mh->dependentDylibCount()) ) { + _missingWeakImports[fixupLoc] = mh->dependentDylibLoadPath(targetInfo.libOrdinal - 1); + } + break; + default: + assert(0 && "unsupported ResolvedSymbolTarget kind in dyld cache"); + } + }; + + handlers.forEachExportsPatch = ^(dyld3::closure::ImageNum imageNum, void (^handler)(const dyld3::closure::ClosureBuilder::CacheDylibsBindingHandlers::PatchInfo&)) { + const dyld3::MachOLoaded* ml = imageNumToML[imageNum]; + for (CacheOffset exportCacheOffset : dylibToItsExports[ml]) { + dyld3::closure::ClosureBuilder::CacheDylibsBindingHandlers::PatchInfo info; + std::vector& uses = exportsToUses[exportCacheOffset]; + uses.erase(std::unique(uses.begin(), uses.end()), uses.end()); + info.exportCacheOffset = (uint32_t)exportCacheOffset; + info.exportSymbolName = exportsToName[exportCacheOffset]; + info.usesCount = (uint32_t)uses.size(); + info.usesArray = &uses.front(); + handler(info); + } + }; + + + // build ImageArray for all dylibs in dyld cache + dyld3::closure::PathOverrides pathOverrides; + dyld3::closure::ClosureBuilder cb(dyld3::closure::kFirstDyldCacheImageNum, _fileSystem, cache, false, pathOverrides, dyld3::closure::ClosureBuilder::AtPath::none, nullptr, _archLayout->archName, _options.platform, &handlers); + dyld3::Array dylibs(&dylibInfos[0], dylibInfos.size(), dylibInfos.size()); + const dyld3::Array aliasesArray(dylibAliases.data(), dylibAliases.size(), dylibAliases.size()); + _imageArray = cb.makeDyldCacheImageArray(_options.optimizeStubs, dylibs, aliasesArray); + if ( cb.diagnostics().hasError() ) { + _diagnostics.error("%s", cb.diagnostics().errorMessage().c_str()); return; + } +} - dyld3::launch_cache::ImageGroup group(groupBinary); - size_t groupSize = group.size(); +void CacheBuilder::addImageArray() +{ + // build trie of dylib paths + __block std::vector dylibEntrys; + _imageArray->forEachImage(^(const dyld3::closure::Image* image, bool& stop) { + dylibEntrys.push_back(DylibIndexTrie::Entry(image->path(), DylibIndex(image->imageNum()-1))); + image->forEachAlias(^(const char *aliasPath, bool &innerStop) { + dylibEntrys.push_back(DylibIndexTrie::Entry(aliasPath, DylibIndex(image->imageNum()-1))); + }); + }); + DylibIndexTrie dylibsTrie(dylibEntrys); + std::vector trieBytes; + dylibsTrie.emit(trieBytes); + while ( (trieBytes.size() % 4) != 0 ) + trieBytes.push_back(0); - 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); + // check for fit + uint64_t imageArraySize = _imageArray->size(); + size_t freeSpace = _readOnlyRegion.bufferSize - _readOnlyRegion.sizeInUse; + if ( imageArraySize+trieBytes.size() > freeSpace ) { + _diagnostics.error("cache buffer too small to hold ImageArray and Trie (buffer size=%lldMB, imageArray size=%lldMB, trie size=%luKB, free space=%ldMB)", + _allocatedBufferSize/1024/1024, imageArraySize/1024/1024, trieBytes.size()/1024, freeSpace/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); + // copy into cache and update header + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + dyldCache->header.dylibsImageArrayAddr = _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse; + dyldCache->header.dylibsImageArraySize = imageArraySize; + dyldCache->header.dylibsTrieAddr = dyldCache->header.dylibsImageArrayAddr + imageArraySize; + dyldCache->header.dylibsTrieSize = trieBytes.size(); + ::memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse, _imageArray, imageArraySize); + ::memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse + imageArraySize, &trieBytes[0], trieBytes.size()); + _readOnlyRegion.sizeInUse += align(imageArraySize+trieBytes.size(),14); } - -void CacheBuilder::addCachedOtherDylibsImageGroup(dyld3::ImageProxyGroup* otherGroup) +void CacheBuilder::addOtherImageArray(const std::vector& otherDylibsAndBundles, std::vector& overflowDylibs) { - const dyld3::launch_cache::binary_format::ImageGroup* groupBinary = otherGroup->makeImageGroupBinary(_diagnostics); - if (!groupBinary) - return; + DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + dyld3::closure::PathOverrides pathOverrides; + dyld3::closure::ClosureBuilder cb(dyld3::closure::kFirstOtherOSImageNum, _fileSystem, cache, false, pathOverrides, dyld3::closure::ClosureBuilder::AtPath::none, nullptr, _archLayout->archName, _options.platform); + + // make ImageArray for other dylibs and bundles + STACK_ALLOC_ARRAY(dyld3::closure::LoadedFileInfo, others, otherDylibsAndBundles.size() + overflowDylibs.size()); + for (const LoadedMachO& other : otherDylibsAndBundles) { + if ( !contains(other.loadedFileInfo.path, ".app/") ) + others.push_back(other.loadedFileInfo); + } + + for (const LoadedMachO* dylib : overflowDylibs) { + if (dylib->mappedFile.mh->canHavePrecomputedDlopenClosure(dylib->mappedFile.runtimePath.c_str(), ^(const char*) {}) ) + others.push_back(dylib->loadedFileInfo); + } - dyld3::launch_cache::ImageGroup group(groupBinary); - size_t groupSize = group.size(); + // Sort the others array by name so that it is deterministic + std::sort(others.begin(), others.end(), + [](const dyld3::closure::LoadedFileInfo& a, const dyld3::closure::LoadedFileInfo& b) { + return strcmp(a.path, b.path) < 0; + }); - 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); + const dyld3::closure::ImageArray* otherImageArray = cb.makeOtherDylibsImageArray(others, (uint32_t)_sortedDylibs.size()); + + // build trie of paths + __block std::vector otherEntrys; + otherImageArray->forEachImage(^(const dyld3::closure::Image* image, bool& stop) { + if ( !image->isInvalid() ) + otherEntrys.push_back(DylibIndexTrie::Entry(image->path(), DylibIndex(image->imageNum()))); + }); + DylibIndexTrie dylibsTrie(otherEntrys); + std::vector trieBytes; + dylibsTrie.emit(trieBytes); + while ( (trieBytes.size() % 4) != 0 ) + trieBytes.push_back(0); + + // check for fit + uint64_t imageArraySize = otherImageArray->size(); + size_t freeSpace = _readOnlyRegion.bufferSize - _readOnlyRegion.sizeInUse; + if ( imageArraySize+trieBytes.size() > freeSpace ) { + _diagnostics.error("cache buffer too small to hold ImageArray and Trie (buffer size=%lldMB, imageArray size=%lldMB, trie size=%luKB, free space=%ldMB)", + _allocatedBufferSize/1024/1024, imageArraySize/1024/1024, trieBytes.size()/1024, freeSpace/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); + // copy into cache and update header + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + dyldCache->header.otherImageArrayAddr = _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse; + dyldCache->header.otherImageArraySize = imageArraySize; + dyldCache->header.otherTrieAddr = dyldCache->header.otherImageArrayAddr + imageArraySize; + dyldCache->header.otherTrieSize = trieBytes.size(); + ::memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse, otherImageArray, imageArraySize); + ::memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse + imageArraySize, &trieBytes[0], trieBytes.size()); + _readOnlyRegion.sizeInUse += align(imageArraySize+trieBytes.size(),14); } -void CacheBuilder::addClosures(const std::map& closures) + +void CacheBuilder::addClosures(const std::vector& osExecutables) { + const DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + + __block std::vector osExecutablesDiags; + __block std::vector osExecutablesClosures; + osExecutablesDiags.resize(osExecutables.size()); + osExecutablesClosures.resize(osExecutables.size()); + + dispatch_apply(osExecutables.size(), DISPATCH_APPLY_AUTO, ^(size_t index) { + const LoadedMachO& loadedMachO = osExecutables[index]; + // don't pre-build closures for staged apps into dyld cache, since they won't run from that location + if ( startsWith(loadedMachO.mappedFile.runtimePath, "/private/var/staged_system_apps/") ) { + return; + } + dyld3::closure::PathOverrides pathOverrides; + dyld3::closure::ClosureBuilder builder(dyld3::closure::kFirstLaunchClosureImageNum, _fileSystem, dyldCache, false, pathOverrides, dyld3::closure::ClosureBuilder::AtPath::all, nullptr, _archLayout->archName, _options.platform, nullptr); + bool issetuid = false; + if ( this->_options.platform == dyld3::Platform::macOS ) + _fileSystem.fileExists(loadedMachO.loadedFileInfo.path, nullptr, nullptr, &issetuid); + const dyld3::closure::LaunchClosure* mainClosure = builder.makeLaunchClosure(loadedMachO.loadedFileInfo, issetuid); + if ( builder.diagnostics().hasError() ) { + osExecutablesDiags[index].error("%s", builder.diagnostics().errorMessage().c_str()); + } + else { + assert(mainClosure != nullptr); + osExecutablesClosures[index] = mainClosure; + } + }); + + std::map closures; + for (uint64_t i = 0, e = osExecutables.size(); i != e; ++i) { + const LoadedMachO& loadedMachO = osExecutables[i]; + const Diagnostics& diag = osExecutablesDiags[i]; + if (diag.hasError()) { + if ( _options.verbose ) { + _diagnostics.warning("building closure for '%s': %s", loadedMachO.mappedFile.runtimePath.c_str(), diag.errorMessage().c_str()); + for (const std::string& warn : diag.warnings() ) + _diagnostics.warning("%s", warn.c_str()); + } + if ( loadedMachO.inputFile && (loadedMachO.inputFile->mustBeIncluded()) ) { + loadedMachO.inputFile->diag.error("%s", diag.errorMessage().c_str()); + } + } else { + // Note, a closure could be null here if it has a path we skip. + if (osExecutablesClosures[i] != nullptr) + closures[loadedMachO.mappedFile.runtimePath] = osExecutablesClosures[i]; + } + } + + osExecutablesDiags.clear(); + osExecutablesClosures.clear(); + // preflight space needed size_t closuresSpace = 0; for (const auto& entry : closures) { - dyld3::launch_cache::Closure closure(entry.second); - closuresSpace += closure.size(); + closuresSpace += entry.second->size(); } - size_t freeSpace = _allocatedBufferSize - _currentFileSize; + size_t freeSpace = _readOnlyRegion.bufferSize - _readOnlyRegion.sizeInUse; 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; + DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + cache->header.progClosuresAddr = _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse; + uint8_t* closuresBase = _readOnlyRegion.buffer + _readOnlyRegion.sizeInUse; std::vector closureEntrys; uint32_t currentClosureOffset = 0; for (const auto& entry : closures) { - const dyld3::launch_cache::binary_format::Closure* closBuf = entry.second; + const dyld3::closure::LaunchClosure* closure = entry.second; closureEntrys.push_back(DylibIndexTrie::Entry(entry.first, DylibIndex(currentClosureOffset))); - dyld3::launch_cache::Closure closure(closBuf); - size_t size = closure.size(); + size_t size = closure->size(); assert((size % 4) == 0); - memcpy(closuresBase+currentClosureOffset, closBuf, size); + memcpy(closuresBase+currentClosureOffset, closure, size); currentClosureOffset += size; freeSpace -= size; - free((void*)closBuf); + closure->deallocate(); } - _buffer->header.progClosuresSize = currentClosureOffset; - _currentFileSize += currentClosureOffset; - freeSpace = _allocatedBufferSize - _currentFileSize; - + cache->header.progClosuresSize = currentClosureOffset; + _readOnlyRegion.sizeInUse += currentClosureOffset; + freeSpace = _readOnlyRegion.bufferSize - _readOnlyRegion.sizeInUse; // build trie of indexes into closures list DylibIndexTrie closureTrie(closureEntrys); std::vector trieBytes; @@ -1781,10 +2586,147 @@ void CacheBuilder::addClosures(const std::mapheader.progClosuresTrieAddr = mappings[2].address + (_currentFileSize - mappings[2].fileOffset); - _buffer->header.progClosuresTrieSize = trieBytes.size(); - _currentFileSize += trieBytes.size(); + memcpy(_readOnlyRegion.buffer + _readOnlyRegion.sizeInUse, &trieBytes[0], trieBytes.size()); + cache->header.progClosuresTrieAddr = _readOnlyRegion.unslidLoadAddress + _readOnlyRegion.sizeInUse; + cache->header.progClosuresTrieSize = trieBytes.size(); + _readOnlyRegion.sizeInUse += trieBytes.size(); + _readOnlyRegion.sizeInUse = align(_readOnlyRegion.sizeInUse, 14); +} + + +bool CacheBuilder::writeCache(void (^cacheSizeCallback)(uint64_t size), bool (^copyCallback)(const uint8_t* src, uint64_t size, uint64_t dstOffset)) +{ + const dyld_cache_header* cacheHeader = (dyld_cache_header*)_readExecuteRegion.buffer; + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(_readExecuteRegion.buffer + cacheHeader->mappingOffset); + assert(_readExecuteRegion.sizeInUse == mappings[0].size); + assert(_readWriteRegion.sizeInUse == mappings[1].size); + assert(_readOnlyRegion.sizeInUse == mappings[2].size); + assert(_readExecuteRegion.cacheFileOffset == mappings[0].fileOffset); + assert(_readWriteRegion.cacheFileOffset == mappings[1].fileOffset); + assert(_readOnlyRegion.cacheFileOffset == mappings[2].fileOffset); + assert(_codeSignatureRegion.sizeInUse == cacheHeader->codeSignatureSize); + assert(cacheHeader->codeSignatureOffset == mappings[2].fileOffset+_readOnlyRegion.sizeInUse+_localSymbolsRegion.sizeInUse); + cacheSizeCallback(_readExecuteRegion.sizeInUse+_readWriteRegion.sizeInUse+_readOnlyRegion.sizeInUse+_localSymbolsRegion.sizeInUse+_codeSignatureRegion.sizeInUse); + bool fullyWritten = copyCallback(_readExecuteRegion.buffer, _readExecuteRegion.sizeInUse, mappings[0].fileOffset); + fullyWritten &= copyCallback(_readWriteRegion.buffer, _readWriteRegion.sizeInUse, mappings[1].fileOffset); + fullyWritten &= copyCallback(_readOnlyRegion.buffer, _readOnlyRegion.sizeInUse, mappings[2].fileOffset); + if ( _localSymbolsRegion.sizeInUse != 0 ) { + assert(cacheHeader->localSymbolsOffset == mappings[2].fileOffset+_readOnlyRegion.sizeInUse); + fullyWritten &= copyCallback(_localSymbolsRegion.buffer, _localSymbolsRegion.sizeInUse, cacheHeader->localSymbolsOffset); + } + fullyWritten &= copyCallback(_codeSignatureRegion.buffer, _codeSignatureRegion.sizeInUse, cacheHeader->codeSignatureOffset); + return fullyWritten; +} + + +void CacheBuilder::writeFile(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 ) { + auto cacheSizeCallback = ^(uint64_t size) { + ::ftruncate(fd, size); + }; + auto copyCallback = ^(const uint8_t* src, uint64_t size, uint64_t dstOffset) { + uint64_t writtenSize = pwrite(fd, src, size, dstOffset); + return writtenSize == size; + }; + bool fullyWritten = writeCache(cacheSizeCallback, copyCallback); + if ( fullyWritten ) { + ::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; // success + } + } + else { + _diagnostics.error("could not write file %s", pathTemplateSpace); + } + ::close(fd); + ::unlink(pathTemplateSpace); + } + else { + _diagnostics.error("could not open file %s", pathTemplateSpace); + } } +void CacheBuilder::writeBuffer(uint8_t*& buffer, uint64_t& bufferSize) { + auto cacheSizeCallback = ^(uint64_t size) { + buffer = (uint8_t*)malloc(size); + bufferSize = size; + }; + auto copyCallback = ^(const uint8_t* src, uint64_t size, uint64_t dstOffset) { + memcpy(buffer + dstOffset, src, size); + return true; + }; + bool fullyWritten = writeCache(cacheSizeCallback, copyCallback); + assert(fullyWritten); +} + +void CacheBuilder::writeMapFile(const std::string& path) +{ + const DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + std::string mapContent = cache->mapFile(); + safeSave(mapContent.c_str(), mapContent.size(), path); +} + +void CacheBuilder::writeMapFileBuffer(uint8_t*& buffer, uint64_t& bufferSize) +{ + const DyldSharedCache* cache = (DyldSharedCache*)_readExecuteRegion.buffer; + std::string mapContent = cache->mapFile(); + buffer = (uint8_t*)malloc(mapContent.size() + 1); + bufferSize = mapContent.size() + 1; + memcpy(buffer, mapContent.data(), bufferSize); +} + + +void CacheBuilder::forEachCacheDylib(void (^callback)(const std::string& path)) { + for (const DylibInfo& dylibInfo : _sortedDylibs) + callback(dylibInfo.runtimePath); +} + + +CacheBuilder::ASLR_Tracker::~ASLR_Tracker() +{ + if ( _bitmap != nullptr ) + ::free(_bitmap); +} + +void CacheBuilder::ASLR_Tracker::setDataRegion(const void* rwRegionStart, size_t rwRegionSize) +{ + _pageCount = (unsigned)(rwRegionSize+_pageSize-1)/_pageSize; + _regionStart = (uint8_t*)rwRegionStart; + _endStart = (uint8_t*)rwRegionStart + rwRegionSize; + _bitmap = (bool*)calloc(_pageCount*(_pageSize/4)*sizeof(bool), 1); +} + +void CacheBuilder::ASLR_Tracker::add(void* loc) +{ + uint8_t* p = (uint8_t*)loc; + assert(p >= _regionStart); + assert(p < _endStart); + _bitmap[(p-_regionStart)/4] = true; +} + +void CacheBuilder::ASLR_Tracker::remove(void* loc) +{ + uint8_t* p = (uint8_t*)loc; + assert(p >= _regionStart); + assert(p < _endStart); + _bitmap[(p-_regionStart)/4] = false; +} + +bool CacheBuilder::ASLR_Tracker::has(void* loc) +{ + uint8_t* p = (uint8_t*)loc; + assert(p >= _regionStart); + assert(p < _endStart); + return _bitmap[(p-_regionStart)/4]; +} + + + diff --git a/dyld3/shared-cache/CacheBuilder.h b/dyld3/shared-cache/CacheBuilder.h index 582ac3e..746cb63 100644 --- a/dyld3/shared-cache/CacheBuilder.h +++ b/dyld3/shared-cache/CacheBuilder.h @@ -27,55 +27,119 @@ #include #include +#include #include #include +#include "ClosureFileSystem.h" #include "DyldSharedCache.h" #include "Diagnostics.h" -#include "ImageProxy.h" +#include "MachOAnalyzer.h" -namespace dyld3 { - namespace launch_cache { - namespace binary_format { - struct ImageGroup; - struct Closure; - } - } -} +template class LinkeditOptimizer; + + +class CacheBuilder { +public: + CacheBuilder(const DyldSharedCache::CreateOptions& options, const dyld3::closure::FileSystem& fileSystem); + + struct InputFile { + enum State { + Unset, + MustBeIncluded, + MustBeIncludedForDependent, + MustBeExcludedIfUnused + }; + InputFile(const char* path, State state) : path(path), state(state) { } + const char* path; + State state = Unset; + Diagnostics diag; -struct CacheBuilder { + bool mustBeIncluded() const { + return (state == MustBeIncluded) || (state == MustBeIncludedForDependent); + } + }; - CacheBuilder(const DyldSharedCache::CreateOptions& options); + // Contains a MachO which has been loaded from the file system and may potentially need to be unloaded later. + struct LoadedMachO { + DyldSharedCache::MappedMachO mappedFile; + dyld3::closure::LoadedFileInfo loadedFileInfo; + InputFile* inputFile; + }; - void 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 std::set evictions(); - const bool agileSignature(); - const std::string cdHashFirst(); - const std::string cdHashSecond(); + void build(std::vector& inputFiles, + std::vector& aliases); + void build(const std::vector& dylibs, + const std::vector& otherOsDylibsInput, + const std::vector& osExecutables, + std::vector& aliases); + void build(const std::vector& dylibsToCache, + const std::vector& otherOsDylibs, + const std::vector& osExecutables, + std::vector& aliases); + void writeFile(const std::string& path); + void writeBuffer(uint8_t*& buffer, uint64_t& size); + void writeMapFile(const std::string& path); + void writeMapFileBuffer(uint8_t*& buffer, uint64_t& bufferSize); + void deleteBuffer(); + std::string errorMessage(); + const std::set warnings(); + const std::set evictions(); + const bool agileSignature(); + const std::string cdHashFirst(); + const std::string cdHashSecond(); + + void forEachCacheDylib(void (^callback)(const std::string& path)); struct SegmentMappingInfo { const void* srcSegment; const char* segName; - uint64_t dstCacheAddress; - uint32_t dstCacheOffset; + void* dstSegment; + uint64_t dstCacheUnslidAddress; + uint32_t dstCacheFileOffset; uint32_t dstCacheSegmentSize; uint32_t copySegmentSize; uint32_t srcSegmentIndex; }; -private: + class ASLR_Tracker + { + public: + ~ASLR_Tracker(); + + void setDataRegion(const void* rwRegionStart, size_t rwRegionSize); + void add(void* p); + void remove(void* p); + bool has(void* p); + const bool* bitmap() { return _bitmap; } + unsigned dataPageCount() { return _pageCount; } + + private: + + uint8_t* _regionStart = nullptr; + uint8_t* _endStart = nullptr; + bool* _bitmap = nullptr; + unsigned _pageCount = 0; + unsigned _pageSize = 4096; + }; + + typedef std::map> LOH_Tracker; - typedef std::unordered_map> SegmentMapping; + struct Region + { + uint8_t* buffer = nullptr; + uint64_t bufferSize = 0; + uint64_t sizeInUse = 0; + uint64_t unslidLoadAddress = 0; + uint64_t cacheFileOffset = 0; + }; +private: + template + friend class LinkeditOptimizer; + struct ArchLayout { uint64_t sharedMemoryStart; @@ -87,6 +151,7 @@ private: uint32_t branchPoolLinkEditSize; uint32_t branchReach; uint8_t sharedRegionAlignP2; + uint8_t slideInfoBytesPerPage; bool sharedRegionsAreDiscontiguous; bool is64; }; @@ -94,50 +159,95 @@ private: static const ArchLayout _s_archLayout[]; static const char* const _s_neverStubEliminate[]; - std::vector makeSortedDylibs(const std::vector& dylibs, const std::unordered_map sortOrder); + struct UnmappedRegion + { + uint8_t* buffer = nullptr; + uint64_t bufferSize = 0; + uint64_t sizeInUse = 0; + }; + + struct DylibInfo + { + const LoadedMachO* input; + std::string runtimePath; + std::vector cacheLocation; + }; - SegmentMapping assignSegmentAddresses(const std::vector& dylibs, struct dyld_cache_mapping_info regions[3]); + void makeSortedDylibs(const std::vector& dylibs, const std::unordered_map sortOrder); + void assignSegmentAddresses(); - 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); + uint64_t cacheOverflowAmount(); + size_t evictLeafDylibs(uint64_t reductionTarget, std::vector& overflowDylibs); 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 writeCacheHeader(); + void copyRawSegments(); + void adjustAllImagesForNewSegmentLocations(); void writeSlideInfoV1(); - void recomputeCacheUUID(void); + void writeSlideInfoV3(const bool bitmap[], unsigned dataPageCoun); + uint16_t pageStartV3(uint8_t* pageContent, uint32_t pageSize, const bool bitmap[]); void findDylibAndSegment(const void* contentPtr, std::string& dylibName, std::string& segName); + void addImageArray(); + void buildImageArray(std::vector& aliases); + void addOtherImageArray(const std::vector&, std::vector& overflowDylibs); + void addClosures(const std::vector&); + void markPaddingInaccessible(); + + bool writeCache(void (^cacheSizeCallback)(uint64_t size), bool (^copyCallback)(const uint8_t* src, uint64_t size, uint64_t dstOffset)); - void addCachedDylibsImageGroup(dyld3::ImageProxyGroup*); - void addCachedOtherDylibsImageGroup(dyld3::ImageProxyGroup*); - void addClosures(const std::map& closures); + template void writeSlideInfoV2(const bool bitmap[], unsigned dataPageCount); + template bool makeRebaseChainV2(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t newOffset, const struct dyld_cache_slide_info2* info); + template void addPageStartsV2(uint8_t* pageContent, const bool bitmap[], const struct dyld_cache_slide_info2* info, + std::vector& pageStarts, std::vector& pageExtras); - 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, + template void writeSlideInfoV4(const bool bitmap[], unsigned dataPageCount); + template bool makeRebaseChainV4(uint8_t* pageContent, uint16_t lastLocationOffset, uint16_t newOffset, const struct dyld_cache_slide_info4* info); + template void addPageStartsV4(uint8_t* pageContent, const bool bitmap[], const struct dyld_cache_slide_info4* info, std::vector& pageStarts, std::vector& pageExtras); + // implemented in AdjustDylibSegemnts.cpp + void adjustDylibSegments(const DylibInfo& dylib, Diagnostics& diag) const; + + // implemented in OptimizerLinkedit.cpp + void optimizeLinkedit(const std::vector& branchPoolOffsets); + + // implemented in OptimizerObjC.cpp + void optimizeObjC(); + + // implemented in OptimizerBranches.cpp + void optimizeAwayStubs(const std::vector& branchPoolStartAddrs, uint64_t branchPoolsLinkEditStartAddr); + + + typedef std::unordered_map InstallNameToMA; + const DyldSharedCache::CreateOptions& _options; - DyldSharedCache* _buffer; + const dyld3::closure::FileSystem& _fileSystem; + Region _readExecuteRegion; + Region _readWriteRegion; + Region _readOnlyRegion; + UnmappedRegion _localSymbolsRegion; + UnmappedRegion _codeSignatureRegion; + vm_address_t _fullAllocatedBuffer; + uint64_t _nonLinkEditReadOnlySize; Diagnostics _diagnostics; - std::set _evictions; + std::set _evictions; const ArchLayout* _archLayout; uint32_t _aliasCount; uint64_t _slideInfoFileOffset; uint64_t _slideInfoBufferSizeAllocated; uint64_t _allocatedBufferSize; - uint64_t _currentFileSize; - uint64_t _vmSize; + std::vector _sortedDylibs; + InstallNameToMA _installNameToCacheDylib; std::unordered_map _dataDirtySegsOrder; - std::vector _pointersForASLR; - dyld3::ImageProxyGroup::PatchTable _patchTable; + // Note this is mutable as the only parallel writes to it are done atomically to the bitmap + mutable ASLR_Tracker _aslrTracker; + std::map _missingWeakImports; + mutable LOH_Tracker _lohTracker; + const dyld3::closure::ImageArray* _imageArray; + uint32_t _sharedStringsPoolVmOffset; std::vector _branchPoolStarts; uint64_t _branchPoolsLinkEditStartAddr; uint8_t _cdHashFirst[20]; @@ -145,18 +255,6 @@ private: }; -// 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) diff --git a/dyld3/shared-cache/DyldSharedCache.cpp b/dyld3/shared-cache/DyldSharedCache.cpp index f544c19..9816d0c 100644 --- a/dyld3/shared-cache/DyldSharedCache.cpp +++ b/dyld3/shared-cache/DyldSharedCache.cpp @@ -37,7 +37,7 @@ #include #include -#if !DYLD_IN_PROCESS +#if BUILDING_CACHE_BUILDER #include #include #include @@ -46,59 +46,83 @@ #endif #define NO_ULEB -#include "MachOParser.h" +#include "MachOLoaded.h" +#include "ClosureFileSystemPhysical.h" #include "CacheBuilder.h" #include "DyldSharedCache.h" -#include "LaunchCache.h" #include "Trie.hpp" #include "StringUtils.h" +#include "FileUtils.h" -#if !DYLD_IN_PROCESS +#if BUILDING_CACHE_BUILDER DyldSharedCache::CreateResults DyldSharedCache::create(const CreateOptions& options, const std::vector& dylibsToCache, const std::vector& otherOsDylibs, const std::vector& osExecutables) { CreateResults results; - CacheBuilder cache(options); + const char* prefix = nullptr; + if ( (options.pathPrefixes.size() == 1) && !options.pathPrefixes[0].empty() ) + prefix = options.pathPrefixes[0].c_str(); + // FIXME: This prefix will be applied to dylib closures and executable closures, even though + // the old code didn't have a prefix on cache dylib closures + dyld3::closure::FileSystemPhysical fileSystem(prefix); + CacheBuilder cache(options, fileSystem); + if (!cache.errorMessage().empty()) { + results.errorMessage = cache.errorMessage(); + return results; + } - cache.build(dylibsToCache, otherOsDylibs, osExecutables); + std::vector aliases; + switch ( options.platform ) { + case dyld3::Platform::iOS: + case dyld3::Platform::watchOS: + case dyld3::Platform::tvOS: + // FIXME: embedded cache builds should be getting aliases from manifest + aliases.push_back({"/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit", "/System/Library/Frameworks/IOKit.framework/IOKit"}); + aliases.push_back({"/usr/lib/libstdc++.6.dylib", "/usr/lib/libstdc++.dylib"}); + aliases.push_back({"/usr/lib/libstdc++.6.dylib", "/usr/lib/libstdc++.6.0.9.dylib"}); + aliases.push_back({"/usr/lib/libz.1.dylib", "/usr/lib/libz.dylib"}); + aliases.push_back({"/usr/lib/libSystem.B.dylib", "/usr/lib/libSystem.dylib"}); + break; + default: + break; + } - results.agileSignature = cache.agileSignature(); - results.cdHashFirst = cache.cdHashFirst(); - results.cdHashSecond = cache.cdHashSecond(); - results.warnings = cache.warnings(); - results.evictions = cache.evictions(); + cache.build(dylibsToCache, otherOsDylibs, osExecutables, aliases); + results.agileSignature = cache.agileSignature(); + results.cdHashFirst = cache.cdHashFirst(); + results.cdHashSecond = cache.cdHashSecond(); + results.warnings = cache.warnings(); + results.evictions = cache.evictions(); 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(); + if ( !options.outputFilePath.empty() ) { + // write cache file, if path non-empty + cache.writeFile(options.outputFilePath); + } + if ( !options.outputMapFilePath.empty() ) { + // write map file, if path non-empty + cache.writeMapFile(options.outputMapFilePath); + } } + results.errorMessage = cache.errorMessage(); + cache.deleteBuffer(); 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)) { + if ( dylib.mh->canBePlacedInDyldCache(dylib.runtimePath.c_str(), ^(const char* msg) { badDylibs[dylib.runtimePath].insert(msg);}) ) { knownDylibs.insert(dylib.runtimePath); - knownDylibs.insert(parser.installName()); - } else { - badDylibs[dylib.runtimePath] = reasons; + knownDylibs.insert(dylib.mh->installName()); } } @@ -111,41 +135,33 @@ bool DyldSharedCache::verifySelfContained(std::vector& dylibsToCach 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) { + dylib.mh->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); - } + badDylibs[dylib.runtimePath].insert(std::string("Could not find dependency '") + loadPath +"'"); knownDylibs.erase(dylib.runtimePath); - dyld3::MachOParser parserBad(dylib.mh); - knownDylibs.erase(parserBad.installName()); + knownDylibs.erase(dylib.mh->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()); + if ( foundMapping.mh->canBePlacedInDyldCache(foundMapping.runtimePath.c_str(), ^(const char* msg) { badDylibs[foundMapping.runtimePath].insert(msg);})) { + // see if existing mapping was returned + bool alreadyInVector = false; + for (const MappedMachO& existing : dylibsToCache) { + if ( existing.mh == foundMapping.mh ) { + alreadyInVector = true; + break; + } } + if ( !alreadyInVector ) + foundMappings.push_back(foundMapping); + knownDylibs.insert(loadPath); + knownDylibs.insert(foundMapping.runtimePath); + knownDylibs.insert(foundMapping.mh->installName()); } } } @@ -179,6 +195,33 @@ void DyldSharedCache::forEachRegion(void (^handler)(const void* content, uint64_ } } +bool DyldSharedCache::inCache(const void* addr, size_t length, bool& readOnly) const +{ + // quick out if before start of cache + if ( addr < this ) + return false; + + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + uintptr_t slide = (uintptr_t)this - (uintptr_t)(mappings[0].address); + uintptr_t unslidStart = (uintptr_t)addr - slide; + + // quick out if after end of cache + if ( unslidStart > (mappings[2].address + mappings[2].size) ) + return false; + + // walk cache regions + const dyld_cache_mapping_info* mappingsEnd = &mappings[header.mappingCount]; + uintptr_t unslidEnd = unslidStart + length; + for (const dyld_cache_mapping_info* m=mappings; m < mappingsEnd; ++m) { + if ( (unslidStart >= m->address) && (unslidEnd < (m->address+m->size)) ) { + readOnly = ((m->initProt & VM_PROT_WRITE) == 0); + return true; + } + } + + return false; +} + 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); @@ -220,7 +263,16 @@ void DyldSharedCache::forEachImageEntry(void (^handler)(const char* path, uint64 } } -void DyldSharedCache::forEachImageTextSegment(void (^handler)(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const uuid_t dylibUUID, const char* installName)) const +const mach_header* DyldSharedCache::getIndexedImageEntry(uint32_t index, 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); + mTime = dylibs[index].modTime; + inode = dylibs[index].inode; + return (mach_header*)((uint8_t*)this + dylibs[index].address - mappings[0].address); +} + +void DyldSharedCache::forEachImageTextSegment(void (^handler)(uint64_t loadAddressUnslid, uint64_t textSegmentSize, const uuid_t dylibUUID, const char* installName, bool& stop)) const { // check for old cache without imagesText array if ( header.mappingOffset < 123 ) @@ -229,13 +281,31 @@ void DyldSharedCache::forEachImageTextSegment(void (^handler)(uint64_t loadAddre // 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); + bool stop = false; + for (const dyld_cache_image_text_info* p=imagesText; p < imagesTextEnd && !stop; ++p) { + handler(p->loadAddress, p->textSegmentSize, p->uuid, (char*)this + p->pathOffset, stop); } } +bool DyldSharedCache::addressInText(uint32_t cacheOffset, uint32_t* imageIndex) const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + if ( cacheOffset > mappings[0].size ) + return false; + uint64_t targetAddr = mappings[0].address + cacheOffset; + // 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) { + if ( (p->loadAddress <= targetAddr) && (targetAddr < p->loadAddress+p->textSegmentSize) ) { + *imageIndex = (uint32_t)(p-imagesText); + return true; + } + } + return false; +} -std::string DyldSharedCache::archName() const +const char* DyldSharedCache::archName() const { const char* archSubString = ((char*)this) + 8; while (*archSubString == ' ') @@ -244,12 +314,12 @@ std::string DyldSharedCache::archName() const } -uint32_t DyldSharedCache::platform() const +dyld3::Platform DyldSharedCache::platform() const { - return header.platform; + return (dyld3::Platform)header.platform; } -#if !DYLD_IN_PROCESS +#if BUILDING_CACHE_BUILDER std::string DyldSharedCache::mapFile() const { __block std::string result; @@ -280,10 +350,10 @@ std::string DyldSharedCache::mapFile() const 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) { + const dyld3::MachOFile* mf = (dyld3::MachOFile*)mh; + mf->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) { char lineBuffer[256]; - sprintf(lineBuffer, "\t%16s 0x%08llX -> 0x%08llX\n", segName, vmAddr, vmAddr+vmSize); + sprintf(lineBuffer, "\t%16s 0x%08llX -> 0x%08llX\n", info.segName, info.vmAddr, info.vmAddr+info.vmSize); result += lineBuffer; }); result += "\n"; @@ -319,8 +389,178 @@ uint64_t DyldSharedCache::mappedSize() const return (endAddr - startAddr); } +bool DyldSharedCache::findMachHeaderImageIndex(const mach_header* mh, uint32_t& imageIndex) const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + uintptr_t slide = (uintptr_t)this - (uintptr_t)(mappings[0].address); + uint64_t unslidMh = (uintptr_t)mh - slide; + const dyld_cache_image_info* dylibs = (dyld_cache_image_info*)((char*)this + header.imagesOffset); + for (uint32_t i=0; i < header.imagesCount; ++i) { + if ( dylibs[i].address == unslidMh ) { + imageIndex = i; + return true; + } + } + return false; +} +bool DyldSharedCache::hasImagePath(const char* dylibPath, uint32_t& imageIndex) const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + if ( mappings[0].fileOffset != 0 ) + return false; + if ( header.mappingOffset >= 0x118 ) { + uintptr_t slide = (uintptr_t)this - (uintptr_t)(mappings[0].address); + const uint8_t* dylibTrieStart = (uint8_t*)(this->header.dylibsTrieAddr + slide); + const uint8_t* dylibTrieEnd = dylibTrieStart + this->header.dylibsTrieSize; + + Diagnostics diag; + const uint8_t* imageNode = dyld3::MachOLoaded::trieWalk(diag, dylibTrieStart, dylibTrieEnd, dylibPath); + if ( imageNode != NULL ) { + imageIndex = (uint32_t)dyld3::MachOFile::read_uleb128(diag, imageNode, dylibTrieEnd); + return true; + } + } + else { + const dyld_cache_image_info* dylibs = (dyld_cache_image_info*)((char*)this + header.imagesOffset); + uint64_t firstImageOffset = 0; + uint64_t firstRegionAddress = mappings[0].address; + for (uint32_t i=0; i < header.imagesCount; ++i) { + const char* aPath = (char*)this + dylibs[i].pathFileOffset; + if ( strcmp(aPath, dylibPath) == 0 ) { + imageIndex = i; + return true; + } + uint64_t offset = dylibs[i].address - firstRegionAddress; + if ( firstImageOffset == 0 ) + firstImageOffset = offset; + // skip over aliases + if ( dylibs[i].pathFileOffset < firstImageOffset) + continue; + } + } + return false; +} + +const dyld3::closure::Image* DyldSharedCache::findDlopenOtherImage(const char* path) const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + if ( mappings[0].fileOffset != 0 ) + return nullptr; + if ( header.mappingOffset < sizeof(dyld_cache_header) ) + return nullptr; + if ( header.otherImageArrayAddr == 0 ) + return nullptr; + uintptr_t slide = (uintptr_t)this - (uintptr_t)(mappings[0].address); + const uint8_t* dylibTrieStart = (uint8_t*)(this->header.otherTrieAddr + slide); + const uint8_t* dylibTrieEnd = dylibTrieStart + this->header.otherTrieSize; + + Diagnostics diag; + const uint8_t* imageNode = dyld3::MachOLoaded::trieWalk(diag, dylibTrieStart, dylibTrieEnd, path); + if ( imageNode != NULL ) { + dyld3::closure::ImageNum imageNum = (uint32_t)dyld3::MachOFile::read_uleb128(diag, imageNode, dylibTrieEnd); + uint64_t arrayAddrOffset = header.otherImageArrayAddr - mappings[0].address; + const dyld3::closure::ImageArray* otherImageArray = (dyld3::closure::ImageArray*)((char*)this + arrayAddrOffset); + return otherImageArray->imageForNum(imageNum); + } + + return nullptr; +} + + + + +const dyld3::closure::LaunchClosure* DyldSharedCache::findClosure(const char* executablePath) const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + uintptr_t slide = (uintptr_t)this - (uintptr_t)(mappings[0].address); + const uint8_t* executableTrieStart = (uint8_t*)(this->header.progClosuresTrieAddr + slide); + const uint8_t* executableTrieEnd = executableTrieStart + this->header.progClosuresTrieSize; + const uint8_t* closuresStart = (uint8_t*)(this->header.progClosuresAddr + slide); + + Diagnostics diag; + const uint8_t* imageNode = dyld3::MachOLoaded::trieWalk(diag, executableTrieStart, executableTrieEnd, executablePath); + if ( (imageNode == NULL) && (strncmp(executablePath, "/System/", 8) == 0) ) { + // anything in /System/ should have a closure. Perhaps it was launched via symlink path + char realPath[PATH_MAX]; + if ( realpath(executablePath, realPath) != NULL ) + imageNode = dyld3::MachOLoaded::trieWalk(diag, executableTrieStart, executableTrieEnd, realPath); + } + if ( imageNode != NULL ) { + uint32_t closureOffset = (uint32_t)dyld3::MachOFile::read_uleb128(diag, imageNode, executableTrieEnd); + if ( closureOffset < this->header.progClosuresSize ) + return (dyld3::closure::LaunchClosure*)((uint8_t*)closuresStart + closureOffset); + } + + return nullptr; +} + +#if !BUILDING_LIBDYLD && !BUILDING_DYLD +void DyldSharedCache::forEachLaunchClosure(void (^handler)(const char* executableRuntimePath, const dyld3::closure::LaunchClosure* closure)) const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + uintptr_t slide = (uintptr_t)this - (uintptr_t)(mappings[0].address); + const uint8_t* executableTrieStart = (uint8_t*)(this->header.progClosuresTrieAddr + slide); + const uint8_t* executableTrieEnd = executableTrieStart + this->header.progClosuresTrieSize; + const uint8_t* closuresStart = (uint8_t*)(this->header.progClosuresAddr + slide); + + std::vector closureEntries; + if ( Trie::parseTrie(executableTrieStart, executableTrieEnd, closureEntries) ) { + for (DylibIndexTrie::Entry& entry : closureEntries ) { + uint32_t offset = entry.info.index; + if ( offset < this->header.progClosuresSize ) + handler(entry.name.c_str(), (const dyld3::closure::LaunchClosure*)(closuresStart+offset)); + } + } +} + +void DyldSharedCache::forEachDlopenImage(void (^handler)(const char* runtimePath, const dyld3::closure::Image* image)) const +{ + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + uintptr_t slide = (uintptr_t)this - (uintptr_t)(mappings[0].address); + const uint8_t* otherTrieStart = (uint8_t*)(this->header.otherTrieAddr + slide); + const uint8_t* otherTrieEnd = otherTrieStart + this->header.otherTrieSize; + + std::vector otherEntries; + if ( Trie::parseTrie(otherTrieStart, otherTrieEnd, otherEntries) ) { + for (const DylibIndexTrie::Entry& entry : otherEntries ) { + dyld3::closure::ImageNum imageNum = entry.info.index; + uint64_t arrayAddrOffset = header.otherImageArrayAddr - mappings[0].address; + const dyld3::closure::ImageArray* otherImageArray = (dyld3::closure::ImageArray*)((char*)this + arrayAddrOffset); + handler(entry.name.c_str(), otherImageArray->imageForNum(imageNum)); + } + } +} +#endif + +const dyld3::closure::ImageArray* DyldSharedCache::cachedDylibsImageArray() const +{ + // check for old cache without imagesArray + if ( header.mappingOffset < 0x100 ) + return nullptr; + + if ( header.dylibsImageArrayAddr == 0 ) + return nullptr; + + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + uint64_t arrayAddrOffset = header.dylibsImageArrayAddr - mappings[0].address; + return (dyld3::closure::ImageArray*)((char*)this + arrayAddrOffset); +} + +const dyld3::closure::ImageArray* DyldSharedCache::otherOSImageArray() const +{ + // check for old cache without imagesArray + if ( header.mappingOffset < sizeof(dyld_cache_header) ) + return nullptr; + + if ( header.otherImageArrayAddr == 0 ) + return nullptr; + + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((char*)this + header.mappingOffset); + uint64_t arrayAddrOffset = header.otherImageArrayAddr - mappings[0].address; + return (dyld3::closure::ImageArray*)((char*)this + arrayAddrOffset); +} diff --git a/dyld3/shared-cache/DyldSharedCache.h b/dyld3/shared-cache/DyldSharedCache.h index 56008b5..3feea53 100644 --- a/dyld3/shared-cache/DyldSharedCache.h +++ b/dyld3/shared-cache/DyldSharedCache.h @@ -29,21 +29,12 @@ #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; - } - } -} +#include "MachOAnalyzer.h" +#include "Closure.h" class VIS_HIDDEN DyldSharedCache @@ -59,17 +50,19 @@ public: struct CreateOptions { + std::string outputFilePath; + std::string outputMapFilePath; 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 isLocallyBuiltCache; bool verbose; bool evictLeafDylibsOnOverflow; std::unordered_map dylibOrdering; @@ -82,11 +75,11 @@ public: { 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) + MappedMachO(const std::string& path, const dyld3::MachOAnalyzer* 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; + const dyld3::MachOAnalyzer* mh; size_t length; uint64_t isSetUID : 1, protectedBySIP : 1, @@ -97,14 +90,19 @@ public: struct CreateResults { - const DyldSharedCache* cacheContent = nullptr; // caller needs to vm_deallocate() when done - size_t cacheLength = 0; - std::string errorMessage; - std::set warnings; - std::set evictions; - bool agileSignature = false; - std::string cdHashFirst; - std::string cdHashSecond; + std::string errorMessage; + std::set warnings; + std::set evictions; + bool agileSignature = false; + std::string cdHashFirst; + std::string cdHashSecond; + }; + + + struct FileAlias + { + std::string realPath; + std::string aliasPath; }; @@ -149,13 +147,13 @@ public: // // Returns the architecture name of the shared cache, e.g. "arm64" // - std::string archName() const; + const char* archName() const; // // Returns the platform the cache is for // - uint32_t platform() const; + dyld3::Platform platform() const; // @@ -165,6 +163,17 @@ public: // + // Searches cache for dylib with specified path + // + bool hasImagePath(const char* dylibPath, uint32_t& imageIndex) const; + + + // + // Searches cache for dylib with specified mach_header + // + bool findMachHeaderImageIndex(const mach_header* mh, uint32_t& imageIndex) const; + + // // Iterates over each dylib in the cache // void forEachImageEntry(void (^handler)(const char* path, uint64_t mTime, uint64_t inode)) const; @@ -173,7 +182,13 @@ public: // // 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; + const mach_header* getIndexedImageEntry(uint32_t index, uint64_t& mTime, uint64_t& node) 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, bool& stop)) const; // @@ -182,6 +197,12 @@ public: void forEachRegion(void (^handler)(const void* content, uint64_t vmAddr, uint64_t size, uint32_t permissions)) const; + // + // Returns if an address range is in this cache, and if so if in a read-only area + // + bool inCache(const void* addr, size_t length, bool& readOnly) const; + + // // returns address the cache would load at if unslid // @@ -200,6 +221,49 @@ public: uint64_t mappedSize() const; + // + // searches cache for pre-built closure for program + // + const dyld3::closure::LaunchClosure* findClosure(const char* executablePath) const; + + + // + // iterates all pre-built closures for program + // + void forEachLaunchClosure(void (^handler)(const char* executableRuntimePath, const dyld3::closure::LaunchClosure* closure)) const; + + + // + // iterates all pre-built Image* for OS dylibs/bundles not in dyld cache + // + void forEachDlopenImage(void (^handler)(const char* runtimePath, const dyld3::closure::Image* image)) const; + + + // + // returns the ImageArray pointer to Images in dyld shared cache + // + const dyld3::closure::ImageArray* cachedDylibsImageArray() const; + + + // + // returns the ImageArray pointer to Images in OS with pre-build dlopen closure + // + const dyld3::closure::ImageArray* otherOSImageArray() const; + + + // + // searches cache for pre-built dlopen closure for OS dylib/bundle + // + const dyld3::closure::Image* findDlopenOtherImage(const char* path) const; + + + // + // returns true if the offset is in the TEXT of some cached dylib and sets *index to the dylib index + // + bool addressInText(uint32_t cacheOffset, uint32_t* index) const; + + + dyld_cache_header header; }; diff --git a/dyld3/shared-cache/FileUtils.cpp b/dyld3/shared-cache/FileUtils.cpp index 19f02a4..1e34aae 100644 --- a/dyld3/shared-cache/FileUtils.cpp +++ b/dyld3/shared-cache/FileUtils.cpp @@ -45,6 +45,7 @@ #include #include "FileUtils.h" +#include "StringUtils.h" #include "Diagnostics.h" #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101200 @@ -52,7 +53,7 @@ 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) +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, bool recurse) { std::string fullDirPath = pathPrefix + path; DIR* dir = ::opendir(fullDirPath.c_str()); @@ -81,7 +82,8 @@ void iterateDirectoryTree(const std::string& pathPrefix, const std::string& path break; if ( dirFilter(dirAndFile) ) break; - iterateDirectoryTree(pathPrefix, dirAndFile, dirFilter, fileCallback); + if (recurse) + iterateDirectoryTree(pathPrefix, dirAndFile, dirFilter, fileCallback, processFiles, true); break; case DT_LNK: // don't follow symlinks, dylib will be found through absolute path @@ -101,7 +103,7 @@ bool safeSave(const void* buffer, size_t bufferLen, const std::string& path) int fd = mkstemp(pathTemplateSpace); if ( fd != -1 ) { ssize_t writtenSize = pwrite(fd, buffer, bufferLen, 0); - if ( writtenSize == bufferLen ) { + if ( (size_t)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); @@ -114,13 +116,13 @@ bool safeSave(const void* buffer, size_t bufferLen, const std::string& path) return false; // failure } -const void* mapFileReadOnly(const std::string& path, size_t& mappedSize) +const void* mapFileReadOnly(const char* path, size_t& mappedSize) { struct stat statBuf; - if ( ::stat(path.c_str(), &statBuf) != 0 ) + if ( ::stat(path, &statBuf) != 0 ) return nullptr; - int fd = ::open(path.c_str(), O_RDONLY); + int fd = ::open(path, O_RDONLY); if ( fd < 0 ) return nullptr; @@ -181,24 +183,37 @@ bool fileExists(const std::string& path) // // 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 parseOrderFile(const std::string& orderFileData) { 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++; + if (orderFileData.empty()) + return order; + + std::stringstream myData(orderFileData); + + uint32_t count = 0; + std::string line; + while ( std::getline(myData, line) ) { + size_t pos = line.find('#'); + if ( pos != std::string::npos ) + line.resize(pos); + while ( !line.empty() && isspace(line.back()) ) { + line.pop_back(); } - myfile.close(); + if ( !line.empty() ) + order[line] = count++; + } + return order; +} + +std::string loadOrderFile(const std::string& orderFilePath) { + std::string order; + + size_t size = 0; + char* data = (char*)mapFileReadOnly(orderFilePath.c_str(), size); + if (data) { + order = std::string(data, size); + ::munmap((void*)data, size); } return order; @@ -305,23 +320,6 @@ 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; @@ -410,5 +408,173 @@ std::pair FileCache::fill(Diagnostics& diags, const std:: return std::make_pair((uint8_t*)buffer_ptr, stat_buf); } +static void normalizePath(std::string& path) { + // Remove a bunch of stuff we don't need, like trailing slashes. + while ( !path.empty() && (path.back() == '/')) + path.pop_back(); +} + +void SymlinkResolver::addFile(Diagnostics& diags, std::string path) { + if (path.front() != '/') { + diags.error("Path must start with '/'"); + return; + } + if (symlinks.find(path) != symlinks.end()) { + diags.error("Cannot add regular file as it is already a symlink"); + return; + } + filePaths.insert(path); +} + +void SymlinkResolver::addSymlink(Diagnostics& diags, std::string fromPath, std::string toPath) { + normalizePath(fromPath); + normalizePath(toPath); + if (fromPath.front() != '/') { + diags.error("Path must start with '/'"); + return; + } + if (filePaths.find(fromPath) != filePaths.end()) { + diags.error("Cannot add symlink from '%s' as it is already a regular path", fromPath.c_str()); + return; + } + auto itAndInserted = symlinks.insert({ fromPath, toPath }); + if (!itAndInserted.second) { + // The path is already a symlink. Make sure its a dupe. + if (toPath != itAndInserted.first->second) { + diags.error("Duplicate symlink for path '%s'", fromPath.c_str()); + return; + } + } +} + +std::string SymlinkResolver::realPath(Diagnostics& diags, const std::string& originalPath) const { + // First make sure the path doesn't have any magic in it. + std::string path = originalPath; + normalizePath(path); + + std::set seenSymlinks; + + // Now see if any prefix is a symlink + if (path.front() != '/') + return path; + + std::string::size_type prev_pos = 0; + while (prev_pos != std::string::npos) { + std::string::size_type pos = path.find("/", prev_pos + 1); + + // First look to see if this path component is special, eg, ., .., etc. + std::string component = path.substr(prev_pos, pos - prev_pos); + if (component == "/..") { + // Fold with the previous path component. + if (prev_pos == 0) { + // This is the root path, and .. applied to / is just / + path = path.substr(3); + prev_pos = 0; + } else { + std::string::size_type lastSlashPos = path.rfind("/", prev_pos - 1); + path = path.substr(0, lastSlashPos) + path.substr(pos); + prev_pos = lastSlashPos; + } + continue; + } else if (component == "/.") { + if (prev_pos == 0) { + // Path starts with /./ so just remove the first one. + path = path.substr(2); + } else { + if (pos == std::string::npos) { + // Trailing . on the path + path = path.substr(0, prev_pos ); + } else { + path = path.substr(0, prev_pos) + path.substr(pos); + } + } + continue; + } else if (component == "/") { + // Path must contain // somewhere so strip out the duplicates. + if (prev_pos == 0) { + // Path starts with // so just remove the first one. + path = path.substr(1); + } else { + if (pos == std::string::npos) { + // Trailing / on the path + path = path.substr(0, prev_pos); + prev_pos = pos; + } else { + path = path.substr(0, pos) + path.substr(pos + 1); + } + } + continue; + } + + // Path is not special, so see if it is a symlink to something. + std::string prefix = path.substr(0, pos); + //printf("%s\n", prefix.c_str()); + auto it = symlinks.find(prefix); + if (it == symlinks.end()) { + // This is not a symlink so move to the next prefix. + prev_pos = pos; + continue; + } + + // If we've already done this prefix then error out. + if (seenSymlinks.count(prefix)) { + diags.error("Loop in symlink processing for '%s'", originalPath.c_str()); + return std::string(); + } + + seenSymlinks.insert(prefix); + + // This is a symlink, so resolve the new path. + std::string toPath = it->second; + if (toPath.front() == '/') { + // Symlink points to an absolute address so substitute the whole prefix for the new path + // If we didn't substitute the last component of the path then there is also a path suffix. + std::string pathSuffix = ""; + if (pos != std::string::npos) { + std::string::size_type nextSlashPos = path.find("/", pos + 1); + if (nextSlashPos != std::string::npos) + pathSuffix = path.substr(nextSlashPos); + } + path = toPath + pathSuffix; + prev_pos = 0; + continue; + } + + // Symlink points to a relative path so we need to do more processing to get the real path. + + // First calculate which part of the previous prefix we'll keep. Eg, in /a/b/c where "b -> blah", we want to keep /a here. + std::string prevPrefix = path.substr(0, prev_pos); + //printf("prevPrefix %s\n", prevPrefix.c_str()); + + // If we didn't substitute the last component of the path then there is also a path suffix. + std::string pathSuffix = ""; + if (prefix.size() != path.size()) + pathSuffix = path.substr(pos); + + // The new path is the remaining prefix, plus the symlink target, plus any remaining suffix from the original path. + path = prevPrefix + "/" + toPath + pathSuffix; + prev_pos = 0; + } + return path; +} + +std::vector SymlinkResolver::getResolvedSymlinks(Diagnostics& diags) { + diags.assertNoError(); + std::vector aliases; + for (auto& fromPathAndToPath : symlinks) { + std::string newPath = realPath(diags, fromPathAndToPath.first); + if (diags.hasError()) { + aliases.clear(); + return aliases; + } + + if (filePaths.count(newPath)) { + aliases.push_back({ newPath, fromPathAndToPath.first }); + // printf("symlink ('%s' -> '%s') resolved to '%s'\n", fromPathAndToPath.first.c_str(), fromPathAndToPath.second.c_str(), newPath.c_str()); + } + } + return aliases; +} + #endif // BUILDING_CACHE_BUILDER diff --git a/dyld3/shared-cache/FileUtils.h b/dyld3/shared-cache/FileUtils.h index dbf6ae4..fe3f21e 100644 --- a/dyld3/shared-cache/FileUtils.h +++ b/dyld3/shared-cache/FileUtils.h @@ -27,20 +27,22 @@ #include +#include +#include #include #include #include #include #include +#include "DyldSharedCache.h" + 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); @@ -59,7 +61,7 @@ extern FileCache fileCache; // 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); + void (^callback)(const std::string& path, const struct stat& statBuf), bool processFiles=true, bool recurse=true); // @@ -69,14 +71,15 @@ void iterateDirectoryTree(const std::string& pathPrefix, const std::string& path bool safeSave(const void* buffer, size_t bufferLen, const std::string& path); -const void* mapFileReadOnly(const std::string& path, size_t& mappedSize); +const void* mapFileReadOnly(const char* 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::unordered_map parseOrderFile(const std::string& orderFileData); +std::string loadOrderFile(const std::string& orderFilePath); std::string normalize_absolute_file_path(std::string path); std::string basePath(const std::string& path); @@ -86,7 +89,21 @@ std::string realFilePath(const std::string& path); std::string toolDir(); +class SymlinkResolver { +public: + SymlinkResolver() { } + + void addFile(Diagnostics& diags, std::string path); + + void addSymlink(Diagnostics& diags, std::string fromPath, std::string toPath); + std::string realPath(Diagnostics& diags, const std::string& path) const; + std::vector getResolvedSymlinks(Diagnostics& diags); + +private: + std::set filePaths; + std::map symlinks; +}; #endif // FileUtils_h diff --git a/dyld3/shared-cache/ImageProxy.cpp b/dyld3/shared-cache/ImageProxy.cpp deleted file mode 100644 index 960e1b9..0000000 --- a/dyld3/shared-cache/ImageProxy.cpp +++ /dev/null @@ -1,2372 +0,0 @@ -/* - * 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++, true); - 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, true); - 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)); - if ( replacementOffsetInImage >= 0xFFFFFFFFULL ) { - diag.warning("bad interposing implementation in %s", _images[imageIndex]->runtimePath().c_str()); - return; - } - 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)); - if ( foundInfo.value >= (1ULL << 32) ) { - diag.warning("bad weak symbol address in %s", proxy->runtimePath().c_str()); - return; - } - 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 deleted file mode 100644 index 35bf42c..0000000 --- a/dyld3/shared-cache/ImageProxy.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * 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 index a15100f..824742f 100644 --- a/dyld3/shared-cache/MachOFileAbstraction.hpp +++ b/dyld3/shared-cache/MachOFileAbstraction.hpp @@ -67,6 +67,20 @@ struct uuid_command { #ifndef CPU_TYPE_ARM64 #define CPU_TYPE_ARM64 ((cpu_type_t) (CPU_TYPE_ARM | CPU_ARCH_ABI64)) #endif +#ifndef CPU_TYPE_ARM64_32 + #ifndef CPU_ARCH_ABI64_32 + #define CPU_ARCH_ABI64_32 0x02000000 + #endif + #define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32) +#endif + +#ifndef CPU_SUBTYPE_ARM64_32_V8 + #define CPU_SUBTYPE_ARM64_32_V8 1 +#endif + +#ifndef CPU_SUBTYPE_ARM64_E + #define CPU_SUBTYPE_ARM64_E 2 +#endif #define ARM64_RELOC_UNSIGNED 0 // for pointers @@ -113,6 +127,7 @@ struct uuid_command { #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 DYLD_CACHE_ADJ_V2_THREADED_POINTER_64 0x0D #define MH_HAS_OBJC 0x40000000 @@ -952,7 +967,7 @@ inline int64_t read_sleb128(const uint8_t*& p, const uint8_t* end) } while (byte & 0x80); // sign extend negative numbers if ( (byte & 0x40) != 0 ) - result |= (-1LL) << bit; + result |= (~0ULL) << bit; return result; } diff --git a/dyld3/shared-cache/Manifest.h b/dyld3/shared-cache/Manifest.h index 3348cfa..e48e4e7 100644 --- a/dyld3/shared-cache/Manifest.h +++ b/dyld3/shared-cache/Manifest.h @@ -39,14 +39,37 @@ #import -#include "MachOParser.h" #include "DyldSharedCache.h" #include "Diagnostics.h" +#include "MachOAnalyzer.h" extern std::string toolDir(); namespace dyld3 { +struct VIS_HIDDEN UUID { + UUID() {} + UUID(const UUID& other) { uuid_copy(_bytes, other._bytes); } + UUID(const uuid_t other) { uuid_copy(&_bytes[0], other); } + UUID(const dyld3::MachOAnalyzer* ml) { ml->getUuid(_bytes); } + bool operator<(const UUID& other) const { return uuid_compare(_bytes, other._bytes) < 0; } + bool operator==(const UUID& other) const { return uuid_compare(_bytes, other._bytes) == 0; } + bool operator!=(const UUID& other) const { return !(*this == other); } + + size_t hash() const + { + size_t retval = 0; + for (size_t i = 0; i < (16 / sizeof(size_t)); ++i) { + retval ^= ((size_t*)(&_bytes[0]))[i]; + } + return retval; + } + const unsigned char* get() const { return _bytes; }; + +private: + uuid_t _bytes; +}; + struct BuildQueueEntry { DyldSharedCache::CreateOptions options; std::vector dylibsForCache; @@ -58,7 +81,7 @@ struct BuildQueueEntry { struct Manifest { struct UUIDInfo { - const mach_header* mh; + const MachOAnalyzer* mh; uint64_t sliceFileOffset; std::size_t size; std::string runtimePath; @@ -66,7 +89,7 @@ struct Manifest { 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) + UUIDInfo(const MachOAnalyzer* 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(), "", "", "", "") {} }; @@ -75,13 +98,6 @@ struct Manifest { std::vector sources; }; - struct File { - MachOParser* parser; - File(MachOParser* P) - : parser(P) - { - } - }; struct SegmentInfo { std::string name; @@ -116,7 +132,7 @@ struct Manifest { CacheInfo developmentCache; CacheInfo productionCache; CacheImageInfo& dylibForInstallname(const std::string& installname); - void exclude(MachOParser* parser, const std::string& reason); + void exclude(const dyld3::MachOAnalyzer* ml, const std::string& reason); void exclude(Manifest& manifest, const UUID& uuid, const std::string& reason); }; @@ -170,29 +186,37 @@ struct Manifest { 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); + Manifest(Diagnostics& D, const std::string& path, bool onlyParseManifest = false); + Manifest(Diagnostics& D, const std::string& path, const std::set& overlays, bool onlyParseManifest = false); - BuildQueueEntry makeQueueEntry(const std::string& outputPath, const std::set& configs, const std::string& arch, bool optimizeStubs, const std::string& prefix, bool verbose); + BuildQueueEntry makeQueueEntry(const std::string& outputPath, const std::set& configs, const std::string& arch, bool optimizeStubs, const std::string& prefix, + bool isLocallyBuiltCache, bool skipWrites, 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 MachOAnalyzer* machOForUUID(const UUID& uuid) const; const std::string buildPathForUUID(const UUID& uuid); const std::string runtimePathForUUID(const UUID& uuid); + const std::string& installNameForUUID(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); + std::set resultsForConfiguration(const std::string& configName); + + // These are used by MRM to support having the Manifest give us a list of files/symlinks from the BOM but we use MRM for the actual cache generation + void forEachMachO(std::string configuration, std::function lambda); + + void forEachSymlink(std::string configuration, std::function lambda); private: NSDictionary* _manifestDict; Diagnostics& _diags; std::map _uuidMap; std::map, UUID> _installNameMap; + std::vector> _symlinks; static dispatch_queue_t _identifierQueue; uint32_t _manifestVersion; std::string _build; @@ -203,6 +227,7 @@ private: std::map _projects; std::map _configurations; std::map> _metabomTagMap; + std::map> _metabomSymlinkTagMap; std::map> _metabomExcludeTagMap; std::map> _metabomRestrictedTagMap; @@ -213,10 +238,8 @@ private: 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 loadParser(const void* p, size_t sliceLength, 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); @@ -226,4 +249,14 @@ private: }; } +namespace std { +template <> +struct hash { + size_t operator()(const dyld3::UUID& x) const + { + return x.hash(); + } +}; +} + #endif /* Manifest_h */ diff --git a/dyld3/shared-cache/Manifest.mm b/dyld3/shared-cache/Manifest.mm index abc23bb..ee6cb4e 100644 --- a/dyld3/shared-cache/Manifest.mm +++ b/dyld3/shared-cache/Manifest.mm @@ -40,6 +40,8 @@ extern "C" { #include "Trie.hpp" #include "FileUtils.h" #include "StringUtils.h" +#include "MachOFile.h" +#include "MachOAnalyzer.h" #include #include @@ -48,6 +50,7 @@ extern "C" { #include #include "Manifest.h" +#include "ClosureFileSystemPhysical.h" namespace { //FIXME this should be in a class @@ -78,27 +81,24 @@ inline bool is_disjoint(const Set1& set1, const Set2& set2) 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) +void Manifest::Results::exclude(const dyld3::MachOAnalyzer* mh, const std::string& reason) { - auto dylibUUID = parser->uuid(); - dylibs[dylibUUID].uuid = dylibUUID; - dylibs[dylibUUID].installname = parser->installName(); - dylibs[dylibUUID].included = false; + UUID dylibUUID(mh); + dylibs[dylibUUID].uuid = dylibUUID; + dylibs[dylibUUID].installname = mh->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; + const MachOAnalyzer* mh = manifest.machOForUUID(uuid); + dylibs[uuid].uuid = uuid; + dylibs[uuid].installname = mh->installName(); + dylibs[uuid].included = false; dylibs[uuid].exclusionInfo = reason; } @@ -262,11 +262,14 @@ 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) +BuildQueueEntry Manifest::makeQueueEntry(const std::string& outputPath, const std::set& configs, const std::string& arch, bool optimizeStubs, + const std::string& prefix, bool isLocallyBuiltCache, bool skipWrites, bool verbose) { dyld3::BuildQueueEntry retval; DyldSharedCache::CreateOptions options; + options.outputFilePath = skipWrites ? "" : outputPath; + options.outputMapFilePath = skipWrites ? "" : outputPath + ".map"; options.archName = arch; options.platform = platform(); options.excludeLocalSymbols = true; @@ -278,12 +281,13 @@ BuildQueueEntry Manifest::makeQueueEntry(const std::string& outputPath, const st options.inodesAreSameAsRuntime = false; options.cacheSupportsASLR = true; options.forSimulator = false; + options.isLocallyBuiltCache = isLocallyBuiltCache; options.verbose = verbose; options.evictLeafDylibsOnOverflow = true; options.loggingPrefix = prefix; - options.pathPrefixes = { "" }; - options.dylibOrdering = loadOrderFile(_dylibOrderFile); - options.dirtyDataSegmentOrdering = loadOrderFile(_dirtyDataOrderFile); + options.pathPrefixes = { "./Root/" }; + options.dylibOrdering = parseOrderFile(loadOrderFile(_dylibOrderFile)); + options.dirtyDataSegmentOrdering = parseOrderFile(loadOrderFile(_dirtyDataOrderFile)); dyld3::BuildQueueEntry queueEntry; retval.configNames = configs; @@ -296,36 +300,62 @@ BuildQueueEntry Manifest::makeQueueEntry(const std::string& outputPath, const st 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) +bool Manifest::loadParser(const void* p, size_t sliceLength, 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)) { + assert(!_diags.hasError()); + + const MachOFile* mf = reinterpret_cast(p); + const std::string archName = mf->archName(); + if ( archName == "unknown" ) { + // Clear the error and punt + _diags.verbose("Dylib located at '%s' has unknown architecture\n", runtimePath.c_str()); return false; } + if ( architectures.count(archName) == 0 ) + return false; - auto parser = MachOParser(mh); - if (_diags.hasError()) { + const MachOAnalyzer* ma = reinterpret_cast(p); + if ( !ma->validMachOForArchAndPlatform(_diags, sliceLength, runtimePath.c_str(), archName.c_str(), _platform) ) { // Clear the error and punt - _diags.verbose("MachoParser error: %s\n", _diags.errorMessage().c_str()); + _diags.verbose("Mach-O error: %s\n", _diags.errorMessage().c_str()); + _diags.clearError(); + return false; + } + + // if this file uses zero-fill expansion, then mapping whole file in one blob will not work + // remapIfZeroFill() will remap the file + closure::FileSystemPhysical fileSystem; + closure::LoadedFileInfo info; + info.fileContent = p; + info.fileContentLen = sliceLength; + info.sliceOffset = 0; + info.sliceLen = sliceLength; + info.inode = 0; + info.mtime = 0; + info.unload = nullptr; + ma = ma->remapIfZeroFill(_diags, fileSystem, info); + + if (ma == nullptr) { + _diags.verbose("Mach-O error: %s\n", _diags.errorMessage().c_str()); _diags.clearError(); return false; } - auto uuid = parser.uuid(); - auto archName = parser.archName(); + uuid_t uuid; + ma->getUuid(uuid); - if (parser.fileType() == MH_DYLIB && architectures.count(parser.archName()) != 0) { - std::string installName = parser.installName(); - auto index = std::make_pair(installName, parser.archName()); + if ( ma->isDylib() ) { + std::string installName = ma->installName(); + auto index = std::make_pair(installName, 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))); + _uuidMap.insert(std::make_pair(uuid, UUIDInfo(ma, sliceLength, sliceOffset, uuid, 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))); + _uuidMap.insert(std::make_pair(uuid, UUIDInfo(ma, sliceLength, sliceOffset, uuid, archName, runtimePath, buildPath, installName))); if (installName[0] != '@' && installName != runtimePath) { _diags.warning("Dylib located at '%s' has installname '%s'", runtimePath.c_str(), installName.c_str()); } @@ -336,11 +366,11 @@ bool Manifest::loadParser(const void* p, size_t size, uint64_t sliceOffset, cons // 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))); + _uuidMap.insert(std::make_pair(uuid, UUIDInfo(ma, sliceLength, sliceOffset, uuid, archName, runtimePath, buildPath, installName))); } } } else { - _uuidMap.insert(std::make_pair(uuid, UUIDInfo(mh, size, sliceOffset, uuid, parser.archName(), runtimePath, buildPath, ""))); + _uuidMap.insert(std::make_pair(uuid, UUIDInfo(ma, sliceLength, sliceOffset, uuid, archName, runtimePath, buildPath, ""))); } return true; } @@ -358,8 +388,8 @@ bool Manifest::loadParsers(const std::string& buildPath, const std::string& runt 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 ( const FatFile* fh = FatFile::isFatFile(p) ) { + fh->forEachSlice(_diags, stat_buf.st_size, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, uint64_t sliceSize, bool& stop) { if (loadParser(sliceStart, sliceSize, (uintptr_t)sliceStart-(uintptr_t)p, runtimePath, buildPath, architectures)) retval = true; }); @@ -369,6 +399,7 @@ bool Manifest::loadParsers(const std::string& buildPath, const std::string& runt return retval; } + const Manifest::UUIDInfo& Manifest::infoForUUID(const UUID& uuid) const { auto i = _uuidMap.find(uuid); assert(i != _uuidMap.end()); @@ -387,8 +418,8 @@ const Manifest::UUIDInfo Manifest::infoForInstallNameAndarch(const std::string& return i->second; } -MachOParser Manifest::parserForUUID(const UUID& uuid) const { - return MachOParser(infoForUUID(uuid).mh); +const MachOAnalyzer* Manifest::machOForUUID(const UUID& uuid) const { + return infoForUUID(uuid).mh; } const std::string Manifest::buildPathForUUID(const UUID& uuid) { @@ -398,22 +429,29 @@ const std::string Manifest::buildPathForUUID(const UUID& uuid) { 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()) + +const std::string& Manifest::installNameForUUID(const UUID& uuid) { + return infoForUUID(uuid).installName; +} + + +Manifest::Manifest(Diagnostics& D, const std::string& path, bool onlyParseManifest) : Manifest(D, path, std::set(), onlyParseManifest) { } -Manifest::Manifest(Diagnostics& D, const std::string& path, const std::set& overlays) : +Manifest::Manifest(Diagnostics& D, const std::string& path, const std::set& overlays, bool onlyParseManifest) : _diags(D) { - NSMutableDictionary* manifestDict = [NSMutableDictionary dictionaryWithContentsOfFile:cppToObjStr(path)]; - NSString* platStr = manifestDict[@"platform"]; + _manifestDict = [NSMutableDictionary dictionaryWithContentsOfFile:cppToObjStr(path)]; + if (!_manifestDict) + return; + NSString* platStr = _manifestDict[@"platform"]; std::set architectures; if (platStr == nullptr) platStr = @"ios"; std::string platformString = [platStr UTF8String]; - setMetabomFile([manifestDict[@"metabomFile"] UTF8String]); + setMetabomFile([_manifestDict[@"metabomFile"] UTF8String]); if (platformString == "ios") { setPlatform(dyld3::Platform::iOS); @@ -430,25 +468,25 @@ Manifest::Manifest(Diagnostics& D, const std::string& path, const std::set Manifest::otherDylibsAndBundles(const const auto& dylibs = _configurations[configuration].architectures[architecture].results.dylibs; for (const auto& dylib : dylibs) { if (!dylib.second.included) { - insert(retval, dylib.second); + const UUIDInfo& info = infoForUUID(dylib.second.uuid); + if ( ((MachOAnalyzer*)(info.mh))->canHavePrecomputedDlopenClosure(info.runtimePath.c_str(), ^(const char*) {}) ) + insert(retval, dylib.second); } } const auto& bundles = _configurations[configuration].architectures[architecture].results.bundles; for (const auto& bundle : bundles) { - insert(retval, bundle.second); + const UUIDInfo& info = infoForUUID(bundle.second.uuid); + if ( ((MachOAnalyzer*)(info.mh))->canHavePrecomputedDlopenClosure(info.runtimePath.c_str(), ^(const char*) {}) ) + insert(retval, bundle.second); } return retval; @@ -654,6 +718,8 @@ std::vector Manifest::mainExecutables(const std::s return retval; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wrange-loop-analysis" bool Manifest::filterForConfig(const std::string& configName) { for (const auto configuration : _configurations) { @@ -671,6 +737,21 @@ bool Manifest::filterForConfig(const std::string& configName) } return false; } +#pragma clang diagnostic pop + +std::set Manifest::resultsForConfiguration(const std::string& configName) { + std::set results; + NSDictionary* configurationResults = _manifestDict[@"results"][[NSString stringWithUTF8String:configName.c_str()]]; + for (NSString* arch in configurationResults) { + NSDictionary* dylibs = configurationResults[arch][@"dylibs"]; + for (NSString* dylib in dylibs) { + NSDictionary* dylibDict = dylibs[dylib]; + if ([dylibDict[@"included"] boolValue]) + results.insert([dylib UTF8String]); + } + } + return results; +} void Manifest::dedupeDispositions(void) { // Since this is all hacky and inference based for now only do it for iOS until XBS @@ -719,88 +800,6 @@ void Manifest::remove(const std::string& config, const std::string& arch) _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) { @@ -816,13 +815,12 @@ void Manifest::calculateClosure(const std::string& configuration, const std::str 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); - } } @@ -837,41 +835,41 @@ void Manifest::calculateClosure(const std::string& configuration, const std::str } processedUuids.insert(uuid); - auto parser = parserForUUID(uuid); + const MachOAnalyzer* mh = machOForUUID(uuid); auto runtimePath = runtimePathForUUID(uuid); - assert(parser.header() != 0); + assert(mh != nullptr); - parser.forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { + mh->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) { + if (mh->isDylib()) { // 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(); + archManifest.results.dylibs[uuid].installname = mh->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"); + if ( (strcmp(mh->installName(), "/System/Library/Caches/com.apple.xpc/sdk.dylib") == 0) + || (strcmp(mh->installName(), "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib") == 0) ) { + archManifest.results.exclude(mh, "Device specific dylib"); continue; } - std::set reasons; - if (parser.canBePlacedInDyldCache(runtimePath, reasons)) { + __block std::set reasons; + if (mh->canBePlacedInDyldCache(runtimePath.c_str(), ^(const char* reason) { reasons.insert(reason); })) { 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"); + archManifest.results.exclude(mh, "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()); + cachedUUIDs.insert(uuid); } else { // It can't be placed in the cache, print out the reasons why std::string reasonString = "Rejected from cached dylibs: " + runtimePath + " " + architecture + " (\""; @@ -882,13 +880,13 @@ void Manifest::calculateClosure(const std::string& configuration, const std::str } } reasonString += "\")"; - archManifest.results.exclude(&parser, reasonString); + archManifest.results.exclude(mh, reasonString); } - } else if (parser.fileType() == MH_BUNDLE) { + } else if (mh->isBundle()) { if (archManifest.results.bundles.count(uuid) == 0) { archManifest.results.bundles[uuid].uuid = uuid; } - } else if (parser.fileType() == MH_EXECUTE) { + } else if (mh->isMainExecutable()) { //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" @@ -911,8 +909,8 @@ void Manifest::calculateClosure(const std::string& configuration, const std::str 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) { + const dyld3::MachOAnalyzer* mh = machOForUUID(uuid); + mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { if (isWeak) return; @@ -924,7 +922,7 @@ void Manifest::calculateClosure(const std::string& configuration, const std::str } if (badDependencies.size()) { - std::string reasonString = "Rejected from cached dylibs: " + std::string(parser.installName()) + " " + architecture + " (\""; + std::string reasonString = "Rejected from cached dylibs: " + std::string(mh->installName()) + " " + architecture + " (\""; for (auto i = badDependencies.begin(); i != badDependencies.end(); ++i) { reasonString += *i; if (i != --badDependencies.end()) { @@ -932,7 +930,7 @@ void Manifest::calculateClosure(const std::string& configuration, const std::str } } reasonString += "\")"; - archManifest.results.exclude(&parser, reasonString); + archManifest.results.exclude(mh, reasonString); } }); } @@ -946,8 +944,8 @@ void Manifest::calculateClosure(const std::string& configuration, const std::str __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) { + const dyld3::MachOAnalyzer* mh = machOForUUID(uuid); + mh->forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) { linkedDylibs.insert(loadPath); }); } @@ -1120,6 +1118,10 @@ void Manifest::write(const std::string& path) cacheDict[@"platform"] = @"macos"; break; case Platform::unknown: + case Platform::iOSMac: + case Platform::iOS_simulator: + case Platform::tvOS_simulator: + case Platform::watchOS_simulator: cacheDict[@"platform"] = @"unknown"; break; } @@ -1131,4 +1133,38 @@ void Manifest::write(const std::string& path) error:&error]; (void)[outData writeToFile:cppToObjStr(path) atomically:YES]; } + + +void Manifest::forEachMachO(std::string configuration, + std::function lambda) { + for (auto& uuidInfo : _uuidMap) { + auto i = _metabomTagMap.find(uuidInfo.second.runtimePath); + assert(i != _metabomTagMap.end()); + auto restrictions = _metabomRestrictedTagMap.find(configuration); + if (restrictions != _metabomRestrictedTagMap.end() && !is_disjoint(restrictions->second, i->second)) { + continue; + } + auto& configManifest = _configurations[configuration]; + auto exclusions = _metabomExcludeTagMap.find(configuration); + bool isExcluded = (exclusions != _metabomExcludeTagMap.end()) && !is_disjoint(exclusions->second, i->second); + bool isAnchor = !is_disjoint(i->second, configManifest.metabomTags); + bool shouldBeExcludedIfLeaf = isExcluded || !isAnchor; + lambda(uuidInfo.second.buildPath, uuidInfo.second.runtimePath, uuidInfo.second.arch, shouldBeExcludedIfLeaf); + } +} + + +void Manifest::forEachSymlink(std::string configuration, + std::function lambda) { + for (const auto& symlink : _symlinks) { + auto i = _metabomSymlinkTagMap.find(symlink.first); + assert(i != _metabomSymlinkTagMap.end()); + auto restrictions = _metabomRestrictedTagMap.find(configuration); + if (restrictions != _metabomRestrictedTagMap.end() && !is_disjoint(restrictions->second, i->second)) { + continue; + } + lambda(symlink.first, symlink.second); + } } + +} //namespace dyld3 diff --git a/dyld3/shared-cache/ObjC2Abstraction.hpp b/dyld3/shared-cache/ObjC2Abstraction.hpp index 59aa455..ec2965f 100644 --- a/dyld3/shared-cache/ObjC2Abstraction.hpp +++ b/dyld3/shared-cache/ObjC2Abstraction.hpp @@ -221,13 +221,13 @@ public: } } - static void addPointers(uint8_t* methodList, std::vector& pointersToAdd) { + static void addPointers(uint8_t* methodList, CacheBuilder::ASLR_Tracker& aslrTracker) { 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)); + aslrTracker.add(&(entry.name)); + aslrTracker.add(&(entry.types)); + aslrTracker.add(&(entry.imp)); } } @@ -380,12 +380,12 @@ public: } } - static void addPointers(uint8_t* propertyList, std::vector& pointersToAdd) { + static void addPointers(uint8_t* propertyList, CacheBuilder::ASLR_Tracker& aslrTracker) { 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)); + aslrTracker.add(&(entry.name)); + aslrTracker.add(&(entry.attributes)); } } @@ -474,18 +474,18 @@ public: P::setP(demangledName, cache->vmAddrForContent((void*)newName)); } - void addPointers(std::vector& pointersToAdd) + void addPointers(ContentAccessor* cache, CacheBuilder::ASLR_Tracker& aslrTracker) { - 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); + aslrTracker.add(&isa); + aslrTracker.add(&name); + if (protocols) aslrTracker.add(&protocols); + if (instanceMethods) aslrTracker.add(&instanceMethods); + if (classMethods) aslrTracker.add(&classMethods); + if (optionalInstanceMethods) aslrTracker.add(&optionalInstanceMethods); + if (optionalClassMethods) aslrTracker.add(&optionalClassMethods); + if (instanceProperties) aslrTracker.add(&instanceProperties); + if (extendedMethodTypes) aslrTracker.add(&extendedMethodTypes); + if (demangledName) aslrTracker.add(&demangledName); } }; @@ -531,10 +531,10 @@ public: } } - static void addPointers(uint8_t* protocolList, std::vector& pointersToAdd) { + static void addPointers(uint8_t* protocolList, CacheBuilder::ASLR_Tracker& aslrTracker) { objc_protocol_list_t

* plist = (objc_protocol_list_t

*)protocolList; for(int i=0 ; i < plist->count; ++i) { - pointersToAdd.push_back(&plist->list[i]); + aslrTracker.add(&plist->list[i]); } } @@ -606,16 +606,16 @@ public: P::setP(baseProperties, cache->vmAddrForContent(proplist)); } - void addMethodListPointer(std::vector& pointersToAdd) { - pointersToAdd.push_back(&this->baseMethods); + void addMethodListPointer(CacheBuilder::ASLR_Tracker& aslrTracker) { + aslrTracker.add(&this->baseMethods); } - void addPropertyListPointer(std::vector& pointersToAdd) { - pointersToAdd.push_back(&this->baseProperties); + void addPropertyListPointer(CacheBuilder::ASLR_Tracker& aslrTracker) { + aslrTracker.add(&this->baseProperties); } - void addProtocolListPointer(std::vector& pointersToAdd) { - pointersToAdd.push_back(&this->baseProtocols); + void addProtocolListPointer(CacheBuilder::ASLR_Tracker& aslrTracker) { + aslrTracker.add(&this->baseProtocols); } }; @@ -637,6 +637,8 @@ public: objc_class_t

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

*)cache->contentForVMAddr(P::getP(superclass)); } + const pint_t* getSuperClassAddress() const { return &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)); } @@ -665,16 +667,16 @@ public: getData(cache)->setPropertyList(cache, proplist); } - void addMethodListPointer(ContentAccessor* cache, std::vector& pointersToAdd) { - getData(cache)->addMethodListPointer(pointersToAdd); + void addMethodListPointer(ContentAccessor* cache, CacheBuilder::ASLR_Tracker& aslrTracker) { + getData(cache)->addMethodListPointer(aslrTracker); } - void addPropertyListPointer(ContentAccessor* cache, std::vector& pointersToAdd) { - getData(cache)->addPropertyListPointer(pointersToAdd); + void addPropertyListPointer(ContentAccessor* cache, CacheBuilder::ASLR_Tracker& aslrTracker) { + getData(cache)->addPropertyListPointer(aslrTracker); } - void addProtocolListPointer(ContentAccessor* cache, std::vector& pointersToAdd) { - getData(cache)->addProtocolListPointer(pointersToAdd); + void addProtocolListPointer(ContentAccessor* cache, CacheBuilder::ASLR_Tracker& aslrTracker) { + getData(cache)->addProtocolListPointer(aslrTracker); } }; @@ -943,6 +945,8 @@ class SelectorOptimizer { V& mVisitor; + std::set selectorRefVMAddrs; + friend class MethodListWalker >; void visitMethodList(objc_method_list_t

*mlist) { @@ -977,6 +981,7 @@ public: pint_t oldValue = selrefs.getVMAddress(i); pint_t newValue = mVisitor.visit(oldValue); selrefs.setVMAddress(i, newValue); + selectorRefVMAddrs.insert(selrefs.getSectionVMAddress() + (i * sizeof(pint_t))); } // message references @@ -989,6 +994,10 @@ public: msg.setName(newValue); } } + + bool isSelectorRefAddress(pint_t vmAddr) const { + return selectorRefVMAddrs.count(vmAddr); + } }; @@ -1083,7 +1092,8 @@ public: // Detect classes that have missing weak-import superclasses. template class WeakClassDetector { - bool noMissing; + bool noMissing; + const std::map* missingWeakImports = nullptr; friend class ClassWalker>; void visitClass(ContentAccessor* cache, const macho_header

*, @@ -1098,18 +1108,26 @@ class WeakClassDetector { } 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)); + // bad: cls's superclass is missing. + // See if we can find the name from the missing weak import map + auto it = missingWeakImports->find((void*)cls->getSuperClassAddress()); + const char* dylibName = "unknown dylib"; + if (it != missingWeakImports->end()) { + dylibName = it->second.c_str(); + } + cache->diagnostics().warning("Superclass of class '%s' is weak-import and missing. Expected in %s", + cls->getName(cache), dylibName); noMissing = false; } } public: - bool noMissingWeakSuperclasses(ContentAccessor* cache, + bool noMissingWeakSuperclasses(ContentAccessor* cache, + const std::map& missingWeakImportsMap, std::vector*> dylibs) { - noMissing = true; + noMissing = true; + missingWeakImports = &missingWeakImportsMap; ClassWalker> classes(*this); for (auto mh : dylibs) { classes.walk(cache, mh); @@ -1197,7 +1215,7 @@ public: return nullptr; } - void update(ContentAccessor* cache, const macho_header

* mh, std::vector& pointersInData) { + void update(ContentAccessor* cache, const macho_header

* mh, CacheBuilder::ASLR_Tracker& aslrTracker) { InfoT* hi = new(&_hInfos[_count++]) InfoT(cache, mh); (void)hi; } diff --git a/dyld3/shared-cache/OptimizerBranches.cpp b/dyld3/shared-cache/OptimizerBranches.cpp index 332e045..805f033 100644 --- a/dyld3/shared-cache/OptimizerBranches.cpp +++ b/dyld3/shared-cache/OptimizerBranches.cpp @@ -39,7 +39,7 @@ #include "StringUtils.h" #include "Trie.hpp" #include "MachOFileAbstraction.hpp" -#include "MachOParser.h" +#include "MachOAnalyzer.h" #include "Diagnostics.h" #include "DyldSharedCache.h" #include "CacheBuilder.h" @@ -330,7 +330,7 @@ template class BranchPoolDylib { public: BranchPoolDylib(DyldSharedCache* cache, uint64_t startAddr, - uint64_t textRegionStartAddr, uint64_t poolLinkEditStartAddr, uint64_t poolLinkEditStartOffset, Diagnostics& diags); + uint64_t textRegionStartAddr, uint64_t poolLinkEditStartAddr, uint64_t poolLinkEditFileOffset, Diagnostics& diags); uint64_t addr() { return _startAddr; } uint64_t getForwardBranch(uint64_t finalTargetAddr, const char* name, std::vector*>& branchIslandPools); @@ -366,15 +366,16 @@ private: template BranchPoolDylib

::BranchPoolDylib(DyldSharedCache* cache, uint64_t poolStartAddr, - uint64_t textRegionStartAddr, uint64_t poolLinkEditStartAddr, uint64_t poolLinkEditStartOffset, Diagnostics& diags) + uint64_t textRegionStartAddr, uint64_t poolLinkEditStartAddr, uint64_t poolLinkEditFileOffset, 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 int64_t cacheSlide = (long)cache - cache->unslidLoadAddress(); const uint64_t textSegSize = branchPoolTextSize(archName); const uint64_t linkEditSegSize = branchPoolLinkEditSize(archName); - const unsigned stubCount = (unsigned)((textSegSize - _firstStubOffset)/4); + const unsigned stubCount = (unsigned)((textSegSize - _firstStubOffset)/sizeof(uint32_t)); const uint32_t linkeditOffsetSymbolTable = 0; const uint32_t linkeditOffsetIndirectSymbolTable = stubCount*sizeof(macho_nlist

); const uint32_t linkeditOffsetSymbolPoolOffset = linkeditOffsetIndirectSymbolTable + stubCount*sizeof(uint32_t); @@ -383,8 +384,8 @@ BranchPoolDylib

::BranchPoolDylib(DyldSharedCache* cache, uint64_t poolStartAd // 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_cputype(dyld3::MachOFile::cpuTypeFromArchName(archName.c_str())); + mh->set_cpusubtype(dyld3::MachOFile::cpuSubtypeFromArchName(archName.c_str())); mh->set_filetype(MH_DYLIB); mh->set_ncmds(6); mh->set_sizeofcmds(is64 ? 0x210 : 100); // FIXME: 32-bit size @@ -423,7 +424,7 @@ BranchPoolDylib

::BranchPoolDylib(DyldSharedCache* cache, uint64_t poolStartAd linkEditSegCmd->set_segname("__LINKEDIT"); linkEditSegCmd->set_vmaddr(poolLinkEditStartAddr); linkEditSegCmd->set_vmsize(linkEditSegSize); - linkEditSegCmd->set_fileoff(poolLinkEditStartOffset); + linkEditSegCmd->set_fileoff(poolLinkEditFileOffset); linkEditSegCmd->set_filesize(linkEditSegSize); linkEditSegCmd->set_maxprot(PROT_READ); linkEditSegCmd->set_initprot(PROT_READ); @@ -445,8 +446,8 @@ BranchPoolDylib

::BranchPoolDylib(DyldSharedCache* cache, uint64_t poolStartAd _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_symoff((uint32_t)(poolLinkEditFileOffset + linkeditOffsetSymbolTable)); + _symbolTableCmd->set_stroff((uint32_t)(poolLinkEditFileOffset + linkeditOffsetSymbolPoolOffset)); _symbolTableCmd->set_strsize((uint32_t)(linkEditSegSize - linkeditOffsetSymbolPoolOffset)); // LC_DYSYMTAB cmd = (macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); @@ -465,7 +466,7 @@ BranchPoolDylib

::BranchPoolDylib(DyldSharedCache* cache, uint64_t poolStartAd _dynamicSymbolTableCmd->set_nmodtab(0); _dynamicSymbolTableCmd->set_extrefsymoff(0); _dynamicSymbolTableCmd->set_nextrefsyms(0); - _dynamicSymbolTableCmd->set_indirectsymoff((uint32_t)(poolLinkEditStartOffset + linkeditOffsetIndirectSymbolTable)); + _dynamicSymbolTableCmd->set_indirectsymoff((uint32_t)(poolLinkEditFileOffset + linkeditOffsetIndirectSymbolTable)); _dynamicSymbolTableCmd->set_nindirectsyms(stubCount); _dynamicSymbolTableCmd->set_extreloff(0); _dynamicSymbolTableCmd->set_nextrel(0); @@ -480,15 +481,15 @@ BranchPoolDylib

::BranchPoolDylib(DyldSharedCache* cache, uint64_t poolStartAd // write stubs section content _stubInstructions = (uint32_t*)((uint8_t*)mh + _firstStubOffset); - for (int i=0; i < stubCount; ++i) { + for (unsigned i=0; i < stubCount; ++i) { E::set32(_stubInstructions[i], 0xD4200000); } // write linkedit content - uint8_t* linkeditBufferStart = (uint8_t*)cache + poolLinkEditStartOffset; + uint8_t* linkeditBufferStart = (uint8_t*)poolLinkEditStartAddr + cacheSlide; // write symbol table _symbolTable = (macho_nlist

*)(linkeditBufferStart); - for (int i=0; i < stubCount; ++i) { + for (unsigned 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); @@ -497,7 +498,7 @@ BranchPoolDylib

::BranchPoolDylib(DyldSharedCache* cache, uint64_t poolStartAd } // write indirect symbol table uint32_t* indirectSymboTable = (uint32_t*)(linkeditBufferStart + linkeditOffsetIndirectSymbolTable); - for (int i=0; i < stubCount; ++i) { + for (unsigned i=0; i < stubCount; ++i) { P::E::set32(indirectSymboTable[i], i); } // write string pool @@ -517,7 +518,7 @@ void BranchPoolDylib

::finalizeLoadCommands() _dynamicSymbolTableCmd->set_nundefsym(_nextIndex); uint8_t digest[CC_MD5_DIGEST_LENGTH]; - CC_MD5(_stubInstructions, _maxStubs*sizeof(uint64_t), digest); + CC_MD5(_stubInstructions, _maxStubs*sizeof(uint32_t), digest); _uuidCmd->set_uuid(digest); if ( verbose ) { @@ -647,13 +648,12 @@ void BranchPoolDylib

::printStats() template class StubOptimizer { public: - StubOptimizer(void* cacheBuffer, macho_header

* mh, Diagnostics& diags); + StubOptimizer(const DyldSharedCache* cache, 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(); } + const uint8_t* exportsTrie() { return &_linkeditBias[_dyldInfo->export_off()]; } uint32_t exportsTrieSize() { return _dyldInfo->export_size(); } uint32_t _stubCount = 0; @@ -674,6 +674,9 @@ private: void optimizeArmCallSites(); void optimizeArmStubs(); uint64_t lazyPointerAddrFromArm64Stub(const uint8_t* stubInstructions, uint64_t stubVMAddr); +#if SUPPORT_ARCH_arm64e + uint64_t lazyPointerAddrFromArm64eStub(const uint8_t* stubInstructions, uint64_t stubVMAddr); +#endif 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, @@ -688,9 +691,10 @@ private: macho_header

* _mh; - void* _cacheBuffer; + int64_t _cacheSlide = 0; + uint64_t _cacheUnslideAddr = 0; + bool _chainedFixups = false; uint32_t _linkeditSize = 0; - uint32_t _linkeditCacheOffset = 0; uint64_t _linkeditAddr = 0; const uint8_t* _linkeditBias = nullptr; const char* _installName = nullptr; @@ -703,18 +707,23 @@ private: 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; + std::unordered_map _targetAddrToName; }; template -StubOptimizer

::StubOptimizer(void* cacheBuffer, macho_header

* mh, Diagnostics& diags) -: _mh(mh), _cacheBuffer(cacheBuffer), _diagnostics(diags) +StubOptimizer

::StubOptimizer(const DyldSharedCache* cache, macho_header

* mh, Diagnostics& diags) +: _mh(mh), _diagnostics(diags) { - _linkeditBias = (uint8_t*)cacheBuffer; + _cacheSlide = (long)cache - cache->unslidLoadAddress(); + _cacheUnslideAddr = cache->unslidLoadAddress(); +#if SUPPORT_ARCH_arm64e + _chainedFixups = (strcmp(cache->archName(), "arm64e") == 0); +#else + _chainedFixups = false; +#endif 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; @@ -742,13 +751,12 @@ StubOptimizer

::StubOptimizer(void* cacheBuffer, macho_header

* mh, Diagnost segCmd =( macho_segment_command

*)cmd; _segCmds.push_back(segCmd); if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { + _linkeditBias = (uint8_t*)(segCmd->vmaddr() + _cacheSlide - segCmd->fileoff()); _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) { @@ -819,15 +827,50 @@ uint64_t StubOptimizer

::lazyPointerAddrFromArm64Stub(const uint8_t* stubInstr return (stubVMAddr & (-4096)) + adrpValue*4096 + ldrValue*8; } +#if SUPPORT_ARCH_arm64e +template +uint64_t StubOptimizer

::lazyPointerAddrFromArm64eStub(const uint8_t* stubInstructions, uint64_t stubVMAddr) +{ + uint32_t stubInstr1 = E::get32(*(uint32_t*)stubInstructions); + // ADRP X17, dyld_mageLoaderCache@page + if ( (stubInstr1 & 0x9F00001F) != 0x90000011 ) { + _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; + + // ADD X17, X17, dyld_mageLoaderCache@pageoff + uint32_t stubInstr2 = E::get32(*(uint32_t*)(stubInstructions + 4)); + if ( (stubInstr2 & 0xFFC003FF) != 0x91000231 ) { + _diagnostics.warning("second instruction of stub (0x%08X) is not ADD for stub at addr 0x%0llX in %s", + stubInstr2, (uint64_t)stubVMAddr, _installName); + return 0; + } + uint32_t addValue = ((stubInstr2 & 0x003FFC00) >> 10); + + // LDR X16, [X17] + uint32_t stubInstr3 = E::get32(*(uint32_t*)(stubInstructions + 8)); + if ( stubInstr3 != 0xF9400230 ) { + _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; + } + return (stubVMAddr & (-4096)) + adrpValue*4096 + addValue; +} +#endif + 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_nlist

* symbolTable = (const macho_nlist

*)(&_linkeditBias[_symTabCmd->symoff()]); + const char* symbolStrings = (char*)(&_linkeditBias[_symTabCmd->stroff()]); + const uint32_t* const indirectTable = (uint32_t*)(&_linkeditBias[_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; @@ -850,6 +893,7 @@ void StubOptimizer

::buildStubMap(const std::unordered_set& never switch ( symbolIndex ) { case INDIRECT_SYMBOL_ABS: case INDIRECT_SYMBOL_LOCAL: + case INDIRECT_SYMBOL_ABS | INDIRECT_SYMBOL_LOCAL: break; default: if ( symbolIndex >= _symTabCmd->nsyms() ) { @@ -866,14 +910,20 @@ void StubOptimizer

::buildStubMap(const std::unordered_set& never } const char* symName = &symbolStrings[stringOffset]; if ( neverStubEliminate.count(symName) ) { - //verboseLog("not bypassing stub to %s in %s because target is interposable\n", symName, _installName); + //fprintf(stderr, "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(); + const uint8_t* stubInstrs = (uint8_t*)(long)stubVMAddr + _cacheSlide; pint_t targetLPAddr = 0; switch ( _mh->cputype() ) { case CPU_TYPE_ARM64: - targetLPAddr = (pint_t)lazyPointerAddrFromArm64Stub(stubInstrs, stubVMAddr); + case CPU_TYPE_ARM64_32: +#if SUPPORT_ARCH_arm64e + if (_mh->cpusubtype() == CPU_SUBTYPE_ARM64_E) + targetLPAddr = (pint_t)lazyPointerAddrFromArm64eStub(stubInstrs, stubVMAddr); + else +#endif + targetLPAddr = (pint_t)lazyPointerAddrFromArm64Stub(stubInstrs, stubVMAddr); break; case CPU_TYPE_ARM: targetLPAddr = (pint_t)lazyPointerAddrFromArmStub(stubInstrs, (uint32_t)stubVMAddr); @@ -885,9 +935,9 @@ void StubOptimizer

::buildStubMap(const std::unordered_set& never } } } - else if ( sectionType == S_LAZY_SYMBOL_POINTERS ) { + else if ( (sectionType == S_LAZY_SYMBOL_POINTERS) || (sectionType == S_NON_LAZY_SYMBOL_POINTERS) ) { pint_t lpVMAddr; - pint_t* lpContent = (pint_t*)(((uint8_t*)_cacheBuffer) + sect->offset()); + pint_t* lpContent = (pint_t*)(sect->addr() + _cacheSlide); 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(); @@ -897,9 +947,24 @@ void StubOptimizer

::buildStubMap(const std::unordered_set& never switch ( symbolIndex ) { case INDIRECT_SYMBOL_ABS: case INDIRECT_SYMBOL_LOCAL: + case INDIRECT_SYMBOL_LOCAL|INDIRECT_SYMBOL_ABS: break; default: lpValue = (pint_t)P::getP(lpContent[j]); + + // Fixup threaded rebase/bind + if ( _chainedFixups ) { + dyld3::MachOLoaded::ChainedFixupPointerOnDisk ptr; + ptr.raw = lpValue; + assert(ptr.authRebase.bind == 0); + if ( ptr.authRebase.auth ) { + lpValue = (pint_t)(_cacheUnslideAddr + ptr.authRebase.target); + } + else { + lpValue = (pint_t)ptr.plainRebase.signExtendedTarget(); + } + } + 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", @@ -915,7 +980,7 @@ void StubOptimizer

::buildStubMap(const std::unordered_set& never } 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); + //fprintf(stderr, "skipping lazy pointer at 0x%0lX to %s in %s because target is within dylib\n", (long)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", @@ -948,7 +1013,7 @@ void StubOptimizer

::forEachCallSiteToAStub(CallSiteHandler handler) return; } - uint8_t* textSectionContent = (uint8_t*)_cacheBuffer + _textSegCacheOffset + _textSection->addr() -_textSegStartAddr; + uint8_t* textSectionContent = (uint8_t*)(_textSection->addr() + _cacheSlide); // Whole :== FromToSection+ // FromToSection :== ToOffset+ @@ -967,8 +1032,8 @@ void StubOptimizer

::forEachCallSiteToAStub(CallSiteHandler handler) 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); + if ( kind > 13 ) { + _diagnostics.error("bad kind (%llu) value in %s\n", kind, _installName); } uint64_t fromSectDeltaCount = read_uleb128(p, infoEnd); uint64_t fromSectionOffset = 0; @@ -1127,11 +1192,12 @@ void StubOptimizer

::optimizeArmStubs() 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 + uint32_t* stubInstructions = (uint32_t*)((uint8_t*)(long)stubVMAddr + _cacheSlide); + assert(stubInstructions[0] == 0xe59fc004); + stubInstructions[0] = 0xe59fc000; // ldr ip, L0 + stubInstructions[1] = 0xe08ff00c; // add pc, pc, ip + stubInstructions[2] = delta; // L0: .long xxxx + stubInstructions[3] = 0xe7ffdefe; // trap _stubOptimizedCount++; } } @@ -1172,7 +1238,7 @@ void StubOptimizer

::optimizeArm64CallSites(std::vector*>& if ( (deltaToFinalTarget > -b128MegLimit) && (deltaToFinalTarget < b128MegLimit) ) { instruction= (instruction & 0xFC000000) | ((deltaToFinalTarget >> 2) & 0x03FFFFFF); _branchesDirectCount++; - return true; + return true; } // find closest branch island pool between instruction and target and get island const auto& pos3 = _targetAddrToName.find((pint_t)finalTargetAddr); @@ -1232,6 +1298,7 @@ void StubOptimizer

::optimizeCallSites(std::vector*>& branc switch ( _mh->cputype() ) { case CPU_TYPE_ARM64: + case CPU_TYPE_ARM64_32: optimizeArm64CallSites(branchIslandPools); if ( verbose ) { _diagnostics.verbose("%5u branches in __text, %5u changed to direct branches, %5u changed to use islands for %s\n", @@ -1251,14 +1318,15 @@ void StubOptimizer

::optimizeCallSites(std::vector*>& branc template void bypassStubs(DyldSharedCache* cache, const std::string& archName, const std::vector& branchPoolStartAddrs, - const char* const neverStubEliminateDylibs[], Diagnostics& diags) + uint64_t branchPoolsLinkEditStartAddr, uint64_t branchPoolsLinkEditStartFileOffset, + 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)); + optimizers.push_back(new StubOptimizer

(cache, (macho_header

*)mh, diags)); }); // construct a BranchPoolDylib for each pool @@ -1282,25 +1350,22 @@ void bypassStubs(DyldSharedCache* cache, const std::string& archName, const std: }); __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; + ((dyld3::MachOFile*)mh)->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool &stop) { + if ( strcmp(info.segName, "__LINKEDIT") == 0 ) { + if ( info.fileOffset >= lastLinkEditRegionUsedOffset ) + lastLinkEditRegionUsedOffset = info.fileOffset + info.vmSize; } }); }); - uint64_t allPoolsLinkEditStartOffset = lastLinkEditRegionUsedOffset; - uint64_t allPoolsLinkEditStartAddr = linkEditRegionStartAddr + allPoolsLinkEditStartOffset - linkEditRegionStartCacheOffset; - uint64_t allPoolsLinkEditSize = linkEditRegionEndAddr - allPoolsLinkEditStartAddr; + uint64_t allPoolsLinkEditStartAddr = branchPoolsLinkEditStartAddr; if ( !branchPoolStartAddrs.empty() ) { - uint64_t poolLinkEditStartAddr = allPoolsLinkEditStartAddr; - uint64_t poolLinkEditStartOffset = allPoolsLinkEditStartOffset; - const uint64_t poolSize = (allPoolsLinkEditSize/branchPoolStartAddrs.size()) & (-4096); + uint64_t poolLinkEditStartAddr = allPoolsLinkEditStartAddr; + uint64_t poolLinkEditFileOffset = branchPoolsLinkEditStartFileOffset; + const uint64_t poolSize = branchPoolLinkEditSize("arm64"); for (uint64_t poolAddr : branchPoolStartAddrs) { - pools.push_back(new BranchPoolDylib

(cache, poolAddr, textRegionStartAddr, poolLinkEditStartAddr, poolLinkEditStartOffset, diags)); - poolLinkEditStartAddr += poolSize; - poolLinkEditStartOffset += poolSize; + pools.push_back(new BranchPoolDylib

(cache, poolAddr, textRegionStartAddr, poolLinkEditStartAddr, poolLinkEditFileOffset, diags)); + poolLinkEditStartAddr += poolSize; + poolLinkEditFileOffset += poolSize; } } } @@ -1361,13 +1426,20 @@ void bypassStubs(DyldSharedCache* cache, const std::string& archName, const std: } -void bypassStubs(DyldSharedCache* cache, const std::vector& branchPoolStartAddrs, const char* const neverStubEliminateDylibs[], Diagnostics& diags) +void CacheBuilder::optimizeAwayStubs(const std::vector& branchPoolStartAddrs, uint64_t branchPoolsLinkEditStartAddr) { - std::string archName = cache->archName(); + DyldSharedCache* dyldCache = (DyldSharedCache*)_readExecuteRegion.buffer; + uint64_t branchPoolsLinkEditStartFileOffset = _readOnlyRegion.cacheFileOffset + branchPoolsLinkEditStartAddr - _readOnlyRegion.unslidLoadAddress; + std::string archName = dyldCache->archName(); +#if SUPPORT_ARCH_arm64_32 + if ( startsWith(archName, "arm64_32") ) + bypassStubs >(dyldCache, archName, branchPoolStartAddrs, branchPoolsLinkEditStartAddr, branchPoolsLinkEditStartFileOffset, _s_neverStubEliminate, _diagnostics); + else +#endif if ( startsWith(archName, "arm64") ) - bypassStubs>(cache, archName, branchPoolStartAddrs, neverStubEliminateDylibs, diags); + bypassStubs >(dyldCache, archName, branchPoolStartAddrs, branchPoolsLinkEditStartAddr, branchPoolsLinkEditStartFileOffset, _s_neverStubEliminate, _diagnostics); else if ( archName == "armv7k" ) - bypassStubs>(cache, archName, branchPoolStartAddrs, neverStubEliminateDylibs, diags); + bypassStubs>(dyldCache, archName, branchPoolStartAddrs, branchPoolsLinkEditStartAddr, branchPoolsLinkEditStartFileOffset, _s_neverStubEliminate, _diagnostics); // no stub optimization done for other arches } diff --git a/dyld3/shared-cache/OptimizerLinkedit.cpp b/dyld3/shared-cache/OptimizerLinkedit.cpp index b8ac19f..ef5db27 100644 --- a/dyld3/shared-cache/OptimizerLinkedit.cpp +++ b/dyld3/shared-cache/OptimizerLinkedit.cpp @@ -40,7 +40,7 @@ #include "Trie.hpp" #include "DyldSharedCache.h" #include "CacheBuilder.h" - +#include "MachOLoaded.h" #define ALIGN_AS_TYPE(value, type) \ ((value + alignof(type) - 1) & (-alignof(type))) @@ -58,21 +58,15 @@ public: // 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) { + for (auto& entry : _map) { + const std::string& symName = entry.first; // 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]) { + for (uint32_t symbolIndex : entry.second) { symbolTable[symbolIndex].set_n_strx(poolOffset); } poolOffset += symName.size() + 1; @@ -91,10 +85,11 @@ public: private: - std::unordered_map> _map; + std::map> _map; }; +} // anonymous namespace struct LocalSymbolInfo @@ -111,7 +106,6 @@ 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); @@ -144,6 +138,9 @@ public: const std::vector*>& segCmds() { return _segCmds; } + static void optimizeLinkedit(CacheBuilder& builder); + static void mergeLinkedits(CacheBuilder& builder, std::vector*>& optimizers); + private: typedef typename P::uint_t pint_t; @@ -153,7 +150,6 @@ private: 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; @@ -421,6 +417,7 @@ AcceleratorTables

::AcceleratorTables(DyldSharedCache* cache, uint64_t linkedi } } + // register exports trie and weak binding info in each dylib with image extra info for (macho_header

* mh : sortedMachHeaders) { LinkeditOptimizer

* op = _machHeaderToOptimizer[mh]; @@ -527,10 +524,11 @@ 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; + uint64_t textSegAddr = 0; + int64_t slide = 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; @@ -579,10 +577,14 @@ LinkeditOptimizer

::LinkeditOptimizer(void* cacheBuffer, macho_header

* mh, 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(); + if ( strcmp(segCmd->segname(), "__TEXT") == 0 ) { + textSegAddr = segCmd->vmaddr(); + slide = (uint64_t)mh - textSegAddr; + } + else if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { _linkeditAddr = segCmd->vmaddr(); + _linkeditBias = (uint8_t*)mh + (_linkeditAddr - textSegAddr) - segCmd->fileoff(); + _linkeditSize = (uint32_t)segCmd->vmsize(); } else if ( segCmd->nsects() > 0 ) { macho_section

* const sectionsStart = (macho_section

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

)); @@ -590,7 +592,7 @@ LinkeditOptimizer

::LinkeditOptimizer(void* cacheBuffer, macho_header

* mh, 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 pint_t* inits = (pint_t*)(sect->addr()+slide); const size_t count = sect->size() / sizeof(pint_t); for (size_t j=0; j < count; ++j) { uint64_t func = P::getP(inits[j]); @@ -947,7 +949,7 @@ void LinkeditOptimizer

::copyIndirectSymbolTable(uint8_t* newLinkEditContent, _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) { + for (uint32_t 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); @@ -958,63 +960,59 @@ void LinkeditOptimizer

::copyIndirectSymbolTable(uint8_t* newLinkEditContent, } template -uint64_t mergeLinkedits(DyldSharedCache* cache, bool dontMapLocalSymbols, bool addAcceleratorTables, std::vector*>& optimizers, Diagnostics& diagnostics, dyld_cache_local_symbols_info** localsInfo) +void LinkeditOptimizer

::mergeLinkedits(CacheBuilder& builder, 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; + uint64_t totalUnoptLinkeditsSize = builder._readOnlyRegion.sizeInUse - builder._nonLinkEditReadOnlySize; uint8_t* newLinkEdit = (uint8_t*)calloc(totalUnoptLinkeditsSize, 1); SortedStringPool

stringPool; uint32_t offset = 0; - diagnostics.verbose("Merged LINKEDIT:\n"); + builder._diagnostics.verbose("Merged LINKEDIT:\n"); // copy weak binding info uint32_t startWeakBindInfosOffset = offset; for (LinkeditOptimizer

* op : optimizers) { - op->copyWeakBindingInfo(newLinkEdit, offset); + // Skip chained fixups as the in-place linked list isn't valid any more + const dyld3::MachOFile* mf = (dyld3::MachOFile*)op->machHeader(); + if (!mf->hasChainedFixups()) + op->copyWeakBindingInfo(newLinkEdit, offset); } - diagnostics.verbose(" weak bindings size: %5uKB\n", (uint32_t)(offset-startWeakBindInfosOffset)/1024); + builder._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); + builder._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); + // Skip chained fixups as the in-place linked list isn't valid any more + const dyld3::MachOFile* mf = (dyld3::MachOFile*)op->machHeader(); + if (!mf->hasChainedFixups()) + op->copyBindingInfo(newLinkEdit, offset); } - diagnostics.verbose(" bindings size: %5uKB\n", (uint32_t)(offset-startBindingsInfosOffset)/1024); + builder._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); + // Skip chained fixups as the in-place linked list isn't valid any more + const dyld3::MachOFile* mf = (dyld3::MachOFile*)op->machHeader(); + if (!mf->hasChainedFixups()) + op->copyLazyBindingInfo(newLinkEdit, offset); } - diagnostics.verbose(" lazy bindings size: %5uKB\n", (offset-startLazyBindingsInfosOffset)/1024); + builder._diagnostics.verbose(" lazy bindings size: %5uKB\n", (offset-startLazyBindingsInfosOffset)/1024); } // copy symbol table entries std::vector> unmappedLocalSymbols; - if ( dontMapLocalSymbols ) + if ( builder._options.excludeLocalSymbols ) unmappedLocalSymbols.reserve(0x01000000); std::vector localSymbolInfos; localSymbolInfos.reserve(optimizers.size()); @@ -1024,7 +1022,7 @@ uint64_t mergeLinkedits(DyldSharedCache* cache, bool dontMapLocalSymbols, bool a uint32_t sharedSymbolTableExportsCount = 0; uint32_t sharedSymbolTableImportsCount = 0; for (LinkeditOptimizer

* op : optimizers) { - op->copyLocalSymbols(newLinkEdit, stringPool, offset, symbolIndex, dontMapLocalSymbols, + op->copyLocalSymbols(newLinkEdit, stringPool, offset, symbolIndex, builder._options.excludeLocalSymbols, localSymbolInfos, unmappedLocalSymbols, localSymbolsStringPool); uint32_t x = symbolIndex; op->copyExportedSymbols(newLinkEdit, stringPool, offset, symbolIndex); @@ -1041,14 +1039,14 @@ uint64_t mergeLinkedits(DyldSharedCache* cache, bool dontMapLocalSymbols, bool a for (LinkeditOptimizer

* op : optimizers) { op->copyFunctionStarts(newLinkEdit, offset); } - diagnostics.verbose(" function starts size: %5uKB\n", (offset-startFunctionStartsOffset)/1024); + builder._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); + builder._diagnostics.verbose(" data in code size: %5uKB\n", (offset-startDataInCodeOffset)/1024); // copy indirect symbol tables for (LinkeditOptimizer

* op : optimizers) { @@ -1063,41 +1061,38 @@ uint64_t mergeLinkedits(DyldSharedCache* cache, bool dontMapLocalSymbols, bool a 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); + uint64_t newLinkeditAlignedSize = align(offset, 14); + builder._diagnostics.verbose(" symbol table size: %5uKB (%d exports, %d imports)\n", (sharedSymbolTableEndOffset-sharedSymbolTableStartOffset)/1024, sharedSymbolTableExportsCount, sharedSymbolTableImportsCount); + builder._diagnostics.verbose(" symbol string pool size: %5uKB\n", sharedSymbolStringsSize/1024); + builder._sharedStringsPoolVmOffset = (uint32_t)((builder._readOnlyRegion.unslidLoadAddress - builder._readExecuteRegion.unslidLoadAddress) + builder._nonLinkEditReadOnlySize + sharedSymbolStringsOffset); // 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); + builder._diagnostics.verbose("LINKEDITS optimized from %uMB to %uMB\n", (uint32_t)totalUnoptLinkeditsSize/(1024*1024), (uint32_t)newLinkeditUnalignedSize/(1024*1024)); + ::memcpy(builder._readOnlyRegion.buffer+builder._nonLinkEditReadOnlySize, newLinkEdit, newLinkeditAlignedSize); ::free(newLinkEdit); + builder._readOnlyRegion.sizeInUse = builder._nonLinkEditReadOnlySize + newLinkeditAlignedSize; // If making cache for customers, add extra accelerator tables for dyld - if ( addAcceleratorTables ) { - AcceleratorTables

tables(cache, linkeditStartAddr, diagnostics, optimizers); + DyldSharedCache* cacheHeader = (DyldSharedCache*)builder._readExecuteRegion.buffer; + if ( builder._options.optimizeStubs ) { + uint64_t addrWhereAccTablesWillBe = builder._readOnlyRegion.unslidLoadAddress+builder._readOnlyRegion.sizeInUse; + uint64_t addrWhereMergedLinkWillStart = builder._readOnlyRegion.unslidLoadAddress+builder._nonLinkEditReadOnlySize; + AcceleratorTables

tables(cacheHeader, addrWhereMergedLinkWillStart, builder._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)); + if ( tablesSize < (builder._readOnlyRegion.bufferSize - builder._readOnlyRegion.sizeInUse) ) { + tables.copyTo(builder._readOnlyRegion.buffer+builder._readOnlyRegion.sizeInUse); + cacheHeader->header.accelerateInfoAddr = addrWhereAccTablesWillBe; + cacheHeader->header.accelerateInfoSize = tablesSize; + builder._readOnlyRegion.sizeInUse += align(tablesSize, 14); + builder._diagnostics.verbose("Accelerator tables %uMB\n", (uint32_t)tablesSize/(1024*1024)); } else { - diagnostics.warning("not enough room to add dyld accelerator tables"); + builder._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; + if ( builder._options.excludeLocalSymbols ) { 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 @@ -1106,49 +1101,59 @@ uint64_t mergeLinkedits(DyldSharedCache* cache, bool dontMapLocalSymbols, bool a 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; + vm_address_t localsBuffer; + if ( ::vm_allocate(mach_task_self(), &localsBuffer, localsBufferSize, VM_FLAGS_ANYWHERE) == 0 ) { + dyld_cache_local_symbols_info* infoHeader = (dyld_cache_local_symbols_info*)localsBuffer; + // 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 (uint32_t 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

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

)); + // copy string pool + localSymbolsStringPool.copyPoolAndUpdateOffsets(((char*)infoHeader)+stringsOffset, newLocalsSymbolTable); + // update cache header + cacheHeader->header.localSymbolsSize = localsBufferSize; + // return buffer of local symbols, caller to free() it + builder._localSymbolsRegion.buffer = (uint8_t*)localsBuffer; + builder._localSymbolsRegion.bufferSize = localsBufferSize; + builder._localSymbolsRegion.sizeInUse = localsBufferSize; + } + else { + builder._diagnostics.warning("could not allocate local symbols"); } - // 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 + uint64_t linkeditsUnslidStartAddr = builder._readOnlyRegion.unslidLoadAddress + builder._nonLinkEditReadOnlySize; + uint32_t linkeditsCacheFileOffset = (uint32_t)(builder._readOnlyRegion.cacheFileOffset + builder._nonLinkEditReadOnlySize); for (LinkeditOptimizer

* op : optimizers) { - op->updateLoadCommands(linkeditStartOffset, linkeditStartAddr, newLinkeditEnd-linkeditStartOffset, + op->updateLoadCommands(linkeditsCacheFileOffset, linkeditsUnslidStartAddr, newLinkeditUnalignedSize, 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) +void LinkeditOptimizer

::optimizeLinkedit(CacheBuilder& builder) { + DyldSharedCache* cache = (DyldSharedCache*)builder._readExecuteRegion.buffer; // 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)); + optimizers.push_back(new LinkeditOptimizer

(cache, (macho_header

*)mh, builder._diagnostics)); }); #if 0 // add optimizer for each branch pool @@ -1158,22 +1163,20 @@ uint64_t optimizeLinkedit(DyldSharedCache* cache, bool dontMapLocalSymbols, bool } #endif // merge linkedit info - uint64_t newFileSize = mergeLinkedits(cache, dontMapLocalSymbols, addAcceleratorTables, optimizers, diag, localsInfo); + mergeLinkedits(builder, optimizers); // 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) +void CacheBuilder::optimizeLinkedit(const std::vector& branchPoolOffsets) { - if ( is64) { - return optimizeLinkedit>(cache, dontMapLocalSymbols, addAcceleratorTables, branchPoolOffsets, diag, localsInfo); + if ( _archLayout->is64 ) { + return LinkeditOptimizer>::optimizeLinkedit(*this); } else { - return optimizeLinkedit>(cache, dontMapLocalSymbols, addAcceleratorTables, branchPoolOffsets, diag, localsInfo); + return LinkeditOptimizer>::optimizeLinkedit(*this); } } diff --git a/dyld3/shared-cache/OptimizerObjC.cpp b/dyld3/shared-cache/OptimizerObjC.cpp index 71fe9dc..3ea4ad2 100644 --- a/dyld3/shared-cache/OptimizerObjC.cpp +++ b/dyld3/shared-cache/OptimizerObjC.cpp @@ -35,7 +35,7 @@ #include "CacheBuilder.h" #include "FileAbstraction.hpp" #include "MachOFileAbstraction.hpp" - +#include "MachOLoaded.h" // Scan a C++ or Swift length-mangled field. static bool scanMangledField(const char *&string, const char *end, @@ -109,37 +109,57 @@ 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 }; - }); + _cacheStart = (uint8_t*)cache; + _cacheUnslideAddr = cache->unslidLoadAddress(); + _slide = (uint64_t)cache - _cacheUnslideAddr; +#if SUPPORT_ARCH_arm64e + _chainedFixups = (strcmp(cache->archName(), "arm64e") == 0); +#else + _chainedFixups = false; +#endif + } + + // Converts from an on disk vmAddr to the real vmAddr + // That is, for a chained fixup, decodes the chain, for a non-chained fixup, does nothing. + uint64_t vmAddrForOnDiskVMAddr(uint64_t vmaddr) { + if ( _chainedFixups ) { + dyld3::MachOLoaded::ChainedFixupPointerOnDisk ptr; + ptr.raw = vmaddr; + assert(ptr.authRebase.bind == 0); + if ( ptr.authRebase.auth ) { + vmaddr = _cacheUnslideAddr + ptr.authRebase.target; + } + else { + vmaddr = ptr.plainRebase.signExtendedTarget(); + } + } + return vmaddr; } 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; + vmaddr = vmAddrForOnDiskVMAddr(vmaddr); + if ( vmaddr != 0 ) { + uint64_t offset = vmaddr - _cacheUnslideAddr; + return _cacheStart + offset; + } else + 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; + if ( content != nullptr ) + return _cacheUnslideAddr + ((uint8_t*)content - _cacheStart); + else + 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]; + Diagnostics& _diagnostics; + uint64_t _slide; + uint64_t _cacheUnslideAddr; + uint8_t* _cacheStart; + bool _chainedFixups; }; @@ -167,6 +187,10 @@ public: return (pint_t)P::getP(_base[index]); } + pint_t getSectionVMAddress() const { + return (pint_t)_section->addr(); + } + T get(pint_t index) const { return (T)_cache->contentForVMAddr(getVMAddress(index)); } @@ -253,6 +277,7 @@ public: { _count++; const char *s = (const char *)_cache->contentForVMAddr(oldValue); + oldValue = (pint_t)_cache->vmAddrForOnDiskVMAddr(oldValue); objc_opt::string_map::iterator element = _selectorStrings.insert(objc_opt::string_map::value_type(s, oldValue)).first; return (pint_t)element->second; @@ -366,7 +391,7 @@ public: const char *writeProtocols(ContentAccessor* cache, uint8_t *& rwdest, size_t& rwremaining, uint8_t *& rodest, size_t& roremaining, - std::vector& pointersInData, + CacheBuilder::ASLR_Tracker& aslrTracker, pint_t protocolClassVMAddr) { if (_protocolCount == 0) return NULL; @@ -430,7 +455,7 @@ public: iter->second = cache->vmAddrForContent(proto); // Add new rebase entries. - proto->addPointers(pointersInData); + proto->addPointers(cache, aslrTracker); } return NULL; @@ -464,7 +489,9 @@ static int percent(size_t num, size_t denom) { template -void optimizeObjC(DyldSharedCache* cache, bool forProduction, std::vector& pointersForASLR, Diagnostics& diag) +void doOptimizeObjC(DyldSharedCache* cache, bool forProduction, CacheBuilder::ASLR_Tracker& aslrTracker, + CacheBuilder::LOH_Tracker& lohTracker, + const std::map& missingWeakImports, Diagnostics& diag) { typedef typename P::E E; typedef typename P::uint_t pint_t; @@ -563,7 +590,7 @@ void optimizeObjC(DyldSharedCache* cache, bool forProduction, std::vector } else { for (const macho_header

* mh : addressSortedDylibs) { - hinfoROOptimizer.update(&cacheAccessor, mh, pointersForASLR); + hinfoROOptimizer.update(&cacheAccessor, mh, aslrTracker); } } @@ -578,7 +605,7 @@ void optimizeObjC(DyldSharedCache* cache, bool forProduction, std::vector } else { for (const macho_header

* mh : addressSortedDylibs) { - hinfoRWOptimizer.update(&cacheAccessor, mh, pointersForASLR); + hinfoRWOptimizer.update(&cacheAccessor, mh, aslrTracker); } } @@ -640,7 +667,7 @@ void optimizeObjC(DyldSharedCache* cache, bool forProduction, std::vector if (forProduction) { WeakClassDetector

weakopt; noMissingWeakSuperclasses = - weakopt.noMissingWeakSuperclasses(&cacheAccessor, sizeSortedDylibs); + weakopt.noMissingWeakSuperclasses(&cacheAccessor, missingWeakImports, sizeSortedDylibs); // Shared cache does not currently support unbound weak references. // Here we assert that there are none. If support is added later then @@ -717,7 +744,7 @@ void optimizeObjC(DyldSharedCache* cache, bool forProduction, std::vector err = protocolOptimizer.writeProtocols(&cacheAccessor, optRWData, optRWRemaining, optROData, optRORemaining, - pointersForASLR, protocolClassVMAddr); + aslrTracker, protocolClassVMAddr); if (err) { diag.warning("%s", err); return; @@ -804,17 +831,120 @@ void optimizeObjC(DyldSharedCache* cache, bool forProduction, std::vector 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); + + // Now that objc has uniqued the selector references, we can apply the LOHs so that ADRP/LDR -> ADRP/ADD + if (forProduction) { + uint64_t lohADRPCount = 0; + uint64_t lohLDRCount = 0; + + for (auto& targetAndInstructions : lohTracker) { + uint64_t targetVMAddr = targetAndInstructions.first; + if (!selOptimizer.isSelectorRefAddress((pint_t)targetVMAddr)) + continue; + + std::set& instructions = targetAndInstructions.second; + // We do 2 passes over the instructions. The first to validate them and the second + // to actually update them. + for (unsigned pass = 0; pass != 2; ++pass) { + uint32_t adrpCount = 0; + uint32_t ldrCount = 0; + for (void* instructionAddress : instructions) { + uint32_t& instruction = *(uint32_t*)instructionAddress; + uint64_t instructionVMAddr = cacheAccessor.vmAddrForContent(&instruction); + uint64_t selRefContent = *(uint64_t*)cacheAccessor.contentForVMAddr(targetVMAddr); + const char* selectorString = (const char*)cacheAccessor.contentForVMAddr(selRefContent); + uint64_t selectorStringVMAddr = cacheAccessor.vmAddrForContent(selectorString); + + if ( (instruction & 0x9F000000) == 0x90000000 ) { + // ADRP + int64_t pageDistance = ((selectorStringVMAddr & ~0xFFF) - (instructionVMAddr & ~0xFFF)); + int64_t newPage21 = pageDistance >> 12; + + if (pass == 0) { + if ( (newPage21 > 2097151) || (newPage21 < -2097151) ) { + diag.verbose("Out of bounds ADRP selector reference target\n"); + instructions.clear(); + break; + } + ++adrpCount; + } + + if (pass == 1) { + instruction = (instruction & 0x9F00001F) | ((newPage21 << 29) & 0x60000000) | ((newPage21 << 3) & 0x00FFFFE0); + ++lohADRPCount; + } + continue; + } + + if ( (instruction & 0x3B000000) == 0x39000000 ) { + // LDR/STR. STR shouldn't be possible as this is a selref! + if (pass == 0) { + if ( (instruction & 0xC0C00000) != 0xC0400000 ) { + // Not a load, or dest reg isn't xN, or uses sign extension + diag.verbose("Bad LDR for selector reference optimisation\n"); + instructions.clear(); + break; + } + if ( (instruction & 0x04000000) != 0 ) { + // Loading a float + diag.verbose("Bad LDR for selector reference optimisation\n"); + instructions.clear(); + break; + } + ++ldrCount; + } + + if (pass == 1) { + uint32_t ldrDestReg = (instruction & 0x1F); + uint32_t ldrBaseReg = ((instruction >> 5) & 0x1F); + + // Convert the LDR to an ADD + instruction = 0x91000000; + instruction |= ldrDestReg; + instruction |= ldrBaseReg << 5; + instruction |= (selectorStringVMAddr & 0xFFF) << 10; + + ++lohLDRCount; + } + continue; + } + + if ( (instruction & 0xFFC00000) == 0x91000000 ) { + // ADD imm12 + // We don't support ADDs. + diag.verbose("Bad ADD for selector reference optimisation\n"); + instructions.clear(); + break; + } + + diag.verbose("Unknown instruction for selref optimisation\n"); + instructions.clear(); + break; + } + if (pass == 0) { + // If we didn't see at least one ADRP/LDR in pass one then don't optimize this location + if ((adrpCount == 0) || (ldrCount == 0)) { + instructions.clear(); + break; + } + } + } + } + + diag.verbose(" Optimized %lld ADRP LOHs\n", lohADRPCount); + diag.verbose(" Optimized %lld LDR LOHs\n", lohLDRCount); + } } } // anon namespace -void optimizeObjC(DyldSharedCache* cache, bool is64, bool customerCache, std::vector& pointersForASLR, Diagnostics& diag) +void CacheBuilder::optimizeObjC() { - if ( is64 ) - optimizeObjC>(cache, customerCache, pointersForASLR, diag); + if ( _archLayout->is64 ) + doOptimizeObjC>((DyldSharedCache*)_readExecuteRegion.buffer, _options.optimizeStubs, _aslrTracker, _lohTracker, _missingWeakImports, _diagnostics); else - optimizeObjC>(cache, customerCache, pointersForASLR, diag); + doOptimizeObjC>((DyldSharedCache*)_readExecuteRegion.buffer, _options.optimizeStubs, _aslrTracker, _lohTracker, _missingWeakImports, _diagnostics); } diff --git a/dyld3/shared-cache/StringUtils.h b/dyld3/shared-cache/StringUtils.h index 701e5fd..d8541a1 100644 --- a/dyld3/shared-cache/StringUtils.h +++ b/dyld3/shared-cache/StringUtils.h @@ -32,6 +32,11 @@ 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 startsWith(const std::string_view& str, const std::string_view& 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()); @@ -55,7 +60,7 @@ inline char hexDigit(uint8_t value) inline void bytesToHex(const uint8_t* bytes, size_t byteCount, char buffer[]) { char* p = buffer; - for (int i=0; i < byteCount; ++i) { + for (size_t i=0; i < byteCount; ++i) { *p++ = hexDigit(bytes[i] >> 4); *p++ = hexDigit(bytes[i] & 0x0F); } diff --git a/dyld3/shared-cache/dyld_cache_format.h b/dyld3/shared-cache/dyld_cache_format.h index de63bf9..da9861b 100644 --- a/dyld3/shared-cache/dyld_cache_format.h +++ b/dyld3/shared-cache/dyld_cache_format.h @@ -59,12 +59,22 @@ struct dyld_cache_header 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 + uint32_t formatVersion : 8, // dyld3::closure::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 + locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache + padding : 21; // TBD 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 + uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache + uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache + uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs + uint64_t dylibsTrieSize; // size of trie of cached dylib paths + uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures + uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures }; @@ -238,10 +248,176 @@ struct dyld_cache_slide_info2 //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 +#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 + + + +// The version 3 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 +// +// There are two cases: +// +// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE +// The page contains no values that need rebasing. +// +// 2) otherwise... +// All rebase locations are in one linked list. The offset of the first +// rebase location in the page is pageStarts[pageIndex]. +// +// A pointer is one of of the variants in dyld_cache_slide_pointer3 +// +// The code for processing a linked list (chain) is: +// +// uint32_t delta = pageStarts[pageIndex]; +// dyld_cache_slide_pointer3* loc = pageStart; +// do { +// loc += delta; +// delta = loc->offsetToNextPointer; +// if ( loc->auth.authenticated ) { +// newValue = loc->offsetFromSharedCacheBase + results->slide + auth_value_add; +// newValue = sign_using_the_various_bits(newValue); +// } +// else { +// uint64_t value51 = loc->pointerValue; +// uint64_t top8Bits = value51 & 0x0007F80000000000ULL; +// uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFFULL; +// uint64_t targetValue = ( top8Bits << 13 ) | bottom43Bits; +// newValue = targetValue + results->slide; +// } +// loc->raw = newValue; +// } while (delta != 0); +// +// +struct dyld_cache_slide_info3 +{ + uint32_t version; // currently 3 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_count; + uint64_t auth_value_add; + uint16_t page_starts[/* page_starts_count */]; +}; + +#define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + +union dyld_cache_slide_pointer3 +{ + uint64_t raw; + struct { + uint64_t pointerValue : 51, + offsetToNextPointer : 11, + unused : 2; + } plain; + + struct { + uint64_t offsetFromSharedCacheBase : 32, + diversityData : 16, + hasAddressDiversity : 1, + key : 2, + offsetToNextPointer : 11, + unused : 1, + authenticated : 1; // = 1; + } auth; +}; + + + +// The version 4 of the slide info is optimized for 32-bit caches up to 1GB. +// Since only interior pointers (pointers that point within the cache) are rebased +// (slid), we know the possible range of the pointers takes 30 bits. That +// gives us two bits to use to chain to the next rebase. +// +// 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_SLIDE4_PAGE_NO_REBASE +// The page contains no values that need rebasing. +// +// 2) (pageStarts[pageIndex] & DYLD_CACHE_SLIDE4_PAGE_USE_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_SLIDE4_PAGE_USE_EXTRA +// Multiple chains 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 chain in the page. The first is at: +// extrasStartIndex = (pageStarts[pageIndex] & DYLD_CACHE_SLIDE4_PAGE_INDEX) +// The next is at extrasStartIndex+1. The last is denoted by +// having the high bit (DYLD_CACHE_SLIDE4_PAGE_EXTRA_END) of the pageExtras[]. +// +// For 32-bit architectures, there are only two bits free (the two 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 12 bytes. +// To reduce the number or chains needed, an optimization was added. Turns +// most of the non-rebased data are small values and can be co-opt'ed into +// being used in the chain. The can be done because nothing +// in the shared cache should point to the first 64KB which are in the shared +// cache header information. So if the resulting pointer points to the +// start of the cache +/-32KB, then it is actually a small number that should +// not be rebased, but just reconstituted. +// +// The code for processing a linked list (chain) is: +// +// uint32_t delta = 1; +// while ( delta != 0 ) { +// uint8_t* loc = pageStart + pageOffset; +// uint32_t rawValue = *((uint32_t*)loc); +// delta = ((rawValue & deltaMask) >> deltaShift); +// uintptr_t newValue = (rawValue & valueMask); +// if ( (newValue & 0xFFFF8000) == 0 ) { +// // small positive non-pointer, use as-is +// } +// else if ( (newValue & 0x3FFF8000) == 0x3FFF8000 ) { +// // small negative non-pointer +// newValue |= 0xC0000000; +// } +// else { +// // pointer that needs rebasing +// newValue += valueAdd; +// newValue += slideAmount; +// } +// *((uint32_t*)loc) = newValue; +// pageOffset += delta; +// } +// +// +struct dyld_cache_slide_info4 +{ + uint32_t version; // currently 4 + 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 (0xC0000000) + uint64_t value_add; // base address of cache + //uint16_t page_starts[page_starts_count]; + //uint16_t page_extras[page_extras_count]; +}; +#define DYLD_CACHE_SLIDE4_PAGE_NO_REBASE 0xFFFF // page has no rebasing +#define DYLD_CACHE_SLIDE4_PAGE_INDEX 0x7FFF // mask of page_starts[] values +#define DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA 0x8000 // index is into extras array (not a chain start offset) +#define DYLD_CACHE_SLIDE4_PAGE_EXTRA_END 0x8000 // last chain entry for page + + struct dyld_cache_local_symbols_info diff --git a/dyld3/shared-cache/dyld_closure_util.cpp b/dyld3/shared-cache/dyld_closure_util.cpp index db01f5f..a5f2973 100644 --- a/dyld3/shared-cache/dyld_closure_util.cpp +++ b/dyld3/shared-cache/dyld_closure_util.cpp @@ -42,153 +42,104 @@ #include #include -#include "LaunchCache.h" -#include "LaunchCacheWriter.h" #include "DyldSharedCache.h" #include "FileUtils.h" -#include "ImageProxy.h" #include "StringUtils.h" -#include "ClosureBuffer.h" +#include "ClosureBuilder.h" +#include "ClosurePrinter.h" +#include "ClosureFileSystemPhysical.h" + +using dyld3::closure::ImageArray; +using dyld3::closure::Image; +using dyld3::closure::ImageNum; +using dyld3::closure::ClosureBuilder; +using dyld3::closure::LaunchClosure; +using dyld3::closure::DlopenClosure; +using dyld3::closure::PathOverrides; +using dyld3::Array; -extern "C" { - #include "closuredProtocol.h" -} +// mmap() an shared cache file read/only but laid out like it would be at runtime static const DyldSharedCache* mapCacheFile(const char* path) { struct stat statbuf; - if (stat(path, &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); + 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; + uint8_t firstPage[4096]; + if ( ::pread(cache_fd, firstPage, 4096, 0) != 4096 ) { + fprintf(stderr, "Error: failed to read shared cache file at %s\n", path); + return nullptr; } - 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); + const dyld_cache_header* header = (dyld_cache_header*)firstPage; + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(firstPage + header->mappingOffset); + + size_t vmSize = (size_t)(mappings[2].address + mappings[2].size - mappings[0].address); + vm_address_t result; + kern_return_t r = ::vm_allocate(mach_task_self(), &result, vmSize, VM_FLAGS_ANYWHERE); + if ( r != KERN_SUCCESS ) { + fprintf(stderr, "Error: failed to allocate space to load shared cache file at %s\n", path); + return nullptr; + } + for (int i=0; i < 3; ++i) { + void* mapped_cache = ::mmap((void*)(result + mappings[i].address - mappings[0].address), (size_t)mappings[i].size, + PROT_READ, MAP_FIXED | MAP_PRIVATE, cache_fd, mappings[i].fileOffset); + 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); - //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; + return (DyldSharedCache*)result; } -*/ static void usage() { - printf("dyld_closure_util program to create of view dyld3 closures\n"); + printf("dyld_closure_util program to create or 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(" -list_dyld_cache_closures # list all launch closures in the dyld shared cache with size\n"); + printf(" -list_dyld_cache_dlopen_closures # list all dlopen closures in the dyld shared cache with size\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(" -print_dyld_cache_dylib # print specified cached dylib as JSON\n"); + printf(" -print_dyld_cache_dylibs # print all cached dylibs as JSON\n"); + printf(" -print_dyld_cache_dlopen # print specified dlopen closure as JSON\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(" -dlopen # for use with -create_closure to simulate that program calling dlopen\n"); printf(" -verbose_fixups # for use with -print* options to force printing fixups\n"); + printf(" -no_at_paths # when building a closure, simulate security not allowing @path expansion\n"); + printf(" -no_fallback_paths # when building a closure, simulate security not allowing default fallback paths\n"); + printf(" -allow_insertion_failures # when building a closure, simulate security allowing unloadable DYLD_INSERT_LIBRARIES to be ignored\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 listCacheDlopenClosures = false; bool printCachedDylibs = false; - bool printOtherDylibs = false; - bool printPatchTable = false; - bool useClosured = false; bool verboseFixups = false; + bool allowAtPaths = true; + bool allowFallbackPaths = true; + bool allowInsertionFailures = false; std::vector buildtimePrefixes; - std::vector envArgs; + std::vector envArgs; std::vector dlopens; if ( argc == 1 ) { @@ -212,14 +163,7 @@ int main(int argc, const char* argv[]) 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 ) { + 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"); @@ -227,9 +171,18 @@ int main(int argc, const char* argv[]) } dlopens.push_back(path); } - else if ( strcmp(arg, "-verbose_fixups") == 0 ) { + else if ( strcmp(arg, "-verbose_fixups") == 0 ) { verboseFixups = true; } + else if ( strcmp(arg, "-no_at_paths") == 0 ) { + allowAtPaths = false; + } + else if ( strcmp(arg, "-no_fallback_paths") == 0 ) { + allowFallbackPaths = false; + } + else if ( strcmp(arg, "-allow_insertion_failures") == 0 ) { + allowInsertionFailures = true; + } else if ( strcmp(arg, "-build_root") == 0 ) { const char* buildRootPath = argv[++i]; if ( buildRootPath == nullptr ) { @@ -238,32 +191,11 @@ int main(int argc, const char* argv[]) } 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, "-list_dyld_cache_dlopen_closures") == 0 ) { + listCacheDlopenClosures = true; } else if ( strcmp(arg, "-print_dyld_cache_closure") == 0 ) { printCacheClosure = argv[++i]; @@ -272,15 +204,9 @@ int main(int argc, const char* argv[]) 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 ) { @@ -288,19 +214,13 @@ int main(int argc, const char* argv[]) return 1; } } - else if ( strcmp(arg, "-print_dyld_cache_other") == 0 ) { + else if ( strcmp(arg, "-print_dyld_cache_dlopen") == 0 ) { printOtherDylib = argv[++i]; if ( printOtherDylib == nullptr ) { - fprintf(stderr, "-print_dyld_cache_other option requires a path \n"); + fprintf(stderr, "-print_dyld_cache_dlopen 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) ) { @@ -309,306 +229,151 @@ int main(int argc, const char* argv[]) } 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; - } + envArgs.push_back(nullptr); + const DyldSharedCache* dyldCache = nullptr; - bool dyldCacheIsRaw = false; + bool dyldCacheIsLive = true; if ( cacheFilePath != nullptr ) { dyldCache = mapCacheFile(cacheFilePath); - dyldCacheIsRaw = true; + dyldCacheIsLive = false; } else { -#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) +#if __MAC_OS_X_VERSION_MIN_REQUIRED && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101300) + fprintf(stderr, "this tool needs to run on macOS 10.13 or later\n"); + return 1; +#else 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(""); + dyld3::Platform platform = dyldCache->platform(); + const char* archName = dyldCache->archName(); - 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()); + PathOverrides pathOverrides; + pathOverrides.setFallbackPathHandling(allowFallbackPaths ? dyld3::closure::PathOverrides::FallbackPathMode::classic : dyld3::closure::PathOverrides::FallbackPathMode::none); + pathOverrides.setEnvVars(&envArgs[0], nullptr, nullptr); + const char* prefix = ( buildtimePrefixes.empty() ? nullptr : buildtimePrefixes.front().c_str()); + //dyld3::PathOverrides pathStuff(envArgs); + STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 3+dlopens.size()); + STACK_ALLOC_ARRAY(dyld3::LoadedImage, loadedArray, 1024); + imagesArrays.push_back(dyldCache->cachedDylibsImageArray()); + imagesArrays.push_back(dyldCache->otherOSImageArray()); + + dyld3::closure::FileSystemPhysical fileSystem(prefix); + ClosureBuilder::AtPath atPathHanding = allowAtPaths ? ClosureBuilder::AtPath::all : ClosureBuilder::AtPath::none; + ClosureBuilder builder(dyld3::closure::kFirstLaunchClosureImageNum, fileSystem, dyldCache, dyldCacheIsLive, pathOverrides, atPathHanding, nullptr, archName, platform, nullptr); + const LaunchClosure* mainClosure = builder.makeLaunchClosure(inputMainExecutablePath, allowInsertionFailures); + if ( builder.diagnostics().hasError() ) { + fprintf(stderr, "dyld_closure_util: %s\n", builder.diagnostics().errorMessage()); return 1; } - for (const std::string& warn : closureDiag.warnings() ) - fprintf(stderr, "dyld_closure_util: warning: %s\n", warn.c_str()); + ImageNum nextNum = builder.nextFreeImageNum(); - 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); + if ( !dlopens.empty() ) + printf("[\n"); + imagesArrays.push_back(mainClosure->images()); + dyld3::closure::printClosureAsJSON(mainClosure, imagesArrays, verboseFixups); + ClosureBuilder::buildLoadOrder(loadedArray, imagesArrays, mainClosure); - 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"); - } + for (const char* path : dlopens) { + 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; + // map unloaded mach-o files for use during closure building + for (dyld3::LoadedImage& li : loadedArray) { + if ( li.loadedAddress() != nullptr ) + continue; + if ( li.image()->inDyldCache() ) { + li.setLoadedAddress((dyld3::MachOLoaded*)((uint8_t*)dyldCache + li.image()->cacheOffset())); + } + else { + Diagnostics diag; + dyld3::closure::LoadedFileInfo loadedFileInfo = dyld3::MachOAnalyzer::load(diag, fileSystem, li.image()->path(), archName, platform); + li.setLoadedAddress((const dyld3::MachOAnalyzer*)loadedFileInfo.fileContent); + } } - } - 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); + ClosureBuilder::AtPath atPathHandingDlopen = allowAtPaths ? ClosureBuilder::AtPath::all : ClosureBuilder::AtPath::onlyInRPaths; + ClosureBuilder dlopenBuilder(nextNum, fileSystem, dyldCache, dyldCacheIsLive, pathOverrides, atPathHandingDlopen, nullptr, archName, platform, nullptr); + ImageNum topImageNum; + const DlopenClosure* dlopenClosure = dlopenBuilder.makeDlopenClosure(path, mainClosure, loadedArray, 0, false, false, &topImageNum); + if ( dlopenBuilder.diagnostics().hasError() ) { + fprintf(stderr, "dyld_closure_util: %s\n", dlopenBuilder.diagnostics().errorMessage()); 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(); + if ( dlopenClosure == nullptr ) { + if ( topImageNum == 0 ) { + fprintf(stderr, "dyld_closure_util: failed to dlopen %s\n", path); + return 1; + } + printf("{\n \"dyld-cache-image-num\": \"0x%04X\"\n}\n", topImageNum); + } + else { + nextNum += dlopenClosure->images()->imageCount(); + imagesArrays.push_back(dlopenClosure->images()); + dyld3::closure::printClosureAsJSON(dlopenClosure, imagesArrays, verboseFixups); + ClosureBuilder::buildLoadOrder(loadedArray, imagesArrays, dlopenClosure); } } - 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); + if ( !dlopens.empty() ) + printf("]\n"); } 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); + dyldCache->forEachLaunchClosure(^(const char* runtimePath, const dyld3::closure::LaunchClosure* closure) { + 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 ( listCacheDlopenClosures ) { + dyldCache->forEachDlopenImage(^(const char* runtimePath, const dyld3::closure::Image* image) { + printf("%6lu %s\n", image->size(), runtimePath); + }); } 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); + const dyld3::closure::LaunchClosure* closure = dyldCache->findClosure(printCacheClosure); + if ( closure != nullptr ) { + STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 3); + imagesArrays.push_back(dyldCache->cachedDylibsImageArray()); + imagesArrays.push_back(dyldCache->otherOSImageArray()); + imagesArrays.push_back(closure->images()); + dyld3::closure::printClosureAsJSON(closure, imagesArrays, 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); + dyld3::closure::printDyldCacheImagesAsJSON(dyldCache, 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); + const dyld3::closure::ImageArray* dylibs = dyldCache->cachedDylibsImageArray(); + STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 2); + imagesArrays.push_back(dylibs); + ImageNum num; + if ( dylibs->hasPath(printCachedDylib, num) ) { + dyld3::closure::printImageAsJSON(dylibs->imageForNum(num), imagesArrays, verboseFixups); } else { - fprintf(stderr, "no such other image found\n"); + fprintf(stderr, "no such 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); + if ( const dyld3::closure::Image* image = dyldCache->findDlopenOtherImage(printOtherDylib) ) { + STACK_ALLOC_ARRAY(const ImageArray*, imagesArrays, 2); + imagesArrays.push_back(dyldCache->cachedDylibsImageArray()); + imagesArrays.push_back(dyldCache->otherOSImageArray()); + dyld3::closure::printImageAsJSON(image, imagesArrays, verboseFixups); } else { - fprintf(stderr, "no such other image found\n"); + fprintf(stderr, "no such 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 index cbedce2..b3a1262 100644 --- a/dyld3/shared-cache/dyld_shared_cache_builder.mm +++ b/dyld3/shared-cache/dyld_shared_cache_builder.mm @@ -63,8 +63,9 @@ #include "DyldSharedCache.h" #include "BuilderUtils.h" #include "FileUtils.h" +#include "JSONWriter.h" #include "StringUtils.h" -#include "MachOParser.h" +#include "mrm_shared_cache_builder.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 @@ -74,9 +75,6 @@ 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; @@ -99,7 +97,7 @@ int runCommandAndWait(Diagnostics& diags, const char* args[]) return res; } -void processRoots(Diagnostics& diags, std::set& roots) +void processRoots(Diagnostics& diags, std::set& roots, const char *tempRootsDir) { std::set processedRoots; struct stat sb; @@ -110,9 +108,16 @@ void processRoots(Diagnostics& diags, std::set& 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")) { + processedRoots.insert(root); + continue; + } + + char tempRootDir[MAXPATHLEN]; + strlcpy(tempRootDir, tempRootsDir, MAXPATHLEN); + strlcat(tempRootDir, "/XXXXXXXX", MAXPATHLEN); + mkdtemp(tempRootDir); + + 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(); @@ -176,6 +181,7 @@ void processRoots(Diagnostics& diags, std::set& roots) bool writeRootList(const std::string& dstRoot, const std::set& roots) { + mkpath_np(dstRoot.c_str(), 0755); if (roots.size() == 0) return false; @@ -192,35 +198,109 @@ bool writeRootList(const std::string& dstRoot, const std::set& root return true; } -std::set cachePaths; - -BOMCopierCopyOperation filteredCopy(BOMCopier copier, const char* path, BOMFSObjType type, off_t size) +BOMCopierCopyOperation filteredCopyExcludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size) { std::string absolutePath = &path[1]; - if (cachePaths.count(absolutePath)) { + void *userData = BOMCopierUserData(copier); + std::set *cachePaths = (std::set*)userData; + if (cachePaths->count(absolutePath)) { return BOMCopierSkipFile; } return BOMCopierContinue; } +BOMCopierCopyOperation filteredCopyIncludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size) +{ + std::string absolutePath = &path[1]; + void *userData = BOMCopierUserData(copier); + std::set *cachePaths = (std::set*)userData; + for (const std::string& cachePath : *cachePaths) { + if (startsWith(cachePath, absolutePath)) + return BOMCopierContinue; + } + if (cachePaths->count(absolutePath)) { + return BOMCopierContinue; + } + return BOMCopierSkipFile; +} + +static std::string dispositionToString(Disposition disposition) { + switch (disposition) { + case Unknown: + return "Unknown"; + case InternalDevelopment: + return "InternalDevelopment"; + case Customer: + return "Customer"; + case InternalMinDevelopment: + return "InternalMinDevelopment"; + } +} + +static std::string platformToString(Platform platform) { + switch (platform) { + case unknown: + return "unknown"; + case macOS: + return "macOS"; + case iOS: + return "iOS"; + case tvOS: + return "tvOS"; + case watchOS: + return "watchOS"; + case bridgeOS: + return "bridgeOS"; + case iOSMac: + return "iOSMac"; + case iOS_simulator: + return "iOS_simulator"; + case tvOS_simulator: + return "tvOS_simulator"; + case watchOS_simulator: + return "watchOS_simulator"; + } +} + +static dyld3::json::Node getBuildOptionsNode(BuildOptions_v1 buildOptions) { + dyld3::json::Node buildOptionsNode; + buildOptionsNode.map["version"].value = dyld3::json::decimal(buildOptions.version); + buildOptionsNode.map["updateName"].value = buildOptions.updateName; + buildOptionsNode.map["deviceName"].value = buildOptions.deviceName; + buildOptionsNode.map["disposition"].value = dispositionToString(buildOptions.disposition); + buildOptionsNode.map["platform"].value = platformToString(buildOptions.platform); + for (unsigned i = 0; i != buildOptions.numArchs; ++i) { + dyld3::json::Node archNode; + archNode.value = buildOptions.archs[i]; + buildOptionsNode.map["archs"].array.push_back(archNode); + } + buildOptionsNode.map["verboseDiagnostics"].value = buildOptions.verboseDiagnostics ? "true" : "false"; + return buildOptionsNode; +} + int main(int argc, const char* argv[]) { @autoreleasepool { __block Diagnostics diags; std::set roots; std::string dylibCacheDir; + std::string artifactDir; std::string release; bool emitDevCaches = true; bool emitElidedDylibs = true; bool listConfigs = false; bool copyRoots = false; bool debug = false; + bool useMRM = false; std::string dstRoot; + std::string emitJSONPath; std::string configuration; std::string resultPath; + std::string baselineDifferenceResultPath; + bool baselineCopyRoots = false; + char* tempRootsDir = strdup("/tmp/dyld_shared_cache_builder.XXXXXX"); - tempRootDir = strdup(tempRootDirTemplate); - mkdtemp(tempRootDir); + mkdtemp(tempRootsDir); for (int i = 1; i < argc; ++i) { const char* arg = argv[i]; @@ -236,6 +316,8 @@ int main(int argc, const char* argv[]) copyRoots = true; } else if (strcmp(arg, "-dylib_cache") == 0) { dylibCacheDir = realPath(argv[++i]); + } else if (strcmp(arg, "-artifact") == 0) { + artifactDir = realPath(argv[++i]); } else if (strcmp(arg, "-no_development_cache") == 0) { emitDevCaches = false; } else if (strcmp(arg, "-no_overflow_dylibs") == 0) { @@ -244,19 +326,29 @@ int main(int argc, const char* argv[]) emitDevCaches = true; } else if (strcmp(arg, "-overflow_dylibs") == 0) { emitElidedDylibs = true; + } else if (strcmp(arg, "-mrm") == 0) { + useMRM = true; + } else if (strcmp(arg, "-emit_json") == 0) { + emitJSONPath = realPath(argv[++i]); } 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 if (strcmp(arg, "-baseline_diff_results") == 0) { + baselineDifferenceResultPath = realPath(argv[++i]); + } else if (strcmp(arg, "-baseline_copy_roots") == 0) { + baselineCopyRoots = true; } else { //usage(); - diags.error("unknown option: %s\n", arg); + fprintf(stderr, "unknown option: %s\n", arg); + exit(-1); } } else { if (!configuration.empty()) { - diags.error("You may only specify one configuration"); + fprintf(stderr, "You may only specify one configuration\n"); + exit(-1); } configuration = argv[i]; } @@ -264,16 +356,20 @@ int main(int argc, const char* argv[]) time_t mytime = time(0); fprintf(stderr, "Started: %s", asctime(localtime(&mytime))); - processRoots(diags, roots); + writeRootList(dstRoot, roots); + processRoots(diags, roots, tempRootsDir); 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"); + if (dylibCacheDir.empty() && artifactDir.empty() && release.empty()) { + fprintf(stderr, "you must specify either -dylib_cache, -artifact or -release\n"); exit(-1); } else if (!dylibCacheDir.empty() && !release.empty()) { - fprintf(stderr, "you may not use -dylib_cache and -release at the same time"); + fprintf(stderr, "you may not use -dylib_cache and -release at the same time\n"); + exit(-1); + } else if (!dylibCacheDir.empty() && !artifactDir.empty()) { + fprintf(stderr, "you may not use -dylib_cache and -artifact at the same time\n"); exit(-1); } @@ -282,6 +378,49 @@ int main(int argc, const char* argv[]) exit(-1); } + if (!baselineDifferenceResultPath.empty() && (roots.size() > 1)) { + fprintf(stderr, "Cannot use -baseline_diff_results with more that one -root\n"); + exit(-1); + } + + if (!artifactDir.empty()) { + // Find the dylib cache dir from inside the artifact dir + struct stat stat_buf; + if (stat(artifactDir.c_str(), &stat_buf) != 0) { + fprintf(stderr, "Could not find artifact path '%s'\n", artifactDir.c_str()); + exit(-1); + } + std::string dir = artifactDir + "/AppleInternal/Developer/DylibCaches"; + if (stat(dir.c_str(), &stat_buf) != 0) { + fprintf(stderr, "Could not find artifact path '%s'\n", dir.c_str()); + exit(-1); + } + + if (!release.empty()) { + // Use the given release + dylibCacheDir = dir + "/" + release + ".dlc"; + } else { + // Find a release directory + __block std::vector subDirectories; + iterateDirectoryTree("", dir, ^(const std::string& dirPath) { + subDirectories.push_back(dirPath); + return false; + }, nullptr, false, false); + + if (subDirectories.empty()) { + fprintf(stderr, "Could not find dlc subdirectories inside '%s'\n", dir.c_str()); + exit(-1); + } + + if (subDirectories.size() > 1) { + fprintf(stderr, "Found too many subdirectories inside artifact path '%s'. Use -release to select one\n", dir.c_str()); + exit(-1); + } + + dylibCacheDir = subDirectories.front(); + } + } + if (dylibCacheDir.empty()) { dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + release + ".dlc"; } @@ -290,7 +429,10 @@ int main(int argc, const char* argv[]) chdir(dylibCacheDir.c_str()); dispatch_async(dispatch_get_main_queue(), ^{ - auto manifest = dyld3::Manifest(diags, dylibCacheDir + "/Manifest.plist", roots); + // If we only want a list of configuations, then tell the manifest to only parse the data and not + // actually get all the macho's. + bool onlyParseManifest = listConfigs && configuration.empty(); + auto manifest = dyld3::Manifest(diags, dylibCacheDir + "/Manifest.plist", roots, onlyParseManifest); if (manifest.build().empty()) { fprintf(stderr, "No manifest found at '%s/Manifest.plist'\n", dylibCacheDir.c_str()); @@ -302,6 +444,9 @@ int main(int argc, const char* argv[]) manifest.forEachConfiguration([](const std::string& configName) { printf("%s\n", configName.c_str()); }); + // If we weren't passed a configuration then exit + if (configuration.empty()) + exit(0); } if (!manifest.filterForConfig(configuration)) { @@ -309,51 +454,327 @@ int main(int argc, const char* argv[]) configuration.c_str(), manifest.build().c_str()); exit(-1); } - manifest.calculateClosure(); - std::vector buildQueue; + (void)mkpath_np((dstRoot + "/System/Library/Caches/com.apple.dyld/").c_str(), 0755); + bool cacheBuildSuccess = false; + if (useMRM) { + + FILE* jsonFile = nullptr; + if (!emitJSONPath.empty()) { + jsonFile = fopen(emitJSONPath.c_str(), "w"); + if (!jsonFile) { + diags.verbose("can't open file '%s', errno=%d\n", emitJSONPath.c_str(), errno); + return; + } + } + dyld3::json::Node buildInvocationNode; + + // Find the archs for the configuration we want. + __block std::set validArchs; + manifest.configuration(configuration).forEachArchitecture(^(const std::string& path) { + validArchs.insert(path); + }); + + if (validArchs.size() != 1) { + fprintf(stderr, "MRM doesn't support more than one arch per configuration: %s\n", + configuration.c_str()); + exit(-1); + } + + const char* archs[validArchs.size()]; + uint64_t archIndex = 0; + for (const std::string& arch : validArchs) { + archs[archIndex++] = arch.c_str(); + } + + BuildOptions_v1 buildOptions; + buildOptions.version = 1; + buildOptions.updateName = manifest.build().c_str(); + buildOptions.deviceName = configuration.c_str(); + buildOptions.disposition = Disposition::Unknown; + buildOptions.platform = (Platform)manifest.platform(); + buildOptions.archs = archs; + buildOptions.numArchs = validArchs.size(); + buildOptions.verboseDiagnostics = debug; + buildOptions.isLocallyBuiltCache = true; + + __block struct SharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder(&buildOptions); + buildInvocationNode.map["build-options"] = getBuildOptionsNode(buildOptions); + + std::set requiredBinaries = { + "/usr/lib/libSystem.B.dylib" + }; + + // Get the file data for every MachO in the BOM. + __block dyld3::json::Node filesNode; + __block std::vector> mappedFiles; + manifest.forEachMachO(configuration, ^(const std::string &buildPath, const std::string &runtimePath, const std::string &arch, bool shouldBeExcludedIfLeaf) { + + // Filter based on arch as the Manifest adds the file once for each UUID. + if (!validArchs.count(arch)) + return; + + struct stat stat_buf; + int fd = ::open(buildPath.c_str(), O_RDONLY, 0); + if (fd == -1) { + diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno); + return; + } + + if (fstat(fd, &stat_buf) == -1) { + diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno); + ::close(fd); + return; + } + + const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buffer == MAP_FAILED) { + diags.verbose("mmap() for file at %s failed, errno=%d\n", buildPath.c_str(), errno); + ::close(fd); + } + ::close(fd); + + mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size); + FileFlags fileFlags = FileFlags::NoFlags; + if (requiredBinaries.count(runtimePath)) + fileFlags = FileFlags::RequiredClosure; + addFile(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags); + + dyld3::json::Node fileNode; + fileNode.map["path"].value = runtimePath; + fileNode.map["flags"].value = "NoFlags"; + filesNode.array.push_back(fileNode); + }); + + __block dyld3::json::Node symlinksNode; + manifest.forEachSymlink(configuration, ^(const std::string &fromPath, const std::string &toPath) { + addSymlink(sharedCacheBuilder, fromPath.c_str(), toPath.c_str()); + + dyld3::json::Node symlinkNode; + symlinkNode.map["from-path"].value = fromPath; + symlinkNode.map["to-path"].value = toPath; + symlinksNode.array.push_back(symlinkNode); + }); + + buildInvocationNode.map["symlinks"] = symlinksNode; + + std::string orderFileData; + if (!manifest.dylibOrderFile().empty()) { + orderFileData = loadOrderFile(manifest.dylibOrderFile()); + if (!orderFileData.empty()) { + addFile(sharedCacheBuilder, "*order file data*", (uint8_t*)orderFileData.data(), orderFileData.size(), FileFlags::DylibOrderFile); + dyld3::json::Node fileNode; + fileNode.map["path"].value = manifest.dylibOrderFile(); + fileNode.map["flags"].value = "DylibOrderFile"; + filesNode.array.push_back(fileNode); + } + } + + std::string dirtyDataOrderFileData; + if (!manifest.dirtyDataOrderFile().empty()) { + dirtyDataOrderFileData = loadOrderFile(manifest.dirtyDataOrderFile()); + if (!dirtyDataOrderFileData.empty()) { + addFile(sharedCacheBuilder, "*dirty data order file data*", (uint8_t*)dirtyDataOrderFileData.data(), dirtyDataOrderFileData.size(), FileFlags::DirtyDataOrderFile); + dyld3::json::Node fileNode; + fileNode.map["path"].value = manifest.dirtyDataOrderFile(); + fileNode.map["flags"].value = "DirtyDataOrderFile"; + filesNode.array.push_back(fileNode); + } + } + + buildInvocationNode.map["files"] = filesNode; + + if (jsonFile) { + dyld3::json::printJSON(buildInvocationNode, 0, jsonFile); + fclose(jsonFile); + jsonFile = nullptr; + } + + cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder); + + if (!cacheBuildSuccess) { + for (uint64 i = 0, e = getErrorCount(sharedCacheBuilder); i != e; ++i) { + const char* errorMessage = getError(sharedCacheBuilder, i); + fprintf(stderr, "ERROR: %s\n", errorMessage); + } + } + + // Now emit each cache we generated, or the errors for them. + for (uint64 i = 0, e = getCacheResultCount(sharedCacheBuilder); i != e; ++i) { + BuildResult result; + getCacheResult(sharedCacheBuilder, i, &result); + if (result.numErrors) { + for (uint64_t errorIndex = 0; errorIndex != result.numErrors; ++errorIndex) { + fprintf(stderr, "[%s] ERROR: %s\n", result.loggingPrefix, result.errors[errorIndex]); + } + cacheBuildSuccess = false; + continue; + } + if (result.numWarnings) { + for (uint64_t warningIndex = 0; warningIndex != result.numWarnings; ++warningIndex) { + fprintf(stderr, "[%s] WARNING: %s\n", result.loggingPrefix, result.warnings[warningIndex]); + } + } + } + + // If we built caches, then write everything out. + // TODO: Decide if we should we write any good caches anyway? + if (cacheBuildSuccess) { + for (uint64 i = 0, e = getFileResultCount(sharedCacheBuilder); i != e; ++i) { + FileResult result; + getFileResult(sharedCacheBuilder, i, &result); + + if (!result.data) + continue; + + const std::string path = dstRoot + result.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 ) { + ::ftruncate(fd, result.size); + uint64_t writtenSize = pwrite(fd, result.data, result.size, 0); + if ( writtenSize == result.size ) { + ::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); + continue; // success + } + } + else { + fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace); + cacheBuildSuccess = false; + } + ::close(fd); + ::unlink(pathTemplateSpace); + } + else { + fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace); + cacheBuildSuccess = false; + } + } + } + + destroySharedCacheBuilder(sharedCacheBuilder); - bool cacheBuildSuccess = build(diags, manifest, dstRoot, false, debug, false, false); + for (auto mappedFile : mappedFiles) + ::munmap((void*)mappedFile.first, mappedFile.second); + } else { + manifest.calculateClosure(); + + cacheBuildSuccess = build(diags, manifest, dstRoot, false, debug, false, false, emitDevCaches, true); + } if (!cacheBuildSuccess) { exit(-1); } - writeRootList(dstRoot, roots); + // Compare this cache to the baseline cache and see if we have any roots to copy over + if (!baselineDifferenceResultPath.empty() || baselineCopyRoots) { + std::set baselineDylibs = manifest.resultsForConfiguration(configuration); + + std::set newDylibs; + manifest.forEachConfiguration([&manifest, &newDylibs](const std::string& configName) { + for (auto& arch : manifest.configuration(configName).architectures) { + for (auto& dylib : arch.second.results.dylibs) { + if (dylib.second.included) { + newDylibs.insert(manifest.installNameForUUID(dylib.first)); + } + } + } + }); + + if (baselineCopyRoots) { + // Work out the set of dylibs in the old cache but not the new one + std::set dylibsMissingFromNewCache; + for (const std::string& baselineDylib : baselineDylibs) { + if (!newDylibs.count(baselineDylib)) + dylibsMissingFromNewCache.insert(baselineDylib); + } + + if (!dylibsMissingFromNewCache.empty()) { + BOMCopier copier = BOMCopierNewWithSys(BomSys_default()); + BOMCopierSetUserData(copier, (void*)&dylibsMissingFromNewCache); + BOMCopierSetCopyFileStartedHandler(copier, filteredCopyIncludingPaths); + std::string dylibCacheRootDir = realFilePath(dylibCacheDir + "/Root"); + if (dylibCacheRootDir == "") { + fprintf(stderr, "Could not find dylib Root directory to copy baseline roots from\n"); + exit(1); + } + BOMCopierCopy(copier, dylibCacheRootDir.c_str(), dstRoot.c_str()); + BOMCopierFree(copier); + + for (const std::string& dylibMissingFromNewCache : dylibsMissingFromNewCache) { + diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.c_str()); + } + } + } + + if (!baselineDifferenceResultPath.empty()) { + auto cppToObjStr = [](const std::string& str) { + return [NSString stringWithUTF8String:str.c_str()]; + }; + + // Work out the set of dylibs in the cache and taken from the -root + NSMutableArray* dylibsFromRoots = [NSMutableArray array]; + for (auto& root : roots) { + for (const std::string& dylibInstallName : newDylibs) { + struct stat sb; + std::string filePath = root + "/" + dylibInstallName; + if (!stat(filePath.c_str(), &sb)) { + [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)]; + } + } + } + + // Work out the set of dylibs in the new cache but not in the baseline cache. + NSMutableArray* dylibsMissingFromBaselineCache = [NSMutableArray array]; + for (const std::string& newDylib : newDylibs) { + if (!baselineDylibs.count(newDylib)) + [dylibsMissingFromBaselineCache addObject:cppToObjStr(newDylib)]; + } + + NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init]; + cacheDict[@"root-paths-in-cache"] = dylibsFromRoots; + cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache; + + NSError* error = nil; + NSData* outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict + format:NSPropertyListBinaryFormat_v1_0 + options:0 + error:&error]; + (void)[outData writeToFile:cppToObjStr(baselineDifferenceResultPath) atomically:YES]; + } + } if (copyRoots) { - manifest.forEachConfiguration([&manifest](const std::string& configName) { + std::set cachePaths; + manifest.forEachConfiguration([&manifest, &cachePaths](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()); + cachePaths.insert(manifest.installNameForUUID(dylib.first)); } } } }); BOMCopier copier = BOMCopierNewWithSys(BomSys_default()); - BOMCopierSetCopyFileStartedHandler(copier, filteredCopy); + BOMCopierSetUserData(copier, (void*)&cachePaths); + BOMCopierSetCopyFileStartedHandler(copier, filteredCopyExcludingPaths); 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. @@ -364,7 +785,7 @@ int main(int argc, const char* argv[]) const char* args[8]; args[0] = (char*)"/bin/rm"; args[1] = (char*)"-rf"; - args[2] = (char*)tempRootDir; + args[2] = (char*)tempRootsDir; args[3] = nullptr; (void)runCommandAndWait(diags, args); diff --git a/dyld3/shared-cache/make_ios_dyld_cache.cpp b/dyld3/shared-cache/make_ios_dyld_cache.cpp index 588d7d8..8bc1632 100644 --- a/dyld3/shared-cache/make_ios_dyld_cache.cpp +++ b/dyld3/shared-cache/make_ios_dyld_cache.cpp @@ -55,7 +55,7 @@ #include #include -#include "MachOParser.h" +#include "MachOFile.h" #include "FileUtils.h" #include "StringUtils.h" #include "DyldSharedCache.h" @@ -86,21 +86,24 @@ static bool addIfMachO(const std::string& buildRootPath, const std::string& runt Diagnostics diag; bool usedWholeFile = false; for (MappedMachOsByCategory& file : files) { - size_t sliceOffset; - size_t sliceLength; + uint64_t sliceOffset = 0; + uint64_t sliceLength = statBuf.st_size; bool fatButMissingSlice; const void* slice = MAP_FAILED; - if ( dyld3::FatUtil::isFatFileWithSlice(diag, wholeFile, statBuf.st_size, file.archName, sliceOffset, sliceLength, fatButMissingSlice) ) { + const dyld3::FatFile* fh = (dyld3::FatFile*)wholeFile; + const dyld3::MachOFile* mh = (dyld3::MachOFile*)wholeFile; + if ( fh->isFatFileWithSlice(diag, statBuf.st_size, file.archName.c_str(), 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) ) { + mh = (dyld3::MachOFile*)slice; + if ( !mh->isMachO(diag, sliceLength) ) { ::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) ) { + else if ( !fatButMissingSlice && mh->isMachO(diag, sliceLength) ) { slice = wholeFile; sliceLength = statBuf.st_size; sliceOffset = 0; @@ -108,15 +111,14 @@ static bool addIfMachO(const std::string& buildRootPath, const std::string& runt //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 ) { + mh = (dyld3::MachOFile*)slice; + if ( mh->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() ) { + if ( mh->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); } @@ -124,9 +126,6 @@ static bool addIfMachO(const std::string& buildRootPath, const std::string& runt 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; } @@ -275,6 +274,7 @@ int main(int argc, const char* argv[]) break; case dyld3::Platform::watchOS: archStrs.insert("armv7k"); + archStrs.insert("arm64_32"); break; case dyld3::Platform::unknown: case dyld3::Platform::macOS: @@ -289,6 +289,8 @@ int main(int argc, const char* argv[]) std::vector allFileSets; if ( archStrs.count("arm64") ) allFileSets.push_back({"arm64"}); + if ( archStrs.count("arm64_32") ) + allFileSets.push_back({"arm64_32"}); if ( archStrs.count("armv7k") ) allFileSets.push_back({"armv7k"}); std::vector paths; @@ -320,6 +322,7 @@ int main(int argc, const char* argv[]) options.inodesAreSameAsRuntime = false; options.cacheSupportsASLR = true; options.forSimulator = false; + options.isLocallyBuiltCache = true; options.verbose = verbose; options.evictLeafDylibsOnOverflow = false; options.pathPrefixes = { rootPath }; diff --git a/dyld3/shared-cache/mrm_shared_cache_builder.cpp b/dyld3/shared-cache/mrm_shared_cache_builder.cpp new file mode 100644 index 0000000..7bb7654 --- /dev/null +++ b/dyld3/shared-cache/mrm_shared_cache_builder.cpp @@ -0,0 +1,647 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * 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 "mrm_shared_cache_builder.h" +#include "CacheBuilder.h" +#include "ClosureFileSystem.h" +#include "FileUtils.h" +#include +#include +#include +#include + +static const uint64_t kMinBuildVersion = 1; //The minimum version BuildOptions struct we can support +static const uint64_t kMaxBuildVersion = 1; //The maximum version BuildOptions struct we can support + +namespace dyld3 { +namespace closure { + +struct FileInfo { + const char* path; + const uint8_t* data; + const uint64_t length; + FileFlags flags; + uint64_t mtime; + uint64_t inode; +}; + +class FileSystemMRM : public FileSystem { +public: + FileSystemMRM() : FileSystem() { } + + bool getRealPath(const char possiblePath[MAXPATHLEN], char realPath[MAXPATHLEN]) const override { + Diagnostics diag; + std::string resolvedPath = symlinkResolver.realPath(diag, possiblePath); + if (diag.hasError()) { + diag.verbose("MRM error: %s\n", diag.errorMessage().c_str()); + diag.clearError(); + return false; + } + + // FIXME: Should we only return real paths of files which point to macho's? For now that is what we are doing + auto it = fileMap.find(resolvedPath); + if (it == fileMap.end()) + return false; + + memcpy(realPath, resolvedPath.c_str(), std::min((size_t)MAXPATHLEN, resolvedPath.size() + 1)); + return true; + } + + bool loadFile(const char* path, LoadedFileInfo& info, char realerPath[MAXPATHLEN], void (^error)(const char* format, ...)) const override { + Diagnostics diag; + std::string resolvedPath = symlinkResolver.realPath(diag, path); + if (diag.hasError()) { + diag.verbose("MRM error: %s\n", diag.errorMessage().c_str()); + diag.clearError(); + return false; + } + + auto it = fileMap.find(resolvedPath); + if (it == fileMap.end()) + return false; + + if (resolvedPath == path) + realerPath[0] = '\0'; + else + memcpy(realerPath, resolvedPath.c_str(), std::min((size_t)MAXPATHLEN, resolvedPath.size() + 1)); + + // The file exists at this exact path. Lets use it! + const FileInfo& fileInfo = files[it->second]; + + info.fileContent = fileInfo.data; + info.fileContentLen = fileInfo.length; + info.sliceOffset = 0; + info.sliceLen = fileInfo.length; + info.inode = fileInfo.inode; + info.mtime = fileInfo.mtime; + info.unload = nullptr; + info.path = path; + return true; + } + + void unloadFile(const LoadedFileInfo& info) const override { + if (info.unload) + info.unload(info); + } + + void unloadPartialFile(LoadedFileInfo& info, uint64_t keepStartOffset, uint64_t keepLength) const override { + // Note we don't actually unload the data here, but we do want to update the offsets for other data structures to track where we are + info.fileContent = (const void*)((char*)info.fileContent + keepStartOffset); + info.fileContentLen = keepLength; + } + + bool fileExists(const char* path, uint64_t* inode=nullptr, uint64_t* mtime=nullptr, bool* issetuid=nullptr) const override { + Diagnostics diag; + std::string resolvedPath = symlinkResolver.realPath(diag, path); + if (diag.hasError()) { + diag.verbose("MRM error: %s\n", diag.errorMessage().c_str()); + diag.clearError(); + return false; + } + + auto it = fileMap.find(resolvedPath); + if (it == fileMap.end()) + return false; + + // The file exists at this exact path. Lets use it! + const FileInfo& fileInfo = files[it->second]; + if (inode) + *inode = fileInfo.inode; + if (mtime) + *mtime = fileInfo.mtime; + if (issetuid) + *issetuid = false; + return true; + } + + // MRM file APIs + bool addFile(const char* path, uint8_t* data, uint64_t size, Diagnostics& diag, FileFlags fileFlags) { + auto iteratorAndInserted = fileMap.insert(std::make_pair(path, files.size())); + if (!iteratorAndInserted.second) { + diag.error("Already have content for path: '%s'", path); + return false; + } + + symlinkResolver.addFile(diag, path); + if (diag.hasError()) + return false; + + // on iOS, inode is used to hold hash of path + uint64_t hash = 0; + for (const char* s = path; *s != '\0'; ++s) + hash += hash*4 + *s; + uint64_t inode = hash; + uint64_t mtime = 0; + + files.push_back((FileInfo){ path, data, size, fileFlags, mtime, inode }); + return true; + } + + bool addSymlink(const char* fromPath, const char* toPath, Diagnostics& diag) { + symlinkResolver.addSymlink(diag, fromPath, toPath); + return !diag.hasError(); + } + + void forEachFileInfo(std::function lambda) { + for (const FileInfo& fileInfo : files) + lambda(fileInfo.path, fileInfo.flags); + } + + size_t fileCount() const { + return files.size(); + } + + std::vector getResolvedSymlinks(Diagnostics& diag) { + return symlinkResolver.getResolvedSymlinks(diag); + } + +private: + std::vector files; + std::map fileMap; + SymlinkResolver symlinkResolver; +}; + +} // namespace closure +} // namespace dyld3 + +struct BuildInstance { + std::unique_ptr options; + std::unique_ptr builder; + std::vector inputFiles; + std::vector errors; + std::vector warnings; + std::vector errorStrings; // Owns the data for the errors + std::vector warningStrings; // Owns the data for the warnings + uint8_t* cacheData = nullptr; + uint64_t cacheSize = 0; + uint8_t* cacheMapData = nullptr; + uint64_t cacheMapSize = 0; + std::string cdHash; // Owns the data for the cdHash +}; + +struct BuildFileResult { + std::string path; + const uint8_t* data; + uint64_t size; +}; + +struct SharedCacheBuilder { + SharedCacheBuilder(const BuildOptions_v1* options); + const BuildOptions_v1* options; + dyld3::closure::FileSystemMRM fileSystem; + + std::string dylibOrderFileData; + std::string dirtyDataOrderFileData; + + // An array of builders and their options as we may have more than one builder for a given device variant. + std::vector builders; + + // The results from all of the builders + // We keep this in a vector to own the data. + std::vector fileResults; + + std::vector errors; + pthread_mutex_t lock; + + enum State { + AcceptingFiles, + Building, + FinishedBuilding + }; + + State state = AcceptingFiles; + + void runSync(void (^block)()) { + pthread_mutex_lock(&lock); + block(); + pthread_mutex_unlock(&lock); + } + + __attribute__((format(printf, 2, 3))) + void error(const char* format, ...) { + va_list list; + va_start(list, format); + Diagnostics diag; + diag.error(format, list); + va_end(list); + + errors.push_back(diag.errorMessage()); + } +}; + +SharedCacheBuilder::SharedCacheBuilder(const BuildOptions_v1* options) : options(options), lock(PTHREAD_MUTEX_INITIALIZER) { + +} + +void validiateBuildOptions(const BuildOptions_v1* options, SharedCacheBuilder& builder) { + if (options->version < kMinBuildVersion) { + builder.error("Builder version %llu is less than minimum supported version of %llu", options->version, kMinBuildVersion); + } + if (options->version > kMaxBuildVersion) { + builder.error("Builder version %llu is greater than maximum supported version of %llu", options->version, kMaxBuildVersion); + } + if (!options->updateName) { + builder.error("updateName must not be null"); + } + if (!options->deviceName) { + builder.error("deviceName must not be null"); + } + switch (options->disposition) { + case Disposition::Unknown: + case Disposition::InternalDevelopment: + case Disposition::Customer: + break; + default: + builder.error("unknown disposition value"); + break; + } + switch (options->platform) { + case Platform::unknown: + builder.error("platform must not be unknown"); + break; + case Platform::macOS: + case Platform::iOS: + case Platform::tvOS: + case Platform::watchOS: + case Platform::bridgeOS: + case Platform::iOSMac: + case Platform::iOS_simulator: + case Platform::tvOS_simulator: + case Platform::watchOS_simulator: + break; + default: + builder.error("unknown platform value"); + break; + } + if (!options->archs) { + builder.error("archs must not be null"); + } + if (!options->numArchs) { + builder.error("numArchs must not be 0"); + } +} + +struct SharedCacheBuilder* createSharedCacheBuilder(const BuildOptions_v1* options) { + SharedCacheBuilder* builder = new SharedCacheBuilder(options); + + // Check the option struct values are valid + validiateBuildOptions(options, *builder); + + return builder; +} + +bool addFile(struct SharedCacheBuilder* builder, const char* path, uint8_t* data, uint64_t size, FileFlags fileFlags) { + __block bool success = false; + builder->runSync(^() { + if (builder->state != SharedCacheBuilder::AcceptingFiles) { + builder->error("Cannot add file: '%s' as we have already started building", path); + return; + } + size_t pathLength = strlen(path); + if (pathLength == 0) { + builder->error("Empty path"); + return; + } + if (pathLength >= MAXPATHLEN) { + builder->error("Path is too long: '%s'", path); + return; + } + if (data == nullptr) { + builder->error("Data cannot be null for file: '%s'", path); + return; + } + switch (fileFlags) { + case NoFlags: + case MustBeInCache: + case ShouldBeExcludedFromCacheIfUnusedLeaf: + case RequiredClosure: + break; + case DylibOrderFile: + builder->dylibOrderFileData = std::string((char*)data, size); + success = true; + return; + case DirtyDataOrderFile: + builder->dirtyDataOrderFileData = std::string((char*)data, size); + success = true; + return; + default: + builder->error("unknown file flags value"); + break; + } + Diagnostics diag; + if (!builder->fileSystem.addFile(path, data, size, diag, fileFlags)) { + builder->errors.push_back(diag.errorMessage()); + return; + } + success = true; + }); + return success; +} + +bool addSymlink(struct SharedCacheBuilder* builder, const char* fromPath, const char* toPath) { + __block bool success = false; + builder->runSync(^() { + if (builder->state != SharedCacheBuilder::AcceptingFiles) { + builder->error("Cannot add file: '%s' as we have already started building", fromPath); + return; + } + size_t pathLength = strlen(fromPath); + if (pathLength == 0) { + builder->error("Empty path"); + return; + } + if (pathLength >= MAXPATHLEN) { + builder->error("Path is too long: '%s'", fromPath); + return; + } + Diagnostics diag; + if (!builder->fileSystem.addSymlink(fromPath, toPath, diag)) { + builder->errors.push_back(diag.errorMessage()); + return; + } + success = true; + }); + return success; +} + +static bool platformExcludeLocalSymbols(Platform platform) { + switch (platform) { + case Platform::unknown: + case Platform::macOS: + return false; + case Platform::iOS: + case Platform::tvOS: + case Platform::watchOS: + case Platform::bridgeOS: + return true; + case Platform::iOSMac: + case Platform::iOS_simulator: + case Platform::tvOS_simulator: + case Platform::watchOS_simulator: + return false; + } +} + +static DyldSharedCache::CodeSigningDigestMode platformCodeSigningDigestMode(Platform platform) { + switch (platform) { + case Platform::unknown: + case Platform::macOS: + case Platform::iOS: + case Platform::tvOS: + return DyldSharedCache::SHA256only; + case Platform::watchOS: + return DyldSharedCache::Agile; + case Platform::bridgeOS: + case Platform::iOSMac: + case Platform::iOS_simulator: + case Platform::tvOS_simulator: + case Platform::watchOS_simulator: + return DyldSharedCache::SHA256only; + } +} + +static bool platformIsForSimulator(Platform platform) { + switch (platform) { + case Platform::unknown: + case Platform::macOS: + case Platform::iOS: + case Platform::tvOS: + case Platform::watchOS: + case Platform::bridgeOS: + case Platform::iOSMac: + return false; + case Platform::iOS_simulator: + case Platform::tvOS_simulator: + case Platform::watchOS_simulator: + return true; + } +} + +static const char* dispositionName(Disposition disposition) { + switch (disposition) { + case Disposition::Unknown: + return ""; + case Disposition::InternalDevelopment: + return "Internal"; + case Disposition::Customer: + return "Customer"; + case Disposition::InternalMinDevelopment: + return "InternalMinDevelopment"; + } +} + +bool runSharedCacheBuilder(struct SharedCacheBuilder* builder) { + __block bool success = false; + builder->runSync(^() { + if (builder->state != SharedCacheBuilder::AcceptingFiles) { + builder->error("Builder has already been run"); + return; + } + builder->state = SharedCacheBuilder::Building; + if (builder->fileSystem.fileCount() == 0) { + builder->error("Cannot run builder with no files"); + } + + Diagnostics diag; + std::vector aliases = builder->fileSystem.getResolvedSymlinks(diag); + if (diag.hasError()) { + diag.verbose("Symlink resolver error: %s\n", diag.errorMessage().c_str()); + } + + if (!builder->errors.empty()) { + builder->error("Skipping running shared cache builder due to previous errors"); + return; + } + + __block std::vector inputFiles; + builder->fileSystem.forEachFileInfo(^(const char* path, FileFlags fileFlags) { + CacheBuilder::InputFile::State state = CacheBuilder::InputFile::Unset; + switch (fileFlags) { + case FileFlags::NoFlags: + state = CacheBuilder::InputFile::Unset; + break; + case FileFlags::MustBeInCache: + state = CacheBuilder::InputFile::MustBeIncluded; + break; + case FileFlags::ShouldBeExcludedFromCacheIfUnusedLeaf: + state = CacheBuilder::InputFile::MustBeExcludedIfUnused; + break; + case FileFlags::RequiredClosure: + state = CacheBuilder::InputFile::MustBeIncluded; + break; + case FileFlags::DylibOrderFile: + case FileFlags::DirtyDataOrderFile: + builder->error("Order files should not be in the file system"); + return; + } + inputFiles.emplace_back((CacheBuilder::InputFile){ path, state }); + }); + + auto addCacheConfiguration = ^(bool isOptimized) { + for (uint64_t i = 0; i != builder->options->numArchs; ++i) { + auto options = std::make_unique((DyldSharedCache::CreateOptions){}); + const char *cacheSuffix = (isOptimized ? "" : ".development"); + std::string runtimePath = (builder->options->platform == Platform::macOS) ? "/private/var/db/dyld/" : "/System/Library/Caches/com.apple.dyld/"; + options->outputFilePath = runtimePath + "dyld_shared_cache_" + builder->options->archs[i] + cacheSuffix; + options->outputMapFilePath = options->outputFilePath + ".map"; + options->archName = builder->options->archs[i]; + options->platform = (dyld3::Platform)builder->options->platform; + options->excludeLocalSymbols = platformExcludeLocalSymbols(builder->options->platform); + options->optimizeStubs = isOptimized; + options->optimizeObjC = true; + options->codeSigningDigestMode = platformCodeSigningDigestMode(builder->options->platform); + options->dylibsRemovedDuringMastering = true; + options->inodesAreSameAsRuntime = false; + options->cacheSupportsASLR = true; + options->forSimulator = platformIsForSimulator(builder->options->platform); + options->isLocallyBuiltCache = builder->options->isLocallyBuiltCache; + options->verbose = builder->options->verboseDiagnostics; + options->evictLeafDylibsOnOverflow = true; + options->loggingPrefix = std::string(builder->options->deviceName) + dispositionName(builder->options->disposition) + "." + builder->options->archs[i] + cacheSuffix; + options->pathPrefixes = { "" }; + options->dylibOrdering = parseOrderFile(builder->dylibOrderFileData); + options->dirtyDataSegmentOrdering = parseOrderFile(builder->dirtyDataOrderFileData); + + auto cacheBuilder = std::make_unique(*options.get(), builder->fileSystem); + builder->builders.emplace_back((BuildInstance) { std::move(options), std::move(cacheBuilder), inputFiles }); + } + }; + + // Enqueue a cache for each configuration + switch (builder->options->disposition) { + case Disposition::Unknown: + case Disposition::InternalDevelopment: + addCacheConfiguration(false); + addCacheConfiguration(true); + break; + case Disposition::Customer: + addCacheConfiguration(true); + case Disposition::InternalMinDevelopment: + addCacheConfiguration(false); + } + + // FIXME: This step can run in parallel. + for (auto& buildInstance : builder->builders) { + CacheBuilder* builder = buildInstance.builder.get(); + builder->build(buildInstance.inputFiles, aliases); + + // First put the warnings in to a vector to own them. + buildInstance.warningStrings.reserve(builder->warnings().size()); + for (const std::string& warning : builder->warnings()) + buildInstance.warningStrings.push_back(warning); + + // Then copy to a vector to reference the owner + buildInstance.warnings.reserve(buildInstance.warningStrings.size()); + for (const std::string& warning : buildInstance.warningStrings) + buildInstance.warnings.push_back(warning.c_str()); + + if (!builder->errorMessage().empty()) { + // First put the errors in to a vector to own them. + buildInstance.errorStrings.push_back(builder->errorMessage()); + + // Then copy to a vector to reference the owner + buildInstance.errors.reserve(buildInstance.errorStrings.size()); + for (const std::string& error : buildInstance.errorStrings) + buildInstance.errors.push_back(error.c_str()); + } + + if (builder->errorMessage().empty()) { + builder->writeBuffer(buildInstance.cacheData, buildInstance.cacheSize); + builder->writeMapFileBuffer(buildInstance.cacheMapData, buildInstance.cacheMapSize); + buildInstance.cdHash = builder->cdHashFirst(); + } + } + + // Now that we have run all of the builds, collect the results + // First push file results for each of the shared caches we built + __block std::map dylibsInCaches; + for (auto& buildInstance : builder->builders) { + CacheBuilder* cacheBuilder = buildInstance.builder.get(); + if (!cacheBuilder->errorMessage().empty()) + continue; + builder->fileResults.push_back((BuildFileResult) { buildInstance.options->outputFilePath, buildInstance.cacheData, buildInstance.cacheSize }); + builder->fileResults.push_back((BuildFileResult) { buildInstance.options->outputMapFilePath, buildInstance.cacheMapData, buildInstance.cacheMapSize }); + + cacheBuilder->forEachCacheDylib(^(const std::string &path) { + ++dylibsInCaches[path]; + }); + } + + // Add entries to tell us to remove all of the dylibs from disk which are in every cache. + const size_t numCaches = builder->builders.size(); + for (const auto& dylibAndCount : dylibsInCaches) { + if (dylibAndCount.second == numCaches) + builder->fileResults.push_back((BuildFileResult) { dylibAndCount.first, nullptr, 0 }); + } + + builder->state = SharedCacheBuilder::FinishedBuilding; + success = true; + }); + return success; +} + +uint64_t getErrorCount(const struct SharedCacheBuilder* builder) { + return builder->errors.size(); +} + +const char* getError(const struct SharedCacheBuilder* builder, uint64_t errorIndex) { + if (errorIndex >= builder->errors.size()) + return nullptr; + return builder->errors[errorIndex].c_str(); +} + +uint64_t getCacheResultCount(const struct SharedCacheBuilder* builder) { + return builder->builders.size(); +} + +void getCacheResult(struct SharedCacheBuilder* builder, uint64_t cacheIndex, BuildResult* result) { + if (cacheIndex >= builder->builders.size()) + return; + + BuildInstance& buildInstance = builder->builders[cacheIndex]; + + result->version = 1; + result->loggingPrefix = buildInstance.options->loggingPrefix.c_str(); + result->warnings = buildInstance.warnings.empty() ? nullptr : buildInstance.warnings.data(); + result->numWarnings = buildInstance.warnings.size(); + result->errors = buildInstance.errors.empty() ? nullptr : buildInstance.errors.data(); + result->numErrors = buildInstance.errors.size(); + result->sharedCachePath = buildInstance.options->outputFilePath.c_str(); + result->cdHash = buildInstance.cdHash.c_str(); +} + +uint64_t getFileResultCount(const struct SharedCacheBuilder* builder) { + return builder->fileResults.size(); +} + +void getFileResult(struct SharedCacheBuilder* builder, uint64_t fileIndex, FileResult* result) { + if (fileIndex >= builder->fileResults.size()) + return; + const BuildFileResult& buildFileResult = builder->fileResults[fileIndex]; + *result = (FileResult) { buildFileResult.path.c_str(), buildFileResult.data, buildFileResult.size }; +} + +void destroySharedCacheBuilder(struct SharedCacheBuilder* builder) { + delete builder; +} diff --git a/dyld3/shared-cache/mrm_shared_cache_builder.h b/dyld3/shared-cache/mrm_shared_cache_builder.h new file mode 100644 index 0000000..352255d --- /dev/null +++ b/dyld3/shared-cache/mrm_shared_cache_builder.h @@ -0,0 +1,141 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- + * + * 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 mrm_shared_cache_builder_hpp +#define mrm_shared_cache_builder_hpp + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Note, this should match PLATFORM_* values in +enum Platform { + unknown = 0, + macOS = 1, // PLATFORM_MACOS + iOS = 2, // PLATFORM_IOS + tvOS = 3, // PLATFORM_TVOS + watchOS = 4, // PLATFORM_WATCHOS + bridgeOS = 5, // PLATFORM_BRIDGEOS + iOSMac = 6, // PLATFORM_IOSMAC + iOS_simulator = 7, // PLATFORM_IOSIMULATOR + tvOS_simulator = 8, // PLATFORM_TVOSSIMULATOR + watchOS_simulator = 9 // PLATFORM_WATCHOSSIMULATOR +}; + +enum Disposition +{ + Unknown = 0, + InternalDevelopment = 1, + Customer = 2, + InternalMinDevelopment = 3 +}; + +enum FileFlags +{ + // Note these are for macho inputs + NoFlags = 0, + MustBeInCache = 1, + ShouldBeExcludedFromCacheIfUnusedLeaf = 2, + RequiredClosure = 3, + + // These are for the order files + DylibOrderFile = 100, + DirtyDataOrderFile = 101 +}; + +struct BuildOptions_v1 +{ + uint64_t version; // Future proofing, set to 1 + const char * updateName; // BuildTrain+UpdateNumber + const char * deviceName; + enum Disposition disposition; // Internal, Customer, etc. + enum Platform platform; // Enum: unknown, macOS, iOS, ... + const char ** archs; + uint64_t numArchs; + bool verboseDiagnostics; + bool isLocallyBuiltCache; +}; + +struct BuildResult { + uint64_t version; // Future proofing, set to 1 + const char* loggingPrefix; + const char ** warnings; + uint64_t numWarnings; + const char ** errors; + uint64_t numErrors; + const char* sharedCachePath; + const char* cdHash; +}; + +struct FileResult { + const char* path; + const uint8_t* data; // Owned by the cache builder. Destroyed by destroySharedCacheBuilder + uint64_t size; +}; + +struct SharedCacheBuilder; + +__API_AVAILABLE(macos(10.12)) +struct SharedCacheBuilder* createSharedCacheBuilder(const struct BuildOptions_v1* options); + +// Add a file. Returns true on success. +__API_AVAILABLE(macos(10.12)) +bool addFile(struct SharedCacheBuilder* builder, const char* path, uint8_t* data, uint64_t size, enum FileFlags fileFlags); + +__API_AVAILABLE(macos(10.12)) +bool addSymlink(struct SharedCacheBuilder* builder, const char* fromPath, const char* toPath); + +__API_AVAILABLE(macos(10.12)) +bool runSharedCacheBuilder(struct SharedCacheBuilder* builder); + +__API_AVAILABLE(macos(10.12)) +uint64_t getErrorCount(const struct SharedCacheBuilder* builder); + +__API_AVAILABLE(macos(10.12)) +const char* getError(const struct SharedCacheBuilder* builder, uint64_t errorIndex); + +__API_AVAILABLE(macos(10.12)) +uint64_t getCacheResultCount(const struct SharedCacheBuilder* builder); + +__API_AVAILABLE(macos(10.12)) +void getCacheResult(struct SharedCacheBuilder* builder, uint64_t cacheIndex, struct BuildResult* result); + +__API_AVAILABLE(macos(10.12)) +uint64_t getFileResultCount(const struct SharedCacheBuilder* builder); + +__API_AVAILABLE(macos(10.12)) +void getFileResult(struct SharedCacheBuilder* builder, uint64_t fileIndex, struct FileResult* result); + +__API_AVAILABLE(macos(10.12)) +void destroySharedCacheBuilder(struct SharedCacheBuilder* builder); + +#ifdef __cplusplus +} +#endif + +#endif /* mrm_shared_cache_builder_hpp */ diff --git a/dyld3/shared-cache/multi_dyld_shared_cache_builder.mm b/dyld3/shared-cache/multi_dyld_shared_cache_builder.mm index 8d106d1..49e58a2 100644 --- a/dyld3/shared-cache/multi_dyld_shared_cache_builder.mm +++ b/dyld3/shared-cache/multi_dyld_shared_cache_builder.mm @@ -246,7 +246,7 @@ int main(int argc, const char* argv[]) makeBoms(manifest, masterDstRoot); }); allBuildsSucceeded = build(diags, manifest, masterDstRoot, true, verbose, skipWrites, - agileChooseSHA256CdHash); + agileChooseSHA256CdHash, true, false); } manifest.write(resultPath); diff --git a/dyld3/shared-cache/update_dyld_shared_cache.cpp b/dyld3/shared-cache/update_dyld_shared_cache.cpp index 8fca51c..cf9e21a 100644 --- a/dyld3/shared-cache/update_dyld_shared_cache.cpp +++ b/dyld3/shared-cache/update_dyld_shared_cache.cpp @@ -25,9 +25,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -58,10 +58,12 @@ #include #include -#include "MachOParser.h" #include "FileUtils.h" #include "StringUtils.h" #include "DyldSharedCache.h" +#include "MachOFile.h" +#include "MachOAnalyzer.h" +#include "ClosureFileSystemPhysical.h" struct MappedMachOsByCategory { @@ -136,6 +138,7 @@ static const char* sDontUsePrefixes[] = { "/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 + "/System/Library/Developer/CoreSimulator/", // ignore Marzipan }; @@ -143,94 +146,58 @@ static bool verbose = false; -static bool addIfMachO(const std::string& pathPrefix, const std::string& runtimePath, const struct stat& statBuf, bool requireSIP, std::vector& files) +static bool addIfMachO(const dyld3::closure::FileSystem& fileSystem, 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 ) + if ( startsWith(runtimePath, "/usr/lib/system/introspection/") ) return false; + + auto warningHandler = ^(const char* msg) { + if ( verbose ) + fprintf(stderr, "update_dyld_shared_cache: warning: cannot build dlopen closure for '%s' because %s\n", runtimePath.c_str(), msg); + }; + bool result = false; - const void* wholeFile = ::mmap(NULL, statBuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if ( wholeFile != MAP_FAILED ) { + for (MappedMachOsByCategory& file : files) { 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; - } + dyld3::closure::LoadedFileInfo loadedFileInfo = dyld3::MachOAnalyzer::load(diag, fileSystem, runtimePath.c_str(), file.archName.c_str(), dyld3::Platform::macOS); + const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)loadedFileInfo.fileContent; + if ( ma != nullptr ) { + bool sipProtected = false; // isProtectedBySIP(fd); + bool issetuid = false; + if ( ma->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, ma, loadedFileInfo.sliceLen, issetuid, sipProtected, loadedFileInfo.sliceOffset, statBuf.st_mtime, statBuf.st_ino); } } - 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); + else if ( ma->canBePlacedInDyldCache(runtimePath.c_str(), ^(const char* msg) {}) ) { + // when SIP is enabled, only dylib protected by SIP can go in cache + if ( !requireSIP || sipProtected ) + file.dylibsForCache.emplace_back(runtimePath, ma, loadedFileInfo.sliceLen, issetuid, sipProtected, loadedFileInfo.sliceOffset, statBuf.st_mtime, statBuf.st_ino); + else if ( ma->canHavePrecomputedDlopenClosure(runtimePath.c_str(), warningHandler) ) + file.otherDylibsAndBundles.emplace_back(runtimePath, ma, loadedFileInfo.sliceLen, issetuid, sipProtected, loadedFileInfo.sliceOffset, statBuf.st_mtime, statBuf.st_ino); } - 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 ( ma->isDylib() ) { + std::string installName = ma->installName(); + if ( startsWith(installName, "@") && !contains(runtimePath, ".app/") && !contains(runtimePath, ".xpc/") ) { + 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()); } } - 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); + else if ( ma->canHavePrecomputedDlopenClosure(runtimePath.c_str(), warningHandler) ) { + file.otherDylibsAndBundles.emplace_back(runtimePath, ma, loadedFileInfo.sliceLen, issetuid, sipProtected, loadedFileInfo.sliceOffset, statBuf.st_mtime, statBuf.st_ino); } - result = true; } + result = true; } - if ( !usedWholeFile ) - ::munmap((void*)wholeFile, statBuf.st_size); } - ::close(fd); + return result; } @@ -244,6 +211,7 @@ static void findAllFiles(const std::vector& pathPrefixes, bool requ bool multiplePrefixes = (pathPrefixes.size() > 1); for (const std::string& prefix : pathPrefixes) { // get all files from overlay for this search dir + dyld3::closure::FileSystemPhysical fileSystem(prefix.c_str()); 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) @@ -260,7 +228,7 @@ static void findAllFiles(const std::vector& pathPrefixes, bool requ return; // if the file is mach-o, add to list - if ( addIfMachO(prefix, path, statBuf, requireSIP, files) ) { + if ( addIfMachO(fileSystem, path, statBuf, requireSIP, files) ) { if ( multiplePrefixes ) alreadyUsed.insert(path); } @@ -322,7 +290,8 @@ static void findOSFilesViaBOMS(const std::vector& pathPrefixes, boo struct stat statBuf2; std::string fullPath2 = prefix2 + runPath; if ( stat(fullPath2.c_str(), &statBuf2) == 0 ) { - addIfMachO(prefix2, runPath, statBuf2, requireSIP, files); + dyld3::closure::FileSystemPhysical fileSystem(prefix2.c_str()); + addIfMachO(fileSystem, runPath, statBuf2, requireSIP, files); runtimePathsFound.insert(runPath); break; } @@ -379,8 +348,7 @@ static bool dontCache(const std::string& volumePrefix, const std::string& archNa return true; } - dyld3::MachOParser parser(aFile.mh); - const char* installName = parser.installName(); + const char* installName = aFile.mh->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; @@ -399,6 +367,17 @@ static bool dontCache(const std::string& volumePrefix, const std::string& archNa return false; } } + // also if runtime path is a symlink to install name + std::string fullRuntime = volumePrefix + aFile.runtimePath; + if ( realpath(fullRuntime.c_str(), resolvedPath) != NULL ) { + std::string resolvedSymlink = resolvedPath; + if ( !volumePrefix.empty() ) { + resolvedSymlink = resolvedSymlink.substr(volumePrefix.size()); + } + if ( resolvedSymlink == installName ) { + 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; } @@ -411,8 +390,7 @@ static void pruneCachedDylibs(const std::string& volumePrefix, const std::unorde std::unordered_map installNameToFirstPath; for (DyldSharedCache::MappedMachO& aFile : fileSet.dylibsForCache) { - dyld3::MachOParser parser(aFile.mh); - const char* installName = parser.installName(); + const char* installName = aFile.mh->installName(); auto pos = installNameToFirstPath.find(installName); if ( pos == installNameToFirstPath.end() ) { installNameToFirstPath[installName] = aFile.runtimePath; @@ -436,11 +414,9 @@ static void pruneOtherDylibs(const std::string& volumePrefix, MappedMachOsByCate { // 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()); + [&](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 @@ -448,9 +424,8 @@ static void pruneExecutables(const std::string& volumePrefix, MappedMachOsByCate [&](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) { + aFile.mh->forEachDependentDylib(^(const char* loadPath, bool, bool, bool, uint32_t, uint32_t, bool &stop) { if ( strcmp(loadPath, "/usr/lib/libxcselect.dylib") == 0 ) isXcodeShim = true; }); @@ -539,7 +514,7 @@ static bool runningOnHaswell() } \ } while ( 0 ) -int main(int argc, const char* argv[]) +int main(int argc, const char* argv[], const char* envp[]) { std::string rootPath; std::string overlayPath; @@ -632,6 +607,31 @@ int main(int argc, const char* argv[]) overlayPath = resolvedPath; } } + + // update_dyld_shared_cache should re-exec() itself to a newer version + std::string newTool; + if ( !rootPath.empty() ) + newTool = rootPath + "/usr/bin/update_dyld_shared_cache"; + else if ( !overlayPath.empty() ) + newTool = overlayPath + "/usr/bin/update_dyld_shared_cache"; + if ( !newTool.empty() ) { + struct stat newToolStatBuf; + if ( stat(newTool.c_str(), &newToolStatBuf) == 0 ) { + char curToolPath[PATH_MAX]; + uint32_t curToolPathsize = PATH_MAX; + int result = _NSGetExecutablePath(curToolPath, &curToolPathsize); + if ( result == 0 ) { + char resolvedCurToolPath[PATH_MAX]; + if ( realpath(curToolPath, resolvedCurToolPath) != NULL ) { + // don't re-exec if we are already running that tool + if ( newTool != resolvedCurToolPath ) { + execve(newTool.c_str(), (char**)argv, (char**)envp); + } + } + } + } + } + // // pathPrefixes for three modes: // 1) no options: { "" } // search only boot volume @@ -645,6 +645,12 @@ int main(int argc, const char* argv[]) if ( cacheDir.empty() ) { + // if -cache_dir is not specified, then write() will eventually fail if we are not running as root + if ( geteuid() != 0 ) { + fprintf(stderr, "update_dyld_shared_cache: must be run as root (sudo)\n"); + return 1; + } + // write cache file into -root or -overlay directory, if used if ( rootPath != "/" ) cacheDir = rootPath + MACOSX_DYLD_SHARED_CACHE_DIR; @@ -652,11 +658,11 @@ int main(int argc, const char* argv[]) 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); + fprintf(stderr, "update_dyld_shared_cache: could not access cache dir: mkpath_np(%s) failed errno=%d\n", cacheDir.c_str(), err); return 1; } @@ -732,11 +738,33 @@ int main(int argc, const char* argv[]) std::string fullPath = prefix + runtimePath; struct stat statBuf; if ( stat(fullPath.c_str(), &statBuf) == 0 ) { + dyld3::closure::FileSystemPhysical fileSystem(prefix.c_str()); + char resolvedPath[PATH_MAX]; + if ( realpath(fullPath.c_str(), resolvedPath) != NULL ) { + std::string resolvedSymlink = resolvedPath; + if ( !rootPath.empty() ) { + resolvedSymlink = resolvedSymlink.substr(rootPath.size()); + } + if ( (runtimePath != resolvedSymlink) && !contains(runtimePath, "InputContext") ) { //HACK remove InputContext when fixed + // path requested is a symlink path, check if real path already loaded + for (const DyldSharedCache::MappedMachO& aDylibMapping : fileSet.dylibsForCache) { + if ( aDylibMapping.runtimePath == resolvedSymlink ) { + if ( verbose ) + fprintf(stderr, "verifySelfContained, redirect %s to %s\n", runtimePath.c_str(), aDylibMapping.runtimePath.c_str()); + return aDylibMapping; + } + } + } + } + std::vector mappedFiles; mappedFiles.push_back({fileSet.archName}); - if ( addIfMachO(prefix, runtimePath, statBuf, requireDylibsBeRootlessProtected, mappedFiles) ) { - if ( !mappedFiles.back().dylibsForCache.empty() ) + if ( addIfMachO(fileSystem, runtimePath, statBuf, requireDylibsBeRootlessProtected, mappedFiles) ) { + if ( !mappedFiles.back().dylibsForCache.empty() ) { + if ( verbose ) + fprintf(stderr, "verifySelfContained, add %s\n", mappedFiles.back().dylibsForCache.back().runtimePath.c_str()); return mappedFiles.back().dylibsForCache.back(); + } } } } @@ -768,19 +796,17 @@ int main(int argc, const char* argv[]) } // 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, "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.otherDylibsAndBundles) { // fprintf(stderr, " %s\n", aFile.runtimePath.c_str()); //} - // Clear the UUID xattr for the existing cache. - // This prevents the existing cache from being used by dyld3 as roots are probably involved - if (removexattr(outFile.c_str(), "cacheUUID", 0) != 0) { - fprintf(stderr, "update_dyld_shared_cache: warning: failure to remove UUID xattr on shared cache file %s with error %s\n", outFile.c_str(), strerror(errno)); - } // build cache new cache file DyldSharedCache::CreateOptions options; + options.outputFilePath = outFile; + options.outputMapFilePath = cacheDir + "/dyld_shared_cache_" + fileSet.archName + ".map"; options.archName = fileSet.archName; options.platform = dyld3::Platform::macOS; options.excludeLocalSymbols = false; @@ -791,6 +817,7 @@ int main(int argc, const char* argv[]) options.inodesAreSameAsRuntime = true; options.cacheSupportsASLR = (fileSet.archName != "i386"); options.forSimulator = false; + options.isLocallyBuiltCache = true; options.verbose = verbose; options.evictLeafDylibsOnOverflow = true; options.pathPrefixes = pathPrefixes; @@ -800,31 +827,12 @@ int main(int argc, const char* argv[]) 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; + if ( results.errorMessage.empty() ) { + wroteSomeCacheFile = true; } else { - // save new cache file to disk and write new .map file - assert(results.cacheContent != nullptr); - if ( !safeSave(results.cacheContent, results.cacheLength, outFile) ) { - fprintf(stderr, "update_dyld_shared_cache: could not write dyld cache file %s\n", outFile.c_str()); - cacheBuildFailure = true; - } - if ( !cacheBuildFailure ) { - uuid_t cacheUUID; - results.cacheContent->getUUID(cacheUUID); - if (setxattr(outFile.c_str(), "cacheUUID", (const void*)&cacheUUID, sizeof(cacheUUID), 0, XATTR_CREATE) != 0) { - fprintf(stderr, "update_dyld_shared_cache: warning: failure to set UUID xattr on shared cache file %s with error %s\n", outFile.c_str(), strerror(errno)); - } - 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); + fprintf(stderr, "update_dyld_shared_cache: %s\n", results.errorMessage.c_str()); + cacheBuildFailure = true; } }); diff --git a/dyld3/shared-cache/update_dyld_sim_shared_cache.cpp b/dyld3/shared-cache/update_dyld_sim_shared_cache.cpp index 404bdc4..31797ec 100644 --- a/dyld3/shared-cache/update_dyld_sim_shared_cache.cpp +++ b/dyld3/shared-cache/update_dyld_sim_shared_cache.cpp @@ -55,10 +55,12 @@ #include #include -#include "MachOParser.h" +#include "MachOFile.h" #include "FileUtils.h" #include "StringUtils.h" #include "DyldSharedCache.h" +#include "MachOAnalyzer.h" +#include "ClosureFileSystemPhysical.h" @@ -92,69 +94,28 @@ static const char* sMacOsAdditions[] = { 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) +static bool addIfMachO(const dyld3::closure::FileSystem& fileSystem, 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 ) { + for (MappedMachOsByCategory& file : files) { 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; - } + dyld3::closure::LoadedFileInfo loadedFileInfo = dyld3::MachOAnalyzer::load(diag, fileSystem, runtimePath.c_str(), file.archName.c_str(), dyld3::Platform::macOS); + const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)loadedFileInfo.fileContent; + if ( ma != nullptr ) { + bool sipProtected = false; // isProtectedBySIP(fd); + bool issetuid = false; + if ( ma->isDynamicExecutable() ) { + //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, ma, loadedFileInfo.sliceLen, issetuid, sipProtected, loadedFileInfo.sliceOffset, statBuf.st_mtime, statBuf.st_ino); + result = true; } + else if ( ma->canBePlacedInDyldCache(runtimePath.c_str(), ^(const char* msg) {}) ) { + file.dylibsForCache.emplace_back(runtimePath, ma, loadedFileInfo.sliceLen, issetuid, sipProtected, loadedFileInfo.sliceOffset, statBuf.st_mtime, statBuf.st_ino); + result = true; + } } - if ( !usedWholeFile ) - ::munmap((void*)wholeFile, statBuf.st_size); - } - ::close(fd); + } return result; } @@ -165,6 +126,7 @@ static void findAllFiles(const std::string& simRuntimeRootPath, dyld3::Platform skipDirs.insert(s); for (const char* searchDir : sSearchDirs ) { + dyld3::closure::FileSystemPhysical fileSystem(simRuntimeRootPath.c_str()); 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); @@ -176,17 +138,19 @@ static void findAllFiles(const std::string& simRuntimeRootPath, dyld3::Platform return; // if the file is mach-o add to list - addIfMachO(simRuntimeRootPath, path, statBuf, platform, files); + addIfMachO(fileSystem, path, statBuf, platform, files); }); } } static void addMacOSAdditions(std::vector& allFileSets) { + dyld3::closure::FileSystemPhysical fileSystem; for (const char* addPath : sMacOsAdditions) { struct stat statBuf; - if ( stat(addPath, &statBuf) == 0 ) - addIfMachO("", addPath, statBuf, dyld3::Platform::macOS, allFileSets); + if ( stat(addPath, &statBuf) == 0 ) { + addIfMachO(fileSystem, addPath, statBuf, dyld3::Platform::macOS, allFileSets); + } } } @@ -208,8 +172,7 @@ static bool dontCache(const std::string& simRuntimeRootPath, const std::string& 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(); + const char* installName = aFile.mh->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; @@ -242,8 +205,7 @@ static void pruneCachedDylibs(const std::string& simRuntimeRootPath, MappedMachO 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(); + const char* installName = aFile.mh->installName(); auto pos = installNameToFirstPath.find(installName); if ( pos == installNameToFirstPath.end() ) { installNameToFirstPath[installName] = aFile.runtimePath; @@ -351,13 +313,13 @@ int main(int argc, const char* argv[]) verbose = true; } else if (strcmp(arg, "-tvOS") == 0) { - platform = dyld3::Platform::tvOS; + platform = dyld3::Platform::tvOS_simulator; } else if (strcmp(arg, "-iOS") == 0) { - platform = dyld3::Platform::iOS; + platform = dyld3::Platform::iOS_simulator; } else if (strcmp(arg, "-watchOS") == 0) { - platform = dyld3::Platform::watchOS; + platform = dyld3::Platform::watchOS_simulator; } else if ( strcmp(arg, "-runtime_dir") == 0 ) { TERMINATE_IF_LAST_ARG("-runtime_dir missing path argument\n"); @@ -406,22 +368,28 @@ int main(int argc, const char* argv[]) if ( archStrs.empty() ) { switch ( platform ) { - case dyld3::Platform::iOS: + case dyld3::Platform::iOS_simulator: archStrs.insert("x86_64"); break; - case dyld3::Platform::tvOS: + case dyld3::Platform::tvOS_simulator: archStrs.insert("x86_64"); break; - case dyld3::Platform::watchOS: + case dyld3::Platform::watchOS_simulator: 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; + case dyld3::Platform::iOS: + case dyld3::Platform::tvOS: + case dyld3::Platform::watchOS: + case dyld3::Platform::iOSMac: + case dyld3::Platform::unknown: + assert(0 && "invalid platform"); + break; } } @@ -456,7 +424,8 @@ int main(int argc, const char* argv[]) if ( stat(fullPath.c_str(), &statBuf) == 0 ) { std::vector mappedFiles; mappedFiles.push_back({fileSet.archName}); - if ( addIfMachO(rootPath, runtimePath, statBuf, platform, mappedFiles) ) { + dyld3::closure::FileSystemPhysical fileSystem(rootPath.c_str()); + if ( addIfMachO(fileSystem, runtimePath, statBuf, platform, mappedFiles) ) { if ( !mappedFiles.back().dylibsForCache.empty() ) return mappedFiles.back().dylibsForCache.back(); } @@ -495,6 +464,8 @@ int main(int argc, const char* argv[]) // build cache new cache file DyldSharedCache::CreateOptions options; + options.outputFilePath = outFile; + options.outputMapFilePath = cacheDir + "/dyld_shared_cache_" + fileSet.archName + ".map"; options.archName = fileSet.archName; options.platform = platform; options.excludeLocalSymbols = false; @@ -505,6 +476,7 @@ int main(int argc, const char* argv[]) options.inodesAreSameAsRuntime = true; options.cacheSupportsASLR = false; options.forSimulator = true; + options.isLocallyBuiltCache = true; options.verbose = verbose; options.evictLeafDylibsOnOverflow = true; options.pathPrefixes = { rootPath }; @@ -512,26 +484,12 @@ int main(int argc, const char* argv[]) // 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()); + 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_sim_shared_cache: %s\n", results.errorMessage.c_str()); + 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); - } - // 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 diff --git a/include/mach-o/dyld.h b/include/mach-o/dyld.h index 770abd4..6ceb38b 100644 --- a/include/mach-o/dyld.h +++ b/include/mach-o/dyld.h @@ -104,17 +104,7 @@ extern void _tlv_atexit(void (*termFunc)(void* objAddr), void* objAddr) __O * 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 +extern void _tlv_bootstrap(void) __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0); /* * The following dyld API's are deprecated as of Mac OS X 10.5. They are either @@ -152,27 +142,27 @@ typedef enum { NSObjectFileImageAccess } NSObjectFileImageReturnCode; -typedef struct __NSObjectFileImage* NSObjectFileImage; +typedef struct __NSObjectFileImage* NSObjectFileImage; /* NSObjectFileImage can only be used with MH_BUNDLE files */ -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 NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* pathName, NSObjectFileImage *objectFileImage) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "dlopen()"); +extern NSObjectFileImageReturnCode NSCreateObjectFileImageFromMemory(const void *address, size_t size, NSObjectFileImage *objectFileImage) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); +extern bool NSDestroyObjectFileImage(NSObjectFileImage objectFileImage) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __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()"); +extern uint32_t NSSymbolDefinitionCountInObjectFileImage(NSObjectFileImage objectFileImage) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); +extern const char* NSSymbolDefinitionNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); +extern uint32_t NSSymbolReferenceCountInObjectFileImage(NSObjectFileImage objectFileImage) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); +extern const char* NSSymbolReferenceNameInObjectFileImage(NSObjectFileImage objectFileImage, uint32_t ordinal, bool *tentative_definition) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); +extern bool NSIsSymbolDefinedInObjectFileImage(NSObjectFileImage objectFileImage, const char* symbolName) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern void* NSGetSectionDataInObjectFileImage(NSObjectFileImage objectFileImage, const char* segmentName, const char* sectionName, size_t *size) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "getsectiondata()"); typedef struct __NSModule* NSModule; -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 const char* NSNameOfModule(NSModule m) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); +extern const char* NSLibraryNameForModule(NSModule m) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); -extern NSModule NSLinkModule(NSObjectFileImage objectFileImage, const char* moduleName, uint32_t options) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlopen()"); +extern NSModule NSLinkModule(NSObjectFileImage objectFileImage, const char* moduleName, uint32_t options) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "dlopen()"); #define NSLINKMODULE_OPTION_NONE 0x0 #define NSLINKMODULE_OPTION_BINDNOW 0x1 #define NSLINKMODULE_OPTION_PRIVATE 0x2 @@ -180,27 +170,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) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern bool NSUnLinkModule(NSModule module, uint32_t options) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __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) __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()"); +extern bool NSIsSymbolNameDefined(const char* symbolName) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern bool NSIsSymbolNameDefinedWithHint(const char* symbolName, const char* libraryNameHint) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern bool NSIsSymbolNameDefinedInImage(const struct mach_header* image, const char* symbolName) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern NSSymbol NSLookupAndBindSymbol(const char* symbolName) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern NSSymbol NSLookupAndBindSymbolWithHint(const char* symbolName, const char* libraryNameHint) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern NSSymbol NSLookupSymbolInModule(NSModule module, const char* symbolName) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "dlsym()"); +extern NSSymbol NSLookupSymbolInImage(const struct mach_header* image, const char* symbolName, uint32_t options) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __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) __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()"); +extern const char* NSNameOfSymbol(NSSymbol symbol) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); +extern void * NSAddressOfSymbol(NSSymbol symbol) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "dlsym()"); +extern NSModule NSModuleForSymbol(NSSymbol symbol) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "dladdr()"); /* error handling API */ typedef enum { @@ -228,7 +218,7 @@ typedef enum { NSOtherErrorInvalidArgs } NSOtherErrorNumbers; -extern void NSLinkEditError(NSLinkEditErrors *c, int *errorNumber, const char** fileName, const char** errorString) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, "dlerror()"); +extern void NSLinkEditError(NSLinkEditErrors *c, int *errorNumber, const char** fileName, const char** errorString) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "dlerror()"); typedef struct { void (*undefined)(const char* symbolName); @@ -237,27 +227,27 @@ typedef struct { const char* fileName, const char* errorString); } NSLinkEditErrorHandlers; -extern void NSInstallLinkEditErrorHandlers(const NSLinkEditErrorHandlers *handlers) __IOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE __OSX_DEPRECATED(10.1, 10.5, ""); +extern void NSInstallLinkEditErrorHandlers(const NSLinkEditErrorHandlers *handlers) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, ""); -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()"); +extern bool NSAddLibrary(const char* pathName) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlopen()"); +extern bool NSAddLibraryWithSearching(const char* pathName) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlopen()"); +extern const struct mach_header* NSAddImage(const char* image_name, uint32_t options) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __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) __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 bool _dyld_present(void) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "always true"); +extern bool _dyld_launched_prebound(void) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "moot"); +extern bool _dyld_all_twolevel_modules_prebound(void) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.3, 10.5, "moot"); +extern bool _dyld_bind_fully_image_containing_address(const void* address) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.5, "dlopen(RTLD_NOW)"); +extern bool _dyld_image_containing_address(const void* address) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.3, 10.5, "dladdr()"); +extern void _dyld_lookup_and_bind(const char* symbol_name, void **address, NSModule* module) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __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) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.1, 10.4, "dlsym()"); +extern void _dyld_lookup_and_bind_fully(const char* symbol_name, void** address, NSModule* module) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __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()"); +extern const struct mach_header* _dyld_get_image_header_containing_address(const void* address) __API_UNAVAILABLE(ios, tvos, watchos) __API_UNAVAILABLE(bridgeos) __OSX_DEPRECATED(10.3, 10.5, "dladdr()"); #if __cplusplus diff --git a/include/mach-o/dyld_images.h b/include/mach-o/dyld_images.h index 4b2da8f..6adb8ab 100644 --- a/include/mach-o/dyld_images.h +++ b/include/mach-o/dyld_images.h @@ -26,6 +26,11 @@ #include #include #include +#include + +#if defined(__cplusplus) && (BUILDING_LIBDYLD || BUILDING_DYLD) +#include +#endif #ifdef __cplusplus extern "C" { @@ -95,7 +100,11 @@ enum { dyld_error_kind_none=0, struct dyld_all_image_infos { uint32_t version; /* 1 in Mac OS X 10.4 and 10.5 */ uint32_t infoArrayCount; - const struct dyld_image_info* infoArray; +#if defined(__cplusplus) && (BUILDING_LIBDYLD || BUILDING_DYLD) + std::atomic infoArray; +#else + const struct dyld_image_info* infoArray; +#endif dyld_image_notifier notification; bool processDetachedFromSharedRegion; /* the following fields are only in version 2 (Mac OS X 10.6, iPhoneOS 2.0) and later */ @@ -129,7 +138,12 @@ struct dyld_all_image_infos { uint8_t sharedCacheUUID[16]; /* the following field is only in version 15 (macOS 10.12, iOS 10.0) and later */ uintptr_t sharedCacheBaseAddress; +#if defined(__cplusplus) && (BUILDING_LIBDYLD || BUILDING_DYLD) + // We want this to be atomic in libdyld so that we can see updates when we map it shared + std::atomic infoArrayChangeTimestamp; +#else uint64_t infoArrayChangeTimestamp; +#endif const char* dyldPath; mach_port_t notifyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; #if __LP64__ diff --git a/include/mach-o/dyld_priv.h b/include/mach-o/dyld_priv.h index 93f1b90..0c3f74e 100644 --- a/include/mach-o/dyld_priv.h +++ b/include/mach-o/dyld_priv.h @@ -24,11 +24,13 @@ #ifndef _MACH_O_DYLD_PRIV_H_ #define _MACH_O_DYLD_PRIV_H_ +#include #include #include #include #include #include +#include #if __cplusplus extern "C" { @@ -39,7 +41,7 @@ extern "C" { // // private interface between libSystem.dylib and dyld // -extern void _dyld_fork_child(); +extern void _dyld_fork_child(void); // DEPRECATED @@ -171,7 +173,53 @@ extern const char* dyld_image_path_containing_address(const void* addr); // Exists in Mac OS X 10.11 and later extern const struct mach_header* dyld_image_header_containing_address(const void* addr); +typedef uint32_t dyld_platform_t; +typedef struct { + dyld_platform_t platform; + uint32_t version; +} dyld_build_version_t; + +// Returns the active platform of the process +extern dyld_platform_t dyld_get_active_platform(void) __API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0), bridgeos(3.0)); + +// Base platforms are platforms that have version numbers (macOS, iOS, watchos, tvOS, bridgeOS) +// All other platforms are mapped to a base platform for version checks +extern dyld_platform_t dyld_get_base_platform(dyld_platform_t platform) __API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0), bridgeos(3.0)); + +// SPI to ask if a platform is a simulation platform +extern bool dyld_is_simulator_platform(dyld_platform_t platform) __API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0), bridgeos(3.0)); + +// Takes a version and returns if the image was built againt that SDK or newer +// In the case of multi_plaform mach-o's it tests against the active platform +extern bool dyld_sdk_at_least(const struct mach_header* mh, dyld_build_version_t version) __API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0), bridgeos(3.0)); + +// Takes a version and returns if the image was built with that minos version or newer +// In the case of multi_plaform mach-o's it tests against the active platform +extern bool dyld_minos_at_least(const struct mach_header* mh, dyld_build_version_t version) __API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0), bridgeos(3.0)); + +// Convenience versions of the previous two functions that run against the the main executable +extern bool dyld_program_sdk_at_least(dyld_build_version_t version) __API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0), bridgeos(3.0)); +extern bool dyld_program_minos_at_least(dyld_build_version_t version) __API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0), bridgeos(3.0)); + +// Function that walks through the load commands and calls the internal block for every version found +// Intended as a fallback for very complex (and rare) version checks, or for tools that need to +// print our everything for diagnostic reasons +extern void dyld_get_image_versions(const struct mach_header* mh, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version)) __API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0), bridgeos(3.0)); + +// Convienence constants for dyld version SPIs. + +//@VERSION_SET_DEFS@ + +//@MACOS_PLATFORM_VERSION_DEFS@ + +//@IOS_PLATFORM_VERSION_DEFS@ + +//@WATCHOS_PLATFORM_VERSION_DEFS@ + +//@TVOS_PLATFORM_VERSION_DEFS@ + +//@BRIDGEOS_PLATFORM_VERSION_DEFS@ // Convienence constants for return values from dyld_get_sdk_version() and friends. @@ -181,7 +229,6 @@ extern const struct mach_header* dyld_image_header_containing_address(const void //@WATCHOS_VERSION_DEFS@ - // // This finds the SDK version a binary was built against. // Returns zero on error, or if SDK version could not be determined. @@ -201,14 +248,13 @@ extern uint32_t dyld_get_sdk_version(const struct mach_header* mh); // // Exists in Mac OS X 10.8 and later // Exists in iOS 6.0 and later -extern uint32_t dyld_get_program_sdk_version(); - +extern uint32_t dyld_get_program_sdk_version(void); -#if __WATCH_OS_VERSION_MIN_REQUIRED +#if TARGET_OS_WATCH // 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() __IOS_UNAVAILABLE __OSX_UNAVAILABLE __WATCHOS_AVAILABLE(2.0); +extern uint32_t dyld_get_program_sdk_watch_os_version(void) __API_AVAILABLE(watchos(2.0)); // watchOS only. @@ -216,22 +262,21 @@ extern uint32_t dyld_get_program_sdk_watch_os_version() __IOS_UNAVAILABLE __OSX_ // 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); +extern uint32_t dyld_get_program_min_watch_os_version(void) __API_AVAILABLE(watchos(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(); +extern uint32_t dyld_get_program_sdk_bridge_os_version(void) __API_AVAILABLE(bridgeos(2.0)); // 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(); +extern uint32_t dyld_get_program_min_bridge_os_version(void) __API_AVAILABLE(bridgeos(2.0)); #endif // @@ -249,7 +294,7 @@ extern uint32_t dyld_get_min_os_version(const struct mach_header* mh); // // Exists in Mac OS X 10.8 and later // Exists in iOS 6.0 and later -extern uint32_t dyld_get_program_min_os_version(); +extern uint32_t dyld_get_program_min_os_version(void); @@ -259,7 +304,7 @@ extern uint32_t dyld_get_program_min_os_version(); // // Exists in iPhoneOS 3.1 and later // Exists in Mac OS X 10.10 and later -extern bool dyld_shared_cache_some_image_overridden(); +extern bool dyld_shared_cache_some_image_overridden(void); @@ -267,7 +312,7 @@ extern bool dyld_shared_cache_some_image_overridden(); // Returns if the process is setuid or is code signed with entitlements. // // Exists in Mac OS X 10.9 and later -extern bool dyld_process_is_restricted(); +extern bool dyld_process_is_restricted(void); @@ -275,7 +320,7 @@ extern bool dyld_process_is_restricted(); // Returns path used by dyld for standard dyld shared cache file for the current arch. // // Exists in Mac OS X 10.11 and later -extern const char* dyld_shared_cache_file_path(); +extern const char* dyld_shared_cache_file_path(void); @@ -371,6 +416,36 @@ extern const void* _dyld_get_shared_cache_range(size_t* length); + +struct dyld_image_uuid_offset { + uuid_t uuid; + uint64_t offsetInImage; + const struct mach_header* image; +}; + +// +// Given an array of addresses, returns info about each address. +// Common usage is the array or addresses was produced by a stack backtrace. +// For each address, returns the where that image was loaded, the offset +// of the address in the image, and the image's uuid. If a specified +// address is unknown to dyld, all fields will be returned a zeros. +// +// Exists in macOS 10.14 and later +// Exists in iOS 12.0 and later +extern void _dyld_images_for_addresses(unsigned count, const void* addresses[], struct dyld_image_uuid_offset infos[]); + + +// +// Lets you register a callback which is called each time an image is loaded and provides the mach_header*, path, and +// whether the image may be unloaded later. During the call to _dyld_register_for_image_loads(), the callback is called +// once for each image currently loaded. +// +// Exists in macOS 10.14 and later +// Exists in iOS 12.0 and later +extern void _dyld_register_for_image_loads(void (*func)(const struct mach_header* mh, const char* path, bool unloadable)); + + + // // When dyld must terminate a process because of a required dependent dylib // could not be loaded or a symbol is missing, dyld calls abort_with_reason() @@ -411,15 +486,15 @@ extern const char* __progname; // called by libSystem_initializer only -extern void _dyld_initializer(); +extern void _dyld_initializer(void); // never called from source code. Used by static linker to implement lazy binding -extern void dyld_stub_binder() __asm__("dyld_stub_binder"); +extern void dyld_stub_binder(void) __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(); +extern void _tlv_exit(void); // temp exports to keep tapi happy, until ASan stops using dyldVersionNumber diff --git a/include/mach-o/dyld_process_info.h b/include/mach-o/dyld_process_info.h index 7db413f..fd91ba9 100644 --- a/include/mach-o/dyld_process_info.h +++ b/include/mach-o/dyld_process_info.h @@ -27,6 +27,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -121,11 +122,11 @@ typedef const struct dyld_process_info_notify_base* dyld_process_info_notify; // extern dyld_process_info_notify _dyld_process_info_notify(task_t task, dispatch_queue_t queue, void (^notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path), - void (^notifyExit)(), + void (^notifyExit)(void), kern_return_t* kernelError); // add block to call right before main() is entered. // does nothing if process is already in main(). -extern void _dyld_process_info_notify_main(dyld_process_info_notify objc, void (^notifyMain)()); +extern void _dyld_process_info_notify_main(dyld_process_info_notify objc, void (^notifyMain)(void)); // stop notifications and invalid dyld_process_info_notify object diff --git a/include/objc-shared-cache.h b/include/objc-shared-cache.h index 6f29230..254008d 100644 --- a/include/objc-shared-cache.h +++ b/include/objc-shared-cache.h @@ -581,7 +581,7 @@ struct objc_protocolopt_t : objc_stringhash_t { objc_stringhash_offset_t *o; o = protocolOffsets(); - for (objc_stringhash_offset_t i = 0; i < capacity; i++) { + for (objc_stringhash_offset_t i = 0; i < (int)capacity; i++) { S32(o[i]); } @@ -1038,13 +1038,13 @@ static void initnorm(key *keys, ub4 nkeys, ub4 alen, ub4 blen, ub4 smax, ub8 sal // gencode *final; /* output, code for the final hash */ { ub4 loga = log2u(alen); /* log based 2 of blen */ - ub4 i; - for (i = 0; i < nkeys; i++) { + dispatch_apply(nkeys, DISPATCH_APPLY_AUTO, ^(size_t index) { + ub4 i = (ub4)index; key *mykey = keys+i; ub8 hash = lookup8(mykey->name_k, mykey->len_k, salt); mykey->a_k = (loga > 0) ? (ub4)(hash >> (UB8BITS-loga)) : 0; mykey->b_k = (blen > 1) ? (hash & (blen-1)) : 0; - } + }); } @@ -1431,7 +1431,7 @@ make_perfect(const string_map& strings) ub8 salt; /* a parameter to the hash function */ ub4 scramble[SCRAMBLE_LEN]; /* used in final hash function */ int ok; - int i; + uint32_t i; perfect_hash result; /* read in the list of keywords */ diff --git a/launch-cache/Architectures.hpp b/launch-cache/Architectures.hpp index fe3eea4..2da76e5 100644 --- a/launch-cache/Architectures.hpp +++ b/launch-cache/Architectures.hpp @@ -54,6 +54,12 @@ struct arm64 }; +struct arm64_32 +{ + typedef Pointer32 P; + +}; + diff --git a/launch-cache/CacheFileAbstraction.hpp b/launch-cache/CacheFileAbstraction.hpp index b1ac76c..e229bb3 100644 --- a/launch-cache/CacheFileAbstraction.hpp +++ b/launch-cache/CacheFileAbstraction.hpp @@ -367,6 +367,30 @@ private: }; +template +class dyldCacheSlideInfo3 { +public: + uint32_t version() const INLINE { return E::get32(fields.version); } + void set_version(uint32_t value) INLINE { E::set32(fields.version, value); } + + uint32_t page_starts_count() const INLINE { return E::get32(fields.page_starts_count); } + void set_page_starts_count(uint32_t value) INLINE { E::set32(fields.page_starts_count, value); } + + uint32_t page_size() const INLINE { return E::get32(fields.page_size); } + void set_page_size(uint32_t value) INLINE { E::set32(fields.page_size, value); } + + uint64_t auth_value_add() const INLINE { return E::get64(fields.auth_value_add); } + void set_auth_value_add(uint64_t value) INLINE { E::set64(fields.auth_value_add, value); } + + uint16_t page_starts(unsigned index) const INLINE { return E::get16(fields.page_starts[index]); } + void set_page_starts(unsigned index, uint16_t value) INLINE { E::set16(fields.page_starts[index], value); } + + +private: + dyld_cache_slide_info3 fields; +}; + + template class dyldCacheLocalSymbolsInfo { diff --git a/launch-cache/MachOFileAbstraction.hpp b/launch-cache/MachOFileAbstraction.hpp index ebd298d..c0c9550 100644 --- a/launch-cache/MachOFileAbstraction.hpp +++ b/launch-cache/MachOFileAbstraction.hpp @@ -68,6 +68,17 @@ struct uuid_command { #define CPU_TYPE_ARM64 ((cpu_type_t) (CPU_TYPE_ARM | CPU_ARCH_ABI64)) #endif +#ifndef CPU_ARCH_ABI64_32 + #define CPU_ARCH_ABI64_32 ((cpu_type_t) 0x02000000) +#endif +#ifndef CPU_TYPE_ARM64_32 + #define CPU_TYPE_ARM64_32 ((cpu_type_t) (CPU_TYPE_ARM | CPU_ARCH_ABI64_32)) +#endif +#ifndef CPU_SUBTYPE_ARM64_32_V8 + #define CPU_SUBTYPE_ARM64_32_V8 ((cpu_subtype_t) 1) +#endif + + #define ARM64_RELOC_UNSIGNED 0 // for pointers @@ -956,7 +967,7 @@ inline int64_t read_sleb128(const uint8_t*& p, const uint8_t* end) } while (byte & 0x80); // sign extend negative numbers if ( (byte & 0x40) != 0 ) - result |= (-1LL) << bit; + result |= (~0ULL) << bit; return result; } diff --git a/launch-cache/dsc_extractor.cpp b/launch-cache/dsc_extractor.cpp index e55d74a..c36ab2c 100644 --- a/launch-cache/dsc_extractor.cpp +++ b/launch-cache/dsc_extractor.cpp @@ -1,16 +1,16 @@ -/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- * * Copyright (c) 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, @@ -18,7 +18,7 @@ * 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@ */ @@ -38,7 +38,12 @@ #include #include -#define NO_ULEB +#include "CodeSigningTypes.h" +#include +#include +#include + +#define NO_ULEB #include "Architectures.hpp" #include "MachOFileAbstraction.hpp" #include "CacheFileAbstraction.hpp" @@ -46,6 +51,8 @@ #include "dsc_iterator.h" #include "dsc_extractor.h" #include "MachOTrie.hpp" +#include "SupportedArchs.h" +#include "DyldSharedCache.h" #include #include @@ -56,601 +63,852 @@ struct seg_info { - seg_info(const char* n, uint64_t o, uint64_t s) - : segName(n), offset(o), sizem(s) { } - const char* segName; - uint64_t offset; - uint64_t sizem; + seg_info(const char* n, uint64_t o, uint64_t s) + : segName(n), offset(o), sizem(s) { } + const char* segName; + uint64_t offset; + uint64_t sizem; }; class CStringHash { public: - size_t operator()(const char* __s) const { - size_t __h = 0; - for ( ; *__s; ++__s) - __h = 5 * __h + *__s; - return __h; - }; + size_t operator()(const char* __s) const { + size_t __h = 0; + for ( ; *__s; ++__s) + __h = 5 * __h + *__s; + return __h; + }; }; class CStringEquals { public: - bool operator()(const char* left, const char* right) const { return (strcmp(left, right) == 0); } + bool operator()(const char* left, const char* right) const { return (strcmp(left, right) == 0); } }; typedef std::unordered_map, CStringHash, CStringEquals> NameToSegments; // Filter to find individual symbol re-exports in trie class NotReExportSymbol { public: - NotReExportSymbol(const std::set &rd) :_reexportDeps(rd) {} - bool operator()(const mach_o::trie::Entry &entry) const { - bool result = isSymbolReExport(entry); - if (result) { - // Xcode 6 leaks in dyld_shared_cache_extract_dylibs - ::free((void*)entry.name); - const_cast(&entry)->name = NULL; - } - return result; - } + NotReExportSymbol(const std::set &rd) :_reexportDeps(rd) {} + bool operator()(const mach_o::trie::Entry &entry) const { + bool result = isSymbolReExport(entry); + if (result) { + // Xcode 6 leaks in dyld_shared_cache_extract_dylibs + ::free((void*)entry.name); + const_cast(&entry)->name = NULL; + } + return result; + } private: - bool isSymbolReExport(const mach_o::trie::Entry &entry) const { - if ( (entry.flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) != EXPORT_SYMBOL_FLAGS_KIND_REGULAR ) - return true; - if ( (entry.flags & EXPORT_SYMBOL_FLAGS_REEXPORT) == 0 ) - return true; - // If the symbol comes from a dylib that is re-exported, this is not an individual symbol re-export - if ( _reexportDeps.count((int)entry.other) != 0 ) - return true; - return false; - } - const std::set &_reexportDeps; + bool isSymbolReExport(const mach_o::trie::Entry &entry) const { + if ( (entry.flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) != EXPORT_SYMBOL_FLAGS_KIND_REGULAR ) + return true; + if ( (entry.flags & EXPORT_SYMBOL_FLAGS_REEXPORT) == 0 ) + return true; + // If the symbol comes from a dylib that is re-exported, this is not an individual symbol re-export + if ( _reexportDeps.count((int)entry.other) != 0 ) + return true; + return false; + } + const std::set &_reexportDeps; }; +template +struct LoadCommandInfo { +}; template -int optimize_linkedit(macho_header* mh, uint64_t textOffsetInCache, const void* mapped_cache, uint64_t* newSize) -{ - typedef typename A::P P; - typedef typename A::P::E E; +class LinkeditOptimizer { + typedef typename A::P P; + typedef typename A::P::E E; typedef typename A::P::uint_t pint_t; - // update header flags - mh->set_flags(mh->flags() & 0x7FFFFFFF); // remove in-cache bit - - // update load commands - uint64_t cumulativeFileSize = 0; - const unsigned origLoadCommandsSize = mh->sizeofcmds(); - unsigned bytesRemaining = origLoadCommandsSize; - unsigned removedCount = 0; - const macho_load_command

* const cmds = (macho_load_command

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

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

* cmd = cmds; - macho_segment_command

* linkEditSegCmd = NULL; - macho_symtab_command

* symtab = NULL; - macho_dysymtab_command

* dynamicSymTab = NULL; - macho_linkedit_data_command

* functionStarts = NULL; - macho_linkedit_data_command

* dataInCode = NULL; - uint32_t exportsTrieOffset = 0; - uint32_t exportsTrieSize = 0; - std::set reexportDeps; - int depIndex = 0; - for (uint32_t i = 0; i < cmdCount; ++i) { - bool remove = false; - switch ( cmd->cmd() ) { - case macho_segment_command

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

* segCmd = (macho_segment_command

*)cmd; - segCmd->set_fileoff(cumulativeFileSize); - 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) { - if ( sect->offset() != 0 ) - sect->set_offset((uint32_t)(cumulativeFileSize+sect->addr()-segCmd->vmaddr())); - } - if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { - linkEditSegCmd = segCmd; - } - cumulativeFileSize += segCmd->filesize(); - } - break; - case LC_DYLD_INFO_ONLY: - { - // zero out all dyld info - macho_dyld_info_command

* dyldInfo = (macho_dyld_info_command

*)cmd; - exportsTrieOffset = dyldInfo->export_off(); - exportsTrieSize = dyldInfo->export_size(); - dyldInfo->set_rebase_off(0); - dyldInfo->set_rebase_size(0); - dyldInfo->set_bind_off(0); - dyldInfo->set_bind_size(0); - dyldInfo->set_weak_bind_off(0); - dyldInfo->set_weak_bind_size(0); - dyldInfo->set_lazy_bind_off(0); - dyldInfo->set_lazy_bind_size(0); - dyldInfo->set_export_off(0); - dyldInfo->set_export_size(0); - } - break; - case LC_SYMTAB: - symtab = (macho_symtab_command

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

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

*)cmd; - break; - case LC_DATA_IN_CODE: - dataInCode = (macho_linkedit_data_command

*)cmd; - break; - case LC_LOAD_DYLIB: - case LC_LOAD_WEAK_DYLIB: - case LC_REEXPORT_DYLIB: - case LC_LOAD_UPWARD_DYLIB: - ++depIndex; - if ( cmd->cmd() == LC_REEXPORT_DYLIB ) { - reexportDeps.insert(depIndex); - } - break; - case LC_SEGMENT_SPLIT_INFO: - // dylibs iOS 9 dyld caches have bogus LC_SEGMENT_SPLIT_INFO - remove = true; - break; - } - uint32_t cmdSize = cmd->cmdsize(); - macho_load_command

* nextCmd = (macho_load_command

*)(((uint8_t*)cmd)+cmdSize); - if ( remove ) { - ::memmove((void*)cmd, (void*)nextCmd, bytesRemaining); - ++removedCount; - } - else { - bytesRemaining -= cmdSize; - cmd = nextCmd; - } - } - // zero out stuff removed - ::bzero((void*)cmd, bytesRemaining); - // update header - mh->set_ncmds(cmdCount - removedCount); - mh->set_sizeofcmds(origLoadCommandsSize - bytesRemaining); - - // rebuild symbol table - if ( linkEditSegCmd == NULL ) { - fprintf(stderr, "__LINKEDIT not found\n"); - return -1; - } - if ( symtab == NULL ) { - fprintf(stderr, "LC_SYMTAB not found\n"); - return -1; - } - if ( dynamicSymTab == NULL ) { - fprintf(stderr, "LC_DYSYMTAB not found\n"); - return -1; - } - - const uint64_t newFunctionStartsOffset = linkEditSegCmd->fileoff(); - uint32_t functionStartsSize = 0; - if ( functionStarts != NULL ) { - // copy function starts from original cache file to new mapped dylib file - functionStartsSize = functionStarts->datasize(); - memcpy((char*)mh + newFunctionStartsOffset, (char*)mapped_cache + functionStarts->dataoff(), functionStartsSize); - } - const uint64_t newDataInCodeOffset = (newFunctionStartsOffset + functionStartsSize + sizeof(pint_t) - 1) & (-sizeof(pint_t)); // pointer align - uint32_t dataInCodeSize = 0; - if ( dataInCode != NULL ) { - // copy data-in-code info from original cache file to new mapped dylib file - dataInCodeSize = dataInCode->datasize(); - memcpy((char*)mh + newDataInCodeOffset, (char*)mapped_cache + dataInCode->dataoff(), dataInCodeSize); - } - - std::vector exports; - if ( exportsTrieSize != 0 ) { - const uint8_t* exportsStart = ((uint8_t*)mapped_cache) + exportsTrieOffset; - const uint8_t* exportsEnd = &exportsStart[exportsTrieSize]; - mach_o::trie::parseTrie(exportsStart, exportsEnd, exports); - exports.erase(std::remove_if(exports.begin(), exports.end(), NotReExportSymbol(reexportDeps)), exports.end()); - } - - // look for local symbol info in unmapped part of shared cache - dyldCacheHeader* header = (dyldCacheHeader*)mapped_cache; - macho_nlist

* localNlists = NULL; - uint32_t localNlistCount = 0; - const char* localStrings = NULL; - const char* localStringsEnd = NULL; - if ( header->mappingOffset() > offsetof(dyld_cache_header,localSymbolsSize) ) { - dyldCacheLocalSymbolsInfo* localInfo = (dyldCacheLocalSymbolsInfo*)(((uint8_t*)mapped_cache) + header->localSymbolsOffset()); - dyldCacheLocalSymbolEntry* entries = (dyldCacheLocalSymbolEntry*)(((uint8_t*)mapped_cache) + header->localSymbolsOffset() + localInfo->entriesOffset()); - macho_nlist

* allLocalNlists = (macho_nlist

*)(((uint8_t*)localInfo) + localInfo->nlistOffset()); - const uint32_t entriesCount = localInfo->entriesCount(); - for (uint32_t i=0; i < entriesCount; ++i) { - if ( entries[i].dylibOffset() == textOffsetInCache ) { - uint32_t localNlistStart = entries[i].nlistStartIndex(); - localNlistCount = entries[i].nlistCount(); - localNlists = &allLocalNlists[localNlistStart]; - localStrings = ((char*)localInfo) + localInfo->stringsOffset(); - localStringsEnd = &localStrings[localInfo->stringsSize()]; - break; - } - } - } - // compute number of symbols in new symbol table - const macho_nlist

* const mergedSymTabStart = (macho_nlist

*)(((uint8_t*)mapped_cache) + symtab->symoff()); - const macho_nlist

* const mergedSymTabend = &mergedSymTabStart[symtab->nsyms()]; - uint32_t newSymCount = symtab->nsyms(); - if ( localNlists != NULL ) { - newSymCount = localNlistCount; - for (const macho_nlist

* s = mergedSymTabStart; s != mergedSymTabend; ++s) { - // skip any locals in cache - if ( (s->n_type() & (N_TYPE|N_EXT)) == N_SECT ) - continue; - ++newSymCount; - } - } - - // add room for N_INDR symbols for re-exported symbols - newSymCount += exports.size(); - - // copy symbol entries and strings from original cache file to new mapped dylib file - const uint64_t newSymTabOffset = (newDataInCodeOffset + dataInCodeSize + sizeof(pint_t) - 1) & (-sizeof(pint_t)); // pointer align - const uint64_t newIndSymTabOffset = newSymTabOffset + newSymCount*sizeof(macho_nlist

); - const uint64_t newStringPoolOffset = newIndSymTabOffset + dynamicSymTab->nindirectsyms()*sizeof(uint32_t); - macho_nlist

* const newSymTabStart = (macho_nlist

*)(((uint8_t*)mh) + newSymTabOffset); - char* const newStringPoolStart = (char*)mh + newStringPoolOffset; - const uint32_t* mergedIndSymTab = (uint32_t*)((char*)mapped_cache + dynamicSymTab->indirectsymoff()); - const char* mergedStringPoolStart = (char*)mapped_cache + symtab->stroff(); - const char* mergedStringPoolEnd = &mergedStringPoolStart[symtab->strsize()]; - macho_nlist

* t = newSymTabStart; - int poolOffset = 0; - uint32_t symbolsCopied = 0; - newStringPoolStart[poolOffset++] = '\0'; // first pool entry is always empty string - for (const macho_nlist

* s = mergedSymTabStart; s != mergedSymTabend; ++s) { - // if we have better local symbol info, skip any locals here - if ( (localNlists != NULL) && ((s->n_type() & (N_TYPE|N_EXT)) == N_SECT) ) - continue; - *t = *s; - t->set_n_strx(poolOffset); - const char* symName = &mergedStringPoolStart[s->n_strx()]; - if ( symName > mergedStringPoolEnd ) - symName = ""; - strcpy(&newStringPoolStart[poolOffset], symName); - poolOffset += (strlen(symName) + 1); - ++t; - ++symbolsCopied; - } - // recreate N_INDR symbols in extracted dylibs for debugger - for (std::vector::iterator it = exports.begin(); it != exports.end(); ++it) { - strcpy(&newStringPoolStart[poolOffset], it->name); - t->set_n_strx(poolOffset); - poolOffset += (strlen(it->name) + 1); - t->set_n_type(N_INDR | N_EXT); - t->set_n_sect(0); - t->set_n_desc(0); - const char* importName = it->importName; - if ( *importName == '\0' ) - importName = it->name; - strcpy(&newStringPoolStart[poolOffset], importName); - t->set_n_value(poolOffset); - poolOffset += (strlen(importName) + 1); - ++t; - ++symbolsCopied; - } - if ( localNlists != NULL ) { - // update load command to reflect new count of locals - dynamicSymTab->set_ilocalsym(symbolsCopied); - dynamicSymTab->set_nlocalsym(localNlistCount); - // copy local symbols - for (uint32_t i=0; i < localNlistCount; ++i) { - const char* localName = &localStrings[localNlists[i].n_strx()]; - if ( localName > localStringsEnd ) - localName = ""; - *t = localNlists[i]; - t->set_n_strx(poolOffset); - strcpy(&newStringPoolStart[poolOffset], localName); - poolOffset += (strlen(localName) + 1); - ++t; - ++symbolsCopied; - } - } - - if ( newSymCount != symbolsCopied ) { - fprintf(stderr, "symbol count miscalculation\n"); - return -1; - } - - // pointer align string pool size - while ( (poolOffset % sizeof(pint_t)) != 0 ) - ++poolOffset; - // copy indirect symbol table - uint32_t* newIndSymTab = (uint32_t*)((char*)mh + newIndSymTabOffset); - memcpy(newIndSymTab, mergedIndSymTab, dynamicSymTab->nindirectsyms()*sizeof(uint32_t)); - - // update load commands - if ( functionStarts != NULL ) { - functionStarts->set_dataoff((uint32_t)newFunctionStartsOffset); - functionStarts->set_datasize(functionStartsSize); - } - if ( dataInCode != NULL ) { - dataInCode->set_dataoff((uint32_t)newDataInCodeOffset); - dataInCode->set_datasize(dataInCodeSize); - } - symtab->set_nsyms(symbolsCopied); - symtab->set_symoff((uint32_t)newSymTabOffset); - symtab->set_stroff((uint32_t)newStringPoolOffset); - symtab->set_strsize(poolOffset); - dynamicSymTab->set_extreloff(0); - dynamicSymTab->set_nextrel(0); - dynamicSymTab->set_locreloff(0); - dynamicSymTab->set_nlocrel(0); - dynamicSymTab->set_indirectsymoff((uint32_t)newIndSymTabOffset); - linkEditSegCmd->set_filesize(symtab->stroff()+symtab->strsize() - linkEditSegCmd->fileoff()); - linkEditSegCmd->set_vmsize( (linkEditSegCmd->filesize()+4095) & (-4096) ); - - // return new size - *newSize = (symtab->stroff()+symtab->strsize()+4095) & (-4096); - - // Xcode 6 leaks in dyld_shared_cache_extract_dylibs - for (std::vector::iterator it = exports.begin(); it != exports.end(); ++it) { - ::free((void*)(it->name)); - } - - - return 0; -} +private: + macho_segment_command

* linkEditSegCmd = NULL; + macho_symtab_command

* symtab = NULL; + macho_dysymtab_command

* dynamicSymTab = NULL; + macho_linkedit_data_command

* functionStarts = NULL; + macho_linkedit_data_command

* dataInCode = NULL; + uint32_t exportsTrieOffset = 0; + uint32_t exportsTrieSize = 0; + std::set reexportDeps; + +public: + + void optimize_loadcommands(macho_header* mh) + { + typedef typename A::P P; + typedef typename A::P::E E; + typedef typename A::P::uint_t pint_t; + + // update header flags + mh->set_flags(mh->flags() & 0x7FFFFFFF); // remove in-cache bit + + // update load commands + uint64_t cumulativeFileSize = 0; + const unsigned origLoadCommandsSize = mh->sizeofcmds(); + unsigned bytesRemaining = origLoadCommandsSize; + unsigned removedCount = 0; + const macho_load_command

* const cmds = (macho_load_command

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

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

* cmd = cmds; + int depIndex = 0; + for (uint32_t i = 0; i < cmdCount; ++i) { + bool remove = false; + switch ( cmd->cmd() ) { + case macho_segment_command

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

* segCmd = (macho_segment_command

*)cmd; + segCmd->set_fileoff(cumulativeFileSize); + segCmd->set_filesize(segCmd->vmsize()); + 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) { + if ( sect->offset() != 0 ) + sect->set_offset((uint32_t)(cumulativeFileSize+sect->addr()-segCmd->vmaddr())); + } + if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) { + linkEditSegCmd = segCmd; + } + cumulativeFileSize += segCmd->filesize(); + break; + } + case LC_DYLD_INFO_ONLY: + { + // zero out all dyld info + macho_dyld_info_command

* dyldInfo = (macho_dyld_info_command

*)cmd; + exportsTrieOffset = dyldInfo->export_off(); + exportsTrieSize = dyldInfo->export_size(); + dyldInfo->set_rebase_off(0); + dyldInfo->set_rebase_size(0); + dyldInfo->set_bind_off(0); + dyldInfo->set_bind_size(0); + dyldInfo->set_weak_bind_off(0); + dyldInfo->set_weak_bind_size(0); + dyldInfo->set_lazy_bind_off(0); + dyldInfo->set_lazy_bind_size(0); + dyldInfo->set_export_off(0); + dyldInfo->set_export_size(0); + } + break; + case LC_SYMTAB: + symtab = (macho_symtab_command

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

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

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

*)cmd; + break; + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + ++depIndex; + if ( cmd->cmd() == LC_REEXPORT_DYLIB ) { + reexportDeps.insert(depIndex); + } + break; + case LC_SEGMENT_SPLIT_INFO: + // dylibs iOS 9 dyld caches have bogus LC_SEGMENT_SPLIT_INFO + remove = true; + break; + } + uint32_t cmdSize = cmd->cmdsize(); + macho_load_command

* nextCmd = (macho_load_command

*)(((uint8_t*)cmd)+cmdSize); + if ( remove ) { + ::memmove((void*)cmd, (void*)nextCmd, bytesRemaining); + ++removedCount; + } + else { + bytesRemaining -= cmdSize; + cmd = nextCmd; + } + } + // zero out stuff removed + ::bzero((void*)cmd, bytesRemaining); + // update header + mh->set_ncmds(cmdCount - removedCount); + mh->set_sizeofcmds(origLoadCommandsSize - bytesRemaining); + } + int optimize_linkedit(std::vector &new_linkedit_data, uint64_t textOffsetInCache, const void* mapped_cache) + { + typedef typename A::P P; + typedef typename A::P::E E; + typedef typename A::P::uint_t pint_t; + + // rebuild symbol table + if ( linkEditSegCmd == NULL ) { + fprintf(stderr, "__LINKEDIT not found\n"); + return -1; + } + if ( symtab == NULL ) { + fprintf(stderr, "LC_SYMTAB not found\n"); + return -1; + } + if ( dynamicSymTab == NULL ) { + fprintf(stderr, "LC_DYSYMTAB not found\n"); + return -1; + } + + const uint64_t newFunctionStartsOffset = new_linkedit_data.size(); + uint32_t functionStartsSize = 0; + if ( functionStarts != NULL ) { + // copy function starts from original cache file to new mapped dylib file + functionStartsSize = functionStarts->datasize(); + new_linkedit_data.insert(new_linkedit_data.end(), + (char*)mapped_cache + functionStarts->dataoff(), + (char*)mapped_cache + functionStarts->dataoff() + functionStartsSize); + } + + // pointer align + while ((linkEditSegCmd->fileoff() + new_linkedit_data.size()) % sizeof(pint_t)) + new_linkedit_data.push_back(0); + + const uint64_t newDataInCodeOffset = new_linkedit_data.size(); + uint32_t dataInCodeSize = 0; + if ( dataInCode != NULL ) { + // copy data-in-code info from original cache file to new mapped dylib file + dataInCodeSize = dataInCode->datasize(); + new_linkedit_data.insert(new_linkedit_data.end(), + (char*)mapped_cache + dataInCode->dataoff(), + (char*)mapped_cache + dataInCode->dataoff() + dataInCodeSize); + } + + std::vector exports; + if ( exportsTrieSize != 0 ) { + const uint8_t* exportsStart = ((uint8_t*)mapped_cache) + exportsTrieOffset; + const uint8_t* exportsEnd = &exportsStart[exportsTrieSize]; + mach_o::trie::parseTrie(exportsStart, exportsEnd, exports); + exports.erase(std::remove_if(exports.begin(), exports.end(), NotReExportSymbol(reexportDeps)), exports.end()); + } + + // look for local symbol info in unmapped part of shared cache + dyldCacheHeader* header = (dyldCacheHeader*)mapped_cache; + macho_nlist

* localNlists = NULL; + uint32_t localNlistCount = 0; + const char* localStrings = NULL; + const char* localStringsEnd = NULL; + if ( header->mappingOffset() > offsetof(dyld_cache_header,localSymbolsSize) ) { + dyldCacheLocalSymbolsInfo* localInfo = (dyldCacheLocalSymbolsInfo*)(((uint8_t*)mapped_cache) + header->localSymbolsOffset()); + dyldCacheLocalSymbolEntry* entries = (dyldCacheLocalSymbolEntry*)(((uint8_t*)mapped_cache) + header->localSymbolsOffset() + localInfo->entriesOffset()); + macho_nlist

* allLocalNlists = (macho_nlist

*)(((uint8_t*)localInfo) + localInfo->nlistOffset()); + const uint32_t entriesCount = localInfo->entriesCount(); + for (uint32_t i=0; i < entriesCount; ++i) { + if ( entries[i].dylibOffset() == textOffsetInCache ) { + uint32_t localNlistStart = entries[i].nlistStartIndex(); + localNlistCount = entries[i].nlistCount(); + localNlists = &allLocalNlists[localNlistStart]; + localStrings = ((char*)localInfo) + localInfo->stringsOffset(); + localStringsEnd = &localStrings[localInfo->stringsSize()]; + break; + } + } + } + // compute number of symbols in new symbol table + const macho_nlist

* const mergedSymTabStart = (macho_nlist

*)(((uint8_t*)mapped_cache) + symtab->symoff()); + const macho_nlist

* const mergedSymTabend = &mergedSymTabStart[symtab->nsyms()]; + uint32_t newSymCount = symtab->nsyms(); + if ( localNlists != NULL ) { + newSymCount = localNlistCount; + for (const macho_nlist

* s = mergedSymTabStart; s != mergedSymTabend; ++s) { + // skip any locals in cache + if ( (s->n_type() & (N_TYPE|N_EXT)) == N_SECT ) + continue; + ++newSymCount; + } + } + + // add room for N_INDR symbols for re-exported symbols + newSymCount += exports.size(); + + // copy symbol entries and strings from original cache file to new mapped dylib file + const char* mergedStringPoolStart = (char*)mapped_cache + symtab->stroff(); + const char* mergedStringPoolEnd = &mergedStringPoolStart[symtab->strsize()]; + + // First count how many entries we need + std::vector> newSymTab; + newSymTab.reserve(newSymCount); + std::vector newSymNames; + + // first pool entry is always empty string + newSymNames.push_back('\0'); + + for (const macho_nlist

* s = mergedSymTabStart; s != mergedSymTabend; ++s) { + // if we have better local symbol info, skip any locals here + if ( (localNlists != NULL) && ((s->n_type() & (N_TYPE|N_EXT)) == N_SECT) ) + continue; + macho_nlist

t = *s; + t.set_n_strx((uint32_t)newSymNames.size()); + const char* symName = &mergedStringPoolStart[s->n_strx()]; + if ( symName > mergedStringPoolEnd ) + symName = ""; + newSymNames.insert(newSymNames.end(), + symName, + symName + (strlen(symName) + 1)); + newSymTab.push_back(t); + } + // recreate N_INDR symbols in extracted dylibs for debugger + for (std::vector::iterator it = exports.begin(); it != exports.end(); ++it) { + macho_nlist

t; + memset(&t, 0, sizeof(t)); + t.set_n_strx((uint32_t)newSymNames.size()); + t.set_n_type(N_INDR | N_EXT); + t.set_n_sect(0); + t.set_n_desc(0); + newSymNames.insert(newSymNames.end(), + it->name, + it->name + (strlen(it->name) + 1)); + const char* importName = it->importName; + if ( *importName == '\0' ) + importName = it->name; + t.set_n_value(newSymNames.size()); + newSymNames.insert(newSymNames.end(), + importName, + importName + (strlen(importName) + 1)); + newSymTab.push_back(t); + } + if ( localNlists != NULL ) { + // update load command to reflect new count of locals + dynamicSymTab->set_ilocalsym((uint32_t)newSymTab.size()); + dynamicSymTab->set_nlocalsym(localNlistCount); + // copy local symbols + for (uint32_t i=0; i < localNlistCount; ++i) { + const char* localName = &localStrings[localNlists[i].n_strx()]; + if ( localName > localStringsEnd ) + localName = ""; + macho_nlist

t = localNlists[i]; + t.set_n_strx((uint32_t)newSymNames.size()); + newSymNames.insert(newSymNames.end(), + localName, + localName + (strlen(localName) + 1)); + newSymTab.push_back(t); + } + } + + if ( newSymCount != newSymTab.size() ) { + fprintf(stderr, "symbol count miscalculation\n"); + return -1; + } + + //const uint64_t newStringPoolOffset = newIndSymTabOffset + dynamicSymTab->nindirectsyms()*sizeof(uint32_t); + //macho_nlist

* const newSymTabStart = (macho_nlist

*)(((uint8_t*)mh) + newSymTabOffset); + //char* const newStringPoolStart = (char*)mh + newStringPoolOffset; + + // pointer align + while ((linkEditSegCmd->fileoff() + new_linkedit_data.size()) % sizeof(pint_t)) + new_linkedit_data.push_back(0); + + const uint64_t newSymTabOffset = new_linkedit_data.size(); + + // Copy sym tab + for (macho_nlist

& sym : newSymTab) { + uint8_t symData[sizeof(macho_nlist

)]; + memcpy(&symData, &sym, sizeof(sym)); + new_linkedit_data.insert(new_linkedit_data.end(), &symData[0], &symData[sizeof(macho_nlist

)]); + } + + const uint64_t newIndSymTabOffset = new_linkedit_data.size(); + + // Copy indirect symbol table + const uint32_t* mergedIndSymTab = (uint32_t*)((char*)mapped_cache + dynamicSymTab->indirectsymoff()); + new_linkedit_data.insert(new_linkedit_data.end(), + (char*)mergedIndSymTab, + (char*)(mergedIndSymTab + dynamicSymTab->nindirectsyms())); + + const uint64_t newStringPoolOffset = new_linkedit_data.size(); + + // pointer align string pool size + while (newSymNames.size() % sizeof(pint_t)) + newSymNames.push_back('\0'); + + new_linkedit_data.insert(new_linkedit_data.end(), newSymNames.begin(), newSymNames.end()); + + // update load commands + if ( functionStarts != NULL ) { + functionStarts->set_dataoff((uint32_t)(newFunctionStartsOffset + linkEditSegCmd->fileoff())); + functionStarts->set_datasize(functionStartsSize); + } + if ( dataInCode != NULL ) { + dataInCode->set_dataoff((uint32_t)(newDataInCodeOffset + linkEditSegCmd->fileoff())); + dataInCode->set_datasize(dataInCodeSize); + } + + symtab->set_nsyms(newSymCount); + symtab->set_symoff((uint32_t)(newSymTabOffset + linkEditSegCmd->fileoff())); + symtab->set_stroff((uint32_t)(newStringPoolOffset + linkEditSegCmd->fileoff())); + symtab->set_strsize((uint32_t)newSymNames.size()); + dynamicSymTab->set_extreloff(0); + dynamicSymTab->set_nextrel(0); + dynamicSymTab->set_locreloff(0); + dynamicSymTab->set_nlocrel(0); + dynamicSymTab->set_indirectsymoff((uint32_t)(newIndSymTabOffset + linkEditSegCmd->fileoff())); + linkEditSegCmd->set_filesize(symtab->stroff()+symtab->strsize() - linkEditSegCmd->fileoff()); + linkEditSegCmd->set_vmsize( (linkEditSegCmd->filesize()+4095) & (-4096) ); + + // Xcode 6 leaks in dyld_shared_cache_extract_dylibs + for (std::vector::iterator it = exports.begin(); it != exports.end(); ++it) { + ::free((void*)(it->name)); + } + + + return 0; + } +}; -static void make_dirs(const char* file_path) +static void make_dirs(const char* file_path) { - //printf("make_dirs(%s)\n", file_path); - char dirs[strlen(file_path)+1]; - strcpy(dirs, file_path); - char* lastSlash = strrchr(dirs, '/'); - if ( lastSlash == NULL ) - return; - lastSlash[1] = '\0'; - struct stat stat_buf; - if ( stat(dirs, &stat_buf) != 0 ) { - char* afterSlash = &dirs[1]; - char* slash; - while ( (slash = strchr(afterSlash, '/')) != NULL ) { - *slash = '\0'; - ::mkdir(dirs, S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); - //printf("mkdir(%s)\n", dirs); - *slash = '/'; - afterSlash = slash+1; - } - } + //printf("make_dirs(%s)\n", file_path); + char dirs[strlen(file_path)+1]; + strcpy(dirs, file_path); + char* lastSlash = strrchr(dirs, '/'); + if ( lastSlash == NULL ) + return; + lastSlash[1] = '\0'; + struct stat stat_buf; + if ( stat(dirs, &stat_buf) != 0 ) { + char* afterSlash = &dirs[1]; + char* slash; + while ( (slash = strchr(afterSlash, '/')) != NULL ) { + *slash = '\0'; + ::mkdir(dirs, S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); + //printf("mkdir(%s)\n", dirs); + *slash = '/'; + afterSlash = slash+1; + } + } } template -size_t dylib_maker(const void* mapped_cache, std::vector &dylib_data, const std::vector& segments) { - typedef typename A::P P; - - size_t additionalSize = 0; - for(std::vector::const_iterator it=segments.begin(); it != segments.end(); ++it) { - additionalSize += it->sizem; - } - - dylib_data.reserve(dylib_data.size() + additionalSize); - - uint32_t nfat_archs = 0; - uint32_t offsetInFatFile = 4096; +size_t dylib_maker(const void* mapped_cache, std::vector &dylib_data, const std::vector& segments) { + typedef typename A::P P; + + int32_t nfat_archs = 0; + uint32_t offsetInFatFile = 4096; uint8_t *base_ptr = &dylib_data.front(); - + #define FH reinterpret_cast(base_ptr) #define FA reinterpret_cast(base_ptr + (8 + (nfat_archs - 1) * sizeof(fat_arch))) - + if(dylib_data.size() >= 4096 && OSSwapBigToHostInt32(FH->magic) == FAT_MAGIC) { - // have fat header, append new arch to end + // have fat header, append new arch to end nfat_archs = OSSwapBigToHostInt32(FH->nfat_arch); - offsetInFatFile = OSSwapBigToHostInt32(FA->offset) + OSSwapBigToHostInt32(FA->size); + offsetInFatFile = OSSwapBigToHostInt32(FA->offset) + OSSwapBigToHostInt32(FA->size); } - - dylib_data.resize(offsetInFatFile); - base_ptr = &dylib_data.front(); - - FH->magic = OSSwapHostToBigInt32(FAT_MAGIC); + + // First see if this slice already exists. + for(std::vector::const_iterator it=segments.begin(); it != segments.end(); ++it) { + if(strcmp(it->segName, "__TEXT") == 0 ) { + const macho_header

*textMH = reinterpret_cast*>((uint8_t*)mapped_cache+it->offset); + + // if this cputype/subtype already exist in fat header, then return immediately + for(int32_t i=0; i < nfat_archs; ++i) { + fat_arch *afa = reinterpret_cast(base_ptr+8)+i; + if (afa->cputype == (cpu_type_t)OSSwapHostToBigInt32(textMH->cputype()) && afa->cpusubtype == (cpu_type_t)OSSwapHostToBigInt32(textMH->cpusubtype())) { + //fprintf(stderr, "arch already exists in fat dylib\n"); + return offsetInFatFile; + } + } + } + } + + if (dylib_data.empty()) { + // Reserve space for the fat header. + dylib_data.resize(4096); + base_ptr = &dylib_data.front(); + FH->magic = OSSwapHostToBigInt32(FAT_MAGIC); + } + FH->nfat_arch = OSSwapHostToBigInt32(++nfat_archs); - + FA->cputype = 0; // filled in later FA->cpusubtype = 0; // filled in later FA->offset = OSSwapHostToBigInt32(offsetInFatFile); FA->size = 0; // filled in later FA->align = OSSwapHostToBigInt32(12); - - // Write regular segments into the buffer - uint64_t totalSize = 0; - uint64_t textOffsetInCache = 0; - for( std::vector::const_iterator it=segments.begin(); it != segments.end(); ++it) { - + + size_t additionalSize = 0; + for(std::vector::const_iterator it=segments.begin(); it != segments.end(); ++it) { + if ( strcmp(it->segName, "__LINKEDIT") != 0 ) + additionalSize += it->sizem; + } + + std::vector new_dylib_data; + new_dylib_data.reserve(additionalSize); + + // Write regular segments into the buffer + uint64_t textOffsetInCache = 0; + for( std::vector::const_iterator it=segments.begin(); it != segments.end(); ++it) { + if(strcmp(it->segName, "__TEXT") == 0 ) { - textOffsetInCache = it->offset; + textOffsetInCache = it->offset; const macho_header

*textMH = reinterpret_cast*>((uint8_t*)mapped_cache+textOffsetInCache); - FA->cputype = OSSwapHostToBigInt32(textMH->cputype()); + FA->cputype = OSSwapHostToBigInt32(textMH->cputype()); FA->cpusubtype = OSSwapHostToBigInt32(textMH->cpusubtype()); - - // if this cputype/subtype already exist in fat header, then return immediately - for(uint32_t i=0; i < nfat_archs-1; ++i) { - fat_arch *afa = reinterpret_cast(base_ptr+8)+i; - - if( afa->cputype == FA->cputype - && afa->cpusubtype == FA->cpusubtype) { - //fprintf(stderr, "arch already exists in fat dylib\n"); - dylib_data.resize(offsetInFatFile); - return offsetInFatFile; - } - } - } - - //printf("segName=%s, offset=0x%llX, size=0x%0llX\n", it->segName, it->offset, it->sizem); - std::copy(((uint8_t*)mapped_cache)+it->offset, ((uint8_t*)mapped_cache)+it->offset+it->sizem, std::back_inserter(dylib_data)); - base_ptr = &dylib_data.front(); - totalSize += it->sizem; - } - - FA->size = OSSwapHostToBigInt32(totalSize); - - // optimize linkedit - uint64_t newSize = dylib_data.size(); - optimize_linkedit(((macho_header

*)(base_ptr+offsetInFatFile)), textOffsetInCache, mapped_cache, &newSize); - - // update fat header with new file size - dylib_data.resize((size_t)(offsetInFatFile+newSize)); - base_ptr = &dylib_data.front(); - FA->size = OSSwapHostToBigInt32(newSize); + } + + //printf("segName=%s, offset=0x%llX, size=0x%0llX\n", it->segName, it->offset, it->sizem); + // Copy all but the __LINKEDIT. It will be copied later during the optimizer in to a temporary buffer but it would + // not be efficient to copy it all now for each dylib. + if (strcmp(it->segName, "__LINKEDIT") == 0 ) + continue; + std::copy(((uint8_t*)mapped_cache)+it->offset, ((uint8_t*)mapped_cache)+it->offset+it->sizem, std::back_inserter(new_dylib_data)); + } + + // optimize linkedit + std::vector new_linkedit_data; + new_linkedit_data.reserve(1 << 20); + + LinkeditOptimizer linkeditOptimizer; + macho_header

* mh = (macho_header

*)&new_dylib_data.front(); + linkeditOptimizer.optimize_loadcommands(mh); + linkeditOptimizer.optimize_linkedit(new_linkedit_data, textOffsetInCache, mapped_cache); + + new_dylib_data.insert(new_dylib_data.end(), new_linkedit_data.begin(), new_linkedit_data.end()); + + // Page align file + while (new_dylib_data.size() % 4096) + new_dylib_data.push_back(0); + + // update fat header with new file size + FA->size = OSSwapHostToBigInt32(new_dylib_data.size()); #undef FH #undef FA - return offsetInFatFile; -} + dylib_data.insert(dylib_data.end(), new_dylib_data.begin(), new_dylib_data.end()); + return offsetInFatFile; +} + +typedef __typeof(dylib_maker) dylib_maker_func; +typedef void (^progress_block)(unsigned current, unsigned total); + +class SharedCacheExtractor; +struct SharedCacheDylibExtractor { + SharedCacheDylibExtractor(const char* name, std::vector segInfo) + : name(name), segInfo(segInfo) { } + + void extractCache(SharedCacheExtractor& context); + const char* name; + const std::vector segInfo; + int result = 0; +}; + +struct SharedCacheExtractor { + SharedCacheExtractor(const NameToSegments& map, + const char* extraction_root_path, + dylib_maker_func* dylib_create_func, + void* mapped_cache, + progress_block progress) + : map(map), extraction_root_path(extraction_root_path), + dylib_create_func(dylib_create_func), mapped_cache(mapped_cache), + progress(progress) { + + extractors.reserve(map.size()); + for (const std::pair>& it : map) + extractors.emplace_back(it.first, it.second); + + // Limit the number of open files. 16 seems to give better performance than higher numbers. + sema = dispatch_semaphore_create(16); + } + int extractCaches(); + + static void extractCache(void *ctx, size_t i); + + const NameToSegments& map; + std::vector extractors; + dispatch_semaphore_t sema; + const char* extraction_root_path; + dylib_maker_func* dylib_create_func; + void* mapped_cache; + progress_block progress; + std::atomic_int count = { 0 }; +}; + +int SharedCacheExtractor::extractCaches() { + dispatch_queue_t process_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); + dispatch_apply_f(map.size(), process_queue, + this, extractCache); + + int result = 0; + for (const SharedCacheDylibExtractor& extractor : extractors) { + if (extractor.result != 0) { + result = extractor.result; + break; + } + } + return result; +} + +void SharedCacheExtractor::extractCache(void *ctx, size_t i) { + SharedCacheExtractor& context = *(SharedCacheExtractor*)ctx; + dispatch_semaphore_wait(context.sema, DISPATCH_TIME_FOREVER); + context.extractors[i].extractCache(context); + dispatch_semaphore_signal(context.sema); +} + +void SharedCacheDylibExtractor::extractCache(SharedCacheExtractor &context) { + + char dylib_path[PATH_MAX]; + strcpy(dylib_path, context.extraction_root_path); + strcat(dylib_path, "/"); + strcat(dylib_path, name); + + //printf("%s with %lu segments\n", dylib_path, it->second.size()); + // make sure all directories in this path exist + make_dirs(dylib_path); + + // open file, create if does not already exist + int fd = ::open(dylib_path, O_CREAT | O_EXLOCK | O_RDWR, 0644); + if ( fd == -1 ) { + fprintf(stderr, "can't open or create dylib file %s, errnor=%d\n", dylib_path, errno); + result = -1; + return; + } + + struct stat statbuf; + if (fstat(fd, &statbuf)) { + fprintf(stderr, "Error: stat failed for dyld file %s, errnor=%d\n", dylib_path, errno); + close(fd); + result = -1; + return; + } + + std::vector vec((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); + result = -1; + return; + } + + const size_t offset = context.dylib_create_func(context.mapped_cache, vec, segInfo); + context.progress(context.count++, (unsigned)context.map.size()); + + if(offset != vec.size()) { + //Write out the first page, and everything after offset + if( pwrite(fd, &vec.front(), 4096, 0) == -1 + || pwrite(fd, &vec.front() + offset, vec.size() - offset, offset) == -1) { + fprintf(stderr, "error writing, errnor=%d\n", errno); + result = -1; + } + } + + close(fd); +} + +static int sharedCacheIsValid(const void* mapped_cache, uint64_t size) { + // First check that the size is good. + // Note the shared cache may not have a codeSignatureSize value set so we need to first make + // sure we have space for the CS_SuperBlob, then later crack that to check for the size of the rest. + const DyldSharedCache* dyldSharedCache = (DyldSharedCache*)mapped_cache; + uint64_t requiredSizeForCSSuperBlob = dyldSharedCache->header.codeSignatureOffset + sizeof(CS_SuperBlob); + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)((uint8_t*)mapped_cache + dyldSharedCache->header.mappingOffset); + if ( requiredSizeForCSSuperBlob > size ) { + fprintf(stderr, "Error: dyld shared cache size 0x%08llx is less than required size of 0x%08llx.\n", size, requiredSizeForCSSuperBlob); + return -1; + } + + // Now see if the code signatures are valid as that tells us the pages aren't corrupt. + // First find all of the regions of the shared cache we computed cd hashes + std::vector> sharedCacheRegions; + sharedCacheRegions.emplace_back(std::make_pair(mappings[0].fileOffset, mappings[0].fileOffset + mappings[0].size)); + sharedCacheRegions.emplace_back(std::make_pair(mappings[1].fileOffset, mappings[1].fileOffset + mappings[1].size)); + sharedCacheRegions.emplace_back(std::make_pair(mappings[2].fileOffset, mappings[2].fileOffset + mappings[2].size)); + if (dyldSharedCache->header.localSymbolsSize) + sharedCacheRegions.emplace_back(std::make_pair(dyldSharedCache->header.localSymbolsOffset, dyldSharedCache->header.localSymbolsOffset + dyldSharedCache->header.localSymbolsSize)); + size_t inBbufferSize = 0; + for (auto& sharedCacheRegion : sharedCacheRegions) + inBbufferSize += (sharedCacheRegion.second - sharedCacheRegion.first); + uint32_t slotCountFromRegions = (uint32_t)((inBbufferSize + CS_PAGE_SIZE - 1) / CS_PAGE_SIZE); + + // Now take the cd hash from the cache itself and validate the regions we found. + uint8_t* codeSignatureRegion = (uint8_t*)mapped_cache + dyldSharedCache->header.codeSignatureOffset; + CS_SuperBlob* sb = reinterpret_cast(codeSignatureRegion); + if (sb->magic != htonl(CSMAGIC_EMBEDDED_SIGNATURE)) { + fprintf(stderr, "Error: dyld shared cache code signature magic is incorrect.\n"); + return -1; + } + + size_t sbSize = ntohl(sb->length); + uint64_t requiredSizeForCS = dyldSharedCache->header.codeSignatureOffset + sbSize; + if ( requiredSizeForCS > size ) { + fprintf(stderr, "Error: dyld shared cache size 0x%08llx is less than required size of 0x%08llx.\n", size, requiredSizeForCS); + return -1; + } + + // Find the offset to the code directory. + CS_CodeDirectory* cd = nullptr; + for (unsigned i =0; i != sb->count; ++i) { + if (ntohl(sb->index[i].type) == CSSLOT_CODEDIRECTORY) { + cd = (CS_CodeDirectory*)(codeSignatureRegion + ntohl(sb->index[i].offset)); + break; + } + } + + if (!cd) { + fprintf(stderr, "Error: dyld shared cache code signature directory is missing.\n"); + return -1; + } + + if ( (uint8_t*)cd > (codeSignatureRegion + sbSize) ) { + fprintf(stderr, "Error: dyld shared cache code signature directory is out of bounds.\n"); + return -1; + } + + if ( cd->magic != htonl(CSMAGIC_CODEDIRECTORY) ) { + fprintf(stderr, "Error: dyld shared cache code signature directory magic is incorrect.\n"); + return -1; + } + + if ( ntohl(cd->nCodeSlots) < slotCountFromRegions ) { + fprintf(stderr, "Error: dyld shared cache code signature directory num slots is incorrect.\n"); + return -1; + } + + uint32_t dscDigestFormat = kCCDigestNone; + switch (cd->hashType) { + case CS_HASHTYPE_SHA1: + dscDigestFormat = kCCDigestSHA1; + break; + case CS_HASHTYPE_SHA256: + dscDigestFormat = kCCDigestSHA256; + break; + default: + break; + } + + if (dscDigestFormat != kCCDigestNone) { + const uint64_t csPageSize = 1 << cd->pageSize; + size_t hashOffset = ntohl(cd->hashOffset); + uint8_t* hashSlot = (uint8_t*)cd + hashOffset; + uint8_t cdHashBuffer[cd->hashSize]; + + // Skip local symbols for now as those aren't being codesign correctly right now. + size_t inBbufferSize = 0; + for (auto& sharedCacheRegion : sharedCacheRegions) { + if (sharedCacheRegion.first == dyldSharedCache->header.localSymbolsOffset) + continue; + inBbufferSize += (sharedCacheRegion.second - sharedCacheRegion.first); + } + uint32_t slotCountToProcess = (uint32_t)((inBbufferSize + CS_PAGE_SIZE - 1) / CS_PAGE_SIZE); + + for (unsigned i = 0; i != slotCountToProcess; ++i) { + // Skip data pages as those may have been slid by ASLR in the extracted file + uint64_t fileOffset = i * csPageSize; + if ( (fileOffset >= mappings[1].fileOffset) && (fileOffset < (mappings[1].fileOffset + mappings[1].size)) ) + continue; + + CCDigest(dscDigestFormat, (uint8_t*)mapped_cache + fileOffset, csPageSize, cdHashBuffer); + uint8_t* cacheCdHashBuffer = hashSlot + (i * cd->hashSize); + if (memcmp(cdHashBuffer, cacheCdHashBuffer, cd->hashSize) != 0) { + fprintf(stderr, "Error: dyld shared cache code signature for page %d is incorrect.\n", i); + return -1; + } + } + } + return 0; +} int dyld_shared_cache_extract_dylibs_progress(const char* shared_cache_file_path, const char* extraction_root_path, - void (^progress)(unsigned current, unsigned total)) + progress_block progress) { - struct stat statbuf; - if (stat(shared_cache_file_path, &statbuf)) { - fprintf(stderr, "Error: stat failed for dyld shared cache at %s\n", shared_cache_file_path); - return -1; - } - - int cache_fd = open(shared_cache_file_path, O_RDONLY); - if (cache_fd < 0) { - fprintf(stderr, "Error: failed to open shared cache file at %s\n", shared_cache_file_path); - return -1; - } - - 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; - } - + struct stat statbuf; + if (stat(shared_cache_file_path, &statbuf)) { + fprintf(stderr, "Error: stat failed for dyld shared cache at %s\n", shared_cache_file_path); + return -1; + } + + int cache_fd = open(shared_cache_file_path, O_RDONLY); + if (cache_fd < 0) { + fprintf(stderr, "Error: failed to open shared cache file at %s\n", shared_cache_file_path); + return -1; + } + + 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; + } + close(cache_fd); - // instantiate arch specific dylib maker - size_t (*dylib_create_func)(const void*, std::vector&, const std::vector&) = NULL; - if ( strcmp((char*)mapped_cache, "dyld_v1 i386") == 0 ) - dylib_create_func = dylib_maker; - else if ( strcmp((char*)mapped_cache, "dyld_v1 x86_64") == 0 ) - dylib_create_func = dylib_maker; - else if ( strcmp((char*)mapped_cache, "dyld_v1 x86_64h") == 0 ) - dylib_create_func = dylib_maker; - else if ( strcmp((char*)mapped_cache, "dyld_v1 armv5") == 0 ) - dylib_create_func = dylib_maker; - else if ( strcmp((char*)mapped_cache, "dyld_v1 armv6") == 0 ) - dylib_create_func = dylib_maker; - else if ( strcmp((char*)mapped_cache, "dyld_v1 armv7") == 0 ) - dylib_create_func = dylib_maker; - else if ( strncmp((char*)mapped_cache, "dyld_v1 armv7", 14) == 0 ) - 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"); + // instantiate arch specific dylib maker + dylib_maker_func* dylib_create_func = nullptr; + if ( strcmp((char*)mapped_cache, "dyld_v1 i386") == 0 ) + dylib_create_func = dylib_maker; + else if ( strcmp((char*)mapped_cache, "dyld_v1 x86_64") == 0 ) + dylib_create_func = dylib_maker; + else if ( strcmp((char*)mapped_cache, "dyld_v1 x86_64h") == 0 ) + dylib_create_func = dylib_maker; + else if ( strcmp((char*)mapped_cache, "dyld_v1 armv5") == 0 ) + dylib_create_func = dylib_maker; + else if ( strcmp((char*)mapped_cache, "dyld_v1 armv6") == 0 ) + dylib_create_func = dylib_maker; + else if ( strcmp((char*)mapped_cache, "dyld_v1 armv7") == 0 ) + dylib_create_func = dylib_maker; + else if ( strncmp((char*)mapped_cache, "dyld_v1 armv7", 14) == 0 ) + dylib_create_func = dylib_maker; + else if ( strcmp((char*)mapped_cache, "dyld_v1 arm64") == 0 ) + dylib_create_func = dylib_maker; +#if SUPPORT_ARCH_arm64e + else if ( strcmp((char*)mapped_cache, "dyld_v1 arm64e") == 0 ) + dylib_create_func = dylib_maker; +#endif +#if SUPPORT_ARCH_arm64_32 + else if ( strcmp((char*)mapped_cache, "dyld_v1arm64_32") == 0 ) + dylib_create_func = dylib_maker; +#endif + else { + fprintf(stderr, "Error: unrecognized dyld shared cache magic.\n"); munmap(mapped_cache, (size_t)statbuf.st_size); - return -1; - } + return -1; + } + + // Verify that the cache isn't corrupt. + if (int result = sharedCacheIsValid(mapped_cache, (uint64_t)statbuf.st_size)) { + munmap(mapped_cache, (size_t)statbuf.st_size); + return result; + } - // iterate through all images in cache and build map of dylibs and segments - __block NameToSegments map; - __block int result = dyld_shared_cache_iterate(mapped_cache, (uint32_t)statbuf.st_size, ^(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo) { + // iterate through all images in cache and build map of dylibs and segments + __block NameToSegments map; + int result = 0; + + result = dyld_shared_cache_iterate(mapped_cache, (uint32_t)statbuf.st_size, ^(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo) { map[dylibInfo->path].push_back(seg_info(segInfo->name, segInfo->fileOffset, segInfo->fileSize)); }); if(result != 0) { - fprintf(stderr, "Error: dyld_shared_cache_iterate_segments_with_slide failed.\n"); + fprintf(stderr, "Error: dyld_shared_cache_iterate_segments_with_slide failed.\n"); munmap(mapped_cache, (size_t)statbuf.st_size); - return result; - } - - // for each dylib instantiate a dylib file - dispatch_group_t group = dispatch_group_create(); - dispatch_semaphore_t sema = dispatch_semaphore_create(2); - dispatch_queue_t process_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); - dispatch_queue_t writer_queue = dispatch_queue_create("dyld writer queue", 0); - - __block unsigned count = 0; - - for ( NameToSegments::iterator it = map.begin(); it != map.end(); ++it) { - dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); - dispatch_group_async(group, process_queue, ^{ - - char dylib_path[PATH_MAX]; - strcpy(dylib_path, extraction_root_path); - strcat(dylib_path, "/"); - strcat(dylib_path, it->first); - - //printf("%s with %lu segments\n", dylib_path, it->second.size()); - // make sure all directories in this path exist - make_dirs(dylib_path); - - // open file, create if does not already exist - int fd = ::open(dylib_path, O_CREAT | O_EXLOCK | O_RDWR, 0644); - if ( fd == -1 ) { - fprintf(stderr, "can't open or create dylib file %s, errnor=%d\n", dylib_path, errno); - result = -1; - return; - } - - struct stat statbuf; - if (fstat(fd, &statbuf)) { - fprintf(stderr, "Error: stat failed for dyld file %s, errnor=%d\n", dylib_path, errno); - close(fd); - result = -1; - return; - } - - 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); - result = -1; - return; - } - - const size_t offset = dylib_create_func(mapped_cache, *vec, it->second); - - dispatch_group_async(group, writer_queue, ^{ - progress(count++, (unsigned)map.size()); - - if(offset != vec->size()) { - //Write out the first page, and everything after offset - if( pwrite(fd, &vec->front(), 4096, 0) == -1 - || pwrite(fd, &vec->front() + offset, vec->size() - offset, offset) == -1) { - fprintf(stderr, "error writing, errnor=%d\n", errno); - result = -1; - } - } - - delete vec; - close(fd); - dispatch_semaphore_signal(sema); - }); - }); - } - - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - dispatch_release(group); - dispatch_release(writer_queue); - + return result; + } + + // for each dylib instantiate a dylib file + SharedCacheExtractor extractor(map, extraction_root_path, dylib_create_func, mapped_cache, progress); + result = extractor.extractCaches(); + munmap(mapped_cache, (size_t)statbuf.st_size); - return result; + return result; } int dyld_shared_cache_extract_dylibs(const char* shared_cache_file_path, const char* extraction_root_path) { - return dyld_shared_cache_extract_dylibs_progress(shared_cache_file_path, extraction_root_path, - ^(unsigned , unsigned) {} ); + return dyld_shared_cache_extract_dylibs_progress(shared_cache_file_path, extraction_root_path, + ^(unsigned , unsigned) {} ); } -#if 0 +#if 0 // test program #include #include @@ -658,36 +916,36 @@ int dyld_shared_cache_extract_dylibs(const char* shared_cache_file_path, const c typedef int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path, - void (^progress)(unsigned current, unsigned total)); + void (^progress)(unsigned current, unsigned total)); int main(int argc, const char* argv[]) { - if ( argc != 3 ) { - fprintf(stderr, "usage: dsc_extractor \n"); - return 1; - } - - //void* handle = dlopen("/Volumes/my/src/dyld/build/Debug/dsc_extractor.bundle", RTLD_LAZY); - void* handle = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle", RTLD_LAZY); - if ( handle == NULL ) { - fprintf(stderr, "dsc_extractor.bundle could not be loaded\n"); - return 1; - } - - extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress"); - if ( proc == NULL ) { - fprintf(stderr, "dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\n"); - return 1; - } - - int result = (*proc)(argv[1], argv[2], ^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); } ); - fprintf(stderr, "dyld_shared_cache_extract_dylibs_progress() => %d\n", result); - return 0; + if ( argc != 3 ) { + fprintf(stderr, "usage: dsc_extractor \n"); + return 1; + } + + //void* handle = dlopen("/Volumes/my/src/dyld/build/Debug/dsc_extractor.bundle", RTLD_LAZY); + void* handle = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle", RTLD_LAZY); + if ( handle == NULL ) { + fprintf(stderr, "dsc_extractor.bundle could not be loaded\n"); + return 1; + } + + extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress"); + if ( proc == NULL ) { + fprintf(stderr, "dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\n"); + return 1; + } + + int result = (*proc)(argv[1], argv[2], ^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); } ); + fprintf(stderr, "dyld_shared_cache_extract_dylibs_progress() => %d\n", result); + return 0; } #endif - - + + diff --git a/launch-cache/dsc_iterator.cpp b/launch-cache/dsc_iterator.cpp index 7aa84ca..2196ac3 100644 --- a/launch-cache/dsc_iterator.cpp +++ b/launch-cache/dsc_iterator.cpp @@ -33,7 +33,7 @@ #include "Architectures.hpp" #include "MachOFileAbstraction.hpp" #include "CacheFileAbstraction.hpp" - +#include "SupportedArchs.h" namespace dyld { @@ -203,8 +203,14 @@ 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); +#if SUPPORT_ARCH_arm64_32 + else if ( strcmp((char*)cache, "dyld_v1arm64_32") == 0 ) + return dyld::walkImages(cache, shared_cache_size, callback); +#endif +#if SUPPORT_ARCH_arm64e else if ( strcmp((char*)cache, "dyld_v1 arm64e") == 0 ) return dyld::walkImages(cache, shared_cache_size, callback); +#endif else return -1; } diff --git a/launch-cache/dyld_cache_format.h b/launch-cache/dyld_cache_format.h index 57b5d07..a8a363b 100644 --- a/launch-cache/dyld_cache_format.h +++ b/launch-cache/dyld_cache_format.h @@ -230,6 +230,77 @@ struct dyld_cache_slide_info2 #define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page + +// The version 3 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 +// +// There are two cases: +// +// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE +// The page contains no values that need rebasing. +// +// 2) otherwise... +// All rebase locations are in one linked list. The offset of the first +// rebase location in the page is pageStarts[pageIndex]. +// +// A pointer is one of : +// { +// uint64_t pointerValue : 51; +// uint64_t offsetToNextPointer : 11; +// uint64_t isBind : 1 = 0; +// uint64_t authenticated : 1 = 0; +// } +// { +// uint32_t offsetFromSharedCacheBase; +// uint16_t diversityData; +// uint16_t hasAddressDiversity : 1; +// uint16_t key : 2; +// uint16_t offsetToNextPointer : 11; +// uint16_t isBind : 1; +// uint16_t authenticated : 1 = 1; +// } +// +// The code for processing a linked list (chain) is: +// +// uint32_t delta = pageStarts[pageIndex]; +// uint8_t* loc = pageStart; +// do { +// loc += delta; +// uintptr_t rawValue = *((uintptr_t*)loc); +// delta = ( (value & 0x3FF8000000000000) >> 51) * sizeof(uint64_t); +// if (extraBindData.isAuthenticated) { +// newValue = ( value & 0xFFFFFFFF ) + results->slide + auth_value_add; +// newValue = sign_using_the_various_bits(newValue); +// } else { +// uint64_t top8Bits = value & 0x0007F80000000000ULL; +// uint64_t bottom43Bits = value & 0x000007FFFFFFFFFFULL; +// uint64_t targetValue = ( top8Bits << 13 ) | bottom43Bits; +// newValue = targetValue + results->slide; +// } +// *((uintptr_t*)loc) = newValue; +// } while (delta != 0 ) +// +// +struct dyld_cache_slide_info3 +{ + uint32_t version; // currently 3 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_count; + uint64_t auth_value_add; + uint16_t page_starts[/* page_starts_count */]; +}; + +#define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + + struct dyld_cache_local_symbols_info { uint32_t nlistOffset; // offset into this chunk of nlist entries diff --git a/launch-cache/dyld_shared_cache_util.cpp b/launch-cache/dyld_shared_cache_util.cpp index 4c56c35..0324b03 100644 --- a/launch-cache/dyld_shared_cache_util.cpp +++ b/launch-cache/dyld_shared_cache_util.cpp @@ -35,11 +35,14 @@ #include #include #include +#include #include #include #include +#include "DyldSharedCache.h" + #include "dsc_iterator.h" #include "dsc_extractor.h" #include "dyld_cache_format.h" @@ -47,6 +50,7 @@ #include "MachOFileAbstraction.hpp" #include "CacheFileAbstraction.hpp" #include "Trie.hpp" +#include "SupportedArchs.h" enum Mode { modeNone, @@ -54,10 +58,12 @@ enum Mode { modeMap, modeDependencies, modeSlideInfo, + modeVerboseSlideInfo, modeAcceleratorInfo, modeTextInfo, modeLinkEdit, modeLocalSymbols, + modeStrings, modeInfo, modeSize, modeExtract @@ -93,9 +99,55 @@ struct Results { }; +// mmap() an shared cache file read/only but laid out like it would be at runtime +static const DyldSharedCache* mapCacheFile(const char* path, size_t& cacheLength) +{ + 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; + } + + uint8_t firstPage[4096]; + if ( ::pread(cache_fd, firstPage, 4096, 0) != 4096 ) { + fprintf(stderr, "Error: failed to read shared cache file at %s\n", path); + return nullptr; + } + const dyld_cache_header* header = (dyld_cache_header*)firstPage; + const dyld_cache_mapping_info* mappings = (dyld_cache_mapping_info*)(firstPage + header->mappingOffset); + + size_t vmSize = (size_t)(mappings[2].address + mappings[2].size - mappings[0].address); + vm_address_t result; + kern_return_t r = ::vm_allocate(mach_task_self(), &result, vmSize, VM_FLAGS_ANYWHERE); + if ( r != KERN_SUCCESS ) { + fprintf(stderr, "Error: failed to allocate space to load shared cache file at %s\n", path); + return nullptr; + } + for (int i=0; i < 3; ++i) { + void* mapped_cache = ::mmap((void*)(result + mappings[i].address - mappings[0].address), (size_t)mappings[i].size, + PROT_READ, MAP_FIXED | MAP_PRIVATE, cache_fd, mappings[i].fileOffset); + 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); + + cacheLength = statbuf.st_size; + + return (DyldSharedCache*)result; +} + + void usage() { - fprintf(stderr, "Usage: dyld_shared_cache_util -list [ -uuid ] [-vmaddr] | -dependents [ -versions ] | -linkedit | -map | -slide_info | -info | -extract [ shared-cache-file ] \n"); + fprintf(stderr, "Usage: dyld_shared_cache_util -list [ -uuid ] [-vmaddr] | -dependents [ -versions ] | -linkedit | -map | -slide_info | -verbose_slide_info | -info | -extract [ shared-cache-file ] \n"); } #if __x86_64__ @@ -136,6 +188,8 @@ 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_ARCH_8_32__ + return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "arm64_32"; #elif __arm64e__ return IPHONE_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME "arm64e"; #elif __arm64__ @@ -341,7 +395,7 @@ void print_map(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_ static void checkMode(Mode mode) { if ( mode != modeNone ) { - fprintf(stderr, "Error: select one of: -list, -dependents, -info, -slide_info, -linkedit, -map, -extract, or -size\n"); + fprintf(stderr, "Error: select one of: -list, -dependents, -info, -slide_info, -verbose_slide_info, -linkedit, -map, -extract, or -size\n"); usage(); exit(1); } @@ -360,6 +414,9 @@ int main (int argc, const char* argv[]) { options.dependentsOfPath = NULL; options.extractionDir = NULL; + bool printStrings = false; + bool printExports = false; + for (uint32_t i = 1; i < argc; i++) { const char* opt = argv[i]; if (opt[0] == '-') { @@ -389,6 +446,10 @@ int main (int argc, const char* argv[]) { checkMode(options.mode); options.mode = modeSlideInfo; } + else if (strcmp(opt, "-verbose_slide_info") == 0) { + checkMode(options.mode); + options.mode = modeVerboseSlideInfo; + } else if (strcmp(opt, "-accelerator_info") == 0) { checkMode(options.mode); options.mode = modeAcceleratorInfo; @@ -397,10 +458,22 @@ int main (int argc, const char* argv[]) { checkMode(options.mode); options.mode = modeTextInfo; } - else if (strcmp(opt, "-local_symbols") == 0) { - checkMode(options.mode); - options.mode = modeLocalSymbols; - } + else if (strcmp(opt, "-local_symbols") == 0) { + checkMode(options.mode); + options.mode = modeLocalSymbols; + } + else if (strcmp(opt, "-strings") == 0) { + if (options.mode != modeStrings) + checkMode(options.mode); + options.mode = modeStrings; + printStrings = true; + } + else if (strcmp(opt, "-exports") == 0) { + if (options.mode != modeStrings) + checkMode(options.mode); + options.mode = modeStrings; + printExports = true; + } else if (strcmp(opt, "-map") == 0) { checkMode(options.mode); options.mode = modeMap; @@ -448,7 +521,7 @@ int main (int argc, const char* argv[]) { exit(1); } - if ( options.mode != modeSlideInfo ) { + if ( options.mode != modeSlideInfo && options.mode != modeVerboseSlideInfo ) { if ( options.printUUIDs && (options.mode != modeList) ) fprintf(stderr, "Warning: -uuid option ignored outside of -list mode\n"); @@ -464,35 +537,39 @@ int main (int argc, const char* argv[]) { exit(1); } } - - struct stat statbuf; - if ( ::stat(sharedCachePath, &statbuf) == -1 ) { - fprintf(stderr, "Error: stat() failed for dyld shared cache at %s, errno=%d\n", sharedCachePath, errno); - exit(1); - } - - int cache_fd = ::open(sharedCachePath, O_RDONLY); - if ( cache_fd < 0 ) { - fprintf(stderr, "Error: open() failed for shared cache file at %s, errno=%d\n", sharedCachePath, errno); - exit(1); - } - 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); - } + + const DyldSharedCache* dyldCache = nullptr; + bool dyldCacheIsLive = true; + size_t cacheLength = 0; + if ( sharedCachePath != nullptr ) { + dyldCache = mapCacheFile(sharedCachePath, cacheLength); + dyldCacheIsLive = false; + } + else { +#if __MAC_OS_X_VERSION_MIN_REQUIRED && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101300) + fprintf(stderr, "this tool needs to run on macOS 10.13 or later\n"); + return 1; +#else + dyldCache = (DyldSharedCache*)_dyld_get_shared_cache_range(&cacheLength); +#endif + } + + options.mappedCache = dyldCache; - if ( options.mode == modeSlideInfo ) { + if ( options.mode == modeSlideInfo || options.mode == modeVerboseSlideInfo ) { const dyldCacheHeader* header = (dyldCacheHeader*)options.mappedCache; if ( header->slideInfoOffset() == 0 ) { fprintf(stderr, "Error: dyld shared cache does not contain slide info\n"); exit(1); } const dyldCacheFileMapping* mappings = (dyldCacheFileMapping*)((char*)options.mappedCache + header->mappingOffset()); + const dyldCacheFileMapping* textMapping = &mappings[0]; const dyldCacheFileMapping* dataMapping = &mappings[1]; + const dyldCacheFileMapping* linkEditMapping = &mappings[2]; uint64_t dataStartAddress = dataMapping->address(); uint64_t dataSize = dataMapping->size(); - const dyldCacheSlideInfo* slideInfoHeader = (dyldCacheSlideInfo*)((char*)options.mappedCache+header->slideInfoOffset()); + uint64_t slideInfoMappedOffset = (header->slideInfoOffset()-linkEditMapping->file_offset()) + (linkEditMapping->address() - textMapping->address()); + const dyldCacheSlideInfo* slideInfoHeader = (dyldCacheSlideInfo*)((char*)options.mappedCache+slideInfoMappedOffset); printf("slide info version=%d\n", slideInfoHeader->version()); if ( slideInfoHeader->version() == 1 ) { printf("toc_count=%d, data page count=%lld\n", slideInfoHeader->toc_count(), dataSize/4096); @@ -515,6 +592,30 @@ int main (int argc, const char* argv[]) { const uint16_t* extras = (uint16_t* )((char*)slideInfo + slideInfo->page_extras_offset()); for (int i=0; i < slideInfo->page_starts_count(); ++i) { const uint16_t start = starts[i]; + auto rebaseChain = [&](uint8_t* pageContent, uint16_t startOffset) + { + uintptr_t slideAmount = 0; + 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; + } + printf(" [% 5d + 0x%04llX]: 0x%016llX\n", i, (uint64_t)(pageOffset), (uint64_t)rawValue); + pageOffset += delta; + } + }; + const uint8_t* dataPagesStart = (uint8_t*)((char*)options.mappedCache + dataMapping->file_offset()); if ( start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE ) { printf("page[% 5d]: no rebasing\n", i); } @@ -525,6 +626,11 @@ int main (int argc, const char* argv[]) { do { uint16_t aStart = extras[j]; printf("start=0x%04X ", aStart & 0x3FFF); + if ( options.mode == modeVerboseSlideInfo ) { + uint8_t* page = (uint8_t*)(long)(dataPagesStart + (slideInfo->page_size()*i)); + uint16_t pageStartOffset = (aStart & 0x3FFF)*4; + rebaseChain(page, pageStartOffset); + } done = (extras[j] & DYLD_CACHE_SLIDE_PAGE_ATTR_END); ++j; } while ( !done ); @@ -532,11 +638,125 @@ int main (int argc, const char* argv[]) { } else { printf("page[% 5d]: start=0x%04X\n", i, starts[i]); + if ( options.mode == modeVerboseSlideInfo ) { + uint8_t* page = (uint8_t*)(long)(dataPagesStart + (slideInfo->page_size()*i)); + uint16_t pageStartOffset = start*4; + rebaseChain(page, pageStartOffset); + } } } } + else if ( slideInfoHeader->version() == 3 ) { + const dyldCacheSlideInfo3* slideInfo = (dyldCacheSlideInfo3*)(slideInfoHeader); + printf("page_size=%d\n", slideInfo->page_size()); + printf("page_starts_count=%d\n", slideInfo->page_starts_count()); + printf("auth_value_add=0x%016llX\n", slideInfo->auth_value_add()); + const uint8_t* dataSegmentStart = (uint8_t*)((char*)options.mappedCache + dataMapping->file_offset()); + for (int i=0; i < slideInfo->page_starts_count(); ++i) { + const uint16_t start = slideInfo->page_starts(i); + if ( start == 0xFFFF ) { + printf("page[% 5d]: no rebasing\n", i); + } + else { + printf("page[% 5d]: start=0x%04X\n", i, start); + if ( options.mode == modeVerboseSlideInfo ) { + typedef Pointer64 P; + typedef typename P::uint_t pint_t; + const uint8_t* pageStart = dataSegmentStart + (i * slideInfo->page_size()); + pint_t delta = start; + const uint8_t* rebaseLocation = pageStart; + do { + rebaseLocation += delta; + pint_t value = (pint_t)P::getP(*(uint64_t*)rebaseLocation); + delta = ( (value & 0x3FF8000000000000) >> 51) * sizeof(pint_t); + + // Regular pointer which needs to fit in 51-bits of value. + // C++ RTTI uses the top bit, so we'll allow the whole top-byte + // and the signed-extended bottom 43-bits to be fit in to 51-bits. + uint64_t top8Bits = value & 0x007F80000000000ULL; + uint64_t bottom43Bits = value & 0x000007FFFFFFFFFFULL; + uint64_t targetValue = ( top8Bits << 13 ) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); + printf(" [% 5d + 0x%04llX]: 0x%016llX\n", i, (uint64_t)(rebaseLocation - pageStart), targetValue); + } while (delta != 0); + } + } + } + } + else if ( slideInfoHeader->version() == 4 ) { + const dyld_cache_slide_info4* slideInfo = (dyld_cache_slide_info4*)(slideInfoHeader); + printf("page_size=%d\n", slideInfo->page_size); + printf("delta_mask=0x%016llX\n", slideInfo->delta_mask); + printf("value_add=0x%016llX\n", slideInfo->value_add); + printf("page_starts_count=%d, page_extras_count=%d\n", slideInfo->page_starts_count, slideInfo->page_extras_count); + const uint16_t* starts = (uint16_t* )((char*)slideInfo + slideInfo->page_starts_offset); + const uint16_t* extras = (uint16_t* )((char*)slideInfo + slideInfo->page_extras_offset); + for (int i=0; i < slideInfo->page_starts_count; ++i) { + const uint16_t start = starts[i]; + auto rebaseChainV4 = [&](uint8_t* pageContent, uint16_t startOffset) + { + uintptr_t slideAmount = 0; + 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; + uint32_t rawValue = *((uint32_t*)loc); + delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); + uintptr_t value = (rawValue & valueMask); + if ( (value & 0xFFFF8000) == 0 ) { + // small positive non-pointer, use as-is + } + else if ( (value & 0x3FFF8000) == 0x3FFF8000 ) { + // small negative non-pointer + value |= 0xC0000000; + } + else { + value += valueAdd; + value += slideAmount; + } + printf(" [% 5d + 0x%04X]: 0x%08X\n", i, pageOffset, rawValue); + pageOffset += delta; + } + }; + const uint8_t* dataPagesStart = (uint8_t*)((char*)options.mappedCache + dataMapping->file_offset()); + if ( start == DYLD_CACHE_SLIDE4_PAGE_NO_REBASE ) { + printf("page[% 5d]: no rebasing\n", i); + } + else if ( start & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA ) { + printf("page[% 5d]: ", i); + int j=(start & DYLD_CACHE_SLIDE4_PAGE_INDEX); + bool done = false; + do { + uint16_t aStart = extras[j]; + printf("start=0x%04X ", aStart & DYLD_CACHE_SLIDE4_PAGE_INDEX); + if ( options.mode == modeVerboseSlideInfo ) { + uint8_t* page = (uint8_t*)(long)(dataPagesStart + (slideInfo->page_size*i)); + uint16_t pageStartOffset = (aStart & DYLD_CACHE_SLIDE4_PAGE_INDEX)*4; + rebaseChainV4(page, pageStartOffset); + } + done = (extras[j] & DYLD_CACHE_SLIDE4_PAGE_EXTRA_END); + ++j; + } while ( !done ); + printf("\n"); + } + else { + printf("page[% 5d]: start=0x%04X\n", i, starts[i]); + if ( options.mode == modeVerboseSlideInfo ) { + uint8_t* page = (uint8_t*)(long)(dataPagesStart + (slideInfo->page_size*i)); + uint16_t pageStartOffset = start*4; + rebaseChainV4(page, pageStartOffset); + } + } + } + } + return 0; } - else if ( options.mode == modeInfo ) { + + if ( options.mode == modeInfo ) { const dyldCacheHeader* header = (dyldCacheHeader*)options.mappedCache; printf("uuid: "); if ( header->mappingOffset() >= 0x68 ) { @@ -553,25 +773,27 @@ int main (int argc, const char* argv[]) { 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)); + uint32_t bitfield = *((uint32_t*)(((char*)header) + 0xDC)); + uint32_t simulator = bitfield & 0x200; + uint32_t locallyBuiltCache = bitfield & 0x400; switch (platform) { case 1: printf("platform: macOS\n"); break; case 2: - if ( simulator & 0x400 ) + if ( simulator ) printf("platform: iOS simulator\n"); else printf("platform: iOS\n"); break; case 3: - if ( simulator & 0x400 ) + if ( simulator ) printf("platform: tvOS simulator\n"); else printf("platform: tvOS\n"); break; case 4: - if ( simulator & 0x400 ) + if ( simulator ) printf("platform: watchOS simulator\n"); else printf("platform: watchOS\n"); @@ -582,6 +804,7 @@ int main (int argc, const char* argv[]) { default: printf("platform: 0x%08X 0x%08X\n", platform, simulator); } + printf("built by: %s\n", locallyBuiltCache ? "local machine" : "B&I"); } printf("image count: %u\n", header->imagesCount()); if ( (header->mappingOffset() >= 0x78) && (header->branchPoolsOffset() != 0) ) { @@ -604,7 +827,7 @@ int main (int argc, const char* argv[]) { mappings[i].address(), mappings[i].address() + mappings[i].size()); } if ( header->codeSignatureOffset() != 0 ) { - uint64_t size = statbuf.st_size - header->codeSignatureOffset(); + uint64_t size = cacheLength - header->codeSignatureOffset(); uint64_t csAddr = mappings[header->mappingCount()-1].address() + mappings[header->mappingCount()-1].size(); if ( size != 0 ) printf(" code sign %3lluMB, file offset: 0x%08llX -> 0x%08llX, address: 0x%08llX -> 0x%08llX\n", @@ -762,6 +985,51 @@ int main (int argc, const char* argv[]) { #endif } } + else if ( options.mode == modeStrings ) { + if (printStrings) { + dyldCache->forEachImage(^(const mach_header *mh, const char *installName) { + const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)mh; + int64_t slide = ma->getSlide(); + ma->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo& info, bool malformedSectionRange, bool& stop) { + if ( ( (info.sectFlags & SECTION_TYPE) == S_CSTRING_LITERALS ) ) { + if ( malformedSectionRange ) { + stop = true; + return; + } + const uint8_t* content = (uint8_t*)(info.sectAddr + slide); + const char* s = (char*)content; + const char* end = s + info.sectSize; + while ( s < end ) { + printf("%s: %s\n", ma->installName(), s); + while (*s != '\0' ) + ++s; + ++s; + } + } + }); + }); + } + + if (printExports) { + dyldCache->forEachImage(^(const mach_header *mh, const char *installName) { + const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)mh; + uint32_t exportTrieRuntimeOffset; + uint32_t exportTrieSize; + if ( ma->hasExportTrie(exportTrieRuntimeOffset, exportTrieSize) ) { + const uint8_t* start = (uint8_t*)mh + exportTrieRuntimeOffset; + const uint8_t* end = start + exportTrieSize; + std::vector exports; + if ( !ExportInfoTrie::parseTrie(start, end, exports) ) { + return; + } + + for (const ExportInfoTrie::Entry& entry: exports) { + printf("%s: %s\n", ma->installName(), entry.name.c_str()); + } + } + }); + } + } else if ( options.mode == modeExtract ) { char pathBuffer[PATH_MAX]; uint32_t bufferSize = PATH_MAX; @@ -810,10 +1078,12 @@ int main (int argc, const char* argv[]) { break; case modeNone: case modeInfo: - case modeSlideInfo: + case modeSlideInfo: + case modeVerboseSlideInfo: case modeAcceleratorInfo: case modeTextInfo: case modeLocalSymbols: + case modeStrings: case modeExtract: break; } @@ -838,16 +1108,22 @@ int main (int argc, const char* argv[]) { break; case modeNone: case modeInfo: - case modeSlideInfo: + case modeSlideInfo: + case modeVerboseSlideInfo: case modeAcceleratorInfo: case modeTextInfo: - case modeLocalSymbols: + case modeLocalSymbols: + case modeStrings: case modeExtract: break; } } - else if ( (strncmp((char*)options.mappedCache, "dyld_v1 armv", 14) == 0) - || (strncmp((char*)options.mappedCache, "dyld_v1 armv", 13) == 0) ) { + else if ( (strncmp((char*)options.mappedCache, "dyld_v1 armv", 14) == 0) + || (strncmp((char*)options.mappedCache, "dyld_v1 armv", 13) == 0) +#if SUPPORT_ARCH_arm64_32 + || (strcmp((char*)options.mappedCache, "dyld_v1arm64_32") == 0) +#endif + ) { switch ( options.mode ) { case modeList: callback = print_list; @@ -866,16 +1142,21 @@ int main (int argc, const char* argv[]) { break; case modeNone: case modeInfo: - case modeSlideInfo: + case modeSlideInfo: + case modeVerboseSlideInfo: case modeAcceleratorInfo: case modeTextInfo: - case modeLocalSymbols: + case modeLocalSymbols: + case modeStrings: case modeExtract: break; } } else if ( (strcmp((char*)options.mappedCache, "dyld_v1 arm64") == 0) - || (strcmp((char*)options.mappedCache, "dyld_v1 arm64e") == 0) ) { +#if SUPPORT_ARCH_arm64e + || (strcmp((char*)options.mappedCache, "dyld_v1 arm64e") == 0) +#endif + ) { switch ( options.mode ) { case modeList: callback = print_list; @@ -894,22 +1175,55 @@ int main (int argc, const char* argv[]) { break; case modeNone: case modeInfo: + case modeSlideInfo: + case modeVerboseSlideInfo: + case modeAcceleratorInfo: + case modeTextInfo: + case modeLocalSymbols: + case modeStrings: + case modeExtract: + break; + } + } +#if SUPPORT_ARCH_arm64_32 + else if ( (strcmp((char*)options.mappedCache, "dyld_v1arm64_32") == 0) ) { + switch ( options.mode ) { + case modeList: + callback = print_list; + break; + case modeMap: + callback = print_map; + break; + case modeDependencies: + callback = print_dependencies; + break; + case modeLinkEdit: + callback = process_linkedit; + break; + case modeSize: + callback = collect_size; + break; + case modeNone: + case modeInfo: case modeSlideInfo: + case modeVerboseSlideInfo: case modeAcceleratorInfo: case modeTextInfo: - case modeLocalSymbols: + case modeLocalSymbols: + case modeStrings: case modeExtract: break; } - } - else { + } +#endif + else { fprintf(stderr, "Error: unrecognized dyld shared cache magic.\n"); exit(1); } __block Results results; results.dependentTargetFound = false; - int iterateResult = dyld_shared_cache_iterate(options.mappedCache, (uint32_t)statbuf.st_size, + int iterateResult = dyld_shared_cache_iterate(options.mappedCache, (uint32_t)cacheLength, ^(const dyld_shared_cache_dylib_info* dylibInfo, const dyld_shared_cache_segment_info* segInfo ) { (callback)(dylibInfo, segInfo, options, results); }); diff --git a/src/ImageLoader.cpp b/src/ImageLoader.cpp index 13117d5..28f89e2 100644 --- a/src/ImageLoader.cpp +++ b/src/ImageLoader.cpp @@ -34,8 +34,11 @@ #include #include #include +#include #include +#include "Tracing.h" + #include "ImageLoader.h" @@ -368,6 +371,7 @@ const ImageLoader::Symbol* ImageLoader::findExportedSymbolInImageOrDependentImag // this is called by initializeMainExecutable() to interpose on the initial set of images void ImageLoader::applyInterposing(const LinkContext& context) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0, 0, 0); if ( fgInterposingTuples.size() != 0 ) this->recursiveApplyInterposing(context); } @@ -390,6 +394,58 @@ uintptr_t ImageLoader::interposedAddress(const LinkContext& context, uintptr_t a return address; } +void ImageLoader::applyInterposingToDyldCache(const LinkContext& context) { +#if USES_CHAINED_BINDS + if (!context.dyldCache) + return; + if (fgInterposingTuples.empty()) + return; + // For each of the interposed addresses, see if any of them are in the shared cache. If so, find + // that image and apply its patch table to all uses. + uintptr_t cacheStart = (uintptr_t)context.dyldCache; + for (std::vector::iterator it=fgInterposingTuples.begin(); it != fgInterposingTuples.end(); it++) { + if ( context.verboseInterposing ) + dyld::log("dyld: interpose: Trying to interpose address 0x%08llx\n", (uint64_t)it->replacee); + uint32_t imageIndex; + uint32_t cacheOffsetOfReplacee = (uint32_t)(it->replacee - cacheStart); + if (!context.dyldCache->addressInText(cacheOffsetOfReplacee, &imageIndex)) + continue; + dyld3::closure::ImageNum imageInCache = imageIndex+1; + if ( context.verboseInterposing ) + dyld::log("dyld: interpose: Found shared cache image %d for 0x%08llx\n", imageInCache, (uint64_t)it->replacee); + const dyld3::closure::Image* image = context.dyldCache->cachedDylibsImageArray()->imageForNum(imageInCache); + image->forEachPatchableExport(^(uint32_t cacheOffsetOfImpl, const char* exportName) { + // Skip patching anything other than this symbol + if (cacheOffsetOfImpl != cacheOffsetOfReplacee) + return; + if ( context.verboseInterposing ) + dyld::log("dyld: interpose: Patching uses of symbol %s in shared cache binary at %s\n", exportName, image->path()); + uintptr_t newLoc = it->replacement; + image->forEachPatchableUseOfExport(cacheOffsetOfImpl, ^(dyld3::closure::Image::PatchableExport::PatchLocation patchLocation) { + uintptr_t* loc = (uintptr_t*)(cacheStart+patchLocation.cacheOffset); +#if __has_feature(ptrauth_calls) + if ( patchLocation.authenticated ) { + dyld3::MachOLoaded::ChainedFixupPointerOnDisk fixupInfo; + fixupInfo.authRebase.auth = true; + fixupInfo.authRebase.addrDiv = patchLocation.usesAddressDiversity; + fixupInfo.authRebase.diversity = patchLocation.discriminator; + fixupInfo.authRebase.key = patchLocation.key; + *loc = fixupInfo.signPointer(loc, newLoc + patchLocation.getAddend()); + if ( context.verboseInterposing ) + dyld::log("dyld: interpose: *%p = %p (JOP: diversity 0x%04X, addr-div=%d, key=%s)\n", + loc, (void*)*loc, patchLocation.discriminator, patchLocation.usesAddressDiversity, patchLocation.keyName()); + return; + } +#endif + if ( context.verboseInterposing ) + dyld::log("dyld: interpose: *%p = 0x%0llX (dyld cache patch) to %s\n", loc, newLoc + patchLocation.getAddend(), exportName); + *loc = newLoc + patchLocation.getAddend(); + }); + }); + } +#endif +} + void ImageLoader::addDynamicInterposingTuples(const struct dyld_interpose_tuple array[], size_t count) { for(size_t i=0; i < count; ++i) { @@ -408,6 +464,26 @@ void ImageLoader::addDynamicInterposingTuples(const struct dyld_interpose_tuple } } +// dyld should tell the kernel when it is doing root fix-ups +void ImageLoader::vmAccountingSetSuspended(const LinkContext& context, bool suspend) +{ +#if __arm__ || __arm64__ + static bool sVmAccountingSuspended = false; + if ( suspend == sVmAccountingSuspended ) + return; + if ( context.verboseBind ) + dyld::log("set vm.footprint_suspend=%d\n", suspend); + int newValue = suspend ? 1 : 0; + int oldValue = 0; + size_t newlen = sizeof(newValue); + size_t oldlen = sizeof(oldValue); + int ret = sysctlbyname("vm.footprint_suspend", &oldValue, &oldlen, &newValue, newlen); + if ( context.verboseBind && (ret != 0) ) + dyld::log("vm.footprint_suspend => %d, errno=%d\n", ret, errno); + sVmAccountingSuspended = suspend; +#endif +} + void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath) { @@ -423,24 +499,30 @@ void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool pr // we only do the loading step for preflights if ( preflightOnly ) return; - + uint64_t t1 = mach_absolute_time(); context.clearAllDepths(); this->recursiveUpdateDepth(context.imageCount()); - uint64_t t2 = mach_absolute_time(); - this->recursiveRebase(context); - context.notifyBatch(dyld_image_state_rebased, false); - - uint64_t t3 = mach_absolute_time(); - this->recursiveBind(context, forceLazysBound, neverUnload); - - uint64_t t4 = mach_absolute_time(); - if ( !context.linkingMainExecutable ) - this->weakBind(context); - uint64_t t5 = mach_absolute_time(); + __block uint64_t t2, t3, t4, t5; + { + dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0); + t2 = mach_absolute_time(); + this->recursiveRebase(context); + context.notifyBatch(dyld_image_state_rebased, false); + + t3 = mach_absolute_time(); + if ( !context.linkingMainExecutable ) + this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload); + + t4 = mach_absolute_time(); + if ( !context.linkingMainExecutable ) + this->weakBind(context); + t5 = mach_absolute_time(); + } - context.notifyBatch(dyld_image_state_bound, false); + if ( !context.linkingMainExecutable ) + context.notifyBatch(dyld_image_state_bound, false); uint64_t t6 = mach_absolute_time(); std::vector dofs; @@ -450,9 +532,10 @@ void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool pr // interpose any dynamically loaded images if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0) ) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0, 0, 0); this->recursiveApplyInterposing(context); } - + // clear error strings (*context.setErrorStrings)(0, NULL, NULL, NULL); @@ -626,10 +709,16 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli } try { unsigned cacheIndex; - dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(), &thisRPaths, cacheIndex); + bool enforceIOSMac = false; + #if __MAC_OS_X_VERSION_MIN_REQUIRED + const dyld3::MachOFile* mf = (dyld3::MachOFile*)this->machHeader(); + if ( mf->supportsPlatform(dyld3::Platform::iOSMac) && !mf->supportsPlatform(dyld3::Platform::macOS) ) + enforceIOSMac = true; + #endif + dependentLib = context.loadLibrary(requiredLibInfo.name, true, this->getPath(), &thisRPaths, enforceIOSMac, cacheIndex); if ( dependentLib == this ) { // found circular reference, perhaps DYLD_LIBARY_PATH is causing this rdar://problem/3684168 - dependentLib = context.loadLibrary(requiredLibInfo.name, false, NULL, NULL, cacheIndex); + dependentLib = context.loadLibrary(requiredLibInfo.name, false, NULL, NULL, enforceIOSMac, cacheIndex); if ( dependentLib != this ) dyld::warn("DYLD_ setting caused circular dependency in %s\n", this->getPath()); } @@ -650,7 +739,8 @@ void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool prefli } // check found library version is compatible // 0xFFFFFFFF is wildcard that matches any version - if ( (requiredLibInfo.info.minVersion != 0xFFFFFFFF) && (actualInfo.minVersion < requiredLibInfo.info.minVersion) ) { + if ( (requiredLibInfo.info.minVersion != 0xFFFFFFFF) && (actualInfo.minVersion < requiredLibInfo.info.minVersion) + && ((dyld3::MachOFile*)(dependentLib->machHeader()))->enforceCompatVersion() ) { // record values for possible use by CrashReporter or Finder dyld::throwf("Incompatible library version: %s requires version %d.%d.%d or later, but %s provides version %d.%d.%d", this->getShortName(), requiredLibInfo.info.minVersion >> 16, (requiredLibInfo.info.minVersion >> 8) & 0xff, requiredLibInfo.info.minVersion & 0xff, @@ -784,7 +874,11 @@ void ImageLoader::recursiveApplyInterposing(const LinkContext& context) } } - +void ImageLoader::recursiveBindWithAccounting(const LinkContext& context, bool forceLazysBound, bool neverUnload) +{ + this->recursiveBind(context, forceLazysBound, neverUnload); + vmAccountingSetSuspended(context, false); +} void ImageLoader::recursiveBind(const LinkContext& context, bool forceLazysBound, bool neverUnload) { @@ -823,6 +917,23 @@ void ImageLoader::recursiveBind(const LinkContext& context, bool forceLazysBound } } + +// These are mangled symbols for all the variants of operator new and delete +// which a main executable can define (non-weak) and override the +// weak-def implementation in the OS. +static const char* sTreatAsWeak[] = { + "__Znwm", "__ZnwmRKSt9nothrow_t", + "__Znam", "__ZnamRKSt9nothrow_t", + "__ZdlPv", "__ZdlPvRKSt9nothrow_t", "__ZdlPvm", + "__ZdaPv", "__ZdaPvRKSt9nothrow_t", "__ZdaPvm", + "__ZnwmSt11align_val_t", "__ZnwmSt11align_val_tRKSt9nothrow_t", + "__ZnamSt11align_val_t", "__ZnamSt11align_val_tRKSt9nothrow_t", + "__ZdlPvSt11align_val_t", "__ZdlPvSt11align_val_tRKSt9nothrow_t", "__ZdlPvmSt11align_val_t", + "__ZdaPvSt11align_val_t", "__ZdaPvSt11align_val_tRKSt9nothrow_t", "__ZdaPvmSt11align_val_t" +}; + + + void ImageLoader::weakBind(const LinkContext& context) { if ( context.verboseWeakBind ) @@ -929,15 +1040,43 @@ void ImageLoader::weakBind(const LinkContext& context) } } } - + } } - + +#if __arm64e__ + for (int i=0; i < count; ++i) { + if ( imagesNeedingCoalescing[i]->usesChainedFixups() ) { + // during binding of references to weak-def symbols, the dyld cache was patched + // but if main executable has non-weak override of operator new or delete it needs is handled here + if ( !imagesNeedingCoalescing[i]->weakSymbolsBound(imageIndexes[i]) ) { + for (const char* weakSymbolName : sTreatAsWeak) { + const ImageLoader* dummy; + imagesNeedingCoalescing[i]->resolveWeak(context, weakSymbolName, true, false, &dummy); + } + } + } + else { + // look for weak def symbols in this image which may override the cache + ImageLoader::CoalIterator coaler; + imagesNeedingCoalescing[i]->initializeCoalIterator(coaler, i, 0); + imagesNeedingCoalescing[i]->incrementCoalIterator(coaler); + while ( !coaler.done ) { + imagesNeedingCoalescing[i]->incrementCoalIterator(coaler); + const ImageLoader* dummy; + // a side effect of resolveWeak() is to patch cache + imagesNeedingCoalescing[i]->resolveWeak(context, coaler.symbolName, true, false, &dummy); + } + } + } +#endif + // mark all as having all weak symbols bound for(int i=0; i < count; ++i) { imagesNeedingCoalescing[i]->setWeakSymbolsBound(imageIndexes[i]); } } + uint64_t t2 = mach_absolute_time(); fgTotalWeakBindTime += t2 - t1; @@ -1075,9 +1214,9 @@ static void printTime(const char* msg, uint64_t partTime, uint64_t totalTime) static uint64_t sUnitsPerSecond = 0; if ( sUnitsPerSecond == 0 ) { struct mach_timebase_info timeBaseInfo; - if ( mach_timebase_info(&timeBaseInfo) == KERN_SUCCESS ) { - sUnitsPerSecond = 1000000000ULL * timeBaseInfo.denom / timeBaseInfo.numer; - } + if ( mach_timebase_info(&timeBaseInfo) != KERN_SUCCESS ) + return; + sUnitsPerSecond = 1000000000ULL * timeBaseInfo.denom / timeBaseInfo.numer; } if ( partTime < sUnitsPerSecond ) { uint32_t milliSecondsTimesHundred = (uint32_t)((partTime*100000)/sUnitsPerSecond); @@ -1333,7 +1472,7 @@ intptr_t ImageLoader::read_sleb128(const uint8_t*& p, const uint8_t* end) } while (byte & 0x80); // sign extend negative numbers if ( (byte & 0x40) != 0 ) - result |= (-1LL) << bit; + result |= (~0ULL) << bit; return (intptr_t)result; } diff --git a/src/ImageLoader.h b/src/ImageLoader.h index fd70e2d..c0113b1 100644 --- a/src/ImageLoader.h +++ b/src/ImageLoader.h @@ -39,6 +39,7 @@ #include #include #include +#include #if __arm__ #include @@ -65,6 +66,7 @@ #include "mach-o/dyld_images.h" #include "mach-o/dyld_priv.h" +#include "DyldSharedCache.h" #if __i386__ #define SHARED_REGION_BASE SHARED_REGION_BASE_I386 @@ -108,8 +110,9 @@ #define SUPPORT_CLASSIC_MACHO __arm__ #define SUPPORT_ZERO_COST_EXCEPTIONS (!__USING_SJLJ_EXCEPTIONS__) #define INITIAL_IMAGE_COUNT 150 - #define SUPPORT_ACCELERATE_TABLES (__arm__ || __arm64__) + #define SUPPORT_ACCELERATE_TABLES !TARGET_IPHONE_SIMULATOR #define SUPPORT_ROOT_PATH TARGET_IPHONE_SIMULATOR + #define USES_CHAINED_BINDS (__arm64e__) #else #define SPLIT_SEG_SHARED_REGION_SUPPORT 0 #define SPLIT_SEG_DYLIB_SUPPORT __i386__ @@ -123,12 +126,14 @@ #define INITIAL_IMAGE_COUNT 200 #define SUPPORT_ACCELERATE_TABLES 0 #define SUPPORT_ROOT_PATH 1 + #define USES_CHAINED_BINDS 0 #endif #define MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE (32*1024) #define MH_HAS_OBJC 0x40000000 + // optimize away dyld's initializers #define VECTOR_NEVER_DESTRUCTED(type) \ namespace std { \ @@ -256,12 +261,14 @@ public: void addTime(const char* name, uint64_t time); }; + + typedef void (^CoalesceNotifier)(const Symbol* implSym, const ImageLoader* implIn, const mach_header* implMh); struct LinkContext { - ImageLoader* (*loadLibrary)(const char* libraryName, bool search, const char* origin, const RPathChain* rpaths, unsigned& cacheIndex); + ImageLoader* (*loadLibrary)(const char* libraryName, bool search, const char* origin, const RPathChain* rpaths, bool enforceIOSMac, unsigned& cacheIndex); void (*terminationRecorder)(ImageLoader* image); bool (*flatExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image); - bool (*coalescedExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image); + bool (*coalescedExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image, CoalesceNotifier); unsigned int (*getCoalescedImages)(ImageLoader* images[], unsigned imageIndex[]); void (*undefinedHandler)(const char* name); MappedRegion* (*getAllMappedRegions)(MappedRegion*); @@ -296,17 +303,23 @@ public: const char* progname; ProgramVars programVars; ImageLoader* mainExecutable; - const char* imageSuffix; + const char* const * imageSuffix; #if SUPPORT_ROOT_PATH const char** rootPaths; #endif + const DyldSharedCache* dyldCache; const dyld_interpose_tuple* dynamicInterposeArray; size_t dynamicInterposeCount; PrebindMode prebindUsage; SharedRegionMode sharedRegionMode; bool dyldLoadedAtSameAddressNeededBySharedCache; bool strictMachORequired; - bool requireCodeSignature; + bool allowAtPaths; + bool allowEnvVarsPrint; + bool allowEnvVarsPath; + bool allowEnvVarsSharedCache; + bool allowClassicFallbackPaths; + bool allowInsertFailures; bool mainExecutableCodeSigned; bool preFetchDisabled; bool prebinding; @@ -314,8 +327,7 @@ public: bool linkingMainExecutable; bool startedInitializingMainExecutable; #if __MAC_OS_X_VERSION_MIN_REQUIRED - bool processIsRestricted; - bool processUsingLibraryValidation; + bool marzipan; #endif bool verboseOpts; bool verboseEnv; @@ -417,7 +429,7 @@ public: bool leaveMapped() { return fLeaveMapped; } // image resides in dyld shared cache - virtual bool inSharedCache() const = 0; + virtual bool inSharedCache() const { return false; }; // checks if the specifed address is within one of this image's segments virtual bool containsAddress(const void* addr) const; @@ -434,11 +446,11 @@ public: // st_mtime from stat() on file time_t lastModified() const; - // only valid for main executables, returns a pointer its entry point from LC_UNIXTHREAD - virtual void* getThreadPC() const = 0; + // only valid for main executables, returns a pointer its entry point from LC_MAIN + virtual void* getEntryFromLC_MAIN() const = 0; - // only valid for main executables, returns a pointer its main from LC_

& dofs); virtual void recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize, @@ -806,12 +835,13 @@ public: static uint64_t fgTotalObjCSetupTime; static uint64_t fgTotalDebuggerPausedTime; static uint64_t fgTotalRebindCacheTime; -protected: static uint64_t fgTotalRebaseTime; static uint64_t fgTotalBindTime; static uint64_t fgTotalWeakBindTime; static uint64_t fgTotalDOF; static uint64_t fgTotalInitTime; + +protected: static std::vector fgInterposingTuples; const char* fPath; diff --git a/src/ImageLoaderMachO.cpp b/src/ImageLoaderMachO.cpp index 1becea3..f63769b 100644 --- a/src/ImageLoaderMachO.cpp +++ b/src/ImageLoaderMachO.cpp @@ -46,6 +46,10 @@ #include #include +#if __has_feature(ptrauth_calls) +#include +#endif + #include "ImageLoaderMachO.h" #include "ImageLoaderMachOCompressed.h" #if SUPPORT_CLASSIC_MACHO @@ -106,13 +110,7 @@ extern "C" long __stack_chk_guard; #define TOOL_LD 3 #endif - - -#if TARGET_IPHONE_SIMULATOR - #define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.dylib" -#else - #define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.B.dylib" -#endif +#define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.B.dylib" // relocation_info.r_length field has value 3 for 64-bit executables and value 2 for 32-bit executables #if __LP64__ @@ -147,7 +145,7 @@ fSegmentsCount(segCount), fIsSplitSeg(false), fInSharedCache(false), fReadOnlyImportSegment(false), #endif fHasSubLibraries(false), fHasSubUmbrella(false), fInUmbrella(false), fHasDOFSections(false), fHasDashInit(false), - fHasInitializers(false), fHasTerminators(false), fNotifyObjC(false), fRetainForObjC(false), fRegisteredAsRequiresCoalescing(false) + fHasInitializers(false), fHasTerminators(false), fNotifyObjC(false), fRetainForObjC(false), fRegisteredAsRequiresCoalescing(false), fOverrideOfCacheImageNum(0) { fIsSplitSeg = ((mh->flags & MH_SPLIT_SEGS) != 0); @@ -383,7 +381,8 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat case LC_VERSION_MIN_WATCHOS: case LC_VERSION_MIN_TVOS: case LC_VERSION_MIN_IPHONEOS: - throw "mach-o, but built for simulator (not macOS)"; + if ( !context.marzipan ) + throw "mach-o, but built for simulator (not macOS)"; break; #endif } @@ -673,8 +672,11 @@ void ImageLoaderMachO::parseLoadCmds(const LinkContext& context) for(unsigned int i=0; i < fSegmentsCount; ++i) { // set up pointer to __LINKEDIT segment if ( strcmp(segName(i),"__LINKEDIT") == 0 ) { - if ( context.requireCodeSignature && (segFileOffset(i) > fCoveredCodeLength)) + #if !__MAC_OS_X_VERSION_MIN_REQUIRED + // historically, macOS never did this check + if ( segFileOffset(i) > fCoveredCodeLength ) dyld::throwf("cannot load '%s' (segment outside of code signature)", this->getShortName()); + #endif fLinkEditBase = (uint8_t*)(segActualLoadAddress(i) - segFileOffset(i)); } #if TEXT_RELOC_SUPPORT @@ -1134,24 +1136,22 @@ void ImageLoaderMachO::setSlide(intptr_t slide) void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* codeSigCmd, int fd, uint64_t offsetInFatFile, const LinkContext& context) { + dyld3::ScopedTimer(DBG_DYLD_TIMING_ATTACH_CODESIGNATURE, 0, 0, 0); // if dylib being loaded has no code signature load command if ( codeSigCmd == NULL) { - if (context.requireCodeSignature ) { - // if we require dylibs to be codesigned there needs to be a signature. - dyld::throwf("required code signature missing for '%s'\n", this->getPath()); - } else { - disableCoverageCheck(); - } + disableCoverageCheck(); } else { #if __MAC_OS_X_VERSION_MIN_REQUIRED // ignore code signatures in binaries built with pre-10.9 tools if ( this->sdkVersion() < DYLD_MACOSX_VERSION_10_9 ) { + disableCoverageCheck(); return; } #endif + fsignatures_t siginfo; - siginfo.fs_file_start=offsetInFatFile; // start of mach-o slice in fat file + siginfo.fs_file_start=offsetInFatFile; // start of mach-o slice in fat file siginfo.fs_blob_start=(void*)(long)(codeSigCmd->dataoff); // start of CD in mach-o file siginfo.fs_blob_size=codeSigCmd->datasize; // size of CD int result = fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo); @@ -1169,28 +1169,26 @@ void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* cod if ( result == -1 ) { if ( (errno == EPERM) || (errno == EBADEXEC) ) dyld::throwf("code signature invalid for '%s'\n", this->getPath()); - if ( context.verboseCodeSignatures ) + if ( context.verboseCodeSignatures ) dyld::log("dyld: Failed registering code signature for %s, errno=%d\n", this->getPath(), errno); siginfo.fs_file_start = UINT64_MAX; } else if ( context.verboseCodeSignatures ) { dyld::log("dyld: Registered code signature for %s\n", this->getPath()); } fCoveredCodeLength = siginfo.fs_file_start; + } -#if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( context.processUsingLibraryValidation ) { - fchecklv checkInfo; - char messageBuffer[512]; - messageBuffer[0] = '\0'; - checkInfo.lv_file_start = offsetInFatFile; - checkInfo.lv_error_message_size = sizeof(messageBuffer); - checkInfo.lv_error_message = messageBuffer; - int res = fcntl(fd, F_CHECK_LV, &checkInfo); - if ( res == -1 ) { - dyld::throwf("code signature in (%s) not valid for use in process using Library Validation: %s", this->getPath(), messageBuffer); - } + { + fchecklv checkInfo; + char messageBuffer[512]; + messageBuffer[0] = '\0'; + checkInfo.lv_file_start = offsetInFatFile; + checkInfo.lv_error_message_size = sizeof(messageBuffer); + checkInfo.lv_error_message = messageBuffer; + int res = fcntl(fd, F_CHECK_LV, &checkInfo); + if ( res == -1 ) { + dyld::throwf("code signature in (%s) not valid for use in process using Library Validation: %s", this->getPath(), messageBuffer); } -#endif } } @@ -1204,25 +1202,17 @@ void ImageLoaderMachO::validateFirstPages(const struct linkedit_data_command* co } #endif if (codeSigCmd != NULL) { - void *fdata = xmmap(NULL, lenFileData, PROT_READ | PROT_EXEC, MAP_SHARED, fd, offsetInFat); + void *fdata = xmmap(NULL, lenFileData, PROT_READ, MAP_SHARED, fd, offsetInFat); if ( fdata == MAP_FAILED ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( context.processUsingLibraryValidation ) { - dyld::throwf("cannot load image with wrong team ID in process using Library Validation"); - } - else -#endif - { - int errnoCopy = errno; - if ( errnoCopy == EPERM ) { - if ( dyld::sandboxBlockedMmap(getPath()) ) - dyld::throwf("file system sandbox blocked mmap() of '%s'", getPath()); - else - dyld::throwf("code signing blocked mmap() of '%s'", getPath()); - } + int errnoCopy = errno; + if ( errnoCopy == EPERM ) { + if ( dyld::sandboxBlockedMmap(getPath()) ) + dyld::throwf("file system sandbox blocked mmap() of '%s'", getPath()); else - dyld::throwf("mmap() errno=%d validating first page of '%s'", errnoCopy, getPath()); + dyld::throwf("code signing blocked mmap() of '%s'", getPath()); } + else + dyld::throwf("mmap() errno=%d validating first page of '%s'", errnoCopy, getPath()); } if ( memcmp(fdata, fileData, lenFileData) != 0 ) dyld::throwf("mmap() page compare failed for '%s'", getPath()); @@ -1240,7 +1230,7 @@ const char* ImageLoaderMachO::getInstallPath() const return NULL; } -void ImageLoaderMachO::registerInterposing() +void ImageLoaderMachO::registerInterposing(const LinkContext& context) { // mach-o files advertise interposing by having a __DATA __interpose section struct InterposeData { uintptr_t replacement; uintptr_t replacee; }; @@ -1349,7 +1339,7 @@ uint32_t ImageLoaderMachO::minOSVersion() const } -void* ImageLoaderMachO::getThreadPC() const +void* ImageLoaderMachO::getEntryFromLC_MAIN() const { const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; @@ -1370,36 +1360,33 @@ void* ImageLoaderMachO::getThreadPC() const } -void* ImageLoaderMachO::getMain() const +void* ImageLoaderMachO::getEntryFromLC_UNIXTHREAD() const { const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_UNIXTHREAD: - { - #if __i386__ - const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16); - void* entry = (void*)(registers->eip + fSlide); - #elif __x86_64__ - const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16); - void* entry = (void*)(registers->rip + fSlide); - #elif __arm__ - const arm_thread_state_t* registers = (arm_thread_state_t*)(((char*)cmd) + 16); - void* entry = (void*)(registers->__pc + fSlide); - #elif __arm64__ - const arm_thread_state64_t* registers = (arm_thread_state64_t*)(((char*)cmd) + 16); - void* entry = (void*)(registers->__pc + fSlide); - #else - #warning need processor specific code - #endif - // verify entry point is in image - if ( this->containsAddress(entry) ) { - return entry; - } - } - break; + if ( cmd->cmd == LC_UNIXTHREAD ) { + #if __i386__ + const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16); + void* entry = (void*)(registers->eip + fSlide); + // verify entry point is in image + if ( this->containsAddress(entry) ) + return entry; + #elif __x86_64__ + const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16); + void* entry = (void*)(registers->rip + fSlide); + // verify entry point is in image + if ( this->containsAddress(entry) ) + return entry; + #elif __arm64__ && !__arm64e__ + // temp support until is fixed + const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16); + void* entry = (void*)(regs64[32] + fSlide); // arm_thread_state64_t.__pc + // verify entry point is in image + if ( this->containsAddress(entry) ) + return entry; + #endif } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } @@ -1514,12 +1501,10 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorpath.offset; if ( (strncmp(path, "@loader_path", 12) == 0) && ((path[12] == '/') || (path[12] == '\0')) ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( context.processIsRestricted && (context.mainExecutable == this) ) { + if ( !context.allowAtPaths && (context.mainExecutable == this) ) { dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @loader_path\n", path, this->getPath()); break; } -#endif char resolvedPath[PATH_MAX]; if ( realpath(this->getPath(), resolvedPath) != NULL ) { char newRealPath[strlen(resolvedPath) + strlen(path)]; @@ -1532,12 +1517,10 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorgetPath()); break; } -#endif char resolvedPath[PATH_MAX]; if ( realpath(context.mainExecutable->getPath(), resolvedPath) != NULL ) { char newRealPath[strlen(resolvedPath) + strlen(path)]; @@ -1549,12 +1532,10 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorgetPath()); break; } -#endif #if SUPPORT_ROOT_PATH else if ( (path[0] == '/') && (context.rootPaths != NULL) ) { // DYLD_ROOT_PATH should apply to LC_RPATH rpaths @@ -1880,7 +1861,7 @@ bool ImageLoaderMachO::findSection(const mach_header* mh, const char* segmentNam } -bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) +const macho_section* ImageLoaderMachO::findSection(const void* imageInterior) const { const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; @@ -1896,13 +1877,7 @@ bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segme const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if ((sect->addr <= unslidInteriorAddress) && (unslidInteriorAddress < (sect->addr+sect->size))) { - if ( segmentName != NULL ) - *segmentName = sect->segname; - if ( sectionName != NULL ) - *sectionName = sect->sectname; - if ( sectionOffset != NULL ) - *sectionOffset = unslidInteriorAddress - sect->addr; - return true; + return sect; } } } @@ -1911,6 +1886,22 @@ bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segme } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } + return nullptr; +} + + +bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) +{ + if (const struct macho_section* sect = findSection(imageInterior)) { + const uintptr_t unslidInteriorAddress = (uintptr_t)imageInterior - this->getSlide(); + if ( segmentName != NULL ) + *segmentName = sect->segname; + if ( sectionName != NULL ) + *sectionName = sect->sectname; + if ( sectionOffset != NULL ) + *sectionOffset = unslidInteriorAddress - sect->addr; + return true; + } return false; } @@ -1979,24 +1970,29 @@ const void* ImageLoaderMachO::getEnd() const return (const void*)lastAddress; } +uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t baseVMAddress, + uintptr_t location, uintptr_t value, + uint8_t type, const char* symbolName, + intptr_t addend, const char* inPath, const char* toPath, const char* msg, + ExtraBindData *extraBindData, uintptr_t slide) +{ + auto logBind = [&]() { + if ( !context.verboseBind ) + return; + if ( addend != 0 ) { + dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX + %ld\n", + msg, shortName(inPath), (uintptr_t)location, + ((toPath != NULL) ? shortName(toPath) : ""), + symbolName, (uintptr_t)location, value, addend); + } else { + dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX\n", + msg, shortName(inPath), (uintptr_t)location, + ((toPath != NULL) ? shortName(toPath) : ""), + symbolName, (uintptr_t)location, value); + } + }; + -uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t location, uintptr_t value, - uint8_t type, const char* symbolName, - intptr_t addend, const char* inPath, const char* toPath, const char* msg) -{ - // log - if ( context.verboseBind ) { - if ( addend != 0 ) - dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX + %ld\n", - msg, shortName(inPath), (uintptr_t)location, - ((toPath != NULL) ? shortName(toPath) : ""), - symbolName, (uintptr_t)location, value, addend); - else - dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX\n", - msg, shortName(inPath), (uintptr_t)location, - ((toPath != NULL) ? shortName(toPath) : ""), - symbolName, (uintptr_t)location, value); - } #if LOG_BINDINGS // dyld::logBindings("%s: %s\n", targetImage->getShortName(), symbolName); #endif @@ -2008,22 +2004,45 @@ uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t l uint32_t value32; switch (type) { case BIND_TYPE_POINTER: + logBind(); // test first so we don't needless dirty pages if ( *locationToFix != newValue ) *locationToFix = newValue; break; - case BIND_TYPE_TEXT_ABSOLUTE32: + case BIND_TYPE_TEXT_ABSOLUTE32: + logBind(); loc32 = (uint32_t*)locationToFix; value32 = (uint32_t)newValue; if ( *loc32 != value32 ) *loc32 = value32; break; - case BIND_TYPE_TEXT_PCREL32: + case BIND_TYPE_TEXT_PCREL32: + logBind(); loc32 = (uint32_t*)locationToFix; value32 = (uint32_t)(newValue - (((uintptr_t)locationToFix) + 4)); if ( *loc32 != value32 ) *loc32 = value32; + break; + case BIND_TYPE_THREADED_BIND: + logBind(); + // test first so we don't needless dirty pages + if ( *locationToFix != newValue ) + *locationToFix = newValue; + break; + case BIND_TYPE_THREADED_REBASE: { + // Regular pointer which needs to fit in 51-bits of value. + // C++ RTTI uses the top bit, so we'll allow the whole top-byte + // and the signed-extended bottom 43-bits to be fit in to 51-bits. + uint64_t top8Bits = *locationToFix & 0x0007F80000000000ULL; + uint64_t bottom43Bits = *locationToFix & 0x000007FFFFFFFFFFULL; + uint64_t targetValue = ( top8Bits << 13 ) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); + newValue = (uintptr_t)(targetValue + slide); + if ( context.verboseRebase ) { + dyld::log("dyld: rebase: %s:*0x%08lX += 0x%08lX = 0x%08lX\n", shortName(inPath), (uintptr_t)locationToFix, slide, newValue); + } + *locationToFix = newValue; break; + } default: dyld::throwf("bad bind type %d", type); } @@ -2082,6 +2101,24 @@ void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context) dd->dyldLazyBinder = (void*)&stub_binding_helper; } #endif // !__arm64__ + // Add work around for existing apps that have deprecated __dyld section + const char* installNm = this->getInstallPath(); + if ( (mh->filetype != MH_DYLIB) || (installNm == NULL) || (strcmp(installNm, "/usr/lib/system/libdyld.dylib") != 0) ) { + #if TARGET_OS_OSX + // don't allow macOS apps build with 10.14 or later SDK and targeting 10.8 or later to have a __dyld section + if ( (minOSVersion() >= 0x000a0800) && (sdkVersion() >= 0x000a0e00) ) + dyld::throwf("__dyld section not supported in %s", this->getPath()); + #endif + #if TARGET_OS_IOS || TARGET_OS_TV + // don't allow iOS apps build with 12.0 or later SDK to have a __dyld section + if ( sdkVersion() >= 0x000c0000 ) + dyld::throwf("__dyld section not supported in %s", this->getPath()); + #endif + #if TARGET_OS_WATCH + if ( sdkVersion() >= 0x00050000 ) + dyld::throwf("__dyld section not supported in %s", this->getPath()); + #endif + } if ( sect->size > offsetof(DATAdyld, dyldFuncLookup) ) { if ( dd->dyldFuncLookup != (void*)&_dyld_func_lookup ) dd->dyldFuncLookup = (void*)&_dyld_func_lookup; @@ -2101,7 +2138,7 @@ void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context) // match what crt1.o supplies, then the program has a custom entry point. // This means it might be doing something that needs to be executed before // initializers are run. - if ( memcmp(this->getMain(), sStandardEntryPointInstructions, 16) != 0 ) { + if ( memcmp(this->getEntryFromLC_UNIXTHREAD(), sStandardEntryPointInstructions, 16) != 0 ) { if ( context.verboseInit ) dyld::log("dyld: program uses non-standard entry point so delaying running of initializers\n"); context.setRunInitialzersOldWay(); @@ -2171,7 +2208,10 @@ bool ImageLoaderMachO::usablePrebinding(const LinkContext& context) const // if prebound and loaded at prebound address, and all libraries are same as when this was prebound, then no need to bind if ( ((this->isPrebindable() && (this->getSlide() == 0)) || fInSharedCache) && this->usesTwoLevelNameSpace() - && this->allDependentLibrariesAsWhenPreBound() ) { +#if !USES_CHAINED_BINDS + && this->allDependentLibrariesAsWhenPreBound() +#endif + ) { // allow environment variables to disable prebinding if ( context.bindFlat ) return false; @@ -2189,6 +2229,14 @@ bool ImageLoaderMachO::usablePrebinding(const LinkContext& context) const return false; } +static void *stripPointer(void *ptr) { +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + void ImageLoaderMachO::doImageInit(const LinkContext& context) { @@ -2200,8 +2248,11 @@ void ImageLoaderMachO::doImageInit(const LinkContext& context) switch (cmd->cmd) { case LC_ROUTINES_COMMAND: Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide); +#if __has_feature(ptrauth_calls) + func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0); +#endif // verify initializers are in image - if ( ! this->containsAddress((void*)func) ) { + if ( ! this->containsAddress(stripPointer((void*)func)) ) { dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath()); } if ( ! dyld::gProcessInfo->libSystemInitialized ) { @@ -2210,9 +2261,10 @@ void ImageLoaderMachO::doImageInit(const LinkContext& context) } if ( context.verboseInit ) dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath()); - 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); - }); + { + dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (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); @@ -2242,7 +2294,7 @@ void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) for (size_t j=0; j < count; ++j) { Initializer func = inits[j]; // verify initializers are in image - if ( ! this->containsAddress((void*)func) ) { + if ( ! this->containsAddress(stripPointer((void*)func)) ) { dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath()); } if ( ! dyld::gProcessInfo->libSystemInitialized ) { @@ -2254,9 +2306,10 @@ 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); - 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); - }); + { + dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (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 @@ -2354,8 +2407,11 @@ void ImageLoaderMachO::doTermination(const LinkContext& context) const size_t count = sect->size / sizeof(uintptr_t); for (size_t j=count; j > 0; --j) { Terminator func = terms[j-1]; +#if __has_feature(ptrauth_calls) + func = (Terminator)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0); +#endif // verify terminators are in image - if ( ! this->containsAddress((void*)func) ) { + if ( ! this->containsAddress(stripPointer((void*)func)) ) { dyld::throwf("termination function %p not in mapped image for %s\n", func, this->getPath()); } if ( context.verboseInit ) @@ -2481,9 +2537,7 @@ void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInF uintptr_t requestedLoadAddress = segPreferredLoadAddress(i) + slide; int protection = 0; if ( !segUnaccessible(i) ) { - // If has text-relocs, don't set x-bit initially. - // Instead set it later after text-relocs have been done. - if ( segExecutable(i) && !(segHasRebaseFixUps(i) && (slide != 0)) ) + if ( segExecutable(i) ) protection |= PROT_EXEC; if ( segReadable(i) ) protection |= PROT_READ; @@ -2582,12 +2636,13 @@ void ImageLoaderMachO::segProtect(unsigned int segIndex, const ImageLoader::Link } } +#if TEXT_RELOC_SUPPORT void ImageLoaderMachO::segMakeWritable(unsigned int segIndex, const ImageLoader::LinkContext& context) { vm_address_t addr = segActualLoadAddress(segIndex); vm_size_t size = segSize(segIndex); const bool setCurrentPermissions = false; - vm_prot_t protection = VM_PROT_WRITE | VM_PROT_READ; + vm_prot_t protection = VM_PROT_WRITE | VM_PROT_READ | VM_PROT_COPY; if ( segExecutable(segIndex) && !segHasRebaseFixUps(segIndex) ) protection |= VM_PROT_EXECUTE; kern_return_t r = vm_protect(mach_task_self(), addr, size, setCurrentPermissions, protection); @@ -2600,7 +2655,7 @@ void ImageLoaderMachO::segMakeWritable(unsigned int segIndex, const ImageLoader: (protection & PROT_READ) ? 'r' : '.', (protection & PROT_WRITE) ? 'w' : '.', (protection & PROT_EXEC) ? 'x' : '.' ); } } - +#endif const char* ImageLoaderMachO::findClosestSymbol(const mach_header* mh, const void* addr, const void** closestAddr) { @@ -2790,3 +2845,12 @@ uintptr_t ImageLoaderMachO::segPreferredAddress(const mach_header* mh, unsigned +uintptr_t ImageLoaderMachO::imageBaseAddress() const { + //printf("imageBaseAddress: %s %d->%d\n", getPath(), 0, segmentCount()); + for (unsigned int i = 0, e = segmentCount(); i != e; ++i) { + if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) + return segPreferredLoadAddress(i); + } + return 0; +} + diff --git a/src/ImageLoaderMachO.h b/src/ImageLoaderMachO.h index 5c48589..db90ff1 100644 --- a/src/ImageLoaderMachO.h +++ b/src/ImageLoaderMachO.h @@ -28,11 +28,21 @@ #include #include -#include +#include +#include + +#if __has_feature(ptrauth_calls) +#include +#endif #include "ImageLoader.h" #include "mach-o/dyld_images.h" +#define BIND_TYPE_THREADED_BIND 100 + + +#define BIND_TYPE_THREADED_REBASE 102 + // // ImageLoaderMachO is a subclass of ImageLoader which loads mach-o format files. @@ -51,8 +61,8 @@ public: void disableCoverageCheck() { fCoveredCodeLength = UINT64_MAX; } const char* getInstallPath() const; - virtual void* getMain() const; - virtual void* getThreadPC() const; + virtual void* getEntryFromLC_UNIXTHREAD() const; + virtual void* getEntryFromLC_MAIN() const; virtual const struct mach_header* machHeader() const; virtual uintptr_t getSlide() const; virtual const void* getEnd() const; @@ -85,6 +95,7 @@ public: virtual bool needsInitialization(); virtual bool getSectionContent(const char* segmentName, const char* sectionName, void** start, size_t* length); virtual void getUnwindInfo(dyld_unwind_sections* info); + virtual const struct macho_section* findSection(const void* imageInterior) const; virtual bool findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset); virtual bool usablePrebinding(const LinkContext& context) const; virtual unsigned int segmentCount() const; @@ -101,11 +112,14 @@ public: virtual uintptr_t segActualLoadAddress(unsigned int) const; virtual uintptr_t segPreferredLoadAddress(unsigned int) const; virtual uintptr_t segActualEndAddress(unsigned int) const; - virtual void registerInterposing(); + virtual void registerInterposing(const LinkContext& context); virtual uint32_t sdkVersion() const; virtual uint32_t minOSVersion() const; virtual const char* libPath(unsigned int) const; virtual bool notifyObjC() const { return fNotifyObjC; } + virtual bool overridesCachedDylib(uint32_t& num) const { num = fOverrideOfCacheImageNum; return (num != 0); } + virtual void setOverridesCachedDylib(uint32_t num) { fOverrideOfCacheImageNum = num; } + static void printStatisticsDetails(unsigned int imageCount, const InitializerTimingList&); static uint32_t minOSVersion(const mach_header*); @@ -117,9 +131,34 @@ public: static bool getLazyBindingInfo(uint32_t& lazyBindingInfoOffset, const uint8_t* lazyInfoStart, const uint8_t* lazyInfoEnd, uint8_t* segIndex, uintptr_t* segOffset, int* ordinal, const char** symbolName, bool* doneAfterBind); static uintptr_t segPreferredAddress(const mach_header* mh, unsigned segIndex); - static uintptr_t bindLocation(const LinkContext& context, uintptr_t location, uintptr_t value, - uint8_t type, const char* symbolName, - intptr_t addend, const char* inPath, const char* toPath, const char* msg); + + uintptr_t imageBaseAddress() const; + + struct ExtraBindData { + ExtraBindData() = default; + explicit ExtraBindData(uint64_t d) : data(d) { } + + union { + uint64_t data = 0; + }; + bool operator==(const ExtraBindData& other) const { + return this->data == other.data; + } + bool operator!=(const ExtraBindData& other) const { + return !(*this == other); + } + bool operator<(const ExtraBindData& other) const { + return data < other.data; + } + + }; + + static uintptr_t bindLocation(const LinkContext& context, uintptr_t baseVMAddress, + uintptr_t location, uintptr_t value, + uint8_t type, const char* symbolName, + intptr_t addend, const char* inPath, const char* toPath, const char* msg, + ExtraBindData *extraBindData, + uintptr_t fSlide); virtual void rebase(const LinkContext& context, uintptr_t slide) = 0; @@ -239,7 +278,8 @@ protected: fHasTerminators : 1, fNotifyObjC : 1, fRetainForObjC : 1, - fRegisteredAsRequiresCoalescing : 1; // Loading MH_DYLIB_STUB causing coalescable miscount + fRegisteredAsRequiresCoalescing : 1, // Loading MH_DYLIB_STUB causing coalescable miscount + fOverrideOfCacheImageNum : 12; static uint32_t fgSymbolTableBinarySearchs; diff --git a/src/ImageLoaderMachOClassic.cpp b/src/ImageLoaderMachOClassic.cpp index 86723e7..70068cd 100644 --- a/src/ImageLoaderMachOClassic.cpp +++ b/src/ImageLoaderMachOClassic.cpp @@ -601,11 +601,13 @@ bool ImageLoaderMachOClassic::isSubframeworkOf(const LinkContext& context, const return true; if ( context.imageSuffix != NULL ) { // when DYLD_IMAGE_SUFFIX is used, lastSlash string needs imageSuffix removed from end - char reexportAndSuffix[strlen(context.imageSuffix)+strlen(exportThruName)+1]; - strcpy(reexportAndSuffix, exportThruName); - strcat(reexportAndSuffix, context.imageSuffix); - if ( strcmp(&lastSlash[1], reexportAndSuffix) == 0 ) - return true; + for(const char* const* suffix = context.imageSuffix; *suffix != NULL; ++suffix) { + char reexportAndSuffix[strlen(*suffix)+strlen(exportThruName)+1]; + strcpy(reexportAndSuffix, exportThruName); + strcat(reexportAndSuffix, *suffix); + if ( strcmp(&lastSlash[1], reexportAndSuffix) == 0 ) + return true; + } } } } @@ -647,11 +649,13 @@ bool ImageLoaderMachOClassic::hasSubLibrary(const LinkContext& context, const Im return true; if ( context.imageSuffix != NULL ) { // when DYLD_IMAGE_SUFFIX is used, childLeafName string needs imageSuffix removed from end - char aSubLibNameAndSuffix[strlen(context.imageSuffix)+strlen(aSubLibName)+1]; - strcpy(aSubLibNameAndSuffix, aSubLibName); - strcat(aSubLibNameAndSuffix, context.imageSuffix); - if ( strcmp(aSubLibNameAndSuffix, childLeafName) == 0 ) - return true; + for(const char* const* suffix = context.imageSuffix; *suffix != NULL; ++suffix) { + char aSubLibNameAndSuffix[strlen(*suffix)+strlen(aSubLibName)+1]; + strcpy(aSubLibNameAndSuffix, aSubLibName); + strcat(aSubLibNameAndSuffix, *suffix); + if ( strcmp(aSubLibNameAndSuffix, childLeafName) == 0 ) + return true; + } } } break; @@ -680,11 +684,13 @@ bool ImageLoaderMachOClassic::hasSubLibrary(const LinkContext& context, const Im return true; if ( context.imageSuffix != NULL ) { // when DYLD_IMAGE_SUFFIX is used, lastSlash string needs imageSuffix removed from end - char umbrellaAndSuffix[strlen(context.imageSuffix)+strlen(aSubUmbrellaName)+1]; - strcpy(umbrellaAndSuffix, aSubUmbrellaName); - strcat(umbrellaAndSuffix, context.imageSuffix); - if ( strcmp(umbrellaAndSuffix, &lastSlash[1]) == 0 ) - return true; + for(const char* const* suffix = context.imageSuffix; *suffix != NULL; ++suffix) { + char umbrellaAndSuffix[strlen(*suffix)+strlen(aSubUmbrellaName)+1]; + strcpy(umbrellaAndSuffix, aSubUmbrellaName); + strcat(umbrellaAndSuffix, *suffix); + if ( strcmp(umbrellaAndSuffix, &lastSlash[1]) == 0 ) + return true; + } } } break; @@ -1083,7 +1089,7 @@ uintptr_t ImageLoaderMachOClassic::resolveUndefined(const LinkContext& context, // symbol requires searching images with coalesced symbols (not done during prebinding) if ( !context.prebinding && !dontCoalesce && (symbolIsWeakReference(undefinedSymbol) || symbolIsWeakDefinition(undefinedSymbol)) ) { const Symbol* sym; - if ( context.coalescedExportFinder(symbolName, &sym, foundIn) ) { + if ( context.coalescedExportFinder(symbolName, &sym, foundIn, nullptr) ) { if ( *foundIn != this ) context.addDynamicReference(this, const_cast(*foundIn)); return (*foundIn)->getExportedSymbolAddress(sym, context, this); @@ -1643,7 +1649,7 @@ void ImageLoaderMachOClassic::updateUsesCoalIterator(CoalIterator& it, uintptr_t if ( reloc->r_pcrel ) type = BIND_TYPE_TEXT_PCREL32; #endif - this->bindLocation(context, (uintptr_t)location, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); + this->bindLocation(context, this->imageBaseAddress(), (uintptr_t)location, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; } } diff --git a/src/ImageLoaderMachOCompressed.cpp b/src/ImageLoaderMachOCompressed.cpp index 627d572..a908c91 100644 --- a/src/ImageLoaderMachOCompressed.cpp +++ b/src/ImageLoaderMachOCompressed.cpp @@ -35,17 +35,40 @@ #include #include #include -#include #include #include #include #include "ImageLoaderMachOCompressed.h" #include "mach-o/dyld_images.h" +#include "Closure.h" +#include "Array.h" #ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE #define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 #endif + +#ifndef BIND_OPCODE_THREADED +#define BIND_OPCODE_THREADED 0xD0 +#endif + +#ifndef BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB +#define BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB 0x00 +#endif + +#ifndef BIND_SUBOPCODE_THREADED_APPLY +#define BIND_SUBOPCODE_THREADED_APPLY 0x01 +#endif + + +#ifndef BIND_SPECIAL_DYLIB_WEAK_LOOKUP +#define BIND_SPECIAL_DYLIB_WEAK_LOOKUP -3 +#endif + +#ifndef CPU_SUBTYPE_ARM64_E + #define CPU_SUBTYPE_ARM64_E 2 +#endif + // relocation_info.r_length field has value 3 for 64-bit executables and value 2 for 32-bit executables #if __LP64__ #define RELOC_SIZE 3 @@ -63,11 +86,6 @@ 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 @@ -489,7 +507,7 @@ void ImageLoaderMachOCompressed::rebase(const LinkContext& context, uintptr_t sl fgTotalRebaseFixups += count; break; default: - dyld::throwf("bad rebase opcode %d", *p); + dyld::throwf("bad rebase opcode %d", *(p-1)); } } } @@ -662,6 +680,95 @@ uintptr_t ImageLoaderMachOCompressed::resolveFlat(const LinkContext& context, co } +#if USES_CHAINED_BINDS +static void patchCacheUsesOf(const ImageLoader::LinkContext& context, const dyld3::closure::Image* overriddenImage, + uint32_t cacheOffsetOfImpl, const char* symbolName, uintptr_t newImpl) +{ + uintptr_t cacheStart = (uintptr_t)context.dyldCache; + overriddenImage->forEachPatchableUseOfExport(cacheOffsetOfImpl, ^(dyld3::closure::Image::PatchableExport::PatchLocation patchLocation) { + uintptr_t* loc = (uintptr_t*)(cacheStart+patchLocation.cacheOffset); +#if __has_feature(ptrauth_calls) + if ( patchLocation.authenticated ) { + dyld3::MachOLoaded::ChainedFixupPointerOnDisk fixupInfo; + fixupInfo.authRebase.auth = true; + fixupInfo.authRebase.addrDiv = patchLocation.usesAddressDiversity; + fixupInfo.authRebase.diversity = patchLocation.discriminator; + fixupInfo.authRebase.key = patchLocation.key; + uintptr_t newValue = fixupInfo.signPointer(loc, newImpl + patchLocation.getAddend()); + if ( *loc != newValue ) { + *loc = newValue; + if ( context.verboseBind ) + dyld::log("dyld: cache fixup: *%p = %p (JOP: diversity 0x%04X, addr-div=%d, key=%s) to %s\n", + loc, (void*)newValue, patchLocation.discriminator, patchLocation.usesAddressDiversity, patchLocation.keyName(), symbolName); + } + return; + } +#endif + uintptr_t newValue =newImpl + patchLocation.getAddend(); + if ( *loc != newValue ) { + *loc = newValue; + if ( context.verboseBind ) + dyld::log("dyld: cache fixup: *%p = %p to %s\n", loc, (void*)newValue, symbolName); + } + }); +} +#endif + + +uintptr_t ImageLoaderMachOCompressed::resolveWeak(const LinkContext& context, const char* symbolName, bool weak_import, + bool runResolver, const ImageLoader** foundIn) +{ + const Symbol* sym; +#if USES_CHAINED_BINDS + __block uintptr_t foundOutsideCache = 0; + __block uintptr_t lastFoundInCache = 0; + CoalesceNotifier notifier = ^(const Symbol* implSym, const ImageLoader* implIn, const mach_header* implMh) { + //dyld::log("notifier: found %s in %p %s\n", symbolName, implMh, implIn->getPath()); + // This block is only called in dyld2 mode when a non-cached image is search for which weak-def implementation to use + // As a side effect of that search we notice any implementations outside and inside the cache, + // and use that to trigger patching the cache to use the implementation outside the cache. + uintptr_t implAddr = implIn->getExportedSymbolAddress(implSym, context, nullptr, false, symbolName); + if ( ((dyld3::MachOLoaded*)implMh)->inDyldCache() ) { + if ( foundOutsideCache != 0 ) { + // have an implementation in cache and and earlier one not in the cache, patch cache to use earlier one + lastFoundInCache = implAddr; + uint32_t imageIndex; + if ( context.dyldCache->findMachHeaderImageIndex(implMh, imageIndex) ) { + const dyld3::closure::Image* overriddenImage = context.dyldCache->cachedDylibsImageArray()->imageForNum(imageIndex+1); + uint32_t cacheOffsetOfImpl = (uint32_t)((uintptr_t)implAddr - (uintptr_t)context.dyldCache); + patchCacheUsesOf(context, overriddenImage, cacheOffsetOfImpl, symbolName, foundOutsideCache); + } + } + } + else { + // record first non-cache implementation + if ( foundOutsideCache == 0 ) + foundOutsideCache = implAddr; + } + }; +#else + CoalesceNotifier notifier = nullptr; +#endif + if ( context.coalescedExportFinder(symbolName, &sym, foundIn, notifier) ) { + if ( *foundIn != this ) + context.addDynamicReference(this, const_cast(*foundIn)); + return (*foundIn)->getExportedSymbolAddress(sym, context, this, runResolver); + } + // if a bundle is loaded privately the above will not find its exports + if ( this->isBundle() && this->hasHiddenExports() ) { + // look in self for needed symbol + sym = this->findShallowExportedSymbol(symbolName, foundIn); + if ( sym != NULL ) + return (*foundIn)->getExportedSymbolAddress(sym, context, this, runResolver); + } + if ( weak_import ) { + // definition can't be found anywhere, ok because it is weak, just return 0 + return 0; + } + throwSymbolNotFound(context, symbolName, this->getPath(), "", "weak"); +} + + uintptr_t ImageLoaderMachOCompressed::resolveTwolevel(const LinkContext& context, const char* symbolName, const ImageLoader* definedInImage, const ImageLoader* requestorImage, unsigned requestorOrdinalOfDef, bool weak_import, bool runResolver, const ImageLoader** foundIn) @@ -718,6 +825,9 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const if ( context.bindFlat || (libraryOrdinal == BIND_SPECIAL_DYLIB_FLAT_LOOKUP) ) { symbolAddress = this->resolveFlat(context, symbolName, weak_import, runResolver, targetImage); } + else if ( libraryOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) { + symbolAddress = this->resolveWeak(context, symbolName, false, runResolver, targetImage); + } else { if ( libraryOrdinal == BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE ) { *targetImage = context.mainExecutable; @@ -763,18 +873,24 @@ uintptr_t ImageLoaderMachOCompressed::resolve(const LinkContext& context, const return symbolAddress; } -uintptr_t ImageLoaderMachOCompressed::bindAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char* symbolName, - uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, const char* msg, - LastLookup* last, bool runResolver) +uintptr_t ImageLoaderMachOCompressed::bindAt(const LinkContext& context, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { const ImageLoader* targetImage; uintptr_t symbolAddress; // resolve symbol - symbolAddress = this->resolve(context, symbolName, symbolFlags, libraryOrdinal, &targetImage, last, runResolver); + if (type == BIND_TYPE_THREADED_REBASE) { + symbolAddress = 0; + targetImage = nullptr; + } else + symbolAddress = image->resolve(context, symbolName, symbolFlags, libraryOrdinal, &targetImage, last, runResolver); // do actual update - return this->bindLocation(context, addr, symbolAddress, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, msg); + return image->bindLocation(context, image->imageBaseAddress(), addr, symbolAddress, type, symbolName, addend, image->getPath(), targetImage ? targetImage->getPath() : NULL, msg, extraBindData, image->fSlide); } @@ -795,6 +911,21 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa // note: flat-namespace binaries need to have imports rebound (even if correctly prebound) if ( this->usablePrebinding(context) ) { // don't need to bind + // except weak which may now be inline with the regular binds + if (this->participatesInCoalescing()) { + // run through all binding opcodes + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + if ( libraryOrdinal != BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) + return (uintptr_t)0; + return ImageLoaderMachOCompressed::bindAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); + } } else { uint64_t t0 = mach_absolute_time(); @@ -804,9 +935,21 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa if ( fTextSegmentBinds ) this->makeTextSegmentWritable(context, true); #endif - + + uint32_t ignore; + bool bindingBecauseOfRoot = ( this->overridesCachedDylib(ignore) || this->inSharedCache() ); + vmAccountingSetSuspended(context, bindingBecauseOfRoot); + // run through all binding opcodes - eachBind(context, &ImageLoaderMachOCompressed::bindAt); + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::bindAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); #if TEXT_RELOC_SUPPORT // if there were __TEXT fixups, restore write protection @@ -824,7 +967,7 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa // the stub may have been altered to point to a shared lazy pointer. if ( fInSharedCache ) this->updateOptimizedLazyPointers(context); - + // tell kernel we are done with chunks of LINKEDIT if ( !context.preFetchDisabled ) this->markFreeLINKEDIT(context); @@ -832,6 +975,23 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa uint64_t t1 = mach_absolute_time(); ImageLoader::fgTotalRebindCacheTime += (t1-t0); } + +#if USES_CHAINED_BINDS + // See if this dylib overrides something in the dyld cache + uint32_t dyldCacheOverrideImageNum; + if ( context.dyldCache && overridesCachedDylib(dyldCacheOverrideImageNum) ) { + // need to patch all other places in cache that point to the overridden dylib, to point to this dylib instead + const dyld3::closure::Image* overriddenImage = context.dyldCache->cachedDylibsImageArray()->imageForNum(dyldCacheOverrideImageNum); + overriddenImage->forEachPatchableExport(^(uint32_t cacheOffsetOfImpl, const char* exportName) { + uintptr_t newImpl = 0; + const ImageLoader* foundIn; + if ( const ImageLoader::Symbol* sym = this->findShallowExportedSymbol(exportName, &foundIn) ) { + newImpl = foundIn->getExportedSymbolAddress(sym, context, this); + } + patchCacheUsesOf(context, overriddenImage, cacheOffsetOfImpl, exportName, newImpl); + }); + } +#endif // set up dyld entry points in image // do last so flat main executables will have __dyld or __program_vars set up @@ -842,49 +1002,179 @@ void ImageLoaderMachOCompressed::doBind(const LinkContext& context, bool forceLa void ImageLoaderMachOCompressed::doBindJustLazies(const LinkContext& context) { - eachLazyBind(context, &ImageLoaderMachOCompressed::bindAt); + eachLazyBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::bindAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); } -#if __arm__ || __arm64__ -int ImageLoaderMachOCompressed::vmAccountingSetSuspended(bool suspend, const LinkContext& context) +void ImageLoaderMachOCompressed::registerInterposing(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); + // mach-o files advertise interposing by having a __DATA __interpose section + struct InterposeData { uintptr_t replacement; uintptr_t replacee; }; + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); + const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; + for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { + if ( ((sect->flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") == 0) && (strcmp(seg->segname, "__DATA") == 0)) ) { + // Ensure section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("interpose section has malformed address range for %s\n", this->getPath()); + __block uintptr_t sectionStart = sect->addr + fSlide; + __block uintptr_t sectionEnd = sectionStart + sect->size; + const size_t count = sect->size / sizeof(InterposeData); + InterposeData interposeArray[count]; + // Note, we memcpy here as rebases may have already been applied. + memcpy(&interposeArray[0], (void*)sectionStart, sect->size); + __block InterposeData *interposeArrayStart = &interposeArray[0]; + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, uintptr_t addr, uint8_t type, + const char* symbolName, uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + if (addr >= sectionStart && addr < sectionEnd) { + if ( context.verboseInterposing ) { + dyld::log("dyld: interposing %s at 0x%lx in range 0x%lx..0x%lx\n", + symbolName, addr, sectionStart, sectionEnd); + } + const ImageLoader* targetImage; + uintptr_t symbolAddress; + + // resolve symbol + if (type == BIND_TYPE_THREADED_REBASE) { + symbolAddress = 0; + targetImage = nullptr; + } else + symbolAddress = image->resolve(ctx, symbolName, symbolFlags, libraryOrdinal, &targetImage, last, runResolver); + + uintptr_t newValue = symbolAddress+addend; + uintptr_t index = (addr - sectionStart) / sizeof(uintptr_t); + switch (type) { + case BIND_TYPE_POINTER: + ((uintptr_t*)interposeArrayStart)[index] = newValue; + break; + case BIND_TYPE_TEXT_ABSOLUTE32: + // unreachable! + abort(); + case BIND_TYPE_TEXT_PCREL32: + // unreachable! + abort(); + case BIND_TYPE_THREADED_BIND: + ((uintptr_t*)interposeArrayStart)[index] = newValue; + break; + case BIND_TYPE_THREADED_REBASE: { + // Regular pointer which needs to fit in 51-bits of value. + // C++ RTTI uses the top bit, so we'll allow the whole top-byte + // and the signed-extended bottom 43-bits to be fit in to 51-bits. + uint64_t top8Bits = (*(uint64_t*)addr) & 0x0007F80000000000ULL; + uint64_t bottom43Bits = (*(uint64_t*)addr) & 0x000007FFFFFFFFFFULL; + uint64_t targetValue = ( top8Bits << 13 ) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); + newValue = (uintptr_t)(targetValue + fSlide); + ((uintptr_t*)interposeArrayStart)[index] = newValue; + break; + } + default: + dyld::throwf("bad bind type %d", type); + } + } + return (uintptr_t)0; + }); + for (size_t j=0; j < count; ++j) { + ImageLoader::InterposeTuple tuple; + tuple.replacement = interposeArray[j].replacement; + tuple.neverImage = this; + tuple.onlyImage = NULL; + tuple.replacee = interposeArray[j].replacee; + if ( context.verboseInterposing ) { + dyld::log("dyld: interposing index %d 0x%lx with 0x%lx\n", + (unsigned)j, interposeArray[j].replacee, interposeArray[j].replacement); + } + // ignore interposing on a weak function that does not exist + if ( tuple.replacee == 0 ) + continue; + // verify that replacement is in this image + if ( this->containsAddress((void*)tuple.replacement) ) { + // chain to any existing interpositions + for (std::vector::iterator it=fgInterposingTuples.begin(); it != fgInterposingTuples.end(); it++) { + if ( it->replacee == tuple.replacee ) { + tuple.replacee = it->replacement; + } + } + ImageLoader::fgInterposingTuples.push_back(tuple); + } + } + } + } + } + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } } + +bool ImageLoaderMachOCompressed::usesChainedFixups() const +{ +#if __arm64e__ + return ( machHeader()->cpusubtype == CPU_SUBTYPE_ARM64_E ); +#else + return false; #endif +} + +struct ThreadedBindData { + ThreadedBindData(const char* symbolName, int64_t addend, long libraryOrdinal, uint8_t symbolFlags, uint8_t type) + : symbolName(symbolName), addend(addend), libraryOrdinal(libraryOrdinal), symbolFlags(symbolFlags), type(type) { } + + std::tuple pack() const { + return std::make_tuple(symbolName, addend, libraryOrdinal, symbolFlags, type); + } + + const char* symbolName = nullptr; + int64_t addend = 0; + long libraryOrdinal = 0; + uint8_t symbolFlags = 0; + uint8_t type = 0; +}; 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; + try { + const dysymtab_command* dynSymbolTable = NULL; + const macho_nlist* symbolTable = NULL; + const char* symbolTableStrings = NULL; + uint32_t maxStringOffset = 0; + + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SYMTAB: + { + const struct symtab_command* symtab = (struct symtab_command*)cmd; + symbolTableStrings = (const char*)&fLinkEditBase[symtab->stroff]; + maxStringOffset = symtab->strsize; + symbolTable = (macho_nlist*)(&fLinkEditBase[symtab->symoff]); + } + break; + case LC_DYSYMTAB: + dynSymbolTable = (struct dysymtab_command*)cmd; + break; } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - 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; uintptr_t address = segActualLoadAddress(0); @@ -897,7 +1187,11 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl intptr_t addend = 0; uintptr_t count; uintptr_t skip; - uintptr_t segOffset; + uintptr_t segOffset = 0; + + dyld3::OverflowSafeArray ordinalTable; + bool useThreadedRebaseBind = false; + ExtraBindData extraBindData; LastLookup last = { 0, 0, NULL, 0, NULL }; const uint8_t* const start = fLinkEditBase + fDyldInfo->bind_off; const uint8_t* const end = &start[fDyldInfo->bind_size]; @@ -964,16 +1258,21 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) - throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); - if ( symbolName == NULL ) - dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); - if ( segmentIndex == -1 ) - dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); - if ( !libraryOrdinalSet ) - dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); - address += sizeof(intptr_t); + if (!useThreadedRebaseBind) { + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) + throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); + if ( symbolName == NULL ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); + if ( segmentIndex == -1 ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); + if ( !libraryOrdinalSet ) + dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + &extraBindData, "", &last, false); + address += sizeof(intptr_t); + } else { + ordinalTable.push_back(ThreadedBindData(symbolName, addend, libraryOrdinal, symboFlags, type)); + } break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) @@ -984,7 +1283,8 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); if ( !libraryOrdinalSet ) dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + &extraBindData, "", &last, false); address += read_uleb128(p, end) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: @@ -996,7 +1296,8 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB"); if ( !libraryOrdinalSet ) dyld::throwf("BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL*"); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + &extraBindData, "", &last, false); address += immediate*sizeof(intptr_t) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: @@ -1011,10 +1312,69 @@ void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handl for (uint32_t i=0; i < count; ++i) { if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "", &last, false); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + &extraBindData, "", &last, false); address += skip + sizeof(intptr_t); } - break; + break; + case BIND_OPCODE_THREADED: + if (sizeof(intptr_t) != 8) { + dyld::throwf("BIND_OPCODE_THREADED require 64-bit"); + return; + } + // Note the immediate is a sub opcode + switch (immediate) { + case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: + count = read_uleb128(p, end); + ordinalTable.clear(); + // FIXME: ld64 wrote the wrong value here and we need to offset by 1 for now. + ordinalTable.reserve(count + 1); + useThreadedRebaseBind = true; + break; + case BIND_SUBOPCODE_THREADED_APPLY: { + uint64_t delta = 0; + do { + address = segmentStartAddress + segOffset; + uint64_t value = *(uint64_t*)address; + + + bool isRebase = (value & (1ULL << 62)) == 0; + if (isRebase) { + { + // Call the bind handler which knows about our bind type being set to rebase + handler(context, this, address, BIND_TYPE_THREADED_REBASE, nullptr, 0, 0, 0, + nullptr, "", &last, false); + } + } else { + // the ordinal is bits [0..15] + uint16_t ordinal = value & 0xFFFF; + if (ordinal >= ordinalTable.count()) { + dyld::throwf("bind ordinal is out of range\n"); + return; + } + std::tie(symbolName, addend, libraryOrdinal, symboFlags, type) = ordinalTable[ordinal].pack(); + if ( (address < segmentStartAddress) || (address >= segmentEndAddress) ) + throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); + { + handler(context, this, address, BIND_TYPE_THREADED_BIND, + symbolName, symboFlags, addend, libraryOrdinal, + nullptr, "", &last, false); + } + } + + // The delta is bits [51..61] + // And bit 62 is to tell us if we are a rebase (0) or bind (1) + value &= ~(1ULL << 62); + delta = ( value & 0x3FF8000000000000 ) >> 51; + segOffset += delta * sizeof(intptr_t); + } while ( delta != 0 ); + break; + } + + default: + dyld::throwf("bad threaded bind subopcode 0x%02X", *p); + } + break; default: dyld::throwf("bad bind opcode %d in bind info", *p); } @@ -1104,7 +1464,8 @@ void ImageLoaderMachOCompressed::eachLazyBind(const LinkContext& context, bind_h throwBadBindingAddress(address, segmentEndAddress, segmentIndex, start, end, p); if ( symbolName == NULL ) dyld::throwf("BIND_OPCODE_DO_BIND missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM"); - (this->*handler)(context, address, type, symbolName, symboFlags, addend, libraryOrdinal, "forced lazy ", NULL, false); + handler(context, this, address, type, symbolName, symboFlags, addend, libraryOrdinal, + NULL, "forced lazy ", NULL, false); address += sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: @@ -1183,7 +1544,8 @@ uintptr_t ImageLoaderMachOCompressed::doBindLazySymbol(uintptr_t* lazyPointer, c if ( !twoLevel || context.bindFlat ) libraryOrdinal = BIND_SPECIAL_DYLIB_FLAT_LOOKUP; uintptr_t ptrToBind = (uintptr_t)lazyPointer; - uintptr_t symbolAddr = bindAt(context, ptrToBind, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal, "lazy ", NULL); + uintptr_t symbolAddr = bindAt(context, this, ptrToBind, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal, + NULL, "lazy ", NULL); ++fgTotalLazyBindFixups; return symbolAddr; } @@ -1229,7 +1591,8 @@ uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol(uint32_t lazyBindingI if ( segOffset > segSize(segIndex) ) dyld::throwf("BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has offset 0x%08lX beyond segment size (0x%08lX)", segOffset, segSize(segIndex)); uintptr_t address = segActualLoadAddress(segIndex) + segOffset; - result = this->bindAt(context, address, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal, "lazy ", NULL, true); + result = bindAt(context, this, address, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal, + NULL, "lazy ", NULL, true); // Some old apps had multiple lazy symbols bound at once } while (!doneAfterBind && !context.strictMachORequired); @@ -1422,17 +1785,17 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - bindLocation(context, address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); + bindLocation(context, this->imageBaseAddress(), address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; address += sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - bindLocation(context, address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); + bindLocation(context, this->imageBaseAddress(), address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; address += read_uleb128(p, end) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - bindLocation(context, address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); + bindLocation(context, this->imageBaseAddress(), address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; address += immediate*sizeof(intptr_t) + sizeof(intptr_t); break; @@ -1440,7 +1803,7 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt count = read_uleb128(p, end); skip = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - bindLocation(context, address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak "); + bindLocation(context, this->imageBaseAddress(), address, value, type, symbolName, addend, this->getPath(), targetImage ? targetImage->getPath() : NULL, "weak ", NULL, fSlide); boundSomething = true; address += skip + sizeof(intptr_t); } @@ -1454,13 +1817,16 @@ void ImageLoaderMachOCompressed::updateUsesCoalIterator(CoalIterator& it, uintpt context.addDynamicReference(this, targetImage); } -uintptr_t ImageLoaderMachOCompressed::interposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char*, - uint8_t, intptr_t, long, const char*, LastLookup*, bool runResolver) +uintptr_t ImageLoaderMachOCompressed::interposeAt(const LinkContext& context, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char*, + uint8_t, intptr_t, long, + ExtraBindData *extraBindData, + const char*, LastLookup*, bool runResolver) { if ( type == BIND_TYPE_POINTER ) { uintptr_t* fixupLocation = (uintptr_t*)addr; uintptr_t curValue = *fixupLocation; - uintptr_t newValue = interposedAddress(context, curValue, this); + uintptr_t newValue = interposedAddress(context, curValue, image); if ( newValue != curValue) { *fixupLocation = newValue; } @@ -1474,13 +1840,32 @@ void ImageLoaderMachOCompressed::doInterpose(const LinkContext& context) dyld::log("dyld: interposing %lu tuples onto image: %s\n", fgInterposingTuples.size(), this->getPath()); // update prebound symbols - eachBind(context, &ImageLoaderMachOCompressed::interposeAt); - eachLazyBind(context, &ImageLoaderMachOCompressed::interposeAt); + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::interposeAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); + eachLazyBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::interposeAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); } -uintptr_t ImageLoaderMachOCompressed::dynamicInterposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char* symbolName, - uint8_t, intptr_t, long, const char*, LastLookup*, bool runResolver) +uintptr_t ImageLoaderMachOCompressed::dynamicInterposeAt(const LinkContext& context, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t, intptr_t, long, + ExtraBindData *extraBindData, + const char*, LastLookup*, bool runResolver) { if ( type == BIND_TYPE_POINTER ) { uintptr_t* fixupLocation = (uintptr_t*)addr; @@ -1492,7 +1877,7 @@ uintptr_t ImageLoaderMachOCompressed::dynamicInterposeAt(const LinkContext& cont if ( value == (uintptr_t)context.dynamicInterposeArray[i].replacee ) { if ( context.verboseInterposing ) { dyld::log("dyld: dynamic interposing: at %p replace %p with %p in %s\n", - fixupLocation, context.dynamicInterposeArray[i].replacee, context.dynamicInterposeArray[i].replacement, this->getPath()); + fixupLocation, context.dynamicInterposeArray[i].replacee, context.dynamicInterposeArray[i].replacement, image->getPath()); } *fixupLocation = (uintptr_t)context.dynamicInterposeArray[i].replacement; } @@ -1507,8 +1892,24 @@ void ImageLoaderMachOCompressed::dynamicInterpose(const LinkContext& context) dyld::log("dyld: dynamic interposing %lu tuples onto image: %s\n", context.dynamicInterposeCount, this->getPath()); // update already bound references to symbols - eachBind(context, &ImageLoaderMachOCompressed::dynamicInterposeAt); - eachLazyBind(context, &ImageLoaderMachOCompressed::dynamicInterposeAt); + eachBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::dynamicInterposeAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); + eachLazyBind(context, ^(const LinkContext& ctx, ImageLoaderMachOCompressed* image, + uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symbolFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver) { + return ImageLoaderMachOCompressed::dynamicInterposeAt(ctx, image, addr, type, symbolName, symbolFlags, + addend, libraryOrdinal, extraBindData, + msg, last, runResolver); + }); } const char* ImageLoaderMachOCompressed::findClosestSymbol(const void* addr, const void** closestAddr) const diff --git a/src/ImageLoaderMachOCompressed.h b/src/ImageLoaderMachOCompressed.h index d042387..23bd022 100644 --- a/src/ImageLoaderMachOCompressed.h +++ b/src/ImageLoaderMachOCompressed.h @@ -66,6 +66,8 @@ public: virtual bool incrementCoalIterator(CoalIterator&); virtual uintptr_t getAddressCoalIterator(CoalIterator&, const LinkContext& contex); virtual void updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, unsigned targetIndex, const LinkContext& context); + virtual void registerInterposing(const LinkContext& context); + virtual bool usesChainedFixups() const; protected: virtual void doInterpose(const LinkContext& context); @@ -89,15 +91,18 @@ protected: #if PREBOUND_IMAGE_SUPPORT virtual void resetPreboundLazyPointers(const LinkContext& context); #endif + virtual uintptr_t resolveWeak(const LinkContext& context, const char* symbolName, bool weak_import, bool runResolver, + const ImageLoader** foundIn); private: struct LastLookup { long ordinal; uint8_t flags; const char* name; uintptr_t result; const ImageLoader* foundIn; }; - typedef uintptr_t (ImageLoaderMachOCompressed::*bind_handler)(const LinkContext& context, uintptr_t addr, uint8_t type, - const char* symbolName, uint8_t symboFlags, intptr_t addend, long libraryOrdinal, - const char* msg, LastLookup* last, bool runResolver); + typedef uintptr_t (^bind_handler)(const LinkContext& context, ImageLoaderMachOCompressed* image, uintptr_t addr, uint8_t type, + const char* symbolName, uint8_t symboFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver); void eachLazyBind(const LinkContext& context, bind_handler); void eachBind(const LinkContext& context, bind_handler); @@ -114,8 +119,10 @@ private: void rebaseAt(const LinkContext& context, uintptr_t addr, uintptr_t slide, uint8_t type); void throwBadRebaseAddress(uintptr_t address, uintptr_t segmentEndAddress, int segmentIndex, const uint8_t* startOpcodes, const uint8_t* endOpcodes, const uint8_t* pos); - uintptr_t bindAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char* symbolName, - uint8_t symboFlags, intptr_t addend, long libraryOrdinal, const char* msg, + static uintptr_t bindAt(const LinkContext& context, ImageLoaderMachOCompressed* image, uintptr_t addr, uint8_t type, const char* symbolName, + uint8_t symboFlags, intptr_t addend, long libraryOrdinal, + ExtraBindData *extraBindData, + const char* msg, LastLookup* last, bool runResolver=false); void bindCompressed(const LinkContext& context); void throwBadBindingAddress(uintptr_t address, uintptr_t segmentEndAddress, int segmentIndex, @@ -129,21 +136,19 @@ private: uintptr_t resolveTwolevel(const LinkContext& context, const char* symbolName, const ImageLoader* definedInImage, const ImageLoader* requestorImage, unsigned requestorOrdinalOfDef, bool weak_import, bool runResolver, const ImageLoader** foundInn); - uintptr_t interposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char*, - uint8_t, intptr_t, long, const char*, LastLookup*, bool runResolver); - uintptr_t dynamicInterposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char*, - uint8_t, intptr_t, long, const char*, LastLookup*, bool runResolver); + static uintptr_t interposeAt(const LinkContext& context, ImageLoaderMachOCompressed* image, uintptr_t addr, uint8_t type, const char*, + uint8_t, intptr_t, long, + ExtraBindData *extraBindData, + const char*, LastLookup*, bool runResolver); + static uintptr_t dynamicInterposeAt(const LinkContext& context, ImageLoaderMachOCompressed* image, uintptr_t addr, uint8_t type, const char*, + uint8_t, intptr_t, long, + ExtraBindData *extraBindData, + const char*, LastLookup*, bool runResolver); 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 a54625d..54929e1 100644 --- a/src/ImageLoaderMegaDylib.cpp +++ b/src/ImageLoaderMegaDylib.cpp @@ -460,17 +460,17 @@ void ImageLoaderMegaDylib::updateUsesCoalIterator(CoalIterator& it, uintptr_t va address += read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: - ImageLoaderMachO::bindLocation(context, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak "); + ImageLoaderMachO::bindLocation(context, 0, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak ", NULL, _slide); boundSomething = true; address += sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: - ImageLoaderMachO::bindLocation(context, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak "); + ImageLoaderMachO::bindLocation(context, 0, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak ", NULL, _slide); boundSomething = true; address += read_uleb128(p, end) + sizeof(intptr_t); break; case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: - ImageLoaderMachO::bindLocation(context, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak "); + ImageLoaderMachO::bindLocation(context, 0, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak ", NULL, _slide); boundSomething = true; address += immediate*sizeof(intptr_t) + sizeof(intptr_t); break; @@ -478,7 +478,7 @@ void ImageLoaderMegaDylib::updateUsesCoalIterator(CoalIterator& it, uintptr_t va count = read_uleb128(p, end); skip = read_uleb128(p, end); for (uint32_t i=0; i < count; ++i) { - ImageLoaderMachO::bindLocation(context, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak "); + ImageLoaderMachO::bindLocation(context, 0, address, value, type, symbolName, addend, getIndexedPath((unsigned)it.imageIndex), targetImagePath, "weak ", NULL, _slide); boundSomething = true; address += skip + sizeof(intptr_t); } @@ -777,24 +777,37 @@ bool ImageLoaderMegaDylib::findInChainedTriesAndDependents(const LinkContext& co } -bool ImageLoaderMegaDylib::flatFindSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image) +bool ImageLoaderMegaDylib::flatFindSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image, ImageLoader::CoalesceNotifier notifier) { + bool found = false; // check export trie of all in-use images for (unsigned i=0; i < _imageCount ; ++i) { uint16_t imageIndex = _bottomUpArray[i]; if ( _stateFlags[imageIndex] == kStateUnused ) continue; +#if USES_CHAINED_BINDS + const macho_header* mh = getIndexedMachHeader(imageIndex); + if ( onlyInCoalesced && (mh->flags & MH_WEAK_DEFINES) == 0 ) + continue; +#else if ( onlyInCoalesced && (_imageExtras[imageIndex].weakBindingsSize == 0) ) continue; +#endif const uint8_t* exportNode; const uint8_t* exportTrieEnd; if ( exportTrieHasNode(name, imageIndex, &exportNode, &exportTrieEnd) ) { - *sym = (Symbol*)exportNode; - *image = this; - return true; + if ( notifier ) + notifier((Symbol*)exportNode, this, (mach_header*)getIndexedMachHeader(imageIndex)); + if ( !found ) { + *sym = (Symbol*)exportNode; + *image = this; + found = true; + } + if ( !onlyInCoalesced ) + return true; } } - return false; + return found; } @@ -881,9 +894,10 @@ 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); - dyld3::kdebug_trace_dyld_duration(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)func, 0, ^{ + { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)getIndexedMachHeader(imageIndex), (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 ) { diff --git a/src/ImageLoaderMegaDylib.h b/src/ImageLoaderMegaDylib.h index 6046da6..80ea046 100644 --- a/src/ImageLoaderMegaDylib.h +++ b/src/ImageLoaderMegaDylib.h @@ -27,7 +27,8 @@ #define __IMAGELOADER_MEGADYLIB__ #include -#include +#include +#include #include "ImageLoaderMachO.h" #include "dyld_cache_format.h" @@ -55,8 +56,8 @@ public: virtual const char* getInstallPath() const; virtual bool inSharedCache() const { return true; } virtual bool containsSymbol(const void* addr) const { unreachable(); } - virtual void* getThreadPC() const { unreachable(); } - virtual void* getMain() const { unreachable(); } + virtual void* getEntryFromLC_MAIN() const { unreachable(); } + virtual void* getEntryFromLC_UNIXTHREAD() const { unreachable(); } virtual const struct mach_header* machHeader() const { unreachable(); } virtual uintptr_t getSlide() const { return _slide; } virtual const void* getEnd() const { unreachable(); } @@ -89,7 +90,8 @@ public: virtual bool needsInitialization() { unreachable(); } virtual bool getSectionContent(const char* segmentName, const char* sectionName, void** start, size_t* length) { unreachable(); } virtual void getUnwindInfo(dyld_unwind_sections* info) { unreachable(); } - virtual bool findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) { unreachable(); } + virtual bool findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) { unreachable(); } + virtual const struct macho_section* findSection(const void* imageInterior) const { unreachable(); } virtual bool isPrebindable() const { unreachable(); } virtual bool usablePrebinding(const LinkContext& context) const { unreachable(); } virtual void getRPaths(const LinkContext& context, std::vector&) const { } @@ -118,7 +120,7 @@ public: virtual uint32_t minOSVersion() const { unreachable(); } // if the image contains interposing functions, register them - virtual void registerInterposing() { unreachable(); } + virtual void registerInterposing(const LinkContext& context) { unreachable(); } virtual ImageLoader* libImage(unsigned int) const { unreachable(); } virtual bool libReExported(unsigned int) const { unreachable(); } @@ -137,7 +139,7 @@ public: bool findUnwindSections(const void* addr, dyld_unwind_sections* info); bool dladdrFromCache(const void* address, Dl_info* info); uintptr_t bindLazy(uintptr_t lazyBindingInfoOffset, const LinkContext& context, const mach_header* mh, unsigned index); - bool flatFindSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image); + bool flatFindSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image, ImageLoader::CoalesceNotifier); void getDylibUUID(unsigned int index, uuid_t) const; protected: diff --git a/src/dyld.cpp b/src/dyld.cpp index 808305e..0a4d97b 100644 --- a/src/dyld.cpp +++ b/src/dyld.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -60,8 +59,31 @@ #include #include #include +#if TARGET_IPHONE_SIMULATOR + enum { + AMFI_DYLD_INPUT_PROC_IN_SIMULATOR = (1 << 0), + }; + enum amfi_dyld_policy_output_flag_set { + AMFI_DYLD_OUTPUT_ALLOW_AT_PATH = (1 << 0), + AMFI_DYLD_OUTPUT_ALLOW_PATH_VARS = (1 << 1), + AMFI_DYLD_OUTPUT_ALLOW_CUSTOM_SHARED_CACHE = (1 << 2), + AMFI_DYLD_OUTPUT_ALLOW_FALLBACK_PATHS = (1 << 3), + AMFI_DYLD_OUTPUT_ALLOW_PRINT_VARS = (1 << 4), + AMFI_DYLD_OUTPUT_ALLOW_FAILED_LIBRARY_INSERTION = (1 << 5), + }; + extern "C" int amfi_check_dyld_policy_self(uint64_t input_flags, uint64_t* output_flags); +#else + #include +#endif +extern "C" { + #include + #include +} #include #include +#if __has_feature(ptrauth_calls) + #include +#endif extern "C" int __fork(); @@ -99,6 +121,16 @@ extern "C" int __fork(); #define CPU_SUBTYPE_ARM64_E 2 #endif +#ifndef CPU_ARCH_ABI64_32 + #define CPU_ARCH_ABI64_32 ((cpu_type_t) 0x02000000) +#endif +#ifndef CPU_TYPE_ARM64_32 + #define CPU_TYPE_ARM64_32 ((cpu_type_t) (CPU_TYPE_ARM | CPU_ARCH_ABI64_32)) +#endif +#ifndef CPU_SUBTYPE_ARM64_32_V8 + #define CPU_SUBTYPE_ARM64_32_V8 ((cpu_subtype_t) 1) +#endif + #ifndef VM_PROT_SLIDE #define VM_PROT_SLIDE 0x20 #endif @@ -111,13 +143,6 @@ extern "C" int __fork(); #include "dyldLibSystemInterface.h" #include "dyld_cache_format.h" #include "dyld_process_info_internal.h" -#include -#if TARGET_IPHONE_SIMULATOR - extern "C" void xcoresymbolication_load_notifier(void *connection, uint64_t load_timestamp, const char *image_path, const struct mach_header *mach_header); - extern "C" void xcoresymbolication_unload_notifier(void *connection, uint64_t unload_timestamp, const char *image_path, const struct mach_header *mach_header); - #define coresymbolication_load_notifier(c, t, p, h) xcoresymbolication_load_notifier(c, t, p, h) - #define coresymbolication_unload_notifier(c, t, p, h) xcoresymbolication_unload_notifier(c, t, p, h) -#endif #if SUPPORT_ACCELERATE_TABLES #include "ImageLoaderMegaDylib.h" @@ -129,19 +154,17 @@ extern "C" int __fork(); #include "dyldSyscallInterface.h" #endif -#include "LaunchCache.h" +#include "Closure.h" #include "libdyldEntryVector.h" -#include "MachOParser.h" +#include "MachOLoaded.h" #include "Loading.h" #include "DyldSharedCache.h" #include "SharedCacheRuntime.h" #include "StringUtils.h" #include "Tracing.h" -#include "DyldCacheParser.h" - -extern "C" { - #include "closuredProtocol.h" -} +#include "ClosureBuilder.h" +#include "ClosureFileSystemPhysical.h" +#include "FileUtils.h" // not libc header for send() syscall interface @@ -231,7 +254,6 @@ struct EnvironmentVariables { bool DYLD_PRINT_OPTS; bool DYLD_PRINT_ENV; bool DYLD_DISABLE_DOFS; - bool DYLD_PRINT_CS_NOTIFICATIONS; // DYLD_SHARED_CACHE_DIR ==> sSharedCacheOverrideDir // DYLD_ROOT_PATH ==> gLinkContext.rootPaths // DYLD_IMAGE_SUFFIX ==> gLinkContext.imageSuffix @@ -272,7 +294,6 @@ static cpu_type_t sHostCPU; static cpu_subtype_t sHostCPUsubtype; #endif static ImageLoaderMachO* sMainExecutable = NULL; -static EnvVarMode sEnvMode = envNone; static size_t sInsertedDylibCount = 0; static std::vector sAllImages; static std::vector sImageRoots; @@ -280,6 +301,7 @@ static std::vector sImageFilesNeedingTermination; static std::vector sImageFilesNeedingDOFUnregistration; static std::vector sAddImageCallbacks; static std::vector sRemoveImageCallbacks; +static std::vector sAddLoadImageCallbacks; static bool sRemoveImageCallbacksInUse = false; static void* sSingleHandlers[7][3]; static void* sBatchHandlers[7][3]; @@ -322,7 +344,6 @@ static OSSpinLock sDynamicReferencesLock = 0; static bool sLogToFile = false; #endif static char sLoadingCrashMessage[1024] = "dyld: launch, loading dependent libraries"; -static bool sSafeMode = false; static _dyld_objc_notify_mapped sNotifyObjCMapped; static _dyld_objc_notify_init sNotifyObjCInit; static _dyld_objc_notify_unmapped sNotifyObjCUnmapped; @@ -340,6 +361,7 @@ static bool sDisableAcceleratorTables = false; bool gUseDyld3 = false; static bool sSkipMain = false; static bool sEnableClosures = false; +static uint64_t launchTraceID = 0; // // The MappedRanges structure is used for fast address->image lookups. @@ -681,8 +703,14 @@ static void notifyAddImageCallbacks(ImageLoader* image) { // use guard so that we cannot notify about the same image twice if ( ! image->addFuncNotified() ) { - for (std::vector::iterator it=sAddImageCallbacks.begin(); it != sAddImageCallbacks.end(); it++) + for (std::vector::iterator it=sAddImageCallbacks.begin(); it != sAddImageCallbacks.end(); it++) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)image->machHeader(), (uint64_t)(*it), 0); (*it)(image->machHeader(), image->getSlide()); + } + for (LoadImageCallback func : sAddLoadImageCallbacks) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)image->machHeader(), (uint64_t)(*func), 0); + (*func)(image->machHeader(), image->getPath(), !image->neverUnload()); + } image->setAddFuncNotified(); } } @@ -763,138 +791,108 @@ static void notifySingleFromCache(dyld_image_states state, const mach_header* mh } } if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && (mh->flags & MH_HAS_OBJC) ) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)mh, 0, 0); (*sNotifyObjCInit)(path, 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] ) +#if !TARGET_OS_SIMULATOR +static void sendMessage(unsigned portSlot, mach_msg_id_t msgId, mach_msg_size_t sendSize, mach_msg_header_t* buffer, mach_msg_size_t bufferSize) { + // Allocate a port to listen on in this monitoring task + mach_port_t sendPort = dyld::gProcessInfo->notifyPorts[portSlot]; + if (sendPort == MACH_PORT_NULL) { 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) + 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 = imageCount/2; - notifyMonitoringDyld(unloading, portSlot, imageHalfCount, infos); - notifyMonitoringDyld(unloading, portSlot, imageCount - imageHalfCount, &infos[imageHalfCount]); + } + mach_port_t replyPort = MACH_PORT_NULL; + mach_port_options_t options = { .flags = MPO_CONTEXT_AS_GUARD | MPO_STRICT, + .mpl = { 1 }}; + kern_return_t kr = mach_port_construct(mach_task_self(), &options, (mach_port_context_t)&replyPort, &replyPort); + if (kr != KERN_SUCCESS) { return; } - uint8_t buffer[totalSize]; - dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)buffer; - header->version = 1; - header->imageCount = imageCount; - header->imagesOffset = sizeof(dyld_process_info_notify_header); - header->stringsOffset = sizeof(dyld_process_info_notify_header) + entriesSize; - header->timestamp = dyld::gProcessInfo->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 (unsigned j=0; j < imageCount; ++j) { - strcpy(pathPool, infos[j].imageFilePath); - uint32_t len = (uint32_t)strlen(pathPool); - bzero(entries->uuid, 16); - const ImageLoader* image = findImageByMachHeader(infos[j].imageLoadAddress); - if ( image != NULL ) { - image->getUUID(entries->uuid); + // Assemble a message + mach_msg_header_t* h = buffer; + h->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE); + h->msgh_id = msgId; + h->msgh_local_port = replyPort; + h->msgh_remote_port = sendPort; + h->msgh_reserved = 0; + h->msgh_size = sendSize; + kr = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG, h->msgh_size, bufferSize, replyPort, 0, MACH_PORT_NULL); + mach_msg_destroy(h); + if ( kr == MACH_SEND_INVALID_DEST ) { + if (OSAtomicCompareAndSwap32(sendPort, 0, (volatile int32_t*)&dyld::gProcessInfo->notifyPorts[portSlot])) { + mach_port_deallocate(mach_task_self(), sendPort); } -#if SUPPORT_ACCELERATE_TABLES - else if ( sAllCacheImagesProxy != NULL ) { - const mach_header* mh; - const char* path; - unsigned index; - if ( sAllCacheImagesProxy->addressInCache(infos[j].imageLoadAddress, &mh, &path, &index) ) { - sAllCacheImagesProxy->getDylibUUID(index, entries->uuid); - } + } + mach_port_destruct(mach_task_self(), replyPort, 0, (mach_port_context_t)&replyPort); +} + +static void notifyMonitoringDyld(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[], + const char* imagePaths[]) +{ + dyld3::ScopedTimer(DBG_DYLD_REMOTE_IMAGE_NOTIFIER, 0, 0, 0); + for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { + if ( dyld::gProcessInfo->notifyPorts[slot] == 0) continue; + unsigned entriesSize = imageCount*sizeof(dyld_process_info_image_entry); + unsigned pathsSize = 0; + for (unsigned j=0; j < imageCount; ++j) { + pathsSize += (strlen(imagePaths[j]) + 1); + } + unsigned totalSize = (sizeof(dyld_process_info_notify_header) + entriesSize + pathsSize + 127) & -128; // align + if ( totalSize > DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE ) { + // Putting all image paths into one message would make buffer too big. + // Instead split into two messages. Recurse as needed until paths fit in buffer. + unsigned imageHalfCount = imageCount/2; + notifyMonitoringDyld(unloading, imageHalfCount, loadAddresses, imagePaths); + notifyMonitoringDyld(unloading, imageCount - imageHalfCount, &loadAddresses[imageHalfCount], &imagePaths[imageHalfCount]); + return; + } + uint8_t buffer[totalSize + MAX_TRAILER_SIZE]; + dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)buffer; + header->version = 1; + header->imageCount = imageCount; + header->imagesOffset = sizeof(dyld_process_info_notify_header); + header->stringsOffset = sizeof(dyld_process_info_notify_header) + entriesSize; + header->timestamp = dyld::gProcessInfo->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 (unsigned j=0; j < imageCount; ++j) { + strcpy(pathPool, imagePaths[j]); + uint32_t len = (uint32_t)strlen(pathPool); + bzero(entries->uuid, 16); + dyld3::MachOFile* mf = (dyld3::MachOFile*)loadAddresses[j]; + mf->getUuid(entries->uuid); + entries->loadAddress = (uint64_t)loadAddresses[j]; + entries->pathStringOffset = (uint32_t)(pathPool - pathPoolStart); + entries->pathLength = len; + pathPool += (len +1); + ++entries; + } + if (unloading) { + sendMessage(slot, DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID, totalSize, (mach_msg_header_t*)buffer, totalSize+MAX_TRAILER_SIZE); + } else { + sendMessage(slot, DYLD_PROCESS_INFO_NOTIFY_LOAD_ID, totalSize, (mach_msg_header_t*)buffer, totalSize+MAX_TRAILER_SIZE); } -#endif - entries->loadAddress = (uint64_t)infos[j].imageLoadAddress; - entries->pathStringOffset = (uint32_t)(pathPool - pathPoolStart); - entries->pathLength = len; - pathPool += (len +1); - ++entries; - } - - if ( sNotifyReplyPorts[portSlot] == 0 ) { - if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[portSlot]) ) - mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[portSlot], sNotifyReplyPorts[portSlot], MACH_MSG_TYPE_MAKE_SEND); - //dyld::log("allocated reply port %d\n", sNotifyReplyPorts[portSlot]); - } - //dyld::log("found port to send to\n"); - mach_msg_header_t* h = (mach_msg_header_t*)buffer; - h->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND); // MACH_MSG_TYPE_MAKE_SEND_ONCE - h->msgh_id = unloading ? DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID : DYLD_PROCESS_INFO_NOTIFY_LOAD_ID; - h->msgh_local_port = sNotifyReplyPorts[portSlot]; - h->msgh_remote_port = dyld::gProcessInfo->notifyPorts[portSlot]; - h->msgh_reserved = 0; - h->msgh_size = (mach_msg_size_t)sizeof(buffer); - //dyld::log("sending to port[%d]=%d, size=%d, reply port=%d, id=0x%X\n", portSlot, dyld::gProcessInfo->notifyPorts[portSlot], h->msgh_size, sNotifyReplyPorts[portSlot], h->msgh_id); - kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_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 - //dyld::log("process requesting notification gone. deallocation send port %d and receive port %d\n", dyld::gProcessInfo->notifyPorts[portSlot], sNotifyReplyPorts[portSlot]); - mach_port_deallocate(mach_task_self(), dyld::gProcessInfo->notifyPorts[portSlot]); - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]); - dyld::gProcessInfo->notifyPorts[portSlot] = 0; - sNotifyReplyPorts[portSlot] = 0; - } - 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() { + dyld3::ScopedTimer(DBG_DYLD_REMOTE_IMAGE_NOTIFIER, 0, 0, 0); for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { - 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"); - 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]); - mach_port_deallocate(mach_task_self(), dyld::gProcessInfo->notifyPorts[slot]); - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); - 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; - } - } + if ( dyld::gProcessInfo->notifyPorts[slot] == 0) continue; + uint8_t buffer[sizeof(mach_msg_header_t) + MAX_TRAILER_SIZE]; + sendMessage(slot, DYLD_PROCESS_INFO_NOTIFY_MAIN_ID, sizeof(mach_msg_header_t), (mach_msg_header_t*)buffer, sizeof(mach_msg_header_t) + MAX_TRAILER_SIZE); } } +#else +extern void notifyMonitoringDyldMain() VIS_HIDDEN; +extern void notifyMonitoringDyld(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[], + const char* imagePaths[]) VIS_HIDDEN; +#endif void notifyKernel(const ImageLoader& image, bool loading) { if ( !image.inSharedCache() ) { @@ -937,6 +935,7 @@ static void notifySingle(dyld_image_states state, const ImageLoader* image, Imag } if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) { uint64_t t0 = mach_absolute_time(); + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0); (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); uint64_t t1 = mach_absolute_time(); uint64_t t2 = mach_absolute_time(); @@ -949,33 +948,10 @@ static void notifySingle(dyld_image_states state, const ImageLoader* image, Imag // mach message csdlc about dynamically unloaded images if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) { notifyKernel(*image, false); - - uint64_t loadTimestamp = mach_absolute_time(); - if ( sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { - dyld::log("dyld: coresymbolication_unload_notifier(%p, 0x%016llX, %p, %s)\n", - dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, image->machHeader(), image->getPath()); - } - if ( dyld::gProcessInfo->coreSymbolicationShmPage != NULL) { - coresymbolication_unload_notifier(dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, image->getPath(), image->machHeader()); - } - for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { - if ( dyld::gProcessInfo->notifyPorts[slot] != 0 ) { - dyld_image_info info; - info.imageLoadAddress = image->machHeader(); - info.imageFilePath = image->getPath(); - info.imageFileModDate = 0; - notifyMonitoringDyld(true, slot, 1, &info); - } - else if ( sNotifyReplyPorts[slot] != 0 ) { - // monitoring process detached from this process, so release reply port - //dyld::log("deallocated reply port %d\n", sNotifyReplyPorts[slot]); - mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); - sNotifyReplyPorts[slot] = 0; - sZombieNotifiers[slot] = false; - } - } + const struct mach_header* loadAddress[] = { image->machHeader() }; + const char* loadPath[] = { image->getPath() }; + notifyMonitoringDyld(true, 1, loadAddress, loadPath); } - } @@ -1070,8 +1046,16 @@ static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image // support _dyld_register_func_for_add_image() if ( state == dyld_image_state_bound ) { for (ImageCallback callback : sAddImageCallbacks) { - for (unsigned i=0; i < cacheCount; ++i) + for (unsigned i=0; i < cacheCount; ++i) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)infos[imageCount+i].imageLoadAddress, (uint64_t)(*callback), 0); (*callback)(infos[imageCount+i].imageLoadAddress, sSharedCacheLoadInfo.slide); + } + } + for (LoadImageCallback func : sAddLoadImageCallbacks) { + for (unsigned i=0; i < cacheCount; ++i) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)infos[imageCount+i].imageLoadAddress, (uint64_t)(*func), 0); + (*func)(infos[imageCount+i].imageLoadAddress, infos[imageCount+i].imageFilePath, false); + } } } imageCount += cacheCount; @@ -1133,6 +1117,7 @@ static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image } } if ( objcImageCount != 0 ) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0); uint64_t t0 = mach_absolute_time(); (*sNotifyObjCMapped)(objcImageCount, paths, mhs); uint64_t t1 = mach_absolute_time(); @@ -1144,32 +1129,38 @@ static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image if ( dontLoadReason != NULL ) throw dontLoadReason; if ( !preflightOnly && (state == dyld_image_state_dependents_mapped) ) { - if ( (dyld::gProcessInfo->coreSymbolicationShmPage != NULL) || sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { - // mach message csdlc about loaded images - uint64_t loadTimestamp = mach_absolute_time(); - for (unsigned j=0; j < imageCount; ++j) { - if ( sEnv.DYLD_PRINT_CS_NOTIFICATIONS ) { - dyld::log("dyld: coresymbolication_load_notifier(%p, 0x%016llX, %p, %s)\n", - dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, infos[j].imageLoadAddress, infos[j].imageFilePath); - } - coresymbolication_load_notifier(dyld::gProcessInfo->coreSymbolicationShmPage, loadTimestamp, infos[j].imageFilePath, infos[j].imageLoadAddress); - } - } - for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { - if ( dyld::gProcessInfo->notifyPorts[slot] ) - notifyMonitoringDyld(false, slot, imageCount, infos); + const struct mach_header* loadAddresses[imageCount]; + const char* loadPaths[imageCount]; + for(uint32_t i = 0; igetState() >= dyld_image_state_bound ) { sRemoveImageCallbacksInUse = true; // This only runs inside dyld's global lock, so ok to use a global for the in-use flag. for (std::vector::iterator it=sRemoveImageCallbacks.begin(); it != sRemoveImageCallbacks.end(); it++) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_REMOVE_IMAGE, (uint64_t)image->machHeader(), (uint64_t)(*it), 0); (*it)(image->machHeader(), image->getSlide()); } sRemoveImageCallbacksInUse = false; @@ -1633,12 +1625,10 @@ static const char** parseColonList(const char* list, const char* mainExecutableD if (*s == ':') { size_t len = s-start; if ( (mainExecutableDir != NULL) && (strncmp(start, "@loader_path/", 13) == 0) ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( gLinkContext.processIsRestricted ) { - dyld::log("dyld: warning: @loader_path/ ignored in restricted process\n"); + if ( !gLinkContext.allowAtPaths ) { + dyld::log("dyld: warning: @loader_path/ ignored because of amfi policy\n"); continue; } -#endif size_t mainExecDirLen = strlen(mainExecutableDir); char* str = new char[mainExecDirLen+len+1]; strcpy(str, mainExecutableDir); @@ -1648,12 +1638,10 @@ static const char** parseColonList(const char* list, const char* mainExecutableD result[index++] = str; } else if ( (mainExecutableDir != NULL) && (strncmp(start, "@executable_path/", 17) == 0) ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( gLinkContext.processIsRestricted ) { - dyld::log("dyld: warning: @executable_path/ ignored in restricted process\n"); + if ( !gLinkContext.allowAtPaths ) { + dyld::log("dyld: warning: @executable_path/ ignored because of amfi policy\n"); continue; } -#endif size_t mainExecDirLen = strlen(mainExecutableDir); char* str = new char[mainExecDirLen+len+1]; strcpy(str, mainExecutableDir); @@ -1673,12 +1661,10 @@ static const char** parseColonList(const char* list, const char* mainExecutableD } size_t len = strlen(start); if ( (mainExecutableDir != NULL) && (strncmp(start, "@loader_path/", 13) == 0) ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( gLinkContext.processIsRestricted ) { - dyld::log("dyld: warning: @loader_path/ ignored in restricted process\n"); + if ( !gLinkContext.allowAtPaths ) { + dyld::log("dyld: warning: @loader_path/ ignored because of amfi policy\n"); } else -#endif { size_t mainExecDirLen = strlen(mainExecutableDir); char* str = new char[mainExecDirLen+len+1]; @@ -1689,12 +1675,10 @@ static const char** parseColonList(const char* list, const char* mainExecutableD } } else if ( (mainExecutableDir != NULL) && (strncmp(start, "@executable_path/", 17) == 0) ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( gLinkContext.processIsRestricted ) { - dyld::log("dyld: warning: @executable_path/ ignored in restricted process\n"); + if ( !gLinkContext.allowAtPaths ) { + dyld::log("dyld: warning: @executable_path/ ignored because of amfi policy\n"); } else -#endif { size_t mainExecDirLen = strlen(mainExecutableDir); char* str = new char[mainExecDirLen+len+1]; @@ -1841,7 +1825,7 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch } #endif else if ( strcmp(key, "DYLD_IMAGE_SUFFIX") == 0 ) { - gLinkContext.imageSuffix = value; + gLinkContext.imageSuffix = parseColonList(value, NULL); } else if ( strcmp(key, "DYLD_INSERT_LIBRARIES") == 0 ) { sEnv.DYLD_INSERT_LIBRARIES = parseColonList(value, NULL); @@ -1929,16 +1913,13 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch else if ( strcmp(key, "DYLD_PRINT_RPATHS") == 0 ) { gLinkContext.verboseRPaths = true; } - else if ( strcmp(key, "DYLD_PRINT_CS_NOTIFICATIONS") == 0 ) { - sEnv.DYLD_PRINT_CS_NOTIFICATIONS = true; - } else if ( strcmp(key, "DYLD_PRINT_INTERPOSING") == 0 ) { gLinkContext.verboseInterposing = true; } else if ( strcmp(key, "DYLD_PRINT_CODE_SIGNATURES") == 0 ) { gLinkContext.verboseCodeSignatures = true; } - else if ( (strcmp(key, "DYLD_SHARED_REGION") == 0) && !sSafeMode ) { + else if ( (strcmp(key, "DYLD_SHARED_REGION") == 0) && gLinkContext.allowEnvVarsSharedCache ) { if ( strcmp(value, "private") == 0 ) { gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; } @@ -1955,12 +1936,17 @@ 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"); } } - else if ( (strcmp(key, "DYLD_SHARED_CACHE_DIR") == 0) && !sSafeMode ) { + else if ( (strcmp(key, "DYLD_SHARED_CACHE_DIR") == 0) && gLinkContext.allowEnvVarsSharedCache ) { sSharedCacheOverrideDir = value; } else if ( strcmp(key, "DYLD_USE_CLOSURES") == 0 ) { - if ( dyld3::loader::internalInstall() ) + if ( dyld3::internalInstall() ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED && __i386__ + // don't support dyld3 for 32-bit macOS +#else sEnableClosures = true; +#endif + } } else if ( strcmp(key, "DYLD_IGNORE_PREBINDING") == 0 ) { if ( strcmp(value, "all") == 0 ) { @@ -1994,7 +1980,7 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch } #endif #if !TARGET_IPHONE_SIMULATOR - else if ( (strcmp(key, "DYLD_PRINT_TO_FILE") == 0) && (mainExecutableDir == NULL) && !sSafeMode ) { + else if ( (strcmp(key, "DYLD_PRINT_TO_FILE") == 0) && (mainExecutableDir == NULL) && gLinkContext.allowEnvVarsSharedCache ) { int fd = open(value, O_WRONLY | O_CREAT | O_APPEND, 0644); if ( fd != -1 ) { sLogfile = fd; @@ -2005,7 +1991,7 @@ void processDyldEnvironmentVariable(const char* key, const char* value, const ch } } else if ( (strcmp(key, "DYLD_SKIP_MAIN") == 0)) { - if ( dyld3::loader::internalInstall() ) + if ( dyld3::internalInstall() ) sSkipMain = true; } #endif @@ -2109,7 +2095,7 @@ static void pruneEnvironmentVariables(const char* envp[], const char*** applep) // Are we testing dyld on an internal config? if ( _simple_getenv(envp, "DYLD_SKIP_MAIN") != NULL ) { - if ( dyld3::loader::internalInstall() ) + if ( dyld3::internalInstall() ) sSkipMain = true; } @@ -2148,7 +2134,7 @@ static void pruneEnvironmentVariables(const char* envp[], const char*** applep) static void defaultUninitializedFallbackPaths(const char* envp[]) { #if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( gLinkContext.processIsRestricted ) { + if ( !gLinkContext.allowClassicFallbackPaths ) { sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = sRestrictedFrameworkFallbackPaths; sEnv.DYLD_FALLBACK_LIBRARY_PATH = sRestrictedLibraryFallbackPaths; return; @@ -2186,7 +2172,7 @@ static void defaultUninitializedFallbackPaths(const char* envp[]) static void checkEnvironmentVariables(const char* envp[]) { - if ( sEnvMode == envNone ) + if ( !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsPrint ) return; const char** p; for(p = envp; *p != NULL; p++) { @@ -2201,7 +2187,7 @@ static void checkEnvironmentVariables(const char* envp[]) char key[keyLen+1]; strncpy(key, keyEqualsValue, keyLen); key[keyLen] = '\0'; - if ( (sEnvMode == envPrintOnly) && (strncmp(key, "DYLD_PRINT_", 11) != 0) ) + if ( (strncmp(key, "DYLD_PRINT_", 11) == 0) && !gLinkContext.allowEnvVarsPrint ) continue; processDyldEnvironmentVariable(key, value, NULL); } @@ -2218,9 +2204,9 @@ static void checkEnvironmentVariables(const char* envp[]) #if SUPPORT_ROOT_PATH // DYLD_IMAGE_SUFFIX and DYLD_ROOT_PATH cannot be used together - if ( (gLinkContext.imageSuffix != NULL) && (gLinkContext.rootPaths != NULL) ) { + if ( (gLinkContext.imageSuffix != NULL && *gLinkContext.imageSuffix != NULL) && (gLinkContext.rootPaths != NULL) ) { dyld::warn("Ignoring DYLD_IMAGE_SUFFIX because DYLD_ROOT_PATH is used.\n"); - gLinkContext.imageSuffix = NULL; + gLinkContext.imageSuffix = NULL; // this leaks allocations from parseColonList } #endif } @@ -2273,6 +2259,9 @@ static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExec #elif __ARM_ARCH_7S__ sHostCPU = CPU_TYPE_ARM; sHostCPUsubtype = CPU_SUBTYPE_ARM_V7S; +#elif __ARM64_ARCH_8_32__ + sHostCPU = CPU_TYPE_ARM64_32; + sHostCPUsubtype = CPU_SUBTYPE_ARM64_32_V8; #elif __arm64e__ sHostCPU = CPU_TYPE_ARM64; sHostCPUsubtype = CPU_SUBTYPE_ARM64_E; @@ -2310,27 +2299,18 @@ static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExec #endif } -static void checkSharedRegionDisable(const mach_header* mainExecutableMH) +static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide) { #if __MAC_OS_X_VERSION_MIN_REQUIRED // if main executable has segments that overlap the shared region, // then disable using the shared region - 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 ) { + if ( mainExecutableMH->intersectsRange(SHARED_REGION_BASE, SHARED_REGION_SIZE) ) { gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion; if ( gLinkContext.verboseMapping ) dyld::warn("disabling shared region because main executable overlaps\n"); } #if __i386__ - if ( gLinkContext.processIsRestricted ) { + if ( !gLinkContext.allowEnvVarsPath ) { // use private or no shared region for suid processes gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; } @@ -2460,8 +2440,10 @@ static const char* getFrameworkPartialPath(const char* path) if ( gLinkContext.imageSuffix != NULL ) { // some debug frameworks have install names that end in _debug if ( strncmp(framework, &leaf[1], len) == 0 ) { - if ( strcmp( gLinkContext.imageSuffix, &leaf[len+1]) == 0 ) - return frameworkStart; + for (const char* const* suffix=gLinkContext.imageSuffix; *suffix != NULL; ++suffix) { + if ( strcmp(*suffix, &leaf[len+1]) == 0 ) + return frameworkStart; + } } } } @@ -2547,6 +2529,19 @@ static const cpu_subtype_t kARM64[kARM64_RowCount][4] = { // armv64 can run: 64 { CPU_SUBTYPE_ARM64_V8, CPU_SUBTYPE_ARM64_ALL, CPU_SUBTYPE_END_OF_LIST }, }; + +#if __ARM64_ARCH_8_32__ +const int kARM64_32_RowCount = 2; +static const cpu_subtype_t kARM64_32[kARM64_32_RowCount][4] = { + + // armv64_32 can run: v8 + { CPU_SUBTYPE_ARM64_32_V8, CPU_SUBTYPE_END_OF_LIST }, + + // armv64 can run: 64 + { CPU_SUBTYPE_ARM64_V8, CPU_SUBTYPE_ARM64_ALL, CPU_SUBTYPE_END_OF_LIST }, +}; +#endif + #endif #if __x86_64__ @@ -2585,6 +2580,16 @@ static const cpu_subtype_t* findCPUSubtypeList(cpu_type_t cpu, cpu_subtype_t sub return kARM64[i]; } break; + +#if __ARM64_ARCH_8_32__ + case CPU_TYPE_ARM64_32: + for (int i=0; i < kARM64_32_RowCount ; ++i) { + if ( kARM64_32[i][0] == subtype ) + return kARM64_32[i]; + } + break; +#endif + #endif #if __x86_64__ case CPU_TYPE_X86_64: @@ -2855,7 +2860,7 @@ static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uint #if SUPPORT_ACCELERATE_TABLES static bool dylibsCanOverrideCache() { - if ( !dyld3::loader::internalInstall() ) + if ( !dyld3::internalInstall() ) return false; return ( (sSharedCacheLoadInfo.loadAddress != nullptr) && (sSharedCacheLoadInfo.loadAddress->header.cacheType == kDyldSharedCacheTypeDevelopment) ); } @@ -2956,9 +2961,34 @@ static bool isSimulatorBinary(const uint8_t* firstPages, const char* path) // grandfather in a few libSystem dylibs if ((strcmp(path, "/usr/lib/system/libsystem_kernel.dylib") == 0) || (strcmp(path, "/usr/lib/system/libsystem_platform.dylib") == 0) || - (strcmp(path, "/usr/lib/system/libsystem_pthread.dylib") == 0)) + (strcmp(path, "/usr/lib/system/libsystem_pthread.dylib") == 0) || + (strcmp(path, "/usr/lib/system/libsystem_platform_debug.dylib") == 0) || + (strcmp(path, "/usr/lib/system/libsystem_pthread_debug.dylib") == 0)) return true; return false; + case LC_BUILD_VERSION: + { + // Same logic as above, but for LC_BUILD_VERSION instead of legacy load commands + const struct build_version_command* buildVersionCmd = (build_version_command*)cmd; + switch(buildVersionCmd->platform) { + case PLATFORM_IOSSIMULATOR: + case PLATFORM_TVOSSIMULATOR: + case PLATFORM_WATCHOSSIMULATOR: + case PLATFORM_WATCHOS: + return true; + #if TARGET_OS_IOSMAC + case 6: + return true; + #endif + case PLATFORM_MACOS: + if ((strcmp(path, "/usr/lib/system/libsystem_kernel.dylib") == 0) || + (strcmp(path, "/usr/lib/system/libsystem_platform.dylib") == 0) || + (strcmp(path, "/usr/lib/system/libsystem_pthread.dylib") == 0) || + (strcmp(path, "/usr/lib/system/libsystem_platform_debug.dylib") == 0) || + (strcmp(path, "/usr/lib/system/libsystem_pthread_debug.dylib") == 0)) + return true; + } + } } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); if ( cmd > cmdsEnd ) @@ -2968,6 +2998,58 @@ static bool isSimulatorBinary(const uint8_t* firstPages, const char* path) } #endif +#if __MAC_OS_X_VERSION_MIN_REQUIRED +static bool iOSMacWhiteListed(const char* path) +{ + static char* whiteListBuffer = nullptr; + static size_t whiteListSize = 0; + static bool tried = false; + if ( !tried ) { + // only try to map file once + whiteListBuffer = (char*)mapFileReadOnly("/System/iOSSupport/dyld/macOS-whitelist.txt", whiteListSize); + tried = true; + } + __block bool result = false; + if ( whiteListBuffer != nullptr ) { + dyld3::forEachLineInFile(whiteListBuffer, whiteListSize, ^(const char* line, bool& stop) { + // lines in the file are prefixes. Any path that starts with one of these lines is allowed to be unzippered + size_t lineLen = strlen(line); + if ( (*line == '/') && strncmp(line, path, lineLen) == 0 ) { + result = true; + stop = true; + } + }); + } + return result; +} + +static bool iOSMacBlackListed(const char* path) +{ + static char* blackListBuffer = nullptr; + static size_t blackListSize = 0; + static bool tried = false; + if ( !tried ) { + // only try to map file once + blackListBuffer = (char*)mapFileReadOnly("/System/iOSSupport/dyld/macOS-blacklist.txt", blackListSize); + tried = true; + } + __block bool result = false; + if ( blackListBuffer != nullptr ) { + dyld3::forEachLineInFile(blackListBuffer, blackListSize, ^(const char* line, bool& stop) { + // lines in the file are prefixes. Any path that starts with one of these lines is allowed to be unzippered + size_t lineLen = strlen(line); + if ( (*line == '/') && strncmp(line, path, lineLen) == 0 ) { + result = true; + stop = true; + } + }); + } + return result; +} + + +#endif + // map in file and instantiate an ImageLoader static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* path, const LoadContext& context) { @@ -2980,6 +3062,7 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* throw "not a file"; uint8_t firstPages[MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE]; + uint8_t *firstPagesPtr = firstPages; bool shortPage = false; // min mach-o file is 4K @@ -3013,6 +3096,7 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* // try mach-o loader if ( shortPage ) throw "file too short"; + if ( isCompatibleMachO(firstPages, path) ) { // only MH_BUNDLE, MH_DYLIB, and some MH_EXECUTE can be dynamically loaded @@ -3053,8 +3137,27 @@ static ImageLoader* loadPhase6(int fd, const struct stat& stat_buf, const char* } #endif - // instantiate an image - ImageLoader* image = ImageLoaderMachO::instantiateFromFile(path, fd, firstPages, headerAndLoadCommandsSize, fileOffset, fileLength, stat_buf, gLinkContext); +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.marzipan ) { + const dyld3::MachOFile* mf = (dyld3::MachOFile*)firstPages; + bool isiOSMacBinary = mf->supportsPlatform(dyld3::Platform::iOSMac) || iOSMacWhiteListed(path); + bool isProhibitedMacOSBinary = !isiOSMacBinary && iOSMacBlackListed(path); + if ( (context.enforceIOSMac && !isiOSMacBinary) || isProhibitedMacOSBinary ) { + throw "mach-o, but not built for iOSMac"; + } + } +#endif + +#if __arm64e__ + if ( (sMainExecutableMachHeader->cpusubtype == CPU_SUBTYPE_ARM64_E) && (mh->cpusubtype != CPU_SUBTYPE_ARM64_E) ) + throw "arm64 dylibs cannot be loaded into arm64e processes"; +#endif + ImageLoader* image = nullptr; + { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_MAP_IMAGE, path, 0, 0); + image = ImageLoaderMachO::instantiateFromFile(path, fd, firstPagesPtr, headerAndLoadCommandsSize, fileOffset, fileLength, stat_buf, gLinkContext); + timer.setData4((uint64_t)image->machHeader()); + } // validate return checkandAddImage(image, context); @@ -3132,6 +3235,7 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const bool didStat = false; bool existsOnDisk; dyld3::SharedCacheFindDylibResults shareCacheResults; + shareCacheResults.image = nullptr; 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) { @@ -3150,7 +3254,7 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const return NULL; } bool useCache = false; - if ( shareCacheResults.imageData == nullptr ) { + if ( shareCacheResults.image == nullptr ) { // HACK to support old caches existsOnDisk = ( my_stat(path, &statBuf) == 0 ); didStat = true; @@ -3160,14 +3264,17 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const 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() ) { + if ( shareCacheResults.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; + uint64_t expectedINode; + uint64_t expectedMtime; + if ( shareCacheResults.image->hasFileModTimeAndInode(expectedINode, expectedMtime) ) { + if ( (expectedMtime == statBuf.st_mtime) && (expectedINode == statBuf.st_ino) ) + useCache = true; + } } else { if ( !existsOnDisk ) @@ -3179,6 +3286,16 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const } } if ( useCache ) { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( gLinkContext.marzipan ) { + const dyld3::MachOFile* mf = (dyld3::MachOFile*)shareCacheResults.mhInCache; + bool isiOSMacBinary = mf->supportsPlatform(dyld3::Platform::iOSMac) || iOSMacWhiteListed(path); + bool isProhibitedMacOSBinary = !isiOSMacBinary && iOSMacBlackListed(path); + if ( (context.enforceIOSMac && !isiOSMacBinary) || isProhibitedMacOSBinary ) { + throw "mach-o, but not built for iOSMac"; + } + } +#endif ImageLoader* imageLoader = ImageLoaderMachO::instantiateFromCache((macho_header*)shareCacheResults.mhInCache, shareCacheResults.pathInCache, shareCacheResults.slideInCache, statBuf, gLinkContext); return checkandAddImage(imageLoader, context); } @@ -3199,8 +3316,13 @@ static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const return NULL; // try opening file imageLoader = loadPhase5open(path, context, statBuf, exceptions); - if ( imageLoader != NULL ) + if ( imageLoader != NULL ) { + if ( shareCacheResults.image != nullptr ) { + // if image was found in cache, but is overridden by a newer file on disk, record what the image overrides + imageLoader->setOverridesCachedDylib(shareCacheResults.image->imageNum()); + } return imageLoader; + } } // just return NULL if file not found, but record any other errors @@ -3282,10 +3404,20 @@ static ImageLoader* loadPhase4(const char* path, const char* orgPath, const Load { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); ImageLoader* image = NULL; - if ( gLinkContext.imageSuffix != NULL ) { - char pathWithSuffix[strlen(path)+strlen( gLinkContext.imageSuffix)+2]; - ImageLoader::addSuffix(path, gLinkContext.imageSuffix, pathWithSuffix); - image = loadPhase5(pathWithSuffix, orgPath, context, cacheIndex, exceptions); + if ( gLinkContext.imageSuffix != NULL ) { + for (const char* const* suffix=gLinkContext.imageSuffix; *suffix != NULL; ++suffix) { + char pathWithSuffix[strlen(path)+strlen(*suffix)+2]; + ImageLoader::addSuffix(path, *suffix, pathWithSuffix); + image = loadPhase5(pathWithSuffix, orgPath, context, cacheIndex, exceptions); + if ( image != NULL ) + break; + } + if ( image != NULL ) { + // if original path is in the dyld cache, then mark this one found as an override + dyld3::SharedCacheFindDylibResults shareCacheResults; + if ( dyld3::findInSharedCacheImage(sSharedCacheLoadInfo, path, &shareCacheResults) && (shareCacheResults.image != nullptr) ) + image->setOverridesCachedDylib(shareCacheResults.image->imageNum()); + } } if ( image == NULL ) image = loadPhase5(path, orgPath, context, cacheIndex, exceptions); @@ -3303,11 +3435,9 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); ImageLoader* image = NULL; if ( strncmp(path, "@executable_path/", 17) == 0 ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED // executable_path cannot be in used in any binary in a setuid process rdar://problem/4589305 - if ( gLinkContext.processIsRestricted ) + if ( !gLinkContext.allowAtPaths ) throwf("unsafe use of @executable_path in %s with restricted binary", context.origin); -#endif // handle @executable_path path prefix const char* executablePath = sExecPath; char newPath[strlen(executablePath) + strlen(path)]; @@ -3337,11 +3467,9 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load } } else if ( (strncmp(path, "@loader_path/", 13) == 0) && (context.origin != NULL) ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED // @loader_path cannot be used from the main executable of a setuid process rdar://problem/4589305 - if ( gLinkContext.processIsRestricted && (strcmp(context.origin, sExecPath) == 0) ) + if ( !gLinkContext.allowAtPaths && (strcmp(context.origin, sExecPath) == 0) ) throwf("unsafe use of @loader_path in %s with restricted binary", context.origin); -#endif // handle @loader_path path prefix char newPath[strlen(context.origin) + strlen(path)]; strcpy(newPath, context.origin); @@ -3405,12 +3533,10 @@ static ImageLoader* loadPhase3(const char* path, const char* orgPath, const Load if ( (exceptions != NULL) && (trailingPath != path) ) return NULL; } -#if __MAC_OS_X_VERSION_MIN_REQUIRED - else if ( gLinkContext.processIsRestricted && (path[0] != '/' ) ) { + else if ( !gLinkContext.allowEnvVarsPath && (path[0] != '/' ) ) { throwf("unsafe use of relative rpath %s in %s with restricted binary", path, context.origin); } -#endif - + return loadPhase4(path, orgPath, context, cacheIndex, exceptions); } @@ -3453,8 +3579,13 @@ static ImageLoader* loadPhase2(const char* path, const char* orgPath, const Load // Look in the cache if appropriate if ( image == NULL) image = loadPhase2cache(npath, orgPath, context, cacheIndex, exceptions); - if ( image != NULL ) + if ( image != NULL ) { + // if original path is in the dyld cache, then mark this one found as an override + dyld3::SharedCacheFindDylibResults shareCacheResults; + if ( dyld3::findInSharedCacheImage(sSharedCacheLoadInfo, path, &shareCacheResults) && (shareCacheResults.image != nullptr) ) + image->setOverridesCachedDylib(shareCacheResults.image->imageNum()); return image; + } } } } @@ -3473,8 +3604,13 @@ static ImageLoader* loadPhase2(const char* path, const char* orgPath, const Load // Look in the cache if appropriate if ( image == NULL) image = loadPhase2cache(libpath, orgPath, context, cacheIndex, exceptions); - if ( image != NULL ) + if ( image != NULL ) { + // if original path is in the dyld cache, then mark this one found as an override + dyld3::SharedCacheFindDylibResults shareCacheResults; + if ( dyld3::findInSharedCacheImage(sSharedCacheLoadInfo, path, &shareCacheResults) && (shareCacheResults.image != nullptr) ) + image->setOverridesCachedDylib(shareCacheResults.image->imageNum()); return image; + } } } return NULL; @@ -3523,16 +3659,41 @@ static ImageLoader* loadPhase0(const char* path, const char* orgPath, const Load { //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions); +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // handle macOS dylibs dlopen()ing versioned path which needs to map to flat path in mazipan simulator + if ( gLinkContext.marzipan && strstr(path, ".framework/Versions/")) { + uintptr_t sourceOffset = 0; + uintptr_t destOffset = 0; + size_t sourceLangth = strlen(path); + char flatPath[sourceLangth]; + flatPath[0] = 0; + const char* frameworkBase = NULL; + while ((frameworkBase = strstr(&path[sourceOffset], ".framework/Versions/"))) { + uintptr_t foundLength = (frameworkBase - &path[sourceOffset]) + strlen(".framework/") ; + strlcat(&flatPath[destOffset], &path[sourceOffset], foundLength); + sourceOffset += foundLength + strlen("Versions/") + 1; + destOffset += foundLength - 1; + } + strlcat(&flatPath[destOffset], &path[sourceOffset], sourceLangth); + ImageLoader* image = loadPhase0(flatPath, orgPath, context, cacheIndex, exceptions); + if ( image != NULL ) + return image; + } +#endif + #if SUPPORT_ROOT_PATH // handle DYLD_ROOT_PATH which forces absolute paths to use a new root if ( (gLinkContext.rootPaths != NULL) && (path[0] == '/') ) { - for(const char* const* rootPath = gLinkContext.rootPaths ; *rootPath != NULL; ++rootPath) { - char newPath[strlen(*rootPath) + strlen(path)+2]; - strcpy(newPath, *rootPath); - strcat(newPath, path); - ImageLoader* image = loadPhase1(newPath, orgPath, context, cacheIndex, exceptions); - if ( image != NULL ) - return image; + for(const char* const* rootPath = gLinkContext.rootPaths; *rootPath != NULL; ++rootPath) { + size_t rootLen = strlen(*rootPath); + if ( strncmp(path, *rootPath, rootLen) != 0 ) { + char newPath[rootLen + strlen(path)+2]; + strcpy(newPath, *rootPath); + strcat(newPath, path); + ImageLoader* image = loadPhase1(newPath, orgPath, context, cacheIndex, exceptions); + if ( image != NULL ) + return image; + } } } #endif @@ -3572,7 +3733,7 @@ ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheI //dyld::log("%s(%s)\n", __func__ , path); char realPath[PATH_MAX]; // when DYLD_IMAGE_SUFFIX is in used, do a realpath(), otherwise a load of "Foo.framework/Foo" will not match - if ( context.useSearchPaths && ( gLinkContext.imageSuffix != NULL) ) { + if ( context.useSearchPaths && ( gLinkContext.imageSuffix != NULL && *gLinkContext.imageSuffix != NULL) ) { if ( realpath(path, realPath) != NULL ) path = realPath; } @@ -3639,6 +3800,8 @@ 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 @@ -3649,6 +3812,7 @@ static void mapSharedCache() // update global state if ( sSharedCacheLoadInfo.loadAddress != nullptr ) { + gLinkContext.dyldCache = sSharedCacheLoadInfo.loadAddress; dyld::gProcessInfo->processDetachedFromSharedRegion = opts.forcePrivate; dyld::gProcessInfo->sharedCacheSlide = sSharedCacheLoadInfo.slide; dyld::gProcessInfo->sharedCacheBaseAddress = (unsigned long)sSharedCacheLoadInfo.loadAddress; @@ -3690,6 +3854,7 @@ ImageLoader* cloneImage(ImageLoader* image) context.mustBeBundle = true; context.mustBeDylib = false; context.canBePIE = false; + context.enforceIOSMac = false; context.origin = NULL; context.rpath = NULL; return loadPhase6(file.getFileDescriptor(), stat_buf, image->getPath(), context); @@ -3745,20 +3910,47 @@ void registerAddCallback(ImageCallback func) // call callback with all existing images for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { ImageLoader* image = *it; - if ( image->getState() >= dyld_image_state_bound && image->getState() < dyld_image_state_terminated ) + if ( image->getState() >= dyld_image_state_bound && image->getState() < dyld_image_state_terminated ) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)image->machHeader(), (uint64_t)(*func), 0); (*func)(image->machHeader(), image->getSlide()); + } } #if SUPPORT_ACCELERATE_TABLES if ( sAllCacheImagesProxy != NULL ) { dyld_image_info infos[allImagesCount()+1]; unsigned cacheCount = sAllCacheImagesProxy->appendImagesToNotify(dyld_image_state_bound, true, infos); for (unsigned i=0; i < cacheCount; ++i) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)infos[i].imageLoadAddress, (uint64_t)(*func), 0); (*func)(infos[i].imageLoadAddress, sSharedCacheLoadInfo.slide); } } #endif } +void registerLoadCallback(LoadImageCallback func) +{ + // now add to list to get notified when any more images are added + sAddLoadImageCallbacks.push_back(func); + + // call callback with all existing images + for (ImageLoader* image : sAllImages) { + if ( image->getState() >= dyld_image_state_bound && image->getState() < dyld_image_state_terminated ) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)image->machHeader(), (uint64_t)(*func), 0); + (*func)(image->machHeader(), image->getPath(), !image->neverUnload()); + } + } +#if SUPPORT_ACCELERATE_TABLES + if ( sAllCacheImagesProxy != NULL ) { + dyld_image_info infos[allImagesCount()+1]; + unsigned cacheCount = sAllCacheImagesProxy->appendImagesToNotify(dyld_image_state_bound, true, infos); + for (unsigned i=0; i < cacheCount; ++i) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, (uint64_t)infos[i].imageLoadAddress, (uint64_t)(*func), 0); + (*func)(infos[i].imageLoadAddress, infos[i].imageFilePath, false); + } + } +#endif +} + void registerRemoveCallback(ImageCallback func) { // ignore calls to register a notification during a notification @@ -3925,11 +4117,13 @@ static void undefinedHandler(const char* symboName) } } -static bool findExportedSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image) +static bool findExportedSymbol(const char* name, bool onlyInCoalesced, const ImageLoader::Symbol** sym, const ImageLoader** image, ImageLoader::CoalesceNotifier notifier=NULL) { // search all images in order const ImageLoader* firstWeakImage = NULL; const ImageLoader::Symbol* firstWeakSym = NULL; + const ImageLoader* firstNonWeakImage = NULL; + const ImageLoader::Symbol* firstNonWeakSym = NULL; const size_t imageCount = sAllImages.size(); for(size_t i=0; i < imageCount; ++i) { ImageLoader* anImage = sAllImages[i]; @@ -3941,23 +4135,42 @@ static bool findExportedSymbol(const char* name, bool onlyInCoalesced, const Ima else if ( i == sInsertedDylibCount ) anImage = sAllImages[0]; } + //dyld::log("findExportedSymbol(%s) looking at %s\n", name, anImage->getPath()); if ( ! anImage->hasHiddenExports() && (!onlyInCoalesced || anImage->hasCoalescedExports()) ) { - *sym = anImage->findExportedSymbol(name, false, image); + const ImageLoader* foundInImage; + *sym = anImage->findExportedSymbol(name, false, &foundInImage); + //dyld::log("findExportedSymbol(%s) found: sym=%p, anImage=%p, foundInImage=%p\n", name, *sym, anImage, foundInImage /*, (foundInImage ? foundInImage->getPath() : "")*/); if ( *sym != NULL ) { + if ( notifier && (foundInImage == anImage) ) + notifier(*sym, foundInImage, foundInImage->machHeader()); // if weak definition found, record first one found - if ( ((*image)->getExportedSymbolInfo(*sym) & ImageLoader::kWeakDefinition) != 0 ) { + if ( (foundInImage->getExportedSymbolInfo(*sym) & ImageLoader::kWeakDefinition) != 0 ) { if ( firstWeakImage == NULL ) { - firstWeakImage = *image; + firstWeakImage = foundInImage; firstWeakSym = *sym; } } else { - // found non-weak, so immediately return with it - return true; + // found non-weak + if ( !onlyInCoalesced ) { + // for flat lookups, return first found + *image = foundInImage; + return true; + } + if ( firstNonWeakImage == NULL ) { + firstNonWeakImage = foundInImage; + firstNonWeakSym = *sym; + } } } } } + if ( firstNonWeakImage != NULL ) { + // found a weak definition, but no non-weak, so return first weak found + *sym = firstNonWeakSym; + *image = firstNonWeakImage; + return true; + } if ( firstWeakSym != NULL ) { // found a weak definition, but no non-weak, so return first weak found *sym = firstWeakSym; @@ -3966,7 +4179,7 @@ static bool findExportedSymbol(const char* name, bool onlyInCoalesced, const Ima } #if SUPPORT_ACCELERATE_TABLES if ( sAllCacheImagesProxy != NULL ) { - if ( sAllCacheImagesProxy->flatFindSymbol(name, onlyInCoalesced, sym, image) ) + if ( sAllCacheImagesProxy->flatFindSymbol(name, onlyInCoalesced, sym, image, notifier) ) return true; } #endif @@ -3979,9 +4192,9 @@ bool flatFindExportedSymbol(const char* name, const ImageLoader::Symbol** sym, c return findExportedSymbol(name, false, sym, image); } -bool findCoalescedExportedSymbol(const char* name, const ImageLoader::Symbol** sym, const ImageLoader** image) +bool findCoalescedExportedSymbol(const char* name, const ImageLoader::Symbol** sym, const ImageLoader** image, ImageLoader::CoalesceNotifier notifier) { - return findExportedSymbol(name, true, sym, image); + return findExportedSymbol(name, true, sym, image, notifier); } @@ -4107,6 +4320,7 @@ void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_in for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { ImageLoader* image = *it; if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0); (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); } } @@ -4210,7 +4424,7 @@ bool dladdrFromCache(const void* address, Dl_info* info) } #endif -static ImageLoader* libraryLocator(const char* libraryName, bool search, const char* origin, const ImageLoader::RPathChain* rpaths, unsigned& cacheIndex) +static ImageLoader* libraryLocator(const char* libraryName, bool search, const char* origin, const ImageLoader::RPathChain* rpaths, bool enforceIOSMac, unsigned& cacheIndex) { dyld::LoadContext context; context.useSearchPaths = search; @@ -4222,6 +4436,7 @@ static ImageLoader* libraryLocator(const char* libraryName, bool search, const c context.mustBeBundle = false; context.mustBeDylib = true; context.canBePIE = false; + context.enforceIOSMac = enforceIOSMac; context.origin = origin; context.rpath = rpaths; return load(libraryName, context, cacheIndex); @@ -4534,30 +4749,32 @@ void garbageCollectImages() } // collect phase: run termination routines for images not marked in-use - __cxa_range_t ranges[maxRangeCount]; - int rangeCount = 0; - for (unsigned i=0; i < deadCount; ++i) { - ImageLoader* image = deadImages[i]; - for (unsigned int j=0; j < image->segmentCount(); ++j) { - if ( !image->segExecutable(j) ) - continue; - if ( rangeCount < maxRangeCount ) { - ranges[rangeCount].addr = (const void*)image->segActualLoadAddress(j); - ranges[rangeCount].length = image->segSize(j); - ++rangeCount; + if ( maxRangeCount != 0 ) { + __cxa_range_t ranges[maxRangeCount]; + int rangeCount = 0; + for (unsigned i=0; i < deadCount; ++i) { + ImageLoader* image = deadImages[i]; + for (unsigned int j=0; j < image->segmentCount(); ++j) { + if ( !image->segExecutable(j) ) + continue; + if ( rangeCount < maxRangeCount ) { + ranges[rangeCount].addr = (const void*)image->segActualLoadAddress(j); + ranges[rangeCount].length = image->segSize(j); + ++rangeCount; + } + } + try { + runImageStaticTerminators(image); + } + catch (const char* msg) { + dyld::warn("problem running terminators for image: %s\n", msg); } } - try { - runImageStaticTerminators(image); - } - catch (const char* msg) { - dyld::warn("problem running terminators for image: %s\n", msg); - } + + // dyld should call __cxa_finalize_ranges() + if ( (rangeCount > 0) && (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 13) ) + (*gLibSystemHelpers->cxa_finalize_ranges)(ranges, rangeCount); } - - // dyld should call __cxa_finalize_ranges() - if ( (rangeCount > 0) && (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 13) ) - (*gLibSystemHelpers->cxa_finalize_ranges)(ranges, rangeCount); // collect phase: delete all images which are not marked in-use bool mightBeMore; @@ -4632,21 +4849,16 @@ static void loadInsertedDylib(const char* path) context.mustBeBundle = false; context.mustBeDylib = true; context.canBePIE = false; + context.enforceIOSMac = true; context.origin = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES context.rpath = NULL; image = load(path, context, cacheIndex); } catch (const char* msg) { -#if TARGET_IPHONE_SIMULATOR - dyld::log("dyld: warning: could not load inserted library '%s' because %s\n", path, msg); -#else -#if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( gLinkContext.processUsingLibraryValidation ) - dyld::log("dyld: warning: could not load inserted library '%s' into library validated process because %s\n", path, msg); + if ( gLinkContext.allowInsertFailures ) + dyld::log("dyld: warning: could not load inserted library '%s' into hardened process because %s\n", path, msg); else -#endif halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg)); -#endif } catch (...) { halt(dyld::mkstringf("could not load inserted library '%s'\n", path)); @@ -4654,78 +4866,66 @@ static void loadInsertedDylib(const char* path) } -// -// Sets: -// sEnvMode -// gLinkContext.requireCodeSignature -// gLinkContext.processIsRestricted // Mac OS X only -// gLinkContext.processUsingLibraryValidation // Mac OS X only -// static void configureProcessRestrictions(const macho_header* mainExecutableMH) { + uint64_t amfiInputFlags = 0; #if TARGET_IPHONE_SIMULATOR - sEnvMode = envAll; - gLinkContext.requireCodeSignature = true; + amfiInputFlags |= AMFI_DYLD_INPUT_PROC_IN_SIMULATOR; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED + if ( hasRestrictedSegment(mainExecutableMH) ) + amfiInputFlags |= AMFI_DYLD_INPUT_PROC_HAS_RESTRICT_SEG; #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 ) { - // Xcode built app for Debug allowed to use DYLD_* variables - sEnvMode = envAll; + if ( isFairPlayEncrypted(mainExecutableMH) ) + amfiInputFlags |= AMFI_DYLD_INPUT_PROC_IS_ENCRYPTED; +#endif + uint64_t amfiOutputFlags = 0; + if ( amfi_check_dyld_policy_self(amfiInputFlags, &amfiOutputFlags) == 0 ) { + gLinkContext.allowAtPaths = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_AT_PATH); + gLinkContext.allowEnvVarsPrint = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_PRINT_VARS); + gLinkContext.allowEnvVarsPath = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_PATH_VARS); + gLinkContext.allowEnvVarsSharedCache = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_CUSTOM_SHARED_CACHE); + gLinkContext.allowClassicFallbackPaths = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_FALLBACK_PATHS); + gLinkContext.allowInsertFailures = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_FAILED_LIBRARY_INSERTION); + } + else { +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // support chrooting from old kernel + bool isRestricted = false; + bool libraryValidation = false; + // any processes with setuid or setgid bit set or with __RESTRICT segment is restricted + if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) { + isRestricted = 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 ) { + isRestricted = true; } - else { - // Development kernel can use DYLD_PRINT_* variables on any FairPlay encrypted app - uint32_t secureValue = 0; - size_t secureValueSize = sizeof(secureValue); - if ( (sysctlbyname("kern.secure_kernel", &secureValue, &secureValueSize, NULL, 0) == 0) && (secureValue == 0) && isFairPlayEncrypted(mainExecutableMH) ) { - sEnvMode = envPrintOnly; - } + // Library Validation loosens searching but requires everything to be code signed + if ( flags & CS_REQUIRE_LV ) { + isRestricted = false; + libraryValidation = true; } } - else { - // Development kernel can run unsigned code - sEnvMode = envAll; - gLinkContext.requireCodeSignature = false; - } - } - if ( issetugid() ) { - sEnvMode = envNone; - } -#elif __MAC_OS_X_VERSION_MIN_REQUIRED - sEnvMode = envAll; - gLinkContext.requireCodeSignature = false; - gLinkContext.processIsRestricted = false; - gLinkContext.processUsingLibraryValidation = false; - // any processes with setuid or setgid bit set or with __RESTRICT segment is restricted - if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) { - gLinkContext.processIsRestricted = true; - } - 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 ) { - gLinkContext.processIsRestricted = true; - } - // Library Validation loosens searching but requires everything to be code signed - if ( flags & CS_REQUIRE_LV ) { - gLinkContext.processIsRestricted = false; - //gLinkContext.requireCodeSignature = true; - gLinkContext.processUsingLibraryValidation = true; - sSafeMode = usingSIP; - } - } + gLinkContext.allowAtPaths = !isRestricted; + gLinkContext.allowEnvVarsPrint = !isRestricted; + gLinkContext.allowEnvVarsPath = !isRestricted; + gLinkContext.allowEnvVarsSharedCache = !libraryValidation || !usingSIP; + gLinkContext.allowClassicFallbackPaths = !isRestricted; + gLinkContext.allowInsertFailures = false; +#else + halt("amfi_check_dyld_policy_self() failed\n"); #endif + } } bool processIsRestricted() { #if __MAC_OS_X_VERSION_MIN_REQUIRED - return gLinkContext.processIsRestricted; + return !gLinkContext.allowEnvVarsPath; #else return false; #endif @@ -4784,7 +4984,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 = { - 8, + 12, // added in version 1 (open_proc_t)&open, &close, @@ -4843,7 +5043,18 @@ static SyscallHelpers sSysCalls = { &task_info, &thread_info, &kdebug_is_enabled, - &kdebug_trace + &kdebug_trace, + // Added in version 9 + &kdebug_trace_string, + // Added in version 10 + &amfi_check_dyld_policy_self, + // Added in version 11 + ¬ifyMonitoringDyldMain, + ¬ifyMonitoringDyld, + // Add in version 12 + &mach_msg_destroy, + &mach_port_construct, + &mach_port_destruct }; __attribute__((noinline)) @@ -4854,12 +5065,16 @@ static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH *startGlue = 0; *mainAddr = 0; + // HACK to allow marzipan dyld_sim to run entitled processes + if ( strncmp(dyldPath, "/System/", 8) != 0 ) { + uint32_t flags; + if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) == -1 ) + return "csops() failed"; + if ( (flags & CS_RESTRICT) == CS_RESTRICT ) + return "dyld_sim cannot be loaded in a restricted process"; + } + // simulator does not support restricted processes - uint32_t flags; - if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) == -1 ) - return "csops() failed"; - if ( (flags & CS_RESTRICT) == CS_RESTRICT ) - return "dyld_sim cannot be loaded in a restricted process"; if ( issetugid() ) return "dyld_sim cannot be loaded in a setuid process"; if ( hasRestrictedSegment(mainExecutableMH) ) @@ -4901,6 +5116,7 @@ static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH return "dyld_sim load commands to large"; if ( (sizeof(macho_header) + mh->sizeofcmds) > 4096 ) return "dyld_sim load commands to large"; + struct linkedit_data_command* codeSigCmd = NULL; const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); const struct load_command* const endCmds = (struct load_command*)(((char*)mh) + sizeof(macho_header) + mh->sizeofcmds); const struct load_command* cmd = cmds; @@ -4947,21 +5163,48 @@ static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH break; case LC_SEGMENT_COMMAND_WRONG: return "dyld_sim wrong load segment load command"; + case LC_CODE_SIGNATURE: + codeSigCmd = (struct linkedit_data_command*)cmd; + break; } cmd = nextCmd; } // last segment must be named __LINKEDIT and not writable + if ( lastSeg == NULL ) + return "dyld_sim has no segments"; if ( strcmp(lastSeg->segname, "__LINKEDIT") != 0 ) return "dyld_sim last segment not __LINKEDIT"; if ( lastSeg->initprot & VM_PROT_WRITE ) return "dyld_sim __LINKEDIT segment writable"; + // must have code signature which is contained within LINKEDIT segment + if ( codeSigCmd == NULL ) + return "dyld_sim not code signed"; + if ( codeSigCmd->dataoff < lastSeg->fileoff ) + return "dyld_sim code signature not in __LINKEDIT"; + if ( (codeSigCmd->dataoff + codeSigCmd->datasize) < codeSigCmd->dataoff ) + return "dyld_sim code signature size wraps"; + if ( (codeSigCmd->dataoff + codeSigCmd->datasize) > (lastSeg->fileoff + lastSeg->filesize) ) + return "dyld_sim code signature extends beyond __LINKEDIT"; + + // register code signature with kernel before mmap()ing segments + fsignatures_t siginfo; + siginfo.fs_file_start=fileOffset; // start of mach-o slice in fat file + siginfo.fs_blob_start=(void*)(long)(codeSigCmd->dataoff); // start of code-signature in mach-o file + siginfo.fs_blob_size=codeSigCmd->datasize; // size of code-signature + int result = fcntl(fd, F_ADDFILESIGS_FOR_DYLD_SIM, &siginfo); + if ( result == -1 ) { + return mkstringf("dyld_sim fcntl(F_ADDFILESIGS_FOR_DYLD_SIM) failed with errno=%d", errno); + } + // file range covered by code signature must extend up to code signature itself + 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); + // reserve space, then mmap each segment vm_address_t loadAddress = 0; if ( ::vm_allocate(mach_task_self(), &loadAddress, mappingSize, VM_FLAGS_ANYWHERE) != 0 ) return "dyld_sim cannot allocate space"; cmd = cmds; - struct linkedit_data_command* codeSigCmd = NULL; struct source_version_command* dyldVersionCmd = NULL; for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { @@ -4977,38 +5220,13 @@ static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH return "dyld_sim mmap() to wrong location"; } break; - case LC_CODE_SIGNATURE: - codeSigCmd = (struct linkedit_data_command*)cmd; - break; case LC_SOURCE_VERSION: dyldVersionCmd = (struct source_version_command*)cmd; break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } - - // must have code signature which is contained within LINKEDIT segment - if ( codeSigCmd == NULL ) - return "dyld_sim not code signed"; - if ( codeSigCmd->dataoff < lastSeg->fileoff ) - return "dyld_sim code signature not in __LINKEDIT"; - if ( (codeSigCmd->dataoff + codeSigCmd->datasize) < codeSigCmd->dataoff ) - return "dyld_sim code signature size wraps"; - if ( (codeSigCmd->dataoff + codeSigCmd->datasize) > (lastSeg->fileoff + lastSeg->filesize) ) - return "dyld_sim code signature extends beyond __LINKEDIT"; - - fsignatures_t siginfo; - siginfo.fs_file_start=fileOffset; // start of mach-o slice in fat file - siginfo.fs_blob_start=(void*)(long)(codeSigCmd->dataoff); // start of code-signature in mach-o file - siginfo.fs_blob_size=codeSigCmd->datasize; // size of code-signature - int result = fcntl(fd, F_ADDFILESIGS_FOR_DYLD_SIM, &siginfo); - if ( result == -1 ) { - return mkstringf("dyld_sim fcntl(F_ADDFILESIGS_FOR_DYLD_SIM) failed with errno=%d", errno); - } close(fd); - // file range covered by code signature must extend up to code signature itself - if ( siginfo.fs_file_start < codeSigCmd->dataoff ) - return 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; @@ -5036,6 +5254,8 @@ static const char* useSimulatorDyld(int fd, const macho_header* mainExecutableMH } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } + if ( entry == 0 ) + return "dyld_sim entry not found"; // notify debugger that dyld_sim is loaded dyld_image_info info; @@ -5072,10 +5292,10 @@ fake_main() -static bool envVarMatches(dyld3::launch_cache::Closure mainClosure, const char* envp[], const char* varName) +static bool envVarMatches(const dyld3::closure::LaunchClosure* mainClosure, const char* envp[], const char* varName) { __block const char* valueFromClosure = nullptr; - mainClosure.forEachEnvVar(^(const char* keyEqualValue, bool& stop) { + mainClosure->forEachEnvVar(^(const char* keyEqualValue, bool& stop) { size_t keyLen = strlen(varName); if ( (strncmp(varName, keyEqualValue, keyLen) == 0) && (keyEqualValue[keyLen] == '=') ) { valueFromClosure = &keyEqualValue[keyLen+1]; @@ -5106,12 +5326,12 @@ static const char* const sEnvVarsToCheck[] = { "DYLD_ROOT_PATH" }; -static bool envVarsMatch(dyld3::launch_cache::Closure mainClosure, const char* envp[]) +static bool envVarsMatch(const dyld3::closure::LaunchClosure* 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); + dyld::log("dyld: closure %p not used because %s changed\n", mainClosure, envVar); return false; } } @@ -5120,98 +5340,118 @@ static bool envVarsMatch(dyld3::launch_cache::Closure mainClosure, const char* e // dyld3: support DYLD_VERSIONED_*_PATHs ? if ( sEnv.DYLD_VERSIONED_LIBRARY_PATH != nullptr ) { if ( gLinkContext.verboseWarnings ) - dyld::log("dyld: closure %p not used because DYLD_VERSIONED_LIBRARY_PATH used\n", mainClosure.binaryData()); + dyld::log("dyld: closure %p not used because DYLD_VERSIONED_LIBRARY_PATH used\n", mainClosure); return false; } if ( sEnv.DYLD_VERSIONED_FRAMEWORK_PATH != nullptr ) { if ( gLinkContext.verboseWarnings ) - dyld::log("dyld: closure %p not used because DYLD_VERSIONED_FRAMEWORK_PATH used\n", mainClosure.binaryData()); + dyld::log("dyld: closure %p not used because DYLD_VERSIONED_FRAMEWORK_PATH used\n", mainClosure); 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[]) +static bool closureValid(const dyld3::closure::LaunchClosure* mainClosure, const dyld3::closure::LoadedFileInfo& mainFileInfo, + 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; + // verify current dyld cache is same as expected + uuid_t expectedCacheUUID; + if ( mainClosure->builtAgainstDyldCache(expectedCacheUUID) ) { + if ( sSharedCacheLoadInfo.loadAddress == nullptr ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p dyld cache not loaded\n", mainClosure); + return false; + } + else { + uuid_t actualCacheUUID; + sSharedCacheLoadInfo.loadAddress->getUUID(actualCacheUUID); + if ( memcmp(expectedCacheUUID, actualCacheUUID, sizeof(uuid_t)) != 0 ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because built against different dyld cache\n", mainClosure); + return false; + } + } } - } -#if __MAC_OS_X_VERSION_MIN_REQUIRED - else { - // If the in-memory cache doesn't have the same UUID xattr as the on-disk cache then we must - // have built a new cache but not rebooted. In this case, don't use dyld3. - const char* sharedCachePath = getStandardSharedCacheFilePath(); - uuid_t inMemoryUUID; - uuid_t onDiskUUID; - sharedCacheUUID(inMemoryUUID); - if (getxattr(sharedCachePath, "cacheUUID", (void*)&onDiskUUID, sizeof(uuid_t), 0, 0) != sizeof(uuid_t)) { - if ( gLinkContext.verboseWarnings ) - dyld::log("dyld: closure %p on disk cache doesn't have a UUID xattr\n", mainClosureData); - return false; + else { + // closure built assume there is no dyld cache + if ( sSharedCacheLoadInfo.loadAddress != nullptr ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p built expecting no dyld cache\n", mainClosure); + return false; + } } - if (memcmp(&inMemoryUUID, &onDiskUUID, sizeof(uuid_t)) != 0) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED + // verify this closure is not from a previous reboot + const char* expectedBootUUID = mainClosure->bootUUID(); + char actualBootSessionUUID[256] = { 0 }; + size_t bootSize = sizeof(actualBootSessionUUID); + bool gotActualBootUUID = (sysctlbyname("kern.bootsessionuuid", actualBootSessionUUID, &bootSize, NULL, 0) == 0); + if ( !gotActualBootUUID || (expectedBootUUID == nullptr) || (strcmp(expectedBootUUID, actualBootSessionUUID) != 0) ) { if ( gLinkContext.verboseWarnings ) - dyld::log("dyld: closure %p not used because current cache on disk and in memory cache have UUID mismatches\n", mainClosureData); + dyld::log("dyld: closure %p built in different boot context\n", mainClosure); return false; } - } #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 all mach-o files have not changed since closure was built + __block bool foundFileThatInvalidatesClosure = false; + mainClosure->images()->forEachImage(^(const dyld3::closure::Image* image, bool& stop) { + __block uint64_t expectedInode; + __block uint64_t expectedMtime; + if ( image->hasFileModTimeAndInode(expectedInode, expectedMtime) ) { + struct stat statBuf; + if ( ::stat(image->path(), &statBuf) == 0 ) { + if ( (statBuf.st_mtime != expectedMtime) || (statBuf.st_ino != expectedInode) ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because mtime/inode for '%s' has changed since closure was built\n", mainClosure, image->path()); + foundFileThatInvalidatesClosure = true; + stop = true; + } + } + else { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because '%s' is needed by closure but is missing\n", mainClosure, image->path()); + foundFileThatInvalidatesClosure = true; + stop = true; + } + } + }); + if ( foundFileThatInvalidatesClosure ) + return false; // verify cdHash of main executable is same as recorded in closure - if ( mainImage.validateUsingCdHash() ) { + uint8_t expectedHash[20]; + const dyld3::closure::Image* mainImage = mainClosure->images()->imageForNum(mainClosure->topImage()); + if ( mainImage->hasCdHash(expectedHash) ) { 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); + dyld::log("dyld: closure %p not used because main executable is not code signed but was expected to be\n", mainClosure); return false; } - if ( memcmp(mainExecutableCDHash, mainClosure.cdHash(), 20) != 0 ) { + if ( memcmp(mainExecutableCDHash, expectedHash, 20) != 0 ) { if ( gLinkContext.verboseWarnings ) - dyld::log("dyld: closure %p not used because main executable cd-hash changed since closure was built\n", mainClosureData); + dyld::log("dyld: closure %p not used because main executable cd-hash changed since closure was built\n", mainClosure); 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 expectedUUID; + bool hasExpect = mainImage->getUuid(expectedUUID); uuid_t actualUUID; - parser.getUuid(actualUUID); - if ( memcmp(actualUUID, closureMainUUID, sizeof(uuid_t)) != 0 ) { + const dyld3::MachOLoaded* mainExecutableMH = (const dyld3::MachOLoaded*)mainFileInfo.fileContent; + bool hasActual = mainExecutableMH->getUuid(actualUUID); + if ( hasExpect != hasActual ) { if ( gLinkContext.verboseWarnings ) - dyld::log("dyld: closure %p not used because UUID of executable changed since closure was built\n", mainClosureData); + dyld::log("dyld: closure %p not used because UUID of executable changed since closure was built\n", mainClosure); + return false; + } + if ( hasExpect && hasActual && memcmp(actualUUID, expectedUUID, sizeof(uuid_t)) != 0 ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because UUID of executable changed since closure was built\n", mainClosure); return false; } @@ -5221,37 +5461,27 @@ static bool closureValid(const dyld3::launch_cache::BinaryClosureData* mainClosu } // verify files that are supposed to be missing actually are missing - __block bool foundFileThatInvalidatesClosure = false; - mainClosure.forEachMustBeMissingFile(^(const char* path, bool& stop) { + 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); + dyld::log("dyld: closure %p not used because found unexpected file '%s'\n", mainClosure, 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; - } - } - } - }); + // verify closure did not require anything unavailable + if ( mainClosure->usedAtPaths() && !gLinkContext.allowAtPaths ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because is used @paths, but process does not allow that\n", mainClosure); + return false; + } + if ( mainClosure->usedFallbackPaths() && !gLinkContext.allowClassicFallbackPaths ) { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: closure %p not used because is used default fallback paths, but process does not allow that\n", mainClosure); + return false; } -#endif return !foundFileThatInvalidatesClosure; } @@ -5270,67 +5500,50 @@ static bool dolog(const char* format, ...) return true; } -static bool launchWithClosure(const dyld3::launch_cache::BinaryClosureData* mainClosureData, +static bool launchWithClosure(const dyld3::closure::LaunchClosure* mainClosure, const DyldSharedCache* dyldCache, - const mach_header* mainExecutableMH, uintptr_t mainExecutableSlide, + const dyld3::MachOLoaded* 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; + // build list of all known ImageArrays (at most three: cached dylibs, other OS dylibs, and main prog) + STACK_ALLOC_ARRAY(const dyld3::closure::ImageArray*, imagesArrays, 3); + const dyld3::closure::ImageArray* mainClosureImages = mainClosure->images(); + if ( dyldCache != nullptr ) { + imagesArrays.push_back(dyldCache->cachedDylibsImageArray()); + if ( auto others = dyldCache->otherOSImageArray() ) + imagesArrays.push_back(others); + } + imagesArrays.push_back(mainClosureImages); + + // allocate space for Array + STACK_ALLOC_ARRAY(dyld3::LoadedImage, allImages, mainClosure->initialLoadCount()); + + __block dyld3::Loader loader(allImages, dyldCache, imagesArrays, (gLinkContext.verboseLoading ? &dolog : &nolog), + (gLinkContext.verboseMapping ? &dolog : &nolog), + (gLinkContext.verboseBind ? &dolog : &nolog), + (gLinkContext.verboseDOF ? &dolog : &nolog)); + dyld3::closure::ImageNum mainImageNum = mainClosure->topImage(); + mainClosureImages->forEachImage(^(const dyld3::closure::Image* image, bool& stop) { + if ( image->imageNum() == mainImageNum ) { + // add main executable (which is already mapped by kernel) to list + dyld3::LoadedImage mainLoadedImage = dyld3::LoadedImage::make(image, mainExecutableMH); + mainLoadedImage.setState(dyld3::LoadedImage::State::mapped); + mainLoadedImage.markLeaveMapped(); + loader.addImage(mainLoadedImage); + stop = true; } - } - 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 + else { + // add inserted library to initial list + loader.addImage(dyld3::LoadedImage::make(image)); + } + }); + + // recursively load all dependents and fill in allImages array 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)); + loader.completeAllDependents(diag); + if ( diag.noError() ) + loader.mapAndFixupAllImages(diag, dyld3::Loader::dtraceUserProbesEnabled()); if ( diag.hasError() ) { if ( gLinkContext.verboseWarnings ) dyld::log("dyld: %s\n", diag.errorMessage()); @@ -5338,27 +5551,24 @@ static bool launchWithClosure(const dyld3::launch_cache::BinaryClosureData* main } //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()); + //for (const dyld3::LoadedImage& info : allImages) { + // dyld::log("mh=%p, path=%s\n", info.loadedAddress(), info.image()->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; - } + // find libdyld entry + dyld3::closure::Image::ResolvedSymbolTarget dyldEntry; + mainClosure->libDyldEntry(dyldEntry); + const dyld3::LibDyldEntryVector* libDyldEntry = (dyld3::LibDyldEntryVector*)loader.resolveTarget(dyldEntry); // 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); + if ( libDyldEntry->vectorVersion > 4 ) + libDyldEntry->setRestrictions(gLinkContext.allowAtPaths, gLinkContext.allowEnvVarsPath); libDyldEntry->setHaltFunction(&halt); + if ( libDyldEntry->vectorVersion > 5 ) { + libDyldEntry->setNotifyMonitoringDyldMain(¬ifyMonitoringDyldMain); + libDyldEntry->setNotifyMonitoringDyld(¬ifyMonitoringDyld); + } if ( libDyldEntry->vectorVersion > 2 ) libDyldEntry->setChildForkFunction(&_dyld_fork_child); #if !TARGET_IPHONE_SIMULATOR @@ -5366,30 +5576,41 @@ static bool launchWithClosure(const dyld3::launch_cache::BinaryClosureData* main libDyldEntry->setLogFunction(&dyld::vlog); #endif libDyldEntry->setOldAllImageInfo(gProcessInfo); - libDyldEntry->setInitialImageList(mainClosureData, dyldCache, sSharedCacheLoadInfo.path, allImages, libSystemMH, libSystemImage); + const dyld3::LoadedImage* libSys = loader.findImage(mainClosure->libSystemImageNum()); + libDyldEntry->setInitialImageList(mainClosure, dyldCache, sSharedCacheLoadInfo.path, allImages, *libSys); // 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(); + if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) { + dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 3); } - else { + dyld3::closure::Image::ResolvedSymbolTarget progEntry; + if ( mainClosure->mainEntry(progEntry) ) { // 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(); + *entry = loader.resolveTarget(progEntry); } - CRSetCrashLogMessage(NULL); + else if ( mainClosure->startEntry(progEntry) ) { + // old style app linked with crt1.o + // entry is "start" function in program + *startGlue = 0; + *entry = loader.resolveTarget(progEntry); + } + else { + assert(0); + } + + CRSetCrashLogMessage("dyld3 mode"); return true; } + +#if !TARGET_IPHONE_SIMULATOR + static void putHexNibble(uint8_t value, char*& p) { if ( value < 10 ) @@ -5405,191 +5626,206 @@ static void putHexByte(uint8_t value, char*& 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); +#if __MAC_OS_X_VERSION_MIN_REQUIRED +static void makeHashOfProgramAndEnv(const char* mainExecutablePath, const uint8_t* mainExecutableCDHash, const char* envp[], uint8_t hash32[32]) +{ + // create hash of main path, main cd hash, cache UUID, DYLD_* env vars + const struct ccdigest_info* di = ccsha256_di(); + ccdigest_di_decl(di, hashTemp); // defines hashTemp array in stack + ccdigest_init(di, hashTemp); + // hash in main executable path + ccdigest_update(di, hashTemp, strlen(mainExecutablePath), mainExecutablePath); + // hash in cdHash of main executable + if ( mainExecutableCDHash != nullptr ) + ccdigest_update(di, hashTemp, 20, mainExecutableCDHash); + // hash in shared cache UUID + if ( sSharedCacheLoadInfo.loadAddress != nullptr ) { + uuid_t cacheUUID; + sSharedCacheLoadInfo.loadAddress->getUUID(cacheUUID); + ccdigest_update(di, hashTemp, sizeof(uuid_t), cacheUUID); + } +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // hash in if process is restricted + ccdigest_update(di, hashTemp, sizeof(gLinkContext.allowEnvVarsPath), &gLinkContext.allowEnvVarsPath); #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'; + // include dyld's UUID so changing dyld invalidates closures + uuid_t dyldUUID; + if ( ((const dyld3::MachOLoaded*)&__dso_handle)->getUuid(dyldUUID) ) + ccdigest_update(di, hashTemp, sizeof(uuid_t), dyldUUID); + + // hash in DYLD_* env vars + for (const char* envVar : sEnvVarsToCheck) { + if ( const char* keyValue = _simple_getenv(envp, envVar) ) + ccdigest_update(di, hashTemp, strlen(keyValue), keyValue); + } + // finish SHA256 into 32-byte value + ccdigest_final(di, hashTemp, hash32); + ccdigest_di_clear(di, hashTemp); } +#endif -#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; +static void buildClosureCachePath(const char* mainExecutablePath,const dyld3::MachOLoaded* mainExecutableMH, + const uint8_t* mainExecutableCDHash, const char* envp[], char closurePath[]) +{ + // build base path of $TMPDIR/dyld/- + const char* tempDir = _simple_getenv(envp, "TMPDIR"); + if ( tempDir == nullptr ) +#if __MAC_OS_X_VERSION_MIN_REQUIRED + tempDir = "/private/tmp/"; +#else + tempDir = "/private/var/tmp/"; +#endif + strlcpy(closurePath, tempDir, PATH_MAX); + strlcat(closurePath, "/com.apple.dyld/", PATH_MAX); + + // make sure dyld sub-dir exists + struct stat statbuf; + if ( ::stat(closurePath, &statbuf) != 0 ) { + ::mkdir(closurePath, S_IRWXU); + } + const char* leafName = strrchr(mainExecutablePath, '/'); + if ( leafName == nullptr ) + leafName = mainExecutablePath; + else + ++leafName; + strlcat(closurePath, leafName, PATH_MAX); + +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // on macOS we allow multiple closures by hashing the env vars into the cache filename + strlcat(closurePath, "-", PATH_MAX); + uint8_t hash32[32]; + makeHashOfProgramAndEnv(mainExecutablePath, mainExecutableCDHash, envp, hash32); + char hashString[72]; + char* s = hashString; + for (int i=0; i < 32; ++i) + putHexByte(hash32[i], s); + *s = '\0'; + strlcat(closurePath, hashString, PATH_MAX); #else - // get port to closured - mach_port_t serverPort = dyld3::loader::lookupClosuredPort(); - if ( serverPort == MACH_PORT_NULL ) - return NULL; + // on iOS, the file name is the leaf name and UUID + uuid_t mainExeUUID; + if ( mainExecutableMH->getUuid(mainExeUUID) ) { + char mainUuidStr[40]; + bytesToHex(mainExeUUID, sizeof(uuid_t), mainUuidStr); + strlcat(closurePath, "-", PATH_MAX); + strlcat(closurePath, mainUuidStr, PATH_MAX); + } +#endif + strlcat(closurePath, ".closure", PATH_MAX); +} - // 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; - } +static const dyld3::closure::LaunchClosure* mapClosureFile(const char* closurePath) +{ + struct stat statbuf; + if ( ::stat(closurePath, &statbuf) == -1 ) + return nullptr; + + int fd = ::open(closurePath, O_RDONLY); + if ( fd < 0 ) + return nullptr; + + const dyld3::closure::LaunchClosure* closure = (dyld3::closure::LaunchClosure*)::mmap(NULL, (size_t)statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + ::close(fd); + + if ( closure == MAP_FAILED ) + return nullptr; + + return closure; +} + +static const dyld3::closure::LaunchClosure* buildLaunchClosure(const uint8_t* mainExecutableCDHash, + const dyld3::closure::LoadedFileInfo& mainFileInfo, const char* envp[]) +{ + const dyld3::MachOLoaded* mainExecutableMH = (const dyld3::MachOLoaded*)mainFileInfo.fileContent; + dyld3::closure::PathOverrides pathOverrides; + pathOverrides.setFallbackPathHandling(gLinkContext.allowClassicFallbackPaths ? dyld3::closure::PathOverrides::FallbackPathMode::classic : dyld3::closure::PathOverrides::FallbackPathMode::restricted); + pathOverrides.setEnvVars(envp, mainExecutableMH, mainFileInfo.path); + STACK_ALLOC_ARRAY(const dyld3::closure::ImageArray*, imagesArrays, 3); + if ( sSharedCacheLoadInfo.loadAddress != nullptr ) { + imagesArrays.push_back(sSharedCacheLoadInfo.loadAddress->cachedDylibsImageArray()); + if ( auto others = sSharedCacheLoadInfo.loadAddress->otherOSImageArray() ) + imagesArrays.push_back(others); } - *s++ = '\0'; - // get uuid of main executable - dyld3::MachOParser mainParser((mach_header*)sMainExecutableMachHeader); - uuid_t mainUuid; - mainParser.getUuid(mainUuid); + dyld3::closure::ClosureBuilder::LaunchErrorInfo* errorInfo = (dyld3::closure::ClosureBuilder::LaunchErrorInfo*)&gProcessInfo->errorKind; + dyld3::closure::FileSystemPhysical fileSystem; + dyld3::closure::ClosureBuilder::AtPath atPathHanding = (gLinkContext.allowAtPaths ? dyld3::closure::ClosureBuilder::AtPath::all : dyld3::closure::ClosureBuilder::AtPath::none); + dyld3::closure::ClosureBuilder builder(dyld3::closure::kFirstLaunchClosureImageNum, fileSystem, sSharedCacheLoadInfo.loadAddress, true, pathOverrides, atPathHanding, errorInfo, mainExecutableMH->archName()); + const dyld3::closure::LaunchClosure* result = builder.makeLaunchClosure(mainFileInfo, gLinkContext.allowInsertFailures); + if ( builder.diagnostics().hasError() ) + halt(builder.diagnostics().errorMessage()); - // 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; + if ( result == nullptr ) + return nullptr; - // release server port - mach_port_deallocate(mach_task_self(), serverPort); + if ( !closureValid(result, mainFileInfo, mainExecutableCDHash, false, envp) ) { + // some how the freshly generated closure is invalid... + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: somehow just built closure is invalid\n"); + return nullptr; + } + // try to save closure to disk for next launch (atomically) + char closurePath[PATH_MAX]; + buildClosureCachePath(mainFileInfo.path, mainExecutableMH, mainExecutableCDHash, envp, closurePath); + char closurePathTemp[PATH_MAX]; + strlcpy(closurePathTemp, closurePath, PATH_MAX); + int mypid = getpid(); + char pidBuf[16]; + char* s = pidBuf; + *s++ = '.'; + putHexByte(mypid >> 24, s); + putHexByte(mypid >> 16, s); + putHexByte(mypid >> 8, s); + putHexByte(mypid, s); + *s = '\0'; + strlcat(closurePathTemp, pidBuf, PATH_MAX); + int fd = ::open(closurePathTemp, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR); + if ( fd != -1 ) { + ::ftruncate(fd, result->size()); + ::write(fd, result, result->size()); + ::fchmod(fd, S_IRUSR); + ::close(fd); + ::rename(closurePathTemp, closurePath); + // free built closure and mmap file() to reduce dirty memory + result->deallocate(); + result = mapClosureFile(closurePath); + } + else if ( gLinkContext.verboseWarnings ) { + dyld::log("could not save closure (errno=%d) to: %s\n", errno, closurePathTemp); + } + + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: just built closure %p (size=%lu) for %s\n", result, result->size(), sExecPath); - if ( success ) - return (const dyld3::launch_cache::BinaryClosureData*)reply; + return result; +} - dyld::log("closure failed to build: %s\n", (char*)reply); - return NULL; -#endif +static const dyld3::closure::LaunchClosure* findCachedLaunchClosure(const uint8_t* mainExecutableCDHash, + const dyld3::closure::LoadedFileInfo& mainFileInfo, + const char* envp[]) +{ + char closurePath[PATH_MAX]; + buildClosureCachePath(mainFileInfo.path, (const dyld3::MachOLoaded*)mainFileInfo.fileContent, mainExecutableCDHash, envp, closurePath); + const dyld3::closure::LaunchClosure* closure = mapClosureFile(closurePath); + if ( closure == nullptr ) + return nullptr; + + if ( !closureValid(closure, mainFileInfo, mainExecutableCDHash, false, envp) ) { + ::munmap((void*)closure, closure->size()); + return nullptr; + } + + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: used cached closure %p (size=%lu) for %s\n", closure, closure->size(), sExecPath); + + return closure; } + #endif // !TARGET_IPHONE_SIMULATOR + #if !__MAC_OS_X_VERSION_MIN_REQUIRED static const char* sWhiteListDirs[] = { "/bin/", @@ -5601,7 +5837,7 @@ static const char* sWhiteListDirs[] = { 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") ) + if ( dyld3::bootArgsContains("force_dyld2=1") ) return false; #if __MAC_OS_X_VERSION_MIN_REQUIRED @@ -5616,18 +5852,30 @@ static bool inWhiteList(const char* execPath) #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 dyld3::bootArgsContains("force_dyld3=1"); +#endif +} + +#if !TARGET_IPHONE_SIMULATOR +static bool isStagedApp(const dyld3::MachOFile* mainExecutableMH, const char* mainExecutablePath) +{ +#if !__MAC_OS_X_VERSION_MIN_REQUIRED + if ( (strncmp(mainExecutablePath, "/var/containers/Bundle/Application/", 35) == 0) + || (strncmp(mainExecutablePath, "/private/var/containers/Bundle/Application/", 43) == 0) ) { + // staged apps are built without LC_ENCRYPTION_INFO + if ( !mainExecutableMH->canBeFairPlayEncrypted() ) return true; - } } - return dyld3::loader::bootArgsContains("force_dyld3=1"); #endif + return false; } +#endif // // Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which @@ -5640,9 +5888,11 @@ _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); + if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) { + launchTraceID = dyld3::kdebug_trace_dyld_duration_start(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, (uint64_t)mainExecutableMH, 0, 0); + } - // Grab the cdHash of the main executable from the environment + // 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) ) @@ -5661,8 +5911,7 @@ _main(const macho_header* mainExecutableMH, uintptr_t 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 ) { - + if ( (rootPath != NULL) ) { // look to see if simulator has its own dyld char simDyldPath[PATH_MAX]; strlcpy(simDyldPath, rootPath, PATH_MAX); @@ -5710,7 +5959,7 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, configureProcessRestrictions(mainExecutableMH); #if __MAC_OS_X_VERSION_MIN_REQUIRED - if ( gLinkContext.processIsRestricted ) { + if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) { pruneEnvironmentVariables(envp, &apple); // set again because envp and apple may have changed or moved setContext(mainExecutableMH, argc, argv, envp, apple); @@ -5721,6 +5970,17 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, checkEnvironmentVariables(envp); defaultUninitializedFallbackPaths(envp); } +#if __MAC_OS_X_VERSION_MIN_REQUIRED + if ( ((dyld3::MachOFile*)mainExecutableMH)->supportsPlatform(dyld3::Platform::iOSMac) + && !((dyld3::MachOFile*)mainExecutableMH)->supportsPlatform(dyld3::Platform::macOS)) { + gLinkContext.rootPaths = parseColonList("/System/iOSSupport", NULL); + gLinkContext.marzipan = true; + if ( sEnv.DYLD_FALLBACK_LIBRARY_PATH == sLibraryFallbackPaths ) + sEnv.DYLD_FALLBACK_LIBRARY_PATH = sRestrictedLibraryFallbackPaths; + if ( sEnv.DYLD_FALLBACK_FRAMEWORK_PATH == sFrameworkFallbackPaths ) + sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = sRestrictedFrameworkFallbackPaths; + } +#endif if ( sEnv.DYLD_PRINT_OPTS ) printOptions(argv); if ( sEnv.DYLD_PRINT_ENV ) @@ -5728,7 +5988,7 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, getHostInfo(mainExecutableMH, mainExecutableSlide); // load shared cache - checkSharedRegionDisable((mach_header*)mainExecutableMH); + checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide); #if TARGET_IPHONE_SIMULATOR // until is fixed gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion; @@ -5737,83 +5997,75 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 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); - } - } - } + bool cacheCompatible = (sSharedCacheLoadInfo.loadAddress == nullptr) || (sSharedCacheLoadInfo.loadAddress->header.formatVersion == dyld3::closure::kFormatVersion); + if ( cacheCompatible && (sEnableClosures || inWhiteList(sExecPath)) ) { + const dyld3::closure::LaunchClosure* mainClosure = nullptr; + dyld3::closure::LoadedFileInfo mainFileInfo; + mainFileInfo.fileContent = mainExecutableMH; + mainFileInfo.path = sExecPath; + // FIXME: If we are saving this closure, this slice offset/length is probably wrong in the case of FAT files. + mainFileInfo.sliceOffset = 0; + mainFileInfo.sliceLen = std::numeric_limits<__typeof(mainFileInfo.sliceLen)>::max(); + struct stat mainExeStatBuf; + if ( ::stat(sExecPath, &mainExeStatBuf) == 0 ) { + mainFileInfo.inode = mainExeStatBuf.st_ino; + mainFileInfo.mtime = mainExeStatBuf.st_mtime; + } + // check for closure in cache first + if ( sSharedCacheLoadInfo.loadAddress != nullptr ) { + mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath); + if ( gLinkContext.verboseWarnings && (mainClosure != nullptr) ) + dyld::log("dyld: found closure %p (size=%lu) in dyld shared cache\n", mainClosure, mainClosure->size()); + } + #if !TARGET_IPHONE_SIMULATOR + if ( (mainClosure == nullptr) || !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) { + mainClosure = nullptr; + if ( sEnableClosures || isStagedApp((dyld3::MachOFile*)mainExecutableMH, sExecPath) ) { + // if forcing closures, and no closure in cache, or it is invalid, check for cached closure + mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp); + if ( mainClosure == nullptr ) { + // if no cached closure found, build new one + mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp); } } + } #endif - if ( gLinkContext.verboseWarnings && (mainClosureData != nullptr) ) - dyld::log("dyld: found closure %p in dyld shared cache\n", mainClosureData); + // try using launch closure + if ( mainClosure != nullptr ) { + CRSetCrashLogMessage("dyld3: launch started"); + bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH, + mainExecutableSlide, argc, argv, envp, apple, &result, startGlue); #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; - } + if ( !launched ) { + // closure is out of date, build new one + mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp); + if ( mainClosure != nullptr ) { + launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH, + mainExecutableSlide, argc, argv, envp, apple, &result, startGlue); } } #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); - } + if ( launched ) { +#if __has_feature(ptrauth_calls) + // start() calls the result pointer as a function pointer so we need to sign it. + result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0); +#endif + if (sSkipMain) + result = (uintptr_t)&fake_main; + return result; + } + else { + if ( gLinkContext.verboseWarnings ) + dyld::log("dyld: unable to use closure %p\n", mainClosure); } } - 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 } + 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 @@ -5823,6 +6075,7 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, sImageRoots.reserve(16); sAddImageCallbacks.reserve(4); sRemoveImageCallbacks.reserve(4); + sAddLoadImageCallbacks.reserve(4); sImageFilesNeedingTermination.reserve(16); sImageFilesNeedingDOFUnregistration.reserve(8); @@ -5839,6 +6092,11 @@ _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, addDyldImageToUUIDList(); #if SUPPORT_ACCELERATE_TABLES +#if __arm64e__ + // Disable accelerator tables when we have threaded rebase/bind, which is arm64e executables only for now. + if (sMainExecutableMachHeader->cpusubtype == CPU_SUBTYPE_ARM64_E) + sDisableAcceleratorTables = true; +#endif bool mainExcutableAlreadyRebased = false; if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) { struct stat statBuf; @@ -5888,7 +6146,7 @@ reloadAllImages: #if __MAC_OS_X_VERSION_MIN_REQUIRED // be less strict about old mach-o binaries uint32_t mainSDK = sMainExecutable->sdkVersion(); - gLinkContext.strictMachORequired = (mainSDK >= DYLD_MACOSX_VERSION_10_12) || gLinkContext.processUsingLibraryValidation; + gLinkContext.strictMachORequired = (mainSDK >= DYLD_MACOSX_VERSION_10_12) || gLinkContext.allowInsertFailures; #else // simulators, iOS, tvOS, and watchOS are always strict gLinkContext.strictMachORequired = true; @@ -5962,7 +6220,7 @@ reloadAllImages: // register interposing info after all inserted libraries are bound so chaining works for(unsigned int i=0; i < sInsertedDylibCount; ++i) { ImageLoader* image = sAllImages[i+1]; - image->registerInterposing(); + image->registerInterposing(gLinkContext); } } @@ -5971,7 +6229,7 @@ reloadAllImages: ImageLoader* image = sAllImages[i]; if ( image->inSharedCache() ) continue; - image->registerInterposing(); + image->registerInterposing(gLinkContext); } #if SUPPORT_ACCELERATE_TABLES if ( (sAllCacheImagesProxy != NULL) && ImageLoader::haveInterposingTuples() ) { @@ -5994,6 +6252,7 @@ reloadAllImages: sImageFilesNeedingDOFUnregistration.clear(); sAddImageCallbacks.clear(); sRemoveImageCallbacks.clear(); + sAddLoadImageCallbacks.clear(); sDisableAcceleratorTables = true; sAllCacheImagesProxy = NULL; sMappedRangesStart = NULL; @@ -6008,7 +6267,23 @@ reloadAllImages: for(int i=0; i < sImageRoots.size(); ++i) { sImageRoots[i]->applyInterposing(gLinkContext); } + ImageLoader::applyInterposingToDyldCache(gLinkContext); gLinkContext.linkingMainExecutable = false; + + // Bind and notify for the main executable now that interposing has been registered + uint64_t bindMainExecutableStartTime = mach_absolute_time(); + sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true); + uint64_t bindMainExecutableEndTime = mach_absolute_time(); + ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime; + gLinkContext.notifyBatch(dyld_image_state_bound, false); + + // Bind and notify for the inserted images now interposing has been registered + if ( sInsertedDylibCount > 0 ) { + for(unsigned int i=0; i < sInsertedDylibCount; ++i) { + ImageLoader* image = sAllImages[i+1]; + image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true); + } + } // do weak binding only after all inserted images linked sMainExecutable->weakBind(gLinkContext); @@ -6044,13 +6319,15 @@ 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); + if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) { + dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2); + } notifyMonitoringDyldMain(); // find entry point for main executable - result = (uintptr_t)sMainExecutable->getThreadPC(); + result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN(); if ( result != 0 ) { - // main executable uses LC_MAIN, needs to return to glue in libdyld.dylib + // main executable uses LC_MAIN, we need to use helper in libdyld to call into main() if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) ) *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit; else @@ -6058,9 +6335,13 @@ reloadAllImages: } else { // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main() - result = (uintptr_t)sMainExecutable->getMain(); + result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD(); *startGlue = 0; } +#if __has_feature(ptrauth_calls) + // start() calls the result pointer as a function pointer so we need to sign it. + result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0); +#endif } catch(const char* message) { syncAllImages(); @@ -6070,10 +6351,12 @@ reloadAllImages: dyld::log("dyld: launch failed\n"); } - CRSetCrashLogMessage(NULL); + CRSetCrashLogMessage("dyld2 mode"); if (sSkipMain) { - dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN, 0, 0); + if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) { + dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2); + } result = (uintptr_t)&fake_main; *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit; } diff --git a/src/dyld.h b/src/dyld.h index de649ab..b4f6696 100644 --- a/src/dyld.h +++ b/src/dyld.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "ImageLoader.h" #include "mach-o/dyld_priv.h" @@ -48,6 +49,7 @@ namespace dyld { bool mustBeBundle; bool mustBeDylib; bool canBePIE; + bool enforceIOSMac; const char* origin; // path for expanding @loader_path const ImageLoader::RPathChain* rpath; // paths for expanding @rpath }; @@ -55,6 +57,7 @@ namespace dyld { typedef void (*ImageCallback)(const struct mach_header* mh, intptr_t slide); + typedef void (*LoadImageCallback)(const mach_header* mh, const char* path, bool unloadable); typedef void (*UndefinedHandler)(const char* symbolName); typedef const char* (*ImageLocator)(const char* dllName); @@ -73,6 +76,7 @@ namespace dyld { extern void registerAddCallback(ImageCallback func); extern void registerRemoveCallback(ImageCallback func); extern void registerUndefinedHandler(UndefinedHandler); + extern void registerLoadCallback(LoadImageCallback func); extern void initializeMainExecutable(); extern void preflight(ImageLoader* image, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex); extern void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex); @@ -134,4 +138,3 @@ namespace dyld { const char* getPathFromIndex(unsigned cacheIndex); #endif } - diff --git a/src/dyldAPIs.cpp b/src/dyldAPIs.cpp index c77e652..891a82e 100644 --- a/src/dyldAPIs.cpp +++ b/src/dyldAPIs.cpp @@ -55,10 +55,36 @@ #include "dyld.h" #include "dyldLibSystemInterface.h" #include "DyldSharedCache.h" +#include "MachOFile.h" #undef _POSIX_C_SOURCE #include "dlfcn.h" +#if __has_feature(ptrauth_calls) + #include +#endif + +#ifndef CPU_SUBTYPE_ARM64_E + #define CPU_SUBTYPE_ARM64_E 2 +#endif + +// relocation_info.r_length field has value 3 for 64-bit executables and value 2 for 32-bit executables +#if __LP64__ +#define RELOC_SIZE 3 +#define LC_SEGMENT_COMMAND LC_SEGMENT_64 +#define LC_ROUTINES_COMMAND LC_ROUTINES_64 +struct macho_segment_command : public segment_command_64 {}; +struct macho_section : public section_64 {}; +struct macho_routines_command : public routines_command_64 {}; +#else +#define RELOC_SIZE 2 +#define LC_SEGMENT_COMMAND LC_SEGMENT +#define LC_ROUTINES_COMMAND LC_ROUTINES +struct macho_segment_command : public segment_command {}; +struct macho_section : public section {}; +struct macho_routines_command : public routines_command {}; +#endif + // this was in dyld_priv.h but it is no longer exported extern "C" { @@ -76,6 +102,16 @@ extern const char* allImagesIndexedPath(uint32_t index); extern "C" int _dyld_func_lookup(const char* name, void** address); +extern "C" void* dlopen_internal(const char* path, int mode, void* callerAddress); +extern "C" bool dlopen_preflight_internal(const char* path, void* callerAddress); +extern "C" void* dlsym_internal(void* handle, const char* symbolName, void* callerAddress); + +extern "C" void* dlopen_compat(const char* path, int mode); +extern "C" bool dlopen_preflight_compat(const char* path); +extern "C" void* dlsym_compat(void* handle, const char* symbolName); + + + // deprecated APIs are still availble on Mac OS X, but not on iPhone OS #if __IPHONE_OS_VERSION_MIN_REQUIRED #define DEPRECATED_APIS_SUPPORTED 0 @@ -136,9 +172,12 @@ static struct dyld_func dyld_funcs[] = { {"__dyld_dladdr", (void*)dladdr }, {"__dyld_dlclose", (void*)dlclose }, {"__dyld_dlerror", (void*)dlerror }, - {"__dyld_dlopen", (void*)dlopen }, - {"__dyld_dlsym", (void*)dlsym }, - {"__dyld_dlopen_preflight", (void*)dlopen_preflight }, + {"__dyld_dlopen_internal", (void*)dlopen_internal }, + {"__dyld_dlsym_internal", (void*)dlsym_internal }, + {"__dyld_dlopen_preflight_internal", (void*)dlopen_preflight_internal }, + {"__dyld_dlopen", (void*)dlopen_compat }, + {"__dyld_dlsym", (void*)dlsym_compat }, + {"__dyld_dlopen_preflight", (void*)dlopen_preflight_compat }, {"__dyld_image_count", (void*)_dyld_image_count }, {"__dyld_get_image_header", (void*)_dyld_get_image_header }, {"__dyld_get_image_vmaddr_slide", (void*)_dyld_get_image_vmaddr_slide }, @@ -167,7 +206,8 @@ static struct dyld_func dyld_funcs[] = { {"__dyld_objc_notify_register", (void*)_dyld_objc_notify_register }, {"__dyld_get_shared_cache_uuid", (void*)_dyld_get_shared_cache_uuid }, {"__dyld_get_shared_cache_range", (void*)_dyld_get_shared_cache_range }, - + {"__dyld_images_for_addresses", (void*)_dyld_images_for_addresses }, + {"__dyld_register_for_image_loads", (void*)_dyld_register_for_image_loads }, // deprecated #if DEPRECATED_APIS_SUPPORTED @@ -257,6 +297,7 @@ struct __NSObjectFileImage const void* imageBaseAddress; // not used with OFI created from files size_t imageLength; // not used with OFI created from files }; +typedef __NSObjectFileImage* NSObjectFileImage; VECTOR_NEVER_DESTRUCTED(NSObjectFileImage); @@ -347,10 +388,27 @@ const char* _dyld_get_image_name(uint32_t image_index) return allImagesIndexedPath(image_index); } +static const void *stripPointer(const void *ptr) { +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + +static void *stripPointer(void *ptr) { +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + const struct mach_header * dyld_image_header_containing_address(const void* address) { if ( dyld::gLogAPIs ) dyld::log("%s(%p)\n", __func__, address); + address = stripPointer(address); #if SUPPORT_ACCELERATE_TABLES const mach_header* mh; const char* path; @@ -550,6 +608,7 @@ const struct mach_header* addImage(void* callerAddress, const char* path, bool s context.mustBeBundle = false; context.mustBeDylib = true; context.canBePIE = false; + context.enforceIOSMac = false; context.origin = callerImage != NULL ? callerImage->getPath() : NULL; // caller's image's path context.rpath = &callersRPaths; // rpaths from caller and main executable @@ -743,6 +802,7 @@ bool _dyld_bind_fully_image_containing_address(const void* address) { if ( dyld::gLogAPIs ) dyld::log("%s(%p)\n", __func__, address); + address = stripPointer(address); dyld::clearErrorMessage(); ImageLoader* image = dyld::findImageContainingAddress(address); if ( image != NULL ) { @@ -793,6 +853,7 @@ NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* pathName context.mustBeBundle = true; context.mustBeDylib = false; context.canBePIE = false; + context.enforceIOSMac = false; context.origin = callerImage != NULL ? callerImage->getPath() : NULL; // caller's image's path context.rpath = NULL; // support not yet implemented @@ -1299,7 +1360,7 @@ static void dlerrorSet(const char* msg) } -bool dlopen_preflight(const char* path) +bool dlopen_preflight_internal(const char* path, void* callerAddress) { if ( dyld::gLogAPIs ) dyld::log("%s(%s)\n", __func__, path); @@ -1344,7 +1405,6 @@ bool dlopen_preflight(const char* path) bool result = false; std::vector rpathsFromCallerImage; try { - void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue ImageLoader* callerImage = dyld::findImageContainingAddress(callerAddress); // for dlopen, use rpath from caller image and from main executable if ( callerImage != NULL ) @@ -1365,6 +1425,7 @@ bool dlopen_preflight(const char* path) context.mustBeBundle = false; context.mustBeDylib = false; context.canBePIE = true; + context.enforceIOSMac = false; context.origin = callerImage != NULL ? callerImage->getPath() : NULL; // caller's image's path context.rpath = &callersRPaths; // rpaths from caller and main executable @@ -1410,14 +1471,13 @@ bool static callerIsNonOSApp(void* callerAddress, const char** shortName) } #endif -void* dlopen(const char* path, int mode) +void* dlopen_internal(const char* path, int mode, void* callerAddress) { if ( dyld::gLogAPIs ) dyld::log("%s(%s, 0x%08X)\n", __func__, ((path==NULL) ? "NULL" : path), mode); #if SUPPORT_ACCELERATE_TABLES if ( dyld::gLogAppAPIs ) { - void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue const char* shortName; if ( callerIsNonOSApp(callerAddress, &shortName) ) { dyld::log("%s: %s(%s, 0x%08X)\n", shortName, __func__, ((path==NULL) ? "NULL" : path), mode); @@ -1481,7 +1541,6 @@ void* dlopen(const char* path, int mode) std::vector rpathsFromCallerImage; ImageLoader::RPathChain callersRPaths(NULL, &rpathsFromCallerImage); try { - void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue ImageLoader* callerImage = dyld::findImageContainingAddress(callerAddress); if ( (mode & RTLD_NOLOAD) == 0 ) { // for dlopen, use rpath from caller image and from main executable @@ -1501,6 +1560,7 @@ void* dlopen(const char* path, int mode) context.mustBeBundle = false; context.mustBeDylib = false; context.canBePIE = true; + context.enforceIOSMac = false; context.origin = callerImage != NULL ? callerImage->getPath() : NULL; // caller's image's path context.rpath = &callersRPaths; // rpaths from caller and main executable @@ -1527,7 +1587,12 @@ void* dlopen(const char* path, int mode) bool alreadyLinked = image->isLinked(); bool forceLazysBound = ( (mode & RTLD_NOW) != 0 ); dyld::link(image, forceLazysBound, false, callersRPaths, cacheIndex); - if ( ! alreadyLinked ) { + if ( alreadyLinked ) { + // upgrade + if ( ((mode & RTLD_LOCAL) == 0) && image->hasHiddenExports() ) + image->setHideExports(false); + } + else { // only hide exports if image is not already in use if ( (mode & RTLD_LOCAL) != 0 ) image->setHideExports(true); @@ -1603,8 +1668,6 @@ void* dlopen(const char* path, int mode) return result; } - - int dlclose(void* handle) { if ( dyld::gLogAPIs ) @@ -1615,7 +1678,14 @@ int dlclose(void* handle) return 0; if ( handle == RTLD_DEFAULT ) return 0; - + +#if SUPPORT_ACCELERATE_TABLES + if ( dyld::isCacheHandle(handle) ) { + dlerrorClear(); + return 0; + } +#endif + ImageLoader* image = (ImageLoader*)(((uintptr_t)handle) & (-4)); // clear mode bits if ( dyld::validImage(image) ) { dlerrorClear(); @@ -1642,6 +1712,12 @@ int dladdr(const void* address, Dl_info* info) if ( dyld::gLogAPIs ) dyld::log("%s(%p, %p)\n", __func__, address, info); + // calling dladdr(xx,NULL) crashes + if ( info == NULL ) + return 0; // failure + + address = stripPointer(address); + CRSetCrashLogMessage("dyld: in dladdr()"); #if SUPPORT_ACCELERATE_TABLES if ( dyld::dladdrFromCache(address, info) ) { @@ -1709,14 +1785,13 @@ char* dlerror() return NULL; } -void* dlsym(void* handle, const char* symbolName) +void* dlsym_internal(void* handle, const char* symbolName, void* callerAddress) { if ( dyld::gLogAPIs ) dyld::log("%s(%p, %s)\n", __func__, handle, symbolName); #if SUPPORT_ACCELERATE_TABLES if ( dyld::gLogAppAPIs ) { - void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue const char* shortName; if ( callerIsNonOSApp(callerAddress, &shortName) ) { dyld::log("%s: %s(%p, %s)\n", shortName, __func__, handle, symbolName); @@ -1742,6 +1817,20 @@ void* dlsym(void* handle, const char* symbolName) if ( dyld::flatFindExportedSymbol(underscoredName, &sym, &image) ) { CRSetCrashLogMessage(NULL); result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, NULL, false, underscoredName); +#if __has_feature(ptrauth_calls) + // Sign the pointer if it points to a function + // Note we only do this if the main executable is arm64e as otherwise we + // may end up calling containsAddress on the accelerator tables. + if ( result && (dyld::gLinkContext.mainExecutable->machHeader()->cpusubtype == CPU_SUBTYPE_ARM64_E) ) { + const ImageLoader* symbolImage = image; + if (!symbolImage->containsAddress(result)) { + symbolImage = dyld::findImageContainingAddress(result); + } + const macho_section *sect = symbolImage ? symbolImage->findSection(result) : NULL; + if ( sect && ((sect->flags & S_ATTR_PURE_INSTRUCTIONS) || (sect->flags & S_ATTR_SOME_INSTRUCTIONS)) ) + result = __builtin_ptrauth_sign_unauthenticated(result, ptrauth_key_asia, 0); + } +#endif if ( dyld::gLogAPIs ) dyld::log(" %s(RTLD_DEFAULT, %s) ==> %p\n", __func__, symbolName, result); return result; @@ -1762,6 +1851,20 @@ void* dlsym(void* handle, const char* symbolName) if ( sym != NULL ) { CRSetCrashLogMessage(NULL); result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, NULL, false, underscoredName); +#if __has_feature(ptrauth_calls) + // Sign the pointer if it points to a function + // Note we only do this if the main executable is arm64e as otherwise we + // may end up calling containsAddress on the accelerator tables. + if ( result && (dyld::gLinkContext.mainExecutable->machHeader()->cpusubtype == CPU_SUBTYPE_ARM64_E) ) { + const ImageLoader* symbolImage = image; + if (!symbolImage->containsAddress(result)) { + symbolImage = dyld::findImageContainingAddress(result); + } + const macho_section *sect = symbolImage ? symbolImage->findSection(result) : NULL; + if ( sect && ((sect->flags & S_ATTR_PURE_INSTRUCTIONS) || (sect->flags & S_ATTR_SOME_INSTRUCTIONS)) ) + result = __builtin_ptrauth_sign_unauthenticated(result, ptrauth_key_asia, 0); + } +#endif if ( dyld::gLogAPIs ) dyld::log(" %s(RTLD_MAIN_ONLY, %s) ==> %p\n", __func__, symbolName, result); return result; @@ -1777,7 +1880,6 @@ void* dlsym(void* handle, const char* symbolName) // magic "search what I would see" handle else if ( handle == RTLD_NEXT ) { - void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue #if SUPPORT_ACCELERATE_TABLES const mach_header* mh; const char* path; @@ -1795,6 +1897,20 @@ void* dlsym(void* handle, const char* symbolName) if ( sym != NULL ) { CRSetCrashLogMessage(NULL); result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext , callerImage, false, underscoredName); +#if __has_feature(ptrauth_calls) + // Sign the pointer if it points to a function + // Note we only do this if the main executable is arm64e as otherwise we + // may end up calling containsAddress on the accelerator tables. + if ( result && (dyld::gLinkContext.mainExecutable->machHeader()->cpusubtype == CPU_SUBTYPE_ARM64_E) ) { + const ImageLoader* symbolImage = image; + if (!symbolImage->containsAddress(result)) { + symbolImage = dyld::findImageContainingAddress(result); + } + const macho_section *sect = symbolImage ? symbolImage->findSection(result) : NULL; + if ( sect && ((sect->flags & S_ATTR_PURE_INSTRUCTIONS) || (sect->flags & S_ATTR_SOME_INSTRUCTIONS)) ) + result = __builtin_ptrauth_sign_unauthenticated(result, ptrauth_key_asia, 0); + } +#endif if ( dyld::gLogAPIs ) dyld::log(" %s(RTLD_NEXT, %s) ==> %p\n", __func__, symbolName, result); return result; @@ -1809,7 +1925,6 @@ void* dlsym(void* handle, const char* symbolName) } // magic "search me, then what I would see" handle else if ( handle == RTLD_SELF ) { - void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue #if SUPPORT_ACCELERATE_TABLES const mach_header* mh; const char* path; @@ -1827,6 +1942,20 @@ void* dlsym(void* handle, const char* symbolName) if ( sym != NULL ) { CRSetCrashLogMessage(NULL); result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, callerImage, false, underscoredName); +#if __has_feature(ptrauth_calls) + // Sign the pointer if it points to a function + // Note we only do this if the main executable is arm64e as otherwise we + // may end up calling containsAddress on the accelerator tables. + if ( result && (dyld::gLinkContext.mainExecutable->machHeader()->cpusubtype == CPU_SUBTYPE_ARM64_E) ) { + const ImageLoader* symbolImage = image; + if (!symbolImage->containsAddress(result)) { + symbolImage = dyld::findImageContainingAddress(result); + } + const macho_section *sect = symbolImage ? symbolImage->findSection(result) : NULL; + if ( sect && ((sect->flags & S_ATTR_PURE_INSTRUCTIONS) || (sect->flags & S_ATTR_SOME_INSTRUCTIONS)) ) + result = __builtin_ptrauth_sign_unauthenticated(result, ptrauth_key_asia, 0); + } +#endif if ( dyld::gLogAPIs ) dyld::log(" %s(RTLD_SELF, %s) ==> %p\n", __func__, symbolName, result); return result; @@ -1861,10 +1990,23 @@ void* dlsym(void* handle, const char* symbolName) ImageLoader* callerImage = NULL; if ( sDynamicInterposing ) { // only take time to look up caller, if dynamic interposing in use - void* callerAddress = __builtin_return_address(1); // note layers: 1: real client, 0: libSystem glue callerImage = dyld::findImageContainingAddress(callerAddress); } result = (void*)image->getExportedSymbolAddress(sym, dyld::gLinkContext, callerImage, false, underscoredName); +#if __has_feature(ptrauth_calls) + // Sign the pointer if it points to a function + // Note we only do this if the main executable is arm64e as otherwise we + // may end up calling containsAddress on the accelerator tables. + if ( result && (dyld::gLinkContext.mainExecutable->machHeader()->cpusubtype == CPU_SUBTYPE_ARM64_E) ) { + const ImageLoader* symbolImage = image; + if (!symbolImage->containsAddress(result)) { + symbolImage = dyld::findImageContainingAddress(result); + } + const macho_section *sect = symbolImage ? symbolImage->findSection(result) : NULL; + if ( sect && ((sect->flags & S_ATTR_PURE_INSTRUCTIONS) || (sect->flags & S_ATTR_SOME_INSTRUCTIONS)) ) + result = __builtin_ptrauth_sign_unauthenticated(result, ptrauth_key_asia, 0); + } +#endif if ( dyld::gLogAPIs ) dyld::log(" %s(%p, %s) ==> %p\n", __func__, handle, symbolName, result); return result; @@ -1882,11 +2024,25 @@ void* dlsym(void* handle, const char* symbolName) return NULL; } +// Note this is only here to support ___pthread_abort in libpthread.a +void* dlsym(void* handle, const char* symbolName) { + return dlsym_internal(handle, symbolName, __builtin_return_address(1)); +} - - - +// *_compat functions are for old binaries that have __dyld section and use it to bypass libdyld.dylib +void* dlopen_compat(const char* path, int mode) +{ + return dlopen_internal(path, mode, (void*)dyld::mainExecutable()->machHeader()); +} +bool dlopen_preflight_compat(const char* path) +{ + return dlopen_preflight_internal(path, (void*)dyld::mainExecutable()->machHeader()); +} +void* dlsym_compat(void* handle, const char* symbolName) +{ + return dlsym_internal(handle, symbolName, (void*)dyld::mainExecutable()->machHeader()); +} @@ -1902,6 +2058,8 @@ static bool client_dyld_find_unwind_sections(void* addr, dyld_unwind_sections* i { //if ( dyld::gLogAPIs ) // dyld::log("%s(%p, %p)\n", __func__, addr, info); + + addr = stripPointer(addr); #if SUPPORT_ACCELERATE_TABLES if ( dyld::findUnwindSections(addr, info) ) @@ -1922,6 +2080,8 @@ const char* dyld_image_path_containing_address(const void* address) if ( dyld::gLogAPIs ) dyld::log("%s(%p)\n", __func__, address); + address = (void*)stripPointer(address); + #if SUPPORT_ACCELERATE_TABLES const mach_header* mh; const char* path; @@ -2030,5 +2190,38 @@ const void* _dyld_get_shared_cache_range(size_t* length) return nullptr; } +void _dyld_images_for_addresses(unsigned count, const void* addresses[], struct dyld_image_uuid_offset infos[]) +{ + for (unsigned i=0; i < count; ++i) { + const void* addr = addresses[i]; + addr = stripPointer(addr); + bzero(&infos[i], sizeof(dyld_image_uuid_offset)); +#if SUPPORT_ACCELERATE_TABLES + const mach_header* mh; + const char* path; + if ( dyld::addressInCache(addr, &mh, &path) ) { + infos[i].image = mh; + infos[i].offsetInImage = (uintptr_t)addr - (uintptr_t)mh; + ((dyld3::MachOFile*)mh)->getUuid(infos[i].uuid); + break; + } +#endif + ImageLoader* image = dyld::findImageContainingAddress(addr); + if ( image != nullptr ) { + infos[i].image = image->machHeader(); + infos[i].offsetInImage = (uintptr_t)addr - (uintptr_t)(image->machHeader()); + image->getUUID(infos[i].uuid); + } + } +} + +void _dyld_register_for_image_loads(void (*func)(const mach_header* mh, const char* path, bool unloadable)) +{ + if ( dyld::gLogAPIs ) + dyld::log("%s(%p)\n", __func__, (void *)func); + dyld::registerLoadCallback(func); +} + + diff --git a/src/dyldAPIsInLibSystem.cpp b/src/dyldAPIsInLibSystem.cpp index 6d72472..d556e9d 100644 --- a/src/dyldAPIsInLibSystem.cpp +++ b/src/dyldAPIsInLibSystem.cpp @@ -26,7 +26,9 @@ #include #include #include +#include +#include #include #include #include @@ -41,10 +43,11 @@ #include "ImageLoader.h" #include "dyldLock.h" -#include "start_glue.h" #include "../dyld3/APIs.h" #include "../dyld3/AllImages.h" +#include "../dyld3/StartGlue.h" +#include "../dyld3/Tracing.h" // this was in dyld_priv.h but it is no longer exported @@ -129,7 +132,6 @@ extern bool gUseDyld3; #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 #define DEPRECATED_APIS_SUPPORTED 0 @@ -532,262 +534,84 @@ int32_t NSVersionOfRunTimeLibrary(const char* libraryName) return (-1); } - -#define PACKED_VERSION(major, minor, tiny) ((((major) & 0xffff) << 16) | (((minor) & 0xff) << 8) | ((tiny) & 0xff)) - - -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 ) - 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 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; - *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; - } - return false; -} - -#if !__WATCH_OS_VERSION_MIN_REQUIRED && !__TV_OS_VERSION_MIN_REQUIRED -static uint32_t deriveSDKVersFromDylibs(const mach_header* mh) -{ - const load_command* startCmds = NULL; - if ( mh->magic == MH_MAGIC_64 ) - startCmds = (load_command*)((char *)mh + sizeof(mach_header_64)); - else if ( mh->magic == MH_MAGIC ) - startCmds = (load_command*)((char *)mh + sizeof(mach_header)); - else - return 0; // not a mach-o file, or wrong endianness - - const load_command* const cmdsEnd = (load_command*)((char*)startCmds + mh->sizeofcmds); - const dylib_command* dylibCmd; - const load_command* cmd = startCmds; - const char* dylibName; - #if __IPHONE_OS_VERSION_MIN_REQUIRED - uint32_t foundationVers = 0; - #else - uint32_t libSystemVers = 0; - #endif - for(uint32_t i = 0; i < mh->ncmds; ++i) { - const load_command* nextCmd = (load_command*)((char *)cmd + cmd->cmdsize); - // sanity check size of command - if ( (cmd->cmdsize < 8) || (nextCmd > cmdsEnd) || (nextCmd < startCmds)) { - return 0; - } - switch ( cmd->cmd ) { - case LC_LOAD_DYLIB: - case LC_LOAD_WEAK_DYLIB: - case LC_LOAD_UPWARD_DYLIB: - dylibCmd = (dylib_command*)cmd; - // sanity check dylib command layout - if ( dylibCmd->dylib.name.offset > cmd->cmdsize ) - return 0; - dylibName = (char*)dylibCmd + dylibCmd->dylib.name.offset; - #if __IPHONE_OS_VERSION_MIN_REQUIRED - if ( strcmp(dylibName, "/System/Library/Frameworks/Foundation.framework/Foundation") == 0 ) - foundationVers = dylibCmd->dylib.current_version; - #else - if ( strcmp(dylibName, "/usr/lib/libSystem.B.dylib") == 0 ) - libSystemVers = dylibCmd->dylib.current_version; - #endif - break; - } - cmd = nextCmd; - } - - 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 - - -#if __WATCH_OS_VERSION_MIN_REQUIRED -static uint32_t watchVersToIOSVers(uint32_t vers) -{ - return vers + 0x00070000; -} - +#if TARGET_OS_WATCH uint32_t dyld_get_program_sdk_watch_os_version() { - if ( gUseDyld3 ) - return dyld3::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 platform; - uint32_t minOS; - uint32_t sdk; + __block uint32_t retval = 0; + __block bool versionFound = false; + dyld3::dyld_get_image_versions((mach_header*)_NSGetMachExecuteHeader(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; - if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { - if ( platform == PLATFORM_WATCHOS ) - return sdk; - } - return 0; + if (dyld_get_base_platform(platform) == PLATFORM_WATCHOS) { + versionFound = true; + retval = sdk_version; + } + }); + + return retval; } uint32_t dyld_get_program_min_watch_os_version() { - if ( gUseDyld3 ) - return dyld3::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; + __block uint32_t retval = 0; + __block bool versionFound = false; + dyld3::dyld_get_image_versions((mach_header*)_NSGetMachExecuteHeader(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; - if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { - if ( platform == PLATFORM_WATCHOS ) - return minOS; // return raw minOS (not mapped to iOS version) - } - return 0; -} + if (dyld_get_base_platform(platform) == PLATFORM_WATCHOS) { + versionFound = true; + retval = min_version; + } + }); + return retval; +} #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 (gUseDyld3) + return dyld3::dyld_get_program_sdk_bridge_os_version(); - if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { - if ( platform == PLATFORM_BRIDGEOS ) - return sdk; - } - return 0; + __block uint32_t retval = 0; + __block bool versionFound = false; + dyld3::dyld_get_image_versions((mach_header*)_NSGetMachExecuteHeader(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; + + if (dyld_get_base_platform(platform) == PLATFORM_BRIDGEOS) { + versionFound = true; + retval = sdk_version; + } + }); + + return retval; } uint32_t dyld_get_program_min_bridge_os_version() { - const mach_header* mh = (mach_header*)_NSGetMachExecuteHeader(); - uint32_t platform; - uint32_t minOS; - uint32_t sdk; + if (gUseDyld3) + return dyld3::dyld_get_program_min_bridge_os_version(); - if ( getVersionLoadCommandInfo(mh, &platform, &minOS, &sdk) ) { - if ( platform == PLATFORM_BRIDGEOS ) - return minOS; // return raw minOS (not mapped to iOS version) - } - return 0; -} + __block uint32_t retval = 0; + __block bool versionFound = false; + dyld3::dyld_get_image_versions((mach_header*)_NSGetMachExecuteHeader(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + if (versionFound) return; + + if (dyld_get_base_platform(platform) == PLATFORM_BRIDGEOS) { + versionFound = true; + retval = min_version; + } + }); + return retval; +} #endif /* @@ -801,112 +625,23 @@ uint32_t dyld_get_program_min_bridge_os_version() */ uint32_t dyld_get_sdk_version(const mach_header* mh) { - if ( gUseDyld3 ) - return dyld3::dyld_get_sdk_version(mh); - - uint32_t platform; - uint32_t minOS; - uint32_t sdk; - - 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 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 - } - } - -#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 - // MacOSX and iOS have old binaries without version load commmand. - return deriveSDKVersFromDylibs(mh); -#endif + return dyld3::dyld_get_sdk_version(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()); + return dyld3::dyld_get_sdk_version((mach_header*)_NSGetMachExecuteHeader()); } uint32_t dyld_get_min_os_version(const struct mach_header* mh) { - if ( gUseDyld3 ) - return dyld3::dyld_get_min_os_version(mh); - - uint32_t platform; - uint32_t minOS; - uint32_t sdk; - - 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 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 - } - } - return 0; + return dyld3::dyld_get_min_os_version(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()); + return dyld3::dyld_get_min_os_version((mach_header*)_NSGetMachExecuteHeader()); } @@ -941,6 +676,55 @@ bool _dyld_get_image_uuid(const struct mach_header* mh, uuid_t uuid) return false; } +dyld_platform_t dyld_get_active_platform(void) { + if (gUseDyld3) + return dyld3::dyld_get_active_platform(); + + // HACK + // Most of the new version SPIs have pure dyld3 implementations, but + // They cannot get to the main executable, so we implement this here + // and they can use this by calling ::dyld_get_active_platform() in the root namespace + static dyld_platform_t sActivePlatform = 0; + if (sActivePlatform) return sActivePlatform; + + dyld3::dyld_get_image_versions((mach_header*)_NSGetMachExecuteHeader(), ^(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version) { + sActivePlatform = platform; + //FIXME assert there is only one? + }); + return sActivePlatform; +} + +dyld_platform_t dyld_get_base_platform(dyld_platform_t platform) { + return dyld3::dyld_get_base_platform(platform); +} + +bool dyld_is_simulator_platform(dyld_platform_t platform) { + return dyld3::dyld_is_simulator_platform(platform); +} + +bool dyld_sdk_at_least(const struct mach_header* mh, dyld_build_version_t version) { + return dyld3::dyld_sdk_at_least(mh, version); +} + +bool dyld_minos_at_least(const struct mach_header* mh, dyld_build_version_t version) { + return dyld3::dyld_minos_at_least(mh, version); +} + +bool dyld_program_sdk_at_least(dyld_build_version_t version) { + return dyld3::dyld_sdk_at_least((mach_header*)_NSGetMachExecuteHeader(),version); +} + +bool dyld_program_minos_at_least(dyld_build_version_t version) { + return dyld3::dyld_minos_at_least((mach_header*)_NSGetMachExecuteHeader(), version); +} + +// Function that walks through the load commands and calls the internal block for every version found +// Intended as a fallback for very complex (and rare) version checks, or for tools that need to +// print our everything for diagnostic reasons +void dyld_get_image_versions(const struct mach_header* mh, void (^callback)(dyld_platform_t platform, uint32_t sdk_version, uint32_t min_version)) { + dyld3::dyld_get_image_versions(mh, callback); +} + #if DEPRECATED_APIS_SUPPORTED @@ -1654,9 +1438,15 @@ static vswapproc swapProc = &vproc_swap_integer; static bool isLaunchdOwned() { - int64_t val = 0; - (*swapProc)(NULL, VPROC_GSK_IS_MANAGED, NULL, &val); - return ( val != 0 ); + static bool checked = false; + static bool result = false; + if ( !checked ) { + checked = true; + int64_t val = 0; + (*swapProc)(NULL, VPROC_GSK_IS_MANAGED, NULL, &val); + result = ( val != 0 ); + } + return result; } static void shared_cache_missing() @@ -1685,7 +1475,7 @@ static dyld::LibSystemHelpers sHelpers = { 13, &dyldGlobalLockAcquire, &dyldGlob &vm_allocate, &mmap, &__cxa_finalize_ranges - }; + }; // @@ -1725,6 +1515,8 @@ char* dlerror() int dladdr(const void* addr, Dl_info* info) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_DLADDR, (uint64_t)addr, 0, 0); + int result = 0; if ( gUseDyld3 ) return dyld3::dladdr(addr, info); @@ -1733,11 +1525,17 @@ int dladdr(const void* addr, Dl_info* info) if(p == NULL) _dyld_func_lookup("__dyld_dladdr", (void**)&p); - return(p(addr, info)); + result = p(addr, info); + timer.setData4(result); + timer.setData5(info != NULL ? info->dli_fbase : 0); + timer.setData6(info != NULL ? info->dli_saddr : 0); + return result; } int dlclose(void* handle) { + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_DLCLOSE, (uint64_t)handle, 0, 0); + int result = 0; if ( gUseDyld3 ) return dyld3::dlclose(handle); @@ -1746,54 +1544,101 @@ int dlclose(void* handle) if(p == NULL) _dyld_func_lookup("__dyld_dlclose", (void**)&p); - return(p(handle)); + result = p(handle); + return result; } 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; - - static void* (*p)(const char* path, int) = NULL; +{ + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_DLOPEN, path, mode, 0); + void* result = nullptr; + + if ( gUseDyld3 ) { + result = dyld3::dlopen_internal(path, mode, __builtin_return_address(0)); + return result; + } + + // dlopen is special. locking is done inside dyld to allow initializer to run without lock + DYLD_NO_LOCK_THIS_BLOCK; + + static void* (*p)(const char* path, int, void*) = NULL; + + if(p == NULL) + _dyld_func_lookup("__dyld_dlopen_internal", (void**)&p); + result = p(path, mode, __builtin_return_address(0)); + // use asm block to prevent tail call optimization + // this is needed because dlopen uses __builtin_return_address() and depends on this glue being in the frame chain + // + __asm__ volatile(""); + timer.setData4(result); + +#if TARGET_OS_OSX + // HACK for iOSMac bringup rdar://40945421 + if ( result == nullptr && dyld_get_active_platform() == PLATFORM_IOSMAC && csr_check(CSR_ALLOW_APPLE_INTERNAL) == 0) { + if (hasPerThreadBufferFor_dlerror()) { + // first char of buffer is flag whether string (starting at second char) is valid + char* buffer = getPerThreadBufferFor_dlerror(2); + + if ( buffer[0] != '\0' && (strstr(&buffer[1], "macOS dylib cannot be loaded into iOSMac process") + || strstr(&buffer[1], "mach-o, but not built for iOSMac")) ) { + // if valid buffer and contains an iOSMac issue + fprintf(stderr, "dyld: iOSMac ERROR: process attempted to dlopen() dylib with macOS dependency: \n"); + fprintf(stderr, "\tdlerror: %s\n", &buffer[1]); + fprintf(stderr, "\tBacktrace:\n"); + + void* stackPointers[128]; + int stackPointersCnt = backtrace(stackPointers, 128); + char** symbolicatedStack = backtrace_symbols(stackPointers, stackPointersCnt); + for (int32_t i = 0; i < stackPointersCnt; ++i) { + fprintf(stderr, "\t\t%s\n", symbolicatedStack[i]); + } + free(symbolicatedStack); + } + } + } +#endif - if(p == NULL) - _dyld_func_lookup("__dyld_dlopen", (void**)&p); - void* result = p(path, mode); - // use asm block to prevent tail call optimization - // this is needed because dlopen uses __builtin_return_address() and depends on this glue being in the frame chain - // - __asm__ volatile(""); - return result; } bool dlopen_preflight(const char* path) { - if ( gUseDyld3 ) - return dyld3::dlopen_preflight(path); + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_DLOPEN_PREFLIGHT, path, 0, 0); + bool result = false; - DYLD_LOCK_THIS_BLOCK; - static bool (*p)(const char* path) = NULL; + if ( gUseDyld3 ) { + result = dyld3::dlopen_preflight_internal(path); + return result; + } - if(p == NULL) - _dyld_func_lookup("__dyld_dlopen_preflight", (void**)&p); - return(p(path)); + DYLD_LOCK_THIS_BLOCK; + static bool (*p)(const char* path, void* callerAddress) = NULL; + + if(p == NULL) + _dyld_func_lookup("__dyld_dlopen_preflight_internal", (void**)&p); + result = p(path, __builtin_return_address(0)); + timer.setData4(result); + return result; } void* dlsym(void* handle, const char* symbol) { - if ( gUseDyld3 ) - return dyld3::dlsym(handle, symbol); + dyld3::ScopedTimer timer(DBG_DYLD_TIMING_DLSYM, handle, symbol, 0); + void* result = nullptr; - DYLD_LOCK_THIS_BLOCK; - static void* (*p)(void* handle, const char* symbol) = NULL; + if ( gUseDyld3 ) { + result = dyld3::dlsym_internal(handle, symbol, __builtin_return_address(0)); + return result; + } - if(p == NULL) - _dyld_func_lookup("__dyld_dlsym", (void**)&p); - return(p(handle, symbol)); + DYLD_LOCK_THIS_BLOCK; + static void* (*p)(void* handle, const char* symbol, void *callerAddress) = NULL; + + if(p == NULL) + _dyld_func_lookup("__dyld_dlsym_internal", (void**)&p); + result = p(handle, symbol, __builtin_return_address(0)); + timer.setData4(result); + return result; } const struct dyld_all_image_infos* _dyld_get_all_image_infos() @@ -1905,6 +1750,31 @@ const void* _dyld_get_shared_cache_range(size_t* length) return p(length); } +void _dyld_images_for_addresses(unsigned count, const void* addresses[], struct dyld_image_uuid_offset infos[]) +{ + if ( gUseDyld3 ) + return dyld3::_dyld_images_for_addresses(count, addresses, infos); + + DYLD_NO_LOCK_THIS_BLOCK; + static const void (*p)(unsigned, const void*[], struct dyld_image_uuid_offset[]) = NULL; + + if(p == NULL) + _dyld_func_lookup("__dyld_images_for_addresses", (void**)&p); + return p(count, addresses, infos); +} + +void _dyld_register_for_image_loads(void (*func)(const mach_header* mh, const char* path, bool unloadable)) +{ + if ( gUseDyld3 ) + return dyld3::_dyld_register_for_image_loads(func); + + DYLD_NO_LOCK_THIS_BLOCK; + static const void (*p)(void (*)(const mach_header* mh, const char* path, bool unloadable)) = NULL; + + if(p == NULL) + _dyld_func_lookup("__dyld_register_for_image_loads", (void**)&p); + return p(func); +} bool dyld_process_is_restricted() { @@ -1968,7 +1838,7 @@ static void* mapStartOfCache(const char* path, size_t length) if ( ::stat(path, &statbuf) == -1 ) return NULL; - if ( statbuf.st_size < length ) + if ( (size_t)statbuf.st_size < length ) return NULL; int cache_fd = ::open(path, O_RDONLY); @@ -2126,5 +1996,3 @@ void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, - - diff --git a/src/dyldExceptions.c b/src/dyldExceptions.c index 487835e..96db62e 100644 --- a/src/dyldExceptions.c +++ b/src/dyldExceptions.c @@ -78,7 +78,8 @@ char* __cxa_get_globals() } char* data = (char*)_ZN4dyld17gLibSystemHelpersE->pthread_getspecific(sCxaKey); if ( data == NULL ) { - data = calloc(2,sizeof(void*)); + long* t = (long*)calloc(2,sizeof(long)); + data = (char*)t; _ZN4dyld17gLibSystemHelpersE->pthread_setspecific(sCxaKey, data); } return data; diff --git a/src/dyldInitialization.cpp b/src/dyldInitialization.cpp index 692b271..e58d08e 100644 --- a/src/dyldInitialization.cpp +++ b/src/dyldInitialization.cpp @@ -67,6 +67,23 @@ extern void syncProcessInfo(); #define POINTER_RELOC GENERIC_RELOC_VANILLA #endif +#ifndef BIND_OPCODE_THREADED +#define BIND_OPCODE_THREADED 0xD0 +#endif + +#ifndef BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB +#define BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB 0x00 +#endif + +#ifndef BIND_SUBOPCODE_THREADED_APPLY +#define BIND_SUBOPCODE_THREADED_APPLY 0x01 +#endif + + +#if __has_feature(ptrauth_calls) +#include +#endif + #if TARGET_IPHONE_SIMULATOR const dyld::SyscallHelpers* gSyscallHelpers = NULL; @@ -124,6 +141,43 @@ static uintptr_t slideOfMainExecutable(const struct macho_header* mh) return 0; } +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 |= (~0ULL) << bit; + return result; +} + // // If the kernel does not load dyld at its preferred address, we need to apply @@ -136,7 +190,215 @@ static void rebaseDyld(const struct macho_header* mh, intptr_t slide) const uint32_t cmd_count = mh->ncmds; const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); const struct load_command* cmd = cmds; - const struct macho_segment_command* linkEditSeg = NULL; + + // First look for compressed info and use it if it exists. + const struct macho_segment_command* linkEditSeg = NULL; + const dyld_info_command* dyldInfoCmd = NULL; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + if ( strcmp(seg->segname, "__LINKEDIT") == 0 ) + linkEditSeg = seg; + break; + } + case LC_DYLD_INFO_ONLY: + dyldInfoCmd = (struct dyld_info_command*)cmd; + break; + } + if (dyldInfoCmd && linkEditSeg) + break; + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + if ( linkEditSeg == NULL ) + throw "dyld missing LINKEDIT"; + + // Reset the iterator. + cmd = cmds; + + auto getSegmentAtIndex = [cmd_count, cmds](unsigned segIndex) -> const struct macho_segment_command* { + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + if (!segIndex) { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + return seg; + } + --segIndex; + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + throw "out of bounds command"; + return 0; + }; + + auto segActualLoadAddress = [&](unsigned segIndex) -> uintptr_t { + const struct macho_segment_command* seg = getSegmentAtIndex(segIndex); + return seg->vmaddr + slide; + }; + +#if __has_feature(ptrauth_calls) + auto imageBaseAddress = [cmds, cmd_count]() -> uintptr_t { + const struct load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: { + const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; + if ( (seg->fileoff == 0) && (seg->filesize != 0) ) + return seg->vmaddr; + break; + } + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + return 0; + }; +#endif + + if (dyldInfoCmd && (dyldInfoCmd->bind_size != 0) ) { + if ( dyldInfoCmd->rebase_size != 0 ) + throw "unthreaded rebases are not supported"; + + const uint8_t* linkEditBase = (uint8_t*)(linkEditSeg->vmaddr + slide - linkEditSeg->fileoff); + + const uint8_t* const start = linkEditBase + dyldInfoCmd->bind_off; + const uint8_t* const end = &start[dyldInfoCmd->bind_size]; + const uint8_t* p = start; + + uintptr_t segmentStartAddress = 0; + uint64_t segOffset = 0; + int segIndex = 0; +#if __has_feature(ptrauth_calls) + uintptr_t fBaseAddress = imageBaseAddress(); +#endif + 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: + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + break; + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + while (*p != '\0') + ++p; + ++p; + break; + case BIND_OPCODE_SET_TYPE_IMM: + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + read_sleb128(p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segIndex = immediate; + segmentStartAddress = segActualLoadAddress(segIndex); + segOffset = read_uleb128(p, end); + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + segOffset += read_uleb128(p, end); + break; + case BIND_OPCODE_DO_BIND: + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + read_uleb128(p, end); + read_uleb128(p, end); + break; + case BIND_OPCODE_THREADED: + // Note the immediate is a sub opcode + switch (immediate) { + case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: + read_uleb128(p, end); + break; + case BIND_SUBOPCODE_THREADED_APPLY: { + uint64_t delta = 0; + do { + uintptr_t address = segmentStartAddress + (uintptr_t)segOffset; + uint64_t value = *(uint64_t*)address; + +#if __has_feature(ptrauth_calls) + uint16_t diversity = (uint16_t)(value >> 32); + bool hasAddressDiversity = (value & (1ULL << 48)) != 0; + ptrauth_key key = (ptrauth_key)((value >> 49) & 0x3); + bool isAuthenticated = (value & (1ULL << 63)) != 0; +#endif + bool isRebase = (value & (1ULL << 62)) == 0; + if (isRebase) { + +#if __has_feature(ptrauth_calls) + if (isAuthenticated) { + // The new value for a rebase is the low 32-bits of the threaded value plus the slide. + uint64_t newValue = (value & 0xFFFFFFFF) + slide; + // Add in the offset from the mach_header + newValue += fBaseAddress; + // We have bits to merge in to the discriminator + uintptr_t discriminator = diversity; + if (hasAddressDiversity) { + // First calculate a new discriminator using the address of where we are trying to store the value + discriminator = __builtin_ptrauth_blend_discriminator((void*)address, discriminator); + } + switch (key) { + case ptrauth_key_asia: + newValue = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)newValue, ptrauth_key_asia, discriminator); + break; + case ptrauth_key_asib: + newValue = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)newValue, ptrauth_key_asib, discriminator); + break; + case ptrauth_key_asda: + newValue = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)newValue, ptrauth_key_asda, discriminator); + break; + case ptrauth_key_asdb: + newValue = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)newValue, ptrauth_key_asdb, discriminator); + break; + } + *(uint64_t*)address = newValue; + } else +#endif + { + // Regular pointer which needs to fit in 51-bits of value. + // C++ RTTI uses the top bit, so we'll allow the whole top-byte + // and the signed-extended bottom 43-bits to be fit in to 51-bits. + uint64_t top8Bits = value & 0x0007F80000000000ULL; + uint64_t bottom43Bits = value & 0x000007FFFFFFFFFFULL; + uint64_t targetValue = ( top8Bits << 13 ) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); + targetValue = targetValue + slide; + *(uint64_t*)address = targetValue; + } + } + + // The delta is bits [51..61] + // And bit 62 is to tell us if we are a rebase (0) or bind (1) + value &= ~(1ULL << 62); + delta = ( value & 0x3FF8000000000000 ) >> 51; + segOffset += delta * sizeof(uintptr_t); + } while ( delta != 0 ); + break; + } + default: + throw "unknown threaded bind subopcode"; + } + break; + default: + throw "unknown bind opcode"; + } + } + return; + } + #if __x86_64__ const struct macho_segment_command* firstWritableSeg = NULL; #endif @@ -146,8 +408,6 @@ static void rebaseDyld(const struct macho_header* mh, intptr_t slide) case LC_SEGMENT_COMMAND: { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; - if ( strcmp(seg->segname, "__LINKEDIT") == 0 ) - linkEditSeg = seg; const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { @@ -176,6 +436,8 @@ static void rebaseDyld(const struct macho_header* mh, intptr_t slide) // use reloc's to rebase all random data pointers #if __x86_64__ + if ( firstWritableSeg == NULL ) + throw "no writable segment in dyld"; const uintptr_t relocBase = firstWritableSeg->vmaddr + slide; #else const uintptr_t relocBase = (uintptr_t)mh; @@ -209,9 +471,14 @@ uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* { // if kernel had to slide dyld, we need to fix up load sensitive locations // we have to do this before using any global variables - if ( slide != 0 ) { - rebaseDyld(dyldsMachHeader, slide); - } + slide = slideOfMainExecutable(dyldsMachHeader); + bool shouldRebase = slide != 0; +#if __has_feature(ptrauth_calls) + shouldRebase = true; +#endif + if ( shouldRebase ) { + rebaseDyld(dyldsMachHeader, slide); + } // allow dyld to use mach messaging mach_init(); diff --git a/src/dyldLibSystemInterface.h b/src/dyldLibSystemInterface.h index f39a275..69da788 100644 --- a/src/dyldLibSystemInterface.h +++ b/src/dyldLibSystemInterface.h @@ -41,19 +41,19 @@ namespace dyld { struct LibSystemHelpers { uintptr_t version; - void (*acquireGlobalDyldLock)(); - void (*releaseGlobalDyldLock)(); + void (*acquireGlobalDyldLock)(void); + void (*releaseGlobalDyldLock)(void); char* (*getThreadBufferFor_dlerror)(size_t sizeRequired); // addded in version 2 void* (*malloc)(size_t); void (*free)(void*); int (*cxa_atexit)(void (*)(void*), void*, void*); // addded in version 3 - void (*dyld_shared_cache_missing)(); - void (*dyld_shared_cache_out_of_date)(); + void (*dyld_shared_cache_missing)(void); + void (*dyld_shared_cache_out_of_date)(void); // addded in version 4 - void (*acquireDyldInitializerLock)(); - void (*releaseDyldInitializerLock)(); + void (*acquireDyldInitializerLock)(void); + void (*releaseDyldInitializerLock)(void); // added in version 5 int (*pthread_key_create)(pthread_key_t*, void (*destructor)(void*)); int (*pthread_setspecific)(pthread_key_t, const void*); @@ -66,9 +66,9 @@ namespace dyld { // added in version 9 void* startGlueToCallExit; // added in version 10 - bool (*hasPerThreadBufferFor_dlerror)(); + bool (*hasPerThreadBufferFor_dlerror)(void); // added in version 11 - bool (*isLaunchdOwned)(); + bool (*isLaunchdOwned)(void); // added in version 12 kern_return_t (*vm_alloc)(vm_map_t task, vm_address_t* addr, vm_size_t size, int flags); void* (*mmap)(void* addr, size_t len, int prot, int flags, int fd, off_t offset); diff --git a/src/dyldNew.cpp b/src/dyldNew.cpp index fb393fa..1511a51 100644 --- a/src/dyldNew.cpp +++ b/src/dyldNew.cpp @@ -69,8 +69,8 @@ void* malloc(size_t size) } else { if ( size > DYLD_POOL_CHUNK_SIZE ) { - dyld::log("dyld malloc overflow: size=%zu\n", size); - exit(1); + dyld::log("dyld malloc overflow: size=%lu\n", size); + dyld::halt("dyld malloc overflow\n"); } size = (size+sizeof(void*)-1) & (-sizeof(void*)); // pointer align uint8_t* result = currentPool->current; @@ -79,8 +79,7 @@ void* malloc(size_t size) vm_address_t addr = 0; kern_return_t r = vm_allocate(mach_task_self(), &addr, DYLD_POOL_CHUNK_SIZE, VM_FLAGS_ANYWHERE); if ( r != KERN_SUCCESS ) { - dyld::log("out of address space for dyld memory pool\n"); - exit(1); + dyld::halt("out of address space for dyld memory pool\n"); } dyld_static_pool* newPool = (dyld_static_pool*)addr; newPool->previousPool = NULL; @@ -90,7 +89,7 @@ void* malloc(size_t size) currentPool = newPool; if ( (currentPool->current + size) > currentPool->end ) { dyld::log("dyld memory pool exhausted: size=%lu\n", size); - exit(1); + dyld::halt("dyld memory pool exhausted\n"); } result = currentPool->current; currentPool->current += size; diff --git a/src/dyldStartup.s b/src/dyldStartup.s index 8f71e0c..34522bf 100644 --- a/src/dyldStartup.s +++ b/src/dyldStartup.s @@ -2,14 +2,14 @@ * Copyright (c) 1999-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, @@ -17,7 +17,7 @@ * 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@ */ /* @@ -29,14 +29,14 @@ * * | STRING AREA | * +-------------+ - * | 0 | + * | 0 | * +-------------+ * | apple[n] | * +-------------+ * : * +-------------+ - * | apple[0] | - * +-------------+ + * | apple[0] | + * +-------------+ * | 0 | * +-------------+ * | env[n] | @@ -80,9 +80,9 @@ __dyld_start: movl %esp,%ebp # pointer to base of kernel frame andl $-16,%esp # force SSE alignment subl $32,%esp # room for locals and outgoing parameters - + call L__dyld_start_picbase -L__dyld_start_picbase: +L__dyld_start_picbase: popl %ebx # set %ebx to runtime value of picbase movl Lmh-L__dyld_start_picbase(%ebx), %ecx # ecx = prefered load address @@ -91,15 +91,15 @@ L__dyld_start_picbase: addl %ebx, %ecx # ecx = actual load address # call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue) movl %edx,(%esp) # param1 = app_mh - movl 4(%ebp),%eax + movl 4(%ebp),%eax movl %eax,4(%esp) # param2 = argc - lea 8(%ebp),%eax + lea 8(%ebp),%eax movl %eax,8(%esp) # param3 = argv movl %ebx,12(%esp) # param4 = slide movl %ecx,16(%esp) # param5 = actual load address lea 28(%esp),%eax movl %eax,20(%esp) # param6 = &startGlue - call __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm + call __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm movl 28(%esp),%edx cmpl $0,%edx jne Lnew @@ -110,7 +110,7 @@ L__dyld_start_picbase: movl $0,%ebp # restore ebp back to zero jmp *%eax # jump to the entry point - # LC_MAIN case, set up stack for call to main() + # LC_MAIN case, set up stack for call to main() Lnew: movl 4(%ebp),%ebx movl %ebx,(%esp) # main param1 = argc leal 8(%ebp),%ecx @@ -124,11 +124,11 @@ Lapple: movl (%ebx),%ecx # look for NULL ending env[] array movl %ebx,12(%esp) # main param4 = apple pushl %edx # simulate return address into _start in libdyld jmp *%eax # jump to main(argc,argv,env,apple) with return address set to _start -#endif +#endif #if !TARGET_IPHONE_SIMULATOR .data -__dyld_start_static_picbase: +__dyld_start_static_picbase: .long L__dyld_start_picbase Lmh: .long ___dso_handle #endif @@ -142,7 +142,7 @@ Lmh: .long ___dso_handle #if !TARGET_IPHONE_SIMULATOR .data .align 3 -__dyld_start_static: +__dyld_start_static: .quad __dyld_start #endif @@ -157,7 +157,7 @@ __dyld_start: movq %rsp,%rbp # pointer to base of kernel frame andq $-16,%rsp # force SSE alignment subq $16,%rsp # room for local variables - + # call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue) movl 8(%rbp),%esi # param2 = argc into %esi leaq 16(%rbp),%rdx # param3 = &argv[0] into %rdx @@ -176,8 +176,8 @@ __dyld_start: addq $8,%rsp # remove the mh argument, and debugger end frame marker movq $0,%rbp # restore ebp back to zero jmp *%rax # jump to the entry point - - # LC_MAIN case, set up stack for call to main() + + # LC_MAIN case, set up stack for call to main() Lnew: addq $16,%rsp # remove local variables pushq %rdi # simulate return address into _start in libdyld movq 8(%rbp),%rdi # main param1 = argc into %rdi @@ -199,10 +199,10 @@ Lapple: movq (%rcx),%r8 .syntax unified .data .align 2 -__dyld_start_static_picbase: +__dyld_start_static_picbase: .long L__dyld_start_picbase - + // Hack to make ___dso_handle work // Without this local symbol, assembler will error out about in subtraction expression // The real ___dso_handle (non-weak) sythesized by the linker @@ -232,16 +232,16 @@ L__dyld_start_picbase: add r2, r8, #8 // r2 = argv ldr r4, Lmh -L3: add r4, r4, pc +L3: add r4, r4, pc str r4, [sp, #0] // [sp] = dyld_mh add r4, sp, #12 str r4, [sp, #4] // [sp+4] = &startGlue - + bl __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm ldr r5, [sp, #12] cmp r5, #0 bne Lnew - + // traditional case, clean up stack and jump to result add sp, r8, #4 // remove the mach_header argument. bx r0 // jump to the program's entry point @@ -251,7 +251,7 @@ Lnew: mov lr, r5 // simulate return address into _start in libdyld mov r5, r0 // save address of main() for later use ldr r0, [r8, #4] // main param1 = argc add r1, r8, #8 // main param2 = argv - add r2, r1, r0, lsl #2 + add r2, r1, r0, lsl #2 add r2, r2, #4 // main param3 = &env[0] mov r3, r2 Lapple: ldr r4, [r3] @@ -273,7 +273,7 @@ Lmh: .long ___dso_handle-L3-8 #if __arm64__ .data .align 3 -__dso_static: +__dso_static: .quad ___dso_handle .text @@ -287,39 +287,71 @@ __dyld_start: stp x1, x0, [sp, #-16]! // make aligned terminating frame mov fp, sp // set up fp to point to terminating frame sub sp, sp, #16 // make room for local variables - ldr x0, [x28] // get app's mh into x0 - ldr x1, [x28, #8] // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment) - add x2, x28, #16 // get argv into x2 +#if __LP64__ + ldr x0, [x28] // get app's mh into x0 + ldr x1, [x28, #8] // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment) + add x2, x28, #16 // get argv into x2 +#else + ldr w0, [x28] // get app's mh into x0 + ldr w1, [x28, #4] // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment) + add w2, w28, #8 // get argv into x2 +#endif adrp x4,___dso_handle@page add x4,x4,___dso_handle@pageoff // get dyld's mh in to x4 adrp x3,__dso_static@page ldr x3,[x3,__dso_static@pageoff] // get unslid start of dyld sub x3,x4,x3 // x3 now has slide of dyld mov x5,sp // x5 has &startGlue - + // call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue) bl __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm mov x16,x0 // save entry point address in x16 +#if __LP64__ ldr x1, [sp] +#else + ldr w1, [sp] +#endif cmp x1, #0 b.ne Lnew // LC_UNIXTHREAD way, clean up stack and jump to result - add sp, x28, #8 // restore unaligned stack pointer without app mh - br x16 // jump to the program's entry point +#if __LP64__ + add sp, x28, #8 // restore unaligned stack pointer without app mh +#else + add sp, x28, #4 // restore unaligned stack pointer without app mh +#endif +#if __arm64e__ + braaz x16 // jump to the program's entry point +#else + br x16 // jump to the program's entry point +#endif // LC_MAIN case, set up stack for call to main() Lnew: mov lr, x1 // simulate return address into _start in libdyld.dylib - ldr x0, [x28, #8] // main param1 = argc - add x1, x28, #16 // main param2 = argv - add x2, x1, x0, lsl #3 - add x2, x2, #8 // main param3 = &env[0] +#if __LP64__ + ldr x0, [x28, #8] // main param1 = argc + add x1, x28, #16 // main param2 = argv + add x2, x1, x0, lsl #3 + add x2, x2, #8 // main param3 = &env[0] mov x3, x2 Lapple: ldr x4, [x3] add x3, x3, #8 +#else + ldr w0, [x28, #4] // main param1 = argc + add x1, x28, #8 // main param2 = argv + add x2, x1, x0, lsl #2 + add x2, x2, #4 // main param3 = &env[0] + mov x3, x2 +Lapple: ldr w4, [x3] + add x3, x3, #4 +#endif cmp x4, #0 b.ne Lapple // main param4 = apple - br x16 +#if __arm64e__ + braaz x16 +#else + br x16 +#endif #endif // __arm64__ diff --git a/src/dyldSyscallInterface.h b/src/dyldSyscallInterface.h index c8b90d3..c8ea4df 100644 --- a/src/dyldSyscallInterface.h +++ b/src/dyldSyscallInterface.h @@ -67,8 +67,8 @@ namespace dyld { bool (*OSAtomicCompareAndSwapPtrBarrier)(void* old, void* nw, void * volatile *value); void (*OSMemoryBarrier)(void); void* (*getProcessInfo)(void); // returns dyld_all_image_infos*; - int* (*errnoAddress)(); - uint64_t (*mach_absolute_time)(); + int* (*errnoAddress)(void); + uint64_t (*mach_absolute_time)(void); // Added in version 2 kern_return_t (*thread_switch)(mach_port_name_t, int, mach_msg_timeout_t); // Added in version 3 @@ -80,7 +80,7 @@ namespace dyld { void (*coresymbolication_unload_notifier)(void *connection, uint64_t unload_timestamp, const char *image_path, const struct mach_header *mach_header); // Added in version 5 int (*proc_regionfilename)(int pid, uint64_t address, void* buffer, uint32_t buffersize); - int (*getpid)(); + int (*getpid)(void); kern_return_t (*mach_port_insert_right)(ipc_space_t task, mach_port_name_t name, mach_port_t poly, mach_msg_type_name_t polyPoly); kern_return_t (*mach_port_allocate)(ipc_space_t, mach_port_right_t, mach_port_name_t*); kern_return_t (*mach_msg)(mach_msg_header_t *, mach_msg_option_t , mach_msg_size_t , mach_msg_size_t , mach_port_name_t , mach_msg_timeout_t , mach_port_name_t); @@ -95,8 +95,20 @@ namespace dyld { 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); + // Add in version 8 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); + // Add in version 9 + uint64_t (*kdebug_trace_string)(uint32_t debugid, uint64_t str_id, const char *str); + // Add in version 10 + int (*amfi_check_dyld_policy_self)(uint64_t input_flags, uint64_t* output_flags); + // Add in version 11 + void (*notifyMonitoringDyldMain)(void); + void (*notifyMonitoringDyld)(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[], const char* imagePaths[]); + // Add in version 12 + void (*mach_msg_destroy)(mach_msg_header_t *msg); + kern_return_t (*mach_port_construct)(ipc_space_t task, mach_port_options_ptr_t options, mach_port_context_t context, mach_port_name_t *name); + kern_return_t (*mach_port_destruct)(ipc_space_t task, mach_port_name_t name, mach_port_delta_t srdelta, mach_port_context_t guard); }; extern const struct SyscallHelpers* gSyscallHelpers; diff --git a/src/dyld_gdb.cpp b/src/dyld_gdb.cpp index 5f4e7b5..7c3ef2a 100644 --- a/src/dyld_gdb.cpp +++ b/src/dyld_gdb.cpp @@ -33,6 +33,7 @@ #include "mach-o/dyld_gdb.h" #include "mach-o/dyld_images.h" #include "mach-o/dyld_process_info.h" +#include "Tracing.h" #include "ImageLoader.h" #include "dyld.h" @@ -191,6 +192,7 @@ void removeImageFromAllImages(const struct mach_header* loadAddress) static void gdb_image_notifier(enum dyld_image_mode mode, uint32_t infoCount, const dyld_image_info info[]) { + dyld3::ScopedTimer(DBG_DYLD_GDB_IMAGE_NOTIFIER, 0, 0, 0); uint64_t machHeaders[infoCount]; for (uint32_t i=0; i < infoCount; ++i) { machHeaders[i] = (uintptr_t)(info[i].imageLoadAddress); @@ -228,10 +230,10 @@ void removeImageFromAllImages(const struct mach_header* loadAddress) struct dyld_all_image_infos dyld_all_image_infos __attribute__ ((section ("__DATA,__all_image_info"))) = { - 15, 0, NULL, &gdb_image_notifier, false, false, (const mach_header*)&__dso_handle, NULL, - XSTR(DYLD_VERSION), NULL, 0, NULL, 0, 0, NULL, &dyld_all_image_infos, + 15, 0, {NULL}, &gdb_image_notifier, false, false, (const mach_header*)&__dso_handle, NULL, + XSTR(DYLD_VERSION), NULL, 0, NULL, 0, 0, NULL, &dyld_all_image_infos, 0, 0, NULL, NULL, NULL, 0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}, - 0, 0, "/usr/lib/dyld", {0}, {0} + 0, {0}, "/usr/lib/dyld", {0}, {0} }; struct dyld_shared_cache_ranges dyld_shared_cache_ranges; diff --git a/src/dyld_process_info.cpp b/src/dyld_process_info.cpp index fc47bce..e373fa9 100644 --- a/src/dyld_process_info.cpp +++ b/src/dyld_process_info.cpp @@ -38,77 +38,127 @@ #include "dyld_images.h" #include "dyld_priv.h" +#include "Tracing.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, size_t size)) { - 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), local_size); - 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; +RemoteBuffer::RemoteBuffer() : _localAddress(0), _size(0) {} +bool RemoteBuffer::map(task_t task, mach_vm_address_t remote_address, bool shared) { + vm_prot_t cur_protection = VM_PROT_NONE; + vm_prot_t max_protection = VM_PROT_NONE; + if (_size == 0) { + _kr = KERN_NO_SPACE; + return false; + } + _localAddress = 0; + _kr = mach_vm_remap(mach_task_self(), + &_localAddress, + _size, + 0, // mask + VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR | (shared ? 0 : VM_FLAGS_RESILIENT_CODESIGN), + task, + remote_address, + !shared, + &cur_protection, + &max_protection, + VM_INHERIT_NONE); + dyld3::kdebug_trace_dyld_marker(DBG_DYLD_DEBUGGING_VM_REMAP, _localAddress, (uint64_t)_size, _kr, remote_address); + if (shared && (cur_protection != (VM_PROT_READ|VM_PROT_WRITE))) { + if (_kr == KERN_SUCCESS && _localAddress != 0) { + _kr = vm_deallocate(mach_task_self(), _localAddress, _size); + dyld3::kdebug_trace_dyld_marker(DBG_DYLD_DEBUGGING_VM_UNMAP, _localAddress, (uint64_t)_size, _kr, 0); } + _localAddress = 0; + _kr = KERN_PROTECTION_FAILURE; } + return (_kr == KERN_SUCCESS); } -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, size_t size) { - block(*reinterpret_cast(buffer)); - }); +RemoteBuffer::RemoteBuffer(task_t task, mach_vm_address_t remote_address, size_t remote_size, bool shared, bool allow_truncation) + : _localAddress(0), _size(remote_size), _kr(KERN_SUCCESS) { + // Try the initial map + if (map(task, remote_address, shared)) return; + // It failed, try to calculate the largest size that can fit in the same page as the remote_address + uint64_t newSize = PAGE_SIZE - remote_address%PAGE_SIZE;; + // If truncation is allowed and the newSize is different than the original size try that + if (!allow_truncation && newSize != _size) return; + _size = newSize; + if (map(task, remote_address, shared)) return; + // That did not work, null out the buffer + _size = 0; + _localAddress = 0; } -}; +RemoteBuffer::~RemoteBuffer() { + if (_localAddress) { + _kr = vm_deallocate(mach_task_self(), _localAddress, _size); + dyld3::kdebug_trace_dyld_marker(DBG_DYLD_DEBUGGING_VM_UNMAP, _localAddress, (uint64_t)_size, _kr, 0); + } +} +void *RemoteBuffer::getLocalAddress() { return (void *)_localAddress; } +size_t RemoteBuffer::getSize() { return _size; } +kern_return_t RemoteBuffer::getKernelReturn() { return _kr; } + +void withRemoteBuffer(task_t task, mach_vm_address_t remote_address, size_t remote_size, bool shared, bool allow_truncation, kern_return_t *kr, void (^block)(void *buffer, size_t size)) { + kern_return_t krSink = KERN_SUCCESS; + if (kr == nullptr) { + kr = &krSink; + } + RemoteBuffer buffer(task, remote_address, remote_size, shared, allow_truncation); + *kr = buffer.getKernelReturn(); + if (*kr == KERN_SUCCESS) { + block(buffer.getLocalAddress(), buffer.getSize()); + } +} + // // Opaque object returned by _dyld_process_info_create() // +struct __attribute__((visibility("hidden"))) dyld_process_info_deleter { // deleter + // dyld_process_info_deleter() {}; + // dyld_process_info_deleter(const dyld_process_info_deleter&) { } + // dyld_process_info_deleter(dyld_process_info_deleter&) {} + // dyld_process_info_deleter(dyld_process_info_deleter&&) {} + void operator()(dyld_process_info_base* p) const { + if (p) { + free(p); + } + }; +}; + +static dyld_process_info_deleter deleter; +typedef std::unique_ptr dyld_process_info_ptr; + struct __attribute__((visibility("hidden"))) dyld_process_info_base { - static dyld_process_info_base* make(task_t task, const dyld_all_image_infos_64& allImageInfo, const dyld_image_info_64 imageArray[], kern_return_t* kr); - static dyld_process_info_base* makeSuspended(task_t task, kern_return_t* kr); + template + static dyld_process_info_ptr make(task_t task, const T1& allImageInfo, uint64_t timestamp, kern_return_t* kr); + template + static dyld_process_info_ptr makeSuspended(task_t task, const T& allImageInfo, kern_return_t* kr); - uint32_t& retainCount() const { return _retainCount; } + std::atomic& retainCount() const { return _retainCount; } dyld_process_cache_info* cacheInfo() const { return (dyld_process_cache_info*)(((char*)this) + _cacheInfoOffset); } dyld_process_state_info* stateInfo() const { return (dyld_process_state_info*)(((char*)this) + _stateInfoOffset); } void forEachImage(void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)) const; void forEachSegment(uint64_t machHeaderAddress, void (^callback)(uint64_t segmentAddress, uint64_t segmentSize, const char* segmentName)) const; + void retain() + { + _retainCount++; + } + + void release() + { + uint32_t newCount = --_retainCount; + + if ( newCount == 0 ) { + free(this); + } + } + private: struct ImageInfo { uuid_t uuid; @@ -129,6 +179,7 @@ private: static bool inCache(uint64_t addr) { return (addr > SHARED_REGION_BASE) && (addr < SHARED_REGION_BASE+SHARED_REGION_SIZE); } kern_return_t addImage(task_t task, bool sameCacheAsThisProcess, uint64_t imageAddress, uint64_t imagePath, const char* imagePathLocal); + kern_return_t addDyldImage(task_t task, uint64_t dyldAddress, uint64_t dyldPathAddress, const char* localPath); bool invalid() { return ((char*)_stringRevBumpPtr < (char*)_curSegment); } @@ -137,11 +188,12 @@ private: const char* copySegmentName(const char*); void addInfoFromLoadCommands(const mach_header* mh, uint64_t addressInTask, size_t size); + kern_return_t addInfoFromRemoteLoadCommands(task_t task, uint64_t remoteMH); void inspectLocalImageLoadCommands(uint64_t imageAddress, void* func); kern_return_t inspectRemoteImageLoadCommands(task_t task, uint64_t imageAddress, void* func); - mutable uint32_t _retainCount; + mutable std::atomic _retainCount; const uint32_t _cacheInfoOffset; const uint32_t _stateInfoOffset; const uint32_t _imageInfosOffset; @@ -174,103 +226,175 @@ dyld_process_info_base::dyld_process_info_base(unsigned imageCount, size_t total { } - -dyld_process_info_base* dyld_process_info_base::make(task_t task, const dyld_all_image_infos_64& allImageInfo, const dyld_image_info_64 imageArray[], kern_return_t* kr) +template +dyld_process_info_ptr dyld_process_info_base::make(task_t task, const T1& allImageInfo, uint64_t timestamp, kern_return_t* kr) { - // figure out how many path strings will need to be copied and their size - const dyld_all_image_infos* myInfo = _dyld_get_all_image_infos(); - bool sameCacheAsThisProcess = !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) { - if ( !inCache(imageArray[i].imageFilePath) ) - ++countOfPathsNeedingCopying; - } - } - else { - countOfPathsNeedingCopying = allImageInfo.infoArrayCount+1; - } - unsigned imageCountWithDyld = allImageInfo.infoArrayCount+1; + __block dyld_process_info_ptr result = nullptr; - // allocate result object - size_t allocationSize = sizeof(dyld_process_info_base) - + sizeof(dyld_process_cache_info) - + sizeof(dyld_process_state_info) - + sizeof(ImageInfo)*(imageCountWithDyld) - + sizeof(SegmentInfo)*imageCountWithDyld*5 - + countOfPathsNeedingCopying*PATH_MAX; - void* storage = malloc(allocationSize); - dyld_process_info_base* obj = new (storage) dyld_process_info_base(imageCountWithDyld, allocationSize); // placement new() - - // fill in base info - dyld_process_cache_info* cacheInfo = obj->cacheInfo(); - memcpy(cacheInfo->cacheUUID, allImageInfo.sharedCacheUUID, 16); - cacheInfo->cacheBaseAddress = allImageInfo.sharedCacheBaseAddress; - cacheInfo->privateCache = allImageInfo.processDetachedFromSharedRegion; - // if no cache is used, allImageInfo has all zeros for cache UUID - cacheInfo->noCache = true; - for (int i=0; i < 16; ++i) { - if ( cacheInfo->cacheUUID[i] != 0 ) { - cacheInfo->noCache = false; + // bail out of dyld is too old + if ( allImageInfo.version < 15 ) { + *kr = KERN_FAILURE; + return nullptr; + } + + // Check if the process is suspended + if (allImageInfo.infoArrayChangeTimestamp == 0) { + result = dyld_process_info_base::makeSuspended(task, allImageInfo, kr); + // If we have a result return it, otherwise rescan + if (result) { + // If it returned the process is suspended and there is nothing more to do + return std::move(result); + } else { + // Check to see if the process change timestamp is greater than 0, if not then sleep to let the process + // finish initializing + if (allImageInfo.infoArrayChangeTimestamp == 0) { + usleep(1000 * 50); // 50ms + } } } - dyld_process_state_info* stateInfo = obj->stateInfo(); - stateInfo->timestamp = allImageInfo.infoArrayChangeTimestamp; - stateInfo->imageCount = imageCountWithDyld; - stateInfo->initialImageCount = (uint32_t)(allImageInfo.initialImageCount+1); - if ( allImageInfo.infoArray != 0 ) - stateInfo->dyldState = dyld_process_state_dyld_initialized; - if ( allImageInfo.libSystemInitialized != 0 ) { - stateInfo->dyldState = dyld_process_state_libSystem_initialized; - if ( allImageInfo.initialImageCount != allImageInfo.infoArrayCount ) - stateInfo->dyldState = dyld_process_state_program_running; - } - if ( allImageInfo.errorMessage != 0 ) - stateInfo->dyldState = allImageInfo.terminationFlags ? dyld_process_state_terminated_before_inits : dyld_process_state_dyld_terminated; + // Test to see if there are no changes and we can exit early + if (timestamp != 0 && timestamp == allImageInfo.infoArrayChangeTimestamp) { + *kr = KERN_SUCCESS; + return nullptr; + } + + for(uint32_t i = 0; i < 10; ++i) { + uint64_t currentTimestamp = allImageInfo.infoArrayChangeTimestamp; + mach_vm_address_t infoArray = allImageInfo.infoArray; + if (currentTimestamp == 0) continue; + if (infoArray == 0) { + // Check if the task is suspended mid dylib load and exit early + mach_task_basic_info ti; + mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; + if ((*kr = task_info(task, MACH_TASK_BASIC_INFO, (task_info_t)&ti, &count))) { + continue; + } - // fill in info for dyld - if ( allImageInfo.dyldPath != 0 ) { - if ( kern_return_t r = obj->addDyldImage(task, allImageInfo.dyldImageLoadAddress, allImageInfo.dyldPath, NULL) ) { - if ( kr != NULL ) - *kr = r; - goto fail; - } - } + // The task is suspended, exit + if (ti.suspend_count != 0) { + // Not exactly correct, but conveys that operation may succeed in the future + *kr = KERN_RESOURCE_SHORTAGE; + return nullptr; + } + continue; + }; + + // For the moment we are going to truncate any image list longer than 8192 because some programs do + // terrible things that corrupt their own image lists and we need to stop clients from crashing + // reading them. We can try to do something more advanced in the future. rdar://27446361 + uint32_t imageCount = allImageInfo.infoArrayCount; + imageCount = MIN(imageCount, 8192); + size_t imageArraySize = imageCount * sizeof(T2); + + withRemoteBuffer(task, infoArray, imageArraySize, false, false, kr, ^(void *buffer, size_t size) { + // figure out how many path strings will need to be copied and their size + T2* imageArray = (T2 *)buffer; + const dyld_all_image_infos* myInfo = _dyld_get_all_image_infos(); + bool sameCacheAsThisProcess = !allImageInfo.processDetachedFromSharedRegion + && !myInfo->processDetachedFromSharedRegion + && ((memcmp(myInfo->sharedCacheUUID, &allImageInfo.sharedCacheUUID[0], 16) == 0) + && (myInfo->sharedCacheSlide == allImageInfo.sharedCacheSlide)); + unsigned countOfPathsNeedingCopying = 0; + if ( sameCacheAsThisProcess ) { + for (uint32_t i=0; i < imageCount; ++i) { + if ( !inCache(imageArray[i].imageFilePath) ) + ++countOfPathsNeedingCopying; + } + } + else { + countOfPathsNeedingCopying = imageCount+1; + } + unsigned imageCountWithDyld = imageCount+1; + + // allocate result object + size_t allocationSize = sizeof(dyld_process_info_base) + + sizeof(dyld_process_cache_info) + + sizeof(dyld_process_state_info) + + sizeof(ImageInfo)*(imageCountWithDyld) + + sizeof(SegmentInfo)*imageCountWithDyld*5 + + countOfPathsNeedingCopying*PATH_MAX; + void* storage = malloc(allocationSize); + auto info = dyld_process_info_ptr(new (storage) dyld_process_info_base(imageCountWithDyld, allocationSize), deleter); + //info = new (storage) dyld_process_info_base(imageCountWithDyld, allocationSize); // placement new() + + // fill in base info + dyld_process_cache_info* cacheInfo = info->cacheInfo(); + memcpy(cacheInfo->cacheUUID, &allImageInfo.sharedCacheUUID[0], 16); + cacheInfo->cacheBaseAddress = allImageInfo.sharedCacheBaseAddress; + cacheInfo->privateCache = allImageInfo.processDetachedFromSharedRegion; + // if no cache is used, allImageInfo has all zeros for cache UUID + cacheInfo->noCache = true; + for (int i=0; i < 16; ++i) { + if ( cacheInfo->cacheUUID[i] != 0 ) { + cacheInfo->noCache = false; + } + } - // fill in info for each image - for (uint32_t i=0; i < allImageInfo.infoArrayCount; ++i) { - if ( kern_return_t r = obj->addImage(task, sameCacheAsThisProcess, imageArray[i].imageLoadAddress, imageArray[i].imageFilePath, NULL) ) { - if ( kr != NULL ) - *kr = r; - goto fail; - } - } + dyld_process_state_info* stateInfo = info->stateInfo(); + stateInfo->timestamp = currentTimestamp; + stateInfo->imageCount = imageCountWithDyld; + stateInfo->initialImageCount = (uint32_t)(allImageInfo.initialImageCount+1); + stateInfo->dyldState = dyld_process_state_dyld_initialized; - // sanity check internal data did not overflow - if ( obj->invalid() ) - goto fail; + if ( allImageInfo.libSystemInitialized != 0 ) { + stateInfo->dyldState = dyld_process_state_libSystem_initialized; + if ( allImageInfo.initialImageCount != imageCount ) { + stateInfo->dyldState = dyld_process_state_program_running; + } + } + if ( allImageInfo.errorMessage != 0 ) { + stateInfo->dyldState = allImageInfo.terminationFlags ? dyld_process_state_terminated_before_inits : dyld_process_state_dyld_terminated; + } + // fill in info for dyld + if ( allImageInfo.dyldPath != 0 ) { + if ((*kr = info->addDyldImage(task, allImageInfo.dyldImageLoadAddress, allImageInfo.dyldPath, NULL))) { + result = nullptr; + return; + } + } + // fill in info for each image + for (uint32_t i=0; i < imageCount; ++i) { + if ((*kr = info->addImage(task, sameCacheAsThisProcess, imageArray[i].imageLoadAddress, imageArray[i].imageFilePath, NULL))) { + result = nullptr; + return; + } + } + // sanity check internal data did not overflow + if ( info->invalid() ) { + *kr = KERN_FAILURE; + result = nullptr; + return; + } - return obj; + result = std::move(info); + }); -fail: - free(obj); - return NULL; + if (result) break; + } + + return std::move(result); } -dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_return_t* kr) +template +dyld_process_info_ptr dyld_process_info_base::makeSuspended(task_t task, const T& allImageInfo, kern_return_t* kr) { pid_t pid; - kern_return_t result = pid_for_task(task, &pid); - if ( result != KERN_SUCCESS ) { - if ( kr != NULL ) - *kr = result; + if ((*kr = pid_for_task(task, &pid))) { return NULL; } + mach_task_basic_info ti; + mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; + if ((*kr = task_info(task, MACH_TASK_BASIC_INFO, (task_info_t)&ti, &count))) { + return nullptr; + } + + // The task is not suspended, exit + if (ti.suspend_count == 0) { + return nullptr; + } + __block unsigned imageCount = 0; // main executable and dyld __block uint64_t mainExecutableAddress = 0; __block uint64_t dyldAddress = 0; @@ -283,14 +407,14 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ vm_region_basic_info_data_64_t info; mach_port_t objectName; unsigned int infoCount = VM_REGION_BASIC_INFO_COUNT_64; - result = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO, - (vm_region_info_t)&info, &infoCount, &objectName); - if ( result != KERN_SUCCESS ) + if (kern_return_t r = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO, + (vm_region_info_t)&info, &infoCount, &objectName)) { break; + } if ( info.protection != (VM_PROT_READ|VM_PROT_EXECUTE) ) continue; // read start of vm region to verify it is a mach header - withRemoteObject(task, address, NULL, ^(mach_header_64 mhBuffer){ + withRemoteObject(task, address, false, 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 @@ -324,8 +448,7 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ + sizeof(SegmentInfo)*imageCount*5 + imageCount*PATH_MAX; void* storage = malloc(allocationSize); - dyld_process_info_base* obj = new (storage) dyld_process_info_base(imageCount, allocationSize); // placement new() - + auto obj = dyld_process_info_ptr(new (storage) dyld_process_info_base(imageCount, allocationSize), deleter); // fill in base info dyld_process_cache_info* cacheInfo = obj->cacheInfo(); bzero(cacheInfo->cacheUUID, 16); @@ -341,24 +464,32 @@ 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, dyldPath) ) { - if ( kr != NULL ) - *kr = r; - free(obj); - return NULL; + if ((*kr = obj->addDyldImage(task, dyldAddress, 0, dyldPath))) { + return nullptr; } } // fill in info for each image if ( mainExecutableAddress != 0 ) { - if ( kern_return_t r = obj->addImage(task, false, mainExecutableAddress, 0, mainExecutablePath) ) { - if ( kr != NULL ) - *kr = r; - free(obj); - return NULL; + if ((*kr = obj->addImage(task, false, mainExecutableAddress, 0, mainExecutablePath))) { + return nullptr; } } + if (allImageInfo.infoArrayChangeTimestamp != 0) { + return nullptr; + } + + count = MACH_TASK_BASIC_INFO_COUNT; + if ((*kr = task_info(task, MACH_TASK_BASIC_INFO, (task_info_t)&ti, &count))) { + return nullptr; + } + + // The task is not suspended, exit + if (ti.suspend_count == 0) { + return nullptr; + } + return obj; } @@ -375,15 +506,15 @@ const char* dyld_process_info_base::addString(const char* str, size_t maxlen) const char* dyld_process_info_base::copyPath(task_t task, uint64_t stringAddressInTask, kern_return_t* kr) { __block const char* retval = NULL; - withRemoteBuffer(task, stringAddressInTask, PATH_MAX, true, kr, ^(void *buffer, size_t size) { + withRemoteBuffer(task, stringAddressInTask, PATH_MAX, false, true, kr, ^(void *buffer, size_t size) { retval = addString(static_cast(buffer), size); }); - return retval; } kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThisProcess, uint64_t imageAddress, uint64_t imagePath, const char* imagePathLocal) { + kern_return_t kr = KERN_SUCCESS; _curImage->loadAddress = imageAddress; _curImage->segmentStartIndex = _curSegmentIndex; if ( imagePathLocal != NULL ) { @@ -393,25 +524,17 @@ kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThis _curImage->path = (const char*)imagePath; } else { - kern_return_t kr; _curImage->path = copyPath(task, imagePath, &kr); - if ( kr ) + if ( kr != KERN_SUCCESS) return kr; } if ( sameCacheAsThisProcess && inCache(imageAddress) ) { addInfoFromLoadCommands((mach_header*)imageAddress, imageAddress, 32*1024); } else { - __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, size_t size) { - addInfoFromLoadCommands((mach_header*)buffer, imageAddress, size); - }); - }); - if (kr != KERN_SUCCESS) { + kr = addInfoFromRemoteLoadCommands(task, imageAddress); + if ( kr != KERN_SUCCESS) return kr; - } } _curImage->segmentsCount = _curSegmentIndex - _curImage->segmentStartIndex; _curImage++; @@ -419,6 +542,34 @@ kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThis } +kern_return_t dyld_process_info_base::addInfoFromRemoteLoadCommands(task_t task, uint64_t remoteMH) { + __block kern_return_t kr = KERN_SUCCESS; + __block size_t headerPagesSize = 0; + __block bool done = false; + + //Since the minimum we can reasonably map is a page, map that. + withRemoteBuffer(task, remoteMH, PAGE_SIZE, false, false, &kr, ^(void * buffer, size_t size) { + const mach_header* mh = (const mach_header*)buffer; + headerPagesSize = sizeof(mach_header) + mh->sizeofcmds; + if (headerPagesSize <= PAGE_SIZE) { + addInfoFromLoadCommands(mh, remoteMH, size); + done = true; + } + }); + + //The load commands did not fit in the first page, but now we know the size, so remap and try again + if (!done) { + if (kr != KERN_SUCCESS) { + return kr; + } + withRemoteBuffer(task, remoteMH, headerPagesSize, false, false, &kr, ^(void * buffer, size_t size) { + addInfoFromLoadCommands((mach_header*)buffer, remoteMH, size); + }); + } + + return kr; +} + kern_return_t dyld_process_info_base::addDyldImage(task_t task, uint64_t dyldAddress, uint64_t dyldPathAddress, const char* localPath) { __block kern_return_t kr = KERN_SUCCESS; @@ -429,19 +580,13 @@ kern_return_t dyld_process_info_base::addDyldImage(task_t task, uint64_t dyldAdd } else { _curImage->path = copyPath(task, dyldPathAddress, &kr); - if ( kr ) + if ( kr != KERN_SUCCESS) return kr; } - 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, size_t size) { - addInfoFromLoadCommands((mach_header*)buffer, dyldAddress, size); - }); - }); - if (kr != KERN_SUCCESS) { + kr = addInfoFromRemoteLoadCommands(task, dyldAddress); + if ( kr != KERN_SUCCESS) return kr; - } _curImage->segmentsCount = _curSegmentIndex - _curImage->segmentStartIndex; _curImage++; @@ -514,14 +659,14 @@ void dyld_process_info_base::forEachSegment(uint64_t machHeaderAddress, void (^c for (const ImageInfo* p = _firstImage; p < _curImage; ++p) { if ( p->loadAddress == machHeaderAddress ) { uint64_t slide = 0; - for (int i=0; i < p->segmentsCount; ++i) { + for (uint32_t i=0; i < p->segmentsCount; ++i) { const SegmentInfo* seg = &_firstSegment[p->segmentStartIndex+i]; if ( strcmp(seg->name, "__TEXT") == 0 ) { slide = machHeaderAddress - seg->addr; break; } } - for (int i=0; i < p->segmentsCount; ++i) { + for (uint32_t i=0; i < p->segmentsCount; ++i) { const SegmentInfo* seg = &_firstSegment[p->segmentStartIndex+i]; callback(seg->addr + slide, seg->size, seg->name); } @@ -530,163 +675,47 @@ void dyld_process_info_base::forEachSegment(uint64_t machHeaderAddress, void (^c } } - - - - -// Implementation that works with existing dyld data structures -static dyld_process_info _dyld_process_info_create_inner(task_t task, uint64_t timestamp, kern_return_t* kr) +dyld_process_info _dyld_process_info_create(task_t task, uint64_t timestamp, kern_return_t* kr) { - if ( kr != NULL ) - *kr = KERN_SUCCESS; + __block dyld_process_info result = nullptr; + kern_return_t krSink = KERN_SUCCESS; + if (kr == nullptr) { + kr = &krSink; + } + *kr = KERN_SUCCESS; task_dyld_info_data_t task_dyld_info; mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; if ( kern_return_t r = task_info(task, TASK_DYLD_INFO, (task_info_t)&task_dyld_info, &count) ) { - if ( kr != NULL ) - *kr = r; - return NULL; + *kr = r; + return nullptr; } //The kernel will return MACH_VM_MIN_ADDRESS for an executable that has not had dyld loaded if (task_dyld_info.all_image_info_addr == MACH_VM_MIN_ADDRESS) - return NULL; + return nullptr; if ( task_dyld_info.all_image_info_size > sizeof(dyld_all_image_infos_64) ) - return NULL; - - // read all_image_infos struct - dyld_all_image_infos_64 allImageInfo64; - mach_vm_size_t readSize = task_dyld_info.all_image_info_size; - if ( kern_return_t r = mach_vm_read_overwrite(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, (vm_address_t)&allImageInfo64, &readSize) ) { - if ( kr != NULL ) - *kr = r; - return NULL; - } - if ( allImageInfo64.infoArrayCount == 0 ) { - // could be task was launch suspended or still launching, wait a moment to see - usleep(1000 * 50); // 50ms - if ( kern_return_t r = mach_vm_read_overwrite(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, (vm_address_t)&allImageInfo64, &readSize) ) { - if ( kr != NULL ) - *kr = r; - return NULL; - } - // if infoArrayCount is still zero, then target was most likely launched suspended - if ( allImageInfo64.infoArrayCount == 0 ) - return dyld_process_info_base::makeSuspended(task, kr); - } - - // bail out of dyld is too old - if ( allImageInfo64.version < 15 ) { - if ( kr != NULL ) - *kr = KERN_INVALID_HOST; - return NULL; - } - - // normalize by expanding 32-bit all_image_infos into 64-bit one - uint32_t imageCount = allImageInfo64.infoArrayCount; - size_t imageArraySize = imageCount * sizeof(dyld_image_info_64); - if ( task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { - const dyld_all_image_infos_32* allImageInfo32 = (dyld_all_image_infos_32*)&allImageInfo64; - dyld_all_image_infos_64 info64; - bzero(&info64, sizeof(info64)); - info64.version = allImageInfo32->version; - info64.infoArrayCount = allImageInfo32->infoArrayCount; - info64.infoArray = allImageInfo32->infoArray; - info64.processDetachedFromSharedRegion = allImageInfo32->processDetachedFromSharedRegion; - info64.libSystemInitialized = allImageInfo32->libSystemInitialized; - info64.dyldImageLoadAddress = allImageInfo32->dyldImageLoadAddress; - info64.initialImageCount = allImageInfo32->initialImageCount; - info64.uuidArrayCount = allImageInfo32->uuidArrayCount; - info64.uuidArray = allImageInfo32->uuidArray; - info64.dyldAllImageInfosAddress = allImageInfo32->dyldAllImageInfosAddress; - info64.sharedCacheSlide = allImageInfo32->sharedCacheSlide; - info64.infoArrayChangeTimestamp = allImageInfo32->infoArrayChangeTimestamp; - info64.sharedCacheBaseAddress = allImageInfo32->sharedCacheBaseAddress; - info64.dyldPath = allImageInfo32->dyldPath; - memcpy((void*)(info64.sharedCacheUUID), (void*)(allImageInfo32->sharedCacheUUID), 16); - allImageInfo64 = info64; - imageCount = allImageInfo64.infoArrayCount; - imageArraySize = imageCount * sizeof(dyld_image_info_32); - } - - // don't do any (more) work if target process's dyld timestamp has not changed since previous query - if ( (timestamp != 0) && (timestamp == allImageInfo64.infoArrayChangeTimestamp) ) { - if ( kr != NULL ) - *kr = KERN_SUCCESS; - return NULL; - } - - // For the moment we are going to truncate any image list longer than 8192 because some programs do - // terrible things that corrupt their own image lists and we need to stop clients from crashing - // reading them. We can try to do something more advanced in the future. rdar://27446361 - imageCount = MIN(imageCount, 8192); - - // read image array - if ( allImageInfo64.infoArray == 0 ) { - // dyld is in middle of updating image list, try again - return NULL; - } - dyld_image_info_64 imageArray64[imageCount]; - if ( kern_return_t r = mach_vm_read_overwrite(task, allImageInfo64.infoArray, imageArraySize, (vm_address_t)&imageArray64, &readSize) ) { - // if image array moved, try whole thing again - if ( kr != NULL ) { - *kr = r; - } - return NULL; - } - // normalize by expanding 32-bit image_infos into 64-bit ones - if ( task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { - const dyld_image_info_32* imageArray32 = (dyld_image_info_32*)&imageArray64; - dyld_image_info_64 tempArray[imageCount]; - for (uint32_t i=0; i < imageCount; ++i) { - tempArray[i].imageLoadAddress = imageArray32[i].imageLoadAddress; - tempArray[i].imageFilePath = imageArray32[i].imageFilePath; - tempArray[i].imageFileModDate = imageArray32[i].imageFileModDate; - } - memcpy(imageArray64, tempArray, sizeof(dyld_image_info_64)*imageCount); - } - - // create object based on local copy of all image infos and image array - dyld_process_info result = dyld_process_info_base::make(task, allImageInfo64, imageArray64, kr); - - // verify nothing has changed by re-reading all_image_infos struct and checking timestamp - if ( result != NULL ) { - dyld_all_image_infos_64 allImageInfo64again; - readSize = task_dyld_info.all_image_info_size; - if ( kern_return_t r = mach_vm_read_overwrite(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, (vm_address_t)&allImageInfo64again, &readSize) ) { - if ( kr != NULL ) - *kr = r; - free((void*)result); - return NULL; - } - uint64_t doneTimeStamp = allImageInfo64again.infoArrayChangeTimestamp; - if ( task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { - const dyld_all_image_infos_32* allImageInfo32 = (dyld_all_image_infos_32*)&allImageInfo64again; - doneTimeStamp = allImageInfo32->infoArrayChangeTimestamp; + return nullptr; + + // We use a true shared memory buffer here, that way by making sure that libdyld in both processes + // reads and writes the the timestamp atomically we can make sure we get a coherent view of the + // remote process. + // That also means that we *MUST* directly read the memory, which is why we template the make() call + withRemoteBuffer(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, true, false, kr, ^(void *buffer, size_t size) { + dyld_process_info_ptr base; + if (task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { + const dyld_all_image_infos_32* info = (const dyld_all_image_infos_32*)buffer; + base = dyld_process_info_base::make(task, *info, timestamp, kr); + } else { + const dyld_all_image_infos_64* info = (const dyld_all_image_infos_64*)buffer; + base = dyld_process_info_base::make(task, *info, timestamp, kr); } - if ( allImageInfo64.infoArrayChangeTimestamp != doneTimeStamp ) { - // image list has changed since we started reading it - // throw out what we have and start over - free((void*)result); - result = nullptr; + if (base) { + result = base.release(); } - } - - return result; -} - - -dyld_process_info _dyld_process_info_create(task_t task, uint64_t timestamp, kern_return_t* kr) -{ - // Other process may be loading and unloading as we read its memory, which can cause a read failure - // Retry if something fails - for (int i=0; i < 100; ++i) { - if ( dyld_process_info result = _dyld_process_info_create_inner( task, timestamp, kr) ) - return result; - - } - return NULL; + }); + return result; } void _dyld_process_info_get_state(dyld_process_info info, dyld_process_state_info* stateInfo) @@ -699,16 +728,14 @@ void _dyld_process_info_get_cache(dyld_process_info info, dyld_process_cache_inf *cacheInfo = *info->cacheInfo(); } -void _dyld_process_info_retain(dyld_process_info info) +void _dyld_process_info_retain(dyld_process_info object) { - info->retainCount() += 1; + const_cast(object)->retain(); } -void _dyld_process_info_release(dyld_process_info info) +void _dyld_process_info_release(dyld_process_info object) { - info->retainCount() -= 1; - if ( info->retainCount() == 0 ) - free((void*)info); + const_cast(object)->release(); } void _dyld_process_info_for_each_image(dyld_process_info info, void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)) diff --git a/src/dyld_process_info_internal.h b/src/dyld_process_info_internal.h index cd8c607..ca12c65 100644 --- a/src/dyld_process_info_internal.h +++ b/src/dyld_process_info_internal.h @@ -25,78 +25,81 @@ #ifndef _DYLD_PROCESS_INFO_INTERNAL_H_ #define _DYLD_PROCESS_INFO_INTERNAL_H_ +#define VIS_HIDDEN __attribute__((visibility("hidden"))) #include #include #include #include +#include +#include struct dyld_all_image_infos_32 { - uint32_t version; - uint32_t infoArrayCount; - uint32_t infoArray; - uint32_t notification; - bool processDetachedFromSharedRegion; - bool libSystemInitialized; - uint32_t dyldImageLoadAddress; - uint32_t jitInfo; - uint32_t dyldVersion; - uint32_t errorMessage; - uint32_t terminationFlags; - uint32_t coreSymbolicationShmPage; - uint32_t systemOrderFlag; - uint32_t uuidArrayCount; - uint32_t uuidArray; - uint32_t dyldAllImageInfosAddress; - uint32_t initialImageCount; - uint32_t errorKind; - uint32_t errorClientOfDylibPath; - uint32_t errorTargetDylibPath; - uint32_t errorSymbol; - uint32_t sharedCacheSlide; - uint8_t sharedCacheUUID[16]; - uint32_t sharedCacheBaseAddress; - uint64_t infoArrayChangeTimestamp; - uint32_t dyldPath; - uint32_t notifyMachPorts[8]; - uint32_t reserved[5]; - uint32_t compact_dyld_image_info_addr; - uint32_t compact_dyld_image_info_size; + uint32_t version; + uint32_t infoArrayCount; + std::atomic infoArray; + uint32_t notification; + bool processDetachedFromSharedRegion; + bool libSystemInitialized; + uint32_t dyldImageLoadAddress; + uint32_t jitInfo; + uint32_t dyldVersion; + uint32_t errorMessage; + uint32_t terminationFlags; + uint32_t coreSymbolicationShmPage; + uint32_t systemOrderFlag; + uint32_t uuidArrayCount; + uint32_t uuidArray; + uint32_t dyldAllImageInfosAddress; + uint32_t initialImageCount; + uint32_t errorKind; + uint32_t errorClientOfDylibPath; + uint32_t errorTargetDylibPath; + uint32_t errorSymbol; + uint32_t sharedCacheSlide; + std::array sharedCacheUUID; + uint32_t sharedCacheBaseAddress; + std::atomic infoArrayChangeTimestamp; + uint32_t dyldPath; + 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 { - uint32_t version; - uint32_t infoArrayCount; - uint64_t infoArray; - uint64_t notification; - bool processDetachedFromSharedRegion; - bool libSystemInitialized; - uint32_t paddingToMakeTheSizeCorrectOn32bitAndDoesntAffect64b; // NOT PART OF DYLD_ALL_IMAGE_INFOS! - uint64_t dyldImageLoadAddress; - uint64_t jitInfo; - uint64_t dyldVersion; - uint64_t errorMessage; - uint64_t terminationFlags; - uint64_t coreSymbolicationShmPage; - uint64_t systemOrderFlag; - uint64_t uuidArrayCount; - uint64_t uuidArray; - uint64_t dyldAllImageInfosAddress; - uint64_t initialImageCount; - uint64_t errorKind; - uint64_t errorClientOfDylibPath; - uint64_t errorTargetDylibPath; - uint64_t errorSymbol; - uint64_t sharedCacheSlide; - uint8_t sharedCacheUUID[16]; - uint64_t sharedCacheBaseAddress; - uint64_t infoArrayChangeTimestamp; - uint64_t dyldPath; - uint32_t notifyMachPorts[8]; - uint64_t reserved[9]; - uint64_t compact_dyld_image_info_addr; - uint64_t compact_dyld_image_info_size; + uint32_t version; + uint32_t infoArrayCount; + std::atomic infoArray; + uint64_t notification; + bool processDetachedFromSharedRegion; + bool libSystemInitialized; + uint32_t paddingToMakeTheSizeCorrectOn32bitAndDoesntAffect64b; // NOT PART OF DYLD_ALL_IMAGE_INFOS! + uint64_t dyldImageLoadAddress; + uint64_t jitInfo; + uint64_t dyldVersion; + uint64_t errorMessage; + uint64_t terminationFlags; + uint64_t coreSymbolicationShmPage; + uint64_t systemOrderFlag; + uint64_t uuidArrayCount; + uint64_t uuidArray; + uint64_t dyldAllImageInfosAddress; + uint64_t initialImageCount; + uint64_t errorKind; + uint64_t errorClientOfDylibPath; + uint64_t errorTargetDylibPath; + uint64_t errorSymbol; + uint64_t sharedCacheSlide; + std::array sharedCacheUUID; + uint64_t sharedCacheBaseAddress; + std::atomic infoArrayChangeTimestamp; + uint64_t dyldPath; + 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 { @@ -132,7 +135,46 @@ struct dyld_process_info_notify_header { uint64_t timestamp; }; +struct VIS_HIDDEN RemoteBuffer { + RemoteBuffer(); + RemoteBuffer(task_t task, mach_vm_address_t remote_address, size_t remote_size, bool shared, bool allow_truncation); + ~RemoteBuffer(); + RemoteBuffer& operator=(RemoteBuffer&& other) { + _localAddress = other._localAddress; + _size = other._size; + _kr = other._kr; + other._localAddress = 0; + other._size = 0; + other._kr = KERN_SUCCESS; + return *this; + } + RemoteBuffer(const RemoteBuffer &) = delete; + RemoteBuffer& operator=(const RemoteBuffer &) = delete; + void *getLocalAddress(); + kern_return_t getKernelReturn(); + size_t getSize(); +private: + bool map(task_t task, mach_vm_address_t remote_address, bool shared); + mach_vm_address_t _localAddress; + vm_size_t _size; + kern_return_t _kr; +}; +// only called during libdyld set up +void setNotifyMonitoringDyldMain(void (*func)()) VIS_HIDDEN; +void setNotifyMonitoringDyld(void (*func)(bool unloading, unsigned imageCount, + const struct mach_header* loadAddresses[], + const char* imagePaths[])) VIS_HIDDEN; + +void withRemoteBuffer(task_t task, mach_vm_address_t remote_address, size_t remote_size, bool shared, bool allow_truncation, kern_return_t *kr, void (^block)(void *buffer, size_t size)) __attribute__((visibility("hidden"))); + +template +VIS_HIDDEN void withRemoteObject(task_t task, mach_vm_address_t remote_address, bool shared, kern_return_t *kr, void (^block)(T t)) +{ + withRemoteBuffer(task, remote_address, sizeof(T), shared, false, kr, ^(void *buffer, size_t size) { + block(*reinterpret_cast(buffer)); + }); +} #endif // _DYLD_PROCESS_INFO_INTERNAL_H_ diff --git a/src/dyld_process_info_notify.cpp b/src/dyld_process_info_notify.cpp index ec9bce4..571e677 100644 --- a/src/dyld_process_info_notify.cpp +++ b/src/dyld_process_info_notify.cpp @@ -29,312 +29,352 @@ #include #include #include +#include + #include "dyld_process_info.h" #include "dyld_process_info_internal.h" #include "dyld_images.h" #include "dyld_priv.h" -#include "LaunchCache.h" #include "Loading.h" #include "AllImages.h" +extern "C" int _dyld_func_lookup(const char* name, void** address); typedef void (^Notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path); typedef void (^NotifyExit)(); typedef void (^NotifyMain)(); - // // Object used for monitoring another processes dyld loads // struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base { - static dyld_process_info_notify_base* make(task_t task, dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, kern_return_t* kr); - ~dyld_process_info_notify_base(); - - bool incRetainCount() const; - bool decRetainCount() const; + dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task, kern_return_t* kr); + ~dyld_process_info_notify_base(); + bool enabled() const; + void retain(); + void release(); 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); } + static void operator delete(void* p) { 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(); - - mutable int32_t _retainCount; - dispatch_queue_t _queue; - Notify _notify; - NotifyExit _notifyExit; - mutable NotifyMain _notifyMain; - task_t _targetTask; - dispatch_source_t _machSource; - uint64_t _portAddressInTarget; - mach_port_t _sendPortInTarget; // target is process being watched for image loading/unloading - mach_port_t _receivePortInMonitor; // monitor is process being notified of image loading/unloading + void handleEvent(); + void teardown(); + void replyToMonitoredProcess(mach_msg_header_t& header); + + RemoteBuffer _remoteAllImageInfoBuffer; + uint32_t* _notifyMachPorts; + uint32_t _notifySlot; + mutable std::atomic _retainCount; + dispatch_queue_t _queue; + Notify _notify; + NotifyExit _notifyExit; + mutable NotifyMain _notifyMain; + task_t _targetTask; + dispatch_source_t _machSource; + mach_port_t _sendPortInTarget; // target is process being watched for image loading/unloading + mach_port_t _receivePortInMonitor; // monitor is process being notified of image loading/unloading + std::atomic _disabled; }; -dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task) - : _retainCount(1), _queue(queue), _notify(notify), _notifyExit(notifyExit), _notifyMain(NULL), _targetTask(task), _machSource(NULL), _portAddressInTarget(0), _sendPortInTarget(0), _receivePortInMonitor(0) +dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, + task_t task, kern_return_t* kr) : + _notifyMachPorts(nullptr), _notifySlot(0), _retainCount(1), _queue(queue), _notify(notify), _notifyExit(notifyExit), + _notifyMain(nullptr), _targetTask(task), _machSource(nullptr), _sendPortInTarget(0), _receivePortInMonitor(0), + _disabled(false) { + assert(_disabled == false); dispatch_retain(_queue); -} - -dyld_process_info_notify_base::~dyld_process_info_notify_base() -{ - if ( _machSource ) { - dispatch_source_cancel(_machSource); - dispatch_release(_machSource); - _machSource = NULL; - } - if ( _portAddressInTarget ) { - unpokeSendPortInTarget(); - _portAddressInTarget = 0; - } - if ( _sendPortInTarget ) { - _sendPortInTarget = 0; - } - dispatch_release(_queue); - if ( _receivePortInMonitor != 0 ) { - mach_port_deallocate(mach_task_self(), _receivePortInMonitor); - _receivePortInMonitor = 0; - } -} - -bool dyld_process_info_notify_base::incRetainCount() const -{ - int32_t newCount = OSAtomicIncrement32(&_retainCount); - return ( newCount == 1 ); -} - -bool dyld_process_info_notify_base::decRetainCount() const -{ - int32_t newCount = OSAtomicDecrement32(&_retainCount); - return ( newCount == 0 ); -} + // Allocate a port to listen on in this monitoring task + mach_port_options_t options = { .flags = MPO_IMPORTANCE_RECEIVER | MPO_CONTEXT_AS_GUARD | MPO_STRICT, + .mpl = { MACH_PORT_QLIMIT_DEFAULT }}; + if ((*kr = mach_port_construct(mach_task_self(), &options, (mach_port_context_t)this, &_receivePortInMonitor))) { + teardown(); + return; + } + if (_targetTask == mach_task_self()) { + _sendPortInTarget = _receivePortInMonitor; + (void)mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND); + } else { + // Insert a deadname right into the port to trigger notifications + kern_return_t r = KERN_NAME_EXISTS; + while (r == KERN_NAME_EXISTS) { + _sendPortInTarget = MACH_PORT_NULL; + //FIXME file radar + r = mach_port_allocate(_targetTask, MACH_PORT_RIGHT_DEAD_NAME, &_sendPortInTarget); + if (r != KERN_SUCCESS) { + *kr = r; + return; + } + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + r = mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND); + } + if (r != KERN_SUCCESS) { + *kr = r; + return; + } + // Notify us if the target dies + mach_port_t previous = MACH_PORT_NULL; + if ((*kr = mach_port_request_notification(_targetTask, _sendPortInTarget, MACH_NOTIFY_DEAD_NAME, 0, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous))) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this); + teardown(); + return; + } + // This is a new port, if there is a previous notifier attached then something is wrong... abort. + if (previous != MACH_PORT_NULL) { + (void)mach_port_deallocate(mach_task_self(), previous); + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this); + teardown(); + return; + } + } -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) -{ - dyld_process_info_notify_base* obj = new dyld_process_info_notify_base(queue, notify, notifyExit, task); + // Setup the event handler for the port + _machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _receivePortInMonitor, 0, _queue); + if (_machSource == nullptr) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this); + teardown(); + return; + } + dispatch_source_set_event_handler(_machSource, ^{ + handleEvent(); + }); + dispatch_source_set_cancel_handler(_machSource, ^{ + if ( _receivePortInMonitor != 0 ) { + (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor, 0, (mach_port_context_t)this); + _receivePortInMonitor = 0; + } + }); + dispatch_activate(_machSource); - if ( kern_return_t r = obj->makePorts() ) { - if ( kr != NULL ) - *kr = r; - goto fail; - } + // get location on all_image_infos in the target task + task_dyld_info_data_t taskDyldInfo; + mach_msg_type_number_t taskDyldInfoCount = TASK_DYLD_INFO_COUNT; + if ((*kr = task_info(_targetTask, TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &taskDyldInfoCount))) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + teardown(); + return; + } + // Poke the portname of our port into the target task + _remoteAllImageInfoBuffer = RemoteBuffer(_targetTask, taskDyldInfo.all_image_info_addr, taskDyldInfo.all_image_info_size, true, false); + *kr = _remoteAllImageInfoBuffer.getKernelReturn(); + if (*kr) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + teardown(); + return; + } - obj->setMachSourceOnQueue(); + static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits"); + if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { + _notifyMachPorts = (uint32_t *)((uint8_t *)_remoteAllImageInfoBuffer.getLocalAddress() + offsetof(dyld_all_image_infos_32,notifyMachPorts)); + } else { + _notifyMachPorts = (uint32_t *)((uint8_t *)_remoteAllImageInfoBuffer.getLocalAddress() + offsetof(dyld_all_image_infos_64,notifyMachPorts)); + } - if ( kern_return_t r = obj->pokeSendPortIntoTarget() ) { - if ( kr != NULL ) - *kr = r; - goto fail; - } +#if 0 + //If all the slots are filled we will sleep and retry a few times before giving up + for (uint32_t i=0; i<10; ++i) { + for (_notifySlot=0; _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++_notifySlot) { + if (OSAtomicCompareAndSwap32(0, _sendPortInTarget, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) { + break; + } + } + if (_notifySlot == DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) { + // all the slots are filled, sleep and try again + usleep(1000 * 50); // 50ms + } else { + // if _notifySlot is set we are done + break; + } + } +#else + for (_notifySlot=0; _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++_notifySlot) { + if (OSAtomicCompareAndSwap32(0, _sendPortInTarget, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) { + break; + } + } +#endif - if ( kr != NULL ) - *kr = KERN_SUCCESS; - return obj; + if (_notifySlot == DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + teardown(); + *kr = KERN_UREFS_OVERFLOW; + return; + } -fail: - delete obj; - return NULL; + *kr = KERN_SUCCESS; } - -kern_return_t dyld_process_info_notify_base::makePorts() -{ - // Allocate a port to listen on in this monitoring task - if ( kern_return_t r = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &_receivePortInMonitor) ) - return r; - - // Add send rights for replying - if ( kern_return_t r = mach_port_insert_right(mach_task_self(), _receivePortInMonitor, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND) ) - return r; - - // Allocate a name in the target. We need a new name to add send rights to - if ( kern_return_t r = mach_port_allocate(_targetTask, MACH_PORT_RIGHT_DEAD_NAME, &_sendPortInTarget) ) - return r; - - // Deallocate the dead name - if ( kern_return_t r = mach_port_mod_refs(_targetTask, _sendPortInTarget, MACH_PORT_RIGHT_DEAD_NAME, -1) ) - return r; - - // Make the dead name a send right to our listening port - if ( kern_return_t r = mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND) ) - return r; - - // Notify us if the target dies - mach_port_t previous = MACH_PORT_NULL; - if ( kern_return_t r = mach_port_request_notification(_targetTask, _sendPortInTarget, MACH_NOTIFY_DEAD_NAME, 0, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous)) - return r; - - //fprintf(stderr, "_sendPortInTarget=%d, _receivePortInMonitor=%d\n", _sendPortInTarget, _receivePortInMonitor); - return KERN_SUCCESS; +dyld_process_info_notify_base::~dyld_process_info_notify_base() { + if (!_disabled) { + fprintf(stderr, "dyld: ~dyld_process_info_notify_base called while still enabled\n"); + } + dispatch_release(_queue); } - - -void dyld_process_info_notify_base::setMachSourceOnQueue() -{ - NotifyExit exitHandler = _notifyExit; - _machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _receivePortInMonitor, 0, _queue); - dispatch_source_set_event_handler(_machSource, ^{ - // This event handler block has an implicit reference to "this" - // if incrementing the count goes to one, that means the object may have already been destroyed - if ( incRetainCount() ) - return; - uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE]; - mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer; - - kern_return_t r = mach_msg(h, MACH_RCV_MSG, 0, sizeof(messageBuffer), _receivePortInMonitor, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); - if ( r == KERN_SUCCESS ) { - //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); - if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_LOAD_ID || h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID ) { - // run notifier block for each [un]load image - const dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)messageBuffer; - const dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&messageBuffer[header->imagesOffset]; - const char* const stringPool = (char*)&messageBuffer[header->stringsOffset]; - for (unsigned i=0; i < header->imageCount; ++i) { - bool isUnload = (h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID); - _notify(isUnload, header->timestamp, entries[i].loadAddress, entries[i].uuid, stringPool + entries[i].pathStringOffset); - } - // reply to dyld, so it can continue - mach_msg_header_t replyHeader; - replyHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND); - replyHeader.msgh_id = 0; - replyHeader.msgh_local_port = MACH_PORT_NULL; - replyHeader.msgh_remote_port = h->msgh_remote_port; - replyHeader.msgh_reserved = 0; - replyHeader.msgh_size = sizeof(replyHeader); - mach_msg(&replyHeader, MACH_SEND_MSG | MACH_SEND_TIMEOUT, replyHeader.msgh_size, 0, MACH_PORT_NULL, 100, MACH_PORT_NULL); - } - else if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_MAIN_ID ) { - if ( _notifyMain != NULL ) { - _notifyMain(); - } - // reply to dyld, so it can continue - mach_msg_header_t replyHeader; - replyHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND); - replyHeader.msgh_id = 0; - replyHeader.msgh_local_port = MACH_PORT_NULL; - replyHeader.msgh_remote_port = h->msgh_remote_port; - replyHeader.msgh_reserved = 0; - replyHeader.msgh_size = sizeof(replyHeader); - mach_msg(&replyHeader, MACH_SEND_MSG | MACH_SEND_TIMEOUT, replyHeader.msgh_size, 0, MACH_PORT_NULL, 100, MACH_PORT_NULL); - } - else if ( h->msgh_id == MACH_NOTIFY_PORT_DELETED ) { - mach_port_t deadPort = ((mach_port_deleted_notification_t *)h)->not_port; - //fprintf(stderr, "received message id=MACH_NOTIFY_PORT_DELETED, size=%d, deadPort=%d\n", h->msgh_size, deadPort); - if ( deadPort == _sendPortInTarget ) { - // target process died. Clean up ports - _sendPortInTarget = 0; - mach_port_deallocate(mach_task_self(), _receivePortInMonitor); - _receivePortInMonitor = 0; - _portAddressInTarget = 0; - // notify that target is gone - exitHandler(); - } - } - else { - fprintf(stderr, "received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); - } +void dyld_process_info_notify_base::teardown() { + if (!_disabled) { + _disabled = true; + // The connection to the target is dead. Clean up ports + if ( _remoteAllImageInfoBuffer.getLocalAddress() != 0 && _notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT) { + mach_port_t extractedPort = MACH_PORT_NULL; + mach_msg_type_name_t extractedPortType; + kern_return_t kr = mach_port_extract_right(_targetTask, _sendPortInTarget, MACH_MSG_TYPE_COPY_SEND, &extractedPort, &extractedPortType); + if (kr == KERN_SUCCESS) { + if (extractedPort == _receivePortInMonitor) { + if (OSAtomicCompareAndSwap32(_sendPortInTarget, 0, (volatile int32_t*)&_notifyMachPorts[_notifySlot])) { + (void)mach_port_deallocate(_targetTask, _sendPortInTarget); + } + } + (void)mach_port_deallocate(mach_task_self(), extractedPort); + } } - if ( decRetainCount() ) - delete this; - }); - dispatch_resume(_machSource); + _sendPortInTarget = 0; + if ( _machSource ) { + dispatch_source_cancel(_machSource); + dispatch_release(_machSource); + _machSource = NULL; + } + if (_notifyExit) { + dispatch_async(_queue, ^{ + _notifyExit(); + }); + } + } } - -kern_return_t dyld_process_info_notify_base::pokeSendPortIntoTarget() +bool dyld_process_info_notify_base::enabled() const { - // get location on all_image_infos in target task - task_dyld_info_data_t taskDyldInfo; - mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; - kern_return_t r = task_info(_targetTask, TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &count); - if ( r ) - return r; - - // remap the page containing all_image_infos into this process r/w - mach_vm_address_t mappedAddress = 0; - mach_vm_size_t mappedSize = taskDyldInfo.all_image_info_size; - vm_prot_t curProt = VM_PROT_NONE; - vm_prot_t maxProt = VM_PROT_NONE; - r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, - _targetTask, taskDyldInfo.all_image_info_addr, false, &curProt, &maxProt, VM_INHERIT_NONE); - if ( r ) - return r; - if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) ) - return KERN_PROTECTION_FAILURE; - - // atomically set port into all_image_info_struct - static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits"); - - mach_vm_address_t mappedAddressToPokePort = 0; - if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) - mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_32,notifyMachPorts); - else - mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_64,notifyMachPorts); - - // use first available slot - bool slotFound = false; - for (int slotIndex=0; slotIndex < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slotIndex) { - if ( OSAtomicCompareAndSwap32Barrier(0, _sendPortInTarget, (volatile int32_t*)mappedAddressToPokePort) ) { - slotFound = true; - break; - } - mappedAddressToPokePort += sizeof(uint32_t); - } - if ( !slotFound ) { - mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); - return KERN_UREFS_OVERFLOW; - } - _portAddressInTarget = taskDyldInfo.all_image_info_addr + mappedAddressToPokePort - mappedAddress; - //fprintf(stderr, "poked port %d into target at address 0x%llX\n", _sendPortInTarget, _portAddressInTarget); - mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); - return r; + return !_disabled; } +void dyld_process_info_notify_base::retain() +{ + _retainCount++; +} - -kern_return_t dyld_process_info_notify_base::unpokeSendPortInTarget() +void dyld_process_info_notify_base::release() { - // remap the page containing all_image_infos into this process r/w - mach_vm_address_t mappedAddress = 0; - mach_vm_size_t mappedSize = sizeof(mach_port_t); - vm_prot_t curProt = VM_PROT_NONE; - vm_prot_t maxProt = VM_PROT_NONE; - kern_return_t r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, - _targetTask, _portAddressInTarget, false, &curProt, &maxProt, VM_INHERIT_NONE); - if ( r ) - return r; - if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) ) - return KERN_PROTECTION_FAILURE; - - OSAtomicCompareAndSwap32Barrier(_sendPortInTarget, 0, (volatile int32_t*)mappedAddress); - - //fprintf(stderr, "cleared port %d from target\n", _sendPortInTarget); - mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize); - return r; + uint32_t newCount = --_retainCount; + + if ( newCount == 0 ) { + teardown(); + } + dispatch_async(_queue, ^{ + delete this; + }); } +void dyld_process_info_notify_base::replyToMonitoredProcess(mach_msg_header_t& header) { + mach_msg_header_t replyHeader; + replyHeader.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSGH_BITS_REMOTE(header.msgh_bits), 0, 0, 0); + replyHeader.msgh_id = 0; + replyHeader.msgh_local_port = MACH_PORT_NULL; + replyHeader.msgh_remote_port = header.msgh_remote_port; + replyHeader.msgh_reserved = 0; + replyHeader.msgh_size = sizeof(replyHeader); + kern_return_t r = mach_msg(&replyHeader, MACH_SEND_MSG, replyHeader.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); + if (r == KERN_SUCCESS) { + header.msgh_remote_port = MACH_PORT_NULL; + } else { + teardown(); + } +} +void dyld_process_info_notify_base::handleEvent() { + // References object may still exist even after the ports are dead. Disable event dispatching + // if the ports have been torn down. + if (_disabled) { + return; + } + // This event handler block has an implicit reference to "this" + // if incrementing the count goes to one, that means the object may have already been destroyed + uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE] = {}; + mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer; + + kern_return_t r = mach_msg(h, MACH_RCV_MSG | MACH_RCV_VOUCHER| MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0), 0, sizeof(messageBuffer)-sizeof(mach_msg_audit_trailer_t), _receivePortInMonitor, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if ( r == KERN_SUCCESS && !(h->msgh_bits & MACH_MSGH_BITS_COMPLEX)) { + //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); + + if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_LOAD_ID || h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID ) { + // run notifier block for each [un]load image + const dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)messageBuffer; + if (sizeof(*header) <= h->msgh_size + && header->imagesOffset <= h->msgh_size + && header->stringsOffset <= h->msgh_size + && (header->imageCount * sizeof(dyld_process_info_image_entry)) <= (h->msgh_size - header->imagesOffset)) { + const dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&messageBuffer[header->imagesOffset]; + const char* const stringPool = (char*)&messageBuffer[header->stringsOffset]; + for (unsigned i=0; i < header->imageCount; ++i) { + bool isUnload = (h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID); + if (entries[i].pathStringOffset <= h->msgh_size - header->stringsOffset) { + //fprintf(stderr, "Notifying about: %s\n", stringPool + entries[i].pathStringOffset); + _notify(isUnload, header->timestamp, entries[i].loadAddress, entries[i].uuid, stringPool + entries[i].pathStringOffset); + } else { + teardown(); + break; + } + } + // reply to dyld, so it can continue + replyToMonitoredProcess(*h); + } else { + teardown(); + } + } + else if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_MAIN_ID ) { + if (h->msgh_size != sizeof(mach_msg_header_t)) { + teardown(); + } else if ( _notifyMain != NULL ) { + _notifyMain(); + } + replyToMonitoredProcess(*h); + } else if ( h->msgh_id == MACH_NOTIFY_PORT_DELETED ) { + mach_port_t deadPort = ((mach_port_deleted_notification_t *)h)->not_port; + // Validate this notification came from the kernel + const mach_msg_audit_trailer_t *audit_tlr = (mach_msg_audit_trailer_t *)((uint8_t *)h + round_msg(h->msgh_size)); + if (audit_tlr->msgh_trailer_type == MACH_MSG_TRAILER_FORMAT_0 + && audit_tlr->msgh_trailer_size >= sizeof(mach_msg_audit_trailer_t) + // We cannot link to libbsm, so we are hardcoding the audit token offset (5) + // And the value the represents the kernel (0) + && audit_tlr->msgh_audit.val[5] == 0 + && deadPort == _sendPortInTarget ) { + teardown(); + } + } + else { + fprintf(stderr, "dyld: received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size); + } + } + mach_msg_destroy(h); +} dyld_process_info_notify _dyld_process_info_notify(task_t task, dispatch_queue_t queue, void (^notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path), void (^notifyExit)(), kern_return_t* kr) { - return dyld_process_info_notify_base::make(task, queue, notify, notifyExit, kr); + kern_return_t krSink = KERN_SUCCESS; + if (kr == nullptr) { + kr = &krSink; + } + *kr = KERN_SUCCESS; + + dyld_process_info_notify result = new dyld_process_info_notify_base(queue, notify, notifyExit, task, kr); + if (result->enabled()) + return result; + const_cast(result)->release(); + return nullptr; } void _dyld_process_info_notify_main(dyld_process_info_notify object, void (^notifyMain)()) @@ -344,181 +384,60 @@ void _dyld_process_info_notify_main(dyld_process_info_notify object, void (^noti void _dyld_process_info_notify_retain(dyld_process_info_notify object) { - object->incRetainCount(); + const_cast(object)->retain(); } void _dyld_process_info_notify_release(dyld_process_info_notify object) { - // Note if _machSource is currently handling a message, the retain count will not be zero - // and object will instead be deleted when handling is done. - if ( object->decRetainCount() ) - delete object; + const_cast(object)->release(); } +static void (*sNotifyMonitoringDyldMain)() = nullptr; +static void (*sNotifyMonitoringDyld)(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[], + const char* imagePaths[]) = nullptr; - - - - - -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) +void setNotifyMonitoringDyldMain(void (*func)()) { - if ( sZombieNotifiers[portSlot] ) - return; + sNotifyMonitoringDyldMain = func; +} - 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 setNotifyMonitoringDyld(void (*func)(bool unloading, unsigned imageCount, + const struct mach_header* loadAddresses[], + const char* imagePaths[])) +{ + sNotifyMonitoringDyld = func; } +namespace dyld3 { + 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; - } - } - } + assert(sNotifyMonitoringDyldMain != nullptr); + sNotifyMonitoringDyldMain(); } -void AllImages::notifyMonitorLoads(const launch_cache::DynArray& newImages) +void AllImages::notifyMonitorLoads(const Array& 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; - } + assert(sNotifyMonitoringDyld != nullptr); + const struct mach_header* loadAddresses[newImages.count()]; + const char* loadPaths[newImages.count()]; + for(uint32_t i = 0; ipath(); } + sNotifyMonitoringDyld(false, (unsigned)newImages.count(), loadAddresses, loadPaths); } -void AllImages::notifyMonitorUnloads(const launch_cache::DynArray& unloadingImages) +void AllImages::notifyMonitorUnloads(const Array& 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; - } + assert(sNotifyMonitoringDyld != nullptr); + const struct mach_header* loadAddresses[unloadingImages.count()]; + const char* loadPaths[unloadingImages.count()]; + for(uint32_t i = 0; ipath(); } + sNotifyMonitoringDyld(true, (unsigned)unloadingImages.count(), loadAddresses, loadPaths); } } // namespace dyld3 diff --git a/src/dyld_usage.cpp b/src/dyld_usage.cpp new file mode 100644 index 0000000..e8d0194 --- /dev/null +++ b/src/dyld_usage.cpp @@ -0,0 +1,838 @@ +/* + * Copyright (c) 2017 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights + * Reserved. 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 1.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.apple.com/publicsource 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 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 "Tracing.h" + +#define DBG_FUNC_ALL (DBG_FUNC_START | DBG_FUNC_END) + + +/* + * MAXCOLS controls when extra data kicks in. + * MAX_WIDE_MODE_COLS controls -w mode to get even wider data in path. + */ +#define MAXCOLS (132) +#define MAXWIDTH (MAXCOLS + 64) + +unsigned int columns = 0; +ktrace_session_t s; +bool wideflag = false; +bool RAW_flag = false; +bool JSON_flag = false; +bool JSON_Tracing_flag = false; +dispatch_source_t sigwinch_source; +static void +exit_usage(void) +{ + fprintf(stderr, "Usage: dyld_usage [-e] [-f mode] [-t seconds] [-R rawfile [-S start_time] [-E end_time]] [pid | cmd [pid | cmd] ...]\n"); + fprintf(stderr, " -e exclude the specified list of pids from the sample\n"); + fprintf(stderr, " and exclude dyld_usage by default\n"); + fprintf(stderr, " -t specifies timeout in seconds (for use in automated tools)\n"); + fprintf(stderr, " -R specifies a raw trace file to process\n"); + fprintf(stderr, " pid selects process(s) to sample\n"); + fprintf(stderr, " cmd selects process(s) matching command string to sample\n"); + fprintf(stderr, "By default (no options) the following processes are excluded from the output:\n"); + fprintf(stderr, "dyld_usage, Terminal, telnetd, sshd, rlogind, tcsh, csh, sh\n\n"); + + exit(1); +} + +static void +get_screenwidth(void) +{ + struct winsize size; + + columns = MAXCOLS; + + if (isatty(STDOUT_FILENO)) { + if (ioctl(1, TIOCGWINSZ, &size) != -1) { + columns = size.ws_col; + + if (columns > MAXWIDTH) + columns = MAXWIDTH; + } + } +} + +std::map gActiveStringIDs; +std::map gActiveStrings; + +const std::string& stringForID(uint64_t id) { + static std::string emptyString = ""; + auto i = gActiveStrings.find(id); + if (i == gActiveStrings.end()) + return emptyString; + return i->second; +} + +static uint64_t +mach_to_nano(uint64_t mach) +{ + uint64_t nanoseconds = 0; + assert(ktrace_convert_timestamp_to_nanoseconds(s, mach, &nanoseconds) == 0); + return nanoseconds; +} + +struct output_renderer { + output_renderer(ktrace_session_t S, ktrace_event_t E) : + _commandName(ktrace_get_execname_for_thread(s, E->threadid)), + _threadid(E->threadid), _pid(ktrace_get_pid_for_thread(s, E->threadid)) {} + void recordEvent(ktrace_event_t event) { + uint32_t code = event->debugid & KDBG_EVENTID_MASK; + if (event->debugid & DBG_FUNC_START) { + switch(code) { + case DBG_DYLD_TIMING_DLOPEN: enqueueEvent(event, true); break; + case DBG_DYLD_TIMING_LAUNCH_EXECUTABLE: enqueueEvent(event, true); break; + case DBG_DYLD_TIMING_DLSYM: enqueueEvent(event, true); break; + case DBG_DYLD_TIMING_STATIC_INITIALIZER: enqueueEvent(event, false); break; + case DBG_DYLD_TIMING_MAP_IMAGE: enqueueEvent(event, false); break; + case DBG_DYLD_TIMING_APPLY_FIXUPS: enqueueEvent(event, false); break; + case DBG_DYLD_TIMING_ATTACH_CODESIGNATURE: enqueueEvent(event, false); break; + case DBG_DYLD_TIMING_BUILD_CLOSURE: enqueueEvent(event, false); break; + case DBG_DYLD_TIMING_DLADDR: enqueueEvent(event, true); break; + case DBG_DYLD_TIMING_DLCLOSE: enqueueEvent(event, true); break; + case DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE: enqueueEvent(event, false); break; + case DBG_DYLD_TIMING_FUNC_FOR_REMOVE_IMAGE: enqueueEvent(event, false); break; + case DBG_DYLD_TIMING_OBJC_INIT: enqueueEvent(event, false); break; + case DBG_DYLD_TIMING_OBJC_MAP: enqueueEvent(event, false); break; + } + } else { + switch(code) { + case DBG_DYLD_TIMING_DLOPEN: dequeueEvent(event, [&](dlopen* endEvent){ + endEvent->result = event->arg2; + }); break; + case DBG_DYLD_TIMING_LAUNCH_EXECUTABLE: dequeueEvent(event, [&](app_launch* endEvent){ + endEvent->launchMode = event->arg4; + }); break; + case DBG_DYLD_TIMING_DLSYM: dequeueEvent(event, [&](dlsym* endEvent){ + endEvent->result = event->arg2; + }); break; + case DBG_DYLD_TIMING_STATIC_INITIALIZER: dequeueEvent(event, [&](static_init* endEvent){}); break; + case DBG_DYLD_TIMING_MAP_IMAGE: dequeueEvent(event, [&](map_image* endEvent){ + endEvent->result = event->arg2; + }); break; + case DBG_DYLD_TIMING_APPLY_FIXUPS: dequeueEvent(event, [&](apply_fixups* endEvent){}); break; + case DBG_DYLD_TIMING_ATTACH_CODESIGNATURE: dequeueEvent(event, [&](attach_signature* endEvent){}); break; + case DBG_DYLD_TIMING_BUILD_CLOSURE: dequeueEvent(event, [&](build_closure* endEvent){}); break; + case DBG_DYLD_TIMING_DLCLOSE: dequeueEvent(event, [&](dlclose* endEvent){ + endEvent->result = (int)event->arg2; + }); break; + case DBG_DYLD_TIMING_DLADDR: dequeueEvent(event, [&](dladdr* endEvent){ + endEvent->result = (int)event->arg2; + endEvent->imageAddress = (int)event->arg3; + endEvent->symbolAddress = (int)event->arg4; + }); break; + case DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE: dequeueEvent(event, [&](add_image_callback* endEvent){}); break; + case DBG_DYLD_TIMING_FUNC_FOR_REMOVE_IMAGE: dequeueEvent(event, [&](remove_image_callback* endEvent){}); break; + case DBG_DYLD_TIMING_OBJC_INIT: dequeueEvent(event, [&](objc_image_init* endEvent){}); break; + case DBG_DYLD_TIMING_OBJC_MAP: dequeueEvent(event, [&](objc_images_map* endEvent){}); break; + } + } + } + + bool empty() { return !_currentRootEvent && _eventStack.empty() && _rootEvents.empty(); } +private: + template + void enqueueEvent(ktrace_event_t event, bool rootEvent) { + auto sharedEvent = std::make_shared(event); + if (!_currentRootEvent) { + if (!rootEvent) return; + assert(_eventStack.empty()); + _currentRootEvent = sharedEvent; + } else { + sharedEvent->setDepth(_eventStack.size()); + _eventStack.back()->addChild(sharedEvent); + } + _eventStack.push_back(sharedEvent); + } + + template + void dequeueEvent(ktrace_event_t event, std::function lambda) { + if (_eventStack.empty()) return; + auto currentEvent = dynamic_cast(_eventStack.back().get()); + currentEvent->setEndEvent(event); + lambda(currentEvent); + _eventStack.pop_back(); + if (_currentRootEvent && _eventStack.empty()) { + output(_currentRootEvent); + _currentRootEvent = nullptr; + } + } + + struct event_pair { + event_pair(ktrace_event_t E) : _startTime(E->timestamp), _walltime(E->walltime), _threadid(E->threadid), _depth(0), + _eventCode(KDBG_EXTRACT_CODE(E->debugid)) {}; + virtual ~event_pair(){} + std::vector>& children() { return _children; } + void setDepth(uint64_t D) { _depth = D; } + uint64_t depth() { return _depth; } + struct timeval walltime() { return _walltime; }; + uint64_t startTimestamp() { return _startTime; }; + uint64_t endTimestamp() { return _endTime; } + unsigned long threadid() { return _threadid; } + void addChild(std::shared_ptr child) {_children.push_back(child);} + void setEndEvent(ktrace_event_t E) { _endTime = E->timestamp; } + uint16_t eventCode() const { return _eventCode; } + private: + std::vector> _children; + uint64_t _startTime; + uint64_t _endTime; + uint64_t _depth; + unsigned long _threadid; + uint16_t _eventCode; + struct timeval _walltime; + }; + + time_t _lastTimeWallSeconds = -1; + std::string _timestampStr; + std::string _commandName; + pid_t _pid; + unsigned long _threadid; + + std::string timestamp(std::shared_ptr event, bool extended) { + std::ostringstream result; + struct timeval now_walltime = event->walltime(); + assert(now_walltime.tv_sec || now_walltime.tv_usec); + + /* try and reuse the timestamp string */ + if (_lastTimeWallSeconds != now_walltime.tv_sec) { + char timestamp[32]; + (void)strftime(timestamp, sizeof (timestamp), "%H:%M:%S", localtime(&now_walltime.tv_sec)); + _lastTimeWallSeconds = now_walltime.tv_sec; + _timestampStr = timestamp; + } + result << _timestampStr; + + if (extended) { + result << "." << std::setw(6) << std::setfill(' ') << std::to_string(now_walltime.tv_usec); + } + return result.str(); + } + + std::string process(std::shared_ptr event, bool extended) { + if (extended) { + std::ostringstream result; + result << _commandName << "." << std::to_string(event->threadid()); + return result.str(); + } else { + std::string result = _commandName; + result.resize(12, ' '); + return result; + } + } + + std::string duration(std::shared_ptr event) { + std::ostringstream result; + uint64_t usecs = (mach_to_nano(event->endTimestamp() - event->startTimestamp()) + (NSEC_PER_USEC - 1)) / NSEC_PER_USEC; + uint64_t secs = usecs / USEC_PER_SEC; + usecs -= secs * USEC_PER_SEC; + result << secs << "." << std::setw(6) << std::setfill('0') << usecs; + return result.str(); + } + +public: + void outputConsole(std::shared_ptr node, uint64_t width, std::ostringstream& sstr, uint64_t depth) { + std::ostringstream line; + bool extended = false; + if (auto dlopenNode = dynamic_cast(node.get())) { + line << "dlopen(\"" << dlopenNode->path << "\", " << dlopenNode->flagString() << ") -> 0x" << dlopenNode->result; + } else if (auto dlsymNode = dynamic_cast(node.get())) { + line << "dlsym(0x" << std::hex << dlsymNode->handle << ", \"" << dlsymNode->symbol << "\") -> " << dlsymNode->result; + } else if (auto mapImageNode = dynamic_cast(node.get())) { + line << "map file \"" << mapImageNode->path << "\" -> 0x" << std::hex << mapImageNode->result; + } else if (auto sigNode = dynamic_cast(node.get())) { + line << "attach codesignature"; + } else if (auto buildClosureNode = dynamic_cast(node.get())) { + line << "build closure"; + } else if (auto launchNode = dynamic_cast(node.get())) { + line << "app launch (dyld" << std::dec << launchNode->launchMode << ") -> 0x" << std::hex << launchNode->address; + } else if (auto initNode = dynamic_cast(node.get())) { + line << "run static initializer 0x" << std::hex << initNode->funcAddress; + } else if (auto initNode = dynamic_cast(node.get())) { + line << "apply fixups"; + } else if (auto dlcloseNode = dynamic_cast(node.get())) { + line << "dlclose(0x" << std::hex << dlcloseNode->handle << ") -> " << dlcloseNode->result; + } else if (auto dladdrNode = dynamic_cast(node.get())) { + line << "dladdr(0x" << dladdrNode->address << ") -> image: 0x" << std::hex << dladdrNode->imageAddress; + line << ", symbol: 0x" << dladdrNode->symbolAddress; + } else if (auto addImageNode = dynamic_cast(node.get())) { + line << std::hex << "add image callback(0x" << addImageNode->funcAddress << ") for image 0x" << addImageNode->libraryAddress; + } else if (auto removeImageNode = dynamic_cast(node.get())) { + line << std::hex << "remove image callback(0x" << removeImageNode->funcAddress << ") for image 0x" << removeImageNode->libraryAddress; + } else if (auto objcInitNode = dynamic_cast(node.get())) { + line << std::hex << "objC init image(0x" << objcInitNode->libraryAddress << ")"; + } else if (auto objcMapNode = dynamic_cast(node.get())) { + line << std::hex << "objC map images callback"; + } + + if (width > MAXCOLS) { + extended = true; + } + + std::string timestampStr = timestamp(node, extended); + std::string lineStr = line.str(); + std::string commandStr = process(node, extended); + std::string durationStr = duration(node); + uint64_t lineMax = width - (timestampStr.length() + commandStr.length() + durationStr.length() + 2*depth + 3); + lineStr.resize(lineMax, ' '); + + sstr << timestampStr << " "; + std::fill_n(std::ostream_iterator(sstr), 2*depth, ' '); + sstr << lineStr << " " << durationStr << " " << commandStr << std::endl; + + for (const auto& child : node->children()) { + outputConsole(child, width, sstr, depth+1); + } + } + + void outputJSON(std::shared_ptr node, std::ostringstream& sstr) { + if (auto dlopenNode = dynamic_cast(node.get())) { + sstr << std::hex; + sstr << "{\"type\":\"dlopen\",\"path\":\"" << dlopenNode->path << "\",\"flags\":\"0x" << dlopenNode->flags << "\""; + sstr << ",\"result\":\"" << dlopenNode->result << "\""; + } else if (auto dlsymNode = dynamic_cast(node.get())) { + sstr << std::hex << "{\"type\":\"dlsym\",\"symbol\":\"" << dlsymNode->symbol << "\",\"handle\":\"0x"; + sstr << dlsymNode->handle << "\",\"result\":\"0x" << dlsymNode->result << "\""; + } else if (auto mapImageNode = dynamic_cast(node.get())) { + sstr << std::hex; + sstr << "{\"type\":\"map_image\",\"path\":\"" << mapImageNode->path << "\",\"result\":\"0x" << mapImageNode->result << "\""; + } else if (auto sigNode = dynamic_cast(node.get())) { + sstr << "{\"type\":\"attach_codesignature\""; + } else if (auto buildClosureNode = dynamic_cast(node.get())) { + sstr << "{\"type\":\"build_closure\""; + } else if (auto launchNode = dynamic_cast(node.get())) { + sstr << std::hex; + sstr << "{\"type\":\"app_launch\",\"address\":\"0x"; + sstr << launchNode->address << "\",\"mode\":" << launchNode->launchMode << ""; + } else if (auto initNode = dynamic_cast(node.get())) { + sstr << std::hex; + sstr << "{\"type\":\"static_init\",\"image_address\":\"0x" << initNode->libraryAddress; + sstr << "\",\"function_address\":\"0x" << initNode->funcAddress << "\""; + } else if (auto initNode = dynamic_cast(node.get())) { + sstr << "{\"type\":\"apply_fixups\""; + } else if (auto dlcloseNode = dynamic_cast(node.get())) { + sstr << std::hex << "{\"type\":\"dlclose\",\"handle\":\"0x"; + sstr << dlcloseNode->handle << "\",\"result\":\"0x" << dlcloseNode->result << "\""; + } else if (auto dladdrNode = dynamic_cast(node.get())) { + sstr << std::hex << "{\"type\":\"dladdr\",\"address\":\"0x" << dladdrNode->address << "\",\"result\":\"0x"; + sstr << dladdrNode->result << "\",\"symbol_address\":\"0x" << dladdrNode->symbolAddress; + sstr << "\",\"image_address\":\"0x" << dladdrNode->imageAddress << "\""; + } else { + sstr << "{\"type\":\"unknown\""; + } + + if (!node->children().empty()) { + bool firstChild = true; + sstr << ",\"children\":["; + for (const auto& child : node->children()) { + if (!firstChild) { + sstr << ","; + } + firstChild = false; + outputJSON(child, sstr); + } + sstr << "]"; + } + sstr << std::dec << ",\"start_nano\":\"" << mach_to_nano(node->startTimestamp()); + sstr << "\",\"end_nano\":\"" << mach_to_nano(node->endTimestamp()) << "\"}"; + } + + void outputTracingJSON(std::shared_ptr node, std::ostringstream& sstr) { + auto emitEventInfo = [&](bool isStart) { + if (auto dlopenNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"dlopen(" << dlopenNode->path << ")\", \"cat\": \"" << "dlopen" << "\""; + } else if (auto dlsymNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"dlsym(" << dlsymNode->symbol << ")\", \"cat\": \"" << "dlsym" << "\""; + } else if (auto mapImageNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"map_image(" << mapImageNode->path << ")\", \"cat\": \"" << "map_image" << "\""; + } else if (auto sigNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "attach_codesignature" << "\", \"cat\": \"" << "attach_codesignature" << "\""; + } else if (auto buildClosureNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "build_closure" << "\", \"cat\": \"" << "build_closure" << "\""; + } else if (auto launchNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "app_launch" << "\", \"cat\": \"" << "app_launch" << "\""; + } else if (auto initNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "static_init" << "\", \"cat\": \"" << "static_init" << "\""; + } else if (auto initNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "apply_fixups" << "\", \"cat\": \"" << "apply_fixups" << "\""; + } else if (auto dlcloseNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "dlclose" << "\", \"cat\": \"" << "dlclose" << "\""; + } else if (auto dladdrNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "dladdr" << "\", \"cat\": \"" << "dladdr" << "\""; + } else if (auto addImageNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "add_image" << "\", \"cat\": \"" << "add_image" << "\""; + } else if (auto removeImageNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "remove_image" << "\", \"cat\": \"" << "remove_image" << "\""; + } else if (auto objcInitNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "objc_init" << "\", \"cat\": \"" << "objc_init" << "\""; + } else if (auto objcMapNode = dynamic_cast(node.get())) { + sstr << "{\"name\": \"" << "objc_map" << "\", \"cat\": \"" << "objc_map" << "\""; + } else { + sstr << "{\"name\": \"" << "unknown" << "\", \"cat\": \"" << node->eventCode() << "\""; + } + if (isStart) { + sstr << ", \"ph\": \"B\", \"pid\": " << _pid << ", \"tid\": " << _threadid << ", \"ts\": " << mach_to_nano(node->startTimestamp()) << "},"; + } else { + sstr << ", \"ph\": \"E\", \"pid\": " << _pid << ", \"tid\": " << _threadid << ", \"ts\": " << mach_to_nano(node->endTimestamp()) << "}"; + } + }; + + emitEventInfo(true); + emitEventInfo(false); + + if (!node->children().empty()) { + for (const auto& child : node->children()) { + sstr << ", "; + outputTracingJSON(child, sstr); + } + } + } + + const std::vector>& rootEvents() const { return _rootEvents; } + +private: + + void output(std::shared_ptr root) { + std::ostringstream ostream; + if (JSON_flag) { + ostream << "{\"command\":\"" << _commandName << "\",\"pid\":\"" << _pid << "\",\"thread\":\""; + ostream << _threadid << "\", \"event\":"; + outputJSON(root, ostream); + ostream << "}" << std::endl; + } else if (JSON_Tracing_flag) { + _rootEvents.push_back(root); + } else { + outputConsole(root, columns, ostream, 0); + } + std::cout << ostream.str(); + if (!RAW_flag) + fflush(stdout); + } + + struct dlopen : event_pair { + dlopen(ktrace_event_t E) : event_pair(E), path(stringForID(E->arg2)), flags((int)E->arg3) {} + std::string flagString() { + std::vector flagStrs; + uint64_t flagCheck = 0; + std::string flagString; + + if (flags & RTLD_LAZY) { + flagStrs.push_back("RTLD_LAZY"); + flagCheck |= RTLD_LAZY; + } + if (flags & RTLD_NOW) { + flagStrs.push_back("RTLD_NOW"); + flagCheck |= RTLD_NOW; + } + if (flags & RTLD_LOCAL) { + flagStrs.push_back("RTLD_LOCAL"); + flagCheck |= RTLD_LOCAL; + } + if (flags & RTLD_GLOBAL) { + flagStrs.push_back("RTLD_GLOBAL"); + flagCheck |= RTLD_GLOBAL; + } + if (flags & RTLD_NOLOAD) { + flagStrs.push_back("RTLD_NOLOAD"); + flagCheck |= RTLD_NOLOAD; + } + if (flags & RTLD_NODELETE) { + flagStrs.push_back("RTLD_NODELETE"); + flagCheck |= RTLD_NODELETE; + } + if (flags & RTLD_FIRST) { + flagStrs.push_back("RTLD_FIRST"); + flagCheck |= RTLD_FIRST; + } + + if (flagCheck == flags) { + for (auto& flagStr : flagStrs) { + if (!flagString.empty()) { + flagString += "|"; + } + flagString += flagStr; + } + } + + return flagString; + } + std::string path; + int flags; + uint64_t result; + }; + + struct dlsym : event_pair { + dlsym(ktrace_event_t E) : event_pair(E), handle(E->arg2), symbol(stringForID(E->arg3)) {} + std::string symbol; + uint64_t handle; + uint64_t result; + }; + + struct dladdr : event_pair { + dladdr(ktrace_event_t E) : event_pair(E), address(E->arg2), imageAddress(0), symbolAddress(0) {} + uint64_t address; + uint64_t imageAddress; + uint64_t symbolAddress; + int result; + }; + + struct dlclose : event_pair { + dlclose(ktrace_event_t E) : event_pair(E), handle(E->arg2) {} + uint64_t handle; + int result; + }; + + struct app_launch : event_pair { + app_launch(ktrace_event_t E) : event_pair(E), address(E->arg2) {} + uint64_t address; + uint64_t launchMode; + std::vector _children; + }; + + struct static_init : event_pair { + static_init(ktrace_event_t E) : event_pair(E), libraryAddress(E->arg2), funcAddress(E->arg3) {} + uint64_t libraryAddress; + uint64_t funcAddress; + }; + + struct map_image : event_pair { + map_image(ktrace_event_t E) : event_pair(E), path(stringForID(E->arg2)) {} + std::string path; + uint64_t result; + }; + + struct apply_fixups : event_pair { + apply_fixups(ktrace_event_t E) : event_pair(E) {} + }; + + struct attach_signature : event_pair { + attach_signature(ktrace_event_t E) : event_pair(E) {} + }; + + struct build_closure : event_pair { + build_closure(ktrace_event_t E) : event_pair(E) {} + }; + + struct add_image_callback : event_pair { + add_image_callback(ktrace_event_t E) : event_pair(E), libraryAddress(E->arg2), funcAddress(E->arg3) {} + uint64_t libraryAddress; + uint64_t funcAddress; + }; + + struct remove_image_callback : event_pair { + remove_image_callback(ktrace_event_t E) : event_pair(E), libraryAddress(E->arg2), funcAddress(E->arg3) {} + uint64_t libraryAddress; + uint64_t funcAddress; + + }; + + struct objc_image_init : event_pair { + objc_image_init(ktrace_event_t E) : event_pair(E), libraryAddress(E->arg2) {} + uint64_t libraryAddress; + }; + + struct objc_images_map : event_pair { + objc_images_map(ktrace_event_t E) : event_pair(E) {} + }; + + std::shared_ptr _currentRootEvent; + std::vector> _eventStack; + std::vector> _rootEvents; +}; + +struct OutputManager { + std::map> sOutputRenders; + + void flush() { + if (JSON_Tracing_flag) { + std::ostringstream ostream; + ostream << "{\"displayTimeUnit\":\"ns\""; + ostream << ", \"traceEvents\": ["; + bool firstEvent = true; + for (const auto& renderer : sOutputRenders) { + for (const auto& root : renderer.second->rootEvents()) { + if (firstEvent) + firstEvent = false; + else + ostream << ", "; + renderer.second->outputTracingJSON(root, ostream); + } + } + ostream << "]"; + ostream << "}" << std::endl; + std::cout << ostream.str(); + if (!RAW_flag) + fflush(stdout); + } + } +}; + +static OutputManager sOutputManager; + +void +setup_ktrace_callbacks(void) +{ + ktrace_events_single(s, TRACEDBG_CODE(DBG_TRACE_STRING, TRACE_STRING_GLOBAL), ^(ktrace_event_t event){ + char argChars[33] = {0}; + memset(&argChars[0], 0, 33); + if ((event->debugid & DBG_FUNC_START) == DBG_FUNC_START) { + uint64_t str_id = event->arg2; + if (((event->debugid & DBG_FUNC_END) == DBG_FUNC_END) + && str_id != 0) { + auto i = gActiveStrings.find(str_id); + if (i != gActiveStrings.end()) { + gActiveStrings.erase(i); + } + } + *((uint64_t*)&argChars[0]) = event->arg3; + *((uint64_t*)&argChars[8]) = event->arg4; + gActiveStringIDs.insert(std::make_pair(event->threadid, str_id)); + gActiveStrings.insert(std::make_pair(str_id, argChars)); + } else { + // Not a start, so lets grab our data + *((uint64_t*)&argChars[0]) = event->arg1; + *((uint64_t*)&argChars[8]) = event->arg2; + *((uint64_t*)&argChars[16]) = event->arg3; + *((uint64_t*)&argChars[24]) = event->arg4; + + auto i = gActiveStringIDs.find(event->threadid); + if (i != gActiveStringIDs.end()) { + auto j = gActiveStrings.find(i->second); + if (j != gActiveStrings.end()) { + j->second += argChars; + } + } + } + + if ((event->debugid & DBG_FUNC_END) == DBG_FUNC_END) { + auto i = gActiveStringIDs.find(event->threadid); + if (i != gActiveStringIDs.end()) { + gActiveStringIDs.erase(i); + } + }; + }); + + // Event though our events are paired, we process them individually so we can + // render nested events + ktrace_events_range(s, KDBG_EVENTID(DBG_DYLD, DBG_DYLD_INTERNAL_SUBCLASS, 0), KDBG_EVENTID(DBG_DYLD, DBG_DYLD_API_SUBCLASS+1, 0), ^(ktrace_event_t event){ + assert((event->debugid & KDBG_FUNC_MASK) != 0); + auto i = sOutputManager.sOutputRenders.find(event->threadid); + if (i == sOutputManager.sOutputRenders.end()) { + sOutputManager.sOutputRenders.emplace(std::make_pair(event->threadid, std::make_unique(s, event))); + i = sOutputManager.sOutputRenders.find(event->threadid); + } + i->second->recordEvent(event); + if (i->second->empty()) { + sOutputManager.sOutputRenders.erase(i); + } + }); +} + +int +main(int argc, char *argv[]) +{ + char ch; + int rv = 0; + bool exclude_pids = false; + uint64_t time_limit_ns = 0; + + get_screenwidth(); + + s = ktrace_session_create(); + assert(s); + + while ((ch = getopt(argc, argv, "jJeR:t:")) != -1) { + switch (ch) { + case 'j': + JSON_flag = true; + break; + case 'J': + JSON_Tracing_flag = true; + break; + case 'e': + exclude_pids = true; + break; + case 't': + time_limit_ns = (uint64_t)(NSEC_PER_SEC * atof(optarg)); + if (time_limit_ns == 0) { + fprintf(stderr, "ERROR: could not set time limit to %s\n", + optarg); + exit(1); + } + break; + case 'R': + RAW_flag = true; + rv = ktrace_set_file(s, optarg); + if (rv) { + fprintf(stderr, "ERROR: reading trace from '%s' failed (%s)\n", optarg, strerror(errno)); + exit(1); + } + break; + default: + exit_usage(); + } + } + + argc -= optind; + argv += optind; + + if (time_limit_ns > 0) { + if (RAW_flag) { + fprintf(stderr, "NOTE: time limit ignored when a raw file is specified\n"); + } else { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, time_limit_ns), + dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), + ^{ + ktrace_end(s, 0); + }); + } + } + + if (!RAW_flag) { + if (geteuid() != 0) { + fprintf(stderr, "'dyld_usage' must be run as root...\n"); + exit(1); + } + + /* + * ktrace can't both *in*clude and *ex*clude pids, so: if we are + * already excluding pids, or if we are not explicitly including + * or excluding any pids, then exclude the defaults. + * + * if on the other hand we are explicitly including pids, we'll + * filter the defaults out naturally. + */ + if (exclude_pids || argc == 0) { + ktrace_exclude_process(s, "dyld_usage"); + ktrace_exclude_process(s, "Terminal"); + ktrace_exclude_process(s, "telnetd"); + ktrace_exclude_process(s, "telnet"); + ktrace_exclude_process(s, "sshd"); + ktrace_exclude_process(s, "rlogind"); + ktrace_exclude_process(s, "tcsh"); + ktrace_exclude_process(s, "csh"); + ktrace_exclude_process(s, "sh"); + ktrace_exclude_process(s, "zsh"); +#if TARGET_OS_EMBEDDED + ktrace_exclude_process(s, "dropbear"); +#endif /* TARGET_OS_EMBEDDED */ + } + } + + /* + * Process the list of specified pids, and in/exclude them as + * appropriate. + */ + while (argc > 0) { + pid_t pid; + char *name; + char *endptr; + + name = argv[0]; + pid = (pid_t)strtoul(name, &endptr, 10); + + if (*name != '\0' && *endptr == '\0') { + if (exclude_pids) { + rv = ktrace_exclude_pid(s, pid); + } else { + if (pid != 0) + rv = ktrace_filter_pid(s, pid); + } + } else { + if (exclude_pids) { + rv = ktrace_exclude_process(s, name); + } else { + if (strcmp(name, "kernel_task")) + rv = ktrace_filter_process(s, name); + } + } + + if (rv == EINVAL) { + fprintf(stderr, "ERROR: cannot both include and exclude simultaneously\n"); + exit(1); + } else { + assert(!rv); + } + + argc--; + argv++; + } + /* provides SIGINT, SIGHUP, SIGPIPE, SIGTERM handlers */ + ktrace_set_signal_handler(s); + ktrace_set_completion_handler(s, ^{ + sOutputManager.flush(); + exit(0); + }); + + signal(SIGWINCH, SIG_IGN); + sigwinch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGWINCH, 0, dispatch_get_main_queue()); + dispatch_source_set_event_handler(sigwinch_source, ^{ + get_screenwidth(); + }); + dispatch_activate(sigwinch_source); + + setup_ktrace_callbacks(); + + ktrace_set_dropped_events_handler(s, ^{ + fprintf(stderr, "dyld_usage: buffer overrun, events generated too quickly\n"); + + /* clear any state that is now potentially invalid */ + }); + + ktrace_set_execnames_enabled(s, KTRACE_FEATURE_LAZY); + ktrace_set_vnode_paths_enabled(s, false); + /* no need to symbolicate addresses */ + ktrace_set_uuid_map_enabled(s, KTRACE_FEATURE_DISABLED); + + rv = ktrace_start(s, dispatch_get_main_queue()); + + if (rv) { + perror("ktrace_start"); + exit(1); + } + + dispatch_main(); + + return 0; +} diff --git a/src/glue.c b/src/glue.c index 99a6644..dfad3f5 100644 --- a/src/glue.c +++ b/src/glue.c @@ -22,6 +22,8 @@ * @APPLE_LICENSE_HEADER_END@ */ +#define _FORTIFY_SOURCE 0 + #include #include #include @@ -41,6 +43,10 @@ #include #include #include +#include +#include +#include + #if TARGET_IPHONE_SIMULATOR #include "dyldSyscallInterface.h" #include "dyld_images.h" @@ -58,6 +64,27 @@ typedef struct mach_header macho_header; typedef struct nlist macho_nlist; #endif + + #define DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE (32*1024) + #define DYLD_PROCESS_INFO_NOTIFY_LOAD_ID 0x1000 + #define DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID 0x2000 + #define DYLD_PROCESS_INFO_NOTIFY_MAIN_ID 0x3000 + + struct dyld_process_info_image_entry { + uuid_t uuid; + uint64_t loadAddress; + uint32_t pathStringOffset; + uint32_t pathLength; + }; + + struct dyld_process_info_notify_header { + mach_msg_header_t header; + uint32_t version; + uint32_t imageCount; + uint32_t imagesOffset; + uint32_t stringsOffset; + uint64_t timestamp; + }; #endif // from _simple.h in libc @@ -109,13 +136,13 @@ void _ZN10__cxxabiv112__unexpectedEPFvvE() } // std::__terminate() called by C++ unwinding code -void _ZSt11__terminatePFvvE(void (*func)()) +void _ZSt11__terminatePFvvE(void (*func)(void)) { _ZN4dyld4haltEPKc("dyld std::__terminate()\n"); } // std::__unexpected() called by C++ unwinding code -void _ZSt12__unexpectedPFvvE(void (*func)()) +void _ZSt12__unexpectedPFvvE(void (*func)(void)) { _ZN4dyld4haltEPKc("dyld std::__unexpected()\n"); } @@ -290,8 +317,6 @@ void __chk_fail() // referenced by libc.a(pthread.o) but unneeded in dyld -void _init_cpu_capabilities() { } -void _cpu_capabilities() {} void set_malloc_singlethreaded() {} int PR_5243343_flag = 0; @@ -312,8 +337,8 @@ FILE* __stderrp = NULL; FILE* __stdoutp = NULL; // work with c++abi.a -void (*__cxa_terminate_handler)() = _ZSt9terminatev; -void (*__cxa_unexpected_handler)() = _ZSt10unexpectedv; +void (*__cxa_terminate_handler)(void) = _ZSt9terminatev; +void (*__cxa_unexpected_handler)(void) = _ZSt10unexpectedv; void abort_message(const char* format, ...) { @@ -375,8 +400,6 @@ int _ZN4dyld7my_openEPKcii(const char* path, int flag, int other) #if TARGET_IPHONE_SIMULATOR -#include - int myopen(const char* path, int oflag, int extra) __asm("_open"); int myopen(const char* path, int oflag, int extra) { return gSyscallHelpers->open(path, oflag, extra); @@ -530,20 +553,25 @@ int readdir_r(DIR* dirp, struct dirent* entry, struct dirent **result) { return gSyscallHelpers->readdir_r(dirp, entry, result); } +// HACK: readdir() is not used in dyld_sim, but it is pulled in by libc.a, then dead stripped. +struct dirent* readdir(DIR *dirp) { + _ZN4dyld4haltEPKc("dyld_sim readdir() not supported\n"); +} + int closedir(DIR* dirp) { if ( gSyscallHelpers->version < 3 ) return EPERM; return gSyscallHelpers->closedir(dirp); } -void xcoresymbolication_load_notifier(void* connection, uint64_t timestamp, const char* path, const struct mach_header* mh) +void coresymbolication_load_notifier(void* connection, uint64_t timestamp, const char* path, const struct mach_header* mh) { // if host dyld supports this notifier, call into host dyld if ( gSyscallHelpers->version >= 4 ) return gSyscallHelpers->coresymbolication_load_notifier(connection, timestamp, path, mh); } -void xcoresymbolication_unload_notifier(void* connection, uint64_t timestamp, const char* path, const struct mach_header* mh) +void coresymbolication_unload_notifier(void* connection, uint64_t timestamp, const char* path, const struct mach_header* mh) { // if host dyld supports this notifier, call into host dyld if ( gSyscallHelpers->version >= 4 ) @@ -556,16 +584,24 @@ void xcoresymbolication_unload_notifier(void* connection, uint64_t timestamp, co #if SUPPORT_HOST_10_11 typedef int (*FuncPtr_proc_regionfilename)(int pid, uint64_t address, void* buffer, uint32_t bufferSize); -typedef pid_t (*FuncPtr_getpid)(); +typedef pid_t (*FuncPtr_getpid)(void); typedef bool (*FuncPtr_mach_port_insert_right)(ipc_space_t task, mach_port_name_t name, mach_port_t poly, mach_msg_type_name_t polyPoly); typedef kern_return_t (*FuncPtr_mach_port_allocate)(ipc_space_t, mach_port_right_t, mach_port_name_t*); typedef mach_msg_return_t (*FuncPtr_mach_msg)(mach_msg_header_t *, mach_msg_option_t , mach_msg_size_t , mach_msg_size_t , mach_port_name_t , mach_msg_timeout_t , mach_port_name_t); +typedef void (*FuncPtr_mach_msg_destroy)(mach_msg_header_t *); +typedef kern_return_t (*FuncPtr_mach_port_construct)(ipc_space_t task, mach_port_options_ptr_t options, mach_port_context_t context, mach_port_name_t *name); +typedef kern_return_t (*FuncPtr_mach_port_destruct)(ipc_space_t task, mach_port_name_t name, mach_port_delta_t srdelta, mach_port_context_t guard); +typedef void (*FuncPtr_notifyMonitoringDyld)(bool unloading, unsigned portSlot, unsigned imageCount, const struct dyld_image_info infos[]); static FuncPtr_proc_regionfilename proc_proc_regionfilename = NULL; static FuncPtr_getpid proc_getpid = NULL; static FuncPtr_mach_port_insert_right proc_mach_port_insert_right = NULL; static FuncPtr_mach_port_allocate proc_mach_port_allocate = NULL; static FuncPtr_mach_msg proc_mach_msg = NULL; +static FuncPtr_mach_msg_destroy proc_mach_msg_destroy = NULL; +static FuncPtr_mach_port_construct proc_mach_port_construct = NULL; +static FuncPtr_mach_port_destruct proc_mach_port_destruct = NULL; +static FuncPtr_notifyMonitoringDyld proc_notifyMonitoringDyld = NULL; @@ -633,10 +669,91 @@ static void findHostFunctions() { else if ( strcmp(name, "_mach_port_allocate") == 0 ) proc_mach_port_allocate = (FuncPtr_mach_port_allocate)(s->n_value + slide); else if ( strcmp(name, "_mach_msg") == 0 ) - proc_mach_msg = (FuncPtr_mach_msg)(s->n_value + slide); + proc_mach_msg = (FuncPtr_mach_msg)(s->n_value + slide); + else if ( strcmp(name, "__ZN4dyldL20notifyMonitoringDyldEbjjPK15dyld_image_info") == 0 ) + proc_notifyMonitoringDyld = (FuncPtr_notifyMonitoringDyld)(s->n_value + slide); } } } + +// Look up sycalls in host dyld needed by coresymbolication_ routines in dyld_sim +static bool findHostLibSystemFunctions() { + // Only look up symbols once + if (proc_mach_msg_destroy != NULL && proc_mach_port_construct != NULL && proc_mach_port_destruct != NULL) + return true; + + const struct mach_header* hostLibSystemMH = NULL; + struct dyld_all_image_infos* imageInfo = (struct dyld_all_image_infos*)(gSyscallHelpers->getProcessInfo()); + const struct dyld_image_info* infoArray = imageInfo->infoArray; + if (infoArray == NULL) + return false; + uint32_t imageCount = imageInfo->infoArrayCount; + for (uint32_t i = 0; incmds; + const struct load_command* const cmds = (struct load_command*)(((char*)hostLibSystemMH)+sizeof(macho_header)); + const struct load_command* cmd = cmds; + const uint8_t* linkEditBase = NULL; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + { + const macho_segment_command* seg = (macho_segment_command*)cmd; + if ( (seg->fileoff == 0) && (seg->filesize != 0) ) + slide = (uintptr_t)hostLibSystemMH - seg->vmaddr; + if ( strcmp(seg->segname, "__LINKEDIT") == 0 ) + linkEditBase = (uint8_t*)(seg->vmaddr - seg->fileoff + slide); + } + break; + case LC_SYMTAB: + { + const struct symtab_command* symtab = (struct symtab_command*)cmd; + if ( linkEditBase == NULL ) + return false; + symbolTableStrings = (const char*)&linkEditBase[symtab->stroff]; + symbolTable = (macho_nlist*)(&linkEditBase[symtab->symoff]); + } + break; + case LC_DYSYMTAB: + dynSymbolTable = (struct dysymtab_command*)cmd; + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + if ( symbolTableStrings == NULL ) + return false;; + if ( dynSymbolTable == NULL ) + return false;; + + // scan local symbols in host dyld looking for load/unload functions + const macho_nlist* const localsStart = &symbolTable[dynSymbolTable->iextdefsym]; + const macho_nlist* const localsEnd= &localsStart[dynSymbolTable->nextdefsym]; + for (const macho_nlist* s = localsStart; s < localsEnd; ++s) { + if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) { + const char* name = &symbolTableStrings[s->n_un.n_strx]; + if ( strcmp(name, "_mach_msg_destroy") == 0 ) + proc_mach_msg_destroy = (FuncPtr_mach_msg_destroy)(s->n_value + slide); + else if ( strcmp(name, "_mach_port_construct") == 0 ) + proc_mach_port_construct = (FuncPtr_mach_port_construct)(s->n_value + slide); + else if ( strcmp(name, "_mach_port_destruct") == 0 ) + proc_mach_port_destruct = (FuncPtr_mach_port_destruct)(s->n_value + slide); + } + } + return (proc_mach_msg_destroy != NULL && proc_mach_port_construct != NULL && proc_mach_port_destruct != NULL); +} #endif @@ -706,6 +823,41 @@ kern_return_t mach_msg(mach_msg_header_t* msg, mach_msg_option_t option, mach_ms #endif } +void mach_msg_destroy(mach_msg_header_t *msg) { + if ( gSyscallHelpers->version >= 12 ) { + gSyscallHelpers->mach_msg_destroy(msg); + return; + } +#if SUPPORT_HOST_10_11 + if (findHostLibSystemFunctions()) { + (*proc_mach_msg_destroy)(msg); + } +#endif +} + +kern_return_t mach_port_construct(ipc_space_t task, mach_port_options_ptr_t options, mach_port_context_t context, mach_port_name_t *name) { + if ( gSyscallHelpers->version >= 12 ) { + return gSyscallHelpers->mach_port_construct(task, options, context, name); + } +#if SUPPORT_HOST_10_11 + if (findHostLibSystemFunctions()) { + return (*proc_mach_port_construct)(task, options, context, name); + } +#endif + return KERN_NOT_SUPPORTED; +} + +kern_return_t mach_port_destruct(ipc_space_t task, mach_port_name_t name, mach_port_delta_t srdelta, mach_port_context_t guard) { + if ( gSyscallHelpers->version >= 12 ) { + return gSyscallHelpers->mach_port_destruct(task, name, srdelta, guard); + } +#if SUPPORT_HOST_10_11 + if (findHostLibSystemFunctions()) { + return (*proc_mach_port_destruct)(task, name, srdelta, guard); + } +#endif + return KERN_NOT_SUPPORTED; +} void abort_with_payload(uint32_t reason_namespace, uint64_t reason_code, void* payload, uint32_t payload_size, const char* reason_string, uint64_t reason_flags) { @@ -774,6 +926,86 @@ int kdebug_trace(uint32_t code, uint64_t arg1, uint64_t arg2, uint64_t arg3, uin return 0; } +uint64_t kdebug_trace_string(uint32_t debugid, uint64_t str_id, const char *str) { + if ( gSyscallHelpers->version >= 9 ) + return gSyscallHelpers->kdebug_trace_string(debugid, str_id, str); + return 0; +} + +uint64_t amfi_check_dyld_policy_self(uint64_t inFlags, uint64_t* outFlags) +{ + if ( gSyscallHelpers->version >= 10 ) + return gSyscallHelpers->amfi_check_dyld_policy_self(inFlags, outFlags); + *outFlags = 0x3F; // on old kernel, simulator process get all flags + return 0; +} + +static mach_port_t sNotifyReplyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; +static bool sZombieNotifiers[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT]; + +void _ZN4dyld24notifyMonitoringDyldMainEv() { + if ( gSyscallHelpers->version >= 11 ) { + gSyscallHelpers->notifyMonitoringDyldMain(); + return; + } + struct dyld_all_image_infos* imageInfo = (struct dyld_all_image_infos*)(gSyscallHelpers->getProcessInfo()); + for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) { + if ( (imageInfo->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 = imageInfo->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]); + mach_port_deallocate(mach_task_self(), imageInfo->notifyPorts[slot]); + mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]); + imageInfo->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 _ZN4dyld20notifyMonitoringDyldEbjPPK11mach_headerPPKc(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[], const char* imagePaths[]) { + if ( gSyscallHelpers->version >= 11 ) { + gSyscallHelpers->notifyMonitoringDyld(unloading, imageCount, loadAddresses, imagePaths); + return; + } +#if SUPPORT_HOST_10_11 + findHostFunctions(); + if ( proc_notifyMonitoringDyld ) { + struct dyld_image_info infos[imageCount]; + for (int i=0; ierrnoAddress(); } @@ -788,6 +1020,10 @@ mach_port_t mach_task_self_ = MACH_PORT_NULL; extern int myerrno_fallback __asm("_errno"); int myerrno_fallback = 0; + +vm_size_t vm_kernel_page_mask = 0xFFF; +vm_size_t vm_page_size = 0x1000; + #endif // TARGET_IPHONE_SIMULATOR @@ -817,4 +1053,52 @@ void _Block_object_dispose(const void* object, int flags) } +unsigned char* CC_SHA384(const void* data, unsigned long len, unsigned char* md) +{ + const struct ccdigest_info *di = ccsha384_di(); + ccdigest_di_decl(di, dc);//declares dc array in stack + ccdigest_init(di, dc); + ccdigest_update(di, dc, len, data); + ccdigest_final(di, dc, md); + ccdigest_di_clear(di, dc); + return NULL; +} + + +unsigned char* CC_SHA256(const void* data, unsigned long len, unsigned char* md) +{ + const struct ccdigest_info *di = ccsha256_di(); + ccdigest_di_decl(di, dc);//declares dc array in stack + ccdigest_init(di, dc); + ccdigest_update(di, dc, len, data); + ccdigest_final(di, dc, md); + ccdigest_di_clear(di, dc); + return NULL; +} + +unsigned char* CC_SHA1(const void* data, unsigned long len, unsigned char* md) +{ + const struct ccdigest_info *di = ccsha1_di(); + ccdigest_di_decl(di, dc);//declares dc array in stack + ccdigest_init(di, dc); + ccdigest_update(di, dc, len, data); + ccdigest_final(di, dc, md); + ccdigest_di_clear(di, dc); + return NULL; +} + +#if !TARGET_IPHONE_SIMULATOR +errno_t memset_s(void* s, rsize_t smax, int c, rsize_t n) +{ + errno_t err = 0; + if (s == NULL) + return EINVAL; + if (n > smax) { + err = EOVERFLOW; + n = smax; + } + memset(s, c, n); + return err; +} +#endif diff --git a/src/start_glue.h b/src/start_glue.h deleted file mode 100644 index 57b03c1..0000000 --- a/src/start_glue.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2013 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 __START_GLUE_H__ -#define __START_GLUE_H__ - -// Implemented in start_glue.s -extern "C" void start(); - - -// need 'start' to be one atom, but entry is in interior - -#if __x86_64__ || __i386__ - #define address_of_start (void*)((uintptr_t)&start + 1) -#elif __arm64__ - #define address_of_start (void*)((uintptr_t)&start + 4) -#elif __arm__ - #define address_of_start (void*)((uintptr_t)&start + 2) -#endif - - - -#endif // __START_GLUE_H__ diff --git a/src/threadLocalHelpers.s b/src/threadLocalHelpers.s index fb63817..1e9aa80 100644 --- a/src/threadLocalHelpers.s +++ b/src/threadLocalHelpers.s @@ -228,12 +228,24 @@ LlazyAllocate: .globl _tlv_get_addr .private_extern _tlv_get_addr _tlv_get_addr: +#if __LP64__ ldr x16, [x0, #8] // get key from descriptor +#else + ldr w16, [x0, #4] // get key from descriptor +#endif mrs x17, TPIDRRO_EL0 and x17, x17, #-8 // clear low 3 bits??? +#if __LP64__ ldr x17, [x17, x16, lsl #3] // get thread allocation address for this key +#else + ldr w17, [x17, x16, lsl #2] // get thread allocation address for this key +#endif cbz x17, LlazyAllocate // if NULL, lazily allocate +#if __LP64__ ldr x16, [x0, #16] // get offset from descriptor +#else + ldr w16, [x0, #8] // get offset from descriptor +#endif add x0, x17, x16 // return allocation+offset ret lr @@ -258,7 +270,11 @@ LlazyAllocate: mov x0, x16 // use key from descriptor as parameter bl _tlv_allocate_and_initialize_for_key ldp x16, x17, [sp], #16 // pop descriptor +#if __LP64__ ldr x16, [x16, #16] // get offset from descriptor +#else + ldr w16, [x16, #8] // get offset from descriptor +#endif add x0, x0, x16 // return allocation+offset ldp q6, q7, [sp], #32 diff --git a/testing/build_tests.py b/testing/build_tests.py index ab4d4fd..1783f7f 100755 --- a/testing/build_tests.py +++ b/testing/build_tests.py @@ -20,6 +20,7 @@ def parseDirectives(testCaseSourceDir): runLines = [] minOS = "" timeout = "" + noCrashLogs = [] for file in os.listdir(testCaseSourceDir): if file.endswith((".c", ".cpp", ".cxx")): with open(testCaseSourceDir + "/" + file) as f: @@ -39,13 +40,16 @@ def parseDirectives(testCaseSourceDir): timeoutIndex = string.find(line, "RUN_TIMEOUT:") if timeoutIndex != -1: timeout = line[timeoutIndex+12:].lstrip() - + noCrashLogsIndex = string.find(line, "NO_CRASH_LOG:") + if noCrashLogsIndex != -1: + noCrashLogs.append(line[noCrashLogsIndex+13:].lstrip()) return { "BUILD": buildLines, "BUILD_ONLY": onlyLines, "BUILD_MIN_OS": minOS, "RUN": runLines, - "RUN_TIMEOUT": timeout + "RUN_TIMEOUT": timeout, + "NO_CRASH_LOG": noCrashLogs } @@ -66,13 +70,13 @@ def useTestCase(testCaseDirectives, platformName): # Use BUILD directives to construct the test case # Use RUN directives to generate a shell script to run test(s) # -def buildTestCase(testCaseDirectives, testCaseSourceDir, toolsDir, sdkDir, minOsOptionsName, defaultMinOS, archOptions, testCaseDestDirBuild, testCaseDestDirRun): +def buildTestCase(testCaseDirectives, testCaseSourceDir, toolsDir, sdkDir, dyldIncludesDir, minOsOptionsName, defaultMinOS, archOptions, testCaseDestDirBuild, testCaseDestDirRun): scratchDir = tempfile.mkdtemp() if testCaseDirectives["BUILD_MIN_OS"]: minOS = testCaseDirectives["BUILD_MIN_OS"] else: minOS = defaultMinOS - compilerSearchOptions = " -isysroot " + sdkDir + " -I" + sdkDir + "/System/Library/Frameworks/System.framework/PrivateHeaders" + compilerSearchOptions = " -isysroot " + sdkDir + " -I" + sdkDir + "/System/Library/Frameworks/System.framework/PrivateHeaders" + " -I" + dyldIncludesDir if minOsOptionsName == "mmacosx-version-min": taskForPidCommand = "touch " envEnableCommand = "touch " @@ -116,8 +120,17 @@ def buildTestCase(testCaseDirectives, testCaseSourceDir, toolsDir, sdkDir, minOs runFile.write("#!/bin/sh\n") runFile.write("cd " + testCaseDestDirRun + "\n") os.chmod(runFilePath, 0755) + runFile.write("echo \"run in dyld2 mode\" \n"); for runline in testCaseDirectives["RUN"]: runFile.write(string.Template(runline).safe_substitute(runSubs) + "\n") + runFile.write("echo \"run in dyld3 mode\" \n"); + for runline in testCaseDirectives["RUN"]: + subLine = string.Template(runline).safe_substitute(runSubs) + if subLine.startswith("sudo "): + subLine = "sudo DYLD_USE_CLOSURES=1 " + subLine[5:] + else: + subLine = "DYLD_USE_CLOSURES=1 " + subLine + runFile.write(subLine + "\n") runFile.write("\n") runFile.close() return 0 @@ -137,13 +150,13 @@ if __name__ == "__main__": if not dyldSrcDir: dyldSrcDir = os.getcwd() testsSrcTopDir = dyldSrcDir + "/testing/test-cases/" + dyldIncludesDir = dyldSrcDir + "/include/" 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" + sdkDir = subprocess.check_output(["xcrun", "-sdk", "macosx.internal", "--show-sdk-path"]).rstrip() toolsDir = os.getenv("TOOLCHAIN_DIR", "/") defaultMinOS = "" - minVersNum = "10.12" + minVersNum = "10.14" minOSOption = os.getenv("DEPLOYMENT_TARGET_CLANG_FLAG_NAME", "") if minOSOption: minOSVersName = os.getenv("DEPLOYMENT_TARGET_CLANG_ENV_NAME", "") @@ -170,7 +183,8 @@ if __name__ == "__main__": else: archOptions = "-arch x86_64" allTests = [] - for f in os.listdir(testsSrcTopDir): + suppressCrashLogs = [] + for f in sorted(os.listdir(testsSrcTopDir)): if f.endswith(".dtest"): testName = f[0:-6] outDirBuild = testsBuildDstTopDir + testName @@ -178,7 +192,7 @@ if __name__ == "__main__": testCaseDir = testsSrcTopDir + f testCaseDirectives = parseDirectives(testCaseDir) if useTestCase(testCaseDirectives, platformName): - result = buildTestCase(testCaseDirectives, testCaseDir, toolsDir, sdkDir, minOSOption, minVersNum, archOptions, outDirBuild, outDirRun) + result = buildTestCase(testCaseDirectives, testCaseDir, toolsDir, sdkDir, dyldIncludesDir, minOSOption, minVersNum, archOptions, outDirBuild, outDirRun) if result: sys.exit(result) mytest = {} @@ -187,15 +201,27 @@ if __name__ == "__main__": mytest["WorkingDirectory"] = testsRunDstTopDir + testName mytest["Command"] = [] mytest["Command"].append("./run.sh") + usesDtrace = False for runline in testCaseDirectives["RUN"]: if "$SUDO" in runline: mytest["AsRoot"] = True + if "dtrace" in runline: + usesDtrace = True if testCaseDirectives["RUN_TIMEOUT"]: mytest["Timeout"] = testCaseDirectives["RUN_TIMEOUT"] + if usesDtrace and minOSOption != "mmacosx-version-min": + mytest["BootArgsSet"] = "dtrace_dof_mode=1" allTests.append(mytest) + if testCaseDirectives["NO_CRASH_LOG"]: + for skipCrash in testCaseDirectives["NO_CRASH_LOG"]: + suppressCrashLogs.append(skipCrash) batsInfo = { "BATSConfigVersion": "0.1.0", "Project": "dyld_tests", "Tests": allTests } + if suppressCrashLogs: + batsInfo["IgnoreCrashes"] = [] + for skipCrash in suppressCrashLogs: + batsInfo["IgnoreCrashes"].append(skipCrash) batsFileDir = dstDir + "/AppleInternal/CoreOS/BATS/unit_tests/" shutil.rmtree(batsFileDir, ignore_errors=True) os.makedirs(batsFileDir) @@ -213,4 +239,3 @@ if __name__ == "__main__": shFile.close() os.chmod(runHelper, 0755) - diff --git a/testing/test-cases/LC_DYLD_ENV-DYLD_LIBRARY_PATH.dtest/foo.c b/testing/test-cases/LC_DYLD_ENV-DYLD_LIBRARY_PATH.dtest/foo.c new file mode 100644 index 0000000..2991d23 --- /dev/null +++ b/testing/test-cases/LC_DYLD_ENV-DYLD_LIBRARY_PATH.dtest/foo.c @@ -0,0 +1,4 @@ +void foo() +{ +} + diff --git a/testing/test-cases/LC_DYLD_ENV-DYLD_LIBRARY_PATH.dtest/main.c b/testing/test-cases/LC_DYLD_ENV-DYLD_LIBRARY_PATH.dtest/main.c new file mode 100644 index 0000000..40f90d3 --- /dev/null +++ b/testing/test-cases/LC_DYLD_ENV-DYLD_LIBRARY_PATH.dtest/main.c @@ -0,0 +1,37 @@ + +// BUILD: mkdir -p $BUILD_DIR/hideyhole +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/hideyhole/libfoo1.dylib -install_name /bad/path/libfoo1.dylib +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/hideyhole/libfoo2.dylib -install_name /bad/path2/libfoo2.dylib +// BUILD: $CC main.c -o $BUILD_DIR/main1.exe $BUILD_DIR/hideyhole/libfoo1.dylib -Wl,-dyld_env,DYLD_LIBRARY_PATH=@executable_path/hideyhole +// BUILD: $CC main.c -o $BUILD_DIR/main2.exe $BUILD_DIR/hideyhole/libfoo1.dylib -Wl,-dyld_env,DYLD_LIBRARY_PATH=@loader_path/hideyhole +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main1.exe +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main2.exe + +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main1.exe +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main2.exe + +// RUN: ./main1.exe +// RUN: ./main2.exe + +#include +#include +#include + +/// Test that main executable's LC_DYLD_ENVIRONMENT can set DYLD_LIBRARY_PATH with @executable_path or @loader_path relative paths + +extern char* __progname; + +int main() +{ + printf("[BEGIN] LC_DYLD_ENV-DYLD_LIBRARY_PATH %s\n", __progname); + + void*h = dlopen("/other/path/libfoo2.dylib", 0); + + if ( h != NULL ) + printf("[PASS] LC_DYLD_ENV-DYLD_LIBRARY_PATH %s\n", __progname); + else + printf("[FAIL] LC_DYLD_ENV-DYLD_LIBRARY_PATH %s\n", __progname); + + return 0; +} + diff --git a/testing/test-cases/NSCreateObjectFileImageFromFile-stress.dtest/foo.c b/testing/test-cases/NSCreateObjectFileImageFromFile-stress.dtest/foo.c new file mode 100644 index 0000000..837ec00 --- /dev/null +++ b/testing/test-cases/NSCreateObjectFileImageFromFile-stress.dtest/foo.c @@ -0,0 +1,7 @@ + +#include +#include + +void fooInBundle() +{ +} diff --git a/testing/test-cases/NSCreateObjectFileImageFromFile-stress.dtest/main.cpp b/testing/test-cases/NSCreateObjectFileImageFromFile-stress.dtest/main.cpp new file mode 100644 index 0000000..54c676d --- /dev/null +++ b/testing/test-cases/NSCreateObjectFileImageFromFile-stress.dtest/main.cpp @@ -0,0 +1,80 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CXX main.cpp -o $BUILD_DIR/NSCreateObjectFileImageFromFile-stress.exe -Wno-deprecated-declarations +// BUILD: $CC foo.c -o $BUILD_DIR/foo.bundle -bundle + +// RUN: ./NSCreateObjectFileImageFromFile-stress.exe $RUN_DIR/foo.bundle + + +#include +#include +#include +#include +#include + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] NSCreateObjectFileImageFromFile-basic\n"); + + const char* path = argv[1]; + + std::vector ofis; + for (unsigned i = 0; i != 32; ++i) { + NSObjectFileImage ofi; + if ( NSCreateObjectFileImageFromFile(path, &ofi) != NSObjectFileImageSuccess ) { + printf("[FAIL] NSCreateObjectFileImageFromFile failed\n"); + return 0; + } + ofis.push_back(ofi); + } + + for (unsigned i = 0; i != 32; ++i) { + NSObjectFileImage ofi = ofis[i]; + 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; + } + } + + for (unsigned i = 0; i != 32; ++i) { + NSObjectFileImage ofi = ofis[i]; + 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/main.c b/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/main.c index 888c043..409ff6b 100644 --- a/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/main.c +++ b/testing/test-cases/NSCreateObjectFileImageFromMemory-basic.dtest/main.c @@ -2,8 +2,11 @@ // BUILD: $CC main.c -o $BUILD_DIR/NSCreateObjectFileImageFromMemory-basic.exe -Wno-deprecated-declarations // BUILD: $CC foo.c -o $BUILD_DIR/foo.bundle -bundle +// BUILD: lipo -thin x86_64 $BUILD_DIR/foo.bundle -output $BUILD_DIR/foo-thin.bundle // RUN: ./NSCreateObjectFileImageFromMemory-basic.exe $RUN_DIR/foo.bundle +// RUN: ./NSCreateObjectFileImageFromMemory-basic.exe $RUN_DIR/foo-thin.bundle + #include diff --git a/testing/test-cases/_dyld_images_for_addresses.dtest/foo.c b/testing/test-cases/_dyld_images_for_addresses.dtest/foo.c new file mode 100644 index 0000000..1e11192 --- /dev/null +++ b/testing/test-cases/_dyld_images_for_addresses.dtest/foo.c @@ -0,0 +1,22 @@ + + +#include +#include + + +int foo1() +{ + return 1; +} + +int foo2() +{ + return 2; +} + +int foo3() +{ + return 3; +} + + diff --git a/testing/test-cases/_dyld_images_for_addresses.dtest/main.c b/testing/test-cases/_dyld_images_for_addresses.dtest/main.c new file mode 100644 index 0000000..668eb1e --- /dev/null +++ b/testing/test-cases/_dyld_images_for_addresses.dtest/main.c @@ -0,0 +1,105 @@ + +// 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/dyld_images_for_addresses.exe + +// RUN: ./dyld_images_for_addresses.exe + +#include +#include +#include +#include +#include + +extern void* __dso_handle; + +extern int foo1(); +extern int foo2(); +extern int foo3(); + + +int myfunc() +{ + return 3; +} + +int myfunc2() +{ + return 3; +} + +static int mystaticfoo() +{ + return 3; +} + +int mydata = 5; +int myarray[10]; + + +int main() +{ + printf("[BEGIN] _dyld_images_for_addresses\n"); + int mylocal; + + const void* addresses[10]; + addresses[0] = &myfunc; + addresses[1] = &myfunc2; + addresses[2] = &mystaticfoo; + addresses[3] = &__dso_handle; + addresses[4] = &mydata; + addresses[5] = &myarray; + addresses[6] = &mylocal; // not owned by dyld, so coresponding dyld_image_uuid_offset should be all zeros + addresses[7] = &foo1; + addresses[8] = &foo2; + addresses[9] = &foo3; + + struct dyld_image_uuid_offset infos[10]; + _dyld_images_for_addresses(10, addresses, infos); + + for (int i=0; i < 10; ++i) { + uuid_string_t str; + uuid_unparse_upper(infos[i].uuid, str); + printf("0x%09llX 0x%08llX %s\n", (long long)infos[i].image, infos[i].offsetInImage, str); + } + + if ( infos[0].image != infos[1].image ) + printf("[FAIL] _dyld_images_for_addresses 1\n"); + else if ( infos[0].image != infos[2].image ) + printf("[FAIL] _dyld_images_for_addresses 2\n"); + else if ( infos[0].image != infos[3].image ) + printf("[FAIL] _dyld_images_for_addresses 3\n"); + else if ( infos[0].image != infos[4].image ) + printf("[FAIL] _dyld_images_for_addresses 4\n"); + else if ( infos[0].image != infos[5].image ) + printf("[FAIL] _dyld_images_for_addresses 5\n"); + else if ( infos[6].image != NULL ) + printf("[FAIL] _dyld_images_for_addresses 6\n"); + else if ( infos[7].image != infos[8].image ) + printf("[FAIL] _dyld_images_for_addresses 7\n"); + else if ( infos[7].image != infos[9].image ) + printf("[FAIL] _dyld_images_for_addresses 8\n"); + else if ( infos[0].image == infos[7].image ) + printf("[FAIL] _dyld_images_for_addresses 9\n"); + else if ( uuid_compare(infos[0].uuid, infos[1].uuid) != 0 ) + printf("[FAIL] _dyld_images_for_addresses 10\n"); + else if ( uuid_compare(infos[0].uuid, infos[2].uuid) != 0 ) + printf("[FAIL] _dyld_images_for_addresses 11\n"); + else if ( uuid_compare(infos[0].uuid, infos[3].uuid) != 0 ) + printf("[FAIL] _dyld_images_for_addresses 12\n"); + else if ( uuid_compare(infos[0].uuid, infos[4].uuid) != 0 ) + printf("[FAIL] _dyld_images_for_addresses 13\n"); + else if ( uuid_compare(infos[0].uuid, infos[5].uuid) != 0 ) + printf("[FAIL] _dyld_images_for_addresses 14\n"); + else if ( uuid_is_null(infos[6].uuid) == 0 ) + printf("[FAIL] _dyld_images_for_addresses 15\n"); + else if ( uuid_compare(infos[7].uuid, infos[8].uuid) != 0 ) + printf("[FAIL] _dyld_images_for_addresses 16\n"); + else if ( uuid_compare(infos[7].uuid, infos[9].uuid) != 0 ) + printf("[FAIL] _dyld_images_for_addresses 17\n"); + else if ( uuid_compare(infos[0].uuid, infos[7].uuid) == 0 ) + printf("[FAIL] _dyld_images_for_addresses 18\n"); + else + printf("[PASS] _dyld_images_for_addresses\n"); + return 0; +} + diff --git a/testing/test-cases/_dyld_is_memory_immutable.dtest/main.c b/testing/test-cases/_dyld_is_memory_immutable.dtest/main.c index a58b85b..eab0fb5 100644 --- a/testing/test-cases/_dyld_is_memory_immutable.dtest/main.c +++ b/testing/test-cases/_dyld_is_memory_immutable.dtest/main.c @@ -11,6 +11,20 @@ #include #include +#if __has_feature(ptrauth_calls) + #include +#endif + +static const void* stripPointer(const void* ptr) +{ +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + + typedef const char* (*BarProc)(void); extern uint32_t _cpu_capabilities; @@ -45,8 +59,8 @@ int main() return 0; } - if ( !_dyld_is_memory_immutable(&strcpy, 4) ) { - printf("[FAIL] _dyld_is_memory_immutable() returned false for function in dyld shared cache\n"); + if ( !_dyld_is_memory_immutable(stripPointer((void*)&strcpy), 4) ) { + printf("[FAIL] _dyld_is_memory_immutable() returned false for strcpy function in dyld shared cache\n"); return 0; } diff --git a/testing/test-cases/_dyld_register_for_image_loads.dtest/foo.c b/testing/test-cases/_dyld_register_for_image_loads.dtest/foo.c new file mode 100644 index 0000000..85e6cd8 --- /dev/null +++ b/testing/test-cases/_dyld_register_for_image_loads.dtest/foo.c @@ -0,0 +1 @@ +void foo() {} diff --git a/testing/test-cases/_dyld_register_for_image_loads.dtest/main.cxx b/testing/test-cases/_dyld_register_for_image_loads.dtest/main.cxx new file mode 100644 index 0000000..3ed7f41 --- /dev/null +++ b/testing/test-cases/_dyld_register_for_image_loads.dtest/main.cxx @@ -0,0 +1,101 @@ + +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CXX main.cxx -o $BUILD_DIR/dyld_register_test.exe $BUILD_DIR/libfoo.dylib -DRUN_DIR="$RUN_DIR" +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libfoo2.dylib -o $BUILD_DIR/libfoo2.dylib +// BUILD: $CC foo.c -bundle -o $BUILD_DIR/foo.bundle + +// RUN: ./dyld_register_test.exe + +#include +#include +#include +#include +#include + +#include + +extern "C" void foo(); + +extern mach_header __dso_handle; + +static std::unordered_set sCurrentImages; + +static void notify(const mach_header* mh, const char* path, bool unloadable) +{ + fprintf(stderr, "mh=%p, path=%s, unloadable=%d\n", mh, path, unloadable); + if ( sCurrentImages.count(mh) != 0 ) { + printf("[FAIL] _dyld_register_for_image_loads: notified twice about %p\n", mh); + exit(0); + } + sCurrentImages.insert(mh); + + const char* leaf = strrchr(path, '/'); + if ( unloadable ) { + if ( (strcmp(leaf, "/libfoo2.dylib") != 0) && (strcmp(leaf, "/foo.bundle") != 0) ) { + printf("[FAIL] _dyld_register_for_image_loads: image incorrectly marked unloadable %p %s\n", mh, path); + exit(0); + } + } + else { + if ( (strcmp(leaf, "/libfoo2.dylib") == 0) || (strcmp(leaf, "/foo.bundle") == 0) ) { + printf("[FAIL] _dyld_register_for_image_loads: image incorrectly marked as not unloadable %p %s\n", mh, path); + exit(0); + } + } +} + + +int main() +{ + printf("[BEGIN] _dyld_register_for_image_loads\n"); + + _dyld_register_for_image_loads(¬ify); + + // verify we were notified about already loaded images + if ( sCurrentImages.count(&__dso_handle) == 0 ) { + printf("[FAIL] _dyld_register_for_image_loads() did not notify us about main executable\n"); + exit(0); + } + const mach_header* libSysMH = dyld_image_header_containing_address((void*)&printf); + if ( sCurrentImages.count(libSysMH) == 0 ) { + printf("[FAIL] _dyld_register_for_image_loads() did not notify us about libsystem_c.dylib\n"); + exit(0); + } + const mach_header* libFoo = dyld_image_header_containing_address((void*)&foo); + if ( sCurrentImages.count(libFoo) == 0 ) { + printf("[FAIL] _dyld_register_for_image_loads() did not notify us about libfoo.dylib\n"); + exit(0); + } + + // verify we were notified about load of libfoo2.dylib + void* handle2 = dlopen(RUN_DIR "/libfoo2.dylib", RTLD_FIRST); + if ( handle2 == NULL ) { + printf("[FAIL] dlopen(\"%s\") failed with: %s\n", RUN_DIR "/libfoo.dylib", dlerror()); + exit(0); + } + const void* libfoo2Foo = dlsym(handle2, "foo"); + const mach_header* libfoo2MH = dyld_image_header_containing_address(libfoo2Foo); + if ( sCurrentImages.count(libfoo2MH) == 0 ) { + printf("[FAIL] _dyld_register_for_image_loads() did not notify us about libfoo2.dylib\n"); + exit(0); + } + + // verify we were notified about load of foo.bundle + void* handleB = dlopen(RUN_DIR "/foo.bundle", RTLD_FIRST); + if ( handleB == NULL ) { + printf("[FAIL] dlopen(\"%s\") failed with: %s\n", RUN_DIR "/foo.bundle", dlerror()); + exit(0); + } + const void* libfooBFoo = dlsym(handle2, "foo"); + const mach_header* libfooB = dyld_image_header_containing_address(libfooBFoo); + if ( sCurrentImages.count(libfooB) == 0 ) { + printf("[FAIL] _dyld_register_for_image_loads() did not notify us about foo.bundle\n"); + exit(0); + } + + + + printf("[PASS] _dyld_register_for_image_loads\n"); + return 0; +} + diff --git a/testing/test-cases/cwd-relative-load.dtest/foo.c b/testing/test-cases/cwd-relative-load.dtest/foo.c new file mode 100644 index 0000000..723758f --- /dev/null +++ b/testing/test-cases/cwd-relative-load.dtest/foo.c @@ -0,0 +1 @@ +int foo = 42; diff --git a/testing/test-cases/cwd-relative-load.dtest/main.c b/testing/test-cases/cwd-relative-load.dtest/main.c new file mode 100644 index 0000000..8f2042f --- /dev/null +++ b/testing/test-cases/cwd-relative-load.dtest/main.c @@ -0,0 +1,28 @@ + + +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name libfoo.dylib +// BUILD: $CC main.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/cwd-load.exe +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/cwd-load.exe + +// RUN: ./cwd-load.exe + +// libfoo.dylib is loaded from the current directory (not an absolute path) + + +#include + +extern int foo; + + +int main() +{ + printf("[BEGIN] cwd-relative-load\n"); + if ( foo == 42 ) + printf("[PASS] cwd-relative-load\n"); + else + printf("[FAIL] cwd-relative-load, wrong value\n"); + + return 0; +} + + diff --git a/testing/test-cases/dladdr-basic.dtest/main.c b/testing/test-cases/dladdr-basic.dtest/main.c index eeeabca..2552006 100644 --- a/testing/test-cases/dladdr-basic.dtest/main.c +++ b/testing/test-cases/dladdr-basic.dtest/main.c @@ -9,6 +9,10 @@ #include #include +#if __has_feature(ptrauth_calls) + #include +#endif + int bar() { @@ -25,6 +29,14 @@ __attribute__((visibility("hidden"))) int hide() return 4; } +static const void *stripPointer(const void *ptr) { +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + // checks global symbol static void verifybar() { @@ -37,7 +49,7 @@ static void verifybar() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"bar\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &bar) { + if ( info.dli_saddr != stripPointer(&bar) ) { printf("[FAIL] dladdr()->dli_saddr is not &bar\n"); exit(0); } @@ -59,7 +71,7 @@ static void verifyfoo() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"foo\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &foo) { + if ( info.dli_saddr != stripPointer(&foo) ) { printf("[FAIL] dladdr()->dli_saddr is not &foo\n"); exit(0); } @@ -81,7 +93,7 @@ static void verifyhide() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"hide\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &hide) { + if ( info.dli_saddr != stripPointer(&hide) ) { printf("[FAIL] dladdr()->dli_saddr is not &hide\n"); exit(0); } @@ -103,7 +115,7 @@ static void verifymalloc() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"malloc\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &malloc) { + if ( info.dli_saddr != stripPointer(&malloc) ) { printf("[FAIL] dladdr()->dli_saddr is not &malloc\n"); exit(0); } @@ -113,6 +125,19 @@ static void verifymalloc() } } +// checks passing NULL for info parameter gracefully fails +static void verifyNULL() +{ + Dl_info info; + if ( dladdr(&malloc, NULL) != 0 ) { + printf("[FAIL] dladdr(&malloc, NULL) did not fail\n"); + exit(0); + } + if ( dladdr(NULL, NULL) != 0 ) { + printf("[FAIL] dladdr(NULL, NULL) did not fail\n"); + exit(0); + } +} int main() { @@ -121,7 +146,7 @@ int main() verifyhide(); verifyfoo(); verifymalloc(); - + verifyNULL(); printf("[PASS] dladdr-basic\n"); return 0; diff --git a/testing/test-cases/dladdr-dylib.dtest/foo.c b/testing/test-cases/dladdr-dylib.dtest/foo.c index e45b27f..92f7b0b 100644 --- a/testing/test-cases/dladdr-dylib.dtest/foo.c +++ b/testing/test-cases/dladdr-dylib.dtest/foo.c @@ -5,9 +5,23 @@ #include #include #include +#if __has_feature(ptrauth_calls) + #include +#endif extern void* __dso_handle; + +static const void* stripPointer(const void* ptr) +{ +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + + int dylib_bar() { return 2; @@ -35,7 +49,7 @@ static void verifybar() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"dylib_bar\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &dylib_bar) { + if ( info.dli_saddr != stripPointer(&dylib_bar) ) { printf("[FAIL] dladdr()->dli_saddr is not &dylib_bar\n"); exit(0); } @@ -57,7 +71,7 @@ static void verifyfoo() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"dylib_foo\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &dylib_foo) { + if ( info.dli_saddr != stripPointer(&dylib_foo) ) { printf("[FAIL] dladdr()->dli_saddr is not &dylib_foo\n"); exit(0); } @@ -69,7 +83,7 @@ static void verifyfoo() // checks hidden symbol static void verifyhide() -{ +{ Dl_info info; if ( dladdr(&dylib_hide, &info) == 0 ) { printf("[FAIL] dladdr(&dylib_hide, xx) failed\n"); @@ -79,7 +93,7 @@ static void verifyhide() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"dylib_hide\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &dylib_hide) { + if ( info.dli_saddr != stripPointer(&dylib_hide) ) { printf("[FAIL] dladdr()->dli_saddr is not &dylib_hide\n"); exit(0); } diff --git a/testing/test-cases/dladdr-dylib.dtest/main.c b/testing/test-cases/dladdr-dylib.dtest/main.c index 5d9d0c9..a4401ab 100644 --- a/testing/test-cases/dladdr-dylib.dtest/main.c +++ b/testing/test-cases/dladdr-dylib.dtest/main.c @@ -1,19 +1,33 @@ // 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 +// BUILD: $CC main.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/dladdr-dylib.exe -// RUN: ./dladdr-basic.exe +// RUN: ./dladdr-dylib.exe #include #include #include #include #include +#if __has_feature(ptrauth_calls) + #include +#endif extern void* __dso_handle; extern void verifyDylib(); + +static const void* stripPointer(const void* ptr) +{ +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + + int bar() { return 2; @@ -41,7 +55,7 @@ static void verifybar() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"bar\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &bar) { + if ( info.dli_saddr != stripPointer(&bar) ) { printf("[FAIL] dladdr()->dli_saddr is not &bar\n"); exit(0); } @@ -63,7 +77,7 @@ static void verifyfoo() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"foo\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &foo) { + if ( info.dli_saddr != stripPointer(&foo) ) { printf("[FAIL] dladdr()->dli_saddr is not &foo\n"); exit(0); } @@ -85,7 +99,7 @@ static void verifyhide() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"hide\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &hide) { + if ( info.dli_saddr != stripPointer(&hide) ) { printf("[FAIL] dladdr()->dli_saddr is not &hide\n"); exit(0); } @@ -107,7 +121,7 @@ static void verifymalloc() printf("[FAIL] dladdr()->dli_sname is \"%s\" instead of \"malloc\"\n", info.dli_sname); exit(0); } - if ( info.dli_saddr != &malloc) { + if ( info.dli_saddr != stripPointer(&malloc) ) { printf("[FAIL] dladdr()->dli_saddr is not &malloc\n"); exit(0); } @@ -120,7 +134,7 @@ static void verifymalloc() int main() { - printf("[BEGIN] dladdr-basic\n"); + printf("[BEGIN] dladdr-dylib\n"); verifybar(); verifyhide(); verifyfoo(); @@ -128,7 +142,7 @@ int main() verifyDylib(); - printf("[PASS] dladdr-basic\n"); + printf("[PASS] dladdr-dylib\n"); return 0; } diff --git a/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo1.c b/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo1.c new file mode 100644 index 0000000..5f56e2b --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo1.c @@ -0,0 +1,15 @@ + + +int __attribute__((weak)) coalA = 1; +int __attribute__((weak)) coalB = 1; + +int foo1_coalA() +{ + return coalA; +} + +int foo1_coalB() +{ + return coalB; +} + diff --git a/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo2.c b/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo2.c new file mode 100644 index 0000000..ea75d41 --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo2.c @@ -0,0 +1,20 @@ + + +int __attribute__((weak)) coalA = 2; +int __attribute__((weak)) coalB = 2; +int __attribute__((weak)) coalC = 2; + +int foo2_coalA() +{ + return coalA; +} + +int foo2_coalB() +{ + return coalB; +} + +int foo2_coalC() +{ + return coalC; +} diff --git a/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo3.c b/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo3.c new file mode 100644 index 0000000..76ad019 --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/foo3.c @@ -0,0 +1,20 @@ + + +int __attribute__((weak)) coalA = 3; +int __attribute__((weak)) coalB = 3; +int __attribute__((weak)) coalC = 3; + +int foo3_coalA() +{ + return coalA; +} + +int foo3_coalB() +{ + return coalB; +} + +int foo3_coalC() +{ + return coalC; +} diff --git a/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/main.c b/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/main.c new file mode 100644 index 0000000..8c91cb5 --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_LOCAL-coalesce.dtest/main.c @@ -0,0 +1,122 @@ + +// BUILD: $CC foo1.c -dynamiclib -install_name $RUN_DIR/libfoo1.dylib -o $BUILD_DIR/libfoo1.dylib +// BUILD: $CC foo2.c -dynamiclib -install_name $RUN_DIR/libfoo2.dylib -o $BUILD_DIR/libfoo2.dylib +// BUILD: $CC foo3.c -dynamiclib -install_name $RUN_DIR/libfoo3.dylib -o $BUILD_DIR/libfoo3.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-RTLD_LOCAL-coalesce.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlopen-RTLD_LOCAL-coalesce.exe + +#include +#include +#include +#include + + +/// +/// This tests the interaction of RTLD_LOCAL and weak-def coalescing. +/// +/// Normally, (for correct C++ ODR), dyld coalesces all weak-def symbols +/// across all images, so that only one copy of each weak symbol name +/// is in use. But, dlopen with RTLD_LOCAL means to "hide" the symbols in +/// that one (top) image being loaded. +/// +/// + +// main *coalA +// libfoo1.dylib coalA *coalB +// libfoo2.dylib coalA coalB coalC // loaded with RTLD_LOCAL +// libfoo3.dylib coalA coalB *coalC +// + +typedef int (*IntProc)(void); + +int __attribute__((weak)) coalA = 0; + +int main() +{ + printf("[BEGIN] dlopen-RTLD_LOCAL-coalesce\n"); + + /// + /// Load three foo dylibs in order + /// + void* handle1 = dlopen(RUN_DIR "/libfoo1.dylib", RTLD_GLOBAL); + void* handle2 = dlopen(RUN_DIR "/libfoo2.dylib", RTLD_LOCAL); + void* handle3 = dlopen(RUN_DIR "/libfoo3.dylib", RTLD_GLOBAL); + if ( handle1 == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-coalesce: dlopen(libfoo1.dylib, RTLD_GLOBAL) failed but it should have worked: %s\n", dlerror()); + return 0; + } + if ( handle2 == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-coalesce: dlopen(libfoo2.dylib, RTLD_LOCAL) failed but it should have worked: %s\n", dlerror()); + return 0; + } + if ( handle3 == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-coalesce: dlopen(libfoo3.dylib, RTLD_GLOBAL) failed but it should have worked: %s\n", dlerror()); + return 0; + } + + + /// + /// Get accessor functions + /// + IntProc foo1_coalA = (IntProc)dlsym(handle1, "foo1_coalA"); + IntProc foo1_coalB = (IntProc)dlsym(handle1, "foo1_coalB"); + IntProc foo2_coalA = (IntProc)dlsym(handle2, "foo2_coalA"); + IntProc foo2_coalB = (IntProc)dlsym(handle2, "foo2_coalB"); + IntProc foo2_coalC = (IntProc)dlsym(handle2, "foo2_coalC"); + IntProc foo3_coalA = (IntProc)dlsym(handle3, "foo3_coalA"); + IntProc foo3_coalB = (IntProc)dlsym(handle3, "foo3_coalB"); + IntProc foo3_coalC = (IntProc)dlsym(handle3, "foo3_coalC"); + if ( !foo1_coalA || !foo1_coalB || + !foo2_coalA || !foo2_coalB || !foo2_coalC || + !foo3_coalA || !foo3_coalB || !foo3_coalC ) { + printf("[FAIL] dlopen-RTLD_LOCAL-coalesce: dlsym() failed\n"); + return 0; + } + + /// + /// Get values for each coal[ABC] seen in each image + /// + int foo1A = (*foo1_coalA)(); + int foo1B = (*foo1_coalB)(); + int foo2A = (*foo2_coalA)(); + int foo2B = (*foo2_coalB)(); + int foo2C = (*foo2_coalC)(); + int foo3A = (*foo3_coalA)(); + int foo3B = (*foo3_coalB)(); + int foo3C = (*foo3_coalC)(); + printf("coalA in main: %d\n", coalA); + printf("coalA in libfoo1: %d\n", foo1A); + printf("coalA in libfoo2: %d\n", foo2A); + printf("coalA in libfoo3: %d\n", foo3A); + + printf("coalB in libfoo1: %d\n", foo1B); + printf("coalB in libfoo2: %d\n", foo2B); + printf("coalB in libfoo3: %d\n", foo3B); + + printf("coalC in libfoo2: %d\n", foo2C); + printf("coalC in libfoo3: %d\n", foo3C); + + + + /// + /// Verify coalescing was done properly (foo2 was skipped because of RTLD_LOCAL) + /// + if ( (foo1A != 0) || (foo2A != 0) || (foo3A != 0) || (coalA != 0) ) { + printf("[FAIL] dlopen-RTLD_LOCAL-coalesce: coalA was not coalesced properly\n"); + return 0; + } + if ( (foo1B != 1) || (foo2B != 1) || (foo3B != 1) ) { + printf("[FAIL] dlopen-RTLD_LOCAL-coalesce: coalB was not coalesced properly\n"); + return 0; + } + if ( (foo2C != 2) || (foo3C != 3) ) { + printf("[FAIL] dlopen-RTLD_LOCAL-coalesce: coalC was not coalesced properly\n"); + return 0; + } + + + + printf("[PASS] dlopen-RTLD_LOCAL-coalesce\n"); + return 0; +} diff --git a/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/bar.c b/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/bar.c new file mode 100644 index 0000000..698dc6c --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/bar.c @@ -0,0 +1,5 @@ +int bar() +{ + return 11; +} + diff --git a/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/foo.c b/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/foo.c new file mode 100644 index 0000000..13457f2 --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/main.c b/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/main.c new file mode 100644 index 0000000..79c113a --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_LOCAL-hides.dtest/main.c @@ -0,0 +1,74 @@ + +// 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 -o $BUILD_DIR/dlopen-RTLD_LOCAL-hides.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlopen-RTLD_LOCAL-hides.exe + +#include +#include +#include +#include + + +int main() +{ + printf("[BEGIN] dlopen-RTLD_LOCAL-hides\n"); + + /// + /// This tests that RTLD_LOCAL prevents RTLD_DEFAULT from finding symbols, but can be found via handle + /// + void* handle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_LOCAL); + if ( handle == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-hides: dlopen(libfoo.dylib, RTLD_LOCAL) failed but it should have worked: %s\n", dlerror()); + return 0; + } + void* sym = dlsym(handle, "foo"); + if ( sym == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-hides: dlsym(handle, \"foo\") failed but it should have worked: %s\n", dlerror()); + return 0; + } + void* sym2 = dlsym(RTLD_DEFAULT, "foo"); + if ( sym2 != NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-hides: dlsym(RTLD_DEFAULT, \"foo\") succeeded but it should have failed\n"); + return 0; + } + + + /// + /// This tests that RTLD_GLOBAL after RTLD_LOCAL allows RTLD_DEFAULT to find symbols + /// + void* handle2 = dlopen(RUN_DIR "/libfoo.dylib", RTLD_GLOBAL); + if ( handle2 == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-hides: dlopen(libfoo.dylib, RTLD_GLOBAL) failed but it should have worked: %s\n", dlerror()); + return 0; + } + void* sym3 = dlsym(RTLD_DEFAULT, "foo"); + if ( sym3 == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-hides: dlsym(RTLD_DEFAULT, \"foo\") failed after upgrading to RTLD_GLOBAL\n"); + return 0; + } + + + /// + /// This tests that RTLD_LOCAL after RTLD_GLOBAL does not block RTLD_DEFAULT from finding symbols + /// + void* handle3 = dlopen(RUN_DIR "/libbar.dylib", RTLD_GLOBAL); + if ( handle3 == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-hides: dlopen(libbar.dylib, RTLD_GLOBAL) failed but it should have worked: %s\n", dlerror()); + return 0; + } + void* handle4 = dlopen(RUN_DIR "/libbar.dylib", RTLD_LOCAL); + if ( handle4 == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-hides: dlopen(libbar.dylib, RTLD_LOCAL) failed but it should have worked: %s\n", dlerror()); + return 0; + } + void* sym4 = dlsym(RTLD_DEFAULT, "bar"); + if ( sym4 == NULL ) { + printf("[FAIL] dlopen-RTLD_LOCAL-hides: dlsym(RTLD_DEFAULT, \"bar\") failed but it should have worked: %s\n", dlerror()); + return 0; + } + + printf("[PASS] dlopen-RTLD_LOCAL-hides\n"); + return 0; +} diff --git a/testing/test-cases/dlopen-RTLD_NODELETE.dtest/bar.c b/testing/test-cases/dlopen-RTLD_NODELETE.dtest/bar.c new file mode 100644 index 0000000..95ef2ab --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_NODELETE.dtest/bar.c @@ -0,0 +1,4 @@ + + +int bar = 11; + diff --git a/testing/test-cases/dlopen-RTLD_NODELETE.dtest/foo.c b/testing/test-cases/dlopen-RTLD_NODELETE.dtest/foo.c new file mode 100644 index 0000000..def52cd --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_NODELETE.dtest/foo.c @@ -0,0 +1,3 @@ + +int foo = 10; + diff --git a/testing/test-cases/dlopen-RTLD_NODELETE.dtest/main.c b/testing/test-cases/dlopen-RTLD_NODELETE.dtest/main.c new file mode 100644 index 0000000..3fff117 --- /dev/null +++ b/testing/test-cases/dlopen-RTLD_NODELETE.dtest/main.c @@ -0,0 +1,78 @@ + +// 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 -o $BUILD_DIR/dlopen-RTLD_NODELETE.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlopen-RTLD_NODELETE.exe + +#include +#include +#include +#include + + +int main() +{ + printf("[BEGIN] dlopen-RTLD_NODELETE\n"); + + /// + /// This tests that RTLD_NODELETE on first dlopen() blocks dlclose() from unloading image + /// + void* handle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_NODELETE); + if ( handle == NULL ) { + printf("[FAIL] dlopen-RTLD_NODELETE: dlopen(libfoo.dylib, RTLD_NODELETE) failed but it should have worked: %s\n", dlerror()); + return 0; + } + int* fooSym = (int*)dlsym(handle, "foo"); + if ( fooSym == NULL ) { + printf("[FAIL] dlopen-RTLD_NODELETE: dlsym(handle, \"foo\") failed but it should have worked: %s\n", dlerror()); + return 0; + } + int fooValue = *fooSym; + dlclose(handle); + Dl_info info; + if ( dladdr(fooSym, &info) != 0 ) { + printf("[FAIL] dlopen-RTLD_NODELETE: dladdr(fooSym, xx) succeeded as if libfoo.dylib was not unloaded\n"); + return 0; + } + // dereference foo pointer. If RTLD_NODELETE worked, this will not crash + if ( *fooSym != fooValue ) { + printf("[FAIL] dlopen-RTLD_NODELETE: value at fooSym changed\n"); + return 0; + } + + /// + /// This tests that RTLD_NODELETE on later dlopen() blocks dlclose() from unloading image + /// + void* handle2 = dlopen(RUN_DIR "/libbar.dylib", RTLD_GLOBAL); + if ( handle2 == NULL ) { + printf("[FAIL] dlopen-RTLD_NODELETE: dlopen(libfoo.dylib, RTLD_GLOBAL) failed but it should have worked: %s\n", dlerror()); + return 0; + } + int* barSym = (int*)dlsym(handle2, "bar"); + if ( barSym == NULL ) { + printf("[FAIL] dlopen-RTLD_NODELETE: dlsym(handle, \"bar\") failed but it should have worked: %s\n", dlerror()); + return 0; + } + int barValue = *barSym; + void* handle3 = dlopen(RUN_DIR "/libbar.dylib", RTLD_NODELETE); + if ( handle3 == NULL ) { + printf("[FAIL] dlopen-RTLD_NODELETE: dlopen(libfoo.dylib, RTLD_NODELETE) failed but it should have worked: %s\n", dlerror()); + return 0; + } + dlclose(handle2); + dlclose(handle3); + if ( dladdr(barSym, &info) != 0 ) { + printf("[FAIL] dlopen-RTLD_NODELETE: dladdr(barSym, xx) succeeded as if libbar.dylib was not unloaded\n"); + return 0; + } + // dereference foo pointer. If RTLD_NODELETE worked, this will not crash + if ( *barSym != barValue ) { + printf("[FAIL] dlopen-RTLD_NODELETE: value at barSym changed\n"); + return 0; + } + + + printf("[PASS] dlopen-RTLD_NODELETE\n"); + return 0; +} diff --git a/testing/test-cases/dlopen-atpath-restricted.dtest/bar.c b/testing/test-cases/dlopen-atpath-restricted.dtest/bar.c new file mode 100644 index 0000000..e425999 --- /dev/null +++ b/testing/test-cases/dlopen-atpath-restricted.dtest/bar.c @@ -0,0 +1 @@ +void bar() {} diff --git a/testing/test-cases/dlopen-atpath-restricted.dtest/foo.c b/testing/test-cases/dlopen-atpath-restricted.dtest/foo.c new file mode 100644 index 0000000..c8e9924 --- /dev/null +++ b/testing/test-cases/dlopen-atpath-restricted.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlopen-atpath-restricted.dtest/main.c b/testing/test-cases/dlopen-atpath-restricted.dtest/main.c new file mode 100644 index 0000000..bd55fb4 --- /dev/null +++ b/testing/test-cases/dlopen-atpath-restricted.dtest/main.c @@ -0,0 +1,80 @@ +// BUILD_ONLY: MacOSX + +// BUILD: mkdir -p $BUILD_DIR/test1 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/test1/libtest1.dylib -install_name @rpath/libtest1.dylib +// BUILD: $CC foo.c -bundle -o $BUILD_DIR/test1.bundle -Wl,-rpath,@loader_path/test1/ $BUILD_DIR/test1/libtest1.dylib + +// BUILD: mkdir -p $BUILD_DIR/test2 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/test2/libtest2.dylib -install_name @loader_path/test2/libtest2.dylib +// BUILD: $CC foo.c -bundle -o $BUILD_DIR/test2.bundle $BUILD_DIR/test2/libtest2.dylib + +// BUILD: mkdir -p $BUILD_DIR/test3 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/test3/libtest3.dylib -install_name @rpath/libtest3.dylib +// BUILD: $CC foo.c -bundle -o $BUILD_DIR/test3.bundle -Wl,-rpath,$RUN_DIR/test3 $BUILD_DIR/test3/libtest3.dylib + +// BUILD: mkdir -p $BUILD_DIR/test4 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/test4/libtest4.dylib -install_name @rpath/libtest4.dylib +// BUILD: $CC foo.c -bundle -o $BUILD_DIR/test4.bundle -Wl,-rpath,@executable_path/test4/ $BUILD_DIR/test4/libtest4.dylib + +// BUILD: mkdir -p $BUILD_DIR/test5 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/test5/libtest5.dylib -install_name @executable_path/test5/libtest5.dylib +// BUILD: $CC foo.c -bundle -o $BUILD_DIR/test5.bundle $BUILD_DIR/test5/libtest5.dylib + + + +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-restricted.exe -DRUN_DIR="$RUN_DIR" -sectcreate __RESTRICT __restrict /dev/null + +// RUN: ./dlopen-restricted.exe + +#include +#include + + +int main(int argc, const char* argv[]) +{ + printf("[BEGIN] dlopen-restricted\n"); + + // test1: LC_RPATH not in main executable uses @loader_path + void* handle = dlopen(RUN_DIR "/test1.bundle", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-restricted test1.bundle\n"); + return 0; + } + + // test2: @loader_path not in main executable + handle = dlopen(RUN_DIR "/test2.bundle", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-restricted test2.bundle\n"); + return 0; + } + + // test3: LC_RPATH not in main executable uses absolute path + handle = dlopen(RUN_DIR "/test3.bundle", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-restricted test3.bundle\n"); + return 0; + } + + // test4: [SHOULD FAIL] LC_RPATH not in main executable uses @executable_path + handle = dlopen(RUN_DIR "/test4.bundle", RTLD_LAZY); + if ( handle != NULL ) { + printf("[FAIL] dlopen-restricted test4.bundle dlopen() should not work\n"); + return 0; + } + + // test5: [SHOULD FAIL] @executable_path in LC_LOAD_DYLIB + handle = dlopen(RUN_DIR "/test5.bundle", RTLD_LAZY); + if ( handle != NULL ) { + printf("[FAIL] dlopen-restricted test5.bundle dlopen() should not work\n"); + return 0; + } + + + + printf("[PASS] dlopen-restricted\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-basic.dtest/main.c b/testing/test-cases/dlopen-basic.dtest/main.c index e506236..ddecb89 100644 --- a/testing/test-cases/dlopen-basic.dtest/main.c +++ b/testing/test-cases/dlopen-basic.dtest/main.c @@ -28,7 +28,7 @@ static void tryImage(const char* path) int result = dlclose(handle); if ( result != 0 ) { - printf("dlclose() returned %c\n", result); + printf("dlclose() returned %d, dlerror()=%s\n", result, dlerror()); printf("[FAIL] dlopen-basic %s\n", path); return; } diff --git a/testing/test-cases/dlopen-flat.dtest/main.c b/testing/test-cases/dlopen-flat.dtest/main.c index 194b6af..d6aed69 100644 --- a/testing/test-cases/dlopen-flat.dtest/main.c +++ b/testing/test-cases/dlopen-flat.dtest/main.c @@ -3,7 +3,7 @@ // 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 +// RUN: ./dlopen-flat.exe #include #include @@ -17,8 +17,7 @@ int main() { // Foo exports foo() void* fooHandle = 0; { - const char* path = RUN_DIR "/libfoo.dylib"; - fooHandle = dlopen(path, RTLD_LAZY); + fooHandle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_LAZY); if (!fooHandle) { printf("dlopen failed with error: %s\n", dlerror()); return 1; @@ -39,8 +38,7 @@ int main() { // Open foo again which should do something. { - const char* path = RUN_DIR "/libfoo.dylib"; - fooHandle = dlopen(path, RTLD_LAZY); + fooHandle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_LAZY); if (!fooHandle) { printf("dlopen failed with error: %s\n", dlerror()); return 1; @@ -55,8 +53,7 @@ int main() { // Bar is going to resolve foo() void* barHandle = 0; { - const char* path = RUN_DIR "/libbar.dylib"; - barHandle = dlopen(path, RTLD_LAZY); + barHandle = dlopen(RUN_DIR "/libbar.dylib", RTLD_LAZY); if (!barHandle) { printf("dlopen failed with error: %s\n", dlerror()); return 1; @@ -77,8 +74,7 @@ int main() { // Open foo again which shouldn't do anything. { - const char* path = RUN_DIR "/libfoo.dylib"; - fooHandle = dlopen(path, RTLD_LAZY); + fooHandle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_LAZY); if (!fooHandle) { printf("dlopen failed with error: %s\n", dlerror()); return 1; diff --git a/testing/test-cases/dlopen-haswell.dtest/a.c b/testing/test-cases/dlopen-haswell.dtest/a.c new file mode 100644 index 0000000..d312ffb --- /dev/null +++ b/testing/test-cases/dlopen-haswell.dtest/a.c @@ -0,0 +1,10 @@ +#include + +bool isHaswell() +{ +#if __x86_64h__ + return true; +#else + return false; +#endif +} diff --git a/testing/test-cases/dlopen-haswell.dtest/main.c b/testing/test-cases/dlopen-haswell.dtest/main.c new file mode 100644 index 0000000..a76b9c5 --- /dev/null +++ b/testing/test-cases/dlopen-haswell.dtest/main.c @@ -0,0 +1,64 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC a.c -dynamiclib -arch x86_64h -o $BUILD_DIR/libHaswellCheck.dylib -install_name $RUN_DIR/libHaswellCheck.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-haswell.exe -DRUN_DIR="$RUN_DIR" + +// RUN: ./dlopen-haswell.exe + + +#include +#include +#include +#include +#include +#include + +typedef bool (*BoolFunc)(void); + + +bool isHaswell_dynamic() +{ + struct host_basic_info info; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + mach_port_t hostPort = mach_host_self(); + kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count); + mach_port_deallocate(mach_task_self(), hostPort); + if ( result == KERN_SUCCESS ) { + return (info.cpu_subtype == CPU_SUBTYPE_X86_64_H); + } + return false; +} + + +int main(int arg, const char* argv[]) +{ + printf("[BEGIN] dlopen-haswell\n"); + + void* handle = dlopen(RUN_DIR "/libHaswellCheck.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-haswell dlopen\n"); + return 0; + } + + BoolFunc libFunc = (BoolFunc)dlsym(handle, "isHaswell"); + if ( libFunc == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-haswell dlsym\n"); + return 0; + } + + // check if haswell slice of libHaswellCheck.dylib was loaded on haswell machines + bool dylibIsHaswellSlice = (*libFunc)(); + bool runtimeIsHaswell = isHaswell_dynamic(); + + if ( dylibIsHaswellSlice != runtimeIsHaswell ) + printf("[FAIL] dlopen-haswell, dylibIsHaswellSlice=%d, runtimeIsHaswell=%d\n", dylibIsHaswellSlice, runtimeIsHaswell); + else + printf("[PASS] dlopen-haswell\n"); + + return 0; +} + + + diff --git a/testing/test-cases/dlopen-haswell/a.c b/testing/test-cases/dlopen-haswell/a.c new file mode 100644 index 0000000..c02be2d --- /dev/null +++ b/testing/test-cases/dlopen-haswell/a.c @@ -0,0 +1 @@ +void zzz() {} diff --git a/testing/test-cases/dlopen-in-init.dtest/foo.c b/testing/test-cases/dlopen-in-init.dtest/foo.c new file mode 100644 index 0000000..5f91679 --- /dev/null +++ b/testing/test-cases/dlopen-in-init.dtest/foo.c @@ -0,0 +1,36 @@ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static void* work1(void* arg) +{ + void* h = dlopen("System/Library/Frameworks/Foundation.framework/Foundation", 0); + + return NULL; +} + + +__attribute__((constructor)) +void myinit() +{ + pthread_t workerThread; + + if ( pthread_create(&workerThread, NULL, work1, NULL) != 0 ) { + printf("[FAIL] dlopen-in-init, pthread_create\n"); + return; + } + + void* dummy; + pthread_join(workerThread, &dummy); +} + diff --git a/testing/test-cases/dlopen-in-init.dtest/main.c b/testing/test-cases/dlopen-in-init.dtest/main.c new file mode 100644 index 0000000..d96489e --- /dev/null +++ b/testing/test-cases/dlopen-in-init.dtest/main.c @@ -0,0 +1,29 @@ + + +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-in-init.exe $BUILD_DIR/libfoo.dylib + +// RUN: ./dlopen-in-init.exe + +#include +#include +#include + + +__attribute__((constructor)) +void myinit() +{ + +} + + +int main() +{ + printf("[BEGIN] dlopen-in-init\n"); + + // The test is for libdyld.dylib to not crash when libfoo.dylib dlopen() stuff in its initializer + + printf("[PASS] dlopen-in-init\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-realpath.dtest/main.c b/testing/test-cases/dlopen-realpath.dtest/main.c index b34e008..c622e9f 100644 --- a/testing/test-cases/dlopen-realpath.dtest/main.c +++ b/testing/test-cases/dlopen-realpath.dtest/main.c @@ -2,7 +2,7 @@ // BUILD: $CC main.c -o $BUILD_DIR/dlopen-realpath.exe // BUILD: cd $BUILD_DIR && ln -s ./IOKit.framework/IOKit IOKit && ln -s /System/Library/Frameworks/IOKit.framework IOKit.framework -// RUN: ./dlopen-realpath.exe +// RUN: DYLD_FALLBACK_LIBRARY_PATH=/baz ./dlopen-realpath.exe #include #include @@ -20,7 +20,7 @@ static void tryImage(const char* path) int result = dlclose(handle); if ( result != 0 ) { - printf("dlclose() returned %c\n", result); + printf("dlclose(%p): %s\n", handle, dlerror()); printf("[FAIL] dlopen-realpath %s\n", path); return; } @@ -34,7 +34,15 @@ int main() { tryImage("./IOKit.framework/IOKit"); tryImage("./././IOKit/../IOKit.framework/IOKit"); - tryImage("./IOKit"); + tryImage("./IOKit"); + + // Also try libSystem which has an alias in the OS to /usr/lib/libSystem.B.dylib + tryImage("/usr/lib/libSystem.dylib"); + + // Also try using non-canonical path + // This requires DYLD_FALLBACK_LIBRARY_PATH to be disabled, otherwise it is found that way + tryImage("//usr/lib/libSystem.dylib"); + tryImage("/usr/./lib/libSystem.dylib"); return 0; } diff --git a/testing/test-cases/dlopen-rpath-from-dylib.dtest/bar.c b/testing/test-cases/dlopen-rpath-from-dylib.dtest/bar.c new file mode 100644 index 0000000..c86856e --- /dev/null +++ b/testing/test-cases/dlopen-rpath-from-dylib.dtest/bar.c @@ -0,0 +1,5 @@ +int bar() +{ + return 0; +} + diff --git a/testing/test-cases/dlopen-rpath-from-dylib.dtest/main.c b/testing/test-cases/dlopen-rpath-from-dylib.dtest/main.c new file mode 100644 index 0000000..ab6936e --- /dev/null +++ b/testing/test-cases/dlopen-rpath-from-dylib.dtest/main.c @@ -0,0 +1,29 @@ + +// BUILD: mkdir -p $BUILD_DIR/dir +// BUILD: $CC bar.c -dynamiclib -install_name @rpath/libbar.dylib -o $BUILD_DIR/dir/libbar.dylib +// BUILD: $CC test.c -dynamiclib -install_name $RUN_DIR/libtest.dylib -o $BUILD_DIR/libtest.dylib -rpath @loader_path/dir +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-rpath-from-dylib.exe $BUILD_DIR/libtest.dylib + +// RUN: ./dlopen-rpath-from-dylib.exe + +#include +#include +#include + + +/// test that a call to dlopen() from a dylib uses the LC_RPATH from that dylib + +extern bool test_dlopen(); + +int main() +{ + printf("[BEGIN] dlopen-rpath-from-dylib\n"); + + if ( test_dlopen() ) + printf("[PASS] dlopen-rpath-from-dylib\n"); + else + printf("[FAIL] dlopen-rpath-from-dylib\n"); + + return 0; +} + diff --git a/testing/test-cases/dlopen-rpath-from-dylib.dtest/test.c b/testing/test-cases/dlopen-rpath-from-dylib.dtest/test.c new file mode 100644 index 0000000..dba4287 --- /dev/null +++ b/testing/test-cases/dlopen-rpath-from-dylib.dtest/test.c @@ -0,0 +1,19 @@ + +#include +#include +#include + +bool test_dlopen() +{ + void* handle = dlopen("@rpath/libbar.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlopen-rpath-from-dylib: dlopen(@rpath/libbar.dylib) failed: %s\n", dlerror()); + return false; + } + + dlclose(handle); + + return true; +} + + diff --git a/testing/test-cases/dlopen-rpath-implicit.dtest/foo.c b/testing/test-cases/dlopen-rpath-implicit.dtest/foo.c new file mode 100644 index 0000000..c8e9924 --- /dev/null +++ b/testing/test-cases/dlopen-rpath-implicit.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlopen-rpath-implicit.dtest/main.c b/testing/test-cases/dlopen-rpath-implicit.dtest/main.c new file mode 100644 index 0000000..6d43d7f --- /dev/null +++ b/testing/test-cases/dlopen-rpath-implicit.dtest/main.c @@ -0,0 +1,30 @@ + +// BUILD: mkdir -p $BUILD_DIR/dir1 +// BUILD: $CC foo.c -dynamiclib -install_name @rpath/libimplicitrpath.dylib -o $BUILD_DIR/dir1/libimplicitrpath.dylib +// BUILD: $CC main.c -o $BUILD_DIR/dlopen-rpath-implicit.exe -rpath @loader_path/dir1 + +// RUN: ./dlopen-rpath-implicit.exe + +#include +#include + + +/// test that a leaf name passed to dlopen() searches the rpath + +int main() +{ + printf("[BEGIN] dlopen-rpath-implicit\n"); + + void* handle = dlopen("libimplicitrpath.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("[FAIL] dlopen-rpath-implicit: dlopen(libimplicitrpath.dylib) failed: %s\n", dlerror()); + return 0; + } + + dlclose(handle); + + printf("[PASS] dlopen-rpath-implicit\n"); + + return 0; +} + diff --git a/testing/test-cases/dlopen-rpath-prev-override.dtest/bad.c b/testing/test-cases/dlopen-rpath-prev-override.dtest/bad.c new file mode 100644 index 0000000..08b8b6a --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev-override.dtest/bad.c @@ -0,0 +1,10 @@ +#include +#include + + +__attribute__((constructor)) +void init() +{ + printf("[FAIL] dlopen-rpath-prev-override\n"); + exit(0); +} diff --git a/testing/test-cases/dlopen-rpath-prev-override.dtest/dyn.c b/testing/test-cases/dlopen-rpath-prev-override.dtest/dyn.c new file mode 100644 index 0000000..852b89b --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev-override.dtest/dyn.c @@ -0,0 +1,5 @@ +int sub2() +{ + return 2; +} + diff --git a/testing/test-cases/dlopen-rpath-prev-override.dtest/foo.c b/testing/test-cases/dlopen-rpath-prev-override.dtest/foo.c new file mode 100644 index 0000000..c8e9924 --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev-override.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlopen-rpath-prev-override.dtest/good.c b/testing/test-cases/dlopen-rpath-prev-override.dtest/good.c new file mode 100644 index 0000000..98c93f6 --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev-override.dtest/good.c @@ -0,0 +1,5 @@ +int sub1() +{ + return 1; +} + diff --git a/testing/test-cases/dlopen-rpath-prev-override.dtest/main.c b/testing/test-cases/dlopen-rpath-prev-override.dtest/main.c new file mode 100644 index 0000000..8aa206f --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev-override.dtest/main.c @@ -0,0 +1,33 @@ + +// BUILD: mkdir -p $BUILD_DIR/dir $BUILD_DIR/good $BUILD_DIR/bad +// BUILD: $CC good.c -dynamiclib -install_name @rpath/libtest.dylib -o $BUILD_DIR/good/libtest.dylib +// BUILD: $CC bad.c -dynamiclib -install_name @rpath/libtest.dylib -o $BUILD_DIR/bad/libtest.dylib +// BUILD: $CC dyn.c -dynamiclib -install_name @rpath/libdynamic.dylib -o $BUILD_DIR/dir/libdynamic.dylib $BUILD_DIR/good/libtest.dylib -rpath @loader_path/../bad +// BUILD: $CC main.c $BUILD_DIR/good/libtest.dylib -DRUN_DIR="$RUN_DIR" -o $BUILD_DIR/dlopen-rpath-prev-override.exe -rpath @loader_path/good + +// RUN: ./dlopen-rpath-prev-override.exe + +// Processing of @rpath in dlopen()s always checks existing loaded images before using LC_RPATHs to find images on disk +// +// main links with @rpath/libtest.dylib found via -rpath @loader_path/good +// main dlopen()s libdynamic.dylib which links with @rpath/libtest.dylib and has LR_PATH to find it in bad/, +// but since it was already loaded from good/, that one should be re-used. + +#include +#include + +int main() +{ + printf("[BEGIN] dlopen-rpath-prev-override\n"); + + void* handle = dlopen(RUN_DIR "/dir/libdynamic.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-rpath-prev-override\n"); + return 0; + } + + printf("[PASS] dlopen-rpath-prev-override\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-rpath-prev.dtest/foo.c b/testing/test-cases/dlopen-rpath-prev.dtest/foo.c new file mode 100644 index 0000000..c8e9924 --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return 10; +} + diff --git a/testing/test-cases/dlopen-rpath-prev.dtest/main.c b/testing/test-cases/dlopen-rpath-prev.dtest/main.c new file mode 100644 index 0000000..67741d0 --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev.dtest/main.c @@ -0,0 +1,30 @@ + +// BUILD: mkdir -p $BUILD_DIR/dir1 $BUILD_DIR/dir2 +// BUILD: $CC sub1.c -dynamiclib -install_name @rpath/librpathstatic.dylib -o $BUILD_DIR/dir1/librpathstatic.dylib +// BUILD: $CC sub2.c -dynamiclib -install_name @rpath/libdynamic.dylib -o $BUILD_DIR/dir2/libdynamic.dylib $BUILD_DIR/dir1/librpathstatic.dylib +// BUILD: $CC foo.c -dynamiclib -install_name $RUN_DIR/libstatic.dylib -o $BUILD_DIR/libstatic.dylib -rpath @loader_path/dir1 $BUILD_DIR/dir1/librpathstatic.dylib +// BUILD: $CC main.c $BUILD_DIR/libstatic.dylib -DRUN_DIR="$RUN_DIR" -o $BUILD_DIR/dlopen-rpath-prev.exe + +// RUN: ./dlopen-rpath-prev.exe + +// main links with libstatic.dylib which uses rpath to link with dir1/librpathstatic.dylib +// main dlopen()s libdynamic.dylib which links with dir1/librpathstatic.dylib, but has no rpath for it and depends on it being already loaded + +#include +#include + +int main() +{ + printf("[BEGIN] dlopen-rpath-prev\n"); + + void* handle = dlopen(RUN_DIR "/dir2/libdynamic.dylib", RTLD_LAZY); + if ( handle == NULL ) { + printf("dlerror(): %s\n", dlerror()); + printf("[FAIL] dlopen-rpath-prev\n"); + return 0; + } + + printf("[PASS] dlopen-rpath-prev\n"); + return 0; +} + diff --git a/testing/test-cases/dlopen-rpath-prev.dtest/sub1.c b/testing/test-cases/dlopen-rpath-prev.dtest/sub1.c new file mode 100644 index 0000000..98c93f6 --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev.dtest/sub1.c @@ -0,0 +1,5 @@ +int sub1() +{ + return 1; +} + diff --git a/testing/test-cases/dlopen-rpath-prev.dtest/sub2.c b/testing/test-cases/dlopen-rpath-prev.dtest/sub2.c new file mode 100644 index 0000000..852b89b --- /dev/null +++ b/testing/test-cases/dlopen-rpath-prev.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 index 237dbd1..09f7e5c 100644 --- a/testing/test-cases/dtrace.dtest/main.c +++ b/testing/test-cases/dtrace.dtest/main.c @@ -1,3 +1,5 @@ +// BUILD_ONLY: MacOSX + // 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 @@ -20,8 +22,9 @@ int main() DYLD_TESTING_CALLBACK(); if (!DYLD_TESTING_CALLBACK_ENABLED()) - printf("[FAIL] !DYLD_TESTING_CALLBACK_ENABLED\n"); + printf("[FAIL] dtrace: DYLD_TESTING_CALLBACK_ENABLED() returned false\n"); + else + printf("[PASS] dtrace\n"); - printf("[PASS] dtrace\n"); return 0; } diff --git a/testing/test-cases/dyld_abort_payload.dtest/main.c b/testing/test-cases/dyld_abort_payload.dtest/main.c index e01234d..9807f09 100644 --- a/testing/test-cases/dyld_abort_payload.dtest/main.c +++ b/testing/test-cases/dyld_abort_payload.dtest/main.c @@ -1,11 +1,14 @@ // BUILD: $CC foo.c -dynamiclib -install_name /cant/find/me.dylib -o $BUILD_DIR/libmissing.dylib // BUILD: $CC emptyMain.c $BUILD_DIR/libmissing.dylib -o $BUILD_DIR/prog_missing_dylib.exe -// BUILD: $CC defSymbol.c -dynamiclib -install_name libMissingSymbols.dylib -o $BUILD_DIR/libMissingSymbols.dylib -// BUILD: $CC defSymbol.c -dynamiclib -install_name libMissingSymbols.dylib -o $BUILD_DIR/libHasSymbols.dylib -DHAS_SYMBOL +// BUILD: $CC defSymbol.c -dynamiclib -install_name $RUN_DIR/libMissingSymbols.dylib -o $BUILD_DIR/libMissingSymbols.dylib +// BUILD: $CC defSymbol.c -dynamiclib -install_name $RUN_DIR/libMissingSymbols.dylib -o $BUILD_DIR/libHasSymbols.dylib -DHAS_SYMBOL // BUILD: $CC useSymbol.c $BUILD_DIR/libHasSymbols.dylib -o $BUILD_DIR/prog_missing_symbol.exe // BUILD: $CC main.c -o $BUILD_DIR/dyld_abort_tests.exe +// NO_CRASH_LOG: prog_missing_dylib.exe +// NO_CRASH_LOG: prog_missing_symbol.exe + // RUN: ./dyld_abort_tests.exe #include @@ -44,16 +47,17 @@ static void childDied(int sig) info.eri_reason_buf_size = OS_REASON_BUFFER_MAX_SIZE; info.eri_kcd_buf = (user_addr_t)packReasonData; //fprintf(stderr, "info=%p\n", &info); - if ( proc_pidinfo(sChildPid, PROC_PIDEXITREASONINFO, 1, &info, PROC_PIDEXITREASONINFO_SIZE) != sizeof(struct proc_exitreasoninfo) ) { - printf("bad return size from proc_pidinfo()\n"); + int procResult = proc_pidinfo(sChildPid, PROC_PIDEXITREASONINFO, 1, &info, PROC_PIDEXITREASONINFO_SIZE); + if ( procResult != sizeof(struct proc_exitreasoninfo) ) { + printf("bad return size from proc_pidinfo(), %d expected %lu\n", procResult, PROC_PIDEXITREASONINFO_SIZE); return; } if ( info.eri_namespace != OS_REASON_DYLD ) { - printf("eri_namespace != OS_REASON_DYLD\n"); + printf("eri_namespace (%d) != OS_REASON_DYLD\n", info.eri_namespace); return; } if ( info.eri_code != sExpectedDyldReason ) { - printf("eri_code != %lld\n", sExpectedDyldReason); + printf("eri_code (%llu) != %lld\n", sExpectedDyldReason, info.eri_code); return; } kcdata_iter_t iter = kcdata_iter(packReasonData, info.eri_reason_buf_size); @@ -88,7 +92,7 @@ static void childDied(int sig) if ( sExpectedDylibPath != NULL ) { if ( dyldInfo->targetDylibPathOffset != 0 ) { const char* targetDylib = (char*)dyldInfo + dyldInfo->targetDylibPathOffset; - if ( strcmp(sExpectedDylibPath, targetDylib) != 0 ) { + if ( strstr(targetDylib, sExpectedDylibPath) == NULL ) { printf("dylib path (%s) not what expected (%s)\n", targetDylib, sExpectedDylibPath); return; } diff --git a/testing/test-cases/dyld_get_image_versions.dtest/main.c b/testing/test-cases/dyld_get_image_versions.dtest/main.c new file mode 100644 index 0000000..3a726b4 --- /dev/null +++ b/testing/test-cases/dyld_get_image_versions.dtest/main.c @@ -0,0 +1,40 @@ + +// BUILD: $CC main.c -o $BUILD_DIR/dyld_get_image_versions.exe + +// RUN: ./dyld_get_image_versions.exe + +#include +#include +#include + +extern struct mach_header __dso_handle; + +int main() +{ + printf("[BEGIN] dyld_get_image_versions\n"); + + // should succeed + dyld_get_image_versions(&__dso_handle, ^(dyld_platform_t platform, uint32_t sdkVersion, uint32_t minOS) { + printf("main binary: platform=%d, sdk=0x%08X, minOS-0x%08X\n", platform, sdkVersion, minOS); + }); + + uint8_t badFile[4096]; + struct mach_header* mh = (struct mach_header*)badFile; + mh->magic = MH_MAGIC_64; + mh->ncmds = 1; + mh->filetype = MH_DYLIB; + mh->sizeofcmds = 40; + struct load_command* lc = (struct load_command*)&badFile[32]; + lc->cmd = 1; + lc->cmdsize = 4000; // bad load command size + + // should detect the mh is bad and not crash or call the callback + dyld_get_image_versions(mh, ^(dyld_platform_t platform, uint32_t sdkVersion, uint32_t minOS) { + printf("bad binary: platform=%d, sdk=0x%08X, minOS-0x%08X\n", platform, sdkVersion, minOS); + }); + + + printf("[PASS] dyld_get_image_versions\n"); + return 0; +} + diff --git a/testing/test-cases/dyld_get_sdk_version.dtest/main.c b/testing/test-cases/dyld_get_sdk_version.dtest/main.c index efa9be1..c5032a0 100644 --- a/testing/test-cases/dyld_get_sdk_version.dtest/main.c +++ b/testing/test-cases/dyld_get_sdk_version.dtest/main.c @@ -26,8 +26,24 @@ int main() return 0; } - printf("[PASS] dyld_get_sdk_version\n"); +#if TARGET_OS_WATCH + uint32_t iosVersion = dyld_get_program_sdk_version(); + uint32_t watchOSVersion = dyld_get_program_sdk_watch_os_version(); + if (iosVersion != (watchOSVersion + 0x00070000)) { + printf("[FAIL] dyld_get_program_sdk_watch_os_version\n"); + return 0; + } +#endif +#if TARGET_OS_BRIDGE + uint32_t iosVersion = dyld_get_program_sdk_version(); + uint32_t bridgeOSVersion = dyld_get_program_sdk_bridge_os_version(); + if (bridgeOSVersion != (watchOSVersion + 0x00090000)) { + printf("[FAIL] dyld_get_program_sdk_watch_os_version\n"); + return 0; + } +#endif + printf("[PASS] dyld_get_sdk_version\n"); 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 41c5ccb..89943ed 100644 --- a/testing/test-cases/dyld_process_info.dtest/main.c +++ b/testing/test-cases/dyld_process_info.dtest/main.c @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -108,13 +111,14 @@ static bool hasCF(task_t task, bool launchedSuspended) bool valueSaysLaunchedSuspended = (stateInfo.dyldState == dyld_process_state_not_started); if ( valueSaysLaunchedSuspended != launchedSuspended ) { printf("[FAIL] dyld_process_info suspend state mismatch\n"); + _dyld_process_info_release(info); return false; } __block bool foundDyld = false; _dyld_process_info_for_each_image(info, ^(uint64_t machHeaderAddress, const uuid_t uuid, const char* path) { //fprintf(stderr, "0x%llX %s\n", machHeaderAddress, path); - if ( strstr(path, "/usr/lib/dyld") != NULL ) + if ( strstr(path, "/dyld") != NULL ) foundDyld = true; }); @@ -126,6 +130,7 @@ static bool hasCF(task_t task, bool launchedSuspended) if ( strstr(path, "/linksWithCF.exe") != NULL ) foundMain = true; }); + _dyld_process_info_release(info); return foundMain && foundDyld; } @@ -141,6 +146,37 @@ static bool hasCF(task_t task, bool launchedSuspended) return foundCF && foundDyld; } +static void checkForLeaks(const char *name) { + printf("[BEGIN] %s checkForLeaks\n", name); + pid_t child; + int stat_loc; + char buffer[PAGE_SIZE]; + (void)snprintf(&buffer[0], 128, "%d", getpid()); + + const char* argv[] = { "/usr/bin/leaks", buffer, NULL }; + int psResult = posix_spawn(&child, "/usr/bin/leaks", NULL, NULL, (char**)argv, environ); + if ( psResult != 0 ) { + printf("[FAIL] %s checkForLeaks posix_spawn failed, err=%d\n", name, psResult); + exit(0); + } + + (void)wait4(child, &stat_loc, 0, NULL); + ssize_t readBytes = 0; + if (WIFEXITED(stat_loc) == 0) { + printf("[FAIL] %s checkForLeaks leaks did not exit\n", name); + exit(0); + } + if (WEXITSTATUS(stat_loc) == 1) { + printf("[FAIL] %s checkForLeaks found leaks\n", name); + exit(0); + } + if (WEXITSTATUS(stat_loc) != 0) { + printf("[FAIL] %s checkForLeaks leaks errored out\n", name); + exit(0); + } + printf("[PASS] %s checkForLeaks\n", name); +} + int main(int argc, const char* argv[]) { @@ -202,5 +238,7 @@ int main(int argc, const char* argv[]) } printf("[PASS] dyld_process_info\n"); + checkForLeaks("dyld_process_info"); + return 0; } 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 d76d44f..ff1466f 100644 --- a/testing/test-cases/dyld_process_info_notify.dtest/main.c +++ b/testing/test-cases/dyld_process_info_notify.dtest/main.c @@ -135,12 +135,8 @@ static bool monitor(struct task_and_pid tp, bool disconnectEarly, bool attachLat dispatch_semaphore_signal(taskDone); } } - //fprintf(stderr, "unload=%d, 0x%012llX <%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> %s\n", - // unload, machHeader, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], - // uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], path); }, ^{ - //fprintf(stderr, "target exited\n"); gotTerminationNotice = true; dispatch_semaphore_signal(taskDone); }, @@ -239,6 +235,10 @@ 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]; + // This loop goes through 10 iterations + // i = 0..7 Should succeed + // i = 8 Should fail, but trigger a release that frees up a slot + // i = 9 Should succeed for (int i=0; i < 10; ++i) { kern_return_t kr; handles[i] = _dyld_process_info_notify(tp.task, serviceQueue, @@ -274,9 +274,28 @@ static void validateMaxNotifies(struct task_and_pid tp) dispatch_release(serviceQueue); } +static bool testSelfAttach(void) { + __block bool retval = false; + kern_return_t kr = KERN_SUCCESS; + dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + dyld_process_info_notify handle = _dyld_process_info_notify(mach_task_self(), serviceQueue, + ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) { + if ( strstr(path, "/libfoo.dylib") != NULL ) { + retval = true; + } + }, + ^{}, + &kr); + if ( handle == NULL ) { + fprintf(stderr, "_dyld_process_info_notify() returned NULL, result=%d\n", kr); + } + void* h = dlopen("./libfoo.dylib", 0); + dlclose(h); + return retval; +} + int main(int argc, const char* argv[]) { - printf("[BEGIN] dyld_process_info_notify\n"); if ( argc < 2 ) { printf("[FAIL] dyld_process_info_notify missing argument\n"); exit(0); @@ -287,6 +306,7 @@ int main(int argc, const char* argv[]) struct task_and_pid child; // test 1) launch test program suspended in same arch as this program + printf("[BEGIN] dyld_process_info_notify laucnh suspended (same arch)\n"); child = launchTest(testProgPath, "", false, true); if ( ! monitor(child, false, false) ) { printf("[FAIL] dyld_process_info_notify launch suspended missed some notifications\n"); @@ -294,19 +314,23 @@ int main(int argc, const char* argv[]) exit(0); } killTest(child); + printf("[PASS] dyld_process_info_notify laucnh suspended (same arch)\n"); // test 2) launch test program in same arch as this program where it sleeps itself + printf("[BEGIN] dyld_process_info_notify laucnh suspend-in-main (same arch)\n"); 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"); - killTest(child); + killTest(child); exit(0); } killTest(child); + printf("[PASS] dyld_process_info_notify laucnh suspend-in-main (same arch)\n"); #if __MAC_OS_X_VERSION_MIN_REQUIRED // test 3) launch test program suspended in opposite arch as this program + printf("[BEGIN] dyld_process_info_notify laucnh suspended (other arch)\n"); child = launchTest(testProgPath, "", true, true); if ( ! monitor(child, false, false) ) { printf("[FAIL] dyld_process_info_notify launch suspended other arch missed some notifications\n"); @@ -314,8 +338,10 @@ int main(int argc, const char* argv[]) exit(0); } killTest(child); + printf("[PASS] dyld_process_info_notify laucnh suspended (other arch)\n"); // test 4) launch test program in opposite arch as this program where it sleeps itself + printf("[BEGIN] dyld_process_info_notify laucnh suspend-in-main (other arch)\n"); 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"); @@ -323,9 +349,11 @@ int main(int argc, const char* argv[]) exit(0); } killTest(child); + printf("[PASS] dyld_process_info_notify laucnh suspend-in-main (other arch)\n"); #endif // test 5) launch test program where we disconnect from it after first dlopen + printf("[BEGIN] dyld_process_info_notify disconnect\n"); child = launchTest(testProgPath, "", false, true); if ( ! monitor(child, true, false) ) { printf("[FAIL] dyld_process_info_notify connect/disconnect missed some notifications\n"); @@ -333,8 +361,15 @@ int main(int argc, const char* argv[]) exit(0); } killTest(child); + printf("[PASS] dyld_process_info_notify disconnect\n"); + + // test 6) attempt to monitor the monitoring process + printf("[BEGIN] dyld_process_info_notify self-attach\n"); + if (! testSelfAttach() ) { + printf("[FAIL] dyld_process_info_notify self notification\n"); + } + printf("[PASS] dyld_process_info_notify self-attach\n"); - printf("[PASS] dyld_process_info_notify\n"); exit(0); }); diff --git a/testing/test-cases/dyld_version_spis.dtest/main.c b/testing/test-cases/dyld_version_spis.dtest/main.c new file mode 100644 index 0000000..f5c77ce --- /dev/null +++ b/testing/test-cases/dyld_version_spis.dtest/main.c @@ -0,0 +1,90 @@ + +// BUILD: $CC main.c -o $BUILD_DIR/versions.exe + +// RUN: ./versions.exe + +#include +#include +#include + +extern struct mach_header __dso_handle; + +int main() +{ + printf("[BEGIN] dyld_version_spi\n"); + dyld_platform_t active = dyld_get_active_platform(); + dyld_platform_t base = dyld_get_base_platform(active); + dyld_build_version_t absoluteMin = { .platform = base, .version = 0 }; + dyld_build_version_t absoluteMax = { .platform = base, .version = 0xffffffff }; + +#if TARGET_OS_OSX + if ( base != PLATFORM_MACOS ) { + printf("[FAIL] base.platform %u incorrect for macOS\n", base); + return 0; + } +#elif TARGET_OS_IOS + if ( base != PLATFORM_IOS ) { + printf("[FAIL] base.platform %u incorrect for iOS\n", base); + return 0; + } +#elif TARGET_OS_TV + if ( base != PLATFORM_TVOS ) { + printf("[FAIL] base.platform %u incorrect for tvOS\n", base); + return 0; + } +#elif TARGET_OS_BRIDGE + if ( base != PLATFORM_BRIDGEOS ) { + printf("[FAIL] base.platform %u incorrect for wacthOS\n", base); + return 0; + } +#elif TARGET_OS_WATCH + if ( base != PLATFORM_WATCHOS ) { + printf("[FAIL] base.platform %u incorrect for bridgeOS\n", base); + return 0; + } +#else + printf("[FAIL] Running on unknown platform\n"); + return 0; +#endif + +#if TARGET_OS_SIMULATOR + if (dyld_is_simulator_platform(active) != true) { + printf("[FAIL] active platform %u should be a simulator\n", active); + return 0; + } +#else + if (dyld_is_simulator_platform(active) == true) { + printf("[FAIL] active platform %u should not be a simulator\n", active); + return 0; + } +#endif + + if (dyld_is_simulator_platform(base) == true) { + printf("[FAIL] base platform %u should not be a simulator\n", base); + return 0; + } + + if (!dyld_sdk_at_least(&__dso_handle, absoluteMin)) { + printf("[FAIL] executable sdk version should not < 1.0.0\n"); + return 0; + } + + if (dyld_sdk_at_least(&__dso_handle, absoluteMax)) { + printf("[FAIL] executable sdk version should not > 65536.0.0\n"); + return 0; + } + + if (!dyld_minos_at_least(&__dso_handle, absoluteMin)) { + printf("[FAIL] executable min version should not < 1.0.0\n"); + return 0; + } + + if (dyld_minos_at_least(&__dso_handle, absoluteMax)) { + printf("[FAIL] executable min version should not > 65536.0.0\n"); + return 0; + } + + printf("[PASS] dyld_version_spi\n"); + return 0; +} + diff --git a/testing/test-cases/dylib-re-export-old-format.dtest/main.c b/testing/test-cases/dylib-re-export-old-format.dtest/main.c index 06e73a7..ea4d7f0 100644 --- a/testing/test-cases/dylib-re-export-old-format.dtest/main.c +++ b/testing/test-cases/dylib-re-export-old-format.dtest/main.c @@ -1,8 +1,8 @@ // BUILD_ONLY: MacOSX // BUILD_MIN_OS: 10.5 -// BUILD: $CC bar.c -dynamiclib -install_name $RUN_DIR/libbar.dylib -o $BUILD_DIR/libbar.dylib -// BUILD: $CC foo.c -dynamiclib $BUILD_DIR/libbar.dylib -sub_library libbar -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib -// BUILD: $CC main.c -o $BUILD_DIR/dylib-re-export.exe $BUILD_DIR/libfoo.dylib -L$BUILD_DIR +// BUILD: $CC bar.c -dynamiclib -install_name $RUN_DIR/libbar.dylib -o $BUILD_DIR/libbar.dylib -nostdlib -ldylib1.o +// BUILD: $CC foo.c -dynamiclib $BUILD_DIR/libbar.dylib -sub_library libbar -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib -nostdlib -ldylib1.o +// BUILD: $CC main.c -o $BUILD_DIR/dylib-re-export.exe $BUILD_DIR/libfoo.dylib -L$BUILD_DIR -nostdlib -lSystem -lcrt1.10.5.o // RUN: ./dylib-re-export.exe @@ -14,11 +14,11 @@ extern int bar(); int main() { - printf("[BEGIN] dylib-re-export\n"); + printf("[BEGIN] dylib-re-export-old-format\n"); if ( bar() == 42 ) - printf("[PASS] dylib-re-export\n"); + printf("[PASS] dylib-re-export-old-format\n"); else - printf("[FAIL] dylib-re-export, wrong value\n"); + printf("[FAIL] dylib-re-export-old-format, wrong value\n"); return 0; } diff --git a/testing/test-cases/dylib-static-link.dtest/present.c b/testing/test-cases/dylib-static-link.dtest/present.c index 4e7aa2d..2eb3393 100644 --- a/testing/test-cases/dylib-static-link.dtest/present.c +++ b/testing/test-cases/dylib-static-link.dtest/present.c @@ -1,8 +1,8 @@ -// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name libfoo.dylib +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib // BUILD: $CC present.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/dylib-static-present.exe -// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/libfoo2.dylib -install_name libfoomissing.dylib +// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/libfoo2.dylib -install_name $RUN_DIR/libfoomissing.dylib // BUILD: $CC missing.c $TEMP_DIR/libfoo2.dylib -o $BUILD_DIR/dylib-static-missing.exe // RUN: ./dylib-static-present.exe diff --git a/testing/test-cases/dylib-static-weak-link.dtest/present.c b/testing/test-cases/dylib-static-weak-link.dtest/present.c index 1f4a6e2..f51e384 100644 --- a/testing/test-cases/dylib-static-weak-link.dtest/present.c +++ b/testing/test-cases/dylib-static-weak-link.dtest/present.c @@ -1,6 +1,6 @@ -// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name libfoo.dylib +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib // BUILD: $CC present.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/dylib-static-weak-present.exe -// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/libfoo2.dylib -install_name libfoomissing.dylib +// BUILD: $CC foo.c -dynamiclib -o $TEMP_DIR/libfoo2.dylib -install_name $RUN_DIR/libfoomissing.dylib // BUILD: $CC missing.c $TEMP_DIR/libfoo2.dylib -o $BUILD_DIR/dylib-static-weak-missing.exe // RUN: ./dylib-static-weak-present.exe diff --git a/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/bar.c b/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/bar.c new file mode 100644 index 0000000..39ac49e --- /dev/null +++ b/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/bar.c @@ -0,0 +1,5 @@ +int bar() +{ + return VALUE; +} + diff --git a/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/foo.c b/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/foo.c new file mode 100644 index 0000000..b6dfea2 --- /dev/null +++ b/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/foo.c @@ -0,0 +1,5 @@ +int foo() +{ + return VALUE; +} + diff --git a/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/main.c b/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/main.c new file mode 100644 index 0000000..61346b8 --- /dev/null +++ b/testing/test-cases/env-DYLD_IMAGE_SUFFIX.dtest/main.c @@ -0,0 +1,80 @@ + +// BUILD: mkdir -p $BUILD_DIR/Bar.framework +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo.dylib -install_name $RUN_DIR/libfoo.dylib -DVALUE=1 +// BUILD: $CC foo.c -dynamiclib -o $BUILD_DIR/libfoo_other.dylib -install_name $RUN_DIR/libfoo.dylib -DVALUE=42 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/Bar.framework/Bar -install_name $RUN_DIR/Bar.framework/Bar -DVALUE=1 +// BUILD: $CC bar.c -dynamiclib -o $BUILD_DIR/Bar.framework/Bar_alt -install_name $RUN_DIR/Bar.framework/Bar -DVALUE=42 +// BUILD: $CC main.c -o $BUILD_DIR/main.exe $BUILD_DIR/libfoo.dylib $BUILD_DIR/Bar.framework/Bar +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main.exe +// BUILD: $CC main.c -o $BUILD_DIR/main-dynamic.exe -DRUN_DIR="$RUN_DIR" +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main-dynamic.exe + +// RUN: ./main.exe +// RUN: DYLD_IMAGE_SUFFIX=_other ./main.exe +// RUN: DYLD_IMAGE_SUFFIX=_alt ./main.exe +// RUN: DYLD_IMAGE_SUFFIX=_alt:_other ./main.exe +// RUN: ./main-dynamic.exe +// RUN: DYLD_IMAGE_SUFFIX=_other ./main-dynamic.exe +// RUN: DYLD_IMAGE_SUFFIX=_alt ./main-dynamic.exe +// RUN: DYLD_IMAGE_SUFFIX=_alt:_other ./main-dynamic.exe + + + + +#include +#include +#include +#include + +extern int foo(); +extern int bar(); + +typedef int (*IntProc)(); + +int main() +{ + const char* suffix = getenv("DYLD_IMAGE_SUFFIX"); + if ( suffix == NULL ) + suffix = ""; + printf("[BEGIN] env-DYLD_IMAGE_SUFFIX-%s\n", suffix); + + const int expectedFoo = (strstr(suffix, "_other") != NULL) ? 42 : 1; + const int expectedBar = (strstr(suffix, "_alt") != NULL) ? 42 : 1;; + +#ifdef RUN_DIR + void* fooHandle = dlopen(RUN_DIR "/libfoo.dylib", RTLD_LAZY); + if ( fooHandle == NULL ) { + printf("[FAIL] env-DYLD_IMAGE_SUFFIX-%s, libfoo.dylib could not be loaded, %s\n", suffix, dlerror()); + return 0; + } + void* barHandle = dlopen(RUN_DIR "/Bar.framework/Bar", RTLD_LAZY); + if ( barHandle == NULL ) { + printf("[FAIL] env-DYLD_IMAGE_SUFFIX-%s, Bar.framework/Bar could not be loaded, %s\n", suffix, dlerror()); + return 0; + } + IntProc fooProc = (IntProc)dlsym(fooHandle, "foo"); + if ( fooProc == NULL ) { + printf("[FAIL] env-DYLD_IMAGE_SUFFIX-%s, symbol 'foo' not found %s\n", suffix, dlerror()); + return 0; + } + IntProc barProc = (IntProc)dlsym(barHandle, "bar"); + if ( barProc == NULL ) { + printf("[FAIL] env-DYLD_IMAGE_SUFFIX-%s, symbol 'bar' not found %s\n", suffix, dlerror()); + return 0; + } + int fooValue = (*fooProc)(); + int barValue = (*barProc)(); +#else + int fooValue = foo(); + int barValue = bar(); +#endif + if ( fooValue != expectedFoo ) + printf("[FAIL] env-DYLD_IMAGE_SUFFIX-%s, foo()=%d expected=%d\n", suffix, fooValue, expectedFoo); + else if ( barValue != expectedBar ) + printf("[FAIL] env-DYLD_IMAGE_SUFFIX-%s, bar()=%d expected=%d\n", suffix, barValue, expectedBar); + else + printf("[PASS] env-DYLD_IMAGE_SUFFIX-%s\n", suffix); + + return 0; +} + diff --git a/testing/test-cases/env-DYLD_LIBRARY_PATH-cache.dtest/main.c b/testing/test-cases/env-DYLD_LIBRARY_PATH-cache.dtest/main.c new file mode 100644 index 0000000..37136fc --- /dev/null +++ b/testing/test-cases/env-DYLD_LIBRARY_PATH-cache.dtest/main.c @@ -0,0 +1,33 @@ + +// BUILD: mkdir -p $BUILD_DIR/override +// BUILD: $CC myzlib.c -dynamiclib -o $BUILD_DIR/override/libz.1.dylib -install_name /usr/lib/libz.1.dylib -compatibility_version 1.0 -framework CoreFoundation +// BUILD: $CC main.c -o $BUILD_DIR/main.exe -lz +// BUILD: $DYLD_ENV_VARS_ENABLE $BUILD_DIR/main.exe + +// RUN: ./main.exe +// RUN: DYLD_LIBRARY_PATH=$RUN_DIR/override/ ./main.exe + +#include +#include +#include +#include +#include + +// The test here is to override libz.1.dylib which is in the dyld cache with our own implementation. + +int main() +{ + bool expectMyDylib = (getenv("DYLD_LIBRARY_PATH") != NULL); + + printf("[BEGIN] env-DYLD_LIBRARY_PATH-cache, %s\n", expectMyDylib ? "my" : "os"); + + bool usingMyDylib = (strcmp(zlibVersion(), "my") == 0); + + if ( usingMyDylib == expectMyDylib ) + printf("[PASS] env-DYLD_LIBRARY_PATH-cache, %s\n", expectMyDylib ? "my" : "os"); + else + printf("[FAIL] env-DYLD_LIBRARY_PATH-cache, %s\n", expectMyDylib ? "my" : "os"); + + return 0; +} + diff --git a/testing/test-cases/env-DYLD_LIBRARY_PATH-cache.dtest/myzlib.c b/testing/test-cases/env-DYLD_LIBRARY_PATH-cache.dtest/myzlib.c new file mode 100644 index 0000000..5bd0c77 --- /dev/null +++ b/testing/test-cases/env-DYLD_LIBRARY_PATH-cache.dtest/myzlib.c @@ -0,0 +1,4 @@ +const char* zlibVersion() +{ + return "my"; +} diff --git a/testing/test-cases/interpose-malloc.dtest/interposer.c b/testing/test-cases/interpose-malloc.dtest/interposer.c index ceac979..a9e5c36 100644 --- a/testing/test-cases/interpose-malloc.dtest/interposer.c +++ b/testing/test-cases/interpose-malloc.dtest/interposer.c @@ -3,22 +3,18 @@ #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 + // bump ptr allocate twice the size and fills with '#' + char* result = malloc(size*2); + memset(result, '#', size*2); return result; } void myfree(void* p) { + free(p); } DYLD_INTERPOSE(mymalloc, malloc) diff --git a/testing/test-cases/no-shared-cache.dtest/main.c b/testing/test-cases/no-shared-cache.dtest/main.c new file mode 100644 index 0000000..9407740 --- /dev/null +++ b/testing/test-cases/no-shared-cache.dtest/main.c @@ -0,0 +1,31 @@ +// BUILD_ONLY: MacOSX + +// BUILD: $CC main.c -framework AppKit -o $BUILD_DIR/no_shared_cache.exe + +// RUN: DYLD_SHARED_REGION=avoid ./no_shared_cache.exe + +#include +#include +#include +#include +#include + + +// This program links with AppKit which in dyld3 mode stress tests building closures when there is no dyld shared cache + +int main() +{ + printf("[BEGIN] no-shared-cache\n"); + + size_t cacheLen; + const void* cacheStart = _dyld_get_shared_cache_range(&cacheLen); + + if ( cacheStart != NULL ) { + printf("[FAIL] no-shared-cache: _dyld_get_shared_cache_range() returned %p even though we are not using a dyld cache\n", cacheStart); + return 0; + } + + printf("[PASS] no-shared-cache\n"); + return 0; +} + diff --git a/testing/test-cases/operator-new.dtest/main.cxx b/testing/test-cases/operator-new.dtest/main.cxx index d985e8f..1773a03 100644 --- a/testing/test-cases/operator-new.dtest/main.cxx +++ b/testing/test-cases/operator-new.dtest/main.cxx @@ -13,24 +13,56 @@ // will turn around and call operator new in this main exectuable // -static void* ptr; +static void* myLastNewAllocation; +static void* myLastDelete; +// Note: this is not weak. That is specifically suppported void* operator new(size_t s) throw (std::bad_alloc) { - ptr = malloc(s); - return ptr; + myLastNewAllocation = malloc(s); + return myLastNewAllocation; +} + +struct Foo { + int bytes[10]; +}; + +// Note: this is weak and because it is in main executable should override OS +__attribute__((weak)) +void operator delete(void* p) throw() +{ + myLastDelete = p; + ::free(p); } int main() { printf("[BEGIN] operator-new\n"); + // test that OS's operator new[] redirects to my operator new + myLastNewAllocation = NULL; char* stuff = new char[24]; - if ( (void*)stuff == ptr ) - printf("[PASS] operator-new\n"); - else - printf("[FAIL] operator-new\n"); + if ( (void*)stuff != myLastNewAllocation ) { + printf("[FAIL] operator-new system array allocator not redirected through my operator new\n"); + return 0; + } + + // test that program uses my operator new + myLastNewAllocation = NULL; + Foo* foo = new Foo(); + if ( (void*)foo != myLastNewAllocation ) { + printf("[FAIL] operator-new allocation not redirected though my operator new\n"); + return 0; + } + + // + delete foo; + if ( (void*)foo != myLastDelete ) { + printf("[FAIL] operator-new deallocation not redirected though my operator delete\n"); + return 0; + } + printf("[PASS] operator-new\n"); return 0; } diff --git a/testing/test-cases/shared_cache_range.dtest/main.c b/testing/test-cases/shared_cache_range.dtest/main.c index 693de54..1e08b62 100644 --- a/testing/test-cases/shared_cache_range.dtest/main.c +++ b/testing/test-cases/shared_cache_range.dtest/main.c @@ -9,6 +9,18 @@ #include #include +#if __has_feature(ptrauth_calls) + #include +#endif + +static const void *stripPointer(const void *ptr) { +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + int main() { @@ -41,7 +53,7 @@ int main() const void* cacheEnd = (char*)cacheStart + cacheLen; // verify malloc is in shared cache - if ( ((void*)&malloc < cacheStart) || ((void*)&malloc > cacheEnd) ) { + if ( (stripPointer((void*)&malloc) < cacheStart) || (stripPointer((void*)&malloc) > cacheEnd) ) { printf("[FAIL] shared_cache_range: malloc is outside range of cache\n"); return 0; } diff --git a/testing/test-cases/symbol-resolver-basic.dtest/foo.c b/testing/test-cases/symbol-resolver-basic.dtest/foo.c new file mode 100644 index 0000000..0ff5b93 --- /dev/null +++ b/testing/test-cases/symbol-resolver-basic.dtest/foo.c @@ -0,0 +1,25 @@ +#include + + +static int foo_ten() +{ + return 10; +} + +static int foo_zero() +{ + return 0; +} + + +// This foo is a "resolver" function that return the actual address of "foo" +void* foo() +{ + __asm__(".symbol_resolver _foo"); // magic until we have compiler support + if ( getenv("TEN") != NULL ) + return &foo_ten; + else + return &foo_zero; +} + + diff --git a/testing/test-cases/symbol-resolver-basic.dtest/foo2.c b/testing/test-cases/symbol-resolver-basic.dtest/foo2.c new file mode 100644 index 0000000..e32024e --- /dev/null +++ b/testing/test-cases/symbol-resolver-basic.dtest/foo2.c @@ -0,0 +1,11 @@ + +#include + +extern int foo(); + +// test that calls to resolver based function in same dylib work +int fooPlusOne() +{ + return foo() + 1; +} + diff --git a/testing/test-cases/symbol-resolver-basic.dtest/main.c b/testing/test-cases/symbol-resolver-basic.dtest/main.c new file mode 100644 index 0000000..896221b --- /dev/null +++ b/testing/test-cases/symbol-resolver-basic.dtest/main.c @@ -0,0 +1,36 @@ + +// BUILD: $CC foo.c foo2.c -dynamiclib -install_name $RUN_DIR/libfoo.dylib -o $BUILD_DIR/libfoo.dylib +// BUILD: $CC main.c $BUILD_DIR/libfoo.dylib -o $BUILD_DIR/symbol-resolver.exe + +// RUN: ./symbol-resolver.exe +// RUN: TEN=1 ./symbol-resolver.exe + + +#include +#include + +extern int foo(); +extern int fooPlusOne(); + + +int main() +{ + if ( getenv("TEN") != NULL ) { + if ( foo() != 10 ) + printf("[FAIL] symbol-resolver-basic: foo() != 10\n"); + else if ( fooPlusOne() != 11 ) + printf("[FAIL] symbol-resolver-basic: fooPlusOne() != 11\n"); + else + printf("[PASS] symbol-resolver-basic\n"); + } + else { + if ( foo() != 0 ) + printf("[FAIL] symbol-resolver-basic: foo() != 0\n"); + else if ( fooPlusOne() != 1 ) + printf("[FAIL] symbol-resolver-basic: fooPlusOne() != 1\n"); + else + printf("[PASS] symbol-resolver-basic\n"); + } + + return 0; +} diff --git a/testing/test-cases/weak-coalesce.dtest/Makefile b/testing/test-cases/weak-coalesce.dtest/Makefile new file mode 100644 index 0000000..543a5be --- /dev/null +++ b/testing/test-cases/weak-coalesce.dtest/Makefile @@ -0,0 +1,52 @@ +## +# 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@ +## +TESTROOT = ../.. +include ${TESTROOT}/include/common.makefile + +all-check: all check + +check: + ./main + +all: main + +main: main.c libfoo1.dylib libbase.dylib + ${CC} ${CCFLAGS} -I${TESTROOT}/include -o main main.c libfoo1.dylib libbase.dylib + +libfoo1.dylib: foo1.c libfoo2.dylib libbase.dylib + ${CC} ${CCFLAGS} -I${TESTROOT}/include -dynamiclib -single_module -o libfoo1.dylib foo1.c libfoo2.dylib libbase.dylib + +libfoo2.dylib: foo2.c libfoo3.dylib libbase.dylib + ${CC} ${CCFLAGS} -I${TESTROOT}/include -dynamiclib -single_module -o libfoo2.dylib foo2.c libfoo3.dylib libbase.dylib + +libfoo3.dylib: foo3.c libbase.dylib + ${CC} ${CCFLAGS} -I${TESTROOT}/include -dynamiclib -single_module -o libfoo3.dylib foo3.c libbase.dylib -prebind -seg1addr 20000 + +libbase.dylib: base.c + ${CC} ${CCFLAGS} -I${TESTROOT}/include -dynamiclib -single_module -o libbase.dylib base.c -prebind -seg1addr 10000 + + + +clean: + ${RM} ${RMFLAGS} *~ main libfoo1.dylib libfoo2.dylib libfoo3.dylib libbase.dylib + diff --git a/testing/test-cases/weak-coalesce.dtest/base.c b/testing/test-cases/weak-coalesce.dtest/base.c new file mode 100644 index 0000000..b8a04c9 --- /dev/null +++ b/testing/test-cases/weak-coalesce.dtest/base.c @@ -0,0 +1,63 @@ +#include +#include + +#include "base.h" + +static bool wasProblem = false; +static const char* coal1Where = NULL; +static int* coal1Addr = NULL; +static int checkInCountCoal1 = 0; + +void baseVerifyCoal1(const char* where, int* addr) +{ + //fprintf(stderr, "baseVerifyCoal1(%s, %p)\n", where, addr); + ++checkInCountCoal1; + if ( coal1Where == NULL ) { + coal1Where = where; + coal1Addr = addr; + } + else { + if ( addr != coal1Addr ) { + fprintf(stderr, "coal1 resolved to different locations. %p in %s and %p in %s\n", + coal1Addr, coal1Where, addr, where); + wasProblem = true; + } + } +} + + +static const char* coal2Where = NULL; +static int* coal2Addr = NULL; +static int checkInCountCoal2 = 0; + +void baseVerifyCoal2(const char* where, int* addr) +{ + //fprintf(stderr, "baseVerifyCoal2(%s, %p)\n", where, addr); + ++checkInCountCoal2; + if ( coal2Where == NULL ) { + coal2Where = where; + coal2Addr = addr; + } + else { + if ( addr != coal2Addr ) { + fprintf(stderr, "coal2 resolved to different locations. %p in %s and %p in %s\n", + coal2Addr, coal2Where, addr, where); + wasProblem = true; + } + } +} + + + +void baseCheck() +{ + if ( wasProblem ) + printf("[FAIL] weak-coalesce: was problem\n"); + else if ( checkInCountCoal1 != 4 ) + printf("[FAIL] weak-coalesce: checkInCountCoal1 != 4\n"); + else if ( checkInCountCoal2 != 4 ) + printf("[FAIL] weak-coalesce: checkInCountCoal2 != 2\n"); + else + printf("[PASS] weak-coalesce\n"); +} + diff --git a/testing/test-cases/weak-coalesce.dtest/base.h b/testing/test-cases/weak-coalesce.dtest/base.h new file mode 100644 index 0000000..0c03cdd --- /dev/null +++ b/testing/test-cases/weak-coalesce.dtest/base.h @@ -0,0 +1,9 @@ + + +extern void baseCheck(); + +extern int coal1; +extern int coal2; + +extern void baseVerifyCoal1(const char* where, int* addr); +extern void baseVerifyCoal2(const char* where, int* addr); diff --git a/testing/test-cases/weak-coalesce.dtest/foo1.c b/testing/test-cases/weak-coalesce.dtest/foo1.c new file mode 100644 index 0000000..0707e0d --- /dev/null +++ b/testing/test-cases/weak-coalesce.dtest/foo1.c @@ -0,0 +1,39 @@ +/* + * 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@ + */ +#include +#include "base.h" + + + +int __attribute__((weak)) coal1 = 1; +int __attribute__((weak)) coal2 = 1; + + +static __attribute__((constructor)) void myinit() +{ + //fprintf(stderr, "myinit() in foo1.c\n"); + baseVerifyCoal1("in foo1", &coal1); + baseVerifyCoal2("in foo1", &coal2); +} + + diff --git a/testing/test-cases/weak-coalesce.dtest/foo2.c b/testing/test-cases/weak-coalesce.dtest/foo2.c new file mode 100644 index 0000000..74d884b --- /dev/null +++ b/testing/test-cases/weak-coalesce.dtest/foo2.c @@ -0,0 +1,14 @@ +#include + +#include "base.h" + +int coal1 = 2; // note: this is not weak and therefore should win +int __attribute__((weak)) coal2 = 2; + +static __attribute__((constructor)) +void myinit() +{ + //fprintf(stderr, "myinit() in foo1.c\n"); + baseVerifyCoal1("in foo2", &coal1); + baseVerifyCoal2("in foo2", &coal2); +} diff --git a/testing/test-cases/weak-coalesce.dtest/foo3.c b/testing/test-cases/weak-coalesce.dtest/foo3.c new file mode 100644 index 0000000..e086d5c --- /dev/null +++ b/testing/test-cases/weak-coalesce.dtest/foo3.c @@ -0,0 +1,15 @@ +#include + +#include "base.h" + +int __attribute__((weak)) coal1 = 3; +int __attribute__((weak)) coal2 = 2; + +static __attribute__((constructor)) +void myinit() +{ + //fprintf(stderr, "myinit() in foo1.c\n"); + baseVerifyCoal1("in foo3", &coal1); + baseVerifyCoal2("in foo3", &coal2); +} + diff --git a/testing/test-cases/weak-coalesce.dtest/main.c b/testing/test-cases/weak-coalesce.dtest/main.c new file mode 100644 index 0000000..4d09091 --- /dev/null +++ b/testing/test-cases/weak-coalesce.dtest/main.c @@ -0,0 +1,26 @@ + +// BUILD: $CC base.c -dynamiclib -install_name $RUN_DIR/libbase.dylib -o $BUILD_DIR/libbase.dylib +// BUILD: $CC foo3.c -dynamiclib -install_name $RUN_DIR/libfoo3.dylib -o $BUILD_DIR/libfoo3.dylib $BUILD_DIR/libbase.dylib +// BUILD: $CC foo2.c -dynamiclib -install_name $RUN_DIR/libfoo2.dylib -o $BUILD_DIR/libfoo2.dylib $BUILD_DIR/libfoo3.dylib $BUILD_DIR/libbase.dylib +// BUILD: $CC foo1.c -dynamiclib -install_name $RUN_DIR/libfoo1.dylib -o $BUILD_DIR/libfoo1.dylib $BUILD_DIR/libfoo2.dylib $BUILD_DIR/libbase.dylib +// BUILD: $CC main.c $BUILD_DIR/libfoo1.dylib $BUILD_DIR/libbase.dylib -o $BUILD_DIR/weak-coalesce.exe + +// RUN: ./weak-coalesce.exe + + +#include +#include + +#include "base.h" + +int main() +{ + printf("[BEGIN] weak-coalesce\n"); + + baseVerifyCoal1("in main", &coal1); + baseVerifyCoal2("in main", &coal2); + + baseCheck(); + return 0; +} +